[
  {
    "path": ".clang-tidy",
    "content": "---\n\n# Exceptions:\n# bugprone-branch-clone is too noisy in parsing code.\n# bugprone-easily-swappable-parameters is too noisy.\n# cert-dcl50-cpp is triggered by hz::string_sprintf()'s C style.\n# clang-analyzer-deadcode.DeadStores is too noisy.\n# clang-analyzer-cplusplus.NewDeleteLeaks doesn't understand Gtkmm memory management.\n# cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers is triggered by attribute database.\n# cppcoreguidelines-avoid-do-while is triggered in DBG macros.\n# cppcoreguidelines-owning-memory doesn't support deleting Gtkmm objects.\n# cppcoreguidelines-pro-type-cstyle-cast needed by GTK C casts.\n# cppcoreguidelines-pro-type-reinterpret-cast is needed by WinAPI-facing functions\n# cppcoreguidelines-pro-type-vararg is triggered by hz::string_sprintf().\n# cppcoreguidelines-pro-bounds-array-to-pointer-decay is triggered by va_start in hz::string_sprintf().\n# cppcoreguidelines-pro-bounds-pointer-arithmetic is useless (triggered by argv) until we have std::span.\n# misc-include-cleaner does not really work.\n# misc-no-recursion is too noisy.\n# modernize-raw-string-literal is very noisy.\n# modernize-use-trailing-return-type is contrary to our style.\n# performance-enum-size is too noisy.\n# readability-avoid-unconditional-preprocessor-if is triggered on platform-specific code\n# readability-convert-member-functions-to-static is triggered for many callbacks.\n# readability-function-cognitive-complexity needed by UI constructors.\n# readability-isolate-declaration is noisy.\n# readability-identifier-length is too noisy.\n\n\nChecks:          >\n  -abseil-*,\n  -altera-*,\n  -android-*,\n  -boost-*,\n  bugprone-*,\n  -bugprone-branch-clone,\n  -bugprone-easily-swappable-parameters,\n  cert-*,\n  -cert-dcl50-cpp,\n  clang-analyzer-*,\n  -clang-analyzer-deadcode.DeadStores,\n  -clang-analyzer-cplusplus.NewDeleteLeaks,\n  concurrency-*,\n  cppcoreguidelines-*,\n  -cppcoreguidelines-avoid-magic-numbers,\n  -cppcoreguidelines-avoid-do-while,\n  -cppcoreguidelines-owning-memory,\n  -cppcoreguidelines-pro-bounds-array-to-pointer-decay,\n  -cppcoreguidelines-pro-type-cstyle-cast,\n  -cppcoreguidelines-pro-type-reinterpret-cast,\n  -cppcoreguidelines-pro-type-vararg,\n  -cppcoreguidelines-pro-bounds-pointer-arithmetic,\n  -darwin-*,\n  -fuchsia-*,\n  google-*\n  hicpp-*,\n  -linuxkernel-*,\n  -llvm-*,\n  -llvmlibc-*,\n  misc-*,\n  -misc-include-cleaner,\n  -misc-no-recursion,\n  modernize-*,\n  -modernize-use-trailing-return-type,\n  -modernize-raw-string-literal,\n  -mpi-*,\n  -objc-*,\n  openmp-*,\n  performance-*,\n  -performance-enum-size,\n  portability-*,\n  readability-*,\n  -readability-avoid-unconditional-preprocessor-if,\n  -readability-convert-member-functions-to-static,\n  -readability-function-cognitive-complexity,\n  -readability-isolate-declaration,\n  -readability-magic-numbers,\n  -readability-identifier-length,\n  -zircon-*\n\nWarningsAsErrors: ''\nHeaderFilterRegex: ''\nFormatStyle:     none\n\nCheckOptions:\n  - key:             bugprone-exception-escape.CheckCapsOnly\n    value:           '1'\n  - key:             cppcoreguidelines-macro-usage.CheckCapsOnly\n    value:           '1'\n  - key:             cppcoreguidelines-owning-memory.LegacyResourceConsumers\n    value:           '::free;::realloc;::freopen;::fclose;Gtk::manage'\n  - key:             misc-assert-side-effect.AssertMacros\n    value:           assert,DBG_ASSERT\n  - key:             misc-assert-side-effect.CheckFunctionCalls\n    value:           '0'\n  - key:             misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic\n    value:          '1'\n  - key:             misc-include-cleaner.IgnoreHeaders\n    value:          'libdebug/*;local_glibmm.h;glib/*;gtk/*;gtkmm/*;glibmm/*'\n  - key:             performance-unnecessary-value-param.AllowedTypes\n    value:           'RefPtr;Ptr$'\n  - key:             readability-braces-around-statements.ShortStatementLines\n    value:           '2'\n  - key:             readability-implicit-bool-conversion.AllowPointerConditions\n    value:           '1'\n  - key:             readability-implicit-bool-cast.AllowConditionalPointerCasts\n    value:           '1'\n\n...\n\n"
  },
  {
    "path": ".gitattributes",
    "content": "\n# Make sure github recognizes header files as C++, not C.\n*.h linguist-language=C++\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: bug, enhancement\nassignees: ashaduri\n\n---\n\n**Version and Environment**\n - GSmartControl version: [Are you using the latest released version or git?]\n - OS: [e.g. openSUSE Linux 15.5]\n\n**Describe the Bug**\n[A clear and concise description of what the bug is].\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Screenshots**\n[If applicable, add screenshots to help explain your problem].\n\n**Additional Context**\n[Add any other context about the problem here (GTK+ version, etc.)]\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: enhancement\nassignees: ashaduri\n\n---\n\n**Version and Environment**\n - GSmartControl version: [Are you using the latest released version or git?]\n - OS: [e.g. openSUSE Linux 15.5]\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/other-issue.md",
    "content": "---\nname: Other issue\nabout: Issues other than bugs and enhancements\ntitle: ''\nlabels: ''\nassignees: ashaduri\n\n---\n\n**Version and Environment**\n - GSmartControl version: [Are you using the latest released version or git?]\n - OS: [e.g. openSUSE Linux 15.5]\n\n**Describe the Issue**\n[A clear and concise description of what the issue is].\n"
  },
  {
    "path": ".github/copilot-instructions.md",
    "content": "# GSmartControl – Copilot Instructions\n\nGSmartControl is a C++20 GTK3/Gtkmm GUI for inspecting hard-drive and SSD health via SMART data\n(wrapping `smartctl` from smartmontools). It supports Linux, Windows (MSYS2/MinGW), macOS, and BSDs.\n\n## Build\n\n```bash\nmkdir build && cd build\n\n# Standard Linux build (uses dev toolchain with warnings enabled)\ncmake .. -DCMAKE_BUILD_TYPE=RelWithDebInfo \\\n         -DCMAKE_TOOLCHAIN_FILE=../toolchains/linux-dev.cmake\n\n# Windows (MSYS2/MinGW64)\ncmake .. -G \"MSYS Makefiles\" \\\n         -DCMAKE_BUILD_TYPE=RelWithDebInfo \\\n         -DCMAKE_TOOLCHAIN_FILE=../toolchains/win64-mingw-msys2.cmake\n\n# Build on MSYS2/MinGW64 (from build dir)\ncmake --build . --target all -j 18\n```\n\nThe `configure-dev` script is a convenience wrapper: `./configure-dev -t <toolchain> -c <compiler> -b <build_type>`.\n\nKey CMake options:\n- `-DAPP_BUILD_TESTS=ON` – enable test targets\n- `-DAPP_BUILD_EXAMPLES=ON` – enable example targets\n- `-DAPP_COMPILER_ENABLE_WARNINGS=ON` – strict compiler warnings (set automatically by `linux-dev.cmake`)\n\n## Tests\n\nTests use **Catch2** (vendored in `dependencies/catch2/`). They are only built when `-DAPP_BUILD_TESTS=ON` is passed.\n\n```bash\n# Build and run all tests\ncmake .. -DAPP_BUILD_TESTS=ON\ncmake --build .\nctest -C RelWithDebInfo --rerun-failed --output-on-failure\n\n# Run a single test executable directly (from build dir)\n./test_all \"[smartctl_parser]\"        # Catch2 tag filter\n./test_all \"test name substring\"      # substring match\n```\n\nTest sources live in `src/applib/tests/` and `src/hz/tests/`; they are linked into `src/test_all/`.\n\n## Linting\n\nClang-tidy config is at `.clang-tidy` in the repo root. Run it via CMake or directly:\n\n```bash\nclang-tidy src/applib/storage_device.cpp -- -std=c++20 $(pkg-config --cflags gtkmm-3.0)\n```\n\n## Architecture\n\nThe codebase is split into libraries and one GUI executable:\n\n| Component | Location | Purpose |\n|---|---|---|\n| `applib` | `src/applib/` | Core logic: SMART parsing, device detection, command execution |\n| `hz` | `src/hz/` | General-purpose C++ utilities (strings, filesystem, error types) |\n| `rconfig` | `src/rconfig/` | Runtime config load/save with auto-save support |\n| `libdebug` | `src/libdebug/` | Debug logging with named channels |\n| `gui` | `src/gui/` | GTK/Gtkmm UI (windows, dialogs, Glade `.ui` files) |\n\n### Parsing pipeline\n\n`smartctl` output is parsed through a strategy pattern:\n- JSON parsers (`smartctl_json_ata_parser`, `smartctl_json_nvme_parser`, `smartctl_json_basic_parser`) target smartctl ≥ 7.3.\n- Text parsers (`smartctl_text_ata_parser`, `smartctl_text_basic_parser`) handle legacy output.\n- Parsed results are stored as `StorageProperty` objects in a `StoragePropertyRepository` on `StorageDevice`.\n\n### Device detection\n\n`StorageDetector` is an abstract interface; platform implementations are:\n- `storage_detector_linux.cpp` – scans `/dev/sd*`, `/dev/nvme*`, etc.\n- `storage_detector_win32.cpp` – WMI/registry enumeration\n- `storage_detector_other.cpp` – generic `/dev` scanning (BSD/macOS)\n\n### Async execution\n\n`AsyncCommandExecutor` wraps `CommandExecutor` (which shells out to `smartctl`) in a background thread. \nCompletion is signalled via **libsigc++** signals back to the GTK main loop. Never call GTK APIs from the worker thread;\nqueue them through signals or `Glib::signal_idle()`.\n\n### Error handling\n\nUse `hz::ExpectedValue<T, E>` (wrapping `tl::expected`) for recoverable errors instead of exceptions. \nEnum-based error types (`StorageDeviceError`, `SmartctlParserError`) are preferred over string errors.\n\n## Key Conventions\n\n**Naming:**\n- Classes: `PascalCase` (e.g., `StorageDevice`, `SmartctlParser`)\n- Methods and members: `snake_case`; private/member fields have a trailing `_` (e.g., `device_list_`)\n- GUI classes in `src/gui/` are prefixed `Gsc` (e.g., `GscMainWindow`)\n- Filenames: `snake_case.cpp` / `snake_case.h`\n\n**Headers:**\n- Use `#ifndef FILENAME_H` / `#define FILENAME_H` guards (not `#pragma once`)\n- Include order: standard library → third-party (GTK/Gtkmm) → project headers\n- All project headers are included relative to `src/` (the include root)\n\n**Modern C++20/23 patterns used throughout:**\n- `std::optional<T>` for nullable values\n- `hz::ExpectedValue<T, E>` for error-returning functions\n- `std::string_view` for non-owning string parameters\n- `fmt::format()` (vendored `fmt` library) for string formatting\n- Smart pointers exclusively; no raw owning pointers\n\n**Vendored dependencies** are in `dependencies/` and added via `add_subdirectory`.\nDo not modify them; prefer upgrading the whole vendored copy.\n\n**Translations:** UI strings are wrapped with `_()` (gettext). `.po` files live in `po/`.\nTranslations are not yet supported.\n\n**Platform guards:** Use `#ifdef CONFIG_KERNEL_FAMILY_WINDOWS`, \n`#ifdef CONFIG_KERNEL_LINUX`, etc.\n\n## Agentic Development\n\nPlace all plans and temporary files in `agent_workspace/`. This directory is ignored by Git.\n"
  },
  {
    "path": ".github/workflows/cmake-tier1.yml",
    "content": "name: Build (Tier 1 platforms)\n\non:\n  # Triggers the workflow on push or pull request events but only for the main branch\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\n\nenv:\n  # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)\n  BUILD_TYPE: RelWithDebInfo\n  SMARTMONTOOLS_INSTALLER: smartmontools-7.4-1.win32-setup.exe\n  SMARTMONTOOLS_URL: https://downloads.sourceforge.net/project/smartmontools/smartmontools/7.4/smartmontools-7.4-1.win32-setup.exe\n\n\njobs:\n\n  linux-ubuntu-24_04:\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Update Package Database\n      run: sudo apt-get update\n\n    - name: Install Dependencies\n      run: sudo apt-get install libgtkmm-3.0-dev gettext\n\n    - name: Create Build Directory\n      run: cmake -E make_directory build\n\n    - name: Configure CMake\n      shell: bash\n      working-directory: ${{github.workspace}}/build\n      env:\n        CC: gcc-14\n        CXX: g++-14\n      run: >\n        cmake $GITHUB_WORKSPACE\n        -DCMAKE_BUILD_TYPE=$BUILD_TYPE\n        -DCMAKE_TOOLCHAIN_FILE=\"$GITHUB_WORKSPACE/toolchains/linux-dev.cmake\"\n\n    - name: Build\n      shell: bash\n      working-directory: ${{github.workspace}}/build\n      run: cmake --build . --config $BUILD_TYPE\n\n    - name: Test\n      working-directory: ${{github.workspace}}/build\n      shell: bash\n      run: ctest -C $BUILD_TYPE --rerun-failed --output-on-failure\n\n    - name: Pack (cmake install and make binary package)\n      shell: bash\n      working-directory: ${{github.workspace}}/build\n      run: cpack -G TBZ2\n\n    - name: Upload artifacts\n      uses: actions/upload-artifact@v4\n      with:\n        name: Linux Packages\n        path: ${{github.workspace}}/build/gsmartcontrol-*-Linux*.*\n\n\n  linux-ubuntu-22_04:\n    # GCC 11\n    runs-on: ubuntu-22.04\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Update Package Database\n      run: sudo apt-get update\n\n    - name: Install Dependencies\n      run: sudo apt-get install libgtkmm-3.0-dev gettext\n\n    - name: Create Build Directory\n      run: cmake -E make_directory build\n\n    - name: Configure CMake\n      shell: bash\n      working-directory: ${{github.workspace}}/build\n      env:\n        CC: gcc-11\n        CXX: g++-11\n      run: >\n        cmake $GITHUB_WORKSPACE\n        -DCMAKE_BUILD_TYPE=$BUILD_TYPE\n        -DCMAKE_TOOLCHAIN_FILE=\"$GITHUB_WORKSPACE/toolchains/linux-dev.cmake\"\n\n    - name: Build\n      shell: bash\n      working-directory: ${{github.workspace}}/build\n      run: cmake --build . --config $BUILD_TYPE\n\n    - name: Test\n      working-directory: ${{github.workspace}}/build\n      shell: bash\n      run: ctest -C $BUILD_TYPE --rerun-failed --output-on-failure\n\n    - name: Pack (cmake install and make binary package)\n      shell: bash\n      working-directory: ${{github.workspace}}/build\n      run: cpack -G TBZ2\n\n#    - name: Upload artifacts\n#      uses: actions/upload-artifact@v4\n#      with:\n#        name: Linux Packages\n#        path: ${{github.workspace}}/build/gsmartcontrol-*-Linux*.*\n\n\n  windows-msys2:\n    runs-on: windows-latest\n    strategy:\n      matrix:\n        include: [\n          { msystem: MINGW64, arch: x86_64, platform: win64, urlpath: mingw64 },\n          # MSYS2 no longer supports for 32-bit Windows.\n          # { msystem: MINGW32, arch: i686, platform: win32, urlpath: mingw32 }\n        ]\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: msys2/setup-msys2@v2\n        # p7zip is needed to extract smartmontools\n        # fontconfig owns /etc/fonts\n        with:\n          msystem: ${{ matrix.msystem }}\n          update: true\n          install: >-\n            mingw-w64-${{ matrix.arch }}-gtkmm3\n            mingw-w64-${{ matrix.arch }}-pkg-config\n            mingw-w64-${{ matrix.arch }}-cmake\n            mingw-w64-${{ matrix.arch }}-gcc\n            mingw-w64-${{ matrix.arch }}-fontconfig\n            mingw-w64-${{ matrix.arch }}-nsis\n            make\n            p7zip\n\n      - name: Downgrade Packages to support Windows 7\n        # librsvg 2.59 requires Windows 8.1 or later (confirmed by dependency walker).\n        # We need to downgrade to 2.58.\n        # This contains gdk_pixbuf loader as well, and re-runs the loader cache update.\n        # https://repo.msys2.org/mingw/mingw32/mingw-w64-i686-librsvg-2.58.0-1-any.pkg.tar.zst\n        # https://repo.msys2.org/mingw/mingw64/mingw-w64-x86_64-librsvg-2.58.0-1-any.pkg.tar.zst\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}\n        run: |\n          wget https://repo.msys2.org/mingw/${{ matrix.urlpath }}/mingw-w64-${{ matrix.arch }}-librsvg-2.58.0-1-any.pkg.tar.zst\n          pacman -U --noconfirm mingw-w64-${{ matrix.arch }}-librsvg-2.58.0-1-any.pkg.tar.zst\n\n      - name: Download Package Requirements\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}\n        run: |\n          mkdir -p build/smartmontools\n          cd build/smartmontools\n          wget $SMARTMONTOOLS_URL\n\n      - name: Extract Package Requirements (64-bit)\n        if: ${{ matrix.msystem == 'MINGW64' }}\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}/build/smartmontools\n        run: >\n          7z -bb1 e $SMARTMONTOOLS_INSTALLER\n          bin/smartctl.exe\n          bin/smartctl-nc.exe\n          bin/update-smart-drivedb.ps1\n          bin/drivedb.h\n\n      - name: Extract Package Requirements (32-bit)\n        if: ${{ matrix.msystem == 'MINGW32' }}\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}/build/smartmontools\n        run: >\n          7z -bb1 e $SMARTMONTOOLS_INSTALLER\n          bin32/smartctl.exe\n          bin32/smartctl-nc.exe\n          bin/update-smart-drivedb.ps1\n          bin/drivedb.h\n\n      - name: List installed files\n        shell: msys2 {0}\n        run: |\n          ls -1R /mingw64\n          ls -1R /mingw32\n\n      - name: List Files in Workspace\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}\n        run: ls -1R .\n\n      - name: Configure CMake\n        shell: msys2 {0}\n        run: >\n          cmake -B build -S \"$GITHUB_WORKSPACE\"\n          -G \"MSYS Makefiles\"\n          -DCMAKE_BUILD_TYPE=$BUILD_TYPE\n          -DCMAKE_TOOLCHAIN_FILE=\"$GITHUB_WORKSPACE/toolchains/${{ matrix.platform }}-mingw-msys2.cmake\"\n\n      - name: Build\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}/build\n        run: cmake --build . --config $BUILD_TYPE\n\n      - name: Test\n        working-directory: ${{github.workspace}}/build\n        shell: msys2 {0}\n        run: ctest -C $BUILD_TYPE --rerun-failed --output-on-failure\n\n      - name: Package ZIP\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}/build\n        run: cpack -G ZIP\n\n      - name: Package NSIS (64-bit)\n        if: ${{ matrix.msystem == 'MINGW64' }}\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}/build\n        run: cpack -G NSIS64\n\n      - name: Package NSIS (32-bit)\n        if: ${{ matrix.msystem == 'MINGW32' }}\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}/build\n        run: cpack -G NSIS\n\n      - name: Upload artifacts\n        if: ${{ matrix.msystem == 'MINGW64' }}\n        uses: actions/upload-artifact@v4\n        with:\n          name: Windows 64-Bit Packages\n          path: ${{github.workspace}}/build/gsmartcontrol-*-win*.*\n\n      - name: Upload artifacts\n        if: ${{ matrix.msystem == 'MINGW32' }}\n        uses: actions/upload-artifact@v4\n        with:\n          name: Windows 32-Bit Packages\n          path: ${{github.workspace}}/build/gsmartcontrol-*-win*.*\n\n"
  },
  {
    "path": ".github/workflows/cmake-tier2-flaky.yml",
    "content": "name: Build (Tier 2 platforms with flaky builds)\n\n# FreeBSD build via vmactions/freebsd-vm is known to fail randomly,\n# so we don't want it to be triggered on every commit.\n\n# Manually triggered\non: [workflow_dispatch]\n\nenv:\n  # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)\n  BUILD_TYPE: RelWithDebInfo\n  CXX: clang++-17\n\njobs:\n\n  freebsd:\n    runs-on: ubuntu-latest\n#    env:\n#    variables to pass to VM\n#    MYTOKEN : ${{ secrets.MYTOKEN }}\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Build and test in FreeBSD\n      id: test\n      uses: vmactions/freebsd-vm@v1\n      with:\n        envs: 'BUILD_TYPE CXX'\n        usesh: true\n        sync: rsync\n        # release: \"14.0\"\n        prepare: >\n          pkg install -y cmake pkgconf gtkmm30 gettext libiconv llvm17\n\n        run: >\n          mkdir build && cd build\n\n          cmake ..\n          -DCMAKE_BUILD_TYPE=$BUILD_TYPE\n          -DAPP_BUILD_EXAMPLES=ON\n          -DAPP_BUILD_TESTS=ON\n          -DAPP_COMPILER_ENABLE_WARNINGS=ON\n\n          cmake --build . --config $BUILD_TYPE\n\n          ctest -C $BUILD_TYPE --rerun-failed --output-on-failure\n"
  },
  {
    "path": ".github/workflows/cmake-tier2.yml",
    "content": "name: Build (Tier 2 platforms)\n\non:\n  # Triggers the workflow on push or pull request events but only for the main branch\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\n\nenv:\n  # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)\n  BUILD_TYPE: RelWithDebInfo\n\n\njobs:\n\n  macos-homebrew:\n    runs-on: macos-latest\n    strategy:\n      matrix:\n        include: [\n          # We need AppleClang 16, but GitHub only has 14 for now.\n          # { compiler: apple-clang },\n          { compiler: clang }\n        ]\n\n    steps:\n    - uses: actions/checkout@v4\n\n    - name: Install Dependencies\n      run: brew install pkg-config gtkmm3 cmake\n\n    - name: Install Clang Compiler\n      if: ${{ matrix.compiler == 'clang' }}\n      run: brew install llvm@17\n\n    - name: Create Build Directory\n      run: cmake -E make_directory build\n\n    - name: Configure CMake (Apple Clang)\n      if: ${{ matrix.compiler == 'apple-clang' }}\n      shell: bash\n      working-directory: ${{github.workspace}}/build\n      run: >\n        cmake $GITHUB_WORKSPACE\n        -DCMAKE_BUILD_TYPE=$BUILD_TYPE\n        -DAPP_BUILD_EXAMPLES=ON\n        -DAPP_BUILD_TESTS=ON\n        -DAPP_COMPILER_ENABLE_WARNINGS=ON\n\n    - name: Configure CMake\n      if: ${{ matrix.compiler == 'clang' }}\n      shell: bash\n      working-directory: ${{github.workspace}}/build\n      run: >\n        cmake $GITHUB_WORKSPACE\n        -DCMAKE_BUILD_TYPE=$BUILD_TYPE\n        -DAPP_BUILD_EXAMPLES=ON\n        -DAPP_BUILD_TESTS=ON\n        -DAPP_COMPILER_ENABLE_WARNINGS=ON\n        -DCMAKE_CXX_COMPILER=$(brew --prefix llvm@17)/bin/clang++\n\n    - name: Build\n      shell: bash\n      working-directory: ${{github.workspace}}/build\n      run: cmake --build . --config $BUILD_TYPE\n\n    - name: Test\n      working-directory: ${{github.workspace}}/build\n      shell: bash\n      run: ctest -C $BUILD_TYPE --rerun-failed --output-on-failure\n\n"
  },
  {
    "path": ".github/workflows/codacy-analysis.yml",
    "content": "# This workflow uses actions that are not certified by GitHub.\n# They are provided by a third-party and are governed by\n# separate terms of service, privacy policy, and support\n# documentation.\n\n# This workflow checks out code, performs a Codacy security scan\n# and integrates the results with the\n# GitHub Advanced Security code scanning feature.  For more information on\n# the Codacy security scan action usage and parameters, see\n# https://github.com/codacy/codacy-analysis-cli-action.\n# For more information on Codacy Analysis CLI in general, see\n# https://github.com/codacy/codacy-analysis-cli.\n\nname: Codacy Security Scan\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [ main ]\n  schedule:\n    - cron: '27 22 * * 1'\n\npermissions:\n  contents: read\n\njobs:\n  codacy-security-scan:\n    permissions:\n      contents: read # for actions/checkout to fetch code\n      security-events: write # for github/codeql-action/upload-sarif to upload SARIF results\n      actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status\n    name: Codacy Security Scan\n    runs-on: ubuntu-24.04\n    steps:\n      # Checkout the repository to the GitHub Actions runner\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis\n      - name: Run Codacy Analysis CLI\n        uses: codacy/codacy-analysis-cli-action@d840f886c4bd4edc059706d09c6a1586111c540b\n        with:\n          # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository\n          # You can also omit the token and run the tools that support default configurations\n          project-token: ${{ secrets.CODACY_PROJECT_TOKEN }}\n          verbose: true\n          output: results.sarif\n          format: sarif\n          # Adjust severity of non-security issues\n          gh-code-scanning-compat: true\n          # Force 0 exit code to allow SARIF file generation\n          # This will handover control about PR rejection to the GitHub side\n          max-allowed-issues: 2147483647\n\n      # Upload the SARIF file generated in the previous step\n      - name: Upload SARIF results file\n        uses: github/codeql-action/upload-sarif@v4\n        with:\n          sarif_file: results.sarif\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: \"CodeQL\"\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [ main ]\n  schedule:\n    - cron: '24 10 * * 6'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-24.04\n    permissions:\n      # required for all workflows\n      security-events: write\n\n      # required to fetch internal or private CodeQL packs\n      #packages: read\n\n      # only required for workflows in private repositories\n      #actions: read\n      #contents: read\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'cpp' ]\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]\n        # Learn more about CodeQL language support at https://git.io/codeql-language-support\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v4\n\n    - name: Update Package Database\n      run: sudo apt-get update\n\n    - name: Install Dependencies\n      run: sudo apt-get install libgtkmm-3.0-dev gettext\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v4\n      with:\n        languages: ${{ matrix.language }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n        # queries: ./path/to/local/query, your-org/your-repo/queries@main\n\n    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n    # If this step fails, then you should remove it and run the build manually (see below)\n    - name: Autobuild\n      env:\n        CC: gcc-13\n        CXX: g++-13\n      uses: github/codeql-action/autobuild@v4\n\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 https://git.io/JvXDl\n\n    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines\n    #    and modify them (or add more) to build your code if your project\n    #    uses a compiled language\n\n    #- run: |\n    #   make bootstrap\n    #   make release\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v4\n"
  },
  {
    "path": ".github/workflows/devskim-analysis.yml",
    "content": "# This workflow uses actions that are not certified by GitHub.\n# They are provided by a third-party and are governed by\n# separate terms of service, privacy policy, and support\n# documentation.\n\nname: DevSkim\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n  schedule:\n    - cron: '25 22 * * 0'\n\njobs:\n  lint:\n    name: DevSkim\n    runs-on: ubuntu-24.04\n    permissions:\n      actions: read\n      contents: read\n      security-events: write\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n\n      - name: Run DevSkim scanner\n        uses: microsoft/DevSkim-Action@v1\n        with:\n          ignore-globs: dummy_to_avoid_action_error,**/.git/**,*.txt,**/dependencies/**,env_tools.h,app_gtkmm_tools.cpp\n        \n      - name: Upload DevSkim scan results to GitHub Security tab\n        uses: github/codeql-action/upload-sarif@v3\n        with:\n          sarif_file: devskim-results.sarif\n"
  },
  {
    "path": ".github/workflows/windows-release.yml",
    "content": "name: Windows Release\n\non:\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\n\nenv:\n  # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)\n  BUILD_TYPE: Release\n  SMARTMONTOOLS_INSTALLER: smartmontools-7.4-1.win32-setup.exe\n  SMARTMONTOOLS_URL: https://downloads.sourceforge.net/project/smartmontools/smartmontools/7.4/smartmontools-7.4-1.win32-setup.exe\n\n\njobs:\n\n  windows-msys2:\n    runs-on: windows-latest\n    strategy:\n      matrix:\n        include: [\n          { msystem: MINGW64, arch: x86_64, platform: win64, urlpath: mingw64 },\n          { msystem: MINGW32, arch: i686, platform: win32, urlpath: mingw32 }\n        ]\n\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: msys2/setup-msys2@v2\n        # p7zip is needed to extract smartmontools\n        # fontconfig owns /etc/fonts\n        with:\n          msystem: ${{ matrix.msystem }}\n          update: true\n          install: >-\n            mingw-w64-${{ matrix.arch }}-gtkmm3\n            mingw-w64-${{ matrix.arch }}-pkg-config\n            mingw-w64-${{ matrix.arch }}-cmake\n            mingw-w64-${{ matrix.arch }}-gcc\n            mingw-w64-${{ matrix.arch }}-fontconfig\n            make\n            p7zip\n\n      - name: Downgrade Packages to support Windows 7\n        # librsvg 2.59 requires Windows 8.1 or later (confirmed by dependency walker).\n        # We need to downgrade to 2.58.\n        # This contains gdk_pixbuf loader as well, and re-runs the loader cache update.\n        # https://repo.msys2.org/mingw/mingw32/mingw-w64-i686-librsvg-2.58.0-1-any.pkg.tar.zst\n        # https://repo.msys2.org/mingw/mingw64/mingw-w64-x86_64-librsvg-2.58.0-1-any.pkg.tar.zst\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}\n        run: |\n          wget https://repo.msys2.org/mingw/${{ matrix.urlpath }}/mingw-w64-${{ matrix.arch }}-librsvg-2.58.0-1-any.pkg.tar.zst\n          pacman -U --noconfirm mingw-w64-${{ matrix.arch }}-librsvg-2.58.0-1-any.pkg.tar.zst\n\n      - name: Download Package Requirements\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}\n        run: |\n          mkdir -p build/smartmontools\n          cd build/smartmontools\n          wget $SMARTMONTOOLS_URL\n\n      - name: Extract Package Requirements (64-bit)\n        if: ${{ matrix.msystem == 'MINGW64' }}\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}/build/smartmontools\n        run: >\n          7z -bb1 e $SMARTMONTOOLS_INSTALLER\n          bin/smartctl.exe\n          bin/smartctl-nc.exe\n          bin/update-smart-drivedb.ps1\n          bin/drivedb.h\n\n      - name: Extract Package Requirements (32-bit)\n        if: ${{ matrix.msystem == 'MINGW32' }}\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}/build/smartmontools\n        run: >\n          7z -bb1 e $SMARTMONTOOLS_INSTALLER\n          bin32/smartctl.exe\n          bin32/smartctl-nc.exe\n          bin/update-smart-drivedb.ps1\n          bin/drivedb.h\n\n      - name: List installed files\n        shell: msys2 {0}\n        run: |\n          ls -1R /mingw64\n          ls -1R /mingw32\n\n      - name: Configure CMake\n        shell: msys2 {0}\n        run: >\n          cmake -B build -S \"$GITHUB_WORKSPACE\"\n          -G \"MSYS Makefiles\"\n          -DCMAKE_BUILD_TYPE=$BUILD_TYPE\n          -DCMAKE_TOOLCHAIN_FILE=\"$GITHUB_WORKSPACE/toolchains/${{ matrix.platform }}-mingw-msys2.cmake\"\n\n      - name: Build\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}/build\n        run: cmake --build . --config $BUILD_TYPE\n\n      - name: Test\n        working-directory: ${{github.workspace}}/build\n        shell: msys2 {0}\n        run: ctest -C $BUILD_TYPE --rerun-failed --output-on-failure\n\n      - name: Package ZIP\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}/build\n        run: cpack -G ZIP\n\n      - name: Package NSIS (64-bit)\n        if: ${{ matrix.msystem == 'MINGW64' }}\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}/build\n        run: cpack -G NSIS64\n\n      - name: Package NSIS (32-bit)\n        if: ${{ matrix.msystem == 'MINGW32' }}\n        shell: msys2 {0}\n        working-directory: ${{github.workspace}}/build\n        run: cpack -G NSIS\n\n      - name: Upload artifacts\n        if: ${{ matrix.msystem == 'MINGW64' }}\n        uses: actions/upload-artifact@v4\n        with:\n          name: Windows 64-Bit Packages\n          path: ${{github.workspace}}/build/gsmartcontrol-*-win*.*\n\n      - name: Upload artifacts\n        if: ${{ matrix.msystem == 'MINGW32' }}\n        uses: actions/upload-artifact@v4\n        with:\n          name: Windows 32-Bit Packages\n          path: ${{github.workspace}}/build/gsmartcontrol-*-win*.*\n\n"
  },
  {
    "path": ".gitignore",
    "content": "\n# Builds\n/0*\n/win32*\n/win64*\n/cmake-build-*\n/build*\n/_codeql_build_dir\n\n# Non-project files\n/TODO\n\n# Doxygen generated\n/doxygen_doc\n\n# OS-generated files\n.DS_Store\n.DS_Store?\n._*\n.Spotlight-V100\n.Trashes\nIcon?\nehthumbs.db\nThumbs.db\n\n# IDE\n/.vs\n/*.pcs\n/.idea\n/CMakeLists.txt.user\n\n# Agents\n/.codebuddy\n\n# Translations\n/po/boldquot.sed\n/po/en@boldquot.header\n/po/en@quot.header\n/po/insert-header.sin\n/po/Makevars.template\n/po/quot.sed\n/po/remove-potcdate.sin\n/po/Rules-quot\n/po/gsmartcontrol.pot\n/po/*.gmo\n\n"
  },
  {
    "path": ".obs/workflows.yml",
    "content": "\n# This file describes OBS workflows:\n# https://openbuildservice.org/help/manuals/obs-user-guide/cha.obs.scm_ci_workflow_integration.html\n\n# Workflow for pull requests\n#build_pull_request_workflow:\n#  steps:\n#    - branch_package:\n#        source_project: home:alex_sh:gsmartcontrol:github_ci:main\n#        source_package: gsmartcontrol\n#        target_project: home:alex_sh:gsmartcontrol:github_ci:main\n#\n#    - configure_repositories:\n#        project: home:alex_sh:gsmartcontrol:github_ci:main\n#        repositories:\n#          - name: openSUSE_Tumbleweed\n#            paths:\n#              - target_project: openSUSE:Factory\n#                target_repository: snapshot\n#            architectures:\n#              - x86_64\n#              - i586\n#\n#    - set_flags:\n#        flags:\n#          - type: publish\n#            status: disable\n#            project: home:alex_sh:gsmartcontrol:github_ci:main\n#\n#  filters:\n#    event: pull_request\n#    branches:\n#      only:\n#        - main\n\n\n# Workflow for pushes\n\n# Note: This adds new repositories to the project if they do not exist.\nbuild_main_workflow:\n  steps:\n    - configure_repositories:\n        project: home:alex_sh:gsmartcontrol:github_ci:main\n        repositories:\n\n          - name: openSUSE_Tumbleweed\n            paths:\n              - target_project: openSUSE:Factory\n                target_repository: snapshot\n            architectures:\n              - x86_64\n\n          - name: openSUSE_Slowroll\n            paths:\n              - target_project: openSUSE:Slowroll\n                target_repository: standard\n            architectures:\n              - x86_64\n\n          - name: openSUSE_Leap_15.6\n            paths:\n              - target_project: openSUSE:Leap:15.6\n                target_repository: standard\n            architectures:\n              - x86_64\n\n          - name: openSUSE_Leap_15.5\n            paths:\n              - target_project: devel:gcc\n                target_repository: openSUSE_Leap_15.5\n              - target_project: openSUSE:Leap:15.5\n                target_repository: standard\n            architectures:\n              - x86_64\n\n          - name: openSUSE_Factory_ARM\n            paths:\n              - target_project: openSUSE:Factory:ARM\n                target_repository: standard\n            architectures:\n              - aarch64\n              - armv7l\n\n          - name: SUSE_SLE-15-SP6\n            paths:\n              - target_project: devel:gcc\n                target_repository: SLE-15\n              - target_project: SUSE:SLE-15-SP6:GA\n                target_repository: standard\n            architectures:\n              - x86_64\n\n          - name: Ubuntu_24.04\n            paths:\n              - target_project: openSUSE:Tools\n                target_repository: xUbuntu_24.04\n              - target_project: Ubuntu:24.04\n                target_repository: universe\n            architectures:\n              - x86_64\n\n          - name: Ubuntu_23.10\n            paths:\n              - target_project: openSUSE:Tools\n                target_repository: xUbuntu_23.10\n              - target_project: Ubuntu:23.10\n                target_repository: universe\n            architectures:\n              - x86_64\n\n          - name: Ubuntu_22.04\n            paths:\n              - target_project: openSUSE:Tools\n                target_repository: xUbuntu_22.04\n              - target_project: Ubuntu:22.04\n                target_repository: universe\n            architectures:\n              - x86_64\n\n          - name: Fedora_Rawhide\n            paths:\n              - target_project: openSUSE:Tools\n                target_repository: Fedora_Rawhide\n              - target_project: Fedora:Rawhide\n                target_repository: standard\n            architectures:\n              - x86_64\n\n          - name: Fedora_41\n            paths:\n              - target_project: openSUSE:Tools\n                target_repository: Fedora_41\n              - target_project: Fedora:41\n                target_repository: standard\n            architectures:\n              - x86_64\n              - aarch64\n\n          - name: Fedora_40\n            paths:\n              - target_project: openSUSE:Tools\n                target_repository: Fedora_40\n              - target_project: Fedora:40\n                target_repository: standard\n            architectures:\n              - x86_64\n              - aarch64\n\n          - name: Fedora_39\n            paths:\n              - target_project: openSUSE:Tools\n                target_repository: Fedora_39\n              - target_project: Fedora:39\n                target_repository: standard\n            architectures:\n              - x86_64\n              - aarch64\n\n          - name: Debian_Unstable\n            paths:\n              - target_project: openSUSE:Tools\n                target_repository: Debian_Unstable\n              - target_project: Debian:Next\n                target_repository: standard\n            architectures:\n              - x86_64\n\n          - name: Debian_Testing\n            paths:\n              - target_project: openSUSE:Tools\n                target_repository: Debian_Testing\n              - target_project: Debian:Testing\n                target_repository: update\n            architectures:\n              - x86_64\n              - i586\n\n    - set_flags:\n        flags:\n          - type: publish\n            status: enable\n            project: home:alex_sh:gsmartcontrol:github_ci:main\n          - type: debuginfo\n            status: enable\n            project: home:alex_sh:gsmartcontrol:github_ci:main\n\n    - rebuild_package:\n        project: home:alex_sh:gsmartcontrol:github_ci:main\n        package: gsmartcontrol\n\n  filters:\n    event: push\n    branches:\n      only:\n        - main\n\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n# 3.14 for proper object library support (wrt linking)\ncmake_minimum_required(VERSION 3.14)\n# Use new policy for option() to supress warnings.\nset(CMAKE_POLICY_DEFAULT_CMP0077 NEW)\n\n\ninclude(${CMAKE_SOURCE_DIR}/version.txt)\n\nproject(gsmartcontrol\n\tVERSION ${CMAKE_PROJECT_VERSION}\n\tDESCRIPTION \"Hard Disk Drive and SSD Health Inspection Tool\"\n\tHOMEPAGE_URL \"https://gsmartcontrol.shaduri.dev\"\n\tLANGUAGES CXX\n)\n\n\n# Provide DATADIR, etc.\ninclude(GNUInstallDirs)\n\nmessage(STATUS \"Using toolchain file: ${CMAKE_TOOLCHAIN_FILE}\")\n\ninclude(src/build_config/compiler_options.cmake)\n\n\n# Disable RPATH manipulation, we don't have shared libraries\nset(CMAKE_SKIP_BUILD_RPATH TRUE)\nset(CMAKE_SKIP_INSTALL_RPATH TRUE)\n\n# Write the binaries to project root (avoids conflicts with manifests, also it's more convenient this way)\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"${PROJECT_BINARY_DIR}\")\n\n\n# User-controlled build options\noption(APP_BUILD_EXAMPLES \"Build examples\" OFF)\noption(APP_BUILD_TESTS \"Build tests\" OFF)\n\n\n# Install documentation\nset(DOC_FILES\n\tLICENSE.LGPL3.txt\n\tLICENSE.txt\n\tNEWS.txt\n\tREADME.md\n)\nif (WIN32)\n\tinstall(FILES ${DOC_FILES} DESTINATION doc/)\n\tinstall(DIRECTORY docs DESTINATION doc/\n\t\tPATTERN \"*.yml\" EXCLUDE\n\t\tPATTERN \"CNAME\" EXCLUDE\n\t)\nelse()\n\tinstall(FILES ${DOC_FILES} TYPE DOC)\n\tinstall(DIRECTORY docs TYPE DOC\n\t\tPATTERN \"*.yml\" EXCLUDE\n\t\tPATTERN \"CNAME\" EXCLUDE\n\t)\nendif()\n\n\n# CTest support\ninclude(CTest)\n#include(${CMAKE_SOURCE_DIR}/dependencies/catch2/Catch2/extras/Catch.cmake)\ninclude(${CMAKE_SOURCE_DIR}/dependencies/catch2/Catch2/contrib/Catch.cmake)\n\n\nadd_subdirectory(contrib)\nadd_subdirectory(data)\nadd_subdirectory(dependencies)\nadd_subdirectory(packaging)\nadd_subdirectory(src)\n\n\n# Packaging support\ninclude(packaging/cpack_options.cmake)\n\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\nashaduri@gmail.com.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "Doxyfile",
    "content": "# Doxyfile 1.8.14\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project.\n#\n# All text after a double hash (##) is considered a comment and is placed in\n# front of the TAG it is preceding.\n#\n# All text after a single hash (#) is considered a comment and will be ignored.\n# The format is:\n# TAG = value [value, ...]\n# For lists, items can also be appended using:\n# TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\\\" \\\").\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the config file\n# that follow. The default is UTF-8 which is also the encoding used for all text\n# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv\n# built into libc) for the transcoding. See\n# https://www.gnu.org/software/libiconv/ for the list of possible encodings.\n# The default value is: UTF-8.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by\n# double-quotes, unless you are using Doxywizard) that should identify the\n# project for which the documentation is generated. This name is used in the\n# title of most generated pages and in a few other places.\n# The default value is: My Project.\n\nPROJECT_NAME           = \"GSmartControl\"\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. This\n# could be handy for archiving the generated documentation or if some version\n# control system is used.\n\nPROJECT_NUMBER         =\n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description\n# for a project that appears at the top of each page and should give viewer a\n# quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          = \"Hard disk drive and SSD health inspection tool\"\n\n# With the PROJECT_LOGO tag one can specify a logo or an icon that is included\n# in the documentation. The maximum height of the logo should not exceed 55\n# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy\n# the logo to the output directory.\n\nPROJECT_LOGO           =\n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path\n# into which the generated documentation will be written. If a relative path is\n# entered, it will be relative to the location where doxygen was started. If\n# left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       = doxygen_doc\n\n# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-\n# directories (in 2 levels) under the output directory of each output format and\n# will distribute the generated files over these directories. Enabling this\n# option can be useful when feeding doxygen a huge amount of source files, where\n# putting all generated files in the same directory would otherwise causes\n# performance problems for the file system.\n# The default value is: NO.\n\nCREATE_SUBDIRS         = NO\n\n# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII\n# characters to appear in the names of generated files. If set to NO, non-ASCII\n# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode\n# U+3044.\n# The default value is: NO.\n\nALLOW_UNICODE_NAMES    = NO\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all constant output in the proper language.\n# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,\n# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),\n# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,\n# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),\n# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,\n# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,\n# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,\n# Ukrainian and Vietnamese.\n# The default value is: English.\n\nOUTPUT_LANGUAGE        = English\n\n# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member\n# descriptions after the members that are listed in the file and class\n# documentation (similar to Javadoc). Set to NO to disable this.\n# The default value is: YES.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief\n# description of a member or function before the detailed description\n#\n# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the\n# brief descriptions will be completely suppressed.\n# The default value is: YES.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator that is\n# used to form the text in various listings. Each string in this list, if found\n# as the leading text of the brief description, will be stripped from the text\n# and the result, after processing the whole list, is used as the annotated\n# text. Otherwise, the brief description is used as-is. If left blank, the\n# following values are used ($name is automatically replaced with the name of\n# the entity):The $name class, The $name widget, The $name file, is, provides,\n# specifies, contains, represents, a, an and the.\n\nABBREVIATE_BRIEF       = \"The $name class\" \\\n                         \"The $name widget\" \\\n                         \"The $name file\" \\\n                         is \\\n                         provides \\\n                         specifies \\\n                         contains \\\n                         represents \\\n                         a \\\n                         an \\\n                         the\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then\n# doxygen will generate a detailed section even if there is only a brief\n# description.\n# The default value is: NO.\n\nALWAYS_DETAILED_SEC    = NO\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all\n# inherited members of a class in the documentation of that class as if those\n# members were ordinary class members. Constructors, destructors and assignment\n# operators of the base classes will not be shown.\n# The default value is: NO.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path\n# before files name in the file list and in the header files. If set to NO the\n# shortest path that makes the file name unique will be used\n# The default value is: YES.\n\nFULL_PATH_NAMES        = YES\n\n# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.\n# Stripping is only done if one of the specified strings matches the left-hand\n# part of the path. The tag can be used to show relative paths in the file list.\n# If left blank the directory from which doxygen is run is used as the path to\n# strip.\n#\n# Note that you can specify absolute paths here, but also relative paths, which\n# will be relative from the directory where doxygen is started.\n# This tag requires that the tag FULL_PATH_NAMES is set to YES.\n\nSTRIP_FROM_PATH        =\n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the\n# path mentioned in the documentation of a class, which tells the reader which\n# header file to include in order to use a class. If left blank only the name of\n# the header file containing the class definition is used. Otherwise one should\n# specify the list of include paths that are normally passed to the compiler\n# using the -I flag.\n\nSTRIP_FROM_INC_PATH    =\n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but\n# less readable) file names. This can be useful is your file systems doesn't\n# support long names like on DOS, Mac, or CD-ROM.\n# The default value is: NO.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the\n# first line (until the first dot) of a Javadoc-style comment as the brief\n# description. If set to NO, the Javadoc-style will behave just like regular Qt-\n# style comments (thus requiring an explicit @brief command for a brief\n# description.)\n# The default value is: NO.\n\nJAVADOC_AUTOBRIEF      = YES\n\n# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first\n# line (until the first dot) of a Qt-style comment as the brief description. If\n# set to NO, the Qt-style will behave just like regular Qt-style comments (thus\n# requiring an explicit \\brief command for a brief description.)\n# The default value is: NO.\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a\n# multi-line C++ special comment block (i.e. a block of //! or /// comments) as\n# a brief description. This used to be the default behavior. The new default is\n# to treat a multi-line C++ comment block as a detailed description. Set this\n# tag to YES if you prefer the old behavior instead.\n#\n# Note that setting this tag to YES also means that rational rose comments are\n# not recognized any more.\n# The default value is: NO.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the\n# documentation from any documented member that it re-implements.\n# The default value is: YES.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new\n# page for each member. If set to NO, the documentation of a member will be part\n# of the file/class/namespace that contains it.\n# The default value is: NO.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen\n# uses this value to replace tabs by spaces in code fragments.\n# Minimum value: 1, maximum value: 16, default value: 4.\n\nTAB_SIZE               = 4\n\n# This tag can be used to specify a number of aliases that act as commands in\n# the documentation. An alias has the form:\n# name=value\n# For example adding\n# \"sideeffect=@par Side Effects:\\n\"\n# will allow you to put the command \\sideeffect (or @sideeffect) in the\n# documentation, which will result in a user-defined paragraph with heading\n# \"Side Effects:\". You can put \\n's in the value part of an alias to insert\n# newlines (in the resulting output). You can put ^^ in the value part of an\n# alias to insert a newline as if a physical newline was in the original file.\n\nALIASES                =\n\n# This tag can be used to specify a number of word-keyword mappings (TCL only).\n# A mapping has the form \"name=value\". For example adding \"class=itcl::class\"\n# will allow you to use the command class in the itcl::class meaning.\n\nTCL_SUBST              =\n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources\n# only. Doxygen will then generate output that is more tailored for C. For\n# instance, some of the names that are used will be different. The list of all\n# members will be omitted, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_FOR_C  = NO\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or\n# Python sources only. Doxygen will then generate output that is more tailored\n# for that language. For instance, namespaces will be presented as packages,\n# qualified scopes will look different, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_JAVA   = NO\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran\n# sources. Doxygen will then generate output that is tailored for Fortran.\n# The default value is: NO.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL\n# sources. Doxygen will then generate output that is tailored for VHDL.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it\n# parses. With this tag you can assign which parser to use for a given\n# extension. Doxygen has a built-in mapping, but you can override or extend it\n# using this tag. The format is ext=language, where ext is a file extension, and\n# language is one of the parsers supported by doxygen: IDL, Java, Javascript,\n# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:\n# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:\n# Fortran. In the later case the parser tries to guess whether the code is fixed\n# or free formatted code, this is the default for Fortran type files), VHDL. For\n# instance to make doxygen treat .inc files as Fortran files (default is PHP),\n# and .f files as C (default is Fortran), use: inc=Fortran f=C.\n#\n# Note: For files without extension you can use no_extension as a placeholder.\n#\n# Note that for custom extensions you also need to set FILE_PATTERNS otherwise\n# the files are not read by doxygen.\n\nEXTENSION_MAPPING      =\n\n# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments\n# according to the Markdown format, which allows for more readable\n# documentation. See http://daringfireball.net/projects/markdown/ for details.\n# The output of markdown processing is further processed by doxygen, so you can\n# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in\n# case of backward compatibilities issues.\n# The default value is: YES.\n\nMARKDOWN_SUPPORT       = YES\n\n# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up\n# to that level are automatically included in the table of contents, even if\n# they do not have an id attribute.\n# Note: This feature currently applies only to Markdown headings.\n# Minimum value: 0, maximum value: 99, default value: 0.\n# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.\n\nTOC_INCLUDE_HEADINGS   = 0\n\n# When enabled doxygen tries to link words that correspond to documented\n# classes, or namespaces to their corresponding documentation. Such a link can\n# be prevented in individual cases by putting a % sign in front of the word or\n# globally by setting AUTOLINK_SUPPORT to NO.\n# The default value is: YES.\n\nAUTOLINK_SUPPORT       = YES\n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want\n# to include (a tag file for) the STL sources as input, then you should set this\n# tag to YES in order to let doxygen match functions declarations and\n# definitions whose arguments contain STL classes (e.g. func(std::string);\n# versus func(std::string) {}). This also make the inheritance and collaboration\n# diagrams that involve STL classes more complete and accurate.\n# The default value is: NO.\n\nBUILTIN_STL_SUPPORT    = YES\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to\n# enable parsing support.\n# The default value is: NO.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:\n# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen\n# will parse them like normal C++ but will assume all classes use public instead\n# of private inheritance when no explicit protection keyword is present.\n# The default value is: NO.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate\n# getter and setter methods for a property. Setting this option to YES will make\n# doxygen to replace the get and set methods by a property in the documentation.\n# This will only work if the methods are indeed getting or setting a simple\n# type. If this is not the case, or you want to show the methods anyway, you\n# should set this option to NO.\n# The default value is: YES.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC\n# tag is set to YES then doxygen will reuse the documentation of the first\n# member in the group (if any) for the other members of the group. By default\n# all members of a group must be documented explicitly.\n# The default value is: NO.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# If one adds a struct or class to a group and this option is enabled, then also\n# any nested class or struct is added to the same group. By default this option\n# is disabled and one has to add nested compounds explicitly via \\ingroup.\n# The default value is: NO.\n\nGROUP_NESTED_COMPOUNDS = NO\n\n# Set the SUBGROUPING tag to YES to allow class member groups of the same type\n# (for instance a group of public functions) to be put as a subgroup of that\n# type (e.g. under the Public Functions section). Set it to NO to prevent\n# subgrouping. Alternatively, this can be done per class using the\n# \\nosubgrouping command.\n# The default value is: YES.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions\n# are shown inside the group in which they are included (e.g. using \\ingroup)\n# instead of on a separate page (for HTML and Man pages) or section (for LaTeX\n# and RTF).\n#\n# Note that this feature does not work in combination with\n# SEPARATE_MEMBER_PAGES.\n# The default value is: NO.\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions\n# with only public data fields or simple typedef fields will be shown inline in\n# the documentation of the scope in which they are defined (i.e. file,\n# namespace, or group documentation), provided this scope is documented. If set\n# to NO, structs, classes, and unions are shown on a separate page (for HTML and\n# Man pages) or section (for LaTeX and RTF).\n# The default value is: NO.\n\nINLINE_SIMPLE_STRUCTS  = NO\n\n# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or\n# enum is documented as struct, union, or enum with the name of the typedef. So\n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct\n# with name TypeT. When disabled the typedef will appear as a member of a file,\n# namespace, or class. And the struct will be named TypeS. This can typically be\n# useful for C code in case the coding convention dictates that all compound\n# types are typedef'ed and only the typedef is referenced, never the tag name.\n# The default value is: NO.\n\nTYPEDEF_HIDES_STRUCT   = NO\n\n# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This\n# cache is used to resolve symbols given their name and scope. Since this can be\n# an expensive process and often the same symbol appears multiple times in the\n# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small\n# doxygen will become slower. If the cache is too large, memory is wasted. The\n# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range\n# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536\n# symbols. At the end of a run doxygen will report the cache usage and suggest\n# the optimal cache size from a speed point of view.\n# Minimum value: 0, maximum value: 9, default value: 0.\n\nLOOKUP_CACHE_SIZE      = 0\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in\n# documentation are documented, even if no documentation was available. Private\n# class members and static file members will be hidden unless the\n# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.\n# Note: This will also disable the warnings about undocumented members that are\n# normally produced when WARNINGS is set to YES.\n# The default value is: NO.\n\nEXTRACT_ALL            = NO\n\n# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will\n# be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIVATE        = YES\n\n# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal\n# scope will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PACKAGE        = YES\n\n# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be\n# included in the documentation.\n# The default value is: NO.\n\nEXTRACT_STATIC         = YES\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined\n# locally in source files will be included in the documentation. If set to NO,\n# only classes defined in header files are included. Does not have any effect\n# for Java sources.\n# The default value is: YES.\n\nEXTRACT_LOCAL_CLASSES  = YES\n\n# This flag is only useful for Objective-C code. If set to YES, local methods,\n# which are defined in the implementation section but not in the interface are\n# included in the documentation. If set to NO, only methods in the interface are\n# included.\n# The default value is: NO.\n\nEXTRACT_LOCAL_METHODS  = NO\n\n# If this flag is set to YES, the members of anonymous namespaces will be\n# extracted and appear in the documentation as a namespace called\n# 'anonymous_namespace{file}', where file will be replaced with the base name of\n# the file that contains the anonymous namespace. By default anonymous namespace\n# are hidden.\n# The default value is: NO.\n\nEXTRACT_ANON_NSPACES   = NO\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all\n# undocumented members inside documented classes or files. If set to NO these\n# members will be included in the various overviews, but no documentation\n# section is generated. This option has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all\n# undocumented classes that are normally visible in the class hierarchy. If set\n# to NO, these classes will be included in the various overviews. This option\n# has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend\n# (class|struct|union) declarations. If set to NO, these declarations will be\n# included in the documentation.\n# The default value is: NO.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any\n# documentation blocks found inside the body of a function. If set to NO, these\n# blocks will be appended to the function's detailed documentation block.\n# The default value is: NO.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation that is typed after a\n# \\internal command is included. If the tag is set to NO then the documentation\n# will be excluded. Set it to YES to include the internal documentation.\n# The default value is: NO.\n\nINTERNAL_DOCS          = NO\n\n# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file\n# names in lower-case letters. If set to YES, upper-case letters are also\n# allowed. This is useful if you have classes or files whose names only differ\n# in case and if your file system supports case sensitive file names. Windows\n# and Mac users are advised to set this option to NO.\n# The default value is: system dependent.\n\nCASE_SENSE_NAMES       = YES\n\n# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with\n# their full class and namespace scopes in the documentation. If set to YES, the\n# scope will be hidden.\n# The default value is: NO.\n\nHIDE_SCOPE_NAMES       = NO\n\n# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will\n# append additional text to a page's title, such as Class Reference. If set to\n# YES the compound reference will be hidden.\n# The default value is: NO.\n\nHIDE_COMPOUND_REFERENCE= NO\n\n# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of\n# the files that are included by a file in the documentation of that file.\n# The default value is: YES.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each\n# grouped member an include statement to the documentation, telling the reader\n# which file to include in order to use the member.\n# The default value is: NO.\n\nSHOW_GROUPED_MEMB_INC  = NO\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include\n# files with double quotes in the documentation rather than with sharp brackets.\n# The default value is: NO.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the\n# documentation for inline members.\n# The default value is: YES.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the\n# (detailed) documentation of file and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order.\n# The default value is: YES.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief\n# descriptions of file, namespace and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order. Note that\n# this will also influence the order of the classes in the class list.\n# The default value is: NO.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the\n# (brief and detailed) documentation of class members so that constructors and\n# destructors are listed first. If set to NO the constructors will appear in the\n# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.\n# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief\n# member documentation.\n# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting\n# detailed member documentation.\n# The default value is: NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy\n# of group names into alphabetical order. If set to NO the group names will\n# appear in their defined order.\n# The default value is: NO.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by\n# fully-qualified names, including namespaces. If set to NO, the class list will\n# be sorted only by class name, not including the namespace part.\n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.\n# Note: This option applies only to the class list, not to the alphabetical\n# list.\n# The default value is: NO.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper\n# type resolution of all parameters of a function it will reject a match between\n# the prototype and the implementation of a member function even if there is\n# only one candidate or it is obvious which candidate to choose by doing a\n# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still\n# accept a match between prototype and implementation in such cases.\n# The default value is: NO.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo\n# list. This list is created by putting \\todo commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test\n# list. This list is created by putting \\test commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug\n# list. This list is created by putting \\bug commands in the documentation.\n# The default value is: YES.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)\n# the deprecated list. This list is created by putting \\deprecated commands in\n# the documentation.\n# The default value is: YES.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional documentation\n# sections, marked by \\if <section_label> ... \\endif and \\cond <section_label>\n# ... \\endcond blocks.\n\nENABLED_SECTIONS       =\n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the\n# initial value of a variable or macro / define can have for it to appear in the\n# documentation. If the initializer consists of more lines than specified here\n# it will be hidden. Use a value of 0 to hide initializers completely. The\n# appearance of the value of individual variables and macros / defines can be\n# controlled using \\showinitializer or \\hideinitializer command in the\n# documentation regardless of this setting.\n# Minimum value: 0, maximum value: 10000, default value: 30.\n\nMAX_INITIALIZER_LINES  = 30\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at\n# the bottom of the documentation of classes and structs. If set to YES, the\n# list will mention the files that were used to generate the documentation.\n# The default value is: YES.\n\nSHOW_USED_FILES        = YES\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This\n# will remove the Files entry from the Quick Index and from the Folder Tree View\n# (if specified).\n# The default value is: YES.\n\nSHOW_FILES             = YES\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces\n# page. This will remove the Namespaces entry from the Quick Index and from the\n# Folder Tree View (if specified).\n# The default value is: YES.\n\nSHOW_NAMESPACES        = YES\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that\n# doxygen should invoke to get the current version for each file (typically from\n# the version control system). Doxygen will invoke the program by executing (via\n# popen()) the command command input-file, where command is the value of the\n# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided\n# by doxygen. Whatever the program writes to standard output is used as the file\n# version. For an example see the documentation.\n\nFILE_VERSION_FILTER    =\n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed\n# by doxygen. The layout file controls the global structure of the generated\n# output files in an output format independent way. To create the layout file\n# that represents doxygen's defaults, run doxygen with the -l option. You can\n# optionally specify a file name after the option, if omitted DoxygenLayout.xml\n# will be used as the name of the layout file.\n#\n# Note that if you run doxygen from a directory containing a file called\n# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE\n# tag is left empty.\n\nLAYOUT_FILE            =\n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files containing\n# the reference definitions. This must be a list of .bib files. The .bib\n# extension is automatically appended if omitted. This requires the bibtex tool\n# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.\n# For LaTeX the style of the bibliography can be controlled using\n# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the\n# search path. See also \\cite for info how to create references.\n\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated to\n# standard output by doxygen. If QUIET is set to YES this implies that the\n# messages are off.\n# The default value is: NO.\n\nQUIET                  = YES\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are\n# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES\n# this implies that the warnings are on.\n#\n# Tip: Turn warnings on while writing the documentation.\n# The default value is: YES.\n\nWARNINGS               = YES\n\n# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate\n# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: YES.\n\nWARN_IF_UNDOCUMENTED   = YES\n\n# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for\n# potential errors in the documentation, such as not documenting some parameters\n# in a documented function, or documenting parameters that don't exist or using\n# markup commands wrongly.\n# The default value is: YES.\n\nWARN_IF_DOC_ERROR      = YES\n\n# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that\n# are documented, but have no documentation for their parameters or return\n# value. If set to NO, doxygen will only warn about wrong or incomplete\n# parameter documentation, but not about the absence of documentation.\n# The default value is: NO.\n\nWARN_NO_PARAMDOC       = NO\n\n# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when\n# a warning is encountered.\n# The default value is: NO.\n\nWARN_AS_ERROR          = NO\n\n# The WARN_FORMAT tag determines the format of the warning messages that doxygen\n# can produce. The string should contain the $file, $line, and $text tags, which\n# will be replaced by the file and line number from which the warning originated\n# and the warning text. Optionally the format may contain $version, which will\n# be replaced by the version of the file (if it could be obtained via\n# FILE_VERSION_FILTER)\n# The default value is: $file:$line: $text.\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning and error\n# messages should be written. If left blank the output is written to standard\n# error (stderr).\n\nWARN_LOGFILE           =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag is used to specify the files and/or directories that contain\n# documented source files. You may enter file names like myfile.cpp or\n# directories like /usr/src/myproject. Separate the files or directories with\n# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING\n# Note: If this tag is empty the current directory is searched.\n\nINPUT                  = .\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n# libiconv (or the iconv built into libc) for the transcoding. See the libiconv\n# documentation (see: https://www.gnu.org/software/libiconv/) for the list of\n# possible encodings.\n# The default value is: UTF-8.\n\nINPUT_ENCODING         = UTF-8\n\n# If the value of the INPUT tag contains directories, you can use the\n# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and\n# *.h) to filter out the source-files in the directories.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# read by doxygen.\n#\n# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,\n# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,\n# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc,\n# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,\n# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf.\n\nFILE_PATTERNS          = *.c \\\n                         *.cc \\\n                         *.cxx \\\n                         *.cpp \\\n                         *.c++ \\\n                         *.java \\\n                         *.ii \\\n                         *.ixx \\\n                         *.ipp \\\n                         *.i++ \\\n                         *.inl \\\n                         *.idl \\\n                         *.ddl \\\n                         *.odl \\\n                         *.h \\\n                         *.hh \\\n                         *.hxx \\\n                         *.hpp \\\n                         *.h++ \\\n                         *.cs \\\n                         *.d \\\n                         *.php \\\n                         *.php4 \\\n                         *.php5 \\\n                         *.phtml \\\n                         *.inc \\\n                         *.m \\\n                         *.markdown \\\n                         *.md \\\n                         *.mm \\\n                         *.dox \\\n                         *.py \\\n                         *.pyw \\\n                         *.f90 \\\n                         *.f95 \\\n                         *.f03 \\\n                         *.f08 \\\n                         *.f \\\n                         *.for \\\n                         *.tcl \\\n                         *.vhd \\\n                         *.vhdl \\\n                         *.ucf \\\n                         *.qsf\n\n# The RECURSIVE tag can be used to specify whether or not subdirectories should\n# be searched for input files as well.\n# The default value is: NO.\n\nRECURSIVE              = YES\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be\n# excluded from the INPUT source files. This way you can easily exclude a\n# subdirectory from a directory tree whose root is specified with the INPUT tag.\n#\n# Note that relative paths are relative to the directory from which doxygen is\n# run.\n\nEXCLUDE                = doxygen_doc\nEXCLUDE                += dependencies\n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or\n# directories that are symbolic links (a Unix file system feature) are excluded\n# from the input.\n# The default value is: NO.\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the\n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude\n# certain files from those directories.\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories for example use the pattern */test/*\n\nEXCLUDE_PATTERNS      = */.git/*\nEXCLUDE_PATTERNS      += */cmake-*/*\n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names\n# (namespaces, classes, functions, etc.) that should be excluded from the\n# output. The symbol name can be a fully qualified name, a word, or if the\n# wildcard * is used, a substring. Examples: ANamespace, AClass,\n# AClass::ANamespace, ANamespace::*Test\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories use the pattern */test/*\n\nEXCLUDE_SYMBOLS        =\n\n# The EXAMPLE_PATH tag can be used to specify one or more files or directories\n# that contain example code fragments that are included (see the \\include\n# command).\n\nEXAMPLE_PATH           =\n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the\n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank all\n# files are included.\n\nEXAMPLE_PATTERNS       = *\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be\n# searched for input files to be used with the \\include or \\dontinclude commands\n# irrespective of the value of the RECURSIVE tag.\n# The default value is: NO.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or directories\n# that contain images that are to be included in the documentation (see the\n# \\image command).\n\nIMAGE_PATH             =\n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should\n# invoke to filter for each input file. Doxygen will invoke the filter program\n# by executing (via popen()) the command:\n#\n# <filter> <input-file>\n#\n# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the\n# name of an input file. Doxygen will then use the output that the filter\n# program writes to standard output. If FILTER_PATTERNS is specified, this tag\n# will be ignored.\n#\n# Note that the filter must not add or remove lines; it is applied before the\n# code is scanned, but not when the output code is generated. If lines are added\n# or removed, the anchors will not be placed correctly.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nINPUT_FILTER           =\n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern\n# basis. Doxygen will compare the file name with each pattern and apply the\n# filter if there is a match. The filters are a list of the form: pattern=filter\n# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how\n# filters are used. If the FILTER_PATTERNS tag is empty or if none of the\n# patterns match the file name, INPUT_FILTER is applied.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nFILTER_PATTERNS        =\n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using\n# INPUT_FILTER) will also be used to filter the input files that are used for\n# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).\n# The default value is: NO.\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file\n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and\n# it is also possible to disable source filtering for a specific pattern using\n# *.ext= (so without naming a filter).\n# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.\n\nFILTER_SOURCE_PATTERNS =\n\n# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that\n# is part of the input, its contents will be placed on the main page\n# (index.html). This can be useful if you have a project on for instance GitHub\n# and want to reuse the introduction page also for the doxygen output.\n\nUSE_MDFILE_AS_MAINPAGE =\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will be\n# generated. Documented entities will be cross-referenced with these sources.\n#\n# Note: To get rid of all source code in the generated output, make sure that\n# also VERBATIM_HEADERS is set to NO.\n# The default value is: NO.\n\nSOURCE_BROWSER         = YES\n\n# Setting the INLINE_SOURCES tag to YES will include the body of functions,\n# classes and enums directly into the documentation.\n# The default value is: NO.\n\nINLINE_SOURCES         = NO\n\n# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any\n# special comment blocks from generated source code fragments. Normal C, C++ and\n# Fortran comments will always remain visible.\n# The default value is: YES.\n\nSTRIP_CODE_COMMENTS    = YES\n\n# If the REFERENCED_BY_RELATION tag is set to YES then for each documented\n# function all documented functions referencing it will be listed.\n# The default value is: NO.\n\nREFERENCED_BY_RELATION = NO\n\n# If the REFERENCES_RELATION tag is set to YES then for each documented function\n# all documented entities called/used by that function will be listed.\n# The default value is: NO.\n\nREFERENCES_RELATION    = NO\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set\n# to YES then the hyperlinks from functions in REFERENCES_RELATION and\n# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will\n# link to the documentation.\n# The default value is: YES.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the\n# source code will show a tooltip with additional information such as prototype,\n# brief description and links to the definition and documentation. Since this\n# will make the HTML file larger and loading of large files a bit slower, you\n# can opt to disable this feature.\n# The default value is: YES.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nSOURCE_TOOLTIPS        = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code will\n# point to the HTML generated by the htags(1) tool instead of doxygen built-in\n# source browser. The htags tool is part of GNU's global source tagging system\n# (see https://www.gnu.org/software/global/global.html). You will need version\n# 4.8.6 or higher.\n#\n# To use it do the following:\n# - Install the latest version of global\n# - Enable SOURCE_BROWSER and USE_HTAGS in the config file\n# - Make sure the INPUT points to the root of the source tree\n# - Run doxygen as normal\n#\n# Doxygen will invoke htags (and that will in turn invoke gtags), so these\n# tools must be available from the command line (i.e. in the search path).\n#\n# The result: instead of the source browser generated by doxygen, the links to\n# source code will now point to the output of htags.\n# The default value is: NO.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a\n# verbatim copy of the header file for each class for which an include is\n# specified. Set to NO to disable this.\n# See also: Section \\class.\n# The default value is: YES.\n\nVERBATIM_HEADERS       = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all\n# compounds will be generated. Enable this if the project contains a lot of\n# classes, structs, unions or interfaces.\n# The default value is: YES.\n\nALPHABETICAL_INDEX     = YES\n\n# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in\n# which the alphabetical index list will be split.\n# Minimum value: 1, maximum value: 20, default value: 5.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nCOLS_IN_ALPHA_INDEX    = 5\n\n# In case all classes in a project start with a common prefix, all classes will\n# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag\n# can be used to specify a prefix (or a list of prefixes) that should be ignored\n# while generating the index headers.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output\n# The default value is: YES.\n\nGENERATE_HTML          = YES\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_OUTPUT            = html\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each\n# generated HTML page (for example: .htm, .php, .asp).\n# The default value is: .html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a user-defined HTML header file for\n# each generated HTML page. If the tag is left blank doxygen will generate a\n# standard header.\n#\n# To get valid HTML the header file that includes any scripts and style sheets\n# that doxygen needs, which is dependent on the configuration options used (e.g.\n# the setting GENERATE_TREEVIEW). It is highly recommended to start with a\n# default header using\n# doxygen -w html new_header.html new_footer.html new_stylesheet.css\n# YourConfigFile\n# and then modify the file new_header.html. See also section \"Doxygen usage\"\n# for information on how to generate the default header that doxygen normally\n# uses.\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. For a description\n# of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_HEADER            =\n\n# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each\n# generated HTML page. If the tag is left blank doxygen will generate a standard\n# footer. See HTML_HEADER for more information on how to generate a default\n# footer and what special commands can be used inside the footer. See also\n# section \"Doxygen usage\" for information on how to generate the default footer\n# that doxygen normally uses.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FOOTER            =\n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style\n# sheet that is used by each HTML page. It can be used to fine-tune the look of\n# the HTML output. If left blank doxygen will generate a default style sheet.\n# See also section \"Doxygen usage\" for information on how to generate the style\n# sheet that doxygen normally uses.\n# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as\n# it is more robust and this tag (HTML_STYLESHEET) will in the future become\n# obsolete.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_STYLESHEET        =\n\n# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# cascading style sheets that are included after the standard style sheets\n# created by doxygen. Using this option one can overrule certain style aspects.\n# This is preferred over using HTML_STYLESHEET since it does not replace the\n# standard style sheet and is therefore more robust against future updates.\n# Doxygen will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list). For an example see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_STYLESHEET  =\n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the HTML output directory. Note\n# that these files will be copied to the base HTML output directory. Use the\n# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these\n# files. In the HTML_STYLESHEET file, use the file name only. Also note that the\n# files will be copied as-is; there are no commands or markers available.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_FILES       =\n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen\n# will adjust the colors in the style sheet and background images according to\n# this color. Hue is specified as an angle on a colorwheel, see\n# https://en.wikipedia.org/wiki/Hue for more information. For instance the value\n# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300\n# purple, and 360 is red again.\n# Minimum value: 0, maximum value: 359, default value: 220.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_HUE    = 220\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors\n# in the HTML output. For a value of 0 the output will use grayscales only. A\n# value of 255 will produce the most vivid colors.\n# Minimum value: 0, maximum value: 255, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_SAT    = 100\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the\n# luminance component of the colors in the HTML output. Values below 100\n# gradually make the output lighter, whereas values above 100 make the output\n# darker. The value divided by 100 is the actual gamma applied, so 80 represents\n# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not\n# change the gamma.\n# Minimum value: 40, maximum value: 240, default value: 80.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_GAMMA  = 80\n\n# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML\n# page will contain the date and time when the page was generated. Setting this\n# to YES can help to show when doxygen was last run and thus if the\n# documentation is up to date.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_TIMESTAMP         = NO\n\n# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML\n# documentation will contain a main index with vertical navigation menus that\n# are dynamically created via Javascript. If disabled, the navigation index will\n# consists of multiple levels of tabs that are statically embedded in every HTML\n# page. Disable this option to support browsers that do not have Javascript,\n# like the Qt help browser.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_MENUS     = YES\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML\n# documentation will contain sections that can be hidden and shown after the\n# page has loaded.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries\n# shown in the various tree structured indices initially; the user can expand\n# and collapse entries dynamically later on. Doxygen will expand the tree to\n# such a level that at most the specified number of entries are visible (unless\n# a fully collapsed tree already exceeds this amount). So setting the number of\n# entries 1 will produce a full collapsed tree by default. 0 is a special value\n# representing an infinite number of entries and will result in a full expanded\n# tree by default.\n# Minimum value: 0, maximum value: 9999, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_INDEX_NUM_ENTRIES = 100\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files will be\n# generated that can be used as input for Apple's Xcode 3 integrated development\n# environment (see: https://developer.apple.com/tools/xcode/), introduced with\n# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a\n# Makefile in the HTML output directory. Running make will produce the docset in\n# that directory and running make install will install the docset in\n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at\n# startup. See https://developer.apple.com/tools/creatingdocsetswithdoxygen.html\n# for more information.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_DOCSET        = NO\n\n# This tag determines the name of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# The default value is: Doxygen generated docs.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# This tag specifies a string that should uniquely identify the documentation\n# set bundle. This should be a reverse domain-name style string, e.g.\n# com.mycompany.MyDocSet. Doxygen will append .docset to the name.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_BUNDLE_ID       = org.doxygen.Project\n\n# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify\n# the documentation publisher. This should be a reverse domain-name style\n# string, e.g. com.mycompany.MyDocSet.documentation.\n# The default value is: org.doxygen.Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_ID    = org.doxygen.Publisher\n\n# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.\n# The default value is: Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_NAME  = Publisher\n\n# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three\n# additional HTML index files: index.hhp, index.hhc, and index.hhk. The\n# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop\n# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on\n# Windows.\n#\n# The HTML Help Workshop contains a compiler that can convert all HTML output\n# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML\n# files are now used as the Windows 98 help format, and will replace the old\n# Windows help format (.hlp) on all Windows platforms in the future. Compressed\n# HTML files also contain an index, a table of contents, and you can search for\n# words in the documentation. The HTML workshop also contains a viewer for\n# compressed HTML files.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_HTMLHELP      = NO\n\n# The CHM_FILE tag can be used to specify the file name of the resulting .chm\n# file. You can add a path in front of the file if the result should not be\n# written to the html output directory.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_FILE               =\n\n# The HHC_LOCATION tag can be used to specify the location (absolute path\n# including file name) of the HTML help compiler (hhc.exe). If non-empty,\n# doxygen will try to run the HTML help compiler on the generated index.hhp.\n# The file has to be specified with full path.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nHHC_LOCATION           =\n\n# The GENERATE_CHI flag controls if a separate .chi index file is generated\n# (YES) or that it should be included in the master .chm file (NO).\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nGENERATE_CHI           = NO\n\n# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)\n# and project file content.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_INDEX_ENCODING     =\n\n# The BINARY_TOC flag controls whether a binary table of contents is generated\n# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it\n# enables the Previous and Next buttons.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nBINARY_TOC             = NO\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members to\n# the table of contents of the HTML help documentation and to the tree view.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nTOC_EXPAND             = NO\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and\n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that\n# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help\n# (.qch) of the generated HTML documentation.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify\n# the file name of the resulting .qch file. The path specified is relative to\n# the HTML output folder.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQCH_FILE               =\n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help\n# Project output. For more information please see Qt Help Project / Namespace\n# (see: http://doc.qt.io/qt-4.8/qthelpproject.html#namespace).\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_NAMESPACE          = org.doxygen.Project\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt\n# Help Project output. For more information please see Qt Help Project / Virtual\n# Folders (see: http://doc.qt.io/qt-4.8/qthelpproject.html#virtual-folders).\n# The default value is: doc.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_VIRTUAL_FOLDER     = doc\n\n# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom\n# filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_NAME   =\n\n# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the\n# custom filter to add. For more information please see Qt Help Project / Custom\n# Filters (see: http://doc.qt.io/qt-4.8/qthelpproject.html#custom-filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_ATTRS  =\n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this\n# project's filter section matches. Qt Help Project / Filter Attributes (see:\n# http://doc.qt.io/qt-4.8/qthelpproject.html#filter-attributes).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_SECT_FILTER_ATTRS  =\n\n# The QHG_LOCATION tag can be used to specify the location of Qt's\n# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the\n# generated .qhp file.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHG_LOCATION           =\n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be\n# generated, together with the HTML files, they form an Eclipse help plugin. To\n# install this plugin and make it available under the help contents menu in\n# Eclipse, the contents of the directory containing the HTML and XML files needs\n# to be copied into the plugins directory of eclipse. The name of the directory\n# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.\n# After copying Eclipse needs to be restarted before the help appears.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the Eclipse help plugin. When installing the plugin\n# the directory name containing the HTML and XML files should also have this\n# name. Each documentation set should have its own identifier.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.\n\nECLIPSE_DOC_ID         = org.doxygen.Project\n\n# If you want full control over the layout of the generated HTML pages it might\n# be necessary to disable the index and replace it with your own. The\n# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top\n# of each HTML page. A value of NO enables the index and the value YES disables\n# it. Since the tabs in the index contain the same information as the navigation\n# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nDISABLE_INDEX          = NO\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index\n# structure should be generated to display hierarchical information. If the tag\n# value is set to YES, a side panel will be generated containing a tree-like\n# index structure (just like the one that is generated for HTML Help). For this\n# to work a browser that supports JavaScript, DHTML, CSS and frames is required\n# (i.e. any modern browser). Windows users are probably better off using the\n# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can\n# further fine-tune the look of the index. As an example, the default style\n# sheet generated by doxygen has an example that shows how to put an image at\n# the root of the tree instead of the PROJECT_NAME. Since the tree basically has\n# the same information as the tab index, you could consider setting\n# DISABLE_INDEX to YES when enabling this option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_TREEVIEW      = YES\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that\n# doxygen will group on one line in the generated HTML documentation.\n#\n# Note that a value of 0 will completely suppress the enum values from appearing\n# in the overview section.\n# Minimum value: 0, maximum value: 20, default value: 4.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nENUM_VALUES_PER_LINE   = 4\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used\n# to set the initial width (in pixels) of the frame in which the tree is shown.\n# Minimum value: 0, maximum value: 1500, default value: 250.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nTREEVIEW_WIDTH         = 250\n\n# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to\n# external symbols imported via tag files in a separate window.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# Use this tag to change the font size of LaTeX formulas included as images in\n# the HTML documentation. When you change the font size after a successful\n# doxygen run you need to manually remove any form_*.png images from the HTML\n# output directory to force them to be regenerated.\n# Minimum value: 8, maximum value: 50, default value: 10.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_FONTSIZE       = 10\n\n# Use the FORMULA_TRANSPARENT tag to determine whether or not the images\n# generated for formulas are transparent PNGs. Transparent PNGs are not\n# supported properly for IE 6.0, but are supported on all modern browsers.\n#\n# Note that when changing this option you need to delete any form_*.png files in\n# the HTML output directory before the changes have effect.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_TRANSPARENT    = YES\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see\n# https://www.mathjax.org) which uses client side Javascript for the rendering\n# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX\n# installed or if you want to formulas look prettier in the HTML output. When\n# enabled you may also need to install MathJax separately and configure the path\n# to it using the MATHJAX_RELPATH option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nUSE_MATHJAX            = NO\n\n# When MathJax is enabled you can set the default output format to be used for\n# the MathJax output. See the MathJax site (see:\n# http://docs.mathjax.org/en/latest/output.html) for more details.\n# Possible values are: HTML-CSS (which is slower, but has the best\n# compatibility), NativeMML (i.e. MathML) and SVG.\n# The default value is: HTML-CSS.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_FORMAT         = HTML-CSS\n\n# When MathJax is enabled you need to specify the location relative to the HTML\n# output directory using the MATHJAX_RELPATH option. The destination directory\n# should contain the MathJax.js script. For instance, if the mathjax directory\n# is located at the same level as the HTML output directory, then\n# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax\n# Content Delivery Network so you can quickly see the result without installing\n# MathJax. However, it is strongly recommended to install a local copy of\n# MathJax from https://www.mathjax.org before deployment.\n# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_RELPATH        = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.2/\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax\n# extension names that should be enabled during MathJax rendering. For example\n# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_EXTENSIONS     =\n\n# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces\n# of code that will be used on startup of the MathJax code. See the MathJax site\n# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an\n# example see the documentation.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_CODEFILE       =\n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box for\n# the HTML output. The underlying search engine uses javascript and DHTML and\n# should work on any modern browser. Note that when using HTML help\n# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)\n# there is already a search function so this one should typically be disabled.\n# For large projects the javascript based search engine can be slow, then\n# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to\n# search using the keyboard; to jump to the search box use <access key> + S\n# (what the <access key> is depends on the OS and browser, but it is typically\n# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down\n# key> to jump into the search results window, the results can be navigated\n# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel\n# the search. The filter options can be selected when the cursor is inside the\n# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>\n# to select a filter and <Enter> or <escape> to activate or cancel the filter\n# option.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSEARCHENGINE           = YES\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be\n# implemented using a web server instead of a web client using Javascript. There\n# are two flavors of web server based searching depending on the EXTERNAL_SEARCH\n# setting. When disabled, doxygen will generate a PHP script for searching and\n# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing\n# and searching needs to be provided by external tools. See the section\n# \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSERVER_BASED_SEARCH    = NO\n\n# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP\n# script for searching. Instead the search results are written to an XML file\n# which needs to be processed by an external indexer. Doxygen will invoke an\n# external search engine pointed to by the SEARCHENGINE_URL option to obtain the\n# search results.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: https://xapian.org/).\n#\n# See the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH        = NO\n\n# The SEARCHENGINE_URL should point to a search engine hosted by a web server\n# which will return the search results when EXTERNAL_SEARCH is enabled.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see: https://xapian.org/). See the section \"External Indexing and\n# Searching\" for details.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHENGINE_URL       =\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed\n# search data is written to a file for indexing by an external tool. With the\n# SEARCHDATA_FILE tag the name of this file can be specified.\n# The default file is: searchdata.xml.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHDATA_FILE        = searchdata.xml\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the\n# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is\n# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple\n# projects and redirect the results back to the right project.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH_ID     =\n\n# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen\n# projects other than the one defined by this configuration file, but that are\n# all added to the same external search index. Each project needs to have a\n# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of\n# to a relative location where the documentation can be found. The format is:\n# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.\n# The default value is: YES.\n\nGENERATE_LATEX         = NO\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be\n# invoked.\n#\n# Note that when enabling USE_PDFLATEX this option is only used for generating\n# bitmaps for formulas in the HTML output, but not in the Makefile that is\n# written to the output directory.\n# The default file is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_CMD_NAME         = latex\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate\n# index for LaTeX.\n# The default file is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nCOMPACT_LATEX          = NO\n\n# The PAPER_TYPE tag can be used to set the paper type that is used by the\n# printer.\n# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x\n# 14 inches) and executive (7.25 x 10.5 inches).\n# The default value is: a4.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPAPER_TYPE             = a4\n\n# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names\n# that should be included in the LaTeX output. The package can be specified just\n# by its name or with the correct syntax as to be used with the LaTeX\n# \\usepackage command. To get the times font for instance you can specify :\n# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}\n# To use the option intlimits with the amsmath package you can specify:\n# EXTRA_PACKAGES=[intlimits]{amsmath}\n# If left blank no extra packages will be included.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nEXTRA_PACKAGES         =\n\n# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the\n# generated LaTeX document. The header should contain everything until the first\n# chapter. If it is left blank doxygen will generate a standard header. See\n# section \"Doxygen usage\" for information on how to let doxygen write the\n# default header to a separate file.\n#\n# Note: Only use a user-defined header if you know what you are doing! The\n# following commands have a special meaning inside the header: $title,\n# $datetime, $date, $doxygenversion, $projectname, $projectnumber,\n# $projectbrief, $projectlogo. Doxygen will replace $title with the empty\n# string, for the replacement values of the other commands the user is referred\n# to HTML_HEADER.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HEADER           =\n\n# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the\n# generated LaTeX document. The footer should contain everything after the last\n# chapter. If it is left blank doxygen will generate a standard footer. See\n# LATEX_HEADER for more information on how to generate a default footer and what\n# special commands can be used inside the footer.\n#\n# Note: Only use a user-defined footer if you know what you are doing!\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_FOOTER           =\n\n# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# LaTeX style sheets that are included after the standard style sheets created\n# by doxygen. Using this option one can overrule certain style aspects. Doxygen\n# will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_STYLESHEET =\n\n# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the LATEX_OUTPUT output\n# directory. Note that the files will be copied as-is; there are no commands or\n# markers available.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_FILES      =\n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is\n# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will\n# contain links (just like the HTML output) instead of page references. This\n# makes the output suitable for online browsing using a PDF viewer.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPDF_HYPERLINKS         = YES\n\n# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate\n# the PDF file directly from the LaTeX files. Set this option to YES, to get a\n# higher quality PDF documentation.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nUSE_PDFLATEX           = YES\n\n# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode\n# command to the generated LaTeX files. This will instruct LaTeX to keep running\n# if errors occur, instead of asking the user for help. This option is also used\n# when generating formulas in HTML.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BATCHMODE        = NO\n\n# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the\n# index chapters (such as File Index, Compound Index, etc.) in the output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HIDE_INDICES     = NO\n\n# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source\n# code with syntax highlighting in the LaTeX output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_SOURCE_CODE      = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the\n# bibliography, e.g. plainnat, or ieeetr. See\n# https://en.wikipedia.org/wiki/BibTeX and \\cite for more info.\n# The default value is: plain.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BIB_STYLE        = plain\n\n# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated\n# page will contain the date and time when the page was generated. Setting this\n# to NO can help when comparing the output of multiple runs.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_TIMESTAMP        = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The\n# RTF output is optimized for Word 97 and may not look too pretty with other RTF\n# readers/editors.\n# The default value is: NO.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: rtf.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will\n# contain hyperlink fields. The RTF file will contain links (just like the HTML\n# output) instead of page references. This makes the output suitable for online\n# browsing using Word or some other Word compatible readers that support those\n# fields.\n#\n# Note: WordPad (write) and others do not support links.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_HYPERLINKS         = NO\n\n# Load stylesheet definitions from file. Syntax is similar to doxygen's config\n# file, i.e. a series of assignments. You only have to provide replacements,\n# missing definitions are set to their default value.\n#\n# See also section \"Doxygen usage\" for information on how to generate the\n# default style sheet that doxygen normally uses.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_STYLESHEET_FILE    =\n\n# Set optional variables used in the generation of an RTF document. Syntax is\n# similar to doxygen's config file. A template extensions file can be generated\n# using doxygen -e rtf extensionFile.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTENSIONS_FILE    =\n\n# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code\n# with syntax highlighting in the RTF output.\n#\n# Note that which sources are shown also depends on other settings such as\n# SOURCE_BROWSER.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_SOURCE_CODE        = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for\n# classes and files.\n# The default value is: NO.\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it. A directory man3 will be created inside the directory specified by\n# MAN_OUTPUT.\n# The default directory is: man.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to the generated\n# man pages. In case the manual section does not start with a number, the number\n# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is\n# optional.\n# The default value is: .3.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_EXTENSION          = .3\n\n# The MAN_SUBDIR tag determines the name of the directory created within\n# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by\n# MAN_EXTENSION with the initial . removed.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_SUBDIR             =\n\n# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it\n# will generate one additional man file for each entity documented in the real\n# man page(s). These additional files only source the real man page, but without\n# them the man command would be unable to find the correct page.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that\n# captures the structure of the code including all documentation.\n# The default value is: NO.\n\nGENERATE_XML           = NO\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: xml.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_OUTPUT             = xml\n\n# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program\n# listings (including syntax highlighting and cross-referencing information) to\n# the XML output. Note that enabling this will significantly increase the size\n# of the XML output.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_PROGRAMLISTING     = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the DOCBOOK output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files\n# that can be used to generate PDF.\n# The default value is: NO.\n\nGENERATE_DOCBOOK       = NO\n\n# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.\n# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in\n# front of it.\n# The default directory is: docbook.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_OUTPUT         = docbook\n\n# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the\n# program listings (including syntax highlighting and cross-referencing\n# information) to the DOCBOOK output. Note that enabling this will significantly\n# increase the size of the DOCBOOK output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_PROGRAMLISTING = NO\n\n#---------------------------------------------------------------------------\n# Configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an\n# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures\n# the structure of the code including all documentation. Note that this feature\n# is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module\n# file that captures the structure of the code including all documentation.\n#\n# Note that this feature is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_PERLMOD       = NO\n\n# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary\n# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI\n# output from the Perl module output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely\n# formatted so it can be parsed by a human reader. This is useful if you want to\n# understand what is going on. On the other hand, if this tag is set to NO, the\n# size of the Perl module output will be much smaller and Perl will parse it\n# just the same.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file are\n# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful\n# so different doxyrules.make files included by the same Makefile don't\n# overwrite each other's variables.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all\n# C-preprocessor directives found in the sources and include files.\n# The default value is: YES.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names\n# in the source code. If set to NO, only conditional compilation will be\n# performed. Macro expansion can be done in a controlled way by setting\n# EXPAND_ONLY_PREDEF to YES.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nMACRO_EXPANSION        = YES\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then\n# the macro expansion is limited to the macros specified with the PREDEFINED and\n# EXPAND_AS_DEFINED tags.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_ONLY_PREDEF     = YES\n\n# If the SEARCH_INCLUDES tag is set to YES, the include files in the\n# INCLUDE_PATH will be searched if a #include is found.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSEARCH_INCLUDES        = YES\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that\n# contain include files that are not input files but should be processed by the\n# preprocessor.\n# This tag requires that the tag SEARCH_INCLUDES is set to YES.\n\nINCLUDE_PATH           = \t/usr/include/gtkmm-3.0 \\\n\t/usr/lib64/gtkmm-3.0/include \\\n\t/usr/include/atkmm-1.6 \\\n\t/usr/include/gdkmm-3.0 \\\n\t/usr/lib64/gdkmm-3.0/include \\\n\t/usr/include/giomm-2.4 \\\n\t/usr/lib64/giomm-2.4/include \\\n\t/usr/include/pangomm-1.4 \\\n\t/usr/lib64/pangomm-1.4/include \\\n\t/usr/include/glibmm-2.4 \\\n\t/usr/lib64/glibmm-2.4/include \\\n\t/usr/include/gtk-3.0 \\\n\t/usr/include/gio-unix-2.0 \\\n\t/usr/include/pango-1.0  \\\n\t/usr/include/atk-1.0 \\\n\t/usr/include/cairo \\\n\t/usr/include/cairomm-1.0 \\\n\t/usr/lib64/cairomm-1.0/include \\\n\t/usr/include/sigc++-2.0 \\\n\t/usr/lib64/sigc++-2.0/include \\\n\t/usr/include/gdk-pixbuf-2.0 \\\n\t/usr/include/glib-2.0 \\\n\t/usr/lib64/glib-2.0/include\n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard\n# patterns (like *.h and *.hpp) to filter out the header-files in the\n# directories. If left blank, the patterns specified with FILE_PATTERNS will be\n# used.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nINCLUDE_FILE_PATTERNS  =\n\n# The PREDEFINED tag can be used to specify one or more macro names that are\n# defined before the preprocessor is started (similar to the -D option of e.g.\n# gcc). The argument of the tag is a list of macros of the form: name or\n# name=definition (no spaces). If the definition and the \"=\" are omitted, \"=1\"\n# is assumed. To prevent a macro definition from being undefined via #undef or\n# recursively expanded use the := operator instead of the = operator.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nPREDEFINED             = DOXYGEN_SHOULD_SKIP_THIS \\\n\t\t\t\t\t\t__GNUC__ \\\n\t\t\t\t\t\t_GNU_SOURCE \\\n\t\t\t\t\t\tCONFIG_KERNEL_DARWIN=1 \\\n\t\t\t\t\t\tCONFIG_KERNEL_DRAGONFLY=1 \\\n\t\t\t\t\t\tCONFIG_KERNEL_FREEBSD=1 \\\n\t\t\t\t\t\tCONFIG_KERNEL_LINUX=1 \\\n\t\t\t\t\t\tCONFIG_KERNEL_NETBSD=1 \\\n\t\t\t\t\t\tCONFIG_KERNEL_OPENBSD=1 \\\n\t\t\t\t\t\tCONFIG_KERNEL_QNX=1 \\\n\t\t\t\t\t\tCONFIG_KERNEL_SOLARIS=1 \\\n\t\t\t\t\t\tCONFIG_KERNEL_WINDOWS32=1 \\\n\t\t\t\t\t\tCONFIG_KERNEL_WINDOWS64=1 \\\n\t\t\t\t\t\tCONFIG_KERNEL_LINUX=1 \\\n\t\t\t\t\t\tENABLE_GLIB=1 \\\n\t\t\t\t\t\tENABLE_GLIBMM=1 \\\n\t\t\t\t\t\t\"HZ_FUNC_PRINTF_CHECK(a,b)=\" \\\n\t\t\t\t\t\t__cplusplus \\\n\t\t\t\t\t\t_WIN32\n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this\n# tag can be used to specify a list of macro names that should be expanded. The\n# macro definition that is found in the sources will be used. Use the PREDEFINED\n# tag if you want to use a different macro definition that overrules the\n# definition found in the source code.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_AS_DEFINED      =\n\n# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will\n# remove all references to function-like macros that are alone on a line, have\n# an all uppercase name, and do not end with a semicolon. Such function macros\n# are typically used for boiler-plate code, and will confuse the parser if not\n# removed.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES tag can be used to specify one or more tag files. For each tag\n# file the location of the external documentation should be added. The format of\n# a tag file without this location is as follows:\n# TAGFILES = file1 file2 ...\n# Adding location for the tag files is done as follows:\n# TAGFILES = file1=loc1 \"file2 = loc2\" ...\n# where loc1 and loc2 can be relative or absolute paths or URLs. See the\n# section \"Linking to external documentation\" for more information about the use\n# of tag files.\n# Note: Each tag file must have a unique name (where the name does NOT include\n# the path). If a tag file is not located in the directory in which doxygen is\n# run, you must also specify the path to the tagfile here.\n\nTAGFILES               =\n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create a\n# tag file that is based on the input files it reads. See section \"Linking to\n# external documentation\" for more information about the usage of tag files.\n\nGENERATE_TAGFILE       =\n\n# If the ALLEXTERNALS tag is set to YES, all external class will be listed in\n# the class index. If set to NO, only the inherited external classes will be\n# listed.\n# The default value is: NO.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed\n# in the modules index. If set to NO, only the current project's groups will be\n# listed.\n# The default value is: YES.\n\nEXTERNAL_GROUPS        = YES\n\n# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in\n# the related pages index. If set to NO, only the current project's pages will\n# be listed.\n# The default value is: YES.\n\nEXTERNAL_PAGES         = YES\n\n# The PERL_PATH should be the absolute path and name of the perl script\n# interpreter (i.e. the result of 'which perl').\n# The default file (with absolute path) is: /usr/bin/perl.\n\nPERL_PATH              = /usr/bin/perl\n\n#---------------------------------------------------------------------------\n# Configuration options related to the dot tool\n#---------------------------------------------------------------------------\n\n# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram\n# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to\n# NO turns the diagrams off. Note that this option also works with HAVE_DOT\n# disabled, but it is recommended to install and use dot, since it yields more\n# powerful graphs.\n# The default value is: YES.\n\nCLASS_DIAGRAMS         = YES\n\n# You can define message sequence charts within doxygen comments using the \\msc\n# command. Doxygen will then run the mscgen tool (see:\n# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the\n# documentation. The MSCGEN_PATH tag allows you to specify the directory where\n# the mscgen tool resides. If left empty the tool is assumed to be found in the\n# default search path.\n\nMSCGEN_PATH            =\n\n# You can include diagrams made with dia in doxygen documentation. Doxygen will\n# then run dia to produce the diagram and insert it in the documentation. The\n# DIA_PATH tag allows you to specify the directory where the dia binary resides.\n# If left empty dia is assumed to be found in the default search path.\n\nDIA_PATH               =\n\n# If set to YES the inheritance and collaboration graphs will hide inheritance\n# and usage relations if the target is undocumented or is not a class.\n# The default value is: YES.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is\n# available from the path. This tool is part of Graphviz (see:\n# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent\n# Bell Labs. The other options in this section have no effect if this option is\n# set to NO\n# The default value is: NO.\n\nHAVE_DOT               = NO\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed\n# to run in parallel. When set to 0 doxygen will base this on the number of\n# processors available in the system. You can set it explicitly to a value\n# larger than 0 to get control over the balance between CPU load and processing\n# speed.\n# Minimum value: 0, maximum value: 32, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NUM_THREADS        = 0\n\n# When you want a differently looking font in the dot files that doxygen\n# generates you can specify the font name using DOT_FONTNAME. You need to make\n# sure dot is able to find the font, which can be done by putting it in a\n# standard location or by setting the DOTFONTPATH environment variable or by\n# setting DOT_FONTPATH to the directory containing the font.\n# The default value is: Helvetica.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTNAME           = Helvetica\n\n# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of\n# dot graphs.\n# Minimum value: 4, maximum value: 24, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTSIZE           = 10\n\n# By default doxygen will tell dot to use the default font as specified with\n# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set\n# the path where dot can find it using this tag.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTPATH           =\n\n# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for\n# each documented class showing the direct and indirect inheritance relations.\n# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCLASS_GRAPH            = YES\n\n# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a\n# graph for each documented class showing the direct and indirect implementation\n# dependencies (inheritance, containment, and class references variables) of the\n# class with other documented classes.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCOLLABORATION_GRAPH    = YES\n\n# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for\n# groups, showing the direct groups dependencies.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and\n# collaboration diagrams in a style similar to the OMG's Unified Modeling\n# Language.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LOOK               = NO\n\n# If the UML_LOOK tag is enabled, the fields and methods are shown inside the\n# class node. If there are many fields or methods and many nodes the graph may\n# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the\n# number of items for each type to make the size more manageable. Set this to 0\n# for no limit. Note that the threshold may be exceeded by 50% before the limit\n# is enforced. So when you set the threshold to 10, up to 15 fields may appear,\n# but if the number exceeds 15, the total amount of fields shown is limited to\n# 10.\n# Minimum value: 0, maximum value: 100, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LIMIT_NUM_FIELDS   = 10\n\n# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and\n# collaboration graphs will show the relations between templates and their\n# instances.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nTEMPLATE_RELATIONS     = NO\n\n# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to\n# YES then doxygen will generate a graph for each documented file showing the\n# direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDE_GRAPH          = YES\n\n# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are\n# set to YES then doxygen will generate a graph for each documented file showing\n# the direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH tag is set to YES then doxygen will generate a call\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable call graphs for selected\n# functions only using the \\callgraph command. Disabling a call graph can be\n# accomplished by means of the command \\hidecallgraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALL_GRAPH             = NO\n\n# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable caller graphs for selected\n# functions only using the \\callergraph command. Disabling a caller graph can be\n# accomplished by means of the command \\hidecallergraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALLER_GRAPH           = NO\n\n# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical\n# hierarchy of all classes instead of a textual one.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGRAPHICAL_HIERARCHY    = YES\n\n# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the\n# dependencies a directory has on other directories in a graphical way. The\n# dependency relations are determined by the #include relations between the\n# files in the directories.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDIRECTORY_GRAPH        = YES\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images\n# generated by dot. For an explanation of the image formats see the section\n# output formats in the documentation of the dot tool (Graphviz (see:\n# http://www.graphviz.org/)).\n# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order\n# to make the SVG files visible in IE 9+ (other browsers do not have this\n# requirement).\n# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,\n# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and\n# png:gdiplus:gdiplus.\n# The default value is: png.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to\n# enable generation of interactive SVG images that allow zooming and panning.\n#\n# Note that this requires a modern browser other than Internet Explorer. Tested\n# and working are Firefox, Chrome, Safari, and Opera.\n# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make\n# the SVG files visible. Older versions of IE do not have SVG support.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINTERACTIVE_SVG        = NO\n\n# The DOT_PATH tag can be used to specify the path where the dot tool can be\n# found. If left blank, it is assumed the dot tool can be found in the path.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_PATH               =\n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that\n# contain dot files that are included in the documentation (see the \\dotfile\n# command).\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOTFILE_DIRS           =\n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that\n# contain msc files that are included in the documentation (see the \\mscfile\n# command).\n\nMSCFILE_DIRS           =\n\n# The DIAFILE_DIRS tag can be used to specify one or more directories that\n# contain dia files that are included in the documentation (see the \\diafile\n# command).\n\nDIAFILE_DIRS           =\n\n# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the\n# path where java can find the plantuml.jar file. If left blank, it is assumed\n# PlantUML is not used or called during a preprocessing step. Doxygen will\n# generate a warning when it encounters a \\startuml command in this case and\n# will not generate output for the diagram.\n\nPLANTUML_JAR_PATH      =\n\n# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a\n# configuration file for plantuml.\n\nPLANTUML_CFG_FILE      =\n\n# When using plantuml, the specified paths are searched for files specified by\n# the !include statement in a plantuml block.\n\nPLANTUML_INCLUDE_PATH  =\n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes\n# that will be shown in the graph. If the number of nodes in a graph becomes\n# larger than this value, doxygen will truncate the graph, which is visualized\n# by representing a node as a red box. Note that doxygen if the number of direct\n# children of the root node in a graph is already larger than\n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that\n# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n# Minimum value: 0, maximum value: 10000, default value: 50.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_GRAPH_MAX_NODES    = 50\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs\n# generated by dot. A depth value of 3 means that only nodes reachable from the\n# root by following a path via at most 3 edges will be shown. Nodes that lay\n# further from the root node will be omitted. Note that setting this option to 1\n# or 2 may greatly reduce the computation time needed for large code bases. Also\n# note that the size of a graph can be further restricted by\n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n# Minimum value: 0, maximum value: 1000, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nMAX_DOT_GRAPH_DEPTH    = 0\n\n# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent\n# background. This is disabled by default, because dot on Windows does not seem\n# to support this out of the box.\n#\n# Warning: Depending on the platform used, enabling this option may lead to\n# badly anti-aliased labels on the edges of a graph (i.e. they become hard to\n# read).\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_TRANSPARENT        = NO\n\n# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output\n# files in one run (i.e. multiple -o and -T options on the command line). This\n# makes dot run faster, but since only newer versions of dot (>1.8.10) support\n# this, this feature is disabled by default.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_MULTI_TARGETS      = YES\n\n# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page\n# explaining the meaning of the various boxes and arrows in the dot generated\n# graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot\n# files that are used to generate the various graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_CLEANUP            = YES\n"
  },
  {
    "path": "LICENSE.LGPL3.txt",
    "content": "                   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions.\n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this license\n   document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\n   document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version.\n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary.\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n"
  },
  {
    "path": "NEWS.txt",
    "content": "Version 2.0.2, released on 2025-02-24\n\tSupport GCC 11 (and Ubuntu 22.04) by using and vendoring fmt library\n\t\t(#77).\n\tSupport an option of saving smartctl output to text instead of JSON (#76).\n\tRestored pre-2.0.0 runtime support for smartctl < 7.3 (based on Text\n\t\tparser); this is an unsupported, old method without NVMe, provided\n\t\tfor compatibility only (#77).\n\tRestored pre-2.0.0 support for standard (non-extended) self-test log found\n\t\tin very old ATA drives; the new JSON parser did not support this\n\t\tpreviously (#79).\n\tBetter support for temperature values in JSON parser; this fixes\n\t\tunreasonably large temperature values (#78).\n\tAdd a tooltip explaining self-test \"lifetime hours\" wrapping behavior\n\t\t(#73).\n\tWindows: Fixed a crash on Windows 7 when opening drive information\n\t\tby using Windows 7-compatible librsvg (#71).\n\tWindows: Avoid using non-integer font sizes.\n\nVersion 2.0.1, released on 2024-11-26\n\tWindows: Fixed a crash when opening a native file dialog (#67).\n\nVersion 2.0.0, released on 2024-11-07\n\tNew JSON-based parser for smartctl output (requires smartctl 7.3+) (#4).\n\tSupport for NVMe drives (self-tests require smartctl 7.4+) (#4).\n\tNew \"Basic\" JSON parser for SCSI, flash, and other non-ATA/NVMe drives.\n\tPCRE library is no longer required (the code has been ported to\n\t\tstd::regex) (#40).\n\tA lot of code has been refactored and modernized using C++17 and C++20\n\t\tfeatures, removing much of the custom library code.\n\tThe build process requires a C++20-compliant compiler now (GCC 13+,\n\t\tClang 17+, Apple Clang 15+).\n\tCMake (3.14+) is now used as a build system instead of autotools.\n\tGSmartControl is now licensed under GNU GPL 3 only.\n\tIcons come from Oxygen project now (LGPL 3+) instead of Crystal project.\n\tDocumentation in md format comes with the source code (\"docs\" directory).\n\tSupport new versions and prereleases of smartctl (#46, #42).\n\tBetter detection of drive types thanks to the JSON parser.\n\tRemoved ATA AODC feature, it has been deprecated for a while now.\n\tInformation window: Some values are aligned and monospaced to improve\n\t\treadability.\n\tFixed command execution popup dialog being on top of all windows (#17).\n\tOther minor UI improvements and fixes.\n\tWindows: Better support for HiDPI (including fractional scaling).\n\tWindows: NSIS package is generated by CPack now.\n\tWindows: Packages are built using GitHub CI now.\n\nVersion 1.1.4, released on 2022-02-04\n\tFixed a crash when trying to render main window icons which have\n\t\tincomplete data; this fixes a rare crash when scanning devices (#13).\n\tFixed rendering compatibility issues with newer GTK3 versions.\n\tFixed build system conflict with C++20 version header (#14).\n\tFixed \"Running command ...\" dialog floating above windows of other\n\t\tapplications (#17).\n\tFixed a crash when system locale is set to invalid locale.\n\tSupport Finnish decimal separator (backported from the Debian package)\n\t\t(#23).\n\tSet LC_NUMERIC=C for smartctl process to avoid locale-dependent number\n\t\tformatting (#23).\n\tFixed printing all GTK messages as warnings; the messages are sorted by\n\t\tseverity now.\n\tAdd current time to default filename in Save Output dialog.\n\tSupport macos stat command in file2csource.sh.\n\tFixed a few typos (backported from the Debian package).\n\tWindows port now uses Adwaita theme due to issues with win32 theme.\n\tWindows port is dpi-aware now.\n\nVersion 1.1.3, released on 2017-11-12\n\tFixed gsmartcontrol-root not launching if GDK_* variables are not set.\n\nVersion 1.1.2, released on 2017-11-11\n\tFixed gsmartcontrol-root not passing GDK_SCALE and GDK_DPI_SCALE variables\n\tto gsmartcontrol when using PolKit.\n\tFixed blurriness of icons in the main window with GDK_SCALE=2.\n\tTweaked the main window interface.\n\tWindows: Show volume labels beside drive letters in icon tooltips.\n\nVersion 1.1.1, released on 2017-09-25\n\tWindows: Use Adwaita GTK+ theme for systems which support Classic Windows\n\t\ttheme, since the default win32 GTK+ theme is broken in it; this\n\t\tincludes Windows 7 and Windows Server.\n\tStatistics entry values are formatted with commas for readability.\n\tMoved help information to website.\n\nVersion 1.1.0, released on 2017-09-07\n\tNew Statistics, Temperature Log, Error Recovery, Physical and Directory\n\t\ttabs.\n\tGeneral tab shows non-SMART device settings as well.\n\tAttributes tab shows entries in \"brief\" format.\n\tError Log tab shows Extended error log by default (if supported).\n\tSelf-Test Log tab now shows Extended self-test log by default (if\n\t\tsupported).\n\tGSmartControl now uses \"-x\" equivalent for retrieving data (as upposed to\n\t\t\"-a\"); loading \"-x\" outputs as virtual drives is also supported.\n\tImplemented ability to copy rows in CSV format from Attribute, Statistics\n\t\tand Self-Test Log tables.\n\tImplemented \"Update Drive Database\" functionality.\n\tWindows: Drive letters are shown for each drive.\n\tScan time is shown under virtual drive icons.\n\tPolkit is supported with gsmartcontrol-root script now.\n\tPcrecpp is no longer bundled, use system-installed one instead.\n\tSmartmontools version 5.43 is required at runtime.\n\tOther minor improvements and fixes.\n\nVersion 1.0.2, released on 2017-07-21\n\tFixed incomplete capturing of smartctl output under Windows.\n\tAdded missing icons under Windows.\n\tFixed being able to turn on AODC even if unsupported.\n\nVersion 1.0.1, released on 2017-06-19\n\tFixed compilation under macOS.\n\tFixed compilation under Fedora Rawhide.\n\tDon't use -mtune=generic for all targets.\n\nVersion 1.0.0, released on 2017-06-16\n\tPorted to GTK+ 3.\n\tTweaked the user interface a bit.\n\tDropped support for Windows XP, 2000 and 2003 (they are no longer\n\t\tsupported by GTK+).\n\tFixed detection of newer system-installed smartmontools under Windows.\n\nVersion 0.9.0, released on 2017-05-11\n\tImplemented (untested) support for Linux-based Areca controllers with\n\t\tenclosures.\n\tImplemented (untested) support for Windows-based Areca controllers (thanks\n\t\tto Richard Kagerer).\n\tImplemented (untested) support for Linux-based HP controllers with cciss\n\t\tand hpsa/hpahcisr drivers (thanks to Fabrice Bacchella).\n\tChanges in Preferences no longer fail silently until rescan/restart.\n\tBetter drive detection under Windows after removable drives are detached.\n\tWindows version is no longer marked as \"dpi aware\" since it's not\n\t\tsupported that well.\n\tDrive attribute descriptions have been updated (including clarifications\n\t\tfor SSDs).\n\tAdded support for SSD-only and HDD-only vendor attributes.\n\tDevices having only basic info can be displayed now in the info window.\n\tFixed BDRW drive detection (it was detected as a HDD).\n\tOther minor improvements.\n\tA number of issues have been fixed (including a crash).\n\nVersion 0.8.7, released on 2012-08-11\n\tImplemented support for Adaptec RAID 5805 controller and possibly other\n\t\tAdaptec models as well (Linux).\n\tImplemented support for Areca RAID controller detection under Linux\n\t\t(untested).\n\tImplemented support for 3ware 3w-sas-supported (twl) RAID controller\n\t\tdetection under Linux (untested).\n\tImplemented support for systems with several different 3ware controllers\n\t\t(twa/twe/twl).\n\tFixed invalid parsing of tw_cli output which caused non-detection of\n\t\tdrives and controllers with controller or port numbers greater than 9\n\t\tif tw_cli was found (3ware linux, windows).\n\tThe duplicate drives are no longer shown for some Intel matrix controllers\n\t\tunder Windows; Intel RAID controllers are fully supported under\n\t\tWindows now.\n\tAdded options to show device name and serial number under drive icons.\n\tUpdated SMART attribute definitions and added warnings for SSD lifetime\n\t\tattributes.\n\tgsmartcontrol-root has a new argument \"--desktop=...\" which replaces the\n\t\told positional argument; compatibility with the old invocation syntax\n\t\thas been retained.\n\tCompletely documented the source using doxygen tags.\n\tAdded support for Fedora's consolehelper.\n\tMade GSmartControl DPI-aware under Windows.\n\tFixed compilation issues with clang and gcc 4.7, as well as glib 2.31.x\n\t\tand newer.\n\tFixed minor issues in NSIS installer.\n\tFixed other minor bugs and made minor improvements.\n\nVersion 0.8.6, released on 2011-06-12\n\tSupport detecting drives behind 3ware controllers (Linux, Windows),\n\t\tincluding tw_cli/cx/px mode in Windows. Having tw_cli is recommended\n\t\tbut not required.\n\tAdded support for specifying -d option and extra parameters via command\n\t\tline, \"Add Device\" and \"Preferences\" dialogs. This change effectively\n\t\tadds full support for multiple drives behind a single device name.\n\tBeesu and su-to-root are supported by gsmartcontrol-root script now.\n\tCompletely revamped the attribute database and its handling, should be a\n\t\tlot more usable and forward-compatible now; SSD attributes are also\n\t\tincluded.\n\tIn-program help has been expanded considerably.\n\tGeneral improvements to user interface have been implemented (better GNOME\n\t\tHIG compliance, better tooltips, dialogs, etc.).\n\tAdded ability to show smartctl output for devices whose info could not be\n\t\tparsed fully.\n\tAttributes in \"brief\" format are supported now.\n\tThe parser has been updated to reflect the recent changes in smartctl.\n\tQuit and rescan operations are no longer denied without confirmation when\n\t\ttests are running.\n\tThe Windows NSIS installer has been vastly improved.\n\tBetter support for Windows Vista and 7.\n\tChanged copyright notices for files with Whatever License to use Unlicense.\n\tAdded other minor features and fixed quite a few bugs.\n\nVersion 0.8.5, released on 2009-09-05\n\tGSmartControl now uses XDG config directory for per-user configuration on\n\t\tUNIX and CSIDL_PROFILE directory on Windows. Existing configuration is\n\t\tmigrated automatically.\n\tThe names are shown correctly for unsupported devices even with the latest\n\t\tsmartctl snapshots now.\n\tSmartctl SVN revision is shown (if available).\n\tThe progress bars update properly when parallel tests are run.\n\tWindows: GSmartControl should be able to operate on any valid filesystem\n\t\tpath (not just locale-representable ones).\n\tWindows: GSmartControl is now officially compilable on x86_64 via mingw64.\n\tFixed compilation under very old gtkmm/libglademm, and with gcc 4.4.\n\tFixed parsing of multiple error types in SMART error log.\n\tAdded minor features and fixed miscellaneous bugs.\n\nVersion 0.8.4, released on 2009-03-23\n\tLinux Software RAID devices are blacklisted now. (backported from Debian).\n\tAttributes tab is before the capabilities tab now.\n\tA man page has been generously contributed by Giuseppe Iuculano\n\t\t<giuseppe@iuculano.it>.\n\tSmartctl version now includes the CVS snapshot date (if available).\n\tWindows: Look for \"smartctl-nc.exe\" instead of \"smartctl.exe\" by default.\n\tWindows: Use smartmontools-supplied smartctl-nc.exe by default (if found).\n\tOther minor changes (mainly Debian backports).\n\nVersion 0.8.3, released on 2008-12-27\n\tA random \"Smartctl returned an empty output\" error on Windows was fixed.\n\t\tThanks to Zurab Khetsuriani for testing.\n\tFixed a parser issue which prevented running self-tests in Windows.\n\tThe supplied icon (hopefully) shows correctly in Windows 2000 now.\n\tThis release adds an official support for Windows 2000 SP4.\n\tAdded scripts to allow GSmartControl to read smartctl data from\n\t\tcron-generated files. This allows users to read somewhat recent\n\t\tsmartctl information without having to run GSmartControl as root.\n\t\tGenerously contributed by Alex Butcher <alex dot butcher 'at'\n\t\tassursys.co.uk>.\n\tConfigure script correctly aborts instead of printing a warning if gtkmm\n\t\tor libglademm (if needed) is not found.\n\tConfigure script now accepts --enable-windows-console,\n\t\t--disable-abort-if-no-gtkmm, --disable-abort-if-no-glade-reader,\n\t\tas well as Windows-supporting \"auto\" for --enable-nsis-wine and\n\t\t--with-nsis.\n\tConfigure's --with-win32-env has been renamed to --with-windows-dlls.\n\tThe \"About\" dialog shows version information now.\n\tMinor bugs were fixed.\n\nVersion 0.8.2, released on 2008-12-10\n\tFixed gsmartcontrol_root.sh script to support distributions with no\n\t\t/usr/sbin in their users' paths (thanks to Erwan Velu).\n\tAdded desktop auto-detection to gsmartcontrol_root.sh script. This allows\n\t\tus to use only one desktop file (thanks to Erwan Velu).\n\tAdded Debian package directory (named \"debian.dist\" for now to avoid\n\t\tcontrol file conflicts with Build Service).\n\tRenamed gsmartcontrol_root.sh to gsmartcontrol-root, to make Debian happy.\n\tAdded make targets for Windows packages (zip and NSIS).\n\tFixed Windows-related issues (Vista is fully supported now).\n\tFixed minor bugs:\n\t\tSmartctl parser is win32-locale-aware now.\n\t\tNo more unnecessary parsing.\n\t\tNo segfault on exit under Windows and Solaris.\n\t\tA friendlier message is displayed if smartctl was not found.\n\t\tNo highlighted labels when switching tabs in Information window.\n\nVersion 0.8.1, released on 2008-11-11\n\tDisabled Linux \"by-id\" drive detection - it's unreliable on some broken\n\t\tsystems.\n\tAdded some more attribute descriptions.\n\tOur names for attributes override smartctls' now.\n\tAdded a proper \"Add Device\" dialog for Windows.\n\tAdded an icon and resource file for Windows.\n\tFixed minor bugs.\n\nVersion 0.8.0rc4, released on 2008-10-20\n\tFreeBSD support (tested with DesktopBSD 1.6 (FreeBSD 6.3) / x86).\n\tNetBSD support (tested with NetBSD 4.0.1 / x86).\n\tOpenBSD support (tested with OpenBSD 4.3 / x86-64 / gcc-3.3.5).\n\tSolaris/gcc support (tested with Solaris 10 / x86 / gcc-3.4.3 / blastwave).\n\tSolaris/sunstudio support (tested with Solaris 10 / x86 / sunstudio12 /\n\t\tsunfreeware).\n\tCode to support Windows, Mac OS X, QNX.\n\tSupport of older pcre versions (at least 4.5, maybe older too).\n\tAdded gsmartcontrol_root.sh script to easily run gsmartcontrol as root.\n\tImproved .desktop files.\n\tLicense for tests and examples is legally more correct now.\n\tMinor bugfixes.\n\nVersion 0.8.0rc3, released on 2008-10-08\n\tAdded support for udevless Linux distributions by providing a fallback\n\t\t/proc/partitions-based drive detection method. The new method adds\n\t\tsupport for Linux 2.4 and older systems. Thanks to Paul Marwick for\n\t\treporting and testing.\n\tFixed invalid error messages with directory-related operations.\n\tFixed invalid verbosity levels of console output of debug messages in\n\t\tnon-debug builds.\n\tImproved rpm spec file - now supports building on OpenSUSE build\n\t\tservice with various distributions as targets.\n\nVersion 0.8.0rc2, released on 2008-10-05\n\tFixed compilation under gcc 4.3. Thanks to Peter Linnell for reporting.\n\tRemoved test data (there's a lot of it and it's unnecessary).\n\nVersion 0.8.0rc1, released on 2008-10-01\n\tInitial public release.\n"
  },
  {
    "path": "README.md",
    "content": "\n# GSmartControl\n\n***Hard disk drive and SSD health inspection tool***\n\n[![Generic badge](https://img.shields.io/badge/Homepage-gsmartcontrol.shaduri.dev-brightgreen.svg)](https://gsmartcontrol.shaduri.dev)\n[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/ashaduri/gsmartcontrol?label=Version)](https://gsmartcontrol.shaduri.dev/downloads)\n[![license: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)\n[![Platforms](https://img.shields.io/badge/Platforms-linux%20%7C%20windows%20%7C%20macos%20%7C%20*bsd-blue)](https://gsmartcontrol.shaduri.dev/software-requirements)\n[![Packaging status](https://repology.org/badge/tiny-repos/gsmartcontrol.svg?header=Software%20distributions%20and%20repositories)](https://repology.org/project/gsmartcontrol/versions)\n[![Codacy Badge](https://api.codacy.com/project/badge/Grade/528f4f7aaf0e446abf7e55d2affc7bec)](https://app.codacy.com/gh/ashaduri/gsmartcontrol?utm_source=github.com&utm_medium=referral&utm_content=ashaduri/gsmartcontrol&utm_campaign=Badge_Grade_Settings)\n\n---\n\n[GSmartControl](https://gsmartcontrol.shaduri.dev)\nis a graphical user interface for smartctl (from [smartmontools](https://www.smartmontools.org/)\npackage), which is a tool for\nquerying and controlling [SMART](https://en.wikipedia.org/wiki/Self-Monitoring,_Analysis_and_Reporting_Technology)\n(Self-Monitoring, Analysis, and Reporting\nTechnology) data on modern hard disk and solid-state drives. It allows you to\ninspect the drive's SMART data to determine its health, as well as run various\ntests on it.\n\n\n## Downloads\n\nThe [Downloads](https://gsmartcontrol.shaduri.dev/downloads) page contains\nall the available packages of GSmartControl.\n\n\n## Features\n- automatically reports and highlights any anomalies;\n- allows enabling/disabling SMART;\n- supports configuration of global and per-drive options for smartctl;\n- performs SMART self-tests;\n- displays drive identity information, capabilities, attributes, device statistics, etc.;\n- can read in smartctl output from a saved file, interpreting it as a read-only virtual device;\n- works on most smartctl-supported operating systems;\n- has extensive help information.\n\n\n### Supported Hardware\n\nGSmartControl supports SATA, PATA, and NVMe drives, as well as drives\nbehind some USB bridges and RAID controllers.\nPlease see the\n[Supported Hardware](https://gsmartcontrol.shaduri.dev/supported-hardware) page\nfor more information.\n\n\n### Supported Platforms\n\nGSmartControl supports all major desktop operating systems, including\nLinux, Windows, macOS, FreeBSD, and other BSD-style operating systems.\nPlease see the\n[Software Requirements](https://gsmartcontrol.shaduri.dev/software-requirements) page\nfor more information.\n\n\n## Copyright and Licensing\n\nGSmartControl is Copyright (C) 2008 - 2025 Alexander Shaduri [ashaduri@gmail.com](mailto:ashaduri@gmail.com) and contributors.\n\nGSmartControl is licensed under the terms of\n[GNU General Public License Version 3](https://www.gnu.org/licenses/gpl-3.0.en.html).\n\nThis program is free software: you can redistribute it and/or modify it under\nthe terms of version 3 of the GNU General Public License as published by the\nFree Software Foundation.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY\nWARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR\nA PARTICULAR PURPOSE. See the GNU General Public Licenses for more details.\n\nThis product includes icons from Oxygen Icons copyright [KDE](https://kde.org)\nand licensed under the [GNU LGPL version 3](https://www.gnu.org/licenses/lgpl-3.0.en.html) or later.\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Supported Versions\n\nThe following versions are supported with security updates:\n\n| Version  | Supported          |\n|----------| ------------------ |\n| git main | :white_check_mark: |\n| 2.0.x    | :white_check_mark: |\n| < 2.0.0 | :x:                |\n\n## Reporting a Vulnerability\n\nPlease report security vulnerabilities privately to\nAlexander Shaduri [ashaduri@gmail.com](mailto:ashaduri@gmail.com).\n"
  },
  {
    "path": "configure-dev",
    "content": "#!/bin/bash\n###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2012 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n# Configure a build directory for development\n\n\nfunction print_usage() {\n\techo -e \"Usage:\"\n\techo -e \"    ${0} [-t <toolchain_name>] [-c <compiler>] [-b <build_type>]\"\n\techo -e \"    [-s <source_dir>] [-g <generator>] [-o <cmake_options>]\"\n\techo -e \"\\nDetails:\"\n\techo -e \"-t <toolchain_name> - toolchain_name can be e.g. \\\"win32-mingw64\\\".\"\n\techo -e \"    A toolchain file \\\"toolchains/<toolchain_name>.cmake\\\" will be used.\"\n\techo -e \"-c <compiler> - compiler is one of:\"\n\techo -e \"    gcc, gcc-<version>, clang, intel.\"\n\techo -e \"    The gcc version is the suffix of gcc and g++ executables, e.g. \\\"4.8\\\" in\"\n\techo -e \"    \\\"gcc-4.8\\\".\"\n\techo -e \"    If unspecified, CC and CXX environment variables are used.\"\n\techo -e \"-b <build_type> - build_type is one of:\"\n\techo -e \"    Debug, Release, RelWithDebInfo, MinSizeRel, none. The default is Debug.\"\n\techo -e \"-s <source_dir> - source_dir is \\\"..\\\" by default.\"\n\techo -e \"-g <cmake_generator> - cmake uses \\\"Unix Makefiles\\\" by default.\"\n\techo -e \"-o <cmake_options> - options to pass directly to cmake.\"\n}\n\ncompiler=\"\"\nbuild_type=\"\";\nsource_dir=\"..\"\ntoolchain_name=\"\"\ngenerator=\"\"\ncmake_options=\"\"\n\nwhile getopts \"t:c:b:s:g:o:h:\" opt; do\n\tcase $opt in\n\t\tt)\n\t\t\techo \"Requested toolchain: $OPTARG\"\n\t\t\ttoolchain_name=\"$OPTARG\";\n\t\t\t;;\n\t\tc)\n\t\t\techo \"Requested compiler: $OPTARG\"\n\t\t\tcompiler=\"$OPTARG\";\n\t\t\t;;\n\t\tb)\n\t\t\techo \"Requested build type: $OPTARG\"\n\t\t\tbuild_type=\"$OPTARG\";\n\t\t\t;;\n\t\ts)\n\t\t\techo \"Requested source directory type: $OPTARG\"\n\t\t\tsource_dir=\"$OPTARG\";\n\t\t\t;;\n\t\tg)\n\t\t\techo \"Requested generator: $OPTARG\"\n\t\t\tgenerator=\"$OPTARG\";\n\t\t\t;;\n\t\to)\n\t\t\techo \"Requested cmake options: $OPTARG\"\n\t\t\tcmake_options=\"$OPTARG\";\n\t\t\t;;\n\t\th)\n\t\t\tprint_usage;\n\t\t\texit 1;\n\t\t\t;;\n\t\t\\?)\n\t\t\techo \"Invalid option: $opt\";\n\t\t\tprint_usage;\n\t\t\texit 1;\n\t\t\t;;\n\tesac\ndone\n\n\nif [ \"$compiler\" != \"\" ] && [ \"$toolchain_name\" != \"\" ]; then\n\techo \"Error: Conflicting options -c and -t specified.\"\n\texit 1;\nfi\n\n\ncmake_flags=\"-Wdev\";\n\nif [ \"$build_type\" = \"none\" ]; then\n\tbuild_type=\"\";\nfi\nif [ \"$build_type\" != \"\" ]; then\n  cmake_flags=\"$cmake_flags -DCMAKE_BUILD_TYPE=$build_type\";\nfi\n\nif [ \"$generator\" != \"\" ]; then\n\tcmake_flags=\"$cmake_flags -G${generator}\";\nfi\n\n\nstatus=1\n\n\n# Toolchain mode\nif [ \"$toolchain_name\" != \"\" ]; then\n\tcmake_flags=\"$cmake_flags -DCMAKE_TOOLCHAIN_FILE='${source_dir}/data/cmake/toolchains/${toolchain_name}.cmake'\"\n\n\techo \"Running:\"\n\techo \"cmake $cmake_flags $source_dir\";\n\n\tcmake $cmake_flags $cmake_options $source_dir\n\tstatus=$?\n\nelse  # Compiler mode\n\tc_compiler=\"\";\n\tcxx_compiler=\"\";\n\n\t# Detect gcc suffix like \"-4.7\"\n\tif [ \"${compiler:0:4}\" = \"gcc-\" ]; then\n\t\tsuffix=${compiler:4};\n\t\tc_compiler=\"gcc-${suffix}\";\n\t\tcxx_compiler=\"g++-${suffix}\";\n\t\tcompiler=\"gcc\";\n\tfi\n\n\tcase $compiler in\n\t\tgcc)\n\t\t\tif [ \"$c_compiler\" = \"\" ]; then\n\t\t\t\tc_compiler=\"gcc${machine_bits_switch}\";\n\t\t\t\tcxx_compiler=\"g++${machine_bits_switch}\";\n\t\t\tfi\n\t\t\t;;\n\t\tintel)\n\t\t\tif [ $machine_bits_precise = \"64\" ]; then\n\t\t\t\tc_compiler=\"icc64\";\n\t\t\t\tcxx_compiler=\"icpc64\";\n\t\t\telse\n\t\t\t\tc_compiler=\"icc32\";\n\t\t\t\tcxx_compiler=\"icpc32\";\n\t\t\tfi\n\t\t\t;;\n\t\tclang)\n\t\t\tc_compiler=\"clang${machine_bits_switch}\";\n\t\t\tcxx_compiler=\"clang++${machine_bits_switch}\";\n\t\t\t;;\n\t\t\"\")\n\t\t\tc_compiler=\"$CC\"\n\t\t\tcxx_compiler=\"$CXX\"\n\t\t\t;;\n\t\t*)\n\t\t\techo \"Unsupported compiler given; use CC and CXX instead.\"\n\t\t\texit 1;\n\t\t\t;;\n\tesac\n\n\techo \"Running:\"\n\techo \"CC=\\\"$c_compiler\\\" CXX=\\\"$cxx_compiler\\\" cmake $cmake_flags $source_dir\";\n\n\tCC=\"$c_compiler\" CXX=\"$cxx_compiler\" cmake $cmake_flags $cmake_options $source_dir\n\tstatus=$?\nfi\n\n\nexit $status\n\n"
  },
  {
    "path": "contrib/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nif (NOT WIN32)\n\tinstall(DIRECTORY \"${CMAKE_SOURCE_DIR}/contrib\" TYPE DOC\n\t\tPATTERN \"CMakeLists.txt\" EXCLUDE)\nendif()\n"
  },
  {
    "path": "contrib/cron-based_noadmin/README",
    "content": "\nThis directory contains scripts and an example crontab to allow GSmartControl\nto read smartctl data from cron-generated files. This allows users to read\nsomewhat recent smartctl information without having to run GSmartControl as\nroot.\n\nPut the scripts into /usr/local/bin, run GSmartControl and set smartctl path\nto \"/usr/local/bin/smartctl_subst.sh\" (without quotes). An example crontab\nruns cron_gather_smart.sh script every 10 minutes, storing its output to\n\"/var/run/smart-$devname\". The smartctl_subst.sh script prints the contents of\nthis file instead of running smartctl, thus eliminating the need to run\nGSmartControl as root when reading SMART values.\n\nNote: These scripts provide read-only capabilities only, they don't allow\nchanging SMART settings or running self-tests.\n\nContributed by Alex Butcher <alex dot butcher 'at' assursys.co.uk>.\n"
  },
  {
    "path": "contrib/cron-based_noadmin/cron_gather_smart.sh",
    "content": "#!/bin/bash\n###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2008 - 2024 Alex Butcher <alex dot butcher 'at' assursys.co.uk>\n#   (C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\ndevice=\"$1\";\n\nif [ \"$device\" = \"\" ]; then\n\techo \"Usage: $0 <device>\"\n\texit 1;\nfi\n\ndev_base=$(basename \"$device\")\nout_file=/var/run/smart-\"$dev_base\"\n\n# Change the path to smartctl if necessary.\nsmartctl -x --json=o \"$device\" > \"$out_file\" 2>&1\n\nchmod 644 \"$out_file\"\n"
  },
  {
    "path": "contrib/cron-based_noadmin/crontab.example",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2008 - 2021 Alex Butcher <alex dot butcher 'at' assursys.co.uk>\n#   (C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n*/10 * * * * root /usr/local/bin/cron-smart.sh /dev/sda\n*/10 * * * * root /usr/local/bin/cron-smart.sh /dev/sdb\n"
  },
  {
    "path": "contrib/cron-based_noadmin/smartctl_subst.sh",
    "content": "#!/bin/bash\n###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2008 - 2022 Alex Butcher <alex dot butcher 'at' assursys.co.uk>\n#   (C) 2008 - 2022 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nif [ \"$1\" = \"-V\" ]; then\n\tsmartctl -V 2>&1\n\texit\nfi\n\nwhile [ $# -ge 1 ]; do\n\tdevice=\"$1\"\n\tshift\ndone\n\nif [ \"$device\" = \"\" ]; then\n\techo \"Usage: $0 <device>\"\n\texit 1;\nfi\n\ndev_base=$(basename \"$device\")\nout_file=/var/run/smart-\"$dev_base\"\n\ncat \"$out_file\"\n"
  },
  {
    "path": "data/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n\n# Generate files\nconfigure_file(\"gsmartcontrol.appdata.in.xml\" \"gsmartcontrol.appdata.xml\" ESCAPE_QUOTES @ONLY)\nconfigure_file(\"gsmartcontrol.in.desktop\" \"gsmartcontrol.desktop\" ESCAPE_QUOTES @ONLY)\nconfigure_file(\"gsmartcontrol-root.in.sh\" \"gsmartcontrol-root.sh\" ESCAPE_QUOTES @ONLY)\nconfigure_file(\"org.gsmartcontrol.in.policy\" \"org.gsmartcontrol.policy\" ESCAPE_QUOTES @ONLY)\n\n# Install app icons\nif (NOT WIN32)\n\tforeach(dir 16 22 24 32 48 64 128 256)\n\t\tinstall(FILES \"${dir}/gsmartcontrol.png\"\n\t\t\tDESTINATION \"${CMAKE_INSTALL_DATADIR}/icons/hicolor/${dir}x${dir}/apps/\")\n\tendforeach()\n\t# Don't run update-icon-cache, cpack is mostly for packaging.\n#\tinstall(CODE \"execute_process (COMMAND gtk-update-icon-cache-3.0 -t -f \\\"@CMAKE_INSTALL_DATADIR@/icons/hicolor\\\")\")\nendif()\n\nif (WIN32)\n\t# .ico file\n\tinstall(FILES gsmartcontrol.ico DESTINATION .)\nendif()\n\nset (DATA_ICONS\n\ticons/drive-optical.png\n\ticons/drive-harddisk.png\n\ticons/drive-removable-media-usb.png\n)\n\n# Install internal icons\nif (WIN32)\n\tinstall(FILES ${DATA_ICONS} DESTINATION icons)\nelse()\n\tinstall(FILES ${DATA_ICONS}\n\t\tDESTINATION \"${CMAKE_INSTALL_DATADIR}/gsmartcontrol/icons\")\nendif()\n\n# Desktop file\nif (NOT WIN32)\n\tinstall(FILES \"${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol.desktop\"\n\t\tDESTINATION \"${CMAKE_INSTALL_DATADIR}/applications/\")\n\n\t# Appdata file\n\tinstall(FILES \"${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol.appdata.xml\"\n\t\tDESTINATION \"${CMAKE_INSTALL_DATADIR}/metainfo/\")\n\n\t# PolKit file\n\tinstall(FILES \"${CMAKE_CURRENT_BINARY_DIR}/org.gsmartcontrol.policy\"\n\t\tDESTINATION \"${CMAKE_INSTALL_DATADIR}/polkit-1/actions/\")\n\n\t# Man pages\n\tinstall(FILES \"man1/gsmartcontrol.1\" DESTINATION \"${CMAKE_INSTALL_MANDIR}/man1\")\n\tinstall(FILES \"man1/gsmartcontrol.1\" DESTINATION \"${CMAKE_INSTALL_MANDIR}/man1\" RENAME \"gsmartcontrol-root.1\")\n\n\t# Scripts (this goes to bin, not sbin, as it doesn't require root privileges before running)\n\tinstall(PROGRAMS \"${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol-root.sh\" TYPE BIN RENAME \"gsmartcontrol-root\")\nendif()\n\n"
  },
  {
    "path": "data/create_ico.sh",
    "content": "#!/bin/bash\n\npng_name=\"gsmartcontrol.png\"\n\n# needs imagemagick\nconvert 16/$png_name 22/$png_name 24/$png_name 32/$png_name \\\n48/$png_name 64/$png_name 128/$png_name 256/$png_name gsmartcontrol.ico\n"
  },
  {
    "path": "data/doxygen/doxy_main_page.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n\n// This file is for generating documentation only. Do NOT include it.\n/// \\file\n/// \\author Alexander Shaduri\n\n/**\n\\mainpage GSmartControl Index\n\n\n\\section intro_section Introduction\nThis is GSmartControl internal documentation.\n*/\n\n// Group definitions and titles.\n\n/**\n\\addtogroup applib Application-Specific Components\n\\addtogroup applib_examples Examples of Application-Specific Components\n\\addtogroup hz HZ Common System Components\n\\addtogroup hz_examples Examples for HZ Common System Components\n\\addtogroup libdebug LibDebug Flexible Debug Logging\n\\addtogroup libdebug_examples Examples for LibDebug Flexible Debug Logging\n\\addtogroup rconfig Resource Manager-Based Configuration\n\\addtogroup rconfig_examples Examples for Resource Manager-Based Configuration\n\\addtogroup gsc GSmartControl Application\n*/\n\n\n"
  },
  {
    "path": "data/gsmartcontrol-root.in.sh",
    "content": "#!/bin/bash\n###############################################################################\n# License: Zlib\n# Copyright:\n#   (C) 2008 - 2022 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n# Run gsmartcontrol with root, asking for root password first.\n# export GSMARTCONTROL_SU to override a su command (e.g. \"kdesu -c\").\n\nEXEC_BIN=\"@CMAKE_INSTALL_FULL_SBINDIR@/gsmartcontrol\";\nprogram_name=\"gsmartcontrol\"\n\n\n# Preserve quotes in arguments\nfinal_args_quoted=\"\";\nfor i in \"$@\";do\n    final_args_quoted=\"$final_args_quoted \\\"${i//\\\"/\\\\\\\"}\\\"\";\ndone;\n\n\nDESKTOP=\"auto\";\n\n# Compatibility with old syntax:\n# gsmartcontrol-root [<desktop> [program_options]]\nif [ \"$1\" == \"auto\" ] || [ \"$1\" == \"kde\" ] || [ \"$1\" == \"gnome\" ] || [ \"$1\" == \"other\" ]; then\n\tDESKTOP=\"$1\";\n\tshift;  # remove $1\nelse\n\t# New syntax:\n\t# gsmartcontrol-root [--desktop=<auto|kde|gnome|other>] [program_options]\n\n\tfor arg in \"$@\"; do\n\t\tcase $arg in\n\t\t\t--desktop=*)\n\t\t\t\tDESKTOP=\"${arg#*=}\";\n\t\t\t\tfinal_args_quoted=\"${final_args_quoted/\\\"$arg\\\"/}\";\n\t\t\t\t;;\n\t\t\t*)\n\t\t\t\t# unknown option\n\t\t\t\t;;\n\t\tesac\n\tdone\nfi\n\nif [ \"$DESKTOP\" != \"auto\" ] && [ \"$DESKTOP\" != \"kde\" ] && \\\n\t\t[ \"$DESKTOP\" != \"gnome\" ] && [ \"$DESKTOP\" != \"other\" ]; then\n\techo \"Usage: $0 [--desktop=<auto|kde|gnome|other>] [<${program_name}_options>]\";\n\texit 1;\nfi\n\n\n# Auto-detect current desktop if auto was specified.\nif [ \"$DESKTOP\" = \"auto\" ]; then\n\t# KDE_SESSION_UID is present on kde3 and kde4.\n\t# Note that it may be empty (but still set)\n\tif [ \"${KDE_SESSION_UID+set}\" = \"set\" ]; then\n\t\tDESKTOP=\"kde\";\n\t# same with gnome\n\telif [ \"${GNOME_DESKTOP_SESSION_ID+set}\" = \"set\" ]; then\n\t\tDESKTOP=\"gnome\";\n\telse\n\t\tDESKTOP=\"other\";\n\tfi\nfi\n\n# echo $DESKTOP;\n\n\n\n\n\n# They're basically the same, only the order is different.\n# pkexec is for PolKit.\n# sux requires xterm to ask for the password.\n# xdg-su is basically like this script, except worse :)\n# su-to-root is a debian/ubuntu official method (although gksu is available).\ngnome_sus=\"pkexec su-to-root gnomesu gksu kdesu beesu xdg-su sux\";\nkde_sus=\"pkexec su-to-root kdesu gnomesu gksu beesu xdg-su sux\";\nother_sus=\"$gnome_sus\";\n\n\ncandidates=\"\";\nfound_su=\"\"\n\nif [ \"$DESKTOP\" = \"gnome\" ]; then\n\tcandidates=\"$gnome_sus\";\nelif [ \"$DESKTOP\" = \"kde\" ]; then\n\tcandidates=\"$kde_sus\";\nelif [ \"$DESKTOP\" = \"other\" ]; then\n\tcandidates=\"$other_sus\";\nfi\n\nif [ \"$GSMARTCONTROL_SU\" = \"\" ]; then\n\tfor subin in $candidates; do\n\t\twhich \"$subin\" &>/dev/null\n\t\tif [ $? -eq 0 ]; then\n\t\t\tfound_su=\"$subin\";\n\t\t\tbreak;\n\t\tfi\n\tdone\n\n\tif [ \"$found_su\" = \"\" ]; then\n\t\txmessage \"Error launching ${program_name}: No suitable su mechanism found.\nTry installing PolKit, kdesu, gnomesu, gksu, beesu or sux first.\";\n\t\texit 1;\n\tfi\nfi\n\n\n# gnomesu and gksu (but not kdesu, not sure about others) fail to adopt\n# root's PATH. Since the user's PATH may not contain /usr/sbin (with smartctl)\n# on some distributions (e.g. mandriva), add it manually. We also add\n# /usr/local/sbin, since that's the default location of custom-compiled smartctl.\n# Add these directories _before_ existing PATH, so that the user is not\n# tricked into running it from some other path (we're running as root with\n# the user's env after all).\n# Add sbindir as well (freebsd seems to require it).\n# Note that beesu won't show a GUI login box if /usr/sbin is before /usr/bin,\n# so add it first as well.\nEXTRA_PATHS=\"/usr/bin:/usr/sbin:/usr/local/sbin:@CMAKE_INSTALL_FULL_SBINDIR@\";\nexport PATH=\"$EXTRA_PATHS:$PATH\"\n\n\n# echo $found_su;\n\n# Examples:\n# gnomesu -c 'gsmartcontrol --no-scan'\n# kdesu -c 'gsmartcontrol --no-scan'\n# beesu -P 'gsmartcontrol --no-scan'\n# su-to-root -X -c 'gsmartcontrol --no-scan'\n# xterm -e sux -c 'gsmartcontrol --no-scan'  # sux asks for password in a terminal\n\n\nfull_cmd=\"\";\n\nif [ \"$GSMARTCONTROL_SU\" != \"\" ]; then\n\tfull_cmd=\"$GSMARTCONTROL_SU '$EXEC_BIN $final_args_quoted'\";\n\nelif [ \"$found_su\" = \"pkexec\" ]; then\n\tif [ \"$GDK_SCALE\" != \"\" ]; then\n\t\tfinal_args_quoted=\"$final_args_quoted --gdk-scale='$GDK_SCALE'\"\n\tfi\n\tif [ \"$GDK_DPI_SCALE\" != \"\" ]; then\n\t\tfinal_args_quoted=\"$final_args_quoted --gdk-dpi-scale='$GDK_DPI_SCALE'\"\n\tfi\n\tfull_cmd=\"pkexec --disable-internal-agent $EXEC_BIN $final_args_quoted\";\n\nelif [ \"$found_su\" = \"sux\" ]; then\n\tfull_cmd=\"xterm -e sux -c '$EXEC_BIN $final_args_quoted'\";\n\nelif [ \"$found_su\" = \"gksu\" ]; then\n\tfull_cmd=\"$found_su '$EXEC_BIN $final_args_quoted'\";\n\nelif [ \"$found_su\" = \"beesu\" ]; then\n\tfull_cmd=\"$found_su -P '$EXEC_BIN $final_args_quoted'\";\n\nelif [ \"$found_su\" = \"su-to-root\" ]; then\n\tfull_cmd=\"$found_su -X -c '$EXEC_BIN $final_args_quoted'\";\n\nelse  # gnomesu, kdesu, xdg-su\n\tfull_cmd=\"$found_su -c '$EXEC_BIN $final_args_quoted'\";\nfi\n\n\n# echo $full_cmd\neval \"$full_cmd\"\n\n"
  },
  {
    "path": "data/gsmartcontrol.appdata.in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<component type=\"desktop\">\n\t<id>gsmartcontrol</id>\n\t<metadata_license>CC0-1.0</metadata_license>\n\t<project_license>GPL-3.0</project_license>\n\t<name>GSmartControl</name>\n\t<summary>Hard Disk and SSD Health Inspection</summary>\n\t<description>\n\t\t<p>\n\t\t\tGSmartControl is a graphical user interface for smartctl, which is a tool for\n\t\t\tquerying and controlling SMART (Self-Monitoring, Analysis, and Reporting\n\t\t\tTechnology) data in hard disk and solid-state drives. It allows you to inspect\n\t\t\tthe drive's SMART data to determine its health, as well as run various tests\n\t\t\ton it.\n\t\t</p>\n\t</description>\n\t<launchable type=\"desktop-id\">gsmartcontrol.desktop</launchable>\n\t<url type=\"homepage\">https://gsmartcontrol.shaduri.dev</url>\n\t<screenshots>\n\t\t<screenshot type=\"default\">\n\t\t\t<caption>Main window</caption>\n\t\t\t<image>https://gsmartcontrol.shaduri.dev/screenshots/main_ok.png</image>\n\t\t</screenshot>\n\t\t<screenshot>\n\t\t\t<caption>Drive identity information</caption>\n\t\t\t<image>https://gsmartcontrol.shaduri.dev/screenshots/info_identity.png</image>\n\t\t</screenshot>\n\t\t<screenshot>\n\t\t\t<caption>Attribute list of a failing drive</caption>\n\t\t\t<image>https://gsmartcontrol.shaduri.dev/screenshots/info_failing.png</image>\n\t\t</screenshot>\n\t</screenshots>\n</component>\n"
  },
  {
    "path": "data/gsmartcontrol.in.desktop",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n[Desktop Entry]\n\n# desktop file version\nVersion=1.0\nType=Application\n\nCategories=System;Monitor;\n\nName=GSmartControl\nName[ca]=GSControlIntel\nName[cs]=GSmartControl\nName[es]=GSmartControl\nName[id]=GSmartControl\nName[it]=GSmartControl\nName[ja]=GSmartControl\nName[pt_BR]=GSmartControl\nName[ru]=GSmartControl\nName[sk]=GSmartControl\nName[sv]=GSmartControl\nName[zh_CN]=GSmartControl\n# short description, usually shown in parentheses after Name\nGenericName=Hard Disk and SSD Health Inspection\nGenericName[ca]=Inspecció de salut del disc dur i de l'SSD\nGenericName[cs]=Prohlídka zdraví pevných a SSD disků\nGenericName[es]=Inspección de la salud de discos duros y SSD\nGenericName[fr]=Inspecteur de santé de disque dur et de SSD\nGenericName[id]=Pemeriksaan Kesehatan Cakram Keras dan SSD\nGenericName[it]=Ispezione della salute del disco rigido e dell'SSD\nGenericName[ja]=ハードディスクと SSD の健全状態の調査\nGenericName[pt_BR]=Inspeção da integridade de disco rígido e SSD\nGenericName[ru]=Проверка состояния жёстких и твердотельных дисков\nGenericName[sk]=Zdravotná inšpekcia pevného disku a SSD\nGenericName[zh_CN]=机械及固态硬盘健康检测\n\n# tooltip\nComment=Monitor and control SMART data on hard disk and solid-state drives\nComment[ca]=Monitoreu i controleu les dades SMART dels discs durs i dels discs sòlids.\nComment[cs]=Sleduje a nastavuje SMART data na pevném disku a na polovodičových discích\nComment[es]=Monitorice y controle datos de SMART sobre discos duros y unidades de estado sólido\nComment[fr]=Surveille et contrôle les données SMART des disques durs et des SSD\nComment[id]=Pantau dan kendalikan data cerdas pada cakram keras dan SSD\nComment[it]=Monitora e controlla i dati SMART sui dischi rigidi e a stato solido\nComment[ja]=ハードディスクや SSD 内の SMART データの監視と制御\nComment[pt_BR]=Monitorar e controlar dados do SMART em discos rígidos e unidades de estado sólido\nComment[ru]=Мониторинг и контроль данных SMART на жёстких и твердотельных дисках\nComment[sk]=Monitorovanie a kontrola údajov SMART na pevných diskoch a diskoch SSD\nComment[zh_CN]=监测及控制机械硬盘和固态硬盘上的SMART数据\n\n# If it's a name only, it looks for \"name.[png|xpm]\" file in\n# $XDG_DATA_DIRS/icons.\n# An absolute file path is also supported.\nIcon=gsmartcontrol\n\n# Run with root permissions. This should work with newer GNOME too.\n# It _should_ work with kde, but it hangs on mine, so use plain kdesu.\n#X-KDE-SubstituteUID=true\n#X-KDE-RootOnly=true\n\n# Run with root permissions.\nExec=\"@CMAKE_INSTALL_FULL_BINDIR@/gsmartcontrol-root\"\n"
  },
  {
    "path": "data/man1/gsmartcontrol.1",
    "content": ".TH GSmartControl \"1\" \"\" \"gsmartcontrol \" \"User Commands\"\n.SH NAME\nGSmartControl \\- Hard disk drive and SSD health inspection tool\n.SH SYNOPSIS\n\\fBgsmartcontrol\\fP [OPTIONS]\n\n\\fBgsmartcontrol\\-root\\fP [--desktop=\\fI<desktop>\\fP] [OPTIONS]\n.SH DESCRIPTION\n\\fBGSmartControl\\fP is a graphical user interface for smartctl (from\nsmartmontools), which is a tool for querying and controlling SMART\n(Self-Monitoring, Analysis, and Reporting Technology) data on modern hard disk\nand solid-state drives. It allows you to inspect the drive's SMART data to\ndetermine its health, as well as run various tests on it.\n.PP\nThis manual page documents briefly the \\fBgsmartcontrol\\fP and\n\\fBgsmartcontrol\\-root\\fP commands.\n.PP\n\\fBgsmartcontrol\\-root\\fP command launches \\fBgsmartcontrol\\fP with\nadministrative privileges. The \\fIdesktop\\fP argument specifies which desktop\nis currently running, for automatic selection of native su mechanism. Valid\nvalues for \\fIdesktop\\fP are \\fBauto\\fP, \\fBkde\\fP, \\fBgnome\\fP, \\fBother\\fP.\n\n.SH OPTIONS\n.SS \"Help Options:\"\n.TP\n\\fB\\-?\\fP, \\fB\\-\\-help\\fP\nShow help options\n.TP\n\\fB\\-\\-help\\-all\\fP\nShow all help options\n.TP\n\\fB\\-\\-help\\-gtk\\fP\nShow GTK+ options\n.TP\n\\fB\\-\\-help\\-debug\\fR\nShow logging options\n.SS \"Application Options:\"\n.TP\n\\fB\\-l\\fP, \\fB\\-\\-no\\-locale\\fP\nDon't use system locale\n.TP\n\\fB\\-V\\fP, \\fB\\-\\-version\\fP\nDisplay version information\n.TP\n\\fB\\-\\-no\\-scan\\fP\nDon't scan devices on startup\n.TP\n\\fB\\-\\-add\\-virtual\\fP\nLoad smartctl data from file, creating a virtual drive. You\ncan specify this option multiple times.\n.TP\n\\fB\\-\\-add\\-device\\fP\nAdd this device to device list. The format of the device is\n\\fI<device>::<type>::<extra_args>\\fP, where type and extra_args are optional.\nThis option is useful with \\fB\\-\\-no\\-scan\\fP to list certain drives only. You\ncan specify this option multiple times. Example:\\fR\n.nf\n\\-\\-add\\-device /dev/sda \\-\\-add\\-device /dev/twa0::3ware,2 \\-\\-add\\-device '/dev/sdb::::-T permissive'\n.fi\n.TP\n\\fB\\-\\-forget\\-devices\\fP\nForget all previously manually added devices\n.TP\n\\fB\\-\\-display\\fP=\\fIDISPLAY\\fP\nX display to use\n.TP\n\\fB\\-v\\fP, \\fB\\-\\-verbose\\fP\nEnable verbose logging; same as \\fB\\-\\-verbosity\\-level\\fP 5\n.TP\n\\fB\\-q\\fP, \\fB\\-\\-quiet\\fP\nDisable logging; same as \\fB\\-\\-verbosity\\-level\\fP 0\n.TP\n\\fB\\-b\\fP, \\fB\\-\\-verbosity\\-level\\fP\nSet verbosity level [0\\-5]\n.PP\n.SH COPYRIGHT\nCopyright \\(co 2008 \\- 2021 Alexander Shaduri <ashaduri@gmail.com>\n.PP\n.SH AUTHOR\nThis manual page was originally written by Giuseppe Iuculano\n<giuseppe@iuculano.it> for the Debian project.\n"
  },
  {
    "path": "data/org.gsmartcontrol.in.policy",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE policyconfig PUBLIC\n \"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN\"\n \"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd\">\n\n<!--\nLicense: BSD Zero Clause License file\nCopyright:\n\t(C) 2008 - 2021 GSmartControl developers\n-->\n\n<policyconfig>\n\n  <action id=\"org.gsmartcontrol\">\n    <message>Authentication is required to run GSmartControl</message>\n    <icon_name>gsmartcontrol</icon_name>\n    <defaults>\n      <allow_any>auth_admin</allow_any>\n      <allow_inactive>auth_admin</allow_inactive>\n      <allow_active>auth_admin</allow_active>\n    </defaults>\n    <annotate key=\"org.freedesktop.policykit.exec.path\">@CMAKE_INSTALL_FULL_SBINDIR@/gsmartcontrol</annotate>\n    <annotate key=\"org.freedesktop.policykit.exec.allow_gui\">true</annotate>\n  </action>\n\n</policyconfig>\n"
  },
  {
    "path": "dependencies/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n# Disable all warnings\nif (${CMAKE_CXX_COMPILER_ID} STREQUAL Clang\n\t\tOR ${CMAKE_CXX_COMPILER_ID} STREQUAL GNU\n\t\tOR ${CMAKE_CXX_COMPILER_ID} STREQUAL AppleClang)\n\tadd_compile_options(-w)\nelseif (${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)\n\tadd_compile_options(/w)\nendif()\n\n\nfind_package(PkgConfig REQUIRED)  # pkg_check_modules()\n\n\n# Gtkmm.\n# Don't make it REQUIRED, we may want to build only the parsers\npkg_check_modules(Gtkmm REQUIRED IMPORTED_TARGET GLOBAL \"gtkmm-3.0>=3.0\")\nadd_library(app_gtkmm_interface INTERFACE)\ntarget_link_libraries(app_gtkmm_interface\n\tINTERFACE\n\t\tPkgConfig::Gtkmm\n)\ntarget_compile_definitions(app_gtkmm_interface\n\tINTERFACE\n\t\tENABLE_GLIB=1\n\t\tENABLE_GLIBMM=1\n\t\t# For porting to GTK4\n#\t\tGTK_DISABLE_DEPRECATED=1\n#\t\tGDK_DISABLE_DEPRECATED=1\n#\t\tGTKMM_DISABLE_DEPRECATED=1\n#\t\tGDKMM_DISABLE_DEPRECATED=1\n#\t\tGLIBMM_DISABLE_DEPRECATED=1\n#\t\tGIOMM_DISABLE_DEPRECATED=1\n)\n\n# Support pre-C++17 glibmm with throw(...) exception specifications\npkg_check_modules(Glibmm \"glibmm-2.4\")\nif (\"${Glibmm_VERSION}\" VERSION_LESS \"2.50.1\")\n\ttarget_compile_definitions(app_gtkmm_interface INTERFACE \"APP_GLIBMM_USES_THROW\")\n\tmessage(STATUS \"Enabling old glibmm throw(...) workaround\")\nendif()\n\n\n# Gettext libintl\nfind_package(Intl REQUIRED)\nadd_library(app_gettext_interface INTERFACE)\nif (Intl_INCLUDE_DIRS)\n\ttarget_include_directories(app_gettext_interface\n\t\tSYSTEM INTERFACE\n\t\t\t${Intl_INCLUDE_DIRS}\n\t)\nendif()\nif (Intl_LIBRARIES)\n\ttarget_link_libraries(app_gettext_interface\n\t\tINTERFACE\n\t\t\t${Intl_LIBRARIES}\n\t)\nendif()\n\n# Gettext binaries and macros.\n# See https://cmake.org/cmake/help/v3.15/module/FindGettext.html\n# find_package(Gettext REQUIRED)\n\n\nadd_subdirectory(catch2)\nadd_subdirectory(fmt)\nadd_subdirectory(nlohmann_json)\nadd_subdirectory(whereami)\nadd_subdirectory(tl_expected)\n"
  },
  {
    "path": "dependencies/catch2/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n#add_subdirectory(Catch2)\n\n\nadd_library(Catch2 INTERFACE)\n\n# Relative sources are allowed only since cmake 3.13.\ntarget_sources(Catch2 INTERFACE\n\t${CMAKE_CURRENT_SOURCE_DIR}/Catch2/single_include/catch2/catch.hpp\n)\n\ntarget_include_directories(Catch2\n\tSYSTEM INTERFACE\n\t\t\"${CMAKE_SOURCE_DIR}/dependencies/catch2/Catch2/single_include\"\n)\n\n\n"
  },
  {
    "path": "dependencies/catch2/Catch2/.clang-format",
    "content": "---\nAccessModifierOffset: '-4'\nAlignEscapedNewlines: Left\nAllowAllConstructorInitializersOnNextLine: 'true'\nBinPackArguments: 'false'\nBinPackParameters: 'false'\nBreakConstructorInitializers: AfterColon\nConstructorInitializerAllOnOneLineOrOnePerLine: 'true'\nDerivePointerAlignment: 'false'\nFixNamespaceComments: 'true'\nIncludeBlocks: Regroup\nIndentCaseLabels: 'false'\nIndentPPDirectives: AfterHash\nIndentWidth: '4'\nLanguage: Cpp\nNamespaceIndentation: All\nPointerAlignment: Left\nSpaceBeforeCtorInitializerColon: 'false'\nSpaceInEmptyParentheses: 'false'\nSpacesInParentheses: 'true'\nStandard: Cpp11\nTabWidth: '4'\nUseTab: Never\n\n...\n"
  },
  {
    "path": "dependencies/catch2/Catch2/.gitattributes",
    "content": "# This sets the default behaviour, overriding core.autocrlf\n* text=auto\n\n# All source files should have unix line-endings in the repository,\n# but convert to native line-endings on checkout\n*.cpp text\n*.h text\n*.hpp text\n\n# Windows specific files should retain windows line-endings\n*.sln text eol=crlf\n\n# Keep executable scripts with LFs so they can be run after being\n# checked out on Windows\n*.py text eol=lf\n\n\n# Keep the single include header with LFs to make sure it is uploaded,\n# hashed etc with LF\nsingle_include/**/*.hpp eol=lf\n# Also keep the LICENCE file with LFs for the same reason\nLICENCE.txt eol=lf\n"
  },
  {
    "path": "dependencies/catch2/Catch2/.gitignore",
    "content": "*.build\n*.pbxuser\n*.mode1v3\n*.ncb\n*.suo\nDebug\nRelease\n*.user\n*.xcuserstate\n.DS_Store\nxcuserdata\nCatchSelfTest.xcscheme\nBreakpoints.xcbkptlist\nprojects/VS2010/TestCatch/_UpgradeReport_Files/\nprojects/VS2010/TestCatch/TestCatch/TestCatch.vcxproj.filters\nprojects/VisualStudio/TestCatch/UpgradeLog.XML\nprojects/CMake/.idea\nprojects/CMake/cmake-build-debug\nUpgradeLog.XML\nResources/DWARF\nprojects/Generated\n*.pyc\nDerivedData\n*.xccheckout\nBuild\n.idea\n.vs\ncmake-build-*\nbenchmark-dir\n.conan/test_package/build\nbazel-*\n"
  },
  {
    "path": "dependencies/catch2/Catch2/LICENSE.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": "dependencies/catch2/Catch2/README.md",
    "content": "<a id=\"top\"></a>\n![catch logo](artwork/catch2-logo-small.png)\n\n[![Github Releases](https://img.shields.io/github/release/catchorg/catch2.svg)](https://github.com/catchorg/catch2/releases)\n[![Build Status](https://travis-ci.org/catchorg/Catch2.svg?branch=v2.x)](https://travis-ci.org/catchorg/Catch2)\n[![Build status](https://ci.appveyor.com/api/projects/status/github/catchorg/Catch2?svg=true)](https://ci.appveyor.com/project/catchorg/catch2)\n[![codecov](https://codecov.io/gh/catchorg/Catch2/branch/v2.x/graph/badge.svg)](https://codecov.io/gh/catchorg/Catch2)\n[![Try online](https://img.shields.io/badge/try-online-blue.svg)](https://wandbox.org/permlink/6JUH8Eybx4CtvkJS)\n[![Join the chat in Discord: https://discord.gg/4CWS9zD](https://img.shields.io/badge/Discord-Chat!-brightgreen.svg)](https://discord.gg/4CWS9zD)\n\n\n<a href=\"https://github.com/catchorg/Catch2/releases/download/v2.13.10/catch.hpp\">The latest version of the single header can be downloaded directly using this link</a>\n\n## Catch2 is released!\n\nIf you've been using an earlier version of Catch, please see the\nBreaking Changes section of [the release notes](https://github.com/catchorg/Catch2/releases/tag/v2.0.1)\nbefore moving to Catch2. You might also like to read [this blog post](https://levelofindirection.com/blog/catch2-released.html) for more details.\n\n## What's the Catch?\n\nCatch2 is a multi-paradigm test framework for C++. which also supports\nObjective-C (and maybe C).\nIt is primarily distributed as a single header file, although certain\nextensions may require additional headers.\n\n## How to use it\nThis documentation comprises these three parts:\n\n* [Why do we need yet another C++ Test Framework?](docs/why-catch.md#top)\n* [Tutorial](docs/tutorial.md#top) - getting started\n* [Reference section](docs/Readme.md#top) - all the details\n\n## More\n* Issues and bugs can be raised on the [Issue tracker on GitHub](https://github.com/catchorg/Catch2/issues)\n* For discussion or questions please use [the dedicated Google Groups forum](https://groups.google.com/forum/?fromgroups#!forum/catch-forum) or our [Discord](https://discord.gg/4CWS9zD)\n* See [who else is using Catch2](docs/opensource-users.md#top)\n"
  },
  {
    "path": "dependencies/catch2/Catch2/contrib/Catch.cmake",
    "content": "# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying\n# file Copyright.txt or https://cmake.org/licensing for details.\n\n#[=======================================================================[.rst:\nCatch\n-----\n\nThis module defines a function to help use the Catch test framework.\n\nThe :command:`catch_discover_tests` discovers tests by asking the compiled test\nexecutable to enumerate its tests.  This does not require CMake to be re-run\nwhen tests change.  However, it may not work in a cross-compiling environment,\nand setting test properties is less convenient.\n\nThis command is intended to replace use of :command:`add_test` to register\ntests, and will create a separate CTest test for each Catch test case.  Note\nthat this is in some cases less efficient, as common set-up and tear-down logic\ncannot be shared by multiple test cases executing in the same instance.\nHowever, it provides more fine-grained pass/fail information to CTest, which is\nusually considered as more beneficial.  By default, the CTest test name is the\nsame as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.\n\n.. command:: catch_discover_tests\n\n  Automatically add tests with CTest by querying the compiled test executable\n  for available tests::\n\n    catch_discover_tests(target\n                         [TEST_SPEC arg1...]\n                         [EXTRA_ARGS arg1...]\n                         [WORKING_DIRECTORY dir]\n                         [TEST_PREFIX prefix]\n                         [TEST_SUFFIX suffix]\n                         [PROPERTIES name1 value1...]\n                         [TEST_LIST var]\n                         [REPORTER reporter]\n                         [OUTPUT_DIR dir]\n                         [OUTPUT_PREFIX prefix}\n                         [OUTPUT_SUFFIX suffix]\n    )\n\n  ``catch_discover_tests`` sets up a post-build command on the test executable\n  that generates the list of tests by parsing the output from running the test\n  with the ``--list-test-names-only`` argument.  This ensures that the full\n  list of tests is obtained.  Since test discovery occurs at build time, it is\n  not necessary to re-run CMake when the list of tests changes.\n  However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set\n  in order to function in a cross-compiling environment.\n\n  Additionally, setting properties on tests is somewhat less convenient, since\n  the tests are not available at CMake time.  Additional test properties may be\n  assigned to the set of tests as a whole using the ``PROPERTIES`` option.  If\n  more fine-grained test control is needed, custom content may be provided\n  through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES`\n  directory property.  The set of discovered tests is made accessible to such a\n  script via the ``<target>_TESTS`` variable.\n\n  The options are:\n\n  ``target``\n    Specifies the Catch executable, which must be a known CMake executable\n    target.  CMake will substitute the location of the built executable when\n    running the test.\n\n  ``TEST_SPEC arg1...``\n    Specifies test cases, wildcarded test cases, tags and tag expressions to\n    pass to the Catch executable with the ``--list-test-names-only`` argument.\n\n  ``EXTRA_ARGS arg1...``\n    Any extra arguments to pass on the command line to each test case.\n\n  ``WORKING_DIRECTORY dir``\n    Specifies the directory in which to run the discovered test cases.  If this\n    option is not provided, the current binary directory is used.\n\n  ``TEST_PREFIX prefix``\n    Specifies a ``prefix`` to be prepended to the name of each discovered test\n    case.  This can be useful when the same test executable is being used in\n    multiple calls to ``catch_discover_tests()`` but with different\n    ``TEST_SPEC`` or ``EXTRA_ARGS``.\n\n  ``TEST_SUFFIX suffix``\n    Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of\n    every discovered test case.  Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may\n    be specified.\n\n  ``PROPERTIES name1 value1...``\n    Specifies additional properties to be set on all tests discovered by this\n    invocation of ``catch_discover_tests``.\n\n  ``TEST_LIST var``\n    Make the list of tests available in the variable ``var``, rather than the\n    default ``<target>_TESTS``.  This can be useful when the same test\n    executable is being used in multiple calls to ``catch_discover_tests()``.\n    Note that this variable is only available in CTest.\n\n  ``REPORTER reporter``\n    Use the specified reporter when running the test case. The reporter will\n    be passed to the Catch executable as ``--reporter reporter``.\n\n  ``OUTPUT_DIR dir``\n    If specified, the parameter is passed along as\n    ``--out dir/<test_name>`` to Catch executable. The actual file name is the\n    same as the test name. This should be used instead of\n    ``EXTRA_ARGS --out foo`` to avoid race conditions writing the result output\n    when using parallel test execution.\n\n  ``OUTPUT_PREFIX prefix``\n    May be used in conjunction with ``OUTPUT_DIR``.\n    If specified, ``prefix`` is added to each output file name, like so\n    ``--out dir/prefix<test_name>``.\n\n  ``OUTPUT_SUFFIX suffix``\n    May be used in conjunction with ``OUTPUT_DIR``.\n    If specified, ``suffix`` is added to each output file name, like so\n    ``--out dir/<test_name>suffix``. This can be used to add a file extension to\n    the output e.g. \".xml\".\n\n#]=======================================================================]\n\n#------------------------------------------------------------------------------\nfunction(catch_discover_tests TARGET)\n  cmake_parse_arguments(\n    \"\"\n    \"\"\n    \"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;REPORTER;OUTPUT_DIR;OUTPUT_PREFIX;OUTPUT_SUFFIX\"\n    \"TEST_SPEC;EXTRA_ARGS;PROPERTIES\"\n    ${ARGN}\n  )\n\n  if(NOT _WORKING_DIRECTORY)\n    set(_WORKING_DIRECTORY \"${CMAKE_CURRENT_BINARY_DIR}\")\n  endif()\n  if(NOT _TEST_LIST)\n    set(_TEST_LIST ${TARGET}_TESTS)\n  endif()\n\n  ## Generate a unique name based on the extra arguments\n  string(SHA1 args_hash \"${_TEST_SPEC} ${_EXTRA_ARGS} ${_REPORTER} ${_OUTPUT_DIR} ${_OUTPUT_PREFIX} ${_OUTPUT_SUFFIX}\")\n  string(SUBSTRING ${args_hash} 0 7 args_hash)\n\n  # Define rule to generate test list for aforementioned test executable\n  set(ctest_include_file \"${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake\")\n  set(ctest_tests_file \"${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake\")\n  get_property(crosscompiling_emulator\n    TARGET ${TARGET}\n    PROPERTY CROSSCOMPILING_EMULATOR\n  )\n  add_custom_command(\n    TARGET ${TARGET} POST_BUILD\n    BYPRODUCTS \"${ctest_tests_file}\"\n    COMMAND \"${CMAKE_COMMAND}\"\n            -D \"TEST_TARGET=${TARGET}\"\n            -D \"TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>\"\n            -D \"TEST_EXECUTOR=${crosscompiling_emulator}\"\n            -D \"TEST_WORKING_DIR=${_WORKING_DIRECTORY}\"\n            -D \"TEST_SPEC=${_TEST_SPEC}\"\n            -D \"TEST_EXTRA_ARGS=${_EXTRA_ARGS}\"\n            -D \"TEST_PROPERTIES=${_PROPERTIES}\"\n            -D \"TEST_PREFIX=${_TEST_PREFIX}\"\n            -D \"TEST_SUFFIX=${_TEST_SUFFIX}\"\n            -D \"TEST_LIST=${_TEST_LIST}\"\n            -D \"TEST_REPORTER=${_REPORTER}\"\n            -D \"TEST_OUTPUT_DIR=${_OUTPUT_DIR}\"\n            -D \"TEST_OUTPUT_PREFIX=${_OUTPUT_PREFIX}\"\n            -D \"TEST_OUTPUT_SUFFIX=${_OUTPUT_SUFFIX}\"\n            -D \"CTEST_FILE=${ctest_tests_file}\"\n            -P \"${_CATCH_DISCOVER_TESTS_SCRIPT}\"\n    VERBATIM\n  )\n\n  file(WRITE \"${ctest_include_file}\"\n    \"if(EXISTS \\\"${ctest_tests_file}\\\")\\n\"\n    \"  include(\\\"${ctest_tests_file}\\\")\\n\"\n    \"else()\\n\"\n    \"  add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\\n\"\n    \"endif()\\n\"\n  )\n\n  if(NOT ${CMAKE_VERSION} VERSION_LESS \"3.10.0\") \n    # Add discovered tests to directory TEST_INCLUDE_FILES\n    set_property(DIRECTORY\n      APPEND PROPERTY TEST_INCLUDE_FILES \"${ctest_include_file}\"\n    )\n  else()\n    # Add discovered tests as directory TEST_INCLUDE_FILE if possible\n    get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET)\n    if (NOT ${test_include_file_set})\n      set_property(DIRECTORY\n        PROPERTY TEST_INCLUDE_FILE \"${ctest_include_file}\"\n      )\n    else()\n      message(FATAL_ERROR\n        \"Cannot set more than one TEST_INCLUDE_FILE\"\n      )\n    endif()\n  endif()\n\nendfunction()\n\n###############################################################################\n\nset(_CATCH_DISCOVER_TESTS_SCRIPT\n  ${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake\n  CACHE INTERNAL \"Catch2 full path to CatchAddTests.cmake helper file\"\n)\n"
  },
  {
    "path": "dependencies/catch2/Catch2/contrib/CatchAddTests.cmake",
    "content": "# Distributed under the OSI-approved BSD 3-Clause License.  See accompanying\n# file Copyright.txt or https://cmake.org/licensing for details.\n\nset(prefix \"${TEST_PREFIX}\")\nset(suffix \"${TEST_SUFFIX}\")\nset(spec ${TEST_SPEC})\nset(extra_args ${TEST_EXTRA_ARGS})\nset(properties ${TEST_PROPERTIES})\nset(reporter ${TEST_REPORTER})\nset(output_dir ${TEST_OUTPUT_DIR})\nset(output_prefix ${TEST_OUTPUT_PREFIX})\nset(output_suffix ${TEST_OUTPUT_SUFFIX})\nset(script)\nset(suite)\nset(tests)\n\nfunction(add_command NAME)\n  set(_args \"\")\n  # use ARGV* instead of ARGN, because ARGN splits arrays into multiple arguments\n  math(EXPR _last_arg ${ARGC}-1)\n  foreach(_n RANGE 1 ${_last_arg})\n    set(_arg \"${ARGV${_n}}\")\n    if(_arg MATCHES \"[^-./:a-zA-Z0-9_]\")\n      set(_args \"${_args} [==[${_arg}]==]\") # form a bracket_argument\n    else()\n      set(_args \"${_args} ${_arg}\")\n    endif()\n  endforeach()\n  set(script \"${script}${NAME}(${_args})\\n\" PARENT_SCOPE)\nendfunction()\n\n# Run test executable to get list of available tests\nif(NOT EXISTS \"${TEST_EXECUTABLE}\")\n  message(FATAL_ERROR\n    \"Specified test executable '${TEST_EXECUTABLE}' does not exist\"\n  )\nendif()\nexecute_process(\n  COMMAND ${TEST_EXECUTOR} \"${TEST_EXECUTABLE}\" ${spec} --list-test-names-only\n  OUTPUT_VARIABLE output\n  RESULT_VARIABLE result\n  WORKING_DIRECTORY \"${TEST_WORKING_DIR}\"\n)\n# Catch --list-test-names-only reports the number of tests, so 0 is... surprising\nif(${result} EQUAL 0)\n  message(WARNING\n    \"Test executable '${TEST_EXECUTABLE}' contains no tests!\\n\"\n  )\nelseif(${result} LESS 0)\n  message(FATAL_ERROR\n    \"Error running test executable '${TEST_EXECUTABLE}':\\n\"\n    \"  Result: ${result}\\n\"\n    \"  Output: ${output}\\n\"\n  )\nendif()\n\nstring(REPLACE \"\\n\" \";\" output \"${output}\")\n\n# Run test executable to get list of available reporters\nexecute_process(\n  COMMAND ${TEST_EXECUTOR} \"${TEST_EXECUTABLE}\" ${spec} --list-reporters\n  OUTPUT_VARIABLE reporters_output\n  RESULT_VARIABLE reporters_result\n  WORKING_DIRECTORY \"${TEST_WORKING_DIR}\"\n)\nif(${reporters_result} EQUAL 0)\n  message(WARNING\n    \"Test executable '${TEST_EXECUTABLE}' contains no reporters!\\n\"\n  )\nelseif(${reporters_result} LESS 0)\n  message(FATAL_ERROR\n    \"Error running test executable '${TEST_EXECUTABLE}':\\n\"\n    \"  Result: ${reporters_result}\\n\"\n    \"  Output: ${reporters_output}\\n\"\n  )\nendif()\nstring(FIND \"${reporters_output}\" \"${reporter}\" reporter_is_valid)\nif(reporter AND ${reporter_is_valid} EQUAL -1)\n  message(FATAL_ERROR\n    \"\\\"${reporter}\\\" is not a valid reporter!\\n\"\n  )\nendif()\n\n# Prepare reporter\nif(reporter)\n  set(reporter_arg \"--reporter ${reporter}\")\nendif()\n\n# Prepare output dir\nif(output_dir AND NOT IS_ABSOLUTE ${output_dir})\n  set(output_dir \"${TEST_WORKING_DIR}/${output_dir}\")\n  if(NOT EXISTS ${output_dir})\n    file(MAKE_DIRECTORY ${output_dir})\n  endif()\nendif()\n\n# Parse output\nforeach(line ${output})\n  set(test ${line})\n  # Escape characters in test case names that would be parsed by Catch2\n  set(test_name ${test})\n  foreach(char , [ ])\n    string(REPLACE ${char} \"\\\\${char}\" test_name ${test_name})\n  endforeach(char)\n  # ...add output dir\n  if(output_dir)\n    string(REGEX REPLACE \"[^A-Za-z0-9_]\" \"_\" test_name_clean ${test_name})\n    set(output_dir_arg \"--out ${output_dir}/${output_prefix}${test_name_clean}${output_suffix}\")\n  endif()\n  \n  # ...and add to script\n  add_command(add_test\n    \"${prefix}${test}${suffix}\"\n    ${TEST_EXECUTOR}\n    \"${TEST_EXECUTABLE}\"\n    \"${test_name}\"\n    ${extra_args}\n    \"${reporter_arg}\"\n    \"${output_dir_arg}\"\n  )\n  add_command(set_tests_properties\n    \"${prefix}${test}${suffix}\"\n    PROPERTIES\n    WORKING_DIRECTORY \"${TEST_WORKING_DIR}\"\n    ${properties}\n  )\n  list(APPEND tests \"${prefix}${test}${suffix}\")\nendforeach()\n\n# Create a list of all discovered tests, which users may use to e.g. set\n# properties on the tests\nadd_command(set ${TEST_LIST} ${tests})\n\n# Write CTest script\nfile(WRITE \"${CTEST_FILE}\" \"${script}\")\n"
  },
  {
    "path": "dependencies/catch2/Catch2/contrib/ParseAndAddCatchTests.cmake",
    "content": "#==================================================================================================#\n#  supported macros                                                                                #\n#    - TEST_CASE,                                                                                  #\n#    - TEMPLATE_TEST_CASE                                                                          #\n#    - SCENARIO,                                                                                   #\n#    - TEST_CASE_METHOD,                                                                           #\n#    - CATCH_TEST_CASE,                                                                            #\n#    - CATCH_TEMPLATE_TEST_CASE                                                                    #\n#    - CATCH_SCENARIO,                                                                             #\n#    - CATCH_TEST_CASE_METHOD.                                                                     #\n#                                                                                                  #\n#  Usage                                                                                           #\n# 1. make sure this module is in the path or add this otherwise:                                   #\n#    set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} \"${CMAKE_SOURCE_DIR}/cmake.modules/\")              #\n# 2. make sure that you've enabled testing option for the project by the call:                     #\n#    enable_testing()                                                                              #\n# 3. add the lines to the script for testing target (sample CMakeLists.txt):                       #\n#        project(testing_target)                                                                   #\n#        set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} \"${CMAKE_SOURCE_DIR}/cmake.modules/\")          #\n#        enable_testing()                                                                          #\n#                                                                                                  #\n#        find_path(CATCH_INCLUDE_DIR \"catch.hpp\")                                                  #\n#        include_directories(${INCLUDE_DIRECTORIES} ${CATCH_INCLUDE_DIR})                          #\n#                                                                                                  #\n#        file(GLOB SOURCE_FILES \"*.cpp\")                                                           #\n#        add_executable(${PROJECT_NAME} ${SOURCE_FILES})                                           #\n#                                                                                                  #\n#        include(ParseAndAddCatchTests)                                                            #\n#        ParseAndAddCatchTests(${PROJECT_NAME})                                                    #\n#                                                                                                  #\n# The following variables affect the behavior of the script:                                       #\n#                                                                                                  #\n#    PARSE_CATCH_TESTS_VERBOSE (Default OFF)                                                       #\n#    -- enables debug messages                                                                     #\n#    PARSE_CATCH_TESTS_NO_HIDDEN_TESTS (Default OFF)                                               #\n#    -- excludes tests marked with [!hide], [.] or [.foo] tags                                     #\n#    PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME (Default ON)                                       #\n#    -- adds fixture class name to the test name                                                   #\n#    PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME (Default ON)                                        #\n#    -- adds cmake target name to the test name                                                    #\n#    PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF)                                      #\n#    -- causes CMake to rerun when file with tests changes so that new tests will be discovered    #\n#                                                                                                  #\n# One can also set (locally) the optional variable OptionalCatchTestLauncher to precise the way    #\n# a test should be run. For instance to use test MPI, one can write                                #\n#     set(OptionalCatchTestLauncher ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${NUMPROC})                 #\n# just before calling this ParseAndAddCatchTests function                                          #\n#                                                                                                  #\n# The AdditionalCatchParameters optional variable can be used to pass extra argument to the test   #\n# command. For example, to include successful tests in the output, one can write                   #\n#     set(AdditionalCatchParameters --success)                                                     #\n#                                                                                                  #\n# After the script, the ParseAndAddCatchTests_TESTS property for the target, and for each source   #\n# file in the target is set, and contains the list of the tests extracted from that target, or     #\n# from that file. This is useful, for example to add further labels or properties to the tests.    #\n#                                                                                                  #\n#==================================================================================================#\n\nif (CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.8)\n  message(FATAL_ERROR \"ParseAndAddCatchTests requires CMake 2.8.8 or newer\")\nendif()\n\noption(PARSE_CATCH_TESTS_VERBOSE \"Print Catch to CTest parser debug messages\" OFF)\noption(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS \"Exclude tests with [!hide], [.] or [.foo] tags\" OFF)\noption(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME \"Add fixture class name to the test name\" ON)\noption(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME \"Add target name to the test name\" ON)\noption(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS \"Add test file to CMAKE_CONFIGURE_DEPENDS property\" OFF)\n\nfunction(ParseAndAddCatchTests_PrintDebugMessage)\n    if(PARSE_CATCH_TESTS_VERBOSE)\n            message(STATUS \"ParseAndAddCatchTests: ${ARGV}\")\n    endif()\nendfunction()\n\n# This removes the contents between\n#  - block comments (i.e. /* ... */)\n#  - full line comments (i.e. // ... )\n# contents have been read into '${CppCode}'.\n# !keep partial line comments\nfunction(ParseAndAddCatchTests_RemoveComments CppCode)\n  string(ASCII 2 CMakeBeginBlockComment)\n  string(ASCII 3 CMakeEndBlockComment)\n  string(REGEX REPLACE \"/\\\\*\" \"${CMakeBeginBlockComment}\" ${CppCode} \"${${CppCode}}\")\n  string(REGEX REPLACE \"\\\\*/\" \"${CMakeEndBlockComment}\" ${CppCode} \"${${CppCode}}\")\n  string(REGEX REPLACE \"${CMakeBeginBlockComment}[^${CMakeEndBlockComment}]*${CMakeEndBlockComment}\" \"\" ${CppCode} \"${${CppCode}}\")\n  string(REGEX REPLACE \"\\n[ \\t]*//+[^\\n]+\" \"\\n\" ${CppCode} \"${${CppCode}}\")\n\n  set(${CppCode} \"${${CppCode}}\" PARENT_SCOPE)\nendfunction()\n\n# Worker function\nfunction(ParseAndAddCatchTests_ParseFile SourceFile TestTarget)\n    # If SourceFile is an object library, do not scan it (as it is not a file). Exit without giving a warning about a missing file.\n    if(SourceFile MATCHES \"\\\\\\$<TARGET_OBJECTS:.+>\")\n        ParseAndAddCatchTests_PrintDebugMessage(\"Detected OBJECT library: ${SourceFile} this will not be scanned for tests.\")\n        return()\n    endif()\n    # According to CMake docs EXISTS behavior is well-defined only for full paths.\n    get_filename_component(SourceFile ${SourceFile} ABSOLUTE)\n    if(NOT EXISTS ${SourceFile})\n        message(WARNING \"Cannot find source file: ${SourceFile}\")\n        return()\n    endif()\n    ParseAndAddCatchTests_PrintDebugMessage(\"parsing ${SourceFile}\")\n    file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME)\n\n    # Remove block and fullline comments\n    ParseAndAddCatchTests_RemoveComments(Contents)\n\n    # Find definition of test names\n    # https://regex101.com/r/JygOND/1\n    string(REGEX MATCHALL \"[ \\t]*(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \\t]*\\\\([ \\t\\n]*\\\"[^\\\"]*\\\"[ \\t\\n]*(,[ \\t\\n]*\\\"[^\\\"]*\\\")?(,[ \\t\\n]*[^\\,\\)]*)*\\\\)[ \\t\\n]*\\{+[ \\t]*(//[^\\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \\t]*[0-9]+)*\" Tests \"${Contents}\")\n\n    if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests)\n      ParseAndAddCatchTests_PrintDebugMessage(\"Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property\")\n      set_property(\n        DIRECTORY\n        APPEND\n        PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile}\n      )\n    endif()\n\n    # check CMP0110 policy for new add_test() behavior\n    if(POLICY CMP0110)\n        cmake_policy(GET CMP0110 _cmp0110_value) # new add_test() behavior\n    else()\n        # just to be thorough explicitly set the variable\n        set(_cmp0110_value)\n    endif()\n\n    foreach(TestName ${Tests})\n        # Strip newlines\n        string(REGEX REPLACE \"\\\\\\\\\\n|\\n\" \"\" TestName \"${TestName}\")\n\n        # Get test type and fixture if applicable\n        string(REGEX MATCH \"(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \\t]*\\\\([^,^\\\"]*\" TestTypeAndFixture \"${TestName}\")\n        string(REGEX MATCH \"(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)\" TestType \"${TestTypeAndFixture}\")\n        string(REGEX REPLACE \"${TestType}\\\\([ \\t]*\" \"\" TestFixture \"${TestTypeAndFixture}\")\n\n        # Get string parts of test definition\n        string(REGEX MATCHALL \"\\\"+([^\\\\^\\\"]|\\\\\\\\\\\")+\\\"+\" TestStrings \"${TestName}\")\n\n        # Strip wrapping quotation marks\n        string(REGEX REPLACE \"^\\\"(.*)\\\"$\" \"\\\\1\" TestStrings \"${TestStrings}\")\n        string(REPLACE \"\\\";\\\"\" \";\" TestStrings \"${TestStrings}\")\n\n        # Validate that a test name and tags have been provided\n        list(LENGTH TestStrings TestStringsLength)\n        if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1)\n            message(FATAL_ERROR \"You must provide a valid test name and tags for all tests in ${SourceFile}\")\n        endif()\n\n        # Assign name and tags\n        list(GET TestStrings 0 Name)\n        if(\"${TestType}\" STREQUAL \"SCENARIO\")\n            set(Name \"Scenario: ${Name}\")\n        endif()\n        if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND \"${TestType}\" MATCHES \"(CATCH_)?TEST_CASE_METHOD\" AND TestFixture )\n            set(CTestName \"${TestFixture}:${Name}\")\n        else()\n            set(CTestName \"${Name}\")\n        endif()\n        if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME)\n            set(CTestName \"${TestTarget}:${CTestName}\")\n        endif()\n        # add target to labels to enable running all tests added from this target\n        set(Labels ${TestTarget})\n        if(TestStringsLength EQUAL 2)\n            list(GET TestStrings 1 Tags)\n            string(TOLOWER \"${Tags}\" Tags)\n            # remove target from labels if the test is hidden\n            if(\"${Tags}\" MATCHES \".*\\\\[!?(hide|\\\\.)\\\\].*\")\n                list(REMOVE_ITEM Labels ${TestTarget})\n            endif()\n            string(REPLACE \"]\" \";\" Tags \"${Tags}\")\n            string(REPLACE \"[\" \"\" Tags \"${Tags}\")\n        else()\n          # unset tags variable from previous loop\n          unset(Tags)\n        endif()\n\n        list(APPEND Labels ${Tags})\n\n        set(HiddenTagFound OFF)\n        foreach(label ${Labels})\n            string(REGEX MATCH \"^!hide|^\\\\.\" result ${label})\n            if(result)\n                set(HiddenTagFound ON)\n                break()\n            endif(result)\n        endforeach(label)\n        if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_LESS \"3.9\")\n            ParseAndAddCatchTests_PrintDebugMessage(\"Skipping test \\\"${CTestName}\\\" as it has [!hide], [.] or [.foo] label\")\n        else()\n            ParseAndAddCatchTests_PrintDebugMessage(\"Adding test \\\"${CTestName}\\\"\")\n            if(Labels)\n                ParseAndAddCatchTests_PrintDebugMessage(\"Setting labels to ${Labels}\")\n            endif()\n\n            # Escape commas in the test spec\n            string(REPLACE \",\" \"\\\\,\" Name ${Name})\n\n            # Work around CMake 3.18.0 change in `add_test()`, before the escaped quotes were necessary,\n            # only with CMake 3.18.0 the escaped double quotes confuse the call. This change is reverted in 3.18.1\n            # And properly introduced in 3.19 with the CMP0110 policy\n            if(_cmp0110_value STREQUAL \"NEW\" OR ${CMAKE_VERSION} VERSION_EQUAL \"3.18\")\n                ParseAndAddCatchTests_PrintDebugMessage(\"CMP0110 set to NEW, no need for add_test(\\\"\\\") workaround\")\n            else()\n                ParseAndAddCatchTests_PrintDebugMessage(\"CMP0110 set to OLD adding \\\"\\\" for add_test() workaround\")\n                set(CTestName \"\\\"${CTestName}\\\"\")\n            endif()\n\n            # Handle template test cases\n            if(\"${TestTypeAndFixture}\" MATCHES \".*TEMPLATE_.*\")\n              set(Name \"${Name} - *\")\n            endif()\n\n            # Add the test and set its properties\n            add_test(NAME \"${CTestName}\" COMMAND ${OptionalCatchTestLauncher} $<TARGET_FILE:${TestTarget}> ${Name} ${AdditionalCatchParameters})\n            # Old CMake versions do not document VERSION_GREATER_EQUAL, so we use VERSION_GREATER with 3.8 instead\n            if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_GREATER \"3.8\")\n                ParseAndAddCatchTests_PrintDebugMessage(\"Setting DISABLED test property\")\n                set_tests_properties(\"${CTestName}\" PROPERTIES DISABLED ON)\n            else()\n                set_tests_properties(\"${CTestName}\" PROPERTIES FAIL_REGULAR_EXPRESSION \"No tests ran\"\n                                                        LABELS \"${Labels}\")\n            endif()\n            set_property(\n              TARGET ${TestTarget}\n              APPEND\n              PROPERTY ParseAndAddCatchTests_TESTS \"${CTestName}\")\n            set_property(\n              SOURCE ${SourceFile}\n              APPEND\n              PROPERTY ParseAndAddCatchTests_TESTS \"${CTestName}\")\n        endif()\n\n\n    endforeach()\nendfunction()\n\n# entry point\nfunction(ParseAndAddCatchTests TestTarget)\n    message(DEPRECATION \"ParseAndAddCatchTest: function deprecated because of possibility of missed test cases. Consider using 'catch_discover_tests' from 'Catch.cmake'\")\n    ParseAndAddCatchTests_PrintDebugMessage(\"Started parsing ${TestTarget}\")\n    get_target_property(SourceFiles ${TestTarget} SOURCES)\n    ParseAndAddCatchTests_PrintDebugMessage(\"Found the following sources: ${SourceFiles}\")\n    foreach(SourceFile ${SourceFiles})\n        ParseAndAddCatchTests_ParseFile(${SourceFile} ${TestTarget})\n    endforeach()\n    ParseAndAddCatchTests_PrintDebugMessage(\"Finished parsing ${TestTarget}\")\nendfunction()\n"
  },
  {
    "path": "dependencies/catch2/Catch2/single_include/catch2/catch.hpp",
    "content": "/*\n *  Catch v2.13.10\n *  Generated: 2022-10-16 11:01:23.452308\n *  ----------------------------------------------------------\n *  This file has been merged from multiple headers. Please don't edit it directly\n *  Copyright (c) 2022 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\n#define CATCH_VERSION_MAJOR 2\n#define CATCH_VERSION_MINOR 13\n#define CATCH_VERSION_PATCH 10\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) || \\\n      (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) || 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 {\n    unsigned 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__) && !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(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, 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: 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 check.\n// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html 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// 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#  if !defined(__clang__) // Handle Clang masquerading for msvc\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(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL)\n#      define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR\n#    endif // MSVC_TRADITIONAL\n\n// Only do this if we're not using clang on Windows, which uses `diagnostic push` & `diagnostic pop`\n#    define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) )\n#    define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION  __pragma( warning(pop) )\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 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++ https://bugs.llvm.org/show_bug.cgi?id=31852\n         // fix should be in clang 8, 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 < 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) && !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) && !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 posix-signal-compatible by default.\n#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !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 support are just rare exceptions.\n#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR)\n#   define CATCH_CONFIG_WCHAR\n#endif\n\n#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !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) && !defined(CATCH_CONFIG_CPP17_OPTIONAL)\n#  define CATCH_CONFIG_CPP17_OPTIONAL\n#endif\n\n#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !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) && !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) && !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) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !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) && !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) && !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) && !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) && !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) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !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 <iosfwd>\n#include <string>\n#include <cstdint>\n\n// We need a dummy global operator<< so we can bring it into Catch namespace later\nstruct Catch_global_namespace_dummy {};\nstd::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy);\n\nnamespace Catch {\n\n    struct CaseSensitive { enum Choice {\n        Yes,\n        No\n    }; };\n\n    class NonCopyable {\n        NonCopyable( NonCopyable const& )              = delete;\n        NonCopyable( NonCopyable && )                  = delete;\n        NonCopyable& operator = ( NonCopyable const& ) = delete;\n        NonCopyable& operator = ( NonCopyable && )     = delete;\n\n    protected:\n        NonCopyable();\n        virtual ~NonCopyable();\n    };\n\n    struct SourceLineInfo {\n\n        SourceLineInfo() = delete;\n        SourceLineInfo( char const* _file, std::size_t _line ) noexcept\n        :   file( _file ),\n            line( _line )\n        {}\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\n    std::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\n    using ::operator<<;\n\n    // Use this in variadic streaming macros to allow\n    //    >> +StreamEndStop\n    // as well as\n    //    >> stuff +StreamEndStop\n    struct StreamEndStop {\n        std::string operator+() const;\n    };\n    template<typename T>\n    T const& operator + ( T const& value, StreamEndStop ) {\n        return value;\n    }\n}\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\n    struct 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{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \\\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\n    class TestSpec;\n\n    struct ITestInvoker {\n        virtual void invoke () const = 0;\n        virtual ~ITestInvoker();\n    };\n\n    class TestCase;\n    struct IConfig;\n\n    struct 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\n    bool isThrowSafe( TestCase const& testCase, IConfig const& config );\n    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config );\n    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config );\n    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config );\n\n}\n\n// end catch_interfaces_testcase.h\n// start catch_stringref.h\n\n#include <cstddef>\n#include <string>\n#include <iosfwd>\n#include <cassert>\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.\n    class StringRef {\n    public:\n        using size_type = std::size_t;\n        using const_iterator = const char*;\n\n    private:\n        static constexpr char const* const s_empty = \"\";\n\n        char const* m_start = s_empty;\n        size_type m_size = 0;\n\n    public: // 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 ),\n            m_size( size )\n        {}\n\n        StringRef( std::string const& stdString ) noexcept\n        :   m_start( stdString.c_str() ),\n            m_size( stdString.size() )\n        {}\n\n        explicit operator std::string() const {\n            return std::string(m_start, m_size);\n        }\n\n    public: // operators\n        auto operator == ( StringRef const& other ) const noexcept -> bool;\n        auto operator != (StringRef const& other) const noexcept -> bool {\n            return !(*this == other);\n        }\n\n        auto operator[] ( size_type index ) const noexcept -> char {\n            assert(index < m_size);\n            return m_start[index];\n        }\n\n    public: // named queries\n        constexpr auto empty() const noexcept -> bool {\n            return m_size == 0;\n        }\n        constexpr auto size() const noexcept -> size_type {\n            return m_size;\n        }\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\n    public: // 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 {\n            return m_start[m_size] == '\\0';\n        }\n\n    public: // iterators\n        constexpr const_iterator begin() const { return m_start; }\n        constexpr const_iterator end() const { return m_start + m_size; }\n    };\n\n    auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&;\n    auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&;\n\n    constexpr 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 -> Catch::StringRef {\n    return Catch::StringRef( rawChars, size );\n}\n\n// end catch_stringref.h\n// start catch_preprocessor.hpp\n\n\n#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__\n#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__)))\n#define CATCH_RECURSION_LEVEL5(...) 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(...) 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, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ )\n#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ )\n#define CATCH_REC_LIST2(f, x, peek, ...)   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, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ )\n#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ )\n#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...)   f(userdata, x) 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 commas between the results,\n// and passes userdata as the first parameter to each invocation,\n// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c)\n#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0))\n\n#define CATCH_REC_LIST(f, ...) 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) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param))\n#else\n// MSVC is adding extra space and needs another indirection to expand 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) (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(...) decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>())\n#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))\n#else\n#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>()))\n#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(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) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1)\n#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) 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) 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) 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) 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) 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) 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) INTERNAL_CATCH_REMOVE_PARENS(_0), 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) INTERNAL_CATCH_REMOVE_PARENS(_0), 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) INTERNAL_CATCH_REMOVE_PARENS(_0), 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...> struct TypeList {};\\\n    template<typename...Ts>\\\n    constexpr auto get_wrapper() noexcept -> TypeList<Ts...> { return {}; }\\\n    template<template<typename...> class...> struct TemplateTypeList{};\\\n    template<template<typename...> class...Cs>\\\n    constexpr auto get_wrapper() noexcept -> TemplateTypeList<Cs...> { return {}; }\\\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> { using type = T; };\\\n    template< template<typename...> class L1, typename...E1, template<typename...> class L2, typename...E2, typename...Rest>\\\n    struct append<L1<E1...>, L2<E2...>, Rest...> { using type = typename append<L1<E1...,E2...>, Rest...>::type; };\\\n    template< template<typename...> class L1, typename...E1, typename...Rest>\\\n    struct append<L1<E1...>, TypeList<mpl_::na>, Rest...> { using type = L1<E1...>; };\\\n    \\\n    template< template<typename...> class Container, template<typename...> class List, typename...elems>\\\n    struct rewrap<TemplateTypeList<Container>, List<elems...>> { using type = TypeList<Container<elems...>>; };\\\n    template< template<typename...> class Container, template<typename...> class List, class...Elems, typename...Elements>\\\n    struct rewrap<TemplateTypeList<Container>, List<Elems...>, Elements...> { using type = typename append<TypeList<Container<Elems...>>, typename rewrap<TemplateTypeList<Container>, Elements...>::type>::type; };\\\n    \\\n    template<template <typename...> class Final, template< typename...> class...Containers, typename...Types>\\\n    struct create<Final, TemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<TemplateTypeList<Containers>, Types...>::type...>::type; };\\\n    template<template <typename...> class Final, template <typename...> class List, typename...Ts>\\\n    struct convert<Final, List<Ts...>> { using type = typename append<Final<>,TypeList<Ts>...>::type; };\n\n#define INTERNAL_CATCH_NTTP_1(signature, ...)\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)> struct Nttp{};\\\n    template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    constexpr auto get_wrapper() noexcept -> Nttp<__VA_ARGS__> { return {}; } \\\n    template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...> struct NttpTemplateTypeList{};\\\n    template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Cs>\\\n    constexpr auto get_wrapper() noexcept -> NttpTemplateTypeList<Cs...> { return {}; } \\\n    \\\n    template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature)>\\\n    struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>> { using type = TypeList<Container<__VA_ARGS__>>; };\\\n    template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature), typename...Elements>\\\n    struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>, Elements...> { using type = typename append<TypeList<Container<__VA_ARGS__>>, typename rewrap<NttpTemplateTypeList<Container>, Elements...>::type>::type; };\\\n    template<template <typename...> class Final, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Containers, typename...Types>\\\n    struct create<Final, NttpTemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<NttpTemplateTypeList<Containers>, Types...>::type...>::type; };\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    {\\\n        Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<Type>), CATCH_INTERNAL_LINEINFO, 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    {\\\n        Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<__VA_ARGS__>), CATCH_INTERNAL_LINEINFO, 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    {\\\n        Catch::AutoReg( Catch::makeTestInvoker(&TestName<Type>::test), CATCH_INTERNAL_LINEINFO, 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    {\\\n        Catch::AutoReg( Catch::makeTestInvoker(&TestName<__VA_ARGS__>::test), 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(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__),INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_0)\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__)\n#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)\n#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__)\n#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__)\n#else\n#define INTERNAL_CATCH_NTTP_0(signature)\n#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1,INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_0)( __VA_ARGS__))\n#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__))\n#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__))\n#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( \"dummy\", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__))\n#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__))\n#endif\n\n// end catch_preprocessor.hpp\n// start catch_meta.hpp\n\n\n#include <type_traits>\n\nnamespace Catch {\n    template<typename T>\n    struct always_false : std::false_type {};\n\n    template <typename> struct true_given : std::true_type {};\n    struct 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\n    template <typename T>\n    struct is_callable;\n\n    template <typename Fun, typename... Args>\n    struct 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.\n    template <typename Func, typename... U>\n    using FunctionReturnType = 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\n    template <typename Func, typename... U>\n    using FunctionReturnType = typename std::remove_reference<typename std::remove_cv<typename std::result_of<Func(U...)>::type>::type>::type;\n#endif\n\n} // namespace Catch\n\nnamespace mpl_{\n    struct na;\n}\n\n// end catch_meta.hpp\nnamespace Catch {\n\ntemplate<typename C>\nclass TestInvokerAsMethod : public ITestInvoker {\n    void (C::*m_testAsMethod)();\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(), StringRef const& tags_ = StringRef() ) noexcept;\n    StringRef name;\n    StringRef tags;\n};\n\nstruct AutoReg : NonCopyable {\n    AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, 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, ... ) \\\n        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, Signature, ... )  \\\n        INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature))\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( TestNameClass, TestName, ClassName, Name, Tags, Signature, ... )    \\\n        namespace{                                                                                  \\\n            namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) {                                      \\\n            INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , 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, Signature, ... ) \\\n            INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ )\n    #else\n        #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \\\n            INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , 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{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* 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{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, \"&\" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* 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 ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, 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_ ), 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 )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), 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[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\\\n                    using expander = int[];\\\n                    (void)expander{(reg_test(Types{}, Catch::NameAndTags{ Name \" - \" + std::string(tmpl_types[index]), Tags } ), 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, Signature, __VA_ARGS__ ) )\n#endif\n\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(TestName, TestFuncName, 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> 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[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\\\n                    constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\\\n                    constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\\\n                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name \" - \" + std::string(tmpl_types[index / num_types]) + \"<\" + std::string(types_list[index % num_types]) + \">\", Tags } ), index++)... };/* NOLINT */\\\n                }                                                     \\\n            };                                                        \\\n            static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \\\n                using TestInit = typename create<TestName, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(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(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), 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(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), 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> 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{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name \" - \" + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + \" - \" + std::to_string(index), Tags } ), 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        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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), Name, Tags, TmplList )\n\n    #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, 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        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, 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[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\\\n                    using expander = int[];\\\n                    (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name \" - \" + std::string(tmpl_types[index]), Tags } ), 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_C_L_A_S_S_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) )\n#endif\n\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(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[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\\\n                    constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\\\n                    constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\\\n                    (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name \" - \" + std::string(tmpl_types[index / num_types]) + \"<\" + std::string(types_list[index % num_types]) + \">\", Tags } ), index++)... };/* NOLINT */ \\\n                }\\\n            };\\\n            static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\\\n                using TestInit = typename create<TestNameClass, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), 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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), 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        INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, Signature, __VA_ARGS__ )\n#else\n    #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\\\n        INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), ClassName, Name, Tags, Signature,__VA_ARGS__ ) )\n#endif\n\n    #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, 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        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{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name \" - \" + std::string(INTERNAL_CATCH_STRINGIZE(TmplList)) + \" - \" + std::to_string(index), Tags } ), 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        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( INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_ ), INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_M_P_L_A_T_E_T_E_S_T_F_U_N_C_ ), 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\n    struct ResultWas { 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    bool isOk( ResultWas::OfType resultType );\n    bool isJustInfo( int flags );\n\n    // ResultDisposition::Flags enum\n    struct ResultDisposition { 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    ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs );\n\n    bool shouldContinueOnFailure( int flags );\n    inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; }\n    bool shouldSuppressFailure( int flags );\n\n} // end namespace Catch\n\n// end catch_result_type.h\nnamespace Catch {\n\n    struct AssertionInfo\n    {\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 <vector>\n#include <cstddef>\n#include <type_traits>\n#include <string>\n// start catch_stream.h\n\n#include <iosfwd>\n#include <cstddef>\n#include <ostream>\n\nnamespace Catch {\n\n    std::ostream& cout();\n    std::ostream& cerr();\n    std::ostream& clog();\n\n    class StringRef;\n\n    struct IStream {\n        virtual ~IStream();\n        virtual std::ostream& stream() const = 0;\n    };\n\n    auto makeStream( StringRef const &filename ) -> IStream const*;\n\n    class ReusableStringStream : NonCopyable {\n        std::size_t m_index;\n        std::ostream* m_oss;\n    public:\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}\n\n// end catch_stream.h\n// start catch_interfaces_enum_values_registry.h\n\n#include <vector>\n\nnamespace Catch {\n\n    namespace Detail {\n        struct 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\n    struct IMutableEnumValuesRegistry {\n        virtual ~IMutableEnumValuesRegistry();\n\n        virtual Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector<int> const& values ) = 0;\n\n        template<typename E>\n        Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, 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} // 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 ) {\n    [obj release];\n}\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&, which MSVC complains about but is harmless\n#endif\n\nnamespace Catch {\n    namespace Detail {\n\n        extern const std::string unprintableString;\n\n        std::string rawMemoryToString( const void *object, std::size_t size );\n\n        template<typename T>\n        std::string rawMemoryToString( const T& object ) {\n          return rawMemoryToString( &object, sizeof(object) );\n        }\n\n        template<typename T>\n        class 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\n        public:\n            static const bool value = decltype(test<std::ostream, const T&>(0))::value;\n        };\n\n        template<typename E>\n        std::string convertUnknownEnumToString( E e );\n\n        template<typename T>\n        typename std::enable_if<\n            !std::is_enum<T>::value && !std::is_base_of<std::exception, T>::value,\n        std::string>::type convertUnstreamable( T const& ) {\n            return Detail::unprintableString;\n        }\n        template<typename T>\n        typename std::enable_if<\n            !std::is_enum<T>::value && std::is_base_of<std::exception, T>::value,\n         std::string>::type convertUnstreamable(T const& ex) {\n            return ex.what();\n        }\n\n        template<typename T>\n        typename std::enable_if<\n            std::is_enum<T>::value\n        , std::string>::type convertUnstreamable( T const& value ) {\n            return convertUnknownEnumToString( value );\n        }\n\n#if defined(_MANAGED)\n        //! Convert a CLR string to a utf8 std::string\n        template<typename T>\n        std::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\n    template <typename T, typename = void>\n    struct StringMaker {\n        template <typename Fake = T>\n        static\n        typename std::enable_if<::Catch::Detail::IsStreamInsertable<Fake>::value, 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\n        typename std::enable_if<!::Catch::Detail::IsStreamInsertable<Fake>::value, 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\n    namespace Detail {\n\n        // This function dispatches all stringification requests inside of Catch.\n        // Should be preferably called fully qualified, like ::Catch::Detail::stringify\n        template <typename T>\n        std::string stringify(const T& e) {\n            return ::Catch::StringMaker<typename std::remove_cv<typename std::remove_reference<T>::type>::type>::convert(e);\n        }\n\n        template<typename E>\n        std::string convertUnknownEnumToString( E e ) {\n            return ::Catch::Detail::stringify(static_cast<typename std::underlying_type<E>::type>(e));\n        }\n\n#if defined(_MANAGED)\n        template <typename T>\n        std::string stringify( T^ e ) {\n            return ::Catch::StringMaker<T^>::convert(e);\n        }\n#endif\n\n    } // namespace Detail\n\n    // Some predefined specializations\n\n    template<>\n    struct StringMaker<std::string> {\n        static std::string convert(const std::string& str);\n    };\n\n#ifdef CATCH_CONFIG_CPP17_STRING_VIEW\n    template<>\n    struct StringMaker<std::string_view> {\n        static std::string convert(std::string_view str);\n    };\n#endif\n\n    template<>\n    struct StringMaker<char const *> {\n        static std::string convert(char const * str);\n    };\n    template<>\n    struct StringMaker<char *> {\n        static std::string convert(char * str);\n    };\n\n#ifdef CATCH_CONFIG_WCHAR\n    template<>\n    struct StringMaker<std::wstring> {\n        static std::string convert(const std::wstring& wstr);\n    };\n\n# ifdef CATCH_CONFIG_CPP17_STRING_VIEW\n    template<>\n    struct StringMaker<std::wstring_view> {\n        static std::string convert(std::wstring_view str);\n    };\n# endif\n\n    template<>\n    struct StringMaker<wchar_t const *> {\n        static std::string convert(wchar_t const * str);\n    };\n    template<>\n    struct 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?\n    template<int SZ>\n    struct StringMaker<char[SZ]> {\n        static std::string convert(char const* str) {\n            return ::Catch::Detail::stringify(std::string{ str });\n        }\n    };\n    template<int SZ>\n    struct 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    };\n    template<int SZ>\n    struct 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)\n    template<>\n    struct StringMaker<std::byte> {\n        static std::string convert(std::byte value);\n    };\n#endif // defined(CATCH_CONFIG_CPP17_BYTE)\n    template<>\n    struct StringMaker<int> {\n        static std::string convert(int value);\n    };\n    template<>\n    struct StringMaker<long> {\n        static std::string convert(long value);\n    };\n    template<>\n    struct StringMaker<long long> {\n        static std::string convert(long long value);\n    };\n    template<>\n    struct StringMaker<unsigned int> {\n        static std::string convert(unsigned int value);\n    };\n    template<>\n    struct StringMaker<unsigned long> {\n        static std::string convert(unsigned long value);\n    };\n    template<>\n    struct StringMaker<unsigned long long> {\n        static std::string convert(unsigned long long value);\n    };\n\n    template<>\n    struct StringMaker<bool> {\n        static std::string convert(bool b);\n    };\n\n    template<>\n    struct StringMaker<char> {\n        static std::string convert(char c);\n    };\n    template<>\n    struct StringMaker<signed char> {\n        static std::string convert(signed char c);\n    };\n    template<>\n    struct StringMaker<unsigned char> {\n        static std::string convert(unsigned char c);\n    };\n\n    template<>\n    struct StringMaker<std::nullptr_t> {\n        static std::string convert(std::nullptr_t);\n    };\n\n    template<>\n    struct StringMaker<float> {\n        static std::string convert(float value);\n        static int precision;\n    };\n\n    template<>\n    struct StringMaker<double> {\n        static std::string convert(double value);\n        static int precision;\n    };\n\n    template <typename T>\n    struct 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\n    template <typename R, typename C>\n    struct 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)\n    template <typename T>\n    struct StringMaker<T^> {\n        static std::string convert( T^ ref ) {\n            return ::Catch::Detail::clrReferenceToString(ref);\n        }\n    };\n#endif\n\n    namespace Detail {\n        template<typename InputIterator, typename Sentinel = InputIterator>\n        std::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    }\n\n#ifdef __OBJC__\n    template<>\n    struct StringMaker<NSString*> {\n        static std::string convert(NSString * nsstring) {\n            if (!nsstring)\n                return \"nil\";\n            return std::string(\"@\") + [nsstring UTF8String];\n        }\n    };\n    template<>\n    struct StringMaker<NSObject*> {\n        static std::string convert(NSObject* nsObject) {\n            return ::Catch::Detail::stringify([nsObject description]);\n        }\n\n    };\n    namespace Detail {\n        inline 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 {\n    template<typename T1, typename T2>\n    struct StringMaker<std::pair<T1, T2> > {\n        static std::string convert(const std::pair<T1, T2>& pair) {\n            ReusableStringStream rss;\n            rss << \"{ \"\n                << ::Catch::Detail::stringify(pair.first)\n                << \", \"\n                << ::Catch::Detail::stringify(pair.second)\n                << \" }\";\n            return rss.str();\n        }\n    };\n}\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 {\n    template<typename T>\n    struct 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}\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 {\n    namespace Detail {\n        template<\n            typename Tuple,\n            std::size_t N = 0,\n            bool = (N < std::tuple_size<Tuple>::value)\n            >\n            struct TupleElementPrinter {\n            static void print(const Tuple& tuple, std::ostream& os) {\n                os << (N ? \", \" : \" \")\n                    << ::Catch::Detail::stringify(std::get<N>(tuple));\n                TupleElementPrinter<Tuple, N + 1>::print(tuple, os);\n            }\n        };\n\n        template<\n            typename Tuple,\n            std::size_t N\n        >\n            struct TupleElementPrinter<Tuple, N, false> {\n            static void print(const Tuple&, std::ostream&) {}\n        };\n\n    }\n\n    template<typename ...Types>\n    struct 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}\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 {\n    template<>\n    struct StringMaker<std::monostate> {\n        static std::string convert(const std::monostate&) {\n            return \"{ }\";\n        }\n    };\n\n    template<typename... Elements>\n    struct 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(\n                    [](const auto& value) {\n                        return ::Catch::Detail::stringify(value);\n                    },\n                    variant\n                );\n            }\n        }\n    };\n}\n#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER\n\nnamespace Catch {\n    // Import begin/ end from std here\n    using std::begin;\n    using std::end;\n\n    namespace detail {\n        template <typename...>\n        struct void_type {\n            using type = void;\n        };\n\n        template <typename T, typename = void>\n        struct is_range_impl : std::false_type {\n        };\n\n        template <typename T>\n        struct is_range_impl<T, typename void_type<decltype(begin(std::declval<T>()))>::type> : std::true_type {\n        };\n    } // namespace detail\n\n    template <typename T>\n    struct is_range : detail::is_range_impl<T> {\n    };\n\n#if defined(_MANAGED) // Managed types are never ranges\n    template <typename T>\n    struct is_range<T^> {\n        static const bool value = false;\n    };\n#endif\n\n    template<typename Range>\n    std::string rangeToString( Range const& range ) {\n        return ::Catch::Detail::rangeToString( begin( range ), end( range ) );\n    }\n\n    // Handle vector<bool> specially\n    template<typename Allocator>\n    std::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\n    template<typename R>\n    struct StringMaker<R, typename std::enable_if<is_range<R>::value && !::Catch::Detail::IsStreamInsertable<R>::value>::type> {\n        static std::string convert( R const& range ) {\n            return rangeToString( range );\n        }\n    };\n\n    template <typename T, int SZ>\n    struct StringMaker<T[SZ]> {\n        static std::string convert(T const(&arr)[SZ]) {\n            return rangeToString(arr);\n        }\n    };\n\n} // namespace Catch\n\n// Separate std::chrono::duration specialization\n#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)\n#include <ctime>\n#include <ratio>\n#include <chrono>\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 << '/'\n        << 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\n    template<typename Value, typename Ratio>\n    struct 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    };\n    template<typename Value>\n    struct 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    };\n    template<typename Value>\n    struct 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    };\n    template<typename Value>\n    struct 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 std::chrono::time_point<system_clock>\n    template<typename Clock, typename Duration>\n    struct 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\n    template<typename Duration>\n    struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> {\n        static std::string convert(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}\n#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER\n\n#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \\\nnamespace Catch { \\\n    template<> struct StringMaker<enumName> { \\\n        static std::string convert( enumName value ) { \\\n            static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #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 (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\n    struct 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 ),\n            m_result( result )\n        {}\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    };\n\n    void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs );\n\n    template<typename LhsT, typename RhsT>\n    class 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\n                    ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) );\n        }\n\n    public:\n        BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs )\n        :   ITransientExpression{ true, comparisonResult },\n            m_lhs( lhs ),\n            m_op( op ),\n            m_rhs( rhs )\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        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\n    template<typename LhsT>\n    class 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\n    public:\n        explicit UnaryExpr( LhsT lhs )\n        :   ITransientExpression{ false, static_cast<bool>(lhs) },\n            m_lhs( lhs )\n        {}\n    };\n\n    // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int)\n    template<typename LhsT, typename RhsT>\n    auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return static_cast<bool>(lhs == rhs); }\n    template<typename T>\n    auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); }\n    template<typename T>\n    auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); }\n    template<typename T>\n    auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; }\n    template<typename T>\n    auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; }\n\n    template<typename LhsT, typename RhsT>\n    auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return static_cast<bool>(lhs != rhs); }\n    template<typename T>\n    auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); }\n    template<typename T>\n    auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); }\n    template<typename T>\n    auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; }\n    template<typename T>\n    auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; }\n\n    template<typename LhsT>\n    class ExprLhs {\n        LhsT m_lhs;\n    public:\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> {\n            return UnaryExpr<LhsT>{ m_lhs };\n        }\n    };\n\n    void handleExpression( ITransientExpression const& expr );\n\n    template<typename T>\n    void handleExpression( ExprLhs<T> const& expr ) {\n        handleExpression( expr.makeUnaryExpr() );\n    }\n\n    struct 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> {\n            return ExprLhs<bool>{ value };\n        }\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 <string>\n#include <chrono>\n\nnamespace Catch {\n\n    class AssertionResult;\n    struct AssertionInfo;\n    struct SectionInfo;\n    struct SectionEndInfo;\n    struct MessageInfo;\n    struct MessageBuilder;\n    struct Counts;\n    struct AssertionReaction;\n    struct SourceLineInfo;\n\n    struct ITransientExpression;\n    struct IGeneratorTracker;\n\n#if defined(CATCH_CONFIG_ENABLE_BENCHMARKING)\n    struct BenchmarkInfo;\n    template <typename Duration = std::chrono::duration<double, std::nano>>\n    struct BenchmarkStats;\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    struct IResultCapture {\n\n        virtual ~IResultCapture();\n\n        virtual bool sectionStarted(    SectionInfo const& sectionInfo,\n                                        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 ) -> 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\n                (   AssertionInfo const& info,\n                    ITransientExpression const& expr,\n                    AssertionReaction& reaction ) = 0;\n        virtual void handleMessage\n                (   AssertionInfo const& info,\n                    ResultWas::OfType resultType,\n                    StringRef const& message,\n                    AssertionReaction& reaction ) = 0;\n        virtual void handleUnexpectedExceptionNotThrown\n                (   AssertionInfo const& info,\n                    AssertionReaction& reaction ) = 0;\n        virtual void handleUnexpectedInflightException\n                (   AssertionInfo const& info,\n                    std::string const& message,\n                    AssertionReaction& reaction ) = 0;\n        virtual void handleIncomplete\n                (   AssertionInfo const& info ) = 0;\n        virtual void handleNonExpr\n                (   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\n    IResultCapture& getResultCapture();\n}\n\n// end catch_interfaces_capture.h\nnamespace Catch {\n\n    struct TestFailureException{};\n    struct AssertionResultData;\n    struct IResultCapture;\n    class RunContext;\n\n    class 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    public:\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\n    struct AssertionReaction {\n        bool shouldDebugBreak = false;\n        bool shouldThrow = false;\n    };\n\n    class AssertionHandler {\n        AssertionInfo m_assertionInfo;\n        AssertionReaction m_reaction;\n        bool m_completed = false;\n        IResultCapture& m_resultCapture;\n\n    public:\n        AssertionHandler\n            (   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\n    void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, 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\n    struct 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    private:\n        static unsigned int globalCount;\n    };\n\n    struct MessageStream {\n\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\n    struct 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\n    class ScopedMessage {\n    public:\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\n    class Capturer {\n        std::vector<MessageInfo> m_messages;\n        IResultCapture& m_resultCapture = getResultCapture();\n        size_t m_captured = 0;\n    public:\n        Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, 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 ) catch(...) { handler.handleUnexpectedInflightException(); }\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( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, 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        } 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( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \\\n        try { \\\n            static_cast<void>(__VA_ARGS__); \\\n            catchAssertionHandler.handleExceptionNotThrownAsExpected(); \\\n        } \\\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( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \\\n        if( catchAssertionHandler.allowThrows() ) \\\n            try { \\\n                static_cast<void>(__VA_ARGS__); \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \\\n            } \\\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( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) \", \" CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \\\n        if( catchAssertionHandler.allowThrows() ) \\\n            try { \\\n                static_cast<void>(expr); \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \\\n            } \\\n            catch( exceptionType const& ) { \\\n                catchAssertionHandler.handleExceptionThrownAsExpected(); \\\n            } \\\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, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \\\n        catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).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, #__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 )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log );\n\n///////////////////////////////////////////////////////////////////////////////\n#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \\\n    Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << 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( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) \", \" CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \\\n        if( catchAssertionHandler.allowThrows() ) \\\n            try { \\\n                static_cast<void>(__VA_ARGS__); \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \\\n            } \\\n            catch( ... ) { \\\n                Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher, #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\n    struct 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\n    struct Totals {\n\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}\n\n// end catch_totals.h\n#include <string>\n\nnamespace Catch {\n\n    struct SectionInfo {\n        SectionInfo\n            (   SourceLineInfo const& _lineInfo,\n                std::string const& _name );\n\n        // Deprecated\n        SectionInfo\n            (   SourceLineInfo const& _lineInfo,\n                std::string const& _name,\n                std::string const& ) : SectionInfo( _lineInfo, _name ) {}\n\n        std::string name;\n        std::string description; // !Deprecated: this will always be empty\n        SourceLineInfo lineInfo;\n    };\n\n    struct 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\n    auto getCurrentNanosecondsSinceEpoch() -> uint64_t;\n    auto getEstimatedClockResolution() -> uint64_t;\n\n    class Timer {\n        uint64_t m_nanoseconds = 0;\n    public:\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\n    class Section : NonCopyable {\n    public:\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\n    private:\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 ) = 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 ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, (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 <string>\n#include <memory>\n\nnamespace Catch {\n\n    class TestCase;\n    struct ITestCaseRegistry;\n    struct IExceptionTranslatorRegistry;\n    struct IExceptionTranslator;\n    struct IReporterRegistry;\n    struct IReporterFactory;\n    struct ITagAliasRegistry;\n    struct IMutableEnumValuesRegistry;\n\n    class StartupExceptionRegistry;\n\n    using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>;\n\n    struct 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\n    struct 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, std::string const& tag, SourceLineInfo const& lineInfo ) = 0;\n        virtual void registerStartupException() noexcept = 0;\n        virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0;\n    };\n\n    IRegistryHub const& getRegistryHub();\n    IMutableRegistryHub& getMutableRegistryHub();\n    void cleanUp();\n    std::string translateActiveException();\n\n}\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 {\n    using exceptionTranslateFunction = std::string(*)();\n\n    struct IExceptionTranslator;\n    using ExceptionTranslators = std::vector<std::unique_ptr<IExceptionTranslator const>>;\n\n    struct IExceptionTranslator {\n        virtual ~IExceptionTranslator();\n        virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0;\n    };\n\n    struct IExceptionTranslatorRegistry {\n        virtual ~IExceptionTranslatorRegistry();\n\n        virtual std::string translateActiveException() const = 0;\n    };\n\n    class ExceptionTranslatorRegistrar {\n        template<typename T>\n        class ExceptionTranslator : public IExceptionTranslator {\n        public:\n\n            ExceptionTranslator( std::string(*translateFunction)( T& ) )\n            : m_translateFunction( translateFunction )\n            {}\n\n            std::string translate( ExceptionTranslators::const_iterator it, 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                }\n                catch( T& ex ) {\n                    return m_translateFunction( ex );\n                }\n#endif\n            }\n\n        protected:\n            std::string(*m_translateFunction)( T& );\n        };\n\n    public:\n        template<typename T>\n        ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) {\n            getMutableRegistryHub().registerTranslator\n                ( new ExceptionTranslator<T>( translateFunction ) );\n        }\n    };\n}\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{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \\\n    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \\\n    static std::string translatorName( signature )\n\n#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( 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\n    class Approx {\n    private:\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\n    public:\n        explicit Approx ( double value );\n\n        static Approx custom();\n\n        Approx operator-() const;\n\n        template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type>\n        Approx operator()( T const& value ) const {\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, 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\n        template <typename T, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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\n    private:\n        double m_epsilon;\n        double m_margin;\n        double m_scale;\n        double m_value;\n    };\n} // end namespace Detail\n\nnamespace literals {\n    Detail::Approx operator \"\" _a(long double val);\n    Detail::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 <string>\n#include <iosfwd>\n#include <vector>\n\nnamespace Catch {\n\n    bool startsWith( std::string const& s, std::string const& prefix );\n    bool startsWith( std::string const& s, char prefix );\n    bool endsWith( std::string const& s, std::string const& suffix );\n    bool endsWith( std::string const& s, char suffix );\n    bool contains( std::string const& s, std::string const& infix );\n    void toLowerInPlace( std::string& s );\n    std::string toLower( std::string const& s );\n    //! Returns a new string without whitespace at the start/end\n    std::string trim( std::string const& str );\n    //! Returns a substring of the original ref without whitespace. Beware lifetimes!\n    StringRef trim(StringRef ref);\n\n    // !!! Be aware, returns refs into original string - make sure original string outlives them\n    std::vector<StringRef> splitStringRef( StringRef str, char delimiter );\n    bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis );\n\n    struct 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}\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 {\n    namespace Impl {\n\n        template<typename ArgT> struct MatchAllOf;\n        template<typename ArgT> struct MatchAnyOf;\n        template<typename ArgT> struct MatchNotOf;\n\n        class MatcherUntypedBase {\n        public:\n            MatcherUntypedBase() = default;\n            MatcherUntypedBase ( MatcherUntypedBase const& ) = default;\n            MatcherUntypedBase& operator = ( MatcherUntypedBase const& ) = delete;\n            std::string toString() const;\n\n        protected:\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\n        template<typename ObjectT>\n        struct 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 some kind of signature mismatch that breaks compilation\n        template<>\n        struct 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\n        template<typename T>\n        struct MatcherBase : MatcherUntypedBase, MatcherMethod<T> {\n\n            MatchAllOf<T> operator && ( MatcherBase const& other ) const;\n            MatchAnyOf<T> operator || ( MatcherBase const& other ) const;\n            MatchNotOf<T> operator ! () const;\n        };\n\n        template<typename ArgT>\n        struct 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        };\n        template<typename ArgT>\n        struct MatchAnyOf : MatcherBase<ArgT> {\n\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\n        template<typename ArgT>\n        struct MatchNotOf : MatcherBase<ArgT> {\n\n            MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {}\n\n            bool match( ArgT const& arg ) const override {\n                return !m_underlyingMatcher.match( arg );\n            }\n\n            std::string describe() const override {\n                return \"not \" + m_underlyingMatcher.toString();\n            }\n            MatcherBase<ArgT> const& m_underlyingMatcher;\n        };\n\n        template<typename T>\n        MatchAllOf<T> MatcherBase<T>::operator && ( MatcherBase const& other ) const {\n            return MatchAllOf<T>() && *this && other;\n        }\n        template<typename T>\n        MatchAnyOf<T> MatcherBase<T>::operator || ( MatcherBase const& other ) const {\n            return MatchAnyOf<T>() || *this || other;\n        }\n        template<typename T>\n        MatchNotOf<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;\npublic:\n\n    ExceptionMessageMatcher(std::string const& message):\n        m_message(message)\n    {}\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\n    namespace Floating {\n\n        enum class FloatingPointKind : uint8_t;\n\n        struct WithinAbsMatcher : MatcherBase<double> {\n            WithinAbsMatcher(double target, double margin);\n            bool match(double const& matchee) const override;\n            std::string describe() const override;\n        private:\n            double m_target;\n            double m_margin;\n        };\n\n        struct 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        private:\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.\n        struct WithinRelMatcher : MatcherBase<double> {\n            WithinRelMatcher(double target, double epsilon);\n            bool match(double const& matchee) const override;\n            std::string describe() const override;\n        private:\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\n    Floating::WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff);\n    Floating::WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff);\n    Floating::WithinAbsMatcher WithinAbs(double target, double margin);\n    Floating::WithinRelMatcher WithinRel(double target, double eps);\n    // defaults epsilon to 100*numeric_limits<double>::epsilon()\n    Floating::WithinRelMatcher WithinRel(double target);\n    Floating::WithinRelMatcher WithinRel(float target, float eps);\n    // defaults epsilon to 100*numeric_limits<float>::epsilon()\n    Floating::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 {\n    std::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;\npublic:\n\n    PredicateMatcher(std::function<bool(T const&)> const& elem, std::string const& descr)\n        :m_predicate(std::move(elem)),\n        m_description(Detail::finalizeDescription(descr))\n    {}\n\n    bool match( T const& item ) const override {\n        return m_predicate(item);\n    }\n\n    std::string describe() const override {\n        return m_description;\n    }\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.\n    template<typename T>\n    Generic::PredicateMatcher<T> Predicate(std::function<bool(T const&)> const& predicate, 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\n    namespace StdString {\n\n        struct CasedString\n        {\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\n        struct 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\n        struct EqualsMatcher : StringMatcherBase {\n            EqualsMatcher( CasedString const& comparator );\n            bool match( std::string const& source ) const override;\n        };\n        struct ContainsMatcher : StringMatcherBase {\n            ContainsMatcher( CasedString const& comparator );\n            bool match( std::string const& source ) const override;\n        };\n        struct StartsWithMatcher : StringMatcherBase {\n            StartsWithMatcher( CasedString const& comparator );\n            bool match( std::string const& source ) const override;\n        };\n        struct EndsWithMatcher : StringMatcherBase {\n            EndsWithMatcher( CasedString const& comparator );\n            bool match( std::string const& source ) const override;\n        };\n\n        struct 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\n        private:\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\n    StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );\n    StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );\n    StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );\n    StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );\n    StdString::RegexMatcher Matches( std::string const& regex, 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\n    namespace Vector {\n        template<typename T, typename Alloc>\n        struct ContainsElementMatcher : MatcherBase<std::vector<T, Alloc>> {\n\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\n        template<typename T, typename AllocComp, typename AllocMatch>\n        struct ContainsMatcher : MatcherBase<std::vector<T, AllocMatch>> {\n\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\n        template<typename T, typename AllocComp, typename AllocMatch>\n        struct EqualsMatcher : MatcherBase<std::vector<T, AllocMatch>> {\n\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\n        template<typename T, typename AllocComp, typename AllocMatch>\n        struct ApproxMatcher : MatcherBase<std::vector<T, AllocMatch>> {\n\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\n        template<typename T, typename AllocComp, typename AllocMatch>\n        struct 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        private:\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\n    template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\n    Vector::ContainsMatcher<T, AllocComp, AllocMatch> Contains( std::vector<T, AllocComp> const& comparator ) {\n        return Vector::ContainsMatcher<T, AllocComp, AllocMatch>( comparator );\n    }\n\n    template<typename T, typename Alloc = std::allocator<T>>\n    Vector::ContainsElementMatcher<T, Alloc> VectorContains( T const& comparator ) {\n        return Vector::ContainsElementMatcher<T, Alloc>( comparator );\n    }\n\n    template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\n    Vector::EqualsMatcher<T, AllocComp, AllocMatch> Equals( std::vector<T, AllocComp> const& comparator ) {\n        return Vector::EqualsMatcher<T, AllocComp, AllocMatch>( comparator );\n    }\n\n    template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\n    Vector::ApproxMatcher<T, AllocComp, AllocMatch> Approx( std::vector<T, AllocComp> const& comparator ) {\n        return Vector::ApproxMatcher<T, AllocComp, AllocMatch>( comparator );\n    }\n\n    template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>\n    Vector::UnorderedEqualsMatcher<T, AllocComp, AllocMatch> UnorderedEquals(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\n    template<typename ArgT, typename MatcherT>\n    class MatchExpr : public ITransientExpression {\n        ArgT const& m_arg;\n        MatcherT m_matcher;\n        StringRef m_matcherString;\n    public:\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\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\n    using StringMatcher = Matchers::Impl::MatcherBase<std::string>;\n\n    void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef const& matcherString  );\n\n    template<typename ArgT, typename MatcherT>\n    auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef const& matcherString  ) -> 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( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) \", \" CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \\\n        INTERNAL_CATCH_TRY { \\\n            catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher, #matcher##_catch_sr ) ); \\\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( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) \", \" CATCH_INTERNAL_STRINGIFY(exceptionType) \", \" CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \\\n        if( catchAssertionHandler.allowThrows() ) \\\n            try { \\\n                static_cast<void>(__VA_ARGS__ ); \\\n                catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \\\n            } \\\n            catch( exceptionType const& ex ) { \\\n                catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher, #matcher##_catch_sr ) ); \\\n            } \\\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\n#include <memory>\n\nnamespace Catch {\n\n    namespace Generators {\n        class GeneratorUntypedBase {\n        public:\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        };\n        using GeneratorBasePtr = std::unique_ptr<GeneratorUntypedBase>;\n\n    } // namespace Generators\n\n    struct 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)\n    template <typename Ex>\n    [[noreturn]]\n    void throw_exception(Ex const& e) {\n        throw e;\n    }\n#else // ^^ Exceptions are enabled //  Exceptions are disabled vv\n    [[noreturn]]\n    void throw_exception(std::exception const& e);\n#endif\n\n    [[noreturn]]\n    void throw_logic_error(std::string const& msg);\n    [[noreturn]]\n    void throw_domain_error(std::string const& msg);\n    [[noreturn]]\n    void throw_runtime_error(std::string const& msg);\n\n} // namespace Catch;\n\n#define CATCH_MAKE_MSG(...) \\\n    (Catch::ReusableStringStream() << __VA_ARGS__).str()\n\n#define CATCH_INTERNAL_ERROR(...) \\\n    Catch::throw_logic_error(CATCH_MAKE_MSG( CATCH_INTERNAL_LINEINFO << \": Internal Catch2 error: \" << __VA_ARGS__))\n\n#define CATCH_ERROR(...) \\\n    Catch::throw_domain_error(CATCH_MAKE_MSG( __VA_ARGS__ ))\n\n#define CATCH_RUNTIME_ERROR(...) \\\n    Catch::throw_runtime_error(CATCH_MAKE_MSG( __VA_ARGS__ ))\n\n#define CATCH_ENFORCE( condition, ... ) \\\n    do{ if( !(condition) ) CATCH_ERROR( __VA_ARGS__ ); } while(false)\n\n// end catch_enforce.h\n#include <memory>\n#include <vector>\n#include <cassert>\n\n#include <utility>\n#include <exception>\n\nnamespace Catch {\n\nclass GeneratorException : public std::exception {\n    const char* const m_msg = \"\";\n\npublic:\n    GeneratorException(const char* msg):\n        m_msg(msg)\n    {}\n\n    const char* what() const noexcept override final;\n};\n\nnamespace Generators {\n\n    // !TBD move this into its own location?\n    namespace pf{\n        template<typename T, typename... Args>\n        std::unique_ptr<T> make_unique( Args&&... args ) {\n            return std::unique_ptr<T>(new T(std::forward<Args>(args)...));\n        }\n    }\n\n    template<typename T>\n    struct 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\n    template<typename T>\n    class SingleValueGenerator final : public IGenerator<T> {\n        T m_value;\n    public:\n        SingleValueGenerator(T&& value) : m_value(std::move(value)) {}\n\n        T const& get() const override {\n            return m_value;\n        }\n        bool next() override {\n            return false;\n        }\n    };\n\n    template<typename T>\n    class 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    public:\n        FixedValuesGenerator( std::initializer_list<T> values ) : m_values( values ) {}\n\n        T const& get() const override {\n            return m_values[m_idx];\n        }\n        bool next() override {\n            ++m_idx;\n            return m_idx < m_values.size();\n        }\n    };\n\n    template <typename T>\n    class GeneratorWrapper final {\n        std::unique_ptr<IGenerator<T>> m_generator;\n    public:\n        GeneratorWrapper(std::unique_ptr<IGenerator<T>> generator):\n            m_generator(std::move(generator))\n        {}\n        T const& get() const {\n            return m_generator->get();\n        }\n        bool next() {\n            return m_generator->next();\n        }\n    };\n\n    template <typename T>\n    GeneratorWrapper<T> value(T&& value) {\n        return GeneratorWrapper<T>(pf::make_unique<SingleValueGenerator<T>>(std::forward<T>(value)));\n    }\n    template <typename T>\n    GeneratorWrapper<T> values(std::initializer_list<T> values) {\n        return GeneratorWrapper<T>(pf::make_unique<FixedValuesGenerator<T>>(values));\n    }\n\n    template<typename T>\n    class 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) {\n            m_generators.emplace_back(value(std::forward<T>(val)));\n        }\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\n    public:\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 {\n            return m_generators[m_current].get();\n        }\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\n    template<typename... Ts>\n    GeneratorWrapper<std::tuple<Ts...>> table( 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 specific type\n    template <typename T>\n    struct as {};\n\n    template<typename T, typename... Gs>\n    auto makeGenerators( GeneratorWrapper<T>&& generator, Gs &&... moreGenerators ) -> Generators<T> {\n        return Generators<T>(std::move(generator), std::forward<Gs>(moreGenerators)...);\n    }\n    template<typename T>\n    auto makeGenerators( GeneratorWrapper<T>&& generator ) -> Generators<T> {\n        return Generators<T>(std::move(generator));\n    }\n    template<typename T, typename... Gs>\n    auto makeGenerators( T&& val, Gs &&... moreGenerators ) -> Generators<T> {\n        return makeGenerators( value( std::forward<T>( val ) ), std::forward<Gs>( moreGenerators )... );\n    }\n    template<typename T, typename U, typename... Gs>\n    auto makeGenerators( as<T>, U&& val, Gs &&... moreGenerators ) -> Generators<T> {\n        return makeGenerators( value( T( std::forward<U>( val ) ) ), std::forward<Gs>( moreGenerators )... );\n    }\n\n    auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker&;\n\n    template<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.\n    auto generate( StringRef generatorName, SourceLineInfo const& lineInfo, L const& generatorExpression ) -> 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 = 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; return makeGenerators( __VA_ARGS__ ); } ) //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; return makeGenerators( __VA_ARGS__ ); } ) //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; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace)\n\n// end catch_generators.hpp\n// start catch_generators_generic.hpp\n\nnamespace Catch {\nnamespace Generators {\n\n    template <typename T>\n    class TakeGenerator : public IGenerator<T> {\n        GeneratorWrapper<T> m_generator;\n        size_t m_returned = 0;\n        size_t m_target;\n    public:\n        TakeGenerator(size_t target, GeneratorWrapper<T>&& generator):\n            m_generator(std::move(generator)),\n            m_target(target)\n        {\n            assert(target != 0 && \"Empty generators are not allowed\");\n        }\n        T const& get() const override {\n            return m_generator.get();\n        }\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\n    template <typename T>\n    GeneratorWrapper<T> take(size_t target, GeneratorWrapper<T>&& generator) {\n        return GeneratorWrapper<T>(pf::make_unique<TakeGenerator<T>>(target, std::move(generator)));\n    }\n\n    template <typename T, typename Predicate>\n    class FilterGenerator : public IGenerator<T> {\n        GeneratorWrapper<T> m_generator;\n        Predicate m_predicate;\n    public:\n        template <typename P = Predicate>\n        FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator):\n            m_generator(std::move(generator)),\n            m_predicate(std::forward<P>(pred))\n        {\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 = nextImpl();\n                if (!has_initial_value) {\n                    Catch::throw_exception(GeneratorException(\"No valid value found in filtered generator\"));\n                }\n            }\n        }\n\n        T const& get() const override {\n            return m_generator.get();\n        }\n\n        bool next() override {\n            return nextImpl();\n        }\n\n    private:\n        bool nextImpl() {\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            return success;\n        }\n    };\n\n    template <typename T, typename Predicate>\n    GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) {\n        return GeneratorWrapper<T>(std::unique_ptr<IGenerator<T>>(pf::make_unique<FilterGenerator<T, Predicate>>(std::forward<Predicate>(pred), std::move(generator))));\n    }\n\n    template <typename T>\n    class 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    public:\n        RepeatGenerator(size_t repeats, GeneratorWrapper<T>&& generator):\n            m_generator(std::move(generator)),\n            m_target_repeats(repeats)\n        {\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 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 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\n    template <typename T>\n    GeneratorWrapper<T> repeat(size_t repeats, GeneratorWrapper<T>&& generator) {\n        return GeneratorWrapper<T>(pf::make_unique<RepeatGenerator<T>>(repeats, std::move(generator)));\n    }\n\n    template <typename T, typename U, typename Func>\n    class 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    public:\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\n        T const& get() const override {\n            return m_cache;\n        }\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\n    template <typename Func, typename U, typename T = FunctionReturnType<Func, U>>\n    GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) {\n        return GeneratorWrapper<T>(\n            pf::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator))\n        );\n    }\n\n    template <typename T, typename U, typename Func>\n    GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) {\n        return GeneratorWrapper<T>(\n            pf::make_unique<MapGenerator<T, U, Func>>(std::forward<Func>(function), std::move(generator))\n        );\n    }\n\n    template <typename T>\n    class 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    public:\n        ChunkGenerator(size_t size, GeneratorWrapper<T> generator) :\n            m_chunk_size(size), m_generator(std::move(generator))\n        {\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(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 {\n            return m_chunk;\n        }\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\n    template <typename T>\n    GeneratorWrapper<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\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\n    struct IResultCapture;\n    struct IRunner;\n    struct IConfig;\n    struct IMutableContext;\n\n    using IConfigPtr = std::shared_ptr<IConfig const>;\n\n    struct IContext\n    {\n        virtual ~IContext();\n\n        virtual IResultCapture* getResultCapture() = 0;\n        virtual IRunner* getRunner() = 0;\n        virtual IConfigPtr const& getConfig() const = 0;\n    };\n\n    struct IMutableContext : IContext\n    {\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\n    private:\n        static IMutableContext *currentContext;\n        friend IMutableContext& getCurrentMutableContext();\n        friend void cleanUpContext();\n        static void createContext();\n    };\n\n    inline IMutableContext& getCurrentMutableContext()\n    {\n        if( !IMutableContext::currentContext )\n            IMutableContext::createContext();\n        // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)\n        return *IMutableContext::currentContext;\n    }\n\n    inline IContext& getCurrentContext()\n    {\n        return getCurrentMutableContext();\n    }\n\n    void cleanUpContext();\n\n    class SimplePcg32;\n    SimplePcg32& rng();\n}\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\n    template<typename T>\n    class Option {\n    public:\n        Option() : nullableValue( nullptr ) {}\n        Option( T const& _value )\n        : nullableValue( new( storage ) T( _value ) )\n        {}\n        Option( Option const& _other )\n        : nullableValue( _other ? new( storage ) T( *_other ) : nullptr )\n        {}\n\n        ~Option() {\n            reset();\n        }\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 {\n            return nullableValue ? *nullableValue : defaultValue;\n        }\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 {\n            return some();\n        }\n\n    private:\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 <string>\n#include <vector>\n#include <memory>\n\nnamespace Catch {\n\n    enum class Verbosity {\n        Quiet = 0,\n        Normal,\n        High\n    };\n\n    struct WarnAbout { enum What {\n        Nothing = 0x00,\n        NoAssertions = 0x01,\n        NoTests = 0x02\n    }; };\n\n    struct ShowDurations { enum OrNot {\n        DefaultForReporter,\n        Always,\n        Never\n    }; };\n    struct RunTests { enum InWhatOrder {\n        InDeclarationOrder,\n        InLexicographicalOrder,\n        InRandomOrder\n    }; };\n    struct UseColour { enum YesOrNo {\n        Auto,\n        Yes,\n        No\n    }; };\n    struct WaitForKeypress { enum When {\n        Never,\n        BeforeStart = 1,\n        BeforeExit = 2,\n        BeforeStartAndExit = BeforeStart | BeforeExit\n    }; };\n\n    class TestSpec;\n\n    struct IConfig : NonCopyable {\n\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\n    using IConfigPtr = std::shared_ptr<IConfig const>;\n}\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)\n    class SimplePcg32 {\n        using state_type = std::uint64_t;\n    public:\n        using result_type = std::uint32_t;\n        static constexpr result_type (min)() {\n            return 0;\n        }\n        static constexpr result_type (max)() {\n            return static_cast<result_type>(-1);\n        }\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\n    private:\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;\npublic:\n\n    RandomFloatingGenerator(Float a, Float b):\n        m_rng(rng()),\n        m_dist(a, b) {\n        static_cast<void>(next());\n    }\n\n    Float const& get() const override {\n        return m_current_number;\n    }\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;\npublic:\n\n    RandomIntegerGenerator(Integer a, Integer b):\n        m_rng(rng()),\n        m_dist(a, b) {\n        static_cast<void>(next());\n    }\n\n    Integer const& get() const override {\n        return m_current_number;\n    }\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,\nGeneratorWrapper<T>>::type\nrandom(T a, T b) {\n    return GeneratorWrapper<T>(\n        pf::make_unique<RandomIntegerGenerator<T>>(a, b)\n    );\n}\n\ntemplate <typename T>\ntypename std::enable_if<std::is_floating_point<T>::value,\nGeneratorWrapper<T>>::type\nrandom(T a, T b) {\n    return GeneratorWrapper<T>(\n        pf::make_unique<RandomFloatingGenerator<T>>(a, b)\n    );\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),\n        m_end(end),\n        m_step(step),\n        m_positive(m_step > T(0))\n    {\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)) && \"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\n    T const& get() const override {\n        return m_current;\n    }\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, \"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, \"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;\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(GeneratorException(\"IteratorGenerator received no valid values\"));\n        }\n    }\n\n    T const& get() const override {\n        return m_elems[m_current];\n    }\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,\n          typename ResultType = typename Container::value_type>\nGeneratorWrapper<ResultType> from_range(Container const& cnt) {\n    return GeneratorWrapper<ResultType>(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 <string>\n#include <vector>\n#include <memory>\n\n#ifdef __clang__\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Wpadded\"\n#endif\n\nnamespace Catch {\n\n    struct ITestInvoker;\n\n    struct 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\n    class TestCase : public TestCaseInfo {\n    public:\n\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\n    private:\n        std::shared_ptr<ITestInvoker> test;\n    };\n\n    TestCase makeTestCase(  ITestInvoker* testCase,\n                            std::string const& className,\n                            NameAndTags const& nameAndTags,\n                            SourceLineInfo const& lineInfo );\n}\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\n    struct IRunner {\n        virtual ~IRunner();\n        virtual bool aborting() const = 0;\n    };\n}\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\n    class OcMethod : public ITestInvoker {\n\n    public:\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    private:\n        virtual ~OcMethod() {}\n\n        Class m_cls;\n        SEL m_sel;\n    };\n\n    namespace Detail{\n\n        inline std::string getAnnotation(   Class cls,\n                                            std::string const& annotationName,\n                                            std::string const& testCaseName ) {\n            NSString* selStr = [[NSString alloc] 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    }\n\n    inline 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( new OcMethod( cls, selector ), className, 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\n    namespace Matchers {\n        namespace Impl {\n        namespace NSStringMatchers {\n\n            struct StringHolder : MatcherBase<NSString*>{\n                StringHolder( NSString* substr ) : m_substr( [substr copy] ){}\n                StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){}\n                StringHolder() {\n                    arcSafeRelease( m_substr );\n                }\n\n                bool match( NSString* str ) const override {\n                    return false;\n                }\n\n                NSString* CATCH_ARC_STRONG m_substr;\n            };\n\n            struct Equals : StringHolder {\n                Equals( NSString* substr ) : StringHolder( substr ){}\n\n                bool match( NSString* str ) const override {\n                    return  (str != nil || m_substr == nil ) &&\n                            [str isEqualToString:m_substr];\n                }\n\n                std::string describe() const override {\n                    return \"equals string: \" + Catch::Detail::stringify( m_substr );\n                }\n            };\n\n            struct 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\n            struct StartsWith : StringHolder {\n                StartsWith( 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 == 0;\n                }\n\n                std::string describe() const override {\n                    return \"starts with: \" + Catch::Detail::stringify( m_substr );\n                }\n            };\n            struct 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\n        inline Impl::NSStringMatchers::Equals\n            Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); }\n\n        inline Impl::NSStringMatchers::Contains\n            Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); }\n\n        inline Impl::NSStringMatchers::StartsWith\n            StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); }\n\n        inline Impl::NSStringMatchers::EndsWith\n            EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); }\n\n    } // namespace Matchers\n\n    using 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{ \\\nreturn @ name; \\\n} \\\n+(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Description_test_, uniqueSuffix ) \\\n{ \\\nreturn @ 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\n{\n    class WildcardPattern {\n        enum WildcardPosition {\n            NoWildcard = 0,\n            WildcardAtStart = 1,\n            WildcardAtEnd = 2,\n            WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd\n        };\n\n    public:\n\n        WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity );\n        virtual ~WildcardPattern() = default;\n        virtual bool matches( std::string const& str ) const;\n\n    private:\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}\n\n// end catch_wildcard_pattern.h\n#include <string>\n#include <vector>\n#include <memory>\n\nnamespace Catch {\n\n    struct IConfig;\n\n    class 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        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        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        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        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\n    public:\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\n    private:\n        std::vector<Filter> m_filters;\n        std::vector<std::string> m_invalidArgs;\n        friend class TestSpecParser;\n    };\n}\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\n    struct TagAlias;\n\n    struct 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\n    class 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\n    public:\n        TestSpecParser( ITagAliasRegistry const& tagAliases );\n\n        TestSpecParser& parse( std::string const& arg );\n        TestSpec testSpec();\n\n    private:\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\n    };\n    TestSpec 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 <vector>\n#include <string>\n\n#ifndef CATCH_CONFIG_CONSOLE_WIDTH\n#define CATCH_CONFIG_CONSOLE_WIDTH 80\n#endif\n\nnamespace Catch {\n\n    struct IStream;\n\n    struct 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\n    class Config : public IConfig {\n    public:\n\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\n    private:\n\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\n    struct AssertionResultData\n    {\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\n    class AssertionResult {\n    public:\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\n\nnamespace Catch {\n    namespace Benchmark {\n        template <typename Duration>\n        struct 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 {\n    namespace Benchmark {\n        struct 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 {\n                return low_severe + low_mild + high_mild + high_severe;\n            }\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 <string>\n#include <iosfwd>\n#include <map>\n#include <set>\n#include <memory>\n#include <algorithm>\n\nnamespace Catch {\n\n    struct 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\n    private:\n        std::ostream* m_stream;\n        IConfigPtr m_fullConfig;\n    };\n\n    struct ReporterPreferences {\n        bool shouldRedirectStdOut = false;\n        bool shouldReportAllAssertions = false;\n    };\n\n    template<typename T>\n    struct 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\n    struct TestRunInfo {\n        TestRunInfo( std::string const& _name );\n        std::string name;\n    };\n    struct GroupInfo {\n        GroupInfo(  std::string const& _name,\n                    std::size_t _groupIndex,\n                    std::size_t _groupsCount );\n\n        std::string name;\n        std::size_t groupIndex;\n        std::size_t groupsCounts;\n    };\n\n    struct 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\n    struct 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\n    struct 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\n    struct TestGroupStats {\n        TestGroupStats( GroupInfo const& _groupInfo,\n                        Totals const& _totals,\n                        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\n    struct TestRunStats {\n        TestRunStats(   TestRunInfo const& _runInfo,\n                        Totals const& _totals,\n                        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)\n    struct 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\n    template <class Duration>\n    struct 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), [](Duration d) { return Duration2(d); });\n            return {\n                info,\n                std::move(samples2),\n                mean,\n                standardDeviation,\n                outliers,\n                outlierVariance,\n            };\n        }\n    };\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    struct 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    };\n    using IStreamingReporterPtr = std::unique_ptr<IStreamingReporter>;\n\n    struct IReporterFactory {\n        virtual ~IReporterFactory();\n        virtual IStreamingReporterPtr create( ReporterConfig const& config ) const = 0;\n        virtual std::string getDescription() const = 0;\n    };\n    using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>;\n\n    struct 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, 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 <cstring>\n#include <cfloat>\n#include <cstdio>\n#include <cassert>\n#include <memory>\n#include <ostream>\n\nnamespace Catch {\n    void prepareExpandedExpression(AssertionResult& result);\n\n    // Returns double formatted as %.3f (format expected on output)\n    std::string getFormattedDuration( double duration );\n\n    //! Should the reporter show\n    bool shouldShowDuration( IConfig const& config, double duration );\n\n    std::string serializeFilters( std::vector<std::string> const& container );\n\n    template<typename DerivedT>\n    struct StreamingReporterBase : IStreamingReporter {\n\n        StreamingReporterBase( ReporterConfig const& _config )\n        :   m_config( _config.fullConfig() ),\n            stream( _config.stream() )\n        {\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 {\n            return m_reporterPrefs;\n        }\n\n        static std::set<Verbosity> getSupportedVerbosities() {\n            return { Verbosity::Normal };\n        }\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 {\n            currentGroupInfo = _groupInfo;\n        }\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\n    template<typename DerivedT>\n    struct 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() ),\n            stream( _config.stream() )\n        {\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 {\n            return m_reporterPrefs;\n        }\n\n        static std::set<Verbosity> getSupportedVerbosities() {\n            return { Verbosity::Normal };\n        }\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            }\n            else {\n                SectionNode& parentNode = *m_sectionStack.back();\n                auto it =\n                    std::find_if(   parentNode.childSections.begin(),\n                                    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                }\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\n    template<char C>\n    char 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\n    struct 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\n    struct 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\n    private:\n        bool m_moved = false;\n    };\n\n    std::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\n\nnamespace Catch {\n\n    template<typename T>\n    class ReporterRegistrar {\n\n        class ReporterFactory : public IReporterFactory {\n\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 {\n                return T::getDescription();\n            }\n        };\n\n    public:\n\n        explicit ReporterRegistrar( std::string const& name ) {\n            getMutableRegistryHub().registerReporter( name, std::make_shared<ReporterFactory>() );\n        }\n    };\n\n    template<typename T>\n    class ListenerRegistrar {\n\n        class ListenerFactory : public IReporterFactory {\n\n            IStreamingReporterPtr create( ReporterConfig const& config ) const override {\n                return std::unique_ptr<T>( new T( config ) );\n            }\n            std::string getDescription() const override {\n                return std::string();\n            }\n        };\n\n    public:\n\n        ListenerRegistrar() {\n            getMutableRegistryHub().registerListener( std::make_shared<ListenerFactory>() );\n        }\n    };\n}\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{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } \\\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{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } \\\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\n    struct CompactReporter : StreamingReporterBase<CompactReporter> {\n\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\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 switch\n                              // Note that 4062 (not all labels are handled\n                              // and default is missing) is enabled\n#endif\n\nnamespace Catch {\n    // Fwd decls\n    struct SummaryColumn;\n    class TablePrinter;\n\n    struct 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    private:\n\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, std::vector<SummaryColumn> const& cols, std::size_t row);\n\n        void printTotalsDivider(Totals const& totals);\n        void printSummaryDivider();\n        void printTestFilters();\n\n    private:\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 {\n    enum class XmlFormatting {\n        None = 0x00,\n        Indent = 0x01,\n        Newline = 0x02,\n    };\n\n    XmlFormatting operator | (XmlFormatting lhs, XmlFormatting rhs);\n    XmlFormatting operator & (XmlFormatting lhs, XmlFormatting rhs);\n\n    class XmlEncode {\n    public:\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\n    private:\n        std::string m_str;\n        ForWhat m_forWhat;\n    };\n\n    class XmlWriter {\n    public:\n\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, XmlFormatting fmt = XmlFormatting::Newline | 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, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n        ScopedElement scopedElement( std::string const& name, 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, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n        XmlWriter& writeComment(std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent);\n\n        void writeStylesheetRef( std::string const& url );\n\n        XmlWriter& writeBlankLine();\n\n        void ensureTagClosed();\n\n    private:\n\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}\n\n// end catch_xmlwriter.h\nnamespace Catch {\n\n    class JunitReporter : public CumulativeReporterBase<JunitReporter> {\n    public:\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 {\n    class XmlReporter : public StreamingReporterBase<XmlReporter> {\n    public:\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\n    public: // StreamingReporterBase\n\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\n    private:\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\n// start catch_clock.hpp\n\n// Clocks\n\n\n#include <chrono>\n#include <ratio>\n\nnamespace Catch {\n    namespace Benchmark {\n        template <typename Clock>\n        using ClockDuration = typename Clock::duration;\n        template <typename Clock>\n        using FloatDuration = std::chrono::duration<double, typename Clock::period>;\n\n        template <typename Clock>\n        using TimePoint = typename Clock::time_point;\n\n        using default_clock = std::chrono::steady_clock;\n\n        template <typename Clock>\n        struct now {\n            TimePoint<Clock> operator()() const {\n                return Clock::now();\n            }\n        };\n\n        using 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\n#if defined(_MSC_VER)\n#   include <atomic> // atomic_thread_fence\n#endif\n\nnamespace Catch {\n    namespace Benchmark {\n#if defined(__GNUC__) || defined(__clang__)\n        template <typename T>\n        inline void keep_memory(T* p) {\n            asm volatile(\"\" : : \"g\"(p) : \"memory\");\n        }\n        inline void keep_memory() {\n            asm volatile(\"\" : : : \"memory\");\n        }\n\n        namespace Detail {\n            inline void optimizer_barrier() { keep_memory(); }\n        } // namespace Detail\n#elif defined(_MSC_VER)\n\n#pragma optimize(\"\", off)\n        template <typename T>\n        inline 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\n        namespace Detail {\n            inline void optimizer_barrier() {\n                std::atomic_thread_fence(std::memory_order_seq_cst);\n            }\n        } // namespace Detail\n\n#endif\n\n        template <typename T>\n        inline void deoptimize_value(T&& x) {\n            keep_memory(&x);\n        }\n\n        template <typename Fn, typename... Args>\n        inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> 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\n        template <typename Fn, typename... Args>\n        inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> 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\n#include <type_traits>\n#include <utility>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename T>\n            struct CompleteType { using type = T; };\n            template <>\n            struct CompleteType<void> { struct type {}; };\n\n            template <typename T>\n            using CompleteType_t = typename CompleteType<T>::type;\n\n            template <typename Result>\n            struct 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            };\n            template <>\n            struct 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 :(\n            template <typename Fun, typename... Args>\n            CompleteType_t<FunctionReturnType<Fun, Args...>> complete_invoke(Fun&& fun, Args&&... args) {\n                return CompleteInvoker<FunctionReturnType<Fun, Args...>>::invoke(std::forward<Fun>(fun), std::forward<Args>(args)...);\n            }\n\n            const std::string benchmarkErrorMsg = \"a benchmark failed to run successfully\";\n        } // namespace Detail\n\n        template <typename Fun>\n        Detail::CompleteType_t<FunctionReturnType<Fun>> user_code(Fun&& fun) {\n            CATCH_TRY{\n                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 {\n    namespace Benchmark {\n        namespace Detail {\n            struct ChronometerConcept {\n                virtual void start() = 0;\n                virtual void finish() = 0;\n                virtual ~ChronometerConcept() = default;\n            };\n            template <typename Clock>\n            struct 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\n        struct Chronometer {\n        public:\n            template <typename Fun>\n            void measure(Fun&& fun) { measure(std::forward<Fun>(fun), is_callable<Fun(int)>()); }\n\n            int runs() const { return k; }\n\n            Chronometer(Detail::ChronometerConcept& meter, int k)\n                : impl(&meter)\n                , k(k) {}\n\n        private:\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) 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\n\nnamespace Catch {\n    namespace Benchmark {\n        template <typename Duration>\n        struct EnvironmentEstimate {\n            Duration mean;\n            OutlierClassification outliers;\n\n            template <typename Duration2>\n            operator EnvironmentEstimate<Duration2>() const {\n                return { mean, outliers };\n            }\n        };\n        template <typename Clock>\n        struct 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\n// start catch_benchmark_function.hpp\n\n // Dumb std::function implementation for consistent call overhead\n\n\n#include <cassert>\n#include <type_traits>\n#include <utility>\n#include <memory>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename T>\n            using Decay = typename std::decay<T>::type;\n            template <typename T, typename U>\n            struct is_related\n                : std::is_same<Decay<T>, Decay<U>> {};\n\n            /// We need to reinvent std::function because every piece of code that might add overhead\n            /// in a measurement context needs to have consistent performance characteristics so that we\n            /// can account for it in the measurement.\n            /// Implementations of std::function with optimizations that aren't always applicable, like\n            /// small buffer optimizations, are not uncommon.\n            /// This is effectively an implementation of std::function without any such optimizations;\n            /// it may be slow, but it is consistently slow.\n            struct BenchmarkFunction {\n            private:\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 {\n                        fun(meter);\n                    }\n                    void call(Chronometer meter, std::false_type) const {\n                        meter.measure(fun);\n                    }\n\n                    Fun fun;\n                };\n\n                struct do_nothing { void operator()() const {} };\n\n                template <typename T>\n                BenchmarkFunction(model<T>* c) : f(c) {}\n\n            public:\n                BenchmarkFunction()\n                    : 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)\n                    : f(std::move(that.f)) {}\n\n                BenchmarkFunction(BenchmarkFunction const& that)\n                    : 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\n            private:\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\n#include <type_traits>\n#include <utility>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename Fun>\n            struct repeater {\n                void operator()(int k) const {\n                    for (int i = 0; i < k; ++i) {\n                        fun();\n                    }\n                }\n                Fun fun;\n            };\n            template <typename Fun>\n            repeater<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\n// start catch_measure.hpp\n\n// Measure\n\n\n// start catch_timing.hpp\n\n// Timing\n\n\n#include <tuple>\n#include <type_traits>\n\nnamespace Catch {\n    namespace Benchmark {\n        template <typename Duration, typename Result>\n        struct Timing {\n            Duration elapsed;\n            Result result;\n            int iterations;\n        };\n        template <typename Clock, typename Func, typename... Args>\n        using TimingOf = 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 {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename Clock, typename Fun, typename... Args>\n            TimingOf<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 <utility>\n#include <type_traits>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename Clock, typename Fun>\n            TimingOf<Clock, Fun, int> measure_one(Fun&& fun, int iters, std::false_type) {\n                return Detail::measure<Clock>(fun, iters);\n            }\n            template <typename Clock, typename Fun>\n            TimingOf<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\n            template <typename Clock, typename Fun>\n            using run_for_at_least_argument_t = typename std::conditional<is_callable<Fun(Chronometer)>::value, Chronometer, int>::type;\n\n            struct 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\n            template <typename Clock, typename Fun>\n            TimingOf<Clock, Fun, run_for_at_least_argument_t<Clock, Fun>> run_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 {\n    namespace Benchmark {\n        template <typename Duration>\n        struct 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, warmup_iterations };\n            }\n\n            template <typename Clock>\n            std::vector<FloatDuration<Clock>> run(const IConfig &cfg, Environment<FloatDuration<Clock>> env) const {\n                // warmup a bit\n                Detail::run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(warmup_time), warmup_iterations, 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()) 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\n// start catch_stats.hpp\n\n// Statistical analysis tools\n\n\n#include <algorithm>\n#include <functional>\n#include <vector>\n#include <iterator>\n#include <numeric>\n#include <tuple>\n#include <cmath>\n#include <utility>\n#include <cstddef>\n#include <random>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            using sample = std::vector<double>;\n\n            double weighted_average_quantile(int k, int q, std::vector<double>::iterator first, std::vector<double>::iterator last);\n\n            template <typename Iterator>\n            OutlierClassification 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) ++o.low_severe;\n                    else if (t < lom) ++o.low_mild;\n                    else if (t > his) ++o.high_severe;\n                    else if (t > him) ++o.high_mild;\n                    ++o.samples_seen;\n                }\n                return o;\n            }\n\n            template <typename Iterator>\n            double mean(Iterator first, Iterator last) {\n                auto count = last - first;\n                double sum = std::accumulate(first, last, 0.);\n                return sum / count;\n            }\n\n            template <typename URng, typename Iterator, typename Estimator>\n            sample 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, [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\n            template <typename Estimator, typename Iterator>\n            sample 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\n            inline double normal_cdf(double x) {\n                return std::erfc(-x / std::sqrt(2.0)) / 2.0;\n            }\n\n            double erfc_inv(double x);\n\n            double normal_quantile(double p);\n\n            template <typename Iterator, typename Estimator>\n            Estimate<double> bootstrap(double confidence_level, Iterator first, Iterator last, sample const& resample, 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) 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(jack.begin(), jack.end(), std::make_pair(0., 0.), [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(), [point](double x) { return x < point; }) / (double)n;\n                // degenerate case with uniform samples\n                if (prob_n == 0) 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 {\n                    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\n            double outlier_variance(Estimate<double> mean, Estimate<double> stddev, int n);\n\n            struct bootstrap_analysis {\n                Estimate<double> mean;\n                Estimate<double> standard_deviation;\n                double outlier_variance;\n            };\n\n            bootstrap_analysis analyse_samples(double confidence_level, int n_resamples, std::vector<double>::iterator first, 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 <iterator>\n#include <tuple>\n#include <vector>\n#include <cmath>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename Clock>\n            std::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(),\n                    std::back_inserter(deltas),\n                    [](TimePoint<Clock> a, TimePoint<Clock> b) { return static_cast<double>((a - b).count()); });\n\n                return deltas;\n            }\n\n            const auto warmup_iterations = 10000;\n            const auto warmup_time = std::chrono::milliseconds(100);\n            const auto minimum_ticks = 1000;\n            const auto warmup_seed = 10000;\n            const auto clock_resolution_estimation_time = std::chrono::milliseconds(500);\n            const auto clock_cost_estimation_time_limit = std::chrono::seconds(1);\n            const auto clock_cost_estimation_tick_limit = 100000;\n            const auto clock_cost_estimation_time = std::chrono::milliseconds(10);\n            const auto clock_cost_estimation_iterations = 10000;\n\n            template <typename Clock>\n            int warmup() {\n                return run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(warmup_time), warmup_seed, &resolution<Clock>)\n                    .iterations;\n            }\n            template <typename Clock>\n            EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_resolution(int iterations) {\n                auto r = run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(clock_resolution_estimation_time), iterations, &resolution<Clock>)\n                    .result;\n                return {\n                    FloatDuration<Clock>(mean(r.begin(), r.end())),\n                    classify_outliers(r.begin(), r.end()),\n                };\n            }\n            template <typename Clock>\n            EnvironmentEstimate<FloatDuration<Clock>> estimate_clock_cost(FloatDuration<Clock> resolution) {\n                auto time_limit = (std::min)(\n                    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                    }).elapsed;\n                };\n                time_clock(1);\n                int iters = clock_cost_estimation_iterations;\n                auto&& r = run_for_at_least<Clock>(std::chrono::duration_cast<ClockDuration<Clock>>(clock_cost_estimation_time), iters, 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\n            template <typename Clock>\n            Environment<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\n// start catch_sample_analysis.hpp\n\n// Benchmark results\n\n\n#include <algorithm>\n#include <vector>\n#include <string>\n#include <iterator>\n\nnamespace Catch {\n    namespace Benchmark {\n        template <typename Duration>\n        struct 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), [](Duration d) { return Duration2(d); });\n                return {\n                    std::move(samples2),\n                    mean,\n                    standard_deviation,\n                    outliers,\n                    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 {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename Duration, typename Iterator>\n            SampleAnalysis<Duration> analyse(const IConfig &cfg, Environment<Duration>, Iterator first, 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), [](Duration d) { return d.count(); });\n\n                    auto analysis = Catch::Benchmark::Detail::analyse_samples(cfg.benchmarkConfidenceInterval(), cfg.benchmarkResamples(), 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), [](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 {\n                        std::move(samples),\n                        Estimate<Duration>{mean, mean, mean, 0.0},\n                        Estimate<Duration>{Duration(0), Duration(0), Duration(0), 0.0},\n                        OutlierClassification{},\n                        0.0\n                    };\n                }\n            }\n        } // namespace Detail\n    } // namespace Benchmark\n} // namespace Catch\n\n// end catch_analyse.hpp\n#include <algorithm>\n#include <functional>\n#include <string>\n#include <vector>\n#include <cmath>\n\nnamespace Catch {\n    namespace Benchmark {\n        struct Benchmark {\n            Benchmark(std::string &&name)\n                : name(std::move(name)) {}\n\n            template <class FUN>\n            Benchmark(std::string &&name, FUN &&func)\n                : fun(std::move(func)), name(std::move(name)) {}\n\n            template <typename Clock>\n            ExecutionPlan<FloatDuration<Clock>> prepare(const IConfig &cfg, Environment<FloatDuration<Clock>> env) const {\n                auto min_time = env.clock_resolution.mean * Detail::minimum_ticks;\n                auto run_time = std::max(min_time, std::chrono::duration_cast<decltype(min_time)>(cfg.benchmarkWarmupTime()));\n                auto&& test = Detail::run_for_at_least<Clock>(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, std::chrono::duration_cast<FloatDuration<Clock>>(cfg.benchmarkWarmupTime()), 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([&] {\n                        return prepare<Clock>(*cfg, env);\n                    });\n\n                    BenchmarkInfo info {\n                        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\n                    getResultCapture().benchmarkStarting(info);\n\n                    auto samples = user_code([&] {\n                        return plan.template run<Clock>(*cfg, env);\n                    });\n\n                    auto analysis = Detail::analyse(*cfg, env, samples.begin(), samples.end());\n                    BenchmarkStats<FloatDuration<Clock>> stats{ info, analysis.samples, analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance };\n                    getResultCapture().benchmarkEnded(stats);\n\n                } CATCH_CATCH_ALL{\n                    if (translateActiveException() != Detail::benchmarkErrorMsg) // benchmark errors have been reported, 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() {\n                return true;\n            }\n\n        private:\n            Detail::BenchmarkFunction fun;\n            std::string name;\n        };\n    }\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\n#include <type_traits>\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n            template <typename T, bool Destruct>\n            struct ObjectStorage\n            {\n                ObjectStorage() : data() {}\n\n                ObjectStorage(const ObjectStorage& other)\n                {\n                    new(&data) T(other.stored_object());\n                }\n\n                ObjectStorage(ObjectStorage&& other)\n                {\n                    new(&data) T(std::move(other.stored_object()));\n                }\n\n                ~ObjectStorage() { destruct_on_exit<T>(); }\n\n                template <typename... Args>\n                void construct(Args&&... args)\n                {\n                    new (&data) T(std::forward<Args>(args)...);\n                }\n\n                template <bool AllowManualDestruction = !Destruct>\n                typename std::enable_if<AllowManualDestruction>::type destruct()\n                {\n                    stored_object().~T();\n                }\n\n            private:\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) { destruct<true>(); }\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() {\n                    return *static_cast<T*>(static_cast<void*>(&data));\n                }\n\n                T const& stored_object() const {\n                    return *static_cast<T*>(static_cast<void*>(&data));\n                }\n\n                struct { alignas(T) unsigned char data[sizeof(T)]; }  data;\n            };\n        }\n\n        template <typename T>\n        using storage_for = Detail::ObjectStorage<T, true>;\n\n        template <typename T>\n        using destructable_object = Detail::ObjectStorage<T, false>;\n    }\n}\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 <string>\n#include <vector>\n#include <memory>\n\nnamespace Catch {\nnamespace TestCaseTracking {\n\n    struct 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\n                && lhs.location == rhs.location;\n        }\n    };\n\n    class ITracker;\n\n    using ITrackerPtr = std::shared_ptr<ITracker>;\n\n    class  ITracker {\n        NameAndLocation m_nameAndLocation;\n\n    public:\n        ITracker(NameAndLocation const& nameAndLoc) :\n            m_nameAndLocation(nameAndLoc)\n        {}\n\n        // static queries\n        NameAndLocation const& nameAndLocation() const {\n            return m_nameAndLocation;\n        }\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\n    class TrackerContext {\n\n        enum RunState {\n            NotStarted,\n            Executing,\n            CompletedCycle\n        };\n\n        ITrackerPtr m_rootTracker;\n        ITracker* m_currentTracker = nullptr;\n        RunState m_runState = NotStarted;\n\n    public:\n\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\n    class TrackerBase : public ITracker {\n    protected:\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\n    public:\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 {\n            return m_runState != NotStarted;\n        }\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\n    private:\n        void moveToParent();\n        void moveToThis();\n    };\n\n    class SectionTracker : public TrackerBase {\n        std::vector<std::string> m_filters;\n        std::string m_trimmed_name;\n    public:\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::TrackerContext;\nusing TestCaseTracking::SectionTracker;\n\n} // namespace Catch\n\n// end catch_test_case_tracker.h\n\n// start catch_leak_detector.h\n\nnamespace Catch {\n\n    struct LeakDetector {\n        LeakDetector();\n        ~LeakDetector();\n    };\n\n}\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 {\n    double erf_inv(double x) {\n        // Code accompanying the article \"Approximating the erfinv function\" in GPU 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\n    double 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., [m](double a, double b) {\n            double diff = b - m;\n            return a + diff * diff;\n            }) / (last - first);\n            return std::sqrt(variance);\n    }\n\n}\n\nnamespace Catch {\n    namespace Benchmark {\n        namespace Detail {\n\n            double weighted_average_quantile(int k, int q, std::vector<double>::iterator first, 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) return xj;\n\n                auto xj1 = *std::min_element(first + (j + 1), last);\n                return xj + g * (xj1 - xj);\n            }\n\n            double erfc_inv(double x) {\n                return erf_inv(1.0 - x);\n            }\n\n            double 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\n            double 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\n            bootstrap_analysis analyse_samples(double confidence_level, int n_resamples, std::vector<double>::iterator first, 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>(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}\n\nnamespace Catch {\nnamespace Detail {\n\n    Approx::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    {}\n\n    Approx Approx::custom() {\n        return Approx( 0 );\n    }\n\n    Approx Approx::operator-() const {\n        auto temp(*this);\n        temp.m_value = -temp.m_value;\n        return temp;\n    }\n\n    std::string Approx::toString() const {\n        ReusableStringStream rss;\n        rss << \"Approx( \" << ::Catch::Detail::stringify( m_value ) << \" )\";\n        return rss.str();\n    }\n\n    bool Approx::equalityComparisonImpl(const double other) const {\n        // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value\n        // Thanks to Richard Harris for his help refining the scaled margin value\n        return marginComparison(m_value, other, m_margin)\n            || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(std::isinf(m_value)? 0 : m_value)));\n    }\n\n    void 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\n    void 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 {\n    Detail::Approx operator \"\" _a(long double val) {\n        return Detail::Approx(val);\n    }\n    Detail::Approx operator \"\" _a(unsigned long long val) {\n        return Detail::Approx(val);\n    }\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 {\n    bool 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 0xd43e0000\")\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__)\n    extern \"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() []{ if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } }()\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\n    class 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    public:\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\n    class FatalConditionHandlerGuard {\n        FatalConditionHandler* m_handler;\n    public:\n        FatalConditionHandlerGuard(FatalConditionHandler* handler):\n            m_handler(handler) {\n            m_handler->engage();\n        }\n        ~FatalConditionHandlerGuard() {\n            m_handler->disengage();\n        }\n    };\n\n} // end namespace Catch\n\n// end catch_fatal_condition.h\n#include <string>\n\nnamespace Catch {\n\n    struct IMutableContext;\n\n    ///////////////////////////////////////////////////////////////////////////\n\n    class RunContext : public IResultCapture, public IRunner {\n\n    public:\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, std::size_t groupIndex, std::size_t groupsCount );\n        void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount );\n\n        Totals runTest(TestCase const& testCase);\n\n        IConfigPtr config() const;\n        IStreamingReporter& reporter() const;\n\n    public: // IResultCapture\n\n        // Assertion handlers\n        void handleExpr\n                (   AssertionInfo const& info,\n                    ITransientExpression const& expr,\n                    AssertionReaction& reaction ) override;\n        void handleMessage\n                (   AssertionInfo const& info,\n                    ResultWas::OfType resultType,\n                    StringRef const& message,\n                    AssertionReaction& reaction ) override;\n        void handleUnexpectedExceptionNotThrown\n                (   AssertionInfo const& info,\n                    AssertionReaction& reaction ) override;\n        void handleUnexpectedInflightException\n                (   AssertionInfo const& info,\n                    std::string const& message,\n                    AssertionReaction& reaction ) override;\n        void handleIncomplete\n                (   AssertionInfo const& info ) override;\n        void handleNonExpr\n                (   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 ) -> 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\n    public:\n        // !TBD We need to do this another way!\n        bool aborting() const final;\n\n    private:\n\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\n                (   AssertionInfo const &info,\n                    ResultWas::OfType resultType,\n                    ITransientExpression const *expr,\n                    bool negated );\n\n        void populateReaction( AssertionReaction& reaction );\n\n    private:\n\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\n    void seedRng(IConfig const& config);\n    unsigned int rngSeed();\n} // end namespace Catch\n\n// end catch_run_context.h\nnamespace Catch {\n\n    namespace {\n        auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& {\n            expr.streamReconstructedExpression( os );\n            return os;\n        }\n    }\n\n    LazyExpression::LazyExpression( bool isNegated )\n    :   m_isNegated( isNegated )\n    {}\n\n    LazyExpression::LazyExpression( LazyExpression const& other ) : m_isNegated( other.m_isNegated ) {}\n\n    LazyExpression::operator bool() const {\n        return m_transientExpression != nullptr;\n    }\n\n    auto 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        }\n        else {\n            os << \"{** error - unchecked empty expression requested **}\";\n        }\n        return os;\n    }\n\n    AssertionHandler::AssertionHandler\n        (   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    {}\n\n    void AssertionHandler::handleExpr( ITransientExpression const& expr ) {\n        m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction );\n    }\n    void AssertionHandler::handleMessage(ResultWas::OfType resultType, StringRef const& message) {\n        m_resultCapture.handleMessage( m_assertionInfo, resultType, message, m_reaction );\n    }\n\n    auto AssertionHandler::allowThrows() const -> bool {\n        return getCurrentContext().getConfig()->allowThrows();\n    }\n\n    void AssertionHandler::complete() {\n        setCompleted();\n        if( m_reaction.shouldDebugBreak ) {\n\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    }\n    void AssertionHandler::setCompleted() {\n        m_completed = true;\n    }\n\n    void AssertionHandler::handleUnexpectedInflightException() {\n        m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction );\n    }\n\n    void AssertionHandler::handleExceptionThrownAsExpected() {\n        m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);\n    }\n    void AssertionHandler::handleExceptionNotThrownAsExpected() {\n        m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction);\n    }\n\n    void AssertionHandler::handleUnexpectedExceptionNotThrown() {\n        m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction );\n    }\n\n    void 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 it\n    // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp\n    void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, 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 {\n    AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression):\n        lazyExpression(_lazyExpression),\n        resultType(_resultType) {}\n\n    std::string AssertionResultData::reconstructExpression() const {\n\n        if( reconstructedExpression.empty() ) {\n            if( lazyExpression ) {\n                ReusableStringStream rss;\n                rss << lazyExpression;\n                reconstructedExpression = rss.str();\n            }\n        }\n        return reconstructedExpression;\n    }\n\n    AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data )\n    :   m_info( info ),\n        m_resultData( data )\n    {}\n\n    // Result was a success\n    bool AssertionResult::succeeded() const {\n        return Catch::isOk( m_resultData.resultType );\n    }\n\n    // Result was a success, or failure is suppressed\n    bool AssertionResult::isOk() const {\n        return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition );\n    }\n\n    ResultWas::OfType AssertionResult::getResultType() const {\n        return m_resultData.resultType;\n    }\n\n    bool AssertionResult::hasExpression() const {\n        return !m_info.capturedExpression.empty();\n    }\n\n    bool AssertionResult::hasMessage() const {\n        return !m_resultData.message.empty();\n    }\n\n    std::string AssertionResult::getExpression() const {\n        // Possibly overallocating by 3 characters should be basically free\n        std::string expr; 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\n    std::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\n    bool AssertionResult::hasExpandedExpression() const {\n        return hasExpression() && getExpandedExpression() != getExpression();\n    }\n\n    std::string AssertionResult::getExpandedExpression() const {\n        std::string expr = m_resultData.reconstructExpression();\n        return expr.empty()\n                ? getExpression()\n                : expr;\n    }\n\n    std::string AssertionResult::getMessage() const {\n        return m_resultData.message;\n    }\n    SourceLineInfo AssertionResult::getSourceInfo() const {\n        return m_info.lineInfo;\n    }\n\n    StringRef AssertionResult::getTestMacroName() const {\n        return m_info.macroName;\n    }\n\n} // end namespace Catch\n// end catch_assertionresult.cpp\n// start catch_capture_matchers.cpp\n\nnamespace Catch {\n\n    using 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 a string and infers\n    // the Equals matcher (so the header does not mention matchers)\n    void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, 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 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\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\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\tstatic std::string chars = \" \\t\\n\\r\";\n\treturn chars.find(c) != std::string::npos;\n}\ninline auto isBreakableBefore(char c) -> bool {\n\tstatic std::string chars = \"[({<|\";\n\treturn chars.find(c) != std::string::npos;\n}\ninline auto isBreakableAfter(char c) -> bool {\n\tstatic std::string chars = \"])}>.,:;*+-=&/\\\\\";\n\treturn chars.find(c) != std::string::npos;\n}\n\nclass Columns;\n\nclass Column {\n\tstd::vector<std::string> m_strings;\n\tsize_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH;\n\tsize_t m_indent = 0;\n\tsize_t m_initialIndent = std::string::npos;\n\npublic:\n\tclass iterator {\n\t\tfriend Column;\n\n\t\tColumn const& m_column;\n\t\tsize_t m_stringIndex = 0;\n\t\tsize_t m_pos = 0;\n\n\t\tsize_t m_len = 0;\n\t\tsize_t m_end = 0;\n\t\tbool m_suffix = false;\n\n\t\titerator(Column const& column, size_t stringIndex)\n\t\t\t: m_column(column),\n\t\t\tm_stringIndex(stringIndex) {}\n\n\t\tauto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; }\n\n\t\tauto isBoundary(size_t at) const -> bool {\n\t\t\tassert(at > 0);\n\t\t\tassert(at <= line().size());\n\n\t\t\treturn at == line().size() ||\n\t\t\t\t(isWhitespace(line()[at]) && !isWhitespace(line()[at - 1])) ||\n\t\t\t\tisBreakableBefore(line()[at]) ||\n\t\t\t\tisBreakableAfter(line()[at - 1]);\n\t\t}\n\n\t\tvoid calcLength() {\n\t\t\tassert(m_stringIndex < m_column.m_strings.size());\n\n\t\t\tm_suffix = false;\n\t\t\tauto width = m_column.m_width - indent();\n\t\t\tm_end = m_pos;\n\t\t\tif (line()[m_pos] == '\\n') {\n\t\t\t\t++m_end;\n\t\t\t}\n\t\t\twhile (m_end < line().size() && line()[m_end] != '\\n')\n\t\t\t\t++m_end;\n\n\t\t\tif (m_end < m_pos + width) {\n\t\t\t\tm_len = m_end - m_pos;\n\t\t\t} else {\n\t\t\t\tsize_t len = width;\n\t\t\t\twhile (len > 0 && !isBoundary(m_pos + len))\n\t\t\t\t\t--len;\n\t\t\t\twhile (len > 0 && isWhitespace(line()[m_pos + len - 1]))\n\t\t\t\t\t--len;\n\n\t\t\t\tif (len > 0) {\n\t\t\t\t\tm_len = len;\n\t\t\t\t} else {\n\t\t\t\t\tm_suffix = true;\n\t\t\t\t\tm_len = width - 1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tauto indent() const -> size_t {\n\t\t\tauto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos;\n\t\t\treturn initial == std::string::npos ? m_column.m_indent : initial;\n\t\t}\n\n\t\tauto addIndentAndSuffix(std::string const &plain) const -> std::string {\n\t\t\treturn std::string(indent(), ' ') + (m_suffix ? plain + \"-\" : plain);\n\t\t}\n\n\tpublic:\n\t\tusing difference_type = std::ptrdiff_t;\n\t\tusing value_type = std::string;\n\t\tusing pointer = value_type * ;\n\t\tusing reference = value_type & ;\n\t\tusing iterator_category = std::forward_iterator_tag;\n\n\t\texplicit iterator(Column const& column) : m_column(column) {\n\t\t\tassert(m_column.m_width > m_column.m_indent);\n\t\t\tassert(m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent);\n\t\t\tcalcLength();\n\t\t\tif (m_len == 0)\n\t\t\t\tm_stringIndex++; // Empty string\n\t\t}\n\n\t\tauto operator *() const -> std::string {\n\t\t\tassert(m_stringIndex < m_column.m_strings.size());\n\t\t\tassert(m_pos <= m_end);\n\t\t\treturn addIndentAndSuffix(line().substr(m_pos, m_len));\n\t\t}\n\n\t\tauto operator ++() -> iterator& {\n\t\t\tm_pos += m_len;\n\t\t\tif (m_pos < line().size() && line()[m_pos] == '\\n')\n\t\t\t\tm_pos += 1;\n\t\t\telse\n\t\t\t\twhile (m_pos < line().size() && isWhitespace(line()[m_pos]))\n\t\t\t\t\t++m_pos;\n\n\t\t\tif (m_pos == line().size()) {\n\t\t\t\tm_pos = 0;\n\t\t\t\t++m_stringIndex;\n\t\t\t}\n\t\t\tif (m_stringIndex < m_column.m_strings.size())\n\t\t\t\tcalcLength();\n\t\t\treturn *this;\n\t\t}\n\t\tauto operator ++(int) -> iterator {\n\t\t\titerator prev(*this);\n\t\t\toperator++();\n\t\t\treturn prev;\n\t\t}\n\n\t\tauto operator ==(iterator const& other) const -> bool {\n\t\t\treturn\n\t\t\t\tm_pos == other.m_pos &&\n\t\t\t\tm_stringIndex == other.m_stringIndex &&\n\t\t\t\t&m_column == &other.m_column;\n\t\t}\n\t\tauto operator !=(iterator const& other) const -> bool {\n\t\t\treturn !operator==(other);\n\t\t}\n\t};\n\tusing const_iterator = iterator;\n\n\texplicit Column(std::string const& text) { m_strings.push_back(text); }\n\n\tauto width(size_t newWidth) -> Column& {\n\t\tassert(newWidth > 0);\n\t\tm_width = newWidth;\n\t\treturn *this;\n\t}\n\tauto indent(size_t newIndent) -> Column& {\n\t\tm_indent = newIndent;\n\t\treturn *this;\n\t}\n\tauto initialIndent(size_t newIndent) -> Column& {\n\t\tm_initialIndent = newIndent;\n\t\treturn *this;\n\t}\n\n\tauto width() const -> size_t { return m_width; }\n\tauto begin() const -> iterator { return iterator(*this); }\n\tauto end() const -> iterator { return { *this, m_strings.size() }; }\n\n\tinline friend std::ostream& operator << (std::ostream& os, Column const& col) {\n\t\tbool first = true;\n\t\tfor (auto line : col) {\n\t\t\tif (first)\n\t\t\t\tfirst = false;\n\t\t\telse\n\t\t\t\tos << \"\\n\";\n\t\t\tos << line;\n\t\t}\n\t\treturn os;\n\t}\n\n\tauto operator + (Column const& other)->Columns;\n\n\tauto toString() const -> std::string {\n\t\tstd::ostringstream oss;\n\t\toss << *this;\n\t\treturn oss.str();\n\t}\n};\n\nclass Spacer : public Column {\n\npublic:\n\texplicit Spacer(size_t spaceWidth) : Column(\"\") {\n\t\twidth(spaceWidth);\n\t}\n};\n\nclass Columns {\n\tstd::vector<Column> m_columns;\n\npublic:\n\n\tclass iterator {\n\t\tfriend Columns;\n\t\tstruct EndTag {};\n\n\t\tstd::vector<Column> const& m_columns;\n\t\tstd::vector<Column::iterator> m_iterators;\n\t\tsize_t m_activeIterators;\n\n\t\titerator(Columns const& columns, EndTag)\n\t\t\t: m_columns(columns.m_columns),\n\t\t\tm_activeIterators(0) {\n\t\t\tm_iterators.reserve(m_columns.size());\n\n\t\t\tfor (auto const& col : m_columns)\n\t\t\t\tm_iterators.push_back(col.end());\n\t\t}\n\n\tpublic:\n\t\tusing difference_type = std::ptrdiff_t;\n\t\tusing value_type = std::string;\n\t\tusing pointer = value_type * ;\n\t\tusing reference = value_type & ;\n\t\tusing iterator_category = std::forward_iterator_tag;\n\n\t\texplicit iterator(Columns const& columns)\n\t\t\t: m_columns(columns.m_columns),\n\t\t\tm_activeIterators(m_columns.size()) {\n\t\t\tm_iterators.reserve(m_columns.size());\n\n\t\t\tfor (auto const& col : m_columns)\n\t\t\t\tm_iterators.push_back(col.begin());\n\t\t}\n\n\t\tauto operator ==(iterator const& other) const -> bool {\n\t\t\treturn m_iterators == other.m_iterators;\n\t\t}\n\t\tauto operator !=(iterator const& other) const -> bool {\n\t\t\treturn m_iterators != other.m_iterators;\n\t\t}\n\t\tauto operator *() const -> std::string {\n\t\t\tstd::string row, padding;\n\n\t\t\tfor (size_t i = 0; i < m_columns.size(); ++i) {\n\t\t\t\tauto width = m_columns[i].width();\n\t\t\t\tif (m_iterators[i] != m_columns[i].end()) {\n\t\t\t\t\tstd::string col = *m_iterators[i];\n\t\t\t\t\trow += padding + col;\n\t\t\t\t\tif (col.size() < width)\n\t\t\t\t\t\tpadding = std::string(width - col.size(), ' ');\n\t\t\t\t\telse\n\t\t\t\t\t\tpadding = \"\";\n\t\t\t\t} else {\n\t\t\t\t\tpadding += std::string(width, ' ');\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn row;\n\t\t}\n\t\tauto operator ++() -> iterator& {\n\t\t\tfor (size_t i = 0; i < m_columns.size(); ++i) {\n\t\t\t\tif (m_iterators[i] != m_columns[i].end())\n\t\t\t\t\t++m_iterators[i];\n\t\t\t}\n\t\t\treturn *this;\n\t\t}\n\t\tauto operator ++(int) -> iterator {\n\t\t\titerator prev(*this);\n\t\t\toperator++();\n\t\t\treturn prev;\n\t\t}\n\t};\n\tusing const_iterator = iterator;\n\n\tauto begin() const -> iterator { return iterator(*this); }\n\tauto end() const -> iterator { return { *this, iterator::EndTag() }; }\n\n\tauto operator += (Column const& col) -> Columns& {\n\t\tm_columns.push_back(col);\n\t\treturn *this;\n\t}\n\tauto operator + (Column const& col) -> Columns {\n\t\tColumns combined = *this;\n\t\tcombined += col;\n\t\treturn combined;\n\t}\n\n\tinline friend std::ostream& operator << (std::ostream& os, Columns const& cols) {\n\n\t\tbool first = true;\n\t\tfor (auto line : cols) {\n\t\t\tif (first)\n\t\t\t\tfirst = false;\n\t\t\telse\n\t\t\t\tos << \"\\n\";\n\t\t\tos << line;\n\t\t}\n\t\treturn os;\n\t}\n\n\tauto toString() const -> std::string {\n\t\tstd::ostringstream oss;\n\t\toss << *this;\n\t\treturn oss.str();\n\t}\n};\n\ninline auto Column::operator + (Column const& other) -> Columns {\n\tColumns cols;\n\tcols += *this;\n\tcols += other;\n\treturn cols;\n}\n}\n\n}\n}\n\n// ----------- end of #include from clara_textflow.hpp -----------\n// ........... back in clara.hpp\n\n#include <cctype>\n#include <string>\n#include <memory>\n#include <set>\n#include <algorithm>\n\n#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) )\n#define CATCH_PLATFORM_WINDOWS\n#endif\n\nnamespace Catch { namespace clara {\nnamespace detail {\n\n    // Traits for extracting arg and return type of lambdas (for single argument lambdas)\n    template<typename L>\n    struct UnaryLambdaTraits : UnaryLambdaTraits<decltype( &L::operator() )> {};\n\n    template<typename ClassT, typename ReturnT, typename... Args>\n    struct UnaryLambdaTraits<ReturnT( ClassT::* )( Args... ) const> {\n        static const bool isValid = false;\n    };\n\n    template<typename ClassT, typename ReturnT, typename ArgT>\n    struct 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\n    class TokenStream;\n\n    // Transport for raw args (copied from main args, or supplied via init list for testing)\n    class Args {\n        friend TokenStream;\n        std::string m_exeName;\n        std::vector<std::string> m_args;\n\n    public:\n        Args( int argc, char const* const* argv )\n            : m_exeName(argv[0]),\n              m_args(argv + 1, argv + argc) {}\n\n        Args( std::initializer_list<std::string> args )\n        :   m_exeName( *args.begin() ),\n            m_args( args.begin()+1, args.end() )\n        {}\n\n        auto exeName() const -> std::string {\n            return m_exeName;\n        }\n    };\n\n    // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string\n    // may encode an option + its argument if the : or = form is used\n    enum class TokenType {\n        Option, Argument\n    };\n    struct Token {\n        TokenType type;\n        std::string token;\n    };\n\n    inline 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 uniformly handled\n    class 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\n    public:\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 ) {\n            loadBuffer();\n        }\n\n        explicit operator bool() const {\n            return !m_tokenBuffer.empty() || it != itEnd;\n        }\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\n    class ResultBase {\n    public:\n        enum Type {\n            Ok, LogicError, RuntimeError\n        };\n\n    protected:\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\n    template<typename T>\n    class ResultValueBase : public ResultBase {\n    public:\n        auto value() const -> T const & {\n            enforceOk();\n            return m_value;\n        }\n\n    protected:\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 ) {\n            new( &m_value ) T( value );\n        }\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\n    template<>\n    class ResultValueBase<void> : public ResultBase {\n    protected:\n        using ResultBase::ResultBase;\n    };\n\n    template<typename T = void>\n    class BasicResult : public ResultValueBase<T> {\n    public:\n        template<typename U>\n        explicit BasicResult( BasicResult<U> const &other )\n        :   ResultValueBase<T>( other.type() ),\n            m_errorMessage( other.errorMessage() )\n        {\n            assert( type() != ResultBase::Ok );\n        }\n\n        template<typename U>\n        static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; }\n        static auto ok() -> BasicResult { return { ResultBase::Ok }; }\n        static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; }\n        static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; }\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\n    protected:\n        void enforceOk() const override {\n\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),\n            m_errorMessage(message)\n        {\n            assert( m_type != ResultBase::Ok );\n        }\n\n        using ResultValueBase<T>::ResultValueBase;\n        using ResultBase::m_type;\n    };\n\n    enum class ParseResultType {\n        Matched, NoMatch, ShortCircuitAll, ShortCircuitSame\n    };\n\n    class ParseState {\n    public:\n\n        ParseState( ParseResultType type, TokenStream const &remainingTokens )\n        : m_type(type),\n          m_remainingTokens( remainingTokens )\n        {}\n\n        auto type() const -> ParseResultType { return m_type; }\n        auto remainingTokens() const -> TokenStream { return m_remainingTokens; }\n\n    private:\n        ParseResultType m_type;\n        TokenStream m_remainingTokens;\n    };\n\n    using Result = BasicResult<void>;\n    using ParserResult = BasicResult<ParseResultType>;\n    using InternalParseResult = BasicResult<ParseState>;\n\n    struct HelpColumns {\n        std::string left;\n        std::string right;\n    };\n\n    template<typename T>\n    inline 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    }\n    inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult {\n        target = source;\n        return ParserResult::ok( ParseResultType::Matched );\n    }\n    inline auto convertInto( std::string const &source, bool &target ) -> ParserResult {\n        std::string srcLC = source;\n        std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( 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: '\" + source + \"'\" );\n        return ParserResult::ok( ParseResultType::Matched );\n    }\n#ifdef CLARA_CONFIG_OPTIONAL_TYPE\n    template<typename T>\n    inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE<T>& target ) -> 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\n    struct 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\n    struct BoundRef : NonCopyable {\n        virtual ~BoundRef() = default;\n        virtual auto isContainer() const -> bool { return false; }\n        virtual auto isFlag() const -> bool { return false; }\n    };\n    struct BoundValueRefBase : BoundRef {\n        virtual auto setValue( std::string const &arg ) -> ParserResult = 0;\n    };\n    struct BoundFlagRefBase : BoundRef {\n        virtual auto setFlag( bool flag ) -> ParserResult = 0;\n        virtual auto isFlag() const -> bool { return true; }\n    };\n\n    template<typename T>\n    struct 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\n    template<typename T>\n    struct 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\n    struct 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\n    template<typename ReturnType>\n    struct LambdaInvoker {\n        static_assert( std::is_same<ReturnType, ParserResult>::value, \"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\n    template<>\n    struct 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\n    template<typename ArgType, typename L>\n    inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult {\n        ArgType temp{};\n        auto result = convertInto( arg, temp );\n        return !result\n           ? result\n           : LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( lambda, temp );\n    }\n\n    template<typename L>\n    struct 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\n    template<typename L>\n    struct 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, \"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\n    enum class Optionality { Optional, Required };\n\n    struct Parser;\n\n    class ParserBase {\n    public:\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 -> 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\n    template<typename DerivedT>\n    class ComposableParserImpl : public ParserBase {\n    public:\n        template<typename T>\n        auto operator|( T const &other ) const -> Parser;\n\n\t\ttemplate<typename T>\n        auto operator+( T const &other ) const -> Parser;\n    };\n\n    // Common code and state for Args and Opts\n    template<typename DerivedT>\n    class ParserRefImpl : public ComposableParserImpl<DerivedT> {\n    protected:\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\n    public:\n        template<typename T>\n        ParserRefImpl( T &ref, std::string const &hint )\n        :   m_ref( std::make_shared<BoundValueRef<T>>( ref ) ),\n            m_hint( hint )\n        {}\n\n        template<typename LambdaT>\n        ParserRefImpl( LambdaT const &ref, std::string const &hint )\n        :   m_ref( std::make_shared<BoundLambda<LambdaT>>( ref ) ),\n            m_hint(hint)\n        {}\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 {\n            return m_optionality == Optionality::Optional;\n        }\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\n    class 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\n    public:\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 specially\n        auto parse( std::string const&, TokenStream const &tokens ) const -> 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\n            auto lastSlash = newName.find_last_of( \"\\\\/\" );\n            auto filename = ( lastSlash == std::string::npos )\n                    ? newName\n                    : 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\n    class Arg : public ParserRefImpl<Arg> {\n    public:\n        using ParserRefImpl::ParserRefImpl;\n\n        auto parse( std::string const &, TokenStream const &tokens ) const -> 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\n    inline 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\n    class Opt : public ParserRefImpl<Opt> {\n    protected:\n        std::vector<std::string> m_optNames;\n\n    public:\n        template<typename LambdaT>\n        explicit Opt( LambdaT const &ref ) : 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 -> 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 \" + token.token );\n                        auto const &argToken = *remainingTokens;\n                        if( argToken.type != TokenType::Argument )\n                            return InternalParseResult::runtimeError( \"Expected argument following \" + 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( 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\n    struct Help : Opt {\n        Help( bool &showHelpFlag )\n        :   Opt([&]( bool flag ) {\n                showHelpFlag = flag;\n                return ParserResult::ok( ParseResultType::ShortCircuitAll );\n            })\n        {\n            static_cast<Opt &>( *this )\n                    (\"display usage information\")\n                    [\"-?\"][\"-h\"][\"--help\"]\n                    .optional();\n        }\n    };\n\n    struct Parser : ParserBase {\n\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 & { return operator|=( other ); }\n        template<typename T>\n        auto operator+( T const &other ) const -> Parser { return operator|( other ); }\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\" << \"  \" << 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 =\n                        TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) +\n                        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 -> InternalParseResult override {\n\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 to do\n            ParserInfo parseInfos[512];\n\n            {\n                size_t i = 0;\n                for (auto const &opt : m_options) parseInfos[i++].parser = &opt;\n                for (auto const &arg : m_args) 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 || 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: \" + result.value().remainingTokens()->token );\n            }\n            // !TBD Check missing required options\n            return result;\n        }\n    };\n\n    template<typename DerivedT>\n    template<typename T>\n    auto 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 Catch::clara\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\n    clara::Parser makeCommandLineParser( ConfigData& config );\n\n} // end namespace Catch\n\n// end catch_commandline.h\n#include <fstream>\n#include <ctime>\n\nnamespace Catch {\n\n    clara::Parser makeCommandLineParser( ConfigData& config ) {\n\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. '\" + 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( \"keypress argument must be one of: never, start, exit or both. '\" + keypress + \"' 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 = 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 + \"'. Check available with --list-reporters\" );\n            return ParserResult::ok( ParseResultType::Matched );\n        };\n\n        auto cli\n            = ExeName( config.processName )\n            | Help( config.showHelp )\n            | Opt( config.listTests )\n                [\"-l\"][\"--list-tests\"]\n                ( \"list all/matching test cases\" )\n            | Opt( config.listTags )\n                [\"-t\"][\"--list-tags\"]\n                ( \"list all/matching tags\" )\n            | Opt( config.showSuccessfulTests )\n                [\"-s\"][\"--success\"]\n                ( \"include successful tests in output\" )\n            | Opt( config.shouldDebugBreak )\n                [\"-b\"][\"--break\"]\n                ( \"break into debugger on failure\" )\n            | Opt( config.noThrow )\n                [\"-e\"][\"--nothrow\"]\n                ( \"skip exception tests\" )\n            | Opt( config.showInvisibles )\n                [\"-i\"][\"--invisibles\"]\n                ( \"show invisibles (tabs, newlines)\" )\n            | Opt( config.outputFilename, \"filename\" )\n                [\"-o\"][\"--out\"]\n                ( \"output filename\" )\n            | Opt( setReporter, \"name\" )\n                [\"-r\"][\"--reporter\"]\n                ( \"reporter to use (defaults to console)\" )\n            | Opt( config.name, \"name\" )\n                [\"-n\"][\"--name\"]\n                ( \"suite name\" )\n            | Opt( [&]( bool ){ config.abortAfter = 1; } )\n                [\"-a\"][\"--abort\"]\n                ( \"abort at first failure\" )\n            | Opt( [&]( int x ){ config.abortAfter = x; }, \"no. failures\" )\n                [\"-x\"][\"--abortx\"]\n                ( \"abort after x failures\" )\n            | Opt( setWarning, \"warning name\" )\n                [\"-w\"][\"--warn\"]\n                ( \"enable warnings\" )\n            | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, \"yes|no\" )\n                [\"-d\"][\"--durations\"]\n                ( \"show test durations\" )\n            | Opt( config.minDuration, \"seconds\" )\n                [\"-D\"][\"--min-duration\"]\n                ( \"show test durations for tests taking at least the given number of seconds\" )\n            | Opt( loadTestNamesFromFile, \"filename\" )\n                [\"-f\"][\"--input-file\"]\n                ( \"load test names to run from a file\" )\n            | Opt( config.filenamesAsTags )\n                [\"-#\"][\"--filenames-as-tags\"]\n                ( \"adds a tag for the filename\" )\n            | Opt( config.sectionsToRun, \"section name\" )\n                [\"-c\"][\"--section\"]\n                ( \"specify section to run\" )\n            | Opt( setVerbosity, \"quiet|normal|high\" )\n                [\"-v\"][\"--verbosity\"]\n                ( \"set output verbosity\" )\n            | Opt( config.listTestNamesOnly )\n                [\"--list-test-names-only\"]\n                ( \"list all/matching test cases names only\" )\n            | Opt( config.listReporters )\n                [\"--list-reporters\"]\n                ( \"list all reporters\" )\n            | Opt( setTestOrder, \"decl|lex|rand\" )\n                [\"--order\"]\n                ( \"test case order (defaults to decl)\" )\n            | Opt( setRngSeed, \"'time'|number\" )\n                [\"--rng-seed\"]\n                ( \"set a specific seed for random numbers\" )\n            | Opt( setColourUsage, \"yes|no\" )\n                [\"--use-colour\"]\n                ( \"should output be colourised\" )\n            | Opt( config.libIdentify )\n                [\"--libidentify\"]\n                ( \"report name and version according to libidentify standard\" )\n            | Opt( setWaitForKeypress, \"never|start|exit|both\" )\n                [\"--wait-for-keypress\"]\n                ( \"waits for a keypress before exiting\" )\n            | Opt( config.benchmarkSamples, \"samples\" )\n                [\"--benchmark-samples\"]\n                ( \"number of samples to collect (default: 100)\" )\n            | Opt( config.benchmarkResamples, \"resamples\" )\n                [\"--benchmark-resamples\"]\n                ( \"number of resamples for the bootstrap (default: 100000)\" )\n            | Opt( config.benchmarkConfidenceInterval, \"confidence interval\" )\n                [\"--benchmark-confidence-interval\"]\n                ( \"confidence interval for the bootstrap (between 0 and 1, default: 0.95)\" )\n            | Opt( config.benchmarkNoAnalysis )\n                [\"--benchmark-no-analysis\"]\n                ( \"perform only measurements; do not perform any analysis\" )\n            | Opt( config.benchmarkWarmupTime, \"benchmarkWarmupTime\" )\n                [\"--benchmark-warmup-time\"]\n                ( \"amount of time in milliseconds spent on warming up each test (default: 100)\" )\n            | Arg( config.testsOrTags, \"test name|pattern|tags\" )\n                ( \"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\n    bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept {\n        return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0);\n    }\n    bool 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 || ( line == other.line && file != other.file && (std::strcmp(file, other.file) < 0));\n    }\n\n    std::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\n    std::string StreamEndStop::operator+() const {\n        return std::string();\n    }\n\n    NonCopyable::NonCopyable() = default;\n    NonCopyable::~NonCopyable() = default;\n\n}\n// end catch_common.cpp\n// start catch_config.cpp\n\nnamespace Catch {\n\n    Config::Config( ConfigData const& data )\n    :   m_data( data ),\n        m_stream( openStream() )\n    {\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\n    std::string const& Config::getFilename() const {\n        return m_data.outputFilename ;\n    }\n\n    bool Config::listTests() const          { return m_data.listTests; }\n    bool Config::listTestNamesOnly() const  { return m_data.listTestNamesOnly; }\n    bool Config::listTags() const           { return m_data.listTags; }\n    bool Config::listReporters() const      { return m_data.listReporters; }\n\n    std::string Config::getProcessName() const { return m_data.processName; }\n    std::string const& Config::getReporterName() const { return m_data.reporterName; }\n\n    std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; }\n    std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; }\n\n    TestSpec const& Config::testSpec() const { return m_testSpec; }\n    bool Config::hasTestFilters() const { return m_hasTestFilters; }\n\n    bool Config::showHelp() const { return m_data.showHelp; }\n\n    // IConfig interface\n    bool Config::allowThrows() const                   { return !m_data.noThrow; }\n    std::ostream& Config::stream() const               { return m_stream->stream(); }\n    std::string Config::name() const                   { return m_data.name.empty() ? m_data.processName : m_data.name; }\n    bool Config::includeSuccessfulResults() const      { return m_data.showSuccessfulTests; }\n    bool Config::warnAboutMissingAssertions() const    { return !!(m_data.warnings & WarnAbout::NoAssertions); }\n    bool Config::warnAboutNoTests() const              { return !!(m_data.warnings & WarnAbout::NoTests); }\n    ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; }\n    double Config::minDuration() const                 { return m_data.minDuration; }\n    RunTests::InWhatOrder Config::runOrder() const     { return m_data.runOrder; }\n    unsigned int Config::rngSeed() const               { return m_data.rngSeed; }\n    UseColour::YesOrNo Config::useColour() const       { return m_data.useColour; }\n    bool Config::shouldDebugBreak() const              { return m_data.shouldDebugBreak; }\n    int Config::abortAfter() const                     { return m_data.abortAfter; }\n    bool Config::showInvisibles() const                { return m_data.showInvisibles; }\n    Verbosity Config::verbosity() const                { return m_data.verbosity; }\n\n    bool Config::benchmarkNoAnalysis() const                      { return m_data.benchmarkNoAnalysis; }\n    int Config::benchmarkSamples() const                          { return m_data.benchmarkSamples; }\n    double Config::benchmarkConfidenceInterval() const            { return m_data.benchmarkConfidenceInterval; }\n    unsigned int Config::benchmarkResamples() const               { return m_data.benchmarkResamples; }\n    std::chrono::milliseconds Config::benchmarkWarmupTime() const { return std::chrono::milliseconds(m_data.benchmarkWarmupTime); }\n\n    IStream const* Config::openStream() {\n        return Catch::makeStream(m_data.outputFilename);\n    }\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\n    class ErrnoGuard {\n    public:\n        ErrnoGuard();\n        ~ErrnoGuard();\n    private:\n        int m_oldErrno;\n    };\n\n}\n\n// end catch_errno_guard.h\n// start catch_windows_h_proxy.h\n\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 {\n    namespace {\n\n        struct IColourImpl {\n            virtual ~IColourImpl() = default;\n            virtual void use( Colour::Code _colourCode ) = 0;\n        };\n\n        struct 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    } // anon namespace\n} // namespace Catch\n\n#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !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\n    class Win32ColourImpl : public IColourImpl {\n    public:\n        Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) )\n        {\n            CONSOLE_SCREEN_BUFFER_INFO csbiInfo;\n            GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo );\n            originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY );\n            originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY );\n        }\n\n        void use( Colour::Code _colourCode ) override {\n            switch( _colourCode ) {\n                case Colour::None:      return setTextAttribute( originalForegroundAttributes );\n                case Colour::White:     return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );\n                case Colour::Red:       return setTextAttribute( FOREGROUND_RED );\n                case Colour::Green:     return setTextAttribute( FOREGROUND_GREEN );\n                case Colour::Blue:      return setTextAttribute( FOREGROUND_BLUE );\n                case Colour::Cyan:      return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN );\n                case Colour::Yellow:    return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN );\n                case Colour::Grey:      return setTextAttribute( 0 );\n\n                case Colour::LightGrey:     return setTextAttribute( FOREGROUND_INTENSITY );\n                case Colour::BrightRed:     return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED );\n                case Colour::BrightGreen:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN );\n                case Colour::BrightWhite:   return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE );\n                case Colour::BrightYellow:  return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN );\n\n                case Colour::Bright: CATCH_INTERNAL_ERROR( \"not a colour\" );\n\n                default:\n                    CATCH_ERROR( \"Unknown colour requested\" );\n            }\n        }\n\n    private:\n        void setTextAttribute( WORD _textAttribute ) {\n            SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes );\n        }\n        HANDLE stdoutHandle;\n        WORD originalForegroundAttributes;\n        WORD originalBackgroundAttributes;\n    };\n\n    IColourImpl* platformColourInstance() {\n        static Win32ColourImpl s_instance;\n\n        IConfigPtr config = getCurrentContext().getConfig();\n        UseColour::YesOrNo colourMode = config\n            ? config->useColour()\n            : UseColour::Auto;\n        if( colourMode == UseColour::Auto )\n            colourMode = UseColour::Yes;\n        return colourMode == UseColour::Yes\n            ? &s_instance\n            : NoColourImpl::instance();\n    }\n\n} // end anon 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\n    class PosixColourImpl : public IColourImpl {\n    public:\n        void use( Colour::Code _colourCode ) override {\n            switch( _colourCode ) {\n                case Colour::None:\n                case Colour::White:     return setColour( \"[0m\" );\n                case Colour::Red:       return setColour( \"[0;31m\" );\n                case Colour::Green:     return setColour( \"[0;32m\" );\n                case Colour::Blue:      return setColour( \"[0;34m\" );\n                case Colour::Cyan:      return setColour( \"[0;36m\" );\n                case Colour::Yellow:    return setColour( \"[0;33m\" );\n                case Colour::Grey:      return setColour( \"[1;30m\" );\n\n                case Colour::LightGrey:     return setColour( \"[0;37m\" );\n                case Colour::BrightRed:     return setColour( \"[1;31m\" );\n                case Colour::BrightGreen:   return setColour( \"[1;32m\" );\n                case Colour::BrightWhite:   return setColour( \"[1;37m\" );\n                case Colour::BrightYellow:  return setColour( \"[1;33m\" );\n\n                case Colour::Bright: CATCH_INTERNAL_ERROR( \"not a colour\" );\n                default: CATCH_INTERNAL_ERROR( \"Unknown colour requested\" );\n            }\n        }\n        static IColourImpl* instance() {\n            static PosixColourImpl s_instance;\n            return &s_instance;\n        }\n\n    private:\n        void setColour( const char* _escapeCode ) {\n            getCurrentContext().getConfig()->stream()\n                << '\\033' << _escapeCode;\n        }\n    };\n\n    bool 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    }\n    IColourImpl* platformColourInstance() {\n        ErrnoGuard guard;\n        IConfigPtr config = getCurrentContext().getConfig();\n        UseColour::YesOrNo colourMode = config\n            ? config->useColour()\n            : UseColour::Auto;\n        if( colourMode == UseColour::Auto )\n            colourMode = useColourOnPlatform()\n                ? UseColour::Yes\n                : UseColour::No;\n        return colourMode == UseColour::Yes\n            ? PosixColourImpl::instance()\n            : NoColourImpl::instance();\n    }\n\n} // end anon namespace\n} // end namespace Catch\n\n#else  // not Windows or ANSI ///////////////////////////////////////////////\n\nnamespace Catch {\n\n    static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); }\n\n} // end namespace Catch\n\n#endif // Windows/ ANSI/ None\n\nnamespace Catch {\n\n    Colour::Colour( Code _colourCode ) { use( _colourCode ); }\n    Colour::Colour( Colour&& other ) noexcept {\n        m_moved = other.m_moved;\n        other.m_moved = true;\n    }\n    Colour& Colour::operator=( Colour&& other ) noexcept {\n        m_moved = other.m_moved;\n        other.m_moved  = true;\n        return *this;\n    }\n\n    Colour::~Colour(){ if( !m_moved ) use( None ); }\n\n    void 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\n    std::ostream& operator << ( std::ostream& os, Colour const& ) {\n        return os;\n    }\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\n    class Context : public IMutableContext, NonCopyable {\n\n    public: // IContext\n        IResultCapture* getResultCapture() override {\n            return m_resultCapture;\n        }\n        IRunner* getRunner() override {\n            return m_runner;\n        }\n\n        IConfigPtr const& getConfig() const override {\n            return m_config;\n        }\n\n        ~Context() override;\n\n    public: // IMutableContext\n        void setResultCapture( IResultCapture* resultCapture ) override {\n            m_resultCapture = resultCapture;\n        }\n        void setRunner( IRunner* runner ) override {\n            m_runner = runner;\n        }\n        void setConfig( IConfigPtr const& config ) override {\n            m_config = config;\n        }\n\n        friend IMutableContext& getCurrentMutableContext();\n\n    private:\n        IConfigPtr m_config;\n        IRunner* m_runner = nullptr;\n        IResultCapture* m_resultCapture = nullptr;\n    };\n\n    IMutableContext *IMutableContext::currentContext = nullptr;\n\n    void IMutableContext::createContext()\n    {\n        currentContext = new Context();\n    }\n\n    void cleanUpContext() {\n        delete IMutableContext::currentContext;\n        IMutableContext::currentContext = nullptr;\n    }\n    IContext::~IContext() = default;\n    IMutableContext::~IMutableContext() = default;\n    Context::~Context() = default;\n\n    SimplePcg32& rng() {\n        static SimplePcg32 s_rng;\n        return s_rng;\n    }\n\n}\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 {\n    void 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\n    namespace Catch {\n        void writeToDebugConsole( std::string const& text ) {\n            __android_log_write( ANDROID_LOG_DEBUG, \"Catch\", text.c_str() );\n        }\n    }\n\n#elif defined(CATCH_PLATFORM_WINDOWS)\n\n    namespace Catch {\n        void writeToDebugConsole( std::string const& text ) {\n            ::OutputDebugStringA( text.c_str() );\n        }\n    }\n\n#else\n\n    namespace Catch {\n        void writeToDebugConsole( std::string const& text ) {\n            // !TBD: Need a version for Mac/ XCode and other IDEs\n            Catch::cout() << text;\n        }\n    }\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 <cassert>\n#  include <sys/types.h>\n#  include <unistd.h>\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\n    namespace 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).\n        bool 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 debugger is active **\\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\n        bool isDebuggerActive() {\n            // We need to find another way to determine this for non-appleclang compilers 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\n    namespace 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.\n        bool 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)\n    extern \"C\" __declspec(dllimport) int __stdcall IsDebuggerPresent();\n    namespace Catch {\n        bool isDebuggerActive() {\n            return IsDebuggerPresent() != 0;\n        }\n    }\n#elif defined(__MINGW32__)\n    extern \"C\" __declspec(dllimport) int __stdcall IsDebuggerPresent();\n    namespace Catch {\n        bool isDebuggerActive() {\n            return IsDebuggerPresent() != 0;\n        }\n    }\n#else\n    namespace Catch {\n       bool isDebuggerActive() { return false; }\n    }\n#endif // Platform\n// end catch_debugger.cpp\n// start catch_decomposer.cpp\n\nnamespace Catch {\n\n    ITransientExpression::~ITransientExpression() = default;\n\n    void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) {\n        if( lhs.size() + rhs.size() < 40 &&\n                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}\n// end catch_decomposer.cpp\n// start catch_enforce.cpp\n\n#include <stdexcept>\n\nnamespace Catch {\n#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER)\n    [[noreturn]]\n    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]]\n    void throw_logic_error(std::string const& msg) {\n        throw_exception(std::logic_error(msg));\n    }\n\n    [[noreturn]]\n    void throw_domain_error(std::string const& msg) {\n        throw_exception(std::domain_error(msg));\n    }\n\n    [[noreturn]]\n    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 <vector>\n#include <memory>\n\nnamespace Catch {\n\n    namespace Detail {\n\n        std::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values );\n\n        class EnumValuesRegistry : public IMutableEnumValuesRegistry {\n\n            std::vector<std::unique_ptr<EnumInfo>> m_enumInfos;\n\n            EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector<int> const& values) override;\n        };\n\n        std::vector<StringRef> parseEnums( StringRef enums );\n\n    } // Detail\n\n} // Catch\n\n// end catch_enum_values_registry.h\n\n#include <map>\n#include <cassert>\n\nnamespace Catch {\n\n    IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() {}\n\n    namespace Detail {\n\n        namespace {\n            // Extracts the actual name part of an enum instance\n            // In other words, it returns the Blue part of Bikeshed::Colour::Blue\n            StringRef 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        }\n\n        std::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\n        EnumInfo::~EnumInfo() {}\n\n        StringRef 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\n        std::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, 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\n        EnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) {\n            m_enumInfos.push_back(makeEnumInfo(enumName, allValueNames, values));\n            return *m_enumInfos.back();\n        }\n\n    } // Detail\n} // Catch\n\n// end catch_enum_values_registry.cpp\n// start catch_errno_guard.cpp\n\n#include <cerrno>\n\nnamespace Catch {\n        ErrnoGuard::ErrnoGuard():m_oldErrno(errno){}\n        ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; }\n}\n// end catch_errno_guard.cpp\n// start catch_exception_translator_registry.cpp\n\n// start catch_exception_translator_registry.h\n\n#include <vector>\n#include <string>\n#include <memory>\n\nnamespace Catch {\n\n    class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry {\n    public:\n        ~ExceptionTranslatorRegistry();\n        virtual void registerTranslator( const IExceptionTranslator* translator );\n        std::string translateActiveException() const override;\n        std::string tryTranslators() const;\n\n    private:\n        std::vector<std::unique_ptr<IExceptionTranslator const>> m_translators;\n    };\n}\n\n// end catch_exception_translator_registry.h\n#ifdef __OBJC__\n#import \"Foundation/Foundation.h\"\n#endif\n\nnamespace Catch {\n\n    ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() {\n    }\n\n    void 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)\n    std::string ExceptionTranslatorRegistry::translateActiveException() const {\n        try {\n#ifdef __OBJC__\n            // In Objective-C try objective-c exceptions first\n            @try {\n                return tryTranslators();\n            }\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        }\n        catch( TestFailureException& ) {\n            std::rethrow_exception(std::current_exception());\n        }\n        catch( std::exception& ex ) {\n            return ex.what();\n        }\n        catch( std::string& msg ) {\n            return msg;\n        }\n        catch( const char* msg ) {\n            return msg;\n        }\n        catch(...) {\n            return \"Unknown exception\";\n        }\n    }\n\n    std::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\n    std::string ExceptionTranslatorRegistry::translateActiveException() const {\n        CATCH_INTERNAL_ERROR(\"Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!\");\n    }\n\n    std::string ExceptionTranslatorRegistry::tryTranslators() const {\n        CATCH_INTERNAL_ERROR(\"Attempted to use exception translators under CATCH_CONFIG_DISABLE_EXCEPTIONS!\");\n    }\n#endif\n\n}\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.\n    void FatalConditionHandler::engage_platform() {}\n    void FatalConditionHandler::disengage_platform() {}\n    FatalConditionHandler::FatalConditionHandler() = default;\n    FatalConditionHandler::~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\n    void 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.\n    constexpr 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\n    struct SignalDefs { DWORD id; const char* name; };\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.\n    static 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\n    static 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\n    static PVOID exceptionHandlerHandle = nullptr;\n\n    // For MSVC, we reserve part of the stack memory for handling\n    // memory overflow structured exception.\n    FatalConditionHandler::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()\n                << \"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.\n    FatalConditionHandler::~FatalConditionHandler() = default;\n\n    void 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\n    void 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\n    struct SignalDefs {\n        int id;\n        const char* name;\n    };\n\n    static SignalDefs signalDefs[] = {\n        { 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\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\n    static char* altStackMem = nullptr;\n    static std::size_t altStackSize = 0;\n    static stack_t oldSigStack{};\n    static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{};\n\n    static 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\n    static 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\n    FatalConditionHandler::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\n    FatalConditionHandler::~FatalConditionHandler() {\n        delete[] altStackMem;\n        // We signal that another instance can be constructed by zeroing\n        // out the pointer.\n        altStackMem = nullptr;\n    }\n\n    void 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\n    void FatalConditionHandler::disengage_platform() {\n        restorePreviousSignalHandlers();\n    }\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 {\n    return m_msg;\n}\n\nnamespace Generators {\n\n    GeneratorUntypedBase::~GeneratorUntypedBase() {}\n\n    auto acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> 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 {\n    IResultCapture::~IResultCapture() = default;\n}\n// end catch_interfaces_capture.cpp\n// start catch_interfaces_config.cpp\n\nnamespace Catch {\n    IConfig::~IConfig() = default;\n}\n// end catch_interfaces_config.cpp\n// start catch_interfaces_exception.cpp\n\nnamespace Catch {\n    IExceptionTranslator::~IExceptionTranslator() = default;\n    IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default;\n}\n// end catch_interfaces_exception.cpp\n// start catch_interfaces_registry_hub.cpp\n\nnamespace Catch {\n    IRegistryHub::~IRegistryHub() = default;\n    IMutableRegistryHub::~IMutableRegistryHub() = default;\n}\n// end catch_interfaces_registry_hub.cpp\n// start catch_interfaces_reporter.cpp\n\n// start catch_reporter_listening.h\n\nnamespace Catch {\n\n    class ListeningReporter : public IStreamingReporter {\n        using Reporters = std::vector<IStreamingReporterPtr>;\n        Reporters m_listeners;\n        IStreamingReporterPtr m_reporter = nullptr;\n        ReporterPreferences m_preferences;\n\n    public:\n        ListeningReporter();\n\n        void addListener( IStreamingReporterPtr&& listener );\n        void addReporter( IStreamingReporterPtr&& reporter );\n\n    public: // IStreamingReporter\n\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\n} // end namespace Catch\n\n// end catch_reporter_listening.h\nnamespace Catch {\n\n    ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig )\n    :   m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {}\n\n    ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream )\n    :   m_stream( &_stream ), m_fullConfig( _fullConfig ) {}\n\n    std::ostream& ReporterConfig::stream() const { return *m_stream; }\n    IConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; }\n\n    TestRunInfo::TestRunInfo( std::string const& _name ) : name( _name ) {}\n\n    GroupInfo::GroupInfo(  std::string const& _name,\n                           std::size_t _groupIndex,\n                           std::size_t _groupsCount )\n    :   name( _name ),\n        groupIndex( _groupIndex ),\n        groupsCounts( _groupsCount )\n    {}\n\n     AssertionStats::AssertionStats( AssertionResult const& _assertionResult,\n                                     std::vector<MessageInfo> const& _infoMessages,\n                                     Totals const& _totals )\n    :   assertionResult( _assertionResult ),\n        infoMessages( _infoMessages ),\n        totals( _totals )\n    {\n        assertionResult.m_resultData.lazyExpression.m_transientExpression = _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(), 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\n     AssertionStats::~AssertionStats() = default;\n\n    SectionStats::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    {}\n\n    SectionStats::~SectionStats() = default;\n\n    TestCaseStats::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    {}\n\n    TestCaseStats::~TestCaseStats() = default;\n\n    TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo,\n                                    Totals const& _totals,\n                                    bool _aborting )\n    :   groupInfo( _groupInfo ),\n        totals( _totals ),\n        aborting( _aborting )\n    {}\n\n    TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo )\n    :   groupInfo( _groupInfo ),\n        aborting( false )\n    {}\n\n    TestGroupStats::~TestGroupStats() = default;\n\n    TestRunStats::TestRunStats(   TestRunInfo const& _runInfo,\n                    Totals const& _totals,\n                    bool _aborting )\n    :   runInfo( _runInfo ),\n        totals( _totals ),\n        aborting( _aborting )\n    {}\n\n    TestRunStats::~TestRunStats() = default;\n\n    void IStreamingReporter::fatalErrorEncountered( StringRef ) {}\n    bool IStreamingReporter::isMulti() const { return false; }\n\n    IReporterFactory::~IReporterFactory() = default;\n    IReporterRegistry::~IReporterRegistry() = default;\n\n} // end namespace Catch\n// end catch_interfaces_reporter.cpp\n// start catch_interfaces_runner.cpp\n\nnamespace Catch {\n    IRunner::~IRunner() = default;\n}\n// end catch_interfaces_runner.cpp\n// start catch_interfaces_testcase.cpp\n\nnamespace Catch {\n    ITestInvoker::~ITestInvoker() = default;\n    ITestCaseRegistry::~ITestCaseRegistry() = default;\n}\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\n    LeakDetector::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}\n\n#else\n\n    Catch::LeakDetector::LeakDetector() {}\n\n#endif\n\nCatch::LeakDetector::~LeakDetector() {\n    Catch::cleanUp();\n}\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\n    std::size_t listTests( Config const& config );\n\n    std::size_t listTestsNamesOnly( Config const& config );\n\n    struct 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\n    std::size_t listTags( Config const& config );\n\n    std::size_t listReporters();\n\n    Option<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 {\n    using namespace clara::TextFlow;\n}\n\n// end catch_text.h\n#include <limits>\n#include <algorithm>\n#include <iomanip>\n\nnamespace Catch {\n\n    std::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()\n                ? Colour::SecondaryText\n                : 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) << 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' << std::endl;\n        return matchedTestCases.size();\n    }\n\n    std::size_t listTestsNamesOnly( Config const& config ) {\n        TestSpec const& testSpec = config.testSpec();\n        std::size_t matchedTests = 0;\n        std::vector<TestCase> matchedTestCases = 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\n    void TagInfo::add( std::string const& spelling ) {\n        ++count;\n        spellings.insert( spelling );\n    }\n\n    std::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; out.reserve(size);\n        for (auto const& spelling : spellings) {\n            out += '[';\n            out += spelling;\n            out += ']';\n        }\n        return out;\n    }\n\n    std::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 = 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\n    std::size_t listReporters() {\n        Catch::cout() << \"Available reporters:\\n\";\n        IReporterRegistry::FactoryMap const& factories = 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()\n                    << Column( factoryKvp.first + \":\" )\n                            .indent(2)\n                            .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\n    Option<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 {\n    namespace Impl {\n\n        std::string MatcherUntypedBase::toString() const {\n            if( m_cachedToString.empty() )\n                m_cachedToString = describe();\n            return m_cachedToString;\n        }\n\n        MatcherUntypedBase::~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}\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 {\n    bool isnan(float f);\n    bool isnan(double d);\n}\n\n// end catch_polyfills.hpp\n// start catch_to_string.hpp\n\n#include <string>\n\nnamespace Catch {\n    template <typename T>\n    std::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 <cstdlib>\n#include <cstdint>\n#include <cstring>\n#include <sstream>\n#include <type_traits>\n#include <iomanip>\n#include <limits>\n\nnamespace Catch {\nnamespace {\n\n    int32_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\n    int64_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\n    template <typename FP>\n    bool 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\n    float nextafter(float x, float y) {\n        return ::nextafterf(x, y);\n    }\n\n    double nextafter(double x, double y) {\n        return ::nextafter(x, y);\n    }\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)\n        << num;\n}\n\n} // end anonymous namespace\n\nnamespace Matchers {\nnamespace Floating {\n\n    enum class FloatingPointKind : uint8_t {\n        Float,\n        Double\n    };\n\n    WithinAbsMatcher::WithinAbsMatcher(double target, double margin)\n        :m_target{ target }, m_margin{ margin } {\n        CATCH_ENFORCE(margin >= 0, \"Invalid margin: \" << margin << '.'\n            << \" 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\n    bool WithinAbsMatcher::match(double const& matchee) const {\n        return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee);\n    }\n\n    std::string WithinAbsMatcher::describe() const {\n        return \"is within \" + ::Catch::Detail::stringify(m_margin) + \" of \" + ::Catch::Detail::stringify(m_target);\n    }\n\n    WithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, FloatingPointKind baseType)\n        :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } {\n        CATCH_ENFORCE(m_type == FloatingPointKind::Double\n                   || 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\n    bool 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), 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\n    std::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\n    WithinRelMatcher::WithinRelMatcher(double target, double epsilon):\n        m_target(target),\n        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\n    bool 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,\n                                std::isinf(relMargin)? 0 : relMargin);\n    }\n\n    std::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\n    namespace StdString {\n\n        CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity )\n        :   m_caseSensitivity( caseSensitivity ),\n            m_str( adjustString( str ) )\n        {}\n        std::string CasedString::adjustString( std::string const& str ) const {\n            return m_caseSensitivity == CaseSensitive::No\n                   ? toLower( str )\n                   : str;\n        }\n        std::string CasedString::caseSensitivitySuffix() const {\n            return m_caseSensitivity == CaseSensitive::No\n                   ? \" (case insensitive)\"\n                   : std::string();\n        }\n\n        StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator )\n        : m_comparator( comparator ),\n          m_operation( operation ) {\n        }\n\n        std::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\n        EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( \"equals\", comparator ) {}\n\n        bool EqualsMatcher::match( std::string const& source ) const {\n            return m_comparator.adjustString( source ) == m_comparator.m_str;\n        }\n\n        ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( \"contains\", comparator ) {}\n\n        bool ContainsMatcher::match( std::string const& source ) const {\n            return contains( m_comparator.adjustString( source ), m_comparator.m_str );\n        }\n\n        StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( \"starts with\", comparator ) {}\n\n        bool StartsWithMatcher::match( std::string const& source ) const {\n            return startsWith( m_comparator.adjustString( source ), m_comparator.m_str );\n        }\n\n        EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( \"ends with\", comparator ) {}\n\n        bool EndsWithMatcher::match( std::string const& source ) const {\n            return endsWith( m_comparator.adjustString( source ), m_comparator.m_str );\n        }\n\n        RegexMatcher::RegexMatcher(std::string regex, CaseSensitive::Choice caseSensitivity): m_regex(std::move(regex)), m_caseSensitivity(caseSensitivity) {}\n\n        bool 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\n        std::string RegexMatcher::describe() const {\n            return \"matches \" + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Choice::Yes)? \" case sensitively\" : \" case insensitively\");\n        }\n\n    } // namespace StdString\n\n    StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) {\n        return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) );\n    }\n    StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) {\n        return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) );\n    }\n    StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) {\n        return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) );\n    }\n    StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) {\n        return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) );\n    }\n\n    StdString::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 {\n    bool uncaught_exceptions();\n} // end namespace Catch\n\n// end catch_uncaught_exceptions.h\n#include <cassert>\n#include <stack>\n\nnamespace Catch {\n\n    MessageInfo::MessageInfo(   StringRef const& _macroName,\n                                SourceLineInfo const& _lineInfo,\n                                ResultWas::OfType _type )\n    :   macroName( _macroName ),\n        lineInfo( _lineInfo ),\n        type( _type ),\n        sequence( ++globalCount )\n    {}\n\n    bool MessageInfo::operator==( MessageInfo const& other ) const {\n        return sequence == other.sequence;\n    }\n\n    bool MessageInfo::operator<( MessageInfo const& other ) const {\n        return sequence < other.sequence;\n    }\n\n    // This may need protecting if threading support is added\n    unsigned int MessageInfo::globalCount = 0;\n\n    ////////////////////////////////////////////////////////////////////////////\n\n    Catch::MessageBuilder::MessageBuilder( StringRef const& macroName,\n                                           SourceLineInfo const& lineInfo,\n                                           ResultWas::OfType type )\n        :m_info(macroName, lineInfo, type) {}\n\n    ////////////////////////////////////////////////////////////////////////////\n\n    ScopedMessage::ScopedMessage( MessageBuilder const& builder )\n    : m_info( builder.m_info ), m_moved()\n    {\n        m_info.message = builder.m_stream.str();\n        getResultCapture().pushScopedMessage( m_info );\n    }\n\n    ScopedMessage::ScopedMessage( ScopedMessage&& old )\n    : m_info( old.m_info ), m_moved()\n    {\n        old.m_moved = true;\n    }\n\n    ScopedMessage::~ScopedMessage() {\n        if ( !uncaught_exceptions() && !m_moved ){\n            getResultCapture().popScopedMessage(m_info);\n        }\n    }\n\n    Capturer::Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, 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    }\n    Capturer::~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\n    void 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\n    class RedirectedStream {\n        std::ostream& m_originalStream;\n        std::ostream& m_redirectionStream;\n        std::streambuf* m_prevBuf;\n\n    public:\n        RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream );\n        ~RedirectedStream();\n    };\n\n    class RedirectedStdOut {\n        ReusableStringStream m_rss;\n        RedirectedStream m_cout;\n    public:\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\n    class RedirectedStdErr {\n        ReusableStringStream m_rss;\n        RedirectedStream m_cerr;\n        RedirectedStream m_clog;\n    public:\n        RedirectedStdErr();\n        auto str() const -> std::string;\n    };\n\n    class RedirectedStreams {\n    public:\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    private:\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.\n    class TempFile {\n    public:\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\n    private:\n        std::FILE* m_file = nullptr;\n    #if defined(_MSC_VER)\n        char m_buffer[L_tmpnam] = { 0 };\n    #endif\n    };\n\n    class OutputRedirect {\n    public:\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\n    private:\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\n    RedirectedStream::RedirectedStream( std::ostream& originalStream, std::ostream& redirectionStream )\n    :   m_originalStream( originalStream ),\n        m_redirectionStream( redirectionStream ),\n        m_prevBuf( m_originalStream.rdbuf() )\n    {\n        m_originalStream.rdbuf( m_redirectionStream.rdbuf() );\n    }\n\n    RedirectedStream::~RedirectedStream() {\n        m_originalStream.rdbuf( m_prevBuf );\n    }\n\n    RedirectedStdOut::RedirectedStdOut() : m_cout( Catch::cout(), m_rss.get() ) {}\n    auto RedirectedStdOut::str() const -> std::string { return m_rss.str(); }\n\n    RedirectedStdErr::RedirectedStdErr()\n    :   m_cerr( Catch::cerr(), m_rss.get() ),\n        m_clog( Catch::clog(), m_rss.get() )\n    {}\n    auto RedirectedStdErr::str() const -> std::string { return m_rss.str(); }\n\n    RedirectedStreams::RedirectedStreams(std::string& redirectedCout, std::string& redirectedCerr)\n    :   m_redirectedCout(redirectedCout),\n        m_redirectedCerr(redirectedCerr)\n    {}\n\n    RedirectedStreams::~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)\n    TempFile::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 << \"' because: \" << buffer);\n        }\n    }\n#else\n    TempFile::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\n    TempFile::~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\n    FILE* TempFile::getFile() {\n        return m_file;\n    }\n\n    std::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\n    OutputRedirect::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\n    OutputRedirect::~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)\n    bool isnan(float f) {\n        return std::isnan(f);\n    }\n    bool isnan(double d) {\n        return std::isnan(d);\n    }\n#else\n    // For now we only use this for embarcadero\n    bool isnan(float f) {\n        return std::_isnan(f);\n    }\n    bool isnan(double d) {\n        return std::_isnan(d);\n    }\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\n        uint32_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}\n\n    SimplePcg32::SimplePcg32(result_type seed_) {\n        seed(seed_);\n    }\n\n    void SimplePcg32::seed(result_type seed_) {\n        m_state = 0;\n        (*this)();\n        m_state += seed_;\n        (*this)();\n    }\n\n    void 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\n    SimplePcg32::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\n    bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs) {\n        return lhs.m_state == rhs.m_state;\n    }\n\n    bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs) {\n        return lhs.m_state != rhs.m_state;\n    }\n}\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 <vector>\n#include <set>\n#include <algorithm>\n#include <ios>\n\nnamespace Catch {\n\n    class TestCase;\n    struct IConfig;\n\n    std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases );\n\n    bool isThrowSafe( TestCase const& testCase, IConfig const& config );\n    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config );\n\n    void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions );\n\n    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config );\n    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config );\n\n    class TestRegistry : public ITestCaseRegistry {\n    public:\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\n    private:\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\n    class TestInvokerAsFunction : public ITestInvoker {\n        void(*m_testAsFunction)();\n    public:\n        TestInvokerAsFunction( void(*testAsFunction)() ) noexcept;\n\n        void invoke() const override;\n    };\n\n    std::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\n    class ReporterRegistry : public IReporterRegistry {\n\n    public:\n\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\n    private:\n        FactoryMap m_factories;\n        Listeners m_listeners;\n    };\n}\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\n    struct 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\n    class TagAliasRegistry : public ITagAliasRegistry {\n    public:\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\n    private:\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 <vector>\n#include <exception>\n\nnamespace Catch {\n\n    class StartupExceptionRegistry {\n#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)\n    public:\n        void add(std::exception_ptr const& exception) noexcept;\n        std::vector<std::exception_ptr> const& getExceptions() const noexcept;\n    private:\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\n    struct ISingleton {\n        virtual ~ISingleton();\n    };\n\n    void addSingleton( ISingleton* singleton );\n    void cleanupSingletons();\n\n    template<typename SingletonImplT, typename InterfaceT = SingletonImplT, typename MutableInterfaceT = InterfaceT>\n    class Singleton : SingletonImplT, public ISingleton {\n\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\n    public:\n        static auto get() -> InterfaceT const& {\n            return *getInternal();\n        }\n        static auto getMutable() -> MutableInterfaceT& {\n            return *getInternal();\n        }\n    };\n\n} // namespace Catch\n\n// end catch_singletons.hpp\nnamespace Catch {\n\n    namespace {\n\n        class RegistryHub : public IRegistryHub, public IMutableRegistryHub,\n                            private NonCopyable {\n\n        public: // IRegistryHub\n            RegistryHub() = default;\n            IReporterRegistry const& getReporterRegistry() const override {\n                return m_reporterRegistry;\n            }\n            ITestCaseRegistry const& getTestCaseRegistry() const override {\n                return m_testCaseRegistry;\n            }\n            IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const override {\n                return m_exceptionTranslatorRegistry;\n            }\n            ITagAliasRegistry const& getTagAliasRegistry() const override {\n                return m_tagAliasRegistry;\n            }\n            StartupExceptionRegistry const& getStartupExceptionRegistry() const override {\n                return m_exceptionRegistry;\n            }\n\n        public: // 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, std::string const& tag, 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(\"Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!\");\n#endif\n            }\n            IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override {\n                return m_enumValuesRegistry;\n            }\n\n        private:\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    }\n\n    using RegistryHubSingleton = Singleton<RegistryHub, IRegistryHub, IMutableRegistryHub>;\n\n    IRegistryHub const& getRegistryHub() {\n        return RegistryHubSingleton::get();\n    }\n    IMutableRegistryHub& getMutableRegistryHub() {\n        return RegistryHubSingleton::getMutable();\n    }\n    void cleanUp() {\n        cleanupSingletons();\n        cleanUpContext();\n    }\n    std::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\n    ReporterRegistry::~ReporterRegistry() = default;\n\n    IStreamingReporterPtr ReporterRegistry::create( std::string const& name, 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\n    void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) {\n        m_factories.emplace(name, factory);\n    }\n    void ReporterRegistry::registerListener( IReporterFactoryPtr const& factory ) {\n        m_listeners.push_back( factory );\n    }\n\n    IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const {\n        return m_factories;\n    }\n    IReporterRegistry::Listeners const& ReporterRegistry::getListeners() const {\n        return m_listeners;\n    }\n\n}\n// end catch_reporter_registry.cpp\n// start catch_result_type.cpp\n\nnamespace Catch {\n\n    bool isOk( ResultWas::OfType resultType ) {\n        return ( resultType & ResultWas::FailureBit ) == 0;\n    }\n    bool isJustInfo( int flags ) {\n        return flags == ResultWas::Info;\n    }\n\n    ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) {\n        return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) );\n    }\n\n    bool shouldContinueOnFailure( int flags )    { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; }\n    bool 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 <cassert>\n#include <algorithm>\n#include <sstream>\n\nnamespace Catch {\n\n    namespace Generators {\n        struct GeneratorTracker : TestCaseTracking::TrackerBase, IGeneratorTracker {\n            GeneratorBasePtr m_generator;\n\n            GeneratorTracker( TestCaseTracking::NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )\n            :   TrackerBase( nameAndLocation, ctx, parent )\n            {}\n            ~GeneratorTracker();\n\n            static GeneratorTracker& acquire( TrackerContext& ctx, 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 = 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 {\n                return !!m_generator;\n            }\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(\n                             m_children.begin(),\n                             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 &&\n                            \"Missing root (test case) level section\" );\n\n                    auto const& parentSection =\n                        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(),\n                                        filters.end(),\n                                        static_cast<SectionTracker&>( *child )\n                                            .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 ||\n                     ( m_runState == CompletedSuccessfully &&\n                       m_generator->next() ) ) {\n                    m_children.clear();\n                    m_runState = Executing;\n                }\n            }\n\n            // IGeneratorTracker interface\n            auto getGenerator() const -> GeneratorBasePtr const& override {\n                return m_generator;\n            }\n            void setGenerator( GeneratorBasePtr&& generator ) override {\n                m_generator = std::move( generator );\n            }\n        };\n        GeneratorTracker::~GeneratorTracker() {}\n    }\n\n    RunContext::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(), ResultDisposition::Normal },\n        m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions )\n    {\n        m_context.setRunner(this);\n        m_context.setConfig(m_config);\n        m_context.setResultCapture(this);\n        m_reporter->testRunStarting(m_runInfo);\n    }\n\n    RunContext::~RunContext() {\n        m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting()));\n    }\n\n    void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) {\n        m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount));\n    }\n\n    void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) {\n        m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting()));\n    }\n\n    Totals 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(m_trackerContext, 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(TestCaseStats(testInfo,\n                                  deltaTotals,\n                                  redirectedCout,\n                                  redirectedCerr,\n                                  aborting()));\n\n        m_activeTestCase = nullptr;\n        m_testCaseTracker = nullptr;\n\n        return deltaTotals;\n    }\n\n    IConfigPtr RunContext::config() const {\n        return m_config;\n    }\n\n    IStreamingReporter& RunContext::reporter() const {\n        return *m_reporter;\n    }\n\n    void 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        }\n        else {\n            m_lastAssertionPassed = true;\n        }\n\n        // We have no use for the return value (whether messages should be cleared), because messages were made scoped\n        // and should be let to clear themselves 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    }\n    void RunContext::resetAssertionInfo() {\n        m_lastAssertionInfo.macroName = StringRef();\n        m_lastAssertionInfo.capturedExpression = \"{Unknown expression after the reported line}\"_sr;\n    }\n\n    bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) {\n        ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, 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    }\n    auto RunContext::acquireGeneratorTracker( StringRef generatorName, SourceLineInfo const& lineInfo ) -> IGeneratorTracker& {\n        using namespace Generators;\n        GeneratorTracker& tracker = GeneratorTracker::acquire(m_trackerContext,\n                                                              TestCaseTracking::NameAndLocation( static_cast<std::string>(generatorName), lineInfo ) );\n        m_lastAssertionInfo.lineInfo = lineInfo;\n        return tracker;\n    }\n\n    bool 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\n    void 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, endInfo.durationInSeconds, missingAssertions));\n        m_messages.clear();\n        m_messageScopes.clear();\n    }\n\n    void 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)\n    void RunContext::benchmarkPreparing(std::string const& name) {\n        m_reporter->benchmarkPreparing(name);\n    }\n    void RunContext::benchmarkStarting( BenchmarkInfo const& info ) {\n        m_reporter->benchmarkStarting( info );\n    }\n    void RunContext::benchmarkEnded( BenchmarkStats<> const& stats ) {\n        m_reporter->benchmarkEnded( stats );\n    }\n    void RunContext::benchmarkFailed(std::string const & error) {\n        m_reporter->benchmarkFailed(error);\n    }\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    void RunContext::pushScopedMessage(MessageInfo const & message) {\n        m_messages.push_back(message);\n    }\n\n    void RunContext::popScopedMessage(MessageInfo const & message) {\n        m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end());\n    }\n\n    void RunContext::emplaceUnscopedMessage( MessageBuilder const& builder ) {\n        m_messageScopes.emplace_back( builder );\n    }\n\n    std::string RunContext::getCurrentTestName() const {\n        return m_activeTestCase\n            ? m_activeTestCase->getTestCaseInfo().name\n            : std::string();\n    }\n\n    const AssertionResult * RunContext::getLastResult() const {\n        return &(*m_lastResult);\n    }\n\n    void RunContext::exceptionEarlyReported() {\n        m_shouldReportUnexpected = false;\n    }\n\n    void 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 errors\n        // 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(TestCaseStats(testInfo,\n                                  deltaTotals,\n                                  std::string(),\n                                  std::string(),\n                                  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\n    bool RunContext::lastAssertionPassed() {\n         return m_lastAssertionPassed;\n    }\n\n    void RunContext::assertionPassed() {\n        m_lastAssertionPassed = true;\n        ++m_totals.assertions.passed;\n        resetAssertionInfo();\n        m_messageScopes.clear();\n    }\n\n    bool RunContext::aborting() const {\n        return m_totals.assertions.failed >= static_cast<std::size_t>(m_config->abortAfter());\n    }\n\n    void 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(), 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        } CATCH_CATCH_ANON (TestFailureException&) {\n            // This just means the test was aborted due to failure\n        } CATCH_CATCH_ALL {\n            // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions\n            // are reported without translation at the point of origin.\n            if( m_shouldReportUnexpected ) {\n                AssertionReaction dummyReaction;\n                handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), 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\n    void RunContext::invokeActiveTestCase() {\n        FatalConditionHandlerGuard _(&m_fatalConditionhandler);\n        m_activeTestCase->invoke();\n    }\n\n    void 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(),\n             itEnd = m_unfinishedSections.rend();\n             it != itEnd;\n             ++it)\n            sectionEnded(*it);\n        m_unfinishedSections.clear();\n    }\n\n    void RunContext::handleExpr(\n        AssertionInfo const& info,\n        ITransientExpression const& expr,\n        AssertionReaction& reaction\n    ) {\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            }\n            else {\n                reportExpr(info, ResultWas::Ok, &expr, negated);\n            }\n        }\n        else {\n            reportExpr(info, ResultWas::ExpressionFailed, &expr, negated );\n            populateReaction( reaction );\n        }\n    }\n    void RunContext::reportExpr(\n            AssertionInfo const &info,\n            ResultWas::OfType resultType,\n            ITransientExpression const *expr,\n            bool negated ) {\n\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\n    void RunContext::handleMessage(\n            AssertionInfo const& info,\n            ResultWas::OfType resultType,\n            StringRef const& message,\n            AssertionReaction& reaction\n    ) {\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    }\n    void RunContext::handleUnexpectedExceptionNotThrown(\n            AssertionInfo const& info,\n            AssertionReaction& reaction\n    ) {\n        handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction);\n    }\n\n    void RunContext::handleUnexpectedInflightException(\n            AssertionInfo const& info,\n            std::string const& message,\n            AssertionReaction& reaction\n    ) {\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\n    void RunContext::populateReaction( AssertionReaction& reaction ) {\n        reaction.shouldDebugBreak = m_config->shouldDebugBreak();\n        reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal);\n    }\n\n    void RunContext::handleIncomplete(\n            AssertionInfo const& info\n    ) {\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    }\n    void RunContext::handleNonExpr(\n            AssertionInfo const &info,\n            ResultWas::OfType resultType,\n            AssertionReaction &reaction\n    ) {\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\n    IResultCapture& getResultCapture() {\n        if (auto* capture = getCurrentContext().getResultCapture())\n            return *capture;\n        else\n            CATCH_INTERNAL_ERROR(\"No result capture instance\");\n    }\n\n    void seedRng(IConfig const& config) {\n        if (config.rngSeed() != 0) {\n            std::srand(config.rngSeed());\n            rng().seed(config.rngSeed());\n        }\n    }\n\n    unsigned int rngSeed() {\n        return getCurrentContext().getConfig()->rngSeed();\n    }\n\n}\n// end catch_run_context.cpp\n// start catch_section.cpp\n\nnamespace Catch {\n\n    Section::Section( SectionInfo const& info )\n    :   m_info( info ),\n        m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) )\n    {\n        m_timer.start();\n    }\n\n    Section::~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\n    Section::operator bool() const {\n        return m_sectionIncluded;\n    }\n\n} // end namespace Catch\n// end catch_section.cpp\n// start catch_section_info.cpp\n\nnamespace Catch {\n\n    SectionInfo::SectionInfo\n        (   SourceLineInfo const& _lineInfo,\n            std::string const& _name )\n    :   name( _name ),\n        lineInfo( _lineInfo )\n    {}\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\n    class Session : NonCopyable {\n    public:\n\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    private:\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\n    struct 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\n    Version const& libraryVersion();\n}\n\n// end catch_version.h\n#include <cstdlib>\n#include <iomanip>\n#include <set>\n#include <iterator>\n\nnamespace Catch {\n\n    namespace {\n        const int MaxExitCode = 255;\n\n        IStreamingReporterPtr 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\n        IStreamingReporterPtr 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\n        class TestGroup {\n        public:\n            explicit TestGroup(std::shared_ptr<Config> const& config)\n            : m_config{config}\n            , m_context{config, makeReporter(config)}\n            {\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\n        private:\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\n        void 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                else\n                {\n                    filename.insert(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    } // anon namespace\n\n    Session::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    }\n    Session::~Session() {\n        Catch::cleanUp();\n    }\n\n    void Session::showHelp() const {\n        Catch::cout()\n                << \"\\nCatch v\" << libraryVersion() << \"\\n\"\n                << m_cli << std::endl\n                << \"For more detailed usage please see the project docs\\n\" << std::endl;\n    }\n    void Session::libIdentify() {\n        Catch::cout()\n                << std::left << std::setw(16) << \"description: \" << \"A Catch2 test executable\\n\"\n                << std::left << std::setw(16) << \"category: \" << \"testframework\\n\"\n                << std::left << std::setw(16) << \"framework: \" << \"Catch Test\\n\"\n                << std::left << std::setw(16) << \"version: \" << libraryVersion() << std::endl;\n    }\n\n    int 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()\n                << Colour( Colour::Red )\n                << \"\\nError(s) in input:\\n\"\n                << Column( result.errorMessage() ).indent( 2 )\n                << \"\\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)\n    int Session::applyCommandLine( int argc, wchar_t const * const * argv ) {\n\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\n    void Session::useConfigData( ConfigData const& configData ) {\n        m_configData = configData;\n        m_config.reset();\n    }\n\n    int 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 << std::endl;\n            static_cast<void>(std::getchar());\n        }\n        return exitCode;\n    }\n\n    clara::Parser const& Session::cli() const {\n        return m_cli;\n    }\n    void Session::cli( clara::Parser const& newParser ) {\n        m_cli = newParser;\n    }\n    ConfigData& Session::configData() {\n        return m_configData;\n    }\n    Config& Session::config() {\n        if( !m_config )\n            m_config = std::make_shared<Config>( m_configData );\n        return *m_config;\n    }\n\n    int 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 (std::min) (MaxExitCode, 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, (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\n    namespace {\n        static 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    }\n\n    ISingleton::~ISingleton() {}\n\n    void addSingleton(ISingleton* singleton ) {\n        getSingletons()->push_back( singleton );\n    }\n    void 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 {\n            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 can do about it\n            std::terminate();\n        }\n    }\n\n    std::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 <iostream>\n#include <fstream>\n#include <sstream>\n#include <vector>\n#include <memory>\n\nnamespace Catch {\n\n    Catch::IStream::~IStream() = default;\n\n    namespace Detail { namespace {\n        template<typename WriterF, std::size_t bufferSize=256>\n        class StreamBufImpl : public std::streambuf {\n            char data[bufferSize];\n            WriterF m_writer;\n\n        public:\n            StreamBufImpl() {\n                setp( data, data + sizeof(data) );\n            }\n\n            ~StreamBufImpl() noexcept {\n                StreamBufImpl::sync();\n            }\n\n        private:\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\n        struct OutputDebugWriter {\n\n            void operator()( std::string const&str ) {\n                writeToDebugConsole( str );\n            }\n        };\n\n        ///////////////////////////////////////////////////////////////////////////\n\n        class FileStream : public IStream {\n            mutable std::ofstream m_ofs;\n        public:\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        public: // IStream\n            std::ostream& stream() const override {\n                return m_ofs;\n            }\n        };\n\n        ///////////////////////////////////////////////////////////////////////////\n\n        class CoutStream : public IStream {\n            mutable std::ostream m_os;\n        public:\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\n        public: // IStream\n            std::ostream& stream() const override { return m_os; }\n        };\n\n        ///////////////////////////////////////////////////////////////////////////\n\n        class DebugOutStream : public IStream {\n            std::unique_ptr<StreamBufImpl<OutputDebugWriter>> m_streamBuf;\n            mutable std::ostream m_os;\n        public:\n            DebugOutStream()\n            :   m_streamBuf( new StreamBufImpl<OutputDebugWriter>() ),\n                m_os( m_streamBuf.get() )\n            {}\n\n            ~DebugOutStream() override = default;\n\n        public: // IStream\n            std::ostream& stream() const override { return m_os; }\n        };\n\n    }} // namespace anon::detail\n\n    ///////////////////////////////////////////////////////////////////////////\n\n    auto 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        }\n        else\n            return new Detail::FileStream( filename );\n    }\n\n    // This class encapsulates the idea of a pool of ostringstreams that can be reused.\n    struct 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            }\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\n    ReusableStringStream::ReusableStringStream()\n    :   m_index( Singleton<StringStreams>::getMutable().add() ),\n        m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() )\n    {}\n\n    ReusableStringStream::~ReusableStringStream() {\n        static_cast<std::ostringstream*>( m_oss )->str(\"\");\n        m_oss->clear();\n        Singleton<StringStreams>::getMutable().release( m_index );\n    }\n\n    auto 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 functions\n    std::ostream& cout() { return std::cout; }\n    std::ostream& cerr() { return std::cerr; }\n    std::ostream& clog() { return std::clog; }\n#endif\n}\n// end catch_stream.cpp\n// start catch_string_manip.cpp\n\n#include <algorithm>\n#include <ostream>\n#include <cstring>\n#include <cctype>\n#include <vector>\n\nnamespace Catch {\n\n    namespace {\n        char toLowerCh(char c) {\n            return static_cast<char>( std::tolower( static_cast<unsigned char>(c) ) );\n        }\n    }\n\n    bool startsWith( std::string const& s, std::string const& prefix ) {\n        return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin());\n    }\n    bool startsWith( std::string const& s, char prefix ) {\n        return !s.empty() && s[0] == prefix;\n    }\n    bool endsWith( std::string const& s, std::string const& suffix ) {\n        return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin());\n    }\n    bool endsWith( std::string const& s, char suffix ) {\n        return !s.empty() && s[s.size()-1] == suffix;\n    }\n    bool contains( std::string const& s, std::string const& infix ) {\n        return s.find( infix ) != std::string::npos;\n    }\n    void toLowerInPlace( std::string& s ) {\n        std::transform( s.begin(), s.end(), s.begin(), toLowerCh );\n    }\n    std::string toLower( std::string const& s ) {\n        std::string lc = s;\n        toLowerInPlace( lc );\n        return lc;\n    }\n    std::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\n    StringRef trim(StringRef ref) {\n        const auto is_ws = [](char c) {\n            return c == ' ' || c == '\\t' || c == '\\n' || c == '\\r';\n        };\n        size_t real_begin = 0;\n        while (real_begin < ref.size() && is_ws(ref[real_begin])) { ++real_begin; }\n        size_t real_end = ref.size();\n        while (real_end > real_begin && is_ws(ref[real_end - 1])) { --real_end; }\n\n        return ref.substr(real_begin, real_end - real_begin);\n    }\n\n    bool 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\n    std::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\n    pluralise::pluralise( std::size_t count, std::string const& label )\n    :   m_count( count ),\n        m_label( label )\n    {}\n\n    std::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}\n// end catch_string_manip.cpp\n// start catch_stringref.cpp\n\n#include <algorithm>\n#include <ostream>\n#include <cstring>\n#include <cstdint>\n\nnamespace Catch {\n    StringRef::StringRef( char const* rawChars ) noexcept\n    : StringRef( rawChars, static_cast<StringRef::size_type>(std::strlen(rawChars) ) )\n    {}\n\n    auto StringRef::c_str() const -> char const* {\n        CATCH_ENFORCE(isNullTerminated(), \"Called StringRef::c_str() on a non-null-terminated instance\");\n        return m_start;\n    }\n    auto StringRef::data() const noexcept -> char const* {\n        return m_start;\n    }\n\n    auto 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    }\n    auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool {\n        return m_size == other.m_size\n            && (std::memcmp( m_start, other.m_start, m_size ) == 0);\n    }\n\n    auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& {\n        return os.write(str.data(), str.size());\n    }\n\n    auto 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 {\n    TagAlias::TagAlias(std::string const & _tag, SourceLineInfo _lineInfo): tag(_tag), lineInfo(_lineInfo) {}\n}\n// end catch_tag_alias.cpp\n// start catch_tag_alias_autoregistrar.cpp\n\nnamespace Catch {\n\n    RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) {\n        CATCH_TRY {\n            getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo);\n        } CATCH_CATCH_ALL {\n            // Do not throw when constructing global objects, instead register the exception to be processed later\n            getMutableRegistryHub().registerStartupException();\n        }\n    }\n\n}\n// end catch_tag_alias_autoregistrar.cpp\n// start catch_tag_alias_registry.cpp\n\n#include <sstream>\n\nnamespace Catch {\n\n    TagAliasRegistry::~TagAliasRegistry() {}\n\n    TagAlias 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\n    std::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 ) +\n                                    registryKvp.second.tag +\n                                    expandedTestSpec.substr( pos + registryKvp.first.size() );\n            }\n        }\n        return expandedTestSpec;\n    }\n\n    void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) {\n        CATCH_ENFORCE( startsWith(alias, \"[@\") && endsWith(alias, ']'),\n                      \"error: tag alias, '\" << alias << \"' is not of the form [@alias name].\\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\n    ITagAliasRegistry::~ITagAliasRegistry() {}\n\n    ITagAliasRegistry const& ITagAliasRegistry::get() {\n        return getRegistryHub().getTagAliasRegistry();\n    }\n\n} // end namespace Catch\n// end catch_tag_alias_registry.cpp\n// start catch_test_case_info.cpp\n\n#include <cctype>\n#include <exception>\n#include <algorithm>\n#include <sstream>\n\nnamespace Catch {\n\n    namespace {\n        TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) {\n            if( startsWith( tag, '.' ) ||\n                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 | TestCaseInfo::IsHidden );\n            else\n                return TestCaseInfo::None;\n        }\n        bool isReservedTag( std::string const& tag ) {\n            return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( static_cast<unsigned char>(tag[0]) );\n        }\n        void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) {\n            CATCH_ENFORCE( !isReservedTag(tag),\n                          \"Tag name: [\" << tag << \"] is not allowed.\\n\"\n                          << \"Tag names starting with non alphanumeric characters are reserved\\n\"\n                          << _lineInfo );\n        }\n    }\n\n    TestCase makeTestCase(  ITestInvoker* _testCase,\n                            std::string const& _className,\n                            NameAndTags const& nameAndTags,\n                            SourceLineInfo const& _lineInfo )\n    {\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            }\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                }\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, _lineInfo );\n        return TestCase( _testCase, std::move(info) );\n    }\n\n    void 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>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) );\n            testCaseInfo.lcaseTags.push_back( lcaseTag );\n        }\n        testCaseInfo.tags = std::move(tags);\n    }\n\n    TestCaseInfo::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    {\n        setTags( *this, _tags );\n    }\n\n    bool TestCaseInfo::isHidden() const {\n        return ( properties & IsHidden ) != 0;\n    }\n    bool TestCaseInfo::throws() const {\n        return ( properties & Throws ) != 0;\n    }\n    bool TestCaseInfo::okToFail() const {\n        return ( properties & (ShouldFail | MayFail ) ) != 0;\n    }\n    bool TestCaseInfo::expectedToFail() const {\n        return ( properties & (ShouldFail ) ) != 0;\n    }\n\n    std::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\n    TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo&& info ) : TestCaseInfo( std::move(info) ), test( testCase ) {}\n\n    TestCase TestCase::withName( std::string const& _newName ) const {\n        TestCase other( *this );\n        other.name = _newName;\n        return other;\n    }\n\n    void TestCase::invoke() const {\n        test->invoke();\n    }\n\n    bool TestCase::operator == ( TestCase const& other ) const {\n        return  test.get() == other.test.get() &&\n                name == other.name &&\n                className == other.className;\n    }\n\n    bool TestCase::operator < ( TestCase const& other ) const {\n        return name < other.name;\n    }\n\n    TestCaseInfo const& TestCase::getTestCaseInfo() const\n    {\n        return *this;\n    }\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\n    namespace {\n        struct TestHasher {\n            using hash_t = uint64_t;\n\n            explicit TestHasher( hash_t hashSuffix ):\n                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\n        private:\n            hash_t m_hashSuffix;\n        };\n    } // end unnamed namespace\n\n    std::vector<TestCase> sortTests( IConfig const& config, 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\n    bool isThrowSafe( TestCase const& testCase, IConfig const& config ) {\n        return !testCase.throws() || config.allowThrows();\n    }\n\n    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) {\n        return testSpec.matches( testCase ) && isThrowSafe( testCase, config );\n    }\n\n    void 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,\n                    \"error: TEST_CASE( \\\"\" << function.name << \"\\\" ) already defined.\\n\"\n                    << \"\\tFirst seen at \" << prev.first->getTestCaseInfo().lineInfo << \"\\n\"\n                    << \"\\tRedefined at \" << function.getTestCaseInfo().lineInfo );\n        }\n    }\n\n    std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, 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    }\n    std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) {\n        return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config );\n    }\n\n    void 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\n    std::vector<TestCase> const& TestRegistry::getAllTests() const {\n        return m_functions;\n    }\n    std::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    ///////////////////////////////////////////////////////////////////////////\n    TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {}\n\n    void TestInvokerAsFunction::invoke() const {\n        m_testAsFunction();\n    }\n\n    std::string extractClassName( StringRef const& classOrQualifiedMethodName ) {\n        std::string className(classOrQualifiedMethodName);\n        if( startsWith( className, '&' ) )\n        {\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 <stdexcept>\n#include <memory>\n#include <sstream>\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\n    NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location )\n    :   name( _name ),\n        location( _location )\n    {}\n\n    ITracker::~ITracker() = default;\n\n    ITracker& TrackerContext::startRun() {\n        m_rootTracker = std::make_shared<SectionTracker>( NameAndLocation( \"{root}\", CATCH_INTERNAL_LINEINFO ), *this, nullptr );\n        m_currentTracker = nullptr;\n        m_runState = Executing;\n        return *m_rootTracker;\n    }\n\n    void TrackerContext::endRun() {\n        m_rootTracker.reset();\n        m_currentTracker = nullptr;\n        m_runState = NotStarted;\n    }\n\n    void TrackerContext::startCycle() {\n        m_currentTracker = m_rootTracker.get();\n        m_runState = Executing;\n    }\n    void TrackerContext::completeCycle() {\n        m_runState = CompletedCycle;\n    }\n\n    bool TrackerContext::completedCycle() const {\n        return m_runState == CompletedCycle;\n    }\n    ITracker& TrackerContext::currentTracker() {\n        return *m_currentTracker;\n    }\n    void TrackerContext::setCurrentTracker( ITracker* tracker ) {\n        m_currentTracker = tracker;\n    }\n\n    TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ):\n        ITracker(nameAndLocation),\n        m_ctx( ctx ),\n        m_parent( parent )\n    {}\n\n    bool TrackerBase::isComplete() const {\n        return m_runState == CompletedSuccessfully || m_runState == Failed;\n    }\n    bool TrackerBase::isSuccessfullyCompleted() const {\n        return m_runState == CompletedSuccessfully;\n    }\n    bool TrackerBase::isOpen() const {\n        return m_runState != NotStarted && !isComplete();\n    }\n    bool TrackerBase::hasChildren() const {\n        return !m_children.empty();\n    }\n\n    void TrackerBase::addChild( ITrackerPtr const& child ) {\n        m_children.push_back( child );\n    }\n\n    ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) {\n        auto it = std::find_if( m_children.begin(), m_children.end(),\n            [&nameAndLocation]( ITrackerPtr const& tracker ){\n                return\n                    tracker->nameAndLocation().location == nameAndLocation.location &&\n                    tracker->nameAndLocation().name == nameAndLocation.name;\n            } );\n        return( it != m_children.end() )\n            ? *it\n            : nullptr;\n    }\n    ITracker& TrackerBase::parent() {\n        assert( m_parent ); // Should always be non-null except for root\n        return *m_parent;\n    }\n\n    void TrackerBase::openChild() {\n        if( m_runState != ExecutingChildren ) {\n            m_runState = ExecutingChildren;\n            if( m_parent )\n                m_parent->openChild();\n        }\n    }\n\n    bool TrackerBase::isSectionTracker() const { return false; }\n    bool TrackerBase::isGeneratorTracker() const { return false; }\n\n    void TrackerBase::open() {\n        m_runState = Executing;\n        moveToThis();\n        if( m_parent )\n            m_parent->openChild();\n    }\n\n    void TrackerBase::close() {\n\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(), [](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    }\n    void TrackerBase::fail() {\n        m_runState = Failed;\n        if( m_parent )\n            m_parent->markAsNeedingAnotherRun();\n        moveToParent();\n        m_ctx.completeCycle();\n    }\n    void TrackerBase::markAsNeedingAnotherRun() {\n        m_runState = NeedsAnotherRun;\n    }\n\n    void TrackerBase::moveToParent() {\n        assert( m_parent );\n        m_ctx.setCurrentTracker( m_parent );\n    }\n    void TrackerBase::moveToThis() {\n        m_ctx.setCurrentTracker( this );\n    }\n\n    SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent )\n    :   TrackerBase( nameAndLocation, ctx, parent ),\n        m_trimmed_name(trim(nameAndLocation.name))\n    {\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\n    bool SectionTracker::isComplete() const {\n        bool complete = true;\n\n        if (m_filters.empty()\n            || 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\n    bool SectionTracker::isSectionTracker() const { return true; }\n\n    SectionTracker& SectionTracker::acquire( TrackerContext& ctx, 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        }\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\n    void SectionTracker::tryOpen() {\n        if( !isComplete() )\n            open();\n    }\n\n    void 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    }\n    void 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\n    std::vector<std::string> const& SectionTracker::getFilters() const {\n        return m_filters;\n    }\n\n    std::string const& SectionTracker::trimmedName() const {\n        return m_trimmed_name;\n    }\n\n} // namespace TestCaseTracking\n\nusing TestCaseTracking::ITracker;\nusing TestCaseTracking::TrackerContext;\nusing TestCaseTracking::SectionTracker;\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\n    auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker* {\n        return new(std::nothrow) TestInvokerAsFunction( testAsFunction );\n    }\n\n    NameAndTags::NameAndTags( StringRef const& name_ , StringRef const& tags_ ) noexcept : name( name_ ), tags( tags_ ) {}\n\n    AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef const& classOrMethod, NameAndTags const& nameAndTags ) noexcept {\n        CATCH_TRY {\n            getMutableRegistryHub()\n                    .registerTest(\n                        makeTestCase(\n                            invoker,\n                            extractClassName( classOrMethod ),\n                            nameAndTags,\n                            lineInfo));\n        } CATCH_CATCH_ALL {\n            // Do not throw when constructing global objects, instead register the exception to be processed later\n            getMutableRegistryHub().registerStartupException();\n        }\n    }\n\n    AutoReg::~AutoReg() = default;\n}\n// end catch_test_registry.cpp\n// start catch_test_spec.cpp\n\n#include <algorithm>\n#include <string>\n#include <vector>\n#include <memory>\n\nnamespace Catch {\n\n    TestSpec::Pattern::Pattern( std::string const& name )\n    : m_name( name )\n    {}\n\n    TestSpec::Pattern::~Pattern() = default;\n\n    std::string const& TestSpec::Pattern::name() const {\n        return m_name;\n    }\n\n    TestSpec::NamePattern::NamePattern( std::string const& name, std::string const& filterString )\n    : Pattern( filterString )\n    , m_wildcardPattern( toLower( name ), CaseSensitive::No )\n    {}\n\n    bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const {\n        return m_wildcardPattern.matches( testCase.name );\n    }\n\n    TestSpec::TagPattern::TagPattern( std::string const& tag, std::string const& filterString )\n    : Pattern( filterString )\n    , m_tag( toLower( tag ) )\n    {}\n\n    bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const {\n        return std::find(begin(testCase.lcaseTags),\n                         end(testCase.lcaseTags),\n                         m_tag) != end(testCase.lcaseTags);\n    }\n\n    TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern )\n    : Pattern( underlyingPattern->name() )\n    , m_underlyingPattern( underlyingPattern )\n    {}\n\n    bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const {\n        return !m_underlyingPattern->matches( testCase );\n    }\n\n    bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const {\n        return std::all_of( m_patterns.begin(), m_patterns.end(), [&]( PatternPtr const& p ){ return p->matches( testCase ); } );\n    }\n\n    std::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\n    bool TestSpec::hasFilters() const {\n        return !m_filters.empty();\n    }\n\n    bool TestSpec::matches( TestCaseInfo const& testCase ) const {\n        return std::any_of( m_filters.begin(), m_filters.end(), [&]( Filter const& f ){ return f.matches( testCase ); } );\n    }\n\n    TestSpec::Matches TestSpec::matchesByFilter( std::vector<TestCase> const& testCases, IConfig const& config ) const\n    {\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\n    const TestSpec::vectorStrings& TestSpec::getInvalidArgs() const{\n        return  (m_invalidArgs);\n    }\n\n}\n// end catch_test_spec.cpp\n// start catch_test_spec_parser.cpp\n\nnamespace Catch {\n\n    TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {}\n\n    TestSpecParser& 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    }\n    TestSpec TestSpecParser::testSpec() {\n        addFilter();\n        return m_testSpec;\n    }\n    bool 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\n    bool 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    }\n    void 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    }\n    bool TestSpecParser::processOtherChar( char c ) {\n        if( !isControlChar( c ) )\n            return false;\n        m_substring += c;\n        endMode();\n        return true;\n    }\n    void TestSpecParser::startNewMode( Mode mode ) {\n        m_mode = mode;\n    }\n    void 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    }\n    void TestSpecParser::escape() {\n        saveLastMode();\n        m_mode = EscapedName;\n        m_escapeChars.push_back(m_realPatternPos);\n    }\n    bool 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\n    void 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\n    void TestSpecParser::saveLastMode() {\n      lastMode = m_mode;\n    }\n\n    void TestSpecParser::revertBackToLastMode() {\n      m_mode = lastMode;\n    }\n\n    bool 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\n    std::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\n    void 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\n    void 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\n    TestSpec 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\n    auto getCurrentNanosecondsSinceEpoch() -> uint64_t {\n        return std::chrono::duration_cast<std::chrono::nanoseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count();\n    }\n\n    namespace {\n        auto 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\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 and exclude outliers\n            // - and potentially do more iterations if there's a high variance.\n            return sum/iterations;\n        }\n    }\n    auto getEstimatedClockResolution() -> uint64_t {\n        static auto s_resolution = estimateClockResolution();\n        return s_resolution;\n    }\n\n    void Timer::start() {\n       m_nanoseconds = getCurrentNanosecondsSinceEpoch();\n    }\n    auto Timer::getElapsedNanoseconds() const -> uint64_t {\n        return getCurrentNanosecondsSinceEpoch() - m_nanoseconds;\n    }\n    auto Timer::getElapsedMicroseconds() const -> uint64_t {\n        return getElapsedNanoseconds()/1000;\n    }\n    auto Timer::getElapsedMilliseconds() const -> unsigned int {\n        return static_cast<unsigned int>(getElapsedMicroseconds()/1000);\n    }\n    auto Timer::getElapsedSeconds() const -> double {\n        return getElapsedMicroseconds()/1000000.0;\n    }\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\n    const std::string unprintableString = \"{?}\";\n\n    namespace {\n        const int hexThreshold = 255;\n\n        struct 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    }\n\n    std::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}\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 )\n        << std::fixed\n        << 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) {\n    return b ? \"true\" : \"false\";\n}\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) {\n    return \"nullptr\";\n}\n\nint StringMaker<float>::precision = 5;\n\nstd::string StringMaker<float>::convert(float value) {\n    return fpToString(value, precision) + 'f';\n}\n\nint StringMaker<double>::precision = 10;\n\nstd::string StringMaker<double>::convert(double value) {\n    return fpToString(value, precision);\n}\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\n    Counts 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\n    Counts& Counts::operator += ( Counts const& other ) {\n        passed += other.passed;\n        failed += other.failed;\n        failedButOk += other.failedButOk;\n        return *this;\n    }\n\n    std::size_t Counts::total() const {\n        return passed + failed + failedButOk;\n    }\n    bool Counts::allPassed() const {\n        return failed == 0 && failedButOk == 0;\n    }\n    bool Counts::allOk() const {\n        return failed == 0;\n    }\n\n    Totals 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\n    Totals& Totals::operator += ( Totals const& other ) {\n        assertions += other.assertions;\n        testCases += other.testCases;\n        return *this;\n    }\n\n    Totals 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}\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 {\n    bool 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\n    Version::Version\n        (   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    {}\n\n    std::ostream& operator << ( std::ostream& os, Version const& version ) {\n        os  << version.majorVersion << '.'\n            << version.minorVersion << '.'\n            << version.patchNumber;\n        // branchName is never null -> 0th char is \\0 if it is empty\n        if (version.branchName[0]) {\n            os << '-' << version.branchName\n               << '.' << version.buildNumber;\n        }\n        return os;\n    }\n\n    Version const& libraryVersion() {\n        static Version version( 2, 13, 10, \"\", 0 );\n        return version;\n    }\n\n}\n// end catch_version.cpp\n// start catch_wildcard_pattern.cpp\n\nnamespace Catch {\n\n    WildcardPattern::WildcardPattern( std::string const& pattern,\n                                      CaseSensitive::Choice caseSensitivity )\n    :   m_caseSensitivity( caseSensitivity ),\n        m_pattern( normaliseString( pattern ) )\n    {\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\n    bool 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\n    std::string WildcardPattern::normaliseString( std::string const& str ) const {\n        return trim( m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str );\n    }\n}\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\n    size_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\n    uint32_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\n    void hexEscapeChar(std::ostream& os, unsigned char c) {\n        std::ios_base::fmtflags f(os.flags());\n        os << \"\\\\x\"\n            << std::uppercase << std::hex << std::setfill('0') << std::setw(2)\n            << static_cast<int>(c);\n        os.flags(f);\n    }\n\n    bool shouldNewline(XmlFormatting fmt) {\n        return !!(static_cast<std::underlying_type<XmlFormatting>::type>(fmt & XmlFormatting::Newline));\n    }\n\n    bool shouldIndent(XmlFormatting fmt) {\n        return !!(static_cast<std::underlying_type<XmlFormatting>::type>(fmt & XmlFormatting::Indent));\n    }\n\n} // anonymous namespace\n\n    XmlFormatting operator | (XmlFormatting lhs, XmlFormatting rhs) {\n        return static_cast<XmlFormatting>(\n            static_cast<std::underlying_type<XmlFormatting>::type>(lhs) |\n            static_cast<std::underlying_type<XmlFormatting>::type>(rhs)\n        );\n    }\n\n    XmlFormatting operator & (XmlFormatting lhs, XmlFormatting rhs) {\n        return static_cast<XmlFormatting>(\n            static_cast<std::underlying_type<XmlFormatting>::type>(lhs) &\n            static_cast<std::underlying_type<XmlFormatting>::type>(rhs)\n        );\n    }\n\n    XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat )\n    :   m_str( str ),\n        m_forWhat( forWhat )\n    {}\n\n    void 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 '<':   os << \"&lt;\"; break;\n            case '&':   os << \"&amp;\"; 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 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 the encoding format\n                // 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 ||\n                    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) ||\n                    (0x80 <= value && value < 0x800   && encBytes > 2) ||\n                    (0x800 < value && value < 0x10000 && encBytes > 3) ||\n                    // Encoded value out of range\n                    (value >= 0x110000)\n                    ) {\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\n    std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {\n        xmlEncode.encodeTo( os );\n        return os;\n    }\n\n    XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer, XmlFormatting fmt )\n    :   m_writer( writer ),\n        m_fmt(fmt)\n    {}\n\n    XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept\n    :   m_writer( other.m_writer ),\n        m_fmt(other.m_fmt)\n    {\n        other.m_writer = nullptr;\n        other.m_fmt = XmlFormatting::None;\n    }\n    XmlWriter::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\n    XmlWriter::ScopedElement::~ScopedElement() {\n        if (m_writer) {\n            m_writer->endElement(m_fmt);\n        }\n    }\n\n    XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, XmlFormatting fmt ) {\n        m_writer->writeText( text, fmt );\n        return *this;\n    }\n\n    XmlWriter::XmlWriter( std::ostream& os ) : m_os( os )\n    {\n        writeDeclaration();\n    }\n\n    XmlWriter::~XmlWriter() {\n        while (!m_tags.empty()) {\n            endElement();\n        }\n        newlineIfNecessary();\n    }\n\n    XmlWriter& 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\n    XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name, XmlFormatting fmt ) {\n        ScopedElement scoped( this, fmt );\n        startElement( name, fmt );\n        return scoped;\n    }\n\n    XmlWriter& 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\n    XmlWriter& 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\n    XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) {\n        m_os << ' ' << name << \"=\\\"\" << ( attribute ? \"true\" : \"false\" ) << '\"';\n        return *this;\n    }\n\n    XmlWriter& 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\n    XmlWriter& 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\n    void XmlWriter::writeStylesheetRef( std::string const& url ) {\n        m_os << \"<?xml-stylesheet type=\\\"text/xsl\\\" href=\\\"\" << url << \"\\\"?>\\n\";\n    }\n\n    XmlWriter& XmlWriter::writeBlankLine() {\n        ensureTagClosed();\n        m_os << '\\n';\n        return *this;\n    }\n\n    void XmlWriter::ensureTagClosed() {\n        if( m_tagIsOpen ) {\n            m_os << '>' << std::flush;\n            newlineIfNecessary();\n            m_tagIsOpen = false;\n        }\n    }\n\n    void XmlWriter::applyFormatting(XmlFormatting fmt) {\n        m_needsNewline = shouldNewline(fmt);\n    }\n\n    void XmlWriter::writeDeclaration() {\n        m_os << \"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\\n\";\n    }\n\n    void XmlWriter::newlineIfNecessary() {\n        if( m_needsNewline ) {\n            m_os << std::endl;\n            m_needsNewline = false;\n        }\n    }\n}\n// end catch_xmlwriter.cpp\n// start catch_reporter_bases.cpp\n\n#include <cstring>\n#include <cfloat>\n#include <cstdio>\n#include <cassert>\n#include <memory>\n\nnamespace Catch {\n    void prepareExpandedExpression(AssertionResult& result) {\n        result.getExpandedExpression();\n    }\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.\n    std::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\n    bool 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\n    std::string serializeFilters( std::vector<std::string> const& container ) {\n        ReusableStringStream oss;\n        bool first = true;\n        for (auto&& filter : container)\n        {\n            if (!first)\n                oss << ' ';\n            else\n                first = false;\n\n            oss << filter;\n        }\n        return oss.str();\n    }\n\n    TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config)\n        :StreamingReporterBase(_config) {}\n\n    std::set<Verbosity> TestEventListenerBase::getSupportedVerbosities() {\n        return { Verbosity::Quiet, Verbosity::Normal, Verbosity::High };\n    }\n\n    void TestEventListenerBase::assertionStarting(AssertionInfo const &) {}\n\n    bool TestEventListenerBase::assertionEnded(AssertionStats const &) {\n        return false;\n    }\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\n    const char* failedString() { return \"FAILED\"; }\n    const char* passedString() { return \"PASSED\"; }\n#else\n    const char* failedString() { return \"failed\"; }\n    const char* passedString() { return \"passed\"; }\n#endif\n\n    // Colour::LightGrey\n    Catch::Colour::Code dimColour() { return Catch::Colour::FileName; }\n\n    std::string bothOrAll( std::size_t count ) {\n        return count == 1 ? std::string() :\n               count == 2 ? \"both \" : \"all \" ;\n    }\n\n} // anon 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) : std::string();\n        out <<\n            \"Failed \" << bothOrAll(totals.testCases.failed)\n            << pluralise(totals.testCases.failed, \"test case\") << \", \"\n            \"failed \" << qualify_assertions_failed <<\n            pluralise(totals.assertions.failed, \"assertion\") << '.';\n    } else if (totals.assertions.total() == 0) {\n        out <<\n            \"Passed \" << bothOrAll(totals.testCases.total())\n            << pluralise(totals.testCases.total(), \"test case\")\n            << \" (no assertions).\";\n    } else if (totals.assertions.failed) {\n        Colour colour(Colour::ResultError);\n        out <<\n            \"Failed \" << pluralise(totals.testCases.failed, \"test case\") << \", \"\n            \"failed \" << pluralise(totals.assertions.failed, \"assertion\") << '.';\n    } else {\n        Colour colour(Colour::ResultSuccess);\n        out <<\n            \"Passed \" << bothOrAll(totals.testCases.passed)\n            << pluralise(totals.testCases.passed, \"test case\") <<\n            \" with \" << 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, 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 {\n        stream << ' ' << issue;\n    }\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} // anon namespace\n\n        std::string CompactReporter::getDescription() {\n            return \"Reports test results on a single line, suitable for IDEs\";\n        }\n\n        void CompactReporter::noMatchingTestCases( std::string const& spec ) {\n            stream << \"No test cases matched '\" << spec << '\\'' << std::endl;\n        }\n\n        void CompactReporter::assertionStarting( AssertionInfo const& ) {}\n\n        bool 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\n        void CompactReporter::sectionEnded(SectionStats const& _sectionStats) {\n            double dur = _sectionStats.durationInSeconds;\n            if ( shouldShowDuration( *m_config, dur ) ) {\n                stream << getFormattedDuration( dur ) << \" s: \" << _sectionStats.sectionInfo.name << std::endl;\n            }\n        }\n\n        void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) {\n            printTotals( stream, _testRunStats.totals );\n            stream << '\\n' << std::endl;\n            StreamingReporterBase::testRunEnded( _testRunStats );\n        }\n\n        CompactReporter::~CompactReporter() {}\n\n    CATCH_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 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, AssertionStats const& _stats, 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 {\n        Auto,\n        Nanoseconds,\n        Microseconds,\n        Milliseconds,\n        Seconds,\n        Minutes\n    };\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),\n        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\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    }\n    friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& {\n        return os << duration.value() << ' ' << duration.unitsAsString();\n    }\n};\n} // end anon 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 ),\n        m_columnInfos( std::move( columnInfos ) ) {}\n\n    auto columnInfos() const -> std::vector<ColumnInfo> const& {\n        return m_columnInfos;\n    }\n\n    void open() {\n        if (!m_isOpen) {\n            m_isOpen = true;\n            *this << RowBreak();\n\n\t\t\tColumns headerCols;\n\t\t\tSpacer spacer(2);\n\t\t\tfor (auto const& info : m_columnInfos) {\n\t\t\t\theaderCols += Column(info.name).width(static_cast<std::size_t>(info.width - 2));\n\t\t\t\theaderCols += spacer;\n\t\t\t}\n\t\t\tm_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(),\n        [&config]() -> std::vector<ColumnInfo> {\n        if (config.fullConfig()->benchmarkNoAnalysis())\n        {\n            return{\n                { \"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            };\n        }\n        else\n        {\n            return{\n                { \"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        }\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 << 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\tlazyPrintWithoutClosingBenchmarkTable();\n\n\tauto nameCol = Column(name).width(static_cast<std::size_t>(m_tablePrinter->columnInfos()[0].width - 2));\n\n\tbool firstLine = true;\n\tfor (auto line : nameCol) {\n\t\tif (!firstLine)\n\t\t\t(*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak();\n\t\telse\n\t\t\tfirstLine = false;\n\n\t\t(*m_tablePrinter) << line << ColumnBreak();\n\t}\n}\n\nvoid ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) {\n    (*m_tablePrinter) << info.samples << ColumnBreak()\n        << 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    {\n        (*m_tablePrinter) << Duration(stats.mean.point.count()) << ColumnBreak();\n    }\n    else\n    {\n        (*m_tablePrinter) << ColumnBreak()\n            << Duration(stats.mean.point.count()) << ColumnBreak()\n            << Duration(stats.mean.lower_bound.count()) << ColumnBreak()\n            << Duration(stats.mean.upper_bound.count()) << ColumnBreak() << ColumnBreak()\n            << Duration(stats.standardDeviation.point.count()) << ColumnBreak()\n            << Duration(stats.standardDeviation.lower_bound.count()) << ColumnBreak()\n            << Duration(stats.standardDeviation.upper_bound.count()) << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak();\n    }\n}\n\nvoid ConsoleReporter::benchmarkFailed(std::string const& error) {\n\tColour colour(Colour::Red);\n    (*m_tablePrinter)\n        << \"Benchmark failed (\" << error << ')'\n        << 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\n    m_tablePrinter->close();\n    lazyPrintWithoutClosingBenchmarkTable();\n}\n\nvoid ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() {\n\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\n        << \" is a Catch v\" << libraryVersion() << \" 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\n            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\n    SummaryColumn( std::string _label, Colour::Code _colour )\n    :   label( std::move( _label ) ),\n        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};\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 << \" (\"\n            << pluralise(totals.assertions.passed, \"assertion\") << \" in \"\n            << pluralise(totals.testCases.passed, \"test case\") << ')'\n            << '\\n';\n    } else {\n\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, std::vector<SummaryColumn> const& cols, 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)\n                << 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 = 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() {\n    stream << getLineOfChars<'-'>() << '\\n';\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 <cassert>\n#include <sstream>\n#include <ctime>\n#include <algorithm>\n#include <iomanip>\n\nnamespace Catch {\n\n    namespace {\n        std::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\n        std::string fileNameTag(const std::vector<std::string> &tags) {\n            auto it = std::find_if(begin(tags),\n                                   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.\n        std::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\n    JunitReporter::JunitReporter( ReporterConfig const& _config )\n        :   CumulativeReporterBase( _config ),\n            xml( _config.stream() )\n        {\n            m_reporterPrefs.shouldRedirectStdOut = true;\n            m_reporterPrefs.shouldReportAllAssertions = true;\n        }\n\n    JunitReporter::~JunitReporter() {}\n\n    std::string JunitReporter::getDescription() {\n        return \"Reports test results in an XML format that looks like Ant's junitreport target\";\n    }\n\n    void JunitReporter::noMatchingTestCases( std::string const& /*spec*/ ) {}\n\n    void JunitReporter::testRunStarting( TestRunInfo const& runInfo )  {\n        CumulativeReporterBase::testRunStarting( runInfo );\n        xml.startElement( \"testsuites\" );\n    }\n\n    void JunitReporter::testGroupStarting( GroupInfo const& groupInfo ) {\n        suiteTimer.start();\n        stdOutForSuite.clear();\n        stdErrForSuite.clear();\n        unexpectedExceptions = 0;\n        CumulativeReporterBase::testGroupStarting( groupInfo );\n    }\n\n    void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) {\n        m_okToFail = testCaseInfo.okToFail();\n    }\n\n    bool JunitReporter::assertionEnded( AssertionStats const& assertionStats ) {\n        if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail )\n            unexpectedExceptions++;\n        return CumulativeReporterBase::assertionEnded( assertionStats );\n    }\n\n    void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {\n        stdOutForSuite += testCaseStats.stdOut;\n        stdErrForSuite += testCaseStats.stdErr;\n        CumulativeReporterBase::testCaseEnded( testCaseStats );\n    }\n\n    void JunitReporter::testGroupEnded( TestGroupStats const& testGroupStats ) {\n        double suiteTime = suiteTimer.getElapsedSeconds();\n        CumulativeReporterBase::testGroupEnded( testGroupStats );\n        writeGroup( *m_testGroups.back(), suiteTime );\n    }\n\n    void JunitReporter::testRunEndedCumulative() {\n        xml.endElement();\n    }\n\n    void 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\n    void 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\n    void 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() ||\n            !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            }\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\")\n                    .writeAttribute(\"message\", \"TEST_CASE tagged with !mayfail\");\n            }\n\n            writeAssertions( sectionNode );\n\n            if( !sectionNode.stdOut.empty() )\n                xml.scopedElement( \"system-out\" ).writeText( trim( sectionNode.stdOut ), XmlFormatting::Newline );\n            if( !sectionNode.stdErr.empty() )\n                xml.scopedElement( \"system-err\" ).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\n    void JunitReporter::writeAssertions( SectionNode const& sectionNode ) {\n        for( auto const& assertion : sectionNode.assertions )\n            writeAssertion( assertion );\n    }\n\n    void 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                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\n    CATCH_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\n    ListeningReporter::ListeningReporter() {\n        // We will assume that listeners will always want all assertions\n        m_preferences.shouldReportAllAssertions = true;\n    }\n\n    void ListeningReporter::addListener( IStreamingReporterPtr&& listener ) {\n        m_listeners.push_back( std::move( listener ) );\n    }\n\n    void 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\n    ReporterPreferences ListeningReporter::getPreferences() const {\n        return m_preferences;\n    }\n\n    std::set<Verbosity> ListeningReporter::getSupportedVerbosities() {\n        return std::set<Verbosity>{ };\n    }\n\n    void 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\n    void 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)\n    void ListeningReporter::benchmarkPreparing( std::string const& name ) {\n\t\tfor (auto const& listener : m_listeners) {\n\t\t\tlistener->benchmarkPreparing(name);\n\t\t}\n\t\tm_reporter->benchmarkPreparing(name);\n\t}\n    void ListeningReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->benchmarkStarting( benchmarkInfo );\n        }\n        m_reporter->benchmarkStarting( benchmarkInfo );\n    }\n    void ListeningReporter::benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->benchmarkEnded( benchmarkStats );\n        }\n        m_reporter->benchmarkEnded( benchmarkStats );\n    }\n\n\tvoid ListeningReporter::benchmarkFailed( std::string const& error ) {\n\t\tfor (auto const& listener : m_listeners) {\n\t\t\tlistener->benchmarkFailed(error);\n\t\t}\n\t\tm_reporter->benchmarkFailed(error);\n\t}\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    void ListeningReporter::testRunStarting( TestRunInfo const& testRunInfo ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->testRunStarting( testRunInfo );\n        }\n        m_reporter->testRunStarting( testRunInfo );\n    }\n\n    void ListeningReporter::testGroupStarting( GroupInfo const& groupInfo ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->testGroupStarting( groupInfo );\n        }\n        m_reporter->testGroupStarting( groupInfo );\n    }\n\n    void ListeningReporter::testCaseStarting( TestCaseInfo const& testInfo ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->testCaseStarting( testInfo );\n        }\n        m_reporter->testCaseStarting( testInfo );\n    }\n\n    void ListeningReporter::sectionStarting( SectionInfo const& sectionInfo ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->sectionStarting( sectionInfo );\n        }\n        m_reporter->sectionStarting( sectionInfo );\n    }\n\n    void 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:\n    bool 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\n    void ListeningReporter::sectionEnded( SectionStats const& sectionStats ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->sectionEnded( sectionStats );\n        }\n        m_reporter->sectionEnded( sectionStats );\n    }\n\n    void ListeningReporter::testCaseEnded( TestCaseStats const& testCaseStats ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->testCaseEnded( testCaseStats );\n        }\n        m_reporter->testCaseEnded( testCaseStats );\n    }\n\n    void ListeningReporter::testGroupEnded( TestGroupStats const& testGroupStats ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->testGroupEnded( testGroupStats );\n        }\n        m_reporter->testGroupEnded( testGroupStats );\n    }\n\n    void ListeningReporter::testRunEnded( TestRunStats const& testRunStats ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->testRunEnded( testRunStats );\n        }\n        m_reporter->testRunEnded( testRunStats );\n    }\n\n    void ListeningReporter::skipTest( TestCaseInfo const& testInfo ) {\n        for ( auto const& listener : m_listeners ) {\n            listener->skipTest( testInfo );\n        }\n        m_reporter->skipTest( testInfo );\n    }\n\n    bool ListeningReporter::isMulti() const {\n        return true;\n    }\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 switch\n                              // Note that 4062 (not all labels are handled\n                              // and default is missing) is enabled\n#endif\n\nnamespace Catch {\n    XmlReporter::XmlReporter( ReporterConfig const& _config )\n    :   StreamingReporterBase( _config ),\n        m_xml(_config.stream())\n    {\n        m_reporterPrefs.shouldRedirectStdOut = true;\n        m_reporterPrefs.shouldReportAllAssertions = true;\n    }\n\n    XmlReporter::~XmlReporter() = default;\n\n    std::string XmlReporter::getDescription() {\n        return \"Reports test results as an XML document\";\n    }\n\n    std::string XmlReporter::getStylesheetRef() const {\n        return std::string();\n    }\n\n    void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) {\n        m_xml\n            .writeAttribute( \"filename\", sourceInfo.file )\n            .writeAttribute( \"line\", sourceInfo.line );\n    }\n\n    void XmlReporter::noMatchingTestCases( std::string const& s ) {\n        StreamingReporterBase::noMatchingTestCases( s );\n    }\n\n    void 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\" )\n                .writeAttribute( \"seed\", m_config->rngSeed() );\n    }\n\n    void XmlReporter::testGroupStarting( GroupInfo const& groupInfo ) {\n        StreamingReporterBase::testGroupStarting( groupInfo );\n        m_xml.startElement( \"Group\" )\n            .writeAttribute( \"name\", groupInfo.name );\n    }\n\n    void 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\n    void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) {\n        StreamingReporterBase::sectionStarting( sectionInfo );\n        if( m_sectionDepth++ > 0 ) {\n            m_xml.startElement( \"Section\" )\n                .writeAttribute( \"name\", trim( sectionInfo.name ) );\n            writeSourceInfo( sectionInfo.lineInfo );\n            m_xml.ensureTagClosed();\n        }\n    }\n\n    void XmlReporter::assertionStarting( AssertionInfo const& ) { }\n\n    bool XmlReporter::assertionEnded( AssertionStats const& assertionStats ) {\n\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\" )\n                            .writeText( msg.message );\n                } else if ( msg.type == ResultWas::Warning ) {\n                    m_xml.scopedElement( \"Warning\" )\n                            .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\" )\n                .writeText( result.getExpression() );\n            m_xml.scopedElement( \"Expanded\" )\n                .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\" )\n                    .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\n    void 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\n    void 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\n    void 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\n    void 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)\n    void XmlReporter::benchmarkPreparing(std::string const& name) {\n        m_xml.startElement(\"BenchmarkResults\")\n            .writeAttribute(\"name\", name);\n    }\n\n    void 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\n    void 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\n    void XmlReporter::benchmarkFailed(std::string const &error) {\n        m_xml.scopedElement(\"failed\").\n            writeAttribute(\"message\", error);\n        m_xml.endElement();\n    }\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n    CATCH_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 {\n    LeakDetector 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#ifndef CATCH_INTERNAL_CDECL\n#ifdef _MSC_VER\n#define CATCH_INTERNAL_CDECL __cdecl\n#else\n#define CATCH_INTERNAL_CDECL\n#endif\n#endif\n\n#if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN)\n// Standard C/C++ Win32 Unicode wmain entry point\nextern \"C\" int CATCH_INTERNAL_CDECL wmain (int argc, wchar_t * argv[], wchar_t * []) {\n#else\n// Standard C/C++ main entry point\nint CATCH_INTERNAL_CDECL 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 CATCH_\n#ifdef CATCH_CONFIG_PREFIX_ALL\n\n#define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( \"CATCH_REQUIRE\", Catch::ResultDisposition::Normal, __VA_ARGS__ )\n#define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( \"CATCH_REQUIRE_FALSE\", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )\n\n#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( \"CATCH_REQUIRE_THROWS\", Catch::ResultDisposition::Normal, __VA_ARGS__ )\n#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( \"CATCH_REQUIRE_THROWS_AS\", exceptionType, Catch::ResultDisposition::Normal, expr )\n#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( \"CATCH_REQUIRE_THROWS_WITH\", Catch::ResultDisposition::Normal, matcher, expr )\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( \"CATCH_REQUIRE_THROWS_MATCHES\", exceptionType, Catch::ResultDisposition::Normal, matcher, expr )\n#endif// CATCH_CONFIG_DISABLE_MATCHERS\n#define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( \"CATCH_REQUIRE_NOTHROW\", Catch::ResultDisposition::Normal, __VA_ARGS__ )\n\n#define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( \"CATCH_CHECK\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( \"CATCH_CHECK_FALSE\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )\n#define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( \"CATCH_CHECKED_IF\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( \"CATCH_CHECKED_ELSE\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( \"CATCH_CHECK_NOFAIL\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )\n\n#define CATCH_CHECK_THROWS( ... )  INTERNAL_CATCH_THROWS( \"CATCH_CHECK_THROWS\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( \"CATCH_CHECK_THROWS_AS\", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )\n#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( \"CATCH_CHECK_THROWS_WITH\", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( \"CATCH_CHECK_THROWS_MATCHES\", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr )\n#endif // CATCH_CONFIG_DISABLE_MATCHERS\n#define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( \"CATCH_CHECK_NOTHROW\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( \"CATCH_CHECK_THAT\", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )\n\n#define CATCH_REQUIRE_THAT( arg, matcher ) 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 ) INTERNAL_CATCH_MSG( \"CATCH_WARN\", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )\n#define CATCH_CAPTURE( ... ) 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, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ )\n#define CATCH_REGISTER_TEST_CASE( Function, ... ) 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( ... ) INTERNAL_CATCH_MSG( \"CATCH_FAIL\", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )\n#define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( \"CATCH_FAIL_CHECK\", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( \"CATCH_SUCCEED\", Catch::ResultWas::Ok, 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, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) 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( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ )\n#else\n#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( 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( ... )       static_assert(   __VA_ARGS__ ,      #__VA_ARGS__ );     CATCH_SUCCEED( #__VA_ARGS__ )\n#define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), \"!(\" #__VA_ARGS__ \")\" ); 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, ... ) 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_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), 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_), name)\n#endif // CATCH_CONFIG_ENABLE_BENCHMARKING\n\n// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required\n#else\n\n#define REQUIRE( ... ) INTERNAL_CATCH_TEST( \"REQUIRE\", Catch::ResultDisposition::Normal, __VA_ARGS__  )\n#define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( \"REQUIRE_FALSE\", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )\n\n#define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( \"REQUIRE_THROWS\", Catch::ResultDisposition::Normal, __VA_ARGS__ )\n#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( \"REQUIRE_THROWS_AS\", exceptionType, Catch::ResultDisposition::Normal, expr )\n#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( \"REQUIRE_THROWS_WITH\", Catch::ResultDisposition::Normal, matcher, expr )\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( \"REQUIRE_THROWS_MATCHES\", exceptionType, Catch::ResultDisposition::Normal, matcher, expr )\n#endif // CATCH_CONFIG_DISABLE_MATCHERS\n#define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( \"REQUIRE_NOTHROW\", Catch::ResultDisposition::Normal, __VA_ARGS__ )\n\n#define CHECK( ... ) INTERNAL_CATCH_TEST( \"CHECK\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( \"CHECK_FALSE\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ )\n#define CHECKED_IF( ... ) INTERNAL_CATCH_IF( \"CHECKED_IF\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( \"CHECKED_ELSE\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( \"CHECK_NOFAIL\", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ )\n\n#define CHECK_THROWS( ... )  INTERNAL_CATCH_THROWS( \"CHECK_THROWS\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( \"CHECK_THROWS_AS\", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr )\n#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( \"CHECK_THROWS_WITH\", Catch::ResultDisposition::ContinueOnFailure, matcher, expr )\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( \"CHECK_THROWS_MATCHES\", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr )\n#endif // CATCH_CONFIG_DISABLE_MATCHERS\n#define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( \"CHECK_NOTHROW\", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n\n#if !defined(CATCH_CONFIG_DISABLE_MATCHERS)\n#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( \"CHECK_THAT\", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )\n\n#define REQUIRE_THAT( arg, matcher ) 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 ) INTERNAL_CATCH_MSG( \"WARN\", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg )\n#define CAPTURE( ... ) 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( ... ) INTERNAL_CATCH_MSG( \"FAIL\", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ )\n#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( \"FAIL_CHECK\", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ )\n#define SUCCEED( ... ) INTERNAL_CATCH_MSG( \"SUCCEED\", Catch::ResultWas::Ok, 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, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) 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( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ )\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) 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, ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#else\n#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) )\n#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) )\n#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) )\n#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) )\n#define TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) )\n#define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) )\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) )\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) )\n#define TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE( __VA_ARGS__ ) )\n#define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( 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( ... )       static_assert(   __VA_ARGS__,  #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ )\n#define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), \"!(\" #__VA_ARGS__ \")\" ); 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, ... ) 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_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), 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_), 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 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( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))\n#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(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() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(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( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__)\n#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__)\n#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) 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, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#else\n#define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) )\n#define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) )\n#define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__ ) )\n#define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( 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, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#endif\n\n// \"BDD-style\" convenience wrappers\n#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))\n#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(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 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( ... ) (void)(0)\n\n#define TEST_CASE( ... )  INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ))\n#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(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() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(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( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__)\n#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__)\n#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) 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, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#else\n#define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) )\n#define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) )\n#define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__ ) )\n#define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( 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, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ )\n#define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) 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 ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature )\n\n// \"BDD-style\" convenience wrappers\n#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( C_A_T_C_H_T_E_S_T_ ) )\n#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(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\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\n"
  },
  {
    "path": "dependencies/catch2/version.txt",
    "content": "Catch2 v2.13.10.\nhttps://github.com/catchorg/Catch2\n"
  },
  {
    "path": "dependencies/fmt/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2025 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nadd_library(fmt STATIC)\n\n# Relative sources are allowed only since cmake 3.13.\ntarget_sources(fmt PRIVATE\n\t${CMAKE_CURRENT_SOURCE_DIR}/fmt/src/format.cc\n\t${CMAKE_CURRENT_SOURCE_DIR}/fmt/include/fmt/args.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/fmt/include/fmt/base.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/fmt/include/fmt/chrono.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/fmt/include/fmt/color.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/fmt/include/fmt/compile.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/fmt/include/fmt/core.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/fmt/include/fmt/format.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/fmt/include/fmt/format-inl.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/fmt/include/fmt/os.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/fmt/include/fmt/ostream.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/fmt/include/fmt/printf.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/fmt/include/fmt/ranges.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/fmt/include/fmt/std.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/fmt/include/fmt/xchar.h\n)\n\ntarget_include_directories(fmt\n\tSYSTEM PUBLIC\n\t\t\"fmt/include\"\n)\n\n"
  },
  {
    "path": "dependencies/fmt/fmt/LICENSE",
    "content": "Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentation files (the\n\"Software\"), to deal in the Software without restriction, including\nwithout limitation the rights to use, copy, modify, merge, publish,\ndistribute, sublicense, and/or sell copies of the Software, and to\npermit persons to whom the Software is furnished to do so, subject to\nthe following conditions:\n\nThe above copyright notice and this permission notice shall be\nincluded in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\nMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\nNONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\nLIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\nOF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\nWITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n--- Optional exception to the license ---\n\nAs an exception, if, as a result of your compiling your source code, portions\nof this Software are embedded into a machine-executable object form of such\nsource code, you may redistribute such embedded portions in such object form\nwithout including the above copyright and permission notices.\n"
  },
  {
    "path": "dependencies/fmt/fmt/README.md",
    "content": "<img src=\"https://user-images.githubusercontent.com/576385/156254208-f5b743a9-88cf-439d-b0c0-923d53e8d551.png\" alt=\"{fmt}\" width=\"25%\"/>\n\n[![image](https://github.com/fmtlib/fmt/workflows/linux/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux)\n[![image](https://github.com/fmtlib/fmt/workflows/macos/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos)\n[![image](https://github.com/fmtlib/fmt/workflows/windows/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows)\n[![fmt is continuously fuzzed at oss-fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?\\%0Acolspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\\%0ASummary&q=proj%3Dfmt&can=1)\n[![Ask questions at StackOverflow with the tag fmt](https://img.shields.io/badge/stackoverflow-fmt-blue.svg)](https://stackoverflow.com/questions/tagged/fmt)\n[![image](https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge)](https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt)\n\n**{fmt}** is an open-source formatting library providing a fast and safe\nalternative to C stdio and C++ iostreams.\n\nIf you like this project, please consider donating to one of the funds\nthat help victims of the war in Ukraine: <https://www.stopputin.net/>.\n\n[Documentation](https://fmt.dev)\n\n[Cheat Sheets](https://hackingcpp.com/cpp/libs/fmt.html)\n\nQ&A: ask questions on [StackOverflow with the tag\nfmt](https://stackoverflow.com/questions/tagged/fmt).\n\nTry {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v).\n\n# Features\n\n- Simple [format API](https://fmt.dev/latest/api/) with positional\n  arguments for localization\n- Implementation of [C++20\n  std::format](https://en.cppreference.com/w/cpp/utility/format) and\n  [C++23 std::print](https://en.cppreference.com/w/cpp/io/print)\n- [Format string syntax](https://fmt.dev/latest/syntax/) similar\n  to Python\\'s\n  [format](https://docs.python.org/3/library/stdtypes.html#str.format)\n- Fast IEEE 754 floating-point formatter with correct rounding,\n  shortness and round-trip guarantees using the\n  [Dragonbox](https://github.com/jk-jeon/dragonbox) algorithm\n- Portable Unicode support\n- Safe [printf\n  implementation](https://fmt.dev/latest/api/#printf-formatting)\n  including the POSIX extension for positional arguments\n- Extensibility: [support for user-defined\n  types](https://fmt.dev/latest/api/#formatting-user-defined-types)\n- High performance: faster than common standard library\n  implementations of `(s)printf`, iostreams, `to_string` and\n  `to_chars`, see [Speed tests](#speed-tests) and [Converting a\n  hundred million integers to strings per\n  second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html)\n- Small code size both in terms of source code with the minimum\n  configuration consisting of just three files, `core.h`, `format.h`\n  and `format-inl.h`, and compiled code; see [Compile time and code\n  bloat](#compile-time-and-code-bloat)\n- Reliability: the library has an extensive set of\n  [tests](https://github.com/fmtlib/fmt/tree/master/test) and is\n  [continuously fuzzed](https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1)\n- Safety: the library is fully type-safe, errors in format strings can\n  be reported at compile time, automatic memory management prevents\n  buffer overflow errors\n- Ease of use: small self-contained code base, no external\n  dependencies, permissive MIT\n  [license](https://github.com/fmtlib/fmt/blob/master/LICENSE)\n- [Portability](https://fmt.dev/latest/#portability) with\n  consistent output across platforms and support for older compilers\n- Clean warning-free codebase even on high warning levels such as\n  `-Wall -Wextra -pedantic`\n- Locale independence by default\n- Optional header-only configuration enabled with the\n  `FMT_HEADER_ONLY` macro\n\nSee the [documentation](https://fmt.dev) for more details.\n\n# Examples\n\n**Print to stdout** ([run](https://godbolt.org/z/Tevcjh))\n\n``` c++\n#include <fmt/core.h>\n\nint main() {\n  fmt::print(\"Hello, world!\\n\");\n}\n```\n\n**Format a string** ([run](https://godbolt.org/z/oK8h33))\n\n``` c++\nstd::string s = fmt::format(\"The answer is {}.\", 42);\n// s == \"The answer is 42.\"\n```\n\n**Format a string using positional arguments**\n([run](https://godbolt.org/z/Yn7Txe))\n\n``` c++\nstd::string s = fmt::format(\"I'd rather be {1} than {0}.\", \"right\", \"happy\");\n// s == \"I'd rather be happy than right.\"\n```\n\n**Print dates and times** ([run](https://godbolt.org/z/c31ExdY3W))\n\n``` c++\n#include <fmt/chrono.h>\n\nint main() {\n  auto now = std::chrono::system_clock::now();\n  fmt::print(\"Date and time: {}\\n\", now);\n  fmt::print(\"Time: {:%H:%M}\\n\", now);\n}\n```\n\nOutput:\n\n    Date and time: 2023-12-26 19:10:31.557195597\n    Time: 19:10\n\n**Print a container** ([run](https://godbolt.org/z/MxM1YqjE7))\n\n``` c++\n#include <vector>\n#include <fmt/ranges.h>\n\nint main() {\n  std::vector<int> v = {1, 2, 3};\n  fmt::print(\"{}\\n\", v);\n}\n```\n\nOutput:\n\n    [1, 2, 3]\n\n**Check a format string at compile time**\n\n``` c++\nstd::string s = fmt::format(\"{:d}\", \"I am not a number\");\n```\n\nThis gives a compile-time error in C++20 because `d` is an invalid\nformat specifier for a string.\n\n**Write a file from a single thread**\n\n``` c++\n#include <fmt/os.h>\n\nint main() {\n  auto out = fmt::output_file(\"guide.txt\");\n  out.print(\"Don't {}\", \"Panic\");\n}\n```\n\nThis can be [5 to 9 times faster than\nfprintf](http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).\n\n**Print with colors and text styles**\n\n``` c++\n#include <fmt/color.h>\n\nint main() {\n  fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold,\n             \"Hello, {}!\\n\", \"world\");\n  fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |\n             fmt::emphasis::underline, \"Olá, {}!\\n\", \"Mundo\");\n  fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,\n             \"你好{}！\\n\", \"世界\");\n}\n```\n\nOutput on a modern terminal with Unicode support:\n\n![image](https://github.com/fmtlib/fmt/assets/%0A576385/2a93c904-d6fa-4aa6-b453-2618e1c327d7)\n\n# Benchmarks\n\n## Speed tests\n\n| Library           | Method        | Run Time, s |\n|-------------------|---------------|-------------|\n| libc              | printf        |   0.91      |\n| libc++            | std::ostream  |   2.49      |\n| {fmt} 9.1         | fmt::print    |   0.74      |\n| Boost Format 1.80 | boost::format |   6.26      |\n| Folly Format      | folly::format |   1.87      |\n\n{fmt} is the fastest of the benchmarked methods, \\~20% faster than\n`printf`.\n\nThe above results were generated by building `tinyformat_test.cpp` on\nmacOS 12.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and\ntaking the best of three runs. In the test, the format string\n`\"%0.10f:%04d:%+g:%s:%p:%c:%%\\n\"` or equivalent is filled 2,000,000\ntimes with output sent to `/dev/null`; for further details refer to the\n[source](https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc).\n\n{fmt} is up to 20-30x faster than `std::ostringstream` and `sprintf` on\nIEEE754 `float` and `double` formatting\n([dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark)) and faster\nthan [double-conversion](https://github.com/google/double-conversion)\nand [ryu](https://github.com/ulfjack/ryu):\n\n[![image](https://user-images.githubusercontent.com/576385/95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png)](https://fmt.dev/unknown_mac64_clang12.0.html)\n\n## Compile time and code bloat\n\nThe script [bloat-test.py][test] from [format-benchmark][bench] tests compile\ntime and code bloat for nontrivial projects. It generates 100 translation units\nand uses `printf()` or its alternative five times in each to simulate a\nmedium-sized project. The resulting executable size and compile time (Apple\nclang version 15.0.0 (clang-1500.1.0.2.5), macOS Sonoma, best of three) is shown\nin the following tables.\n\n[test]: https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py\n[bench]: https://github.com/fmtlib/format-benchmark\n\n**Optimized build (-O3)**\n\n| Method        | Compile Time, s | Executable size, KiB | Stripped size, KiB |\n|---------------|-----------------|----------------------|--------------------|\n| printf        |             1.6 |                   54 |                 50 |\n| IOStreams     |            25.9 |                   98 |                 84 |\n| fmt 83652df   |             4.8 |                   54 |                 50 |\n| tinyformat    |            29.1 |                  161 |                136 |\n| Boost Format  |            55.0 |                  530 |                317 |\n\n{fmt} is fast to compile and is comparable to `printf` in terms of per-call\nbinary size (within a rounding error on this system).\n\n**Non-optimized build**\n\n| Method        | Compile Time, s | Executable size, KiB | Stripped size, KiB |\n|---------------|-----------------|----------------------|--------------------|\n| printf        |             1.4 |                   54 |                 50 |\n| IOStreams     |            23.4 |                   92 |                 68 |\n| {fmt} 83652df |             4.4 |                   89 |                 85 |\n| tinyformat    |            24.5 |                  204 |                161 |\n| Boost Format  |            36.4 |                  831 |                462 |\n\n`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries\nto compare formatting function overhead only. Boost Format is a\nheader-only library so it doesn\\'t provide any linkage options.\n\n## Running the tests\n\nPlease refer to [Building the\nlibrary](https://fmt.dev/latest/get-started/#building-from-source) for\ninstructions on how to build the library and run the unit tests.\n\nBenchmarks reside in a separate repository,\n[format-benchmarks](https://github.com/fmtlib/format-benchmark), so to\nrun the benchmarks you first need to clone this repository and generate\nMakefiles with CMake:\n\n    $ git clone --recursive https://github.com/fmtlib/format-benchmark.git\n    $ cd format-benchmark\n    $ cmake .\n\nThen you can run the speed test:\n\n    $ make speed-test\n\nor the bloat test:\n\n    $ make bloat-test\n\n# Migrating code\n\n[clang-tidy](https://clang.llvm.org/extra/clang-tidy/) v18 provides the\n[modernize-use-std-print](https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html)\ncheck that is capable of converting occurrences of `printf` and\n`fprintf` to `fmt::print` if configured to do so. (By default it\nconverts to `std::print`.)\n\n# Notable projects using this library\n\n- [0 A.D.](https://play0ad.com/): a free, open-source, cross-platform\n  real-time strategy game\n- [AMPL/MP](https://github.com/ampl/mp): an open-source library for\n  mathematical programming\n- [Apple's FoundationDB](https://github.com/apple/foundationdb): an open-source,\n  distributed, transactional key-value store\n- [Aseprite](https://github.com/aseprite/aseprite): animated sprite\n  editor & pixel art tool\n- [AvioBook](https://www.aviobook.aero/en): a comprehensive aircraft\n  operations suite\n- [Blizzard Battle.net](https://battle.net/): an online gaming\n  platform\n- [Celestia](https://celestia.space/): real-time 3D visualization of\n  space\n- [Ceph](https://ceph.com/): a scalable distributed storage system\n- [ccache](https://ccache.dev/): a compiler cache\n- [ClickHouse](https://github.com/ClickHouse/ClickHouse): an\n  analytical database management system\n- [ContextVision](https://www.contextvision.com/): medical imaging software\n- [Contour](https://github.com/contour-terminal/contour/): a modern\n  terminal emulator\n- [CUAUV](https://cuauv.org/): Cornell University\\'s autonomous\n  underwater vehicle\n- [Drake](https://drake.mit.edu/): a planning, control, and analysis\n  toolbox for nonlinear dynamical systems (MIT)\n- [Envoy](https://github.com/envoyproxy/envoy): C++ L7 proxy and\n  communication bus (Lyft)\n- [FiveM](https://fivem.net/): a modification framework for GTA V\n- [fmtlog](https://github.com/MengRao/fmtlog): a performant\n  fmtlib-style logging library with latency in nanoseconds\n- [Folly](https://github.com/facebook/folly): Facebook open-source\n  library\n- [GemRB](https://gemrb.org/): a portable open-source implementation\n  of Bioware's Infinity Engine\n- [Grand Mountain\n  Adventure](https://store.steampowered.com/app/1247360/Grand_Mountain_Adventure/):\n  a beautiful open-world ski & snowboarding game\n- [HarpyWar/pvpgn](https://github.com/pvpgn/pvpgn-server): Player vs\n  Player Gaming Network with tweaks\n- [KBEngine](https://github.com/kbengine/kbengine): an open-source\n  MMOG server engine\n- [Keypirinha](https://keypirinha.com/): a semantic launcher for\n  Windows\n- [Kodi](https://kodi.tv/) (formerly xbmc): home theater software\n- [Knuth](https://kth.cash/): high-performance Bitcoin full-node\n- [libunicode](https://github.com/contour-terminal/libunicode/): a\n  modern C++17 Unicode library\n- [MariaDB](https://mariadb.org/): relational database management\n  system\n- [Microsoft Verona](https://github.com/microsoft/verona): research\n  programming language for concurrent ownership\n- [MongoDB](https://mongodb.com/): distributed document database\n- [MongoDB Smasher](https://github.com/duckie/mongo_smasher): a small\n  tool to generate randomized datasets\n- [OpenSpace](https://openspaceproject.com/): an open-source\n  astrovisualization framework\n- [PenUltima Online (POL)](https://www.polserver.com/): an MMO server,\n  compatible with most Ultima Online clients\n- [PyTorch](https://github.com/pytorch/pytorch): an open-source\n  machine learning library\n- [quasardb](https://www.quasardb.net/): a distributed,\n  high-performance, associative database\n- [Quill](https://github.com/odygrd/quill): asynchronous low-latency\n  logging library\n- [QKW](https://github.com/ravijanjam/qkw): generalizing aliasing to\n  simplify navigation, and execute complex multi-line terminal\n  command sequences\n- [redis-cerberus](https://github.com/HunanTV/redis-cerberus): a Redis\n  cluster proxy\n- [redpanda](https://vectorized.io/redpanda): a 10x faster Kafka®\n  replacement for mission-critical systems written in C++\n- [rpclib](http://rpclib.net/): a modern C++ msgpack-RPC server and\n  client library\n- [Salesforce Analytics\n  Cloud](https://www.salesforce.com/analytics-cloud/overview/):\n  business intelligence software\n- [Scylla](https://www.scylladb.com/): a Cassandra-compatible NoSQL\n  data store that can handle 1 million transactions per second on a\n  single server\n- [Seastar](http://www.seastar-project.org/): an advanced, open-source\n  C++ framework for high-performance server applications on modern\n  hardware\n- [spdlog](https://github.com/gabime/spdlog): super fast C++ logging\n  library\n- [Stellar](https://www.stellar.org/): financial platform\n- [Touch Surgery](https://www.touchsurgery.com/): surgery simulator\n- [TrinityCore](https://github.com/TrinityCore/TrinityCore):\n  open-source MMORPG framework\n- [🐙 userver framework](https://userver.tech/): open-source\n  asynchronous framework with a rich set of abstractions and database\n  drivers\n- [Windows Terminal](https://github.com/microsoft/terminal): the new\n  Windows terminal\n\n[More\\...](https://github.com/search?q=fmtlib&type=Code)\n\nIf you are aware of other projects using this library, please let me\nknow by [email](mailto:victor.zverovich@gmail.com) or by submitting an\n[issue](https://github.com/fmtlib/fmt/issues).\n\n# Motivation\n\nSo why yet another formatting library?\n\nThere are plenty of methods for doing this task, from standard ones like\nthe printf family of function and iostreams to Boost Format and\nFastFormat libraries. The reason for creating a new library is that\nevery existing solution that I found either had serious issues or\ndidn\\'t provide all the features I needed.\n\n## printf\n\nThe good thing about `printf` is that it is pretty fast and readily\navailable being a part of the C standard library. The main drawback is\nthat it doesn\\'t support user-defined types. `printf` also has safety\nissues although they are somewhat mitigated with [\\_\\_attribute\\_\\_\n((format (printf,\n\\...))](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html) in\nGCC. There is a POSIX extension that adds positional arguments required\nfor\n[i18n](https://en.wikipedia.org/wiki/Internationalization_and_localization)\nto `printf` but it is not a part of C99 and may not be available on some\nplatforms.\n\n## iostreams\n\nThe main issue with iostreams is best illustrated with an example:\n\n``` c++\nstd::cout << std::setprecision(2) << std::fixed << 1.23456 << \"\\n\";\n```\n\nwhich is a lot of typing compared to printf:\n\n``` c++\nprintf(\"%.2f\\n\", 1.23456);\n```\n\nMatthew Wilson, the author of FastFormat, called this \\\"chevron hell\\\".\niostreams don\\'t support positional arguments by design.\n\nThe good part is that iostreams support user-defined types and are safe\nalthough error handling is awkward.\n\n## Boost Format\n\nThis is a very powerful library that supports both `printf`-like format\nstrings and positional arguments. Its main drawback is performance.\nAccording to various benchmarks, it is much slower than other methods\nconsidered here. Boost Format also has excessive build times and severe\ncode bloat issues (see [Benchmarks](#benchmarks)).\n\n## FastFormat\n\nThis is an interesting library that is fast, safe and has positional\narguments. However, it has significant limitations, citing its author:\n\n> Three features that have no hope of being accommodated within the\n> current design are:\n>\n> - Leading zeros (or any other non-space padding)\n> - Octal/hexadecimal encoding\n> - Runtime width/alignment specification\n\nIt is also quite big and has a heavy dependency, on STLSoft, which might be\ntoo restrictive for use in some projects.\n\n## Boost Spirit.Karma\n\nThis is not a formatting library but I decided to include it here for\ncompleteness. As iostreams, it suffers from the problem of mixing\nverbatim text with arguments. The library is pretty fast, but slower on\ninteger formatting than `fmt::format_to` with format string compilation\non Karma\\'s own benchmark, see [Converting a hundred million integers to\nstrings per\nsecond](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html).\n\n# License\n\n{fmt} is distributed under the MIT\n[license](https://github.com/fmtlib/fmt/blob/master/LICENSE).\n\n# Documentation License\n\nThe [Format String Syntax](https://fmt.dev/latest/syntax/) section\nin the documentation is based on the one from Python [string module\ndocumentation](https://docs.python.org/3/library/string.html#module-string).\nFor this reason, the documentation is distributed under the Python\nSoftware Foundation license available in\n[doc/python-license.txt](https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt).\nIt only applies if you distribute the documentation of {fmt}.\n\n# Maintainers\n\nThe {fmt} library is maintained by Victor Zverovich\n([vitaut](https://github.com/vitaut)) with contributions from many other\npeople. See\n[Contributors](https://github.com/fmtlib/fmt/graphs/contributors) and\n[Releases](https://github.com/fmtlib/fmt/releases) for some of the\nnames. Let us know if your contribution is not listed or mentioned\nincorrectly and we\\'ll make it right.\n\n# Security Policy\n\nTo report a security issue, please disclose it at [security\nadvisory](https://github.com/fmtlib/fmt/security/advisories/new).\n\nThis project is maintained by a team of volunteers on a\nreasonable-effort basis. As such, please give us at least *90* days to\nwork on a fix before public exposure.\n"
  },
  {
    "path": "dependencies/fmt/fmt/include/fmt/args.h",
    "content": "// Formatting library for C++ - dynamic argument lists\n//\n// Copyright (c) 2012 - present, Victor Zverovich\n// All rights reserved.\n//\n// For the license information refer to format.h.\n\n#ifndef FMT_ARGS_H_\n#define FMT_ARGS_H_\n\n#ifndef FMT_MODULE\n#  include <functional>  // std::reference_wrapper\n#  include <memory>      // std::unique_ptr\n#  include <vector>\n#endif\n\n#include \"format.h\"  // std_string_view\n\nFMT_BEGIN_NAMESPACE\nnamespace detail {\n\ntemplate <typename T> struct is_reference_wrapper : std::false_type {};\ntemplate <typename T>\nstruct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};\n\ntemplate <typename T> auto unwrap(const T& v) -> const T& { return v; }\ntemplate <typename T>\nauto unwrap(const std::reference_wrapper<T>& v) -> const T& {\n  return static_cast<const T&>(v);\n}\n\n// node is defined outside dynamic_arg_list to workaround a C2504 bug in MSVC\n// 2022 (v17.10.0).\n//\n// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for\n// templates it doesn't complain about inability to deduce single translation\n// unit for placing vtable. So node is made a fake template.\ntemplate <typename = void> struct node {\n  virtual ~node() = default;\n  std::unique_ptr<node<>> next;\n};\n\nclass dynamic_arg_list {\n  template <typename T> struct typed_node : node<> {\n    T value;\n\n    template <typename Arg>\n    FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}\n\n    template <typename Char>\n    FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)\n        : value(arg.data(), arg.size()) {}\n  };\n\n  std::unique_ptr<node<>> head_;\n\n public:\n  template <typename T, typename Arg> auto push(const Arg& arg) -> const T& {\n    auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));\n    auto& value = new_node->value;\n    new_node->next = std::move(head_);\n    head_ = std::move(new_node);\n    return value;\n  }\n};\n}  // namespace detail\n\n/**\n * A dynamic list of formatting arguments with storage.\n *\n * It can be implicitly converted into `fmt::basic_format_args` for passing\n * into type-erased formatting functions such as `fmt::vformat`.\n */\ntemplate <typename Context> class dynamic_format_arg_store {\n private:\n  using char_type = typename Context::char_type;\n\n  template <typename T> struct need_copy {\n    static constexpr detail::type mapped_type =\n        detail::mapped_type_constant<T, char_type>::value;\n\n    enum {\n      value = !(detail::is_reference_wrapper<T>::value ||\n                std::is_same<T, basic_string_view<char_type>>::value ||\n                std::is_same<T, detail::std_string_view<char_type>>::value ||\n                (mapped_type != detail::type::cstring_type &&\n                 mapped_type != detail::type::string_type &&\n                 mapped_type != detail::type::custom_type))\n    };\n  };\n\n  template <typename T>\n  using stored_t = conditional_t<\n      std::is_convertible<T, std::basic_string<char_type>>::value &&\n          !detail::is_reference_wrapper<T>::value,\n      std::basic_string<char_type>, T>;\n\n  // Storage of basic_format_arg must be contiguous.\n  std::vector<basic_format_arg<Context>> data_;\n  std::vector<detail::named_arg_info<char_type>> named_info_;\n\n  // Storage of arguments not fitting into basic_format_arg must grow\n  // without relocation because items in data_ refer to it.\n  detail::dynamic_arg_list dynamic_args_;\n\n  friend class basic_format_args<Context>;\n\n  auto data() const -> const basic_format_arg<Context>* {\n    return named_info_.empty() ? data_.data() : data_.data() + 1;\n  }\n\n  template <typename T> void emplace_arg(const T& arg) {\n    data_.emplace_back(arg);\n  }\n\n  template <typename T>\n  void emplace_arg(const detail::named_arg<char_type, T>& arg) {\n    if (named_info_.empty())\n      data_.insert(data_.begin(), basic_format_arg<Context>(nullptr, 0));\n    data_.emplace_back(detail::unwrap(arg.value));\n    auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {\n      data->pop_back();\n    };\n    std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>\n        guard{&data_, pop_one};\n    named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});\n    data_[0] = {named_info_.data(), named_info_.size()};\n    guard.release();\n  }\n\n public:\n  constexpr dynamic_format_arg_store() = default;\n\n  operator basic_format_args<Context>() const {\n    return basic_format_args<Context>(data(), static_cast<int>(data_.size()),\n                                      !named_info_.empty());\n  }\n\n  /**\n   * Adds an argument into the dynamic store for later passing to a formatting\n   * function.\n   *\n   * Note that custom types and string types (but not string views) are copied\n   * into the store dynamically allocating memory if necessary.\n   *\n   * **Example**:\n   *\n   *     fmt::dynamic_format_arg_store<fmt::format_context> store;\n   *     store.push_back(42);\n   *     store.push_back(\"abc\");\n   *     store.push_back(1.5f);\n   *     std::string result = fmt::vformat(\"{} and {} and {}\", store);\n   */\n  template <typename T> void push_back(const T& arg) {\n    if (detail::const_check(need_copy<T>::value))\n      emplace_arg(dynamic_args_.push<stored_t<T>>(arg));\n    else\n      emplace_arg(detail::unwrap(arg));\n  }\n\n  /**\n   * Adds a reference to the argument into the dynamic store for later passing\n   * to a formatting function.\n   *\n   * **Example**:\n   *\n   *     fmt::dynamic_format_arg_store<fmt::format_context> store;\n   *     char band[] = \"Rolling Stones\";\n   *     store.push_back(std::cref(band));\n   *     band[9] = 'c'; // Changing str affects the output.\n   *     std::string result = fmt::vformat(\"{}\", store);\n   *     // result == \"Rolling Scones\"\n   */\n  template <typename T> void push_back(std::reference_wrapper<T> arg) {\n    static_assert(\n        need_copy<T>::value,\n        \"objects of built-in types and string views are always copied\");\n    emplace_arg(arg.get());\n  }\n\n  /**\n   * Adds named argument into the dynamic store for later passing to a\n   * formatting function. `std::reference_wrapper` is supported to avoid\n   * copying of the argument. The name is always copied into the store.\n   */\n  template <typename T>\n  void push_back(const detail::named_arg<char_type, T>& arg) {\n    const char_type* arg_name =\n        dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();\n    if (detail::const_check(need_copy<T>::value)) {\n      emplace_arg(\n          fmt::arg(arg_name, dynamic_args_.push<stored_t<T>>(arg.value)));\n    } else {\n      emplace_arg(fmt::arg(arg_name, arg.value));\n    }\n  }\n\n  /// Erase all elements from the store.\n  void clear() {\n    data_.clear();\n    named_info_.clear();\n    dynamic_args_ = {};\n  }\n\n  /// Reserves space to store at least `new_cap` arguments including\n  /// `new_cap_named` named arguments.\n  void reserve(size_t new_cap, size_t new_cap_named) {\n    FMT_ASSERT(new_cap >= new_cap_named,\n               \"set of arguments includes set of named arguments\");\n    data_.reserve(new_cap);\n    named_info_.reserve(new_cap_named);\n  }\n\n  /// Returns the number of elements in the store.\n  size_t size() const noexcept { return data_.size(); }\n};\n\nFMT_END_NAMESPACE\n\n#endif  // FMT_ARGS_H_\n"
  },
  {
    "path": "dependencies/fmt/fmt/include/fmt/base.h",
    "content": "// Formatting library for C++ - the base API for char/UTF-8\n//\n// Copyright (c) 2012 - present, Victor Zverovich\n// All rights reserved.\n//\n// For the license information refer to format.h.\n\n#ifndef FMT_BASE_H_\n#define FMT_BASE_H_\n\n#if defined(FMT_IMPORT_STD) && !defined(FMT_MODULE)\n#  define FMT_MODULE\n#endif\n\n#ifndef FMT_MODULE\n#  include <limits.h>  // CHAR_BIT\n#  include <stdio.h>   // FILE\n#  include <string.h>  // memcmp\n\n#  include <type_traits>  // std::enable_if\n#endif\n\n// The fmt library version in the form major * 10000 + minor * 100 + patch.\n#define FMT_VERSION 110103\n\n// Detect compiler versions.\n#if defined(__clang__) && !defined(__ibmxl__)\n#  define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)\n#else\n#  define FMT_CLANG_VERSION 0\n#endif\n#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)\n#  define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)\n#else\n#  define FMT_GCC_VERSION 0\n#endif\n#if defined(__ICL)\n#  define FMT_ICC_VERSION __ICL\n#elif defined(__INTEL_COMPILER)\n#  define FMT_ICC_VERSION __INTEL_COMPILER\n#else\n#  define FMT_ICC_VERSION 0\n#endif\n#if defined(_MSC_VER)\n#  define FMT_MSC_VERSION _MSC_VER\n#else\n#  define FMT_MSC_VERSION 0\n#endif\n\n// Detect standard library versions.\n#ifdef _GLIBCXX_RELEASE\n#  define FMT_GLIBCXX_RELEASE _GLIBCXX_RELEASE\n#else\n#  define FMT_GLIBCXX_RELEASE 0\n#endif\n#ifdef _LIBCPP_VERSION\n#  define FMT_LIBCPP_VERSION _LIBCPP_VERSION\n#else\n#  define FMT_LIBCPP_VERSION 0\n#endif\n\n#ifdef _MSVC_LANG\n#  define FMT_CPLUSPLUS _MSVC_LANG\n#else\n#  define FMT_CPLUSPLUS __cplusplus\n#endif\n\n// Detect __has_*.\n#ifdef __has_feature\n#  define FMT_HAS_FEATURE(x) __has_feature(x)\n#else\n#  define FMT_HAS_FEATURE(x) 0\n#endif\n#ifdef __has_include\n#  define FMT_HAS_INCLUDE(x) __has_include(x)\n#else\n#  define FMT_HAS_INCLUDE(x) 0\n#endif\n#ifdef __has_builtin\n#  define FMT_HAS_BUILTIN(x) __has_builtin(x)\n#else\n#  define FMT_HAS_BUILTIN(x) 0\n#endif\n#ifdef __has_cpp_attribute\n#  define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x)\n#else\n#  define FMT_HAS_CPP_ATTRIBUTE(x) 0\n#endif\n\n#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \\\n  (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute))\n\n#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \\\n  (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute))\n\n// Detect C++14 relaxed constexpr.\n#ifdef FMT_USE_CONSTEXPR\n// Use the provided definition.\n#elif FMT_GCC_VERSION >= 702 && FMT_CPLUSPLUS >= 201402L\n// GCC only allows constexpr member functions in non-literal types since 7.2:\n// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66297.\n#  define FMT_USE_CONSTEXPR 1\n#elif FMT_ICC_VERSION\n#  define FMT_USE_CONSTEXPR 0  // https://github.com/fmtlib/fmt/issues/1628\n#elif FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VERSION >= 1912\n#  define FMT_USE_CONSTEXPR 1\n#else\n#  define FMT_USE_CONSTEXPR 0\n#endif\n#if FMT_USE_CONSTEXPR\n#  define FMT_CONSTEXPR constexpr\n#else\n#  define FMT_CONSTEXPR\n#endif\n\n// Detect consteval, C++20 constexpr extensions and std::is_constant_evaluated.\n#if !defined(__cpp_lib_is_constant_evaluated)\n#  define FMT_USE_CONSTEVAL 0\n#elif FMT_CPLUSPLUS < 201709L\n#  define FMT_USE_CONSTEVAL 0\n#elif FMT_GLIBCXX_RELEASE && FMT_GLIBCXX_RELEASE < 10\n#  define FMT_USE_CONSTEVAL 0\n#elif FMT_LIBCPP_VERSION && FMT_LIBCPP_VERSION < 10000\n#  define FMT_USE_CONSTEVAL 0\n#elif defined(__apple_build_version__) && __apple_build_version__ < 14000029L\n#  define FMT_USE_CONSTEVAL 0  // consteval is broken in Apple clang < 14.\n#elif FMT_MSC_VERSION && FMT_MSC_VERSION < 1929\n#  define FMT_USE_CONSTEVAL 0  // consteval is broken in MSVC VS2019 < 16.10.\n#elif defined(__cpp_consteval)\n#  define FMT_USE_CONSTEVAL 1\n#elif FMT_GCC_VERSION >= 1002 || FMT_CLANG_VERSION >= 1101\n#  define FMT_USE_CONSTEVAL 1\n#else\n#  define FMT_USE_CONSTEVAL 0\n#endif\n#if FMT_USE_CONSTEVAL\n#  define FMT_CONSTEVAL consteval\n#  define FMT_CONSTEXPR20 constexpr\n#else\n#  define FMT_CONSTEVAL\n#  define FMT_CONSTEXPR20\n#endif\n\n// Check if exceptions are disabled.\n#ifdef FMT_USE_EXCEPTIONS\n// Use the provided definition.\n#elif defined(__GNUC__) && !defined(__EXCEPTIONS)\n#  define FMT_USE_EXCEPTIONS 0\n#elif defined(__clang__) && !defined(__cpp_exceptions)\n#  define FMT_USE_EXCEPTIONS 0\n#elif FMT_MSC_VERSION && !_HAS_EXCEPTIONS\n#  define FMT_USE_EXCEPTIONS 0\n#else\n#  define FMT_USE_EXCEPTIONS 1\n#endif\n#if FMT_USE_EXCEPTIONS\n#  define FMT_TRY try\n#  define FMT_CATCH(x) catch (x)\n#else\n#  define FMT_TRY if (true)\n#  define FMT_CATCH(x) if (false)\n#endif\n\n#ifdef FMT_NO_UNIQUE_ADDRESS\n// Use the provided definition.\n#elif FMT_CPLUSPLUS < 202002L\n// Not supported.\n#elif FMT_HAS_CPP_ATTRIBUTE(no_unique_address)\n#  define FMT_NO_UNIQUE_ADDRESS [[no_unique_address]]\n// VS2019 v16.10 and later except clang-cl (https://reviews.llvm.org/D110485).\n#elif FMT_MSC_VERSION >= 1929 && !FMT_CLANG_VERSION\n#  define FMT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]\n#endif\n#ifndef FMT_NO_UNIQUE_ADDRESS\n#  define FMT_NO_UNIQUE_ADDRESS\n#endif\n\n#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough)\n#  define FMT_FALLTHROUGH [[fallthrough]]\n#elif defined(__clang__)\n#  define FMT_FALLTHROUGH [[clang::fallthrough]]\n#elif FMT_GCC_VERSION >= 700 && \\\n    (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520)\n#  define FMT_FALLTHROUGH [[gnu::fallthrough]]\n#else\n#  define FMT_FALLTHROUGH\n#endif\n\n// Disable [[noreturn]] on MSVC/NVCC because of bogus unreachable code warnings.\n#if FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && !defined(__NVCC__)\n#  define FMT_NORETURN [[noreturn]]\n#else\n#  define FMT_NORETURN\n#endif\n\n#ifdef FMT_NODISCARD\n// Use the provided definition.\n#elif FMT_HAS_CPP17_ATTRIBUTE(nodiscard)\n#  define FMT_NODISCARD [[nodiscard]]\n#else\n#  define FMT_NODISCARD\n#endif\n\n#ifdef FMT_DEPRECATED\n// Use the provided definition.\n#elif FMT_HAS_CPP14_ATTRIBUTE(deprecated)\n#  define FMT_DEPRECATED [[deprecated]]\n#else\n#  define FMT_DEPRECATED /* deprecated */\n#endif\n\n#ifdef FMT_ALWAYS_INLINE\n// Use the provided definition.\n#elif FMT_GCC_VERSION || FMT_CLANG_VERSION\n#  define FMT_ALWAYS_INLINE inline __attribute__((always_inline))\n#else\n#  define FMT_ALWAYS_INLINE inline\n#endif\n// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode.\n#ifdef NDEBUG\n#  define FMT_INLINE FMT_ALWAYS_INLINE\n#else\n#  define FMT_INLINE inline\n#endif\n\n#if FMT_GCC_VERSION || FMT_CLANG_VERSION\n#  define FMT_VISIBILITY(value) __attribute__((visibility(value)))\n#else\n#  define FMT_VISIBILITY(value)\n#endif\n\n// Detect pragmas.\n#define FMT_PRAGMA_IMPL(x) _Pragma(#x)\n#if FMT_GCC_VERSION >= 504 && !defined(__NVCOMPILER)\n// Workaround a _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884\n// and an nvhpc warning: https://github.com/fmtlib/fmt/pull/2582.\n#  define FMT_PRAGMA_GCC(x) FMT_PRAGMA_IMPL(GCC x)\n#else\n#  define FMT_PRAGMA_GCC(x)\n#endif\n#if FMT_CLANG_VERSION\n#  define FMT_PRAGMA_CLANG(x) FMT_PRAGMA_IMPL(clang x)\n#else\n#  define FMT_PRAGMA_CLANG(x)\n#endif\n#if FMT_MSC_VERSION\n#  define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__))\n#else\n#  define FMT_MSC_WARNING(...)\n#endif\n\n#ifndef FMT_BEGIN_NAMESPACE\n#  define FMT_BEGIN_NAMESPACE \\\n    namespace fmt {           \\\n    inline namespace v11 {\n#  define FMT_END_NAMESPACE \\\n    }                       \\\n    }\n#endif\n\n#ifndef FMT_EXPORT\n#  define FMT_EXPORT\n#  define FMT_BEGIN_EXPORT\n#  define FMT_END_EXPORT\n#endif\n\n#ifdef _WIN32\n#  define FMT_WIN32 1\n#else\n#  define FMT_WIN32 0\n#endif\n\n#if !defined(FMT_HEADER_ONLY) && FMT_WIN32\n#  if defined(FMT_LIB_EXPORT)\n#    define FMT_API __declspec(dllexport)\n#  elif defined(FMT_SHARED)\n#    define FMT_API __declspec(dllimport)\n#  endif\n#elif defined(FMT_LIB_EXPORT) || defined(FMT_SHARED)\n#  define FMT_API FMT_VISIBILITY(\"default\")\n#endif\n#ifndef FMT_API\n#  define FMT_API\n#endif\n\n#ifndef FMT_OPTIMIZE_SIZE\n#  define FMT_OPTIMIZE_SIZE 0\n#endif\n\n// FMT_BUILTIN_TYPE=0 may result in smaller library size at the cost of higher\n// per-call binary size by passing built-in types through the extension API.\n#ifndef FMT_BUILTIN_TYPES\n#  define FMT_BUILTIN_TYPES 1\n#endif\n\n#define FMT_APPLY_VARIADIC(expr) \\\n  using ignore = int[];          \\\n  (void)ignore { 0, (expr, 0)... }\n\n// Enable minimal optimizations for more compact code in debug mode.\nFMT_PRAGMA_GCC(push_options)\n#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE)\nFMT_PRAGMA_GCC(optimize(\"Og\"))\n#endif\nFMT_PRAGMA_CLANG(diagnostic push)\n\nFMT_BEGIN_NAMESPACE\n\n// Implementations of enable_if_t and other metafunctions for older systems.\ntemplate <bool B, typename T = void>\nusing enable_if_t = typename std::enable_if<B, T>::type;\ntemplate <bool B, typename T, typename F>\nusing conditional_t = typename std::conditional<B, T, F>::type;\ntemplate <bool B> using bool_constant = std::integral_constant<bool, B>;\ntemplate <typename T>\nusing remove_reference_t = typename std::remove_reference<T>::type;\ntemplate <typename T>\nusing remove_const_t = typename std::remove_const<T>::type;\ntemplate <typename T>\nusing remove_cvref_t = typename std::remove_cv<remove_reference_t<T>>::type;\ntemplate <typename T>\nusing make_unsigned_t = typename std::make_unsigned<T>::type;\ntemplate <typename T>\nusing underlying_t = typename std::underlying_type<T>::type;\ntemplate <typename T> using decay_t = typename std::decay<T>::type;\nusing nullptr_t = decltype(nullptr);\n\n#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500\n// A workaround for gcc 4.9 to make void_t work in a SFINAE context.\ntemplate <typename...> struct void_t_impl {\n  using type = void;\n};\ntemplate <typename... T> using void_t = typename void_t_impl<T...>::type;\n#else\ntemplate <typename...> using void_t = void;\n#endif\n\nstruct monostate {\n  constexpr monostate() {}\n};\n\n// An enable_if helper to be used in template parameters which results in much\n// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed\n// to workaround a bug in MSVC 2019 (see #1140 and #1186).\n#ifdef FMT_DOC\n#  define FMT_ENABLE_IF(...)\n#else\n#  define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0\n#endif\n\ntemplate <typename T> constexpr auto min_of(T a, T b) -> T {\n  return a < b ? a : b;\n}\ntemplate <typename T> constexpr auto max_of(T a, T b) -> T {\n  return a > b ? a : b;\n}\n\nnamespace detail {\n// Suppresses \"unused variable\" warnings with the method described in\n// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/.\n// (void)var does not work on many Intel compilers.\ntemplate <typename... T> FMT_CONSTEXPR void ignore_unused(const T&...) {}\n\nconstexpr auto is_constant_evaluated(bool default_value = false) noexcept\n    -> bool {\n// Workaround for incompatibility between clang 14 and libstdc++ consteval-based\n// std::is_constant_evaluated: https://github.com/fmtlib/fmt/issues/3247.\n#if FMT_CPLUSPLUS >= 202002L && FMT_GLIBCXX_RELEASE >= 12 && \\\n    (FMT_CLANG_VERSION >= 1400 && FMT_CLANG_VERSION < 1500)\n  ignore_unused(default_value);\n  return __builtin_is_constant_evaluated();\n#elif defined(__cpp_lib_is_constant_evaluated)\n  ignore_unused(default_value);\n  return std::is_constant_evaluated();\n#else\n  return default_value;\n#endif\n}\n\n// Suppresses \"conditional expression is constant\" warnings.\ntemplate <typename T> FMT_ALWAYS_INLINE constexpr auto const_check(T val) -> T {\n  return val;\n}\n\nFMT_NORETURN FMT_API void assert_fail(const char* file, int line,\n                                      const char* message);\n\n#if defined(FMT_ASSERT)\n// Use the provided definition.\n#elif defined(NDEBUG)\n// FMT_ASSERT is not empty to avoid -Wempty-body.\n#  define FMT_ASSERT(condition, message) \\\n    fmt::detail::ignore_unused((condition), (message))\n#else\n#  define FMT_ASSERT(condition, message)                                    \\\n    ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \\\n         ? (void)0                                                          \\\n         : fmt::detail::assert_fail(__FILE__, __LINE__, (message)))\n#endif\n\n#ifdef FMT_USE_INT128\n// Use the provided definition.\n#elif defined(__SIZEOF_INT128__) && !defined(__NVCC__) && \\\n    !(FMT_CLANG_VERSION && FMT_MSC_VERSION)\n#  define FMT_USE_INT128 1\nusing int128_opt = __int128_t;  // An optional native 128-bit integer.\nusing uint128_opt = __uint128_t;\ninline auto map(int128_opt x) -> int128_opt { return x; }\ninline auto map(uint128_opt x) -> uint128_opt { return x; }\n#else\n#  define FMT_USE_INT128 0\n#endif\n#if !FMT_USE_INT128\nenum class int128_opt {};\nenum class uint128_opt {};\n// Reduce template instantiations.\ninline auto map(int128_opt) -> monostate { return {}; }\ninline auto map(uint128_opt) -> monostate { return {}; }\n#endif\n\n#ifndef FMT_USE_BITINT\n#  define FMT_USE_BITINT (FMT_CLANG_VERSION >= 1500)\n#endif\n\n#if FMT_USE_BITINT\nFMT_PRAGMA_CLANG(diagnostic ignored \"-Wbit-int-extension\")\ntemplate <int N> using bitint = _BitInt(N);\ntemplate <int N> using ubitint = unsigned _BitInt(N);\n#else\ntemplate <int N> struct bitint {};\ntemplate <int N> struct ubitint {};\n#endif  // FMT_USE_BITINT\n\n// Casts a nonnegative integer to unsigned.\ntemplate <typename Int>\nFMT_CONSTEXPR auto to_unsigned(Int value) -> make_unsigned_t<Int> {\n  FMT_ASSERT(std::is_unsigned<Int>::value || value >= 0, \"negative value\");\n  return static_cast<make_unsigned_t<Int>>(value);\n}\n\ntemplate <typename Char>\nusing unsigned_char = conditional_t<sizeof(Char) == 1, unsigned char, unsigned>;\n\n// A heuristic to detect std::string and std::[experimental::]string_view.\n// It is mainly used to avoid dependency on <[experimental/]string_view>.\ntemplate <typename T, typename Enable = void>\nstruct is_std_string_like : std::false_type {};\ntemplate <typename T>\nstruct is_std_string_like<T, void_t<decltype(std::declval<T>().find_first_of(\n                                 typename T::value_type(), 0))>>\n    : std::is_convertible<decltype(std::declval<T>().data()),\n                          const typename T::value_type*> {};\n\n// Check if the literal encoding is UTF-8.\nenum { is_utf8_enabled = \"\\u00A7\"[1] == '\\xA7' };\nenum { use_utf8 = !FMT_WIN32 || is_utf8_enabled };\n\n#ifndef FMT_UNICODE\n#  define FMT_UNICODE 1\n#endif\n\nstatic_assert(!FMT_UNICODE || use_utf8,\n              \"Unicode support requires compiling with /utf-8\");\n\ntemplate <typename T> constexpr const char* narrow(const T*) { return nullptr; }\nconstexpr FMT_ALWAYS_INLINE const char* narrow(const char* s) { return s; }\n\ntemplate <typename Char>\nFMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, std::size_t n)\n    -> int {\n  if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n);\n  for (; n != 0; ++s1, ++s2, --n) {\n    if (*s1 < *s2) return -1;\n    if (*s1 > *s2) return 1;\n  }\n  return 0;\n}\n\nnamespace adl {\nusing namespace std;\n\ntemplate <typename Container>\nauto invoke_back_inserter()\n    -> decltype(back_inserter(std::declval<Container&>()));\n}  // namespace adl\n\ntemplate <typename It, typename Enable = std::true_type>\nstruct is_back_insert_iterator : std::false_type {};\n\ntemplate <typename It>\nstruct is_back_insert_iterator<\n    It, bool_constant<std::is_same<\n            decltype(adl::invoke_back_inserter<typename It::container_type>()),\n            It>::value>> : std::true_type {};\n\n// Extracts a reference to the container from *insert_iterator.\ntemplate <typename OutputIt>\ninline FMT_CONSTEXPR20 auto get_container(OutputIt it) ->\n    typename OutputIt::container_type& {\n  struct accessor : OutputIt {\n    FMT_CONSTEXPR20 accessor(OutputIt base) : OutputIt(base) {}\n    using OutputIt::container;\n  };\n  return *accessor(it).container;\n}\n}  // namespace detail\n\n// Parsing-related public API and forward declarations.\nFMT_BEGIN_EXPORT\n\n/**\n * An implementation of `std::basic_string_view` for pre-C++17. It provides a\n * subset of the API. `fmt::basic_string_view` is used for format strings even\n * if `std::basic_string_view` is available to prevent issues when a library is\n * compiled with a different `-std` option than the client code (which is not\n * recommended).\n */\ntemplate <typename Char> class basic_string_view {\n private:\n  const Char* data_;\n  size_t size_;\n\n public:\n  using value_type = Char;\n  using iterator = const Char*;\n\n  constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {}\n\n  /// Constructs a string reference object from a C string and a size.\n  constexpr basic_string_view(const Char* s, size_t count) noexcept\n      : data_(s), size_(count) {}\n\n  constexpr basic_string_view(nullptr_t) = delete;\n\n  /// Constructs a string reference object from a C string.\n#if FMT_GCC_VERSION\n  FMT_ALWAYS_INLINE\n#endif\n  FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) {\n#if FMT_HAS_BUILTIN(__buitin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION\n    if (std::is_same<Char, char>::value) {\n      size_ = __builtin_strlen(detail::narrow(s));\n      return;\n    }\n#endif\n    size_t len = 0;\n    while (*s++) ++len;\n    size_ = len;\n  }\n\n  /// Constructs a string reference from a `std::basic_string` or a\n  /// `std::basic_string_view` object.\n  template <typename S,\n            FMT_ENABLE_IF(detail::is_std_string_like<S>::value&& std::is_same<\n                          typename S::value_type, Char>::value)>\n  FMT_CONSTEXPR basic_string_view(const S& s) noexcept\n      : data_(s.data()), size_(s.size()) {}\n\n  /// Returns a pointer to the string data.\n  constexpr auto data() const noexcept -> const Char* { return data_; }\n\n  /// Returns the string size.\n  constexpr auto size() const noexcept -> size_t { return size_; }\n\n  constexpr auto begin() const noexcept -> iterator { return data_; }\n  constexpr auto end() const noexcept -> iterator { return data_ + size_; }\n\n  constexpr auto operator[](size_t pos) const noexcept -> const Char& {\n    return data_[pos];\n  }\n\n  FMT_CONSTEXPR void remove_prefix(size_t n) noexcept {\n    data_ += n;\n    size_ -= n;\n  }\n\n  FMT_CONSTEXPR auto starts_with(basic_string_view<Char> sv) const noexcept\n      -> bool {\n    return size_ >= sv.size_ && detail::compare(data_, sv.data_, sv.size_) == 0;\n  }\n  FMT_CONSTEXPR auto starts_with(Char c) const noexcept -> bool {\n    return size_ >= 1 && *data_ == c;\n  }\n  FMT_CONSTEXPR auto starts_with(const Char* s) const -> bool {\n    return starts_with(basic_string_view<Char>(s));\n  }\n\n  // Lexicographically compare this string reference to other.\n  FMT_CONSTEXPR auto compare(basic_string_view other) const -> int {\n    int result =\n        detail::compare(data_, other.data_, min_of(size_, other.size_));\n    if (result != 0) return result;\n    return size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1);\n  }\n\n  FMT_CONSTEXPR friend auto operator==(basic_string_view lhs,\n                                       basic_string_view rhs) -> bool {\n    return lhs.compare(rhs) == 0;\n  }\n  friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool {\n    return lhs.compare(rhs) != 0;\n  }\n  friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool {\n    return lhs.compare(rhs) < 0;\n  }\n  friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool {\n    return lhs.compare(rhs) <= 0;\n  }\n  friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool {\n    return lhs.compare(rhs) > 0;\n  }\n  friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool {\n    return lhs.compare(rhs) >= 0;\n  }\n};\n\nusing string_view = basic_string_view<char>;\n\n/// Specifies if `T` is an extended character type. Can be specialized by users.\ntemplate <typename T> struct is_xchar : std::false_type {};\ntemplate <> struct is_xchar<wchar_t> : std::true_type {};\ntemplate <> struct is_xchar<char16_t> : std::true_type {};\ntemplate <> struct is_xchar<char32_t> : std::true_type {};\n#ifdef __cpp_char8_t\ntemplate <> struct is_xchar<char8_t> : std::true_type {};\n#endif\n\n// DEPRECATED! Will be replaced with an alias to prevent specializations.\ntemplate <typename T> struct is_char : is_xchar<T> {};\ntemplate <> struct is_char<char> : std::true_type {};\n\ntemplate <typename T> class basic_appender;\nusing appender = basic_appender<char>;\n\n// Checks whether T is a container with contiguous storage.\ntemplate <typename T> struct is_contiguous : std::false_type {};\n\nclass context;\ntemplate <typename OutputIt, typename Char> class generic_context;\ntemplate <typename Char> class parse_context;\n\n// Longer aliases for C++20 compatibility.\ntemplate <typename Char> using basic_format_parse_context = parse_context<Char>;\nusing format_parse_context = parse_context<char>;\ntemplate <typename OutputIt, typename Char>\nusing basic_format_context =\n    conditional_t<std::is_same<OutputIt, appender>::value, context,\n                  generic_context<OutputIt, Char>>;\nusing format_context = context;\n\ntemplate <typename Char>\nusing buffered_context =\n    conditional_t<std::is_same<Char, char>::value, context,\n                  generic_context<basic_appender<Char>, Char>>;\n\ntemplate <typename Context> class basic_format_arg;\ntemplate <typename Context> class basic_format_args;\n\n// A separate type would result in shorter symbols but break ABI compatibility\n// between clang and gcc on ARM (#1919).\nusing format_args = basic_format_args<context>;\n\n// A formatter for objects of type T.\ntemplate <typename T, typename Char = char, typename Enable = void>\nstruct formatter {\n  // A deleted default constructor indicates a disabled formatter.\n  formatter() = delete;\n};\n\n/// Reports a format error at compile time or, via a `format_error` exception,\n/// at runtime.\n// This function is intentionally not constexpr to give a compile-time error.\nFMT_NORETURN FMT_API void report_error(const char* message);\n\nenum class presentation_type : unsigned char {\n  // Common specifiers:\n  none = 0,\n  debug = 1,   // '?'\n  string = 2,  // 's' (string, bool)\n\n  // Integral, bool and character specifiers:\n  dec = 3,  // 'd'\n  hex,      // 'x' or 'X'\n  oct,      // 'o'\n  bin,      // 'b' or 'B'\n  chr,      // 'c'\n\n  // String and pointer specifiers:\n  pointer = 3,  // 'p'\n\n  // Floating-point specifiers:\n  exp = 1,  // 'e' or 'E' (1 since there is no FP debug presentation)\n  fixed,    // 'f' or 'F'\n  general,  // 'g' or 'G'\n  hexfloat  // 'a' or 'A'\n};\n\nenum class align { none, left, right, center, numeric };\nenum class sign { none, minus, plus, space };\nenum class arg_id_kind { none, index, name };\n\n// Basic format specifiers for built-in and string types.\nclass basic_specs {\n private:\n  // Data is arranged as follows:\n  //\n  //  0                   1                   2                   3\n  //  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1\n  // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n  // |type |align| w | p | s |u|#|L|  f  |          unused           |\n  // +-----+-----+---+---+---+-+-+-+-----+---------------------------+\n  //\n  //   w - dynamic width info\n  //   p - dynamic precision info\n  //   s - sign\n  //   u - uppercase (e.g. 'X' for 'x')\n  //   # - alternate form ('#')\n  //   L - localized\n  //   f - fill size\n  //\n  // Bitfields are not used because of compiler bugs such as gcc bug 61414.\n  enum : unsigned {\n    type_mask = 0x00007,\n    align_mask = 0x00038,\n    width_mask = 0x000C0,\n    precision_mask = 0x00300,\n    sign_mask = 0x00C00,\n    uppercase_mask = 0x01000,\n    alternate_mask = 0x02000,\n    localized_mask = 0x04000,\n    fill_size_mask = 0x38000,\n\n    align_shift = 3,\n    width_shift = 6,\n    precision_shift = 8,\n    sign_shift = 10,\n    fill_size_shift = 15,\n\n    max_fill_size = 4\n  };\n\n  unsigned data_ = 1 << fill_size_shift;\n  static_assert(sizeof(data_) * CHAR_BIT >= 18, \"\");\n\n  // Character (code unit) type is erased to prevent template bloat.\n  char fill_data_[max_fill_size] = {' '};\n\n  FMT_CONSTEXPR void set_fill_size(size_t size) {\n    data_ = (data_ & ~fill_size_mask) |\n            (static_cast<unsigned>(size) << fill_size_shift);\n  }\n\n public:\n  constexpr auto type() const -> presentation_type {\n    return static_cast<presentation_type>(data_ & type_mask);\n  }\n  FMT_CONSTEXPR void set_type(presentation_type t) {\n    data_ = (data_ & ~type_mask) | static_cast<unsigned>(t);\n  }\n\n  constexpr auto align() const -> align {\n    return static_cast<fmt::align>((data_ & align_mask) >> align_shift);\n  }\n  FMT_CONSTEXPR void set_align(fmt::align a) {\n    data_ = (data_ & ~align_mask) | (static_cast<unsigned>(a) << align_shift);\n  }\n\n  constexpr auto dynamic_width() const -> arg_id_kind {\n    return static_cast<arg_id_kind>((data_ & width_mask) >> width_shift);\n  }\n  FMT_CONSTEXPR void set_dynamic_width(arg_id_kind w) {\n    data_ = (data_ & ~width_mask) | (static_cast<unsigned>(w) << width_shift);\n  }\n\n  FMT_CONSTEXPR auto dynamic_precision() const -> arg_id_kind {\n    return static_cast<arg_id_kind>((data_ & precision_mask) >>\n                                    precision_shift);\n  }\n  FMT_CONSTEXPR void set_dynamic_precision(arg_id_kind p) {\n    data_ = (data_ & ~precision_mask) |\n            (static_cast<unsigned>(p) << precision_shift);\n  }\n\n  constexpr bool dynamic() const {\n    return (data_ & (width_mask | precision_mask)) != 0;\n  }\n\n  constexpr auto sign() const -> sign {\n    return static_cast<fmt::sign>((data_ & sign_mask) >> sign_shift);\n  }\n  FMT_CONSTEXPR void set_sign(fmt::sign s) {\n    data_ = (data_ & ~sign_mask) | (static_cast<unsigned>(s) << sign_shift);\n  }\n\n  constexpr auto upper() const -> bool { return (data_ & uppercase_mask) != 0; }\n  FMT_CONSTEXPR void set_upper() { data_ |= uppercase_mask; }\n\n  constexpr auto alt() const -> bool { return (data_ & alternate_mask) != 0; }\n  FMT_CONSTEXPR void set_alt() { data_ |= alternate_mask; }\n  FMT_CONSTEXPR void clear_alt() { data_ &= ~alternate_mask; }\n\n  constexpr auto localized() const -> bool {\n    return (data_ & localized_mask) != 0;\n  }\n  FMT_CONSTEXPR void set_localized() { data_ |= localized_mask; }\n\n  constexpr auto fill_size() const -> size_t {\n    return (data_ & fill_size_mask) >> fill_size_shift;\n  }\n\n  template <typename Char, FMT_ENABLE_IF(std::is_same<Char, char>::value)>\n  constexpr auto fill() const -> const Char* {\n    return fill_data_;\n  }\n  template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>\n  constexpr auto fill() const -> const Char* {\n    return nullptr;\n  }\n\n  template <typename Char> constexpr auto fill_unit() const -> Char {\n    using uchar = unsigned char;\n    return static_cast<Char>(static_cast<uchar>(fill_data_[0]) |\n                             (static_cast<uchar>(fill_data_[1]) << 8) |\n                             (static_cast<uchar>(fill_data_[2]) << 16));\n  }\n\n  FMT_CONSTEXPR void set_fill(char c) {\n    fill_data_[0] = c;\n    set_fill_size(1);\n  }\n\n  template <typename Char>\n  FMT_CONSTEXPR void set_fill(basic_string_view<Char> s) {\n    auto size = s.size();\n    set_fill_size(size);\n    if (size == 1) {\n      unsigned uchar = static_cast<detail::unsigned_char<Char>>(s[0]);\n      fill_data_[0] = static_cast<char>(uchar);\n      fill_data_[1] = static_cast<char>(uchar >> 8);\n      fill_data_[2] = static_cast<char>(uchar >> 16);\n      return;\n    }\n    FMT_ASSERT(size <= max_fill_size, \"invalid fill\");\n    for (size_t i = 0; i < size; ++i)\n      fill_data_[i & 3] = static_cast<char>(s[i]);\n  }\n\n  FMT_CONSTEXPR void copy_fill_from(const basic_specs& specs) {\n    set_fill_size(specs.fill_size());\n    for (size_t i = 0; i < max_fill_size; ++i)\n      fill_data_[i] = specs.fill_data_[i];\n  }\n};\n\n// Format specifiers for built-in and string types.\nstruct format_specs : basic_specs {\n  int width;\n  int precision;\n\n  constexpr format_specs() : width(0), precision(-1) {}\n};\n\n/**\n * Parsing context consisting of a format string range being parsed and an\n * argument counter for automatic indexing.\n */\ntemplate <typename Char = char> class parse_context {\n private:\n  basic_string_view<Char> fmt_;\n  int next_arg_id_;\n\n  enum { use_constexpr_cast = !FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200 };\n\n  FMT_CONSTEXPR void do_check_arg_id(int arg_id);\n\n public:\n  using char_type = Char;\n  using iterator = const Char*;\n\n  constexpr explicit parse_context(basic_string_view<Char> fmt,\n                                   int next_arg_id = 0)\n      : fmt_(fmt), next_arg_id_(next_arg_id) {}\n\n  /// Returns an iterator to the beginning of the format string range being\n  /// parsed.\n  constexpr auto begin() const noexcept -> iterator { return fmt_.begin(); }\n\n  /// Returns an iterator past the end of the format string range being parsed.\n  constexpr auto end() const noexcept -> iterator { return fmt_.end(); }\n\n  /// Advances the begin iterator to `it`.\n  FMT_CONSTEXPR void advance_to(iterator it) {\n    fmt_.remove_prefix(detail::to_unsigned(it - begin()));\n  }\n\n  /// Reports an error if using the manual argument indexing; otherwise returns\n  /// the next argument index and switches to the automatic indexing.\n  FMT_CONSTEXPR auto next_arg_id() -> int {\n    if (next_arg_id_ < 0) {\n      report_error(\"cannot switch from manual to automatic argument indexing\");\n      return 0;\n    }\n    int id = next_arg_id_++;\n    do_check_arg_id(id);\n    return id;\n  }\n\n  /// Reports an error if using the automatic argument indexing; otherwise\n  /// switches to the manual indexing.\n  FMT_CONSTEXPR void check_arg_id(int id) {\n    if (next_arg_id_ > 0) {\n      report_error(\"cannot switch from automatic to manual argument indexing\");\n      return;\n    }\n    next_arg_id_ = -1;\n    do_check_arg_id(id);\n  }\n  FMT_CONSTEXPR void check_arg_id(basic_string_view<Char>) {\n    next_arg_id_ = -1;\n  }\n  FMT_CONSTEXPR void check_dynamic_spec(int arg_id);\n};\n\nFMT_END_EXPORT\n\nnamespace detail {\n\n// Constructs fmt::basic_string_view<Char> from types implicitly convertible\n// to it, deducing Char. Explicitly convertible types such as the ones returned\n// from FMT_STRING are intentionally excluded.\ntemplate <typename Char, FMT_ENABLE_IF(is_char<Char>::value)>\nconstexpr auto to_string_view(const Char* s) -> basic_string_view<Char> {\n  return s;\n}\ntemplate <typename T, FMT_ENABLE_IF(is_std_string_like<T>::value)>\nconstexpr auto to_string_view(const T& s)\n    -> basic_string_view<typename T::value_type> {\n  return s;\n}\ntemplate <typename Char>\nconstexpr auto to_string_view(basic_string_view<Char> s)\n    -> basic_string_view<Char> {\n  return s;\n}\n\ntemplate <typename T, typename Enable = void>\nstruct has_to_string_view : std::false_type {};\n// detail:: is intentional since to_string_view is not an extension point.\ntemplate <typename T>\nstruct has_to_string_view<\n    T, void_t<decltype(detail::to_string_view(std::declval<T>()))>>\n    : std::true_type {};\n\n/// String's character (code unit) type. detail:: is intentional to prevent ADL.\ntemplate <typename S,\n          typename V = decltype(detail::to_string_view(std::declval<S>()))>\nusing char_t = typename V::value_type;\n\nenum class type {\n  none_type,\n  // Integer types should go first,\n  int_type,\n  uint_type,\n  long_long_type,\n  ulong_long_type,\n  int128_type,\n  uint128_type,\n  bool_type,\n  char_type,\n  last_integer_type = char_type,\n  // followed by floating-point types.\n  float_type,\n  double_type,\n  long_double_type,\n  last_numeric_type = long_double_type,\n  cstring_type,\n  string_type,\n  pointer_type,\n  custom_type\n};\n\n// Maps core type T to the corresponding type enum constant.\ntemplate <typename T, typename Char>\nstruct type_constant : std::integral_constant<type, type::custom_type> {};\n\n#define FMT_TYPE_CONSTANT(Type, constant) \\\n  template <typename Char>                \\\n  struct type_constant<Type, Char>        \\\n      : std::integral_constant<type, type::constant> {}\n\nFMT_TYPE_CONSTANT(int, int_type);\nFMT_TYPE_CONSTANT(unsigned, uint_type);\nFMT_TYPE_CONSTANT(long long, long_long_type);\nFMT_TYPE_CONSTANT(unsigned long long, ulong_long_type);\nFMT_TYPE_CONSTANT(int128_opt, int128_type);\nFMT_TYPE_CONSTANT(uint128_opt, uint128_type);\nFMT_TYPE_CONSTANT(bool, bool_type);\nFMT_TYPE_CONSTANT(Char, char_type);\nFMT_TYPE_CONSTANT(float, float_type);\nFMT_TYPE_CONSTANT(double, double_type);\nFMT_TYPE_CONSTANT(long double, long_double_type);\nFMT_TYPE_CONSTANT(const Char*, cstring_type);\nFMT_TYPE_CONSTANT(basic_string_view<Char>, string_type);\nFMT_TYPE_CONSTANT(const void*, pointer_type);\n\nconstexpr auto is_integral_type(type t) -> bool {\n  return t > type::none_type && t <= type::last_integer_type;\n}\nconstexpr auto is_arithmetic_type(type t) -> bool {\n  return t > type::none_type && t <= type::last_numeric_type;\n}\n\nconstexpr auto set(type rhs) -> int { return 1 << static_cast<int>(rhs); }\nconstexpr auto in(type t, int set) -> bool {\n  return ((set >> static_cast<int>(t)) & 1) != 0;\n}\n\n// Bitsets of types.\nenum {\n  sint_set =\n      set(type::int_type) | set(type::long_long_type) | set(type::int128_type),\n  uint_set = set(type::uint_type) | set(type::ulong_long_type) |\n             set(type::uint128_type),\n  bool_set = set(type::bool_type),\n  char_set = set(type::char_type),\n  float_set = set(type::float_type) | set(type::double_type) |\n              set(type::long_double_type),\n  string_set = set(type::string_type),\n  cstring_set = set(type::cstring_type),\n  pointer_set = set(type::pointer_type)\n};\n\nstruct view {};\n\ntemplate <typename Char, typename T> struct named_arg;\ntemplate <typename T> struct is_named_arg : std::false_type {};\ntemplate <typename T> struct is_static_named_arg : std::false_type {};\n\ntemplate <typename Char, typename T>\nstruct is_named_arg<named_arg<Char, T>> : std::true_type {};\n\ntemplate <typename Char, typename T> struct named_arg : view {\n  const Char* name;\n  const T& value;\n\n  named_arg(const Char* n, const T& v) : name(n), value(v) {}\n  static_assert(!is_named_arg<T>::value, \"nested named arguments\");\n};\n\ntemplate <bool B = false> constexpr auto count() -> int { return B ? 1 : 0; }\ntemplate <bool B1, bool B2, bool... Tail> constexpr auto count() -> int {\n  return (B1 ? 1 : 0) + count<B2, Tail...>();\n}\n\ntemplate <typename... Args> constexpr auto count_named_args() -> int {\n  return count<is_named_arg<Args>::value...>();\n}\ntemplate <typename... Args> constexpr auto count_static_named_args() -> int {\n  return count<is_static_named_arg<Args>::value...>();\n}\n\ntemplate <typename Char> struct named_arg_info {\n  const Char* name;\n  int id;\n};\n\ntemplate <typename Char, typename T, FMT_ENABLE_IF(!is_named_arg<T>::value)>\nvoid init_named_arg(named_arg_info<Char>*, int& arg_index, int&, const T&) {\n  ++arg_index;\n}\ntemplate <typename Char, typename T, FMT_ENABLE_IF(is_named_arg<T>::value)>\nvoid init_named_arg(named_arg_info<Char>* named_args, int& arg_index,\n                    int& named_arg_index, const T& arg) {\n  named_args[named_arg_index++] = {arg.name, arg_index++};\n}\n\ntemplate <typename T, typename Char,\n          FMT_ENABLE_IF(!is_static_named_arg<T>::value)>\nFMT_CONSTEXPR void init_static_named_arg(named_arg_info<Char>*, int& arg_index,\n                                         int&) {\n  ++arg_index;\n}\ntemplate <typename T, typename Char,\n          FMT_ENABLE_IF(is_static_named_arg<T>::value)>\nFMT_CONSTEXPR void init_static_named_arg(named_arg_info<Char>* named_args,\n                                         int& arg_index, int& named_arg_index) {\n  named_args[named_arg_index++] = {T::name, arg_index++};\n}\n\n// To minimize the number of types we need to deal with, long is translated\n// either to int or to long long depending on its size.\nenum { long_short = sizeof(long) == sizeof(int) };\nusing long_type = conditional_t<long_short, int, long long>;\nusing ulong_type = conditional_t<long_short, unsigned, unsigned long long>;\n\ntemplate <typename T>\nusing format_as_result =\n    remove_cvref_t<decltype(format_as(std::declval<const T&>()))>;\ntemplate <typename T>\nusing format_as_member_result =\n    remove_cvref_t<decltype(formatter<T>::format_as(std::declval<const T&>()))>;\n\ntemplate <typename T, typename Enable = std::true_type>\nstruct use_format_as : std::false_type {};\n// format_as member is only used to avoid injection into the std namespace.\ntemplate <typename T, typename Enable = std::true_type>\nstruct use_format_as_member : std::false_type {};\n\n// Only map owning types because mapping views can be unsafe.\ntemplate <typename T>\nstruct use_format_as<\n    T, bool_constant<std::is_arithmetic<format_as_result<T>>::value>>\n    : std::true_type {};\ntemplate <typename T>\nstruct use_format_as_member<\n    T, bool_constant<std::is_arithmetic<format_as_member_result<T>>::value>>\n    : std::true_type {};\n\ntemplate <typename T, typename U = remove_const_t<T>>\nusing use_formatter =\n    bool_constant<(std::is_class<T>::value || std::is_enum<T>::value ||\n                   std::is_union<T>::value || std::is_array<T>::value) &&\n                  !has_to_string_view<T>::value && !is_named_arg<T>::value &&\n                  !use_format_as<T>::value && !use_format_as_member<U>::value>;\n\ntemplate <typename Char, typename T, typename U = remove_const_t<T>>\nauto has_formatter_impl(T* p, buffered_context<Char>* ctx = nullptr)\n    -> decltype(formatter<U, Char>().format(*p, *ctx), std::true_type());\ntemplate <typename Char> auto has_formatter_impl(...) -> std::false_type;\n\n// T can be const-qualified to check if it is const-formattable.\ntemplate <typename T, typename Char> constexpr auto has_formatter() -> bool {\n  return decltype(has_formatter_impl<Char>(static_cast<T*>(nullptr)))::value;\n}\n\n// Maps formatting argument types to natively supported types or user-defined\n// types with formatters. Returns void on errors to be SFINAE-friendly.\ntemplate <typename Char> struct type_mapper {\n  static auto map(signed char) -> int;\n  static auto map(unsigned char) -> unsigned;\n  static auto map(short) -> int;\n  static auto map(unsigned short) -> unsigned;\n  static auto map(int) -> int;\n  static auto map(unsigned) -> unsigned;\n  static auto map(long) -> long_type;\n  static auto map(unsigned long) -> ulong_type;\n  static auto map(long long) -> long long;\n  static auto map(unsigned long long) -> unsigned long long;\n  static auto map(int128_opt) -> int128_opt;\n  static auto map(uint128_opt) -> uint128_opt;\n  static auto map(bool) -> bool;\n\n  template <int N>\n  static auto map(bitint<N>) -> conditional_t<N <= 64, long long, void>;\n  template <int N>\n  static auto map(ubitint<N>)\n      -> conditional_t<N <= 64, unsigned long long, void>;\n\n  template <typename T, FMT_ENABLE_IF(is_char<T>::value)>\n  static auto map(T) -> conditional_t<\n      std::is_same<T, char>::value || std::is_same<T, Char>::value, Char, void>;\n\n  static auto map(float) -> float;\n  static auto map(double) -> double;\n  static auto map(long double) -> long double;\n\n  static auto map(Char*) -> const Char*;\n  static auto map(const Char*) -> const Char*;\n  template <typename T, typename C = char_t<T>,\n            FMT_ENABLE_IF(!std::is_pointer<T>::value)>\n  static auto map(const T&) -> conditional_t<std::is_same<C, Char>::value,\n                                             basic_string_view<C>, void>;\n\n  static auto map(void*) -> const void*;\n  static auto map(const void*) -> const void*;\n  static auto map(volatile void*) -> const void*;\n  static auto map(const volatile void*) -> const void*;\n  static auto map(nullptr_t) -> const void*;\n  template <typename T, FMT_ENABLE_IF(std::is_pointer<T>::value ||\n                                      std::is_member_pointer<T>::value)>\n  static auto map(const T&) -> void;\n\n  template <typename T, FMT_ENABLE_IF(use_format_as<T>::value)>\n  static auto map(const T& x) -> decltype(map(format_as(x)));\n  template <typename T, FMT_ENABLE_IF(use_format_as_member<T>::value)>\n  static auto map(const T& x) -> decltype(map(formatter<T>::format_as(x)));\n\n  template <typename T, FMT_ENABLE_IF(use_formatter<T>::value)>\n  static auto map(T&) -> conditional_t<has_formatter<T, Char>(), T&, void>;\n\n  template <typename T, FMT_ENABLE_IF(is_named_arg<T>::value)>\n  static auto map(const T& named_arg) -> decltype(map(named_arg.value));\n};\n\n// detail:: is used to workaround a bug in MSVC 2017.\ntemplate <typename T, typename Char>\nusing mapped_t = decltype(detail::type_mapper<Char>::map(std::declval<T&>()));\n\n// A type constant after applying type_mapper.\ntemplate <typename T, typename Char = char>\nusing mapped_type_constant = type_constant<mapped_t<T, Char>, Char>;\n\ntemplate <typename T, typename Context,\n          type TYPE =\n              mapped_type_constant<T, typename Context::char_type>::value>\nusing stored_type_constant = std::integral_constant<\n    type, Context::builtin_types || TYPE == type::int_type ? TYPE\n                                                           : type::custom_type>;\n// A parse context with extra data used only in compile-time checks.\ntemplate <typename Char>\nclass compile_parse_context : public parse_context<Char> {\n private:\n  int num_args_;\n  const type* types_;\n  using base = parse_context<Char>;\n\n public:\n  FMT_CONSTEXPR explicit compile_parse_context(basic_string_view<Char> fmt,\n                                               int num_args, const type* types,\n                                               int next_arg_id = 0)\n      : base(fmt, next_arg_id), num_args_(num_args), types_(types) {}\n\n  constexpr auto num_args() const -> int { return num_args_; }\n  constexpr auto arg_type(int id) const -> type { return types_[id]; }\n\n  FMT_CONSTEXPR auto next_arg_id() -> int {\n    int id = base::next_arg_id();\n    if (id >= num_args_) report_error(\"argument not found\");\n    return id;\n  }\n\n  FMT_CONSTEXPR void check_arg_id(int id) {\n    base::check_arg_id(id);\n    if (id >= num_args_) report_error(\"argument not found\");\n  }\n  using base::check_arg_id;\n\n  FMT_CONSTEXPR void check_dynamic_spec(int arg_id) {\n    ignore_unused(arg_id);\n    if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id]))\n      report_error(\"width/precision is not integer\");\n  }\n};\n\n// An argument reference.\ntemplate <typename Char> union arg_ref {\n  FMT_CONSTEXPR arg_ref(int idx = 0) : index(idx) {}\n  FMT_CONSTEXPR arg_ref(basic_string_view<Char> n) : name(n) {}\n\n  int index;\n  basic_string_view<Char> name;\n};\n\n// Format specifiers with width and precision resolved at formatting rather\n// than parsing time to allow reusing the same parsed specifiers with\n// different sets of arguments (precompilation of format strings).\ntemplate <typename Char = char> struct dynamic_format_specs : format_specs {\n  arg_ref<Char> width_ref;\n  arg_ref<Char> precision_ref;\n};\n\n// Converts a character to ASCII. Returns '\\0' on conversion failure.\ntemplate <typename Char, FMT_ENABLE_IF(std::is_integral<Char>::value)>\nconstexpr auto to_ascii(Char c) -> char {\n  return c <= 0xff ? static_cast<char>(c) : '\\0';\n}\n\n// Returns the number of code units in a code point or 1 on error.\ntemplate <typename Char>\nFMT_CONSTEXPR auto code_point_length(const Char* begin) -> int {\n  if (const_check(sizeof(Char) != 1)) return 1;\n  auto c = static_cast<unsigned char>(*begin);\n  return static_cast<int>((0x3a55000000000000ull >> (2 * (c >> 3))) & 3) + 1;\n}\n\n// Parses the range [begin, end) as an unsigned integer. This function assumes\n// that the range is non-empty and the first character is a digit.\ntemplate <typename Char>\nFMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end,\n                                         int error_value) noexcept -> int {\n  FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', \"\");\n  unsigned value = 0, prev = 0;\n  auto p = begin;\n  do {\n    prev = value;\n    value = value * 10 + unsigned(*p - '0');\n    ++p;\n  } while (p != end && '0' <= *p && *p <= '9');\n  auto num_digits = p - begin;\n  begin = p;\n  int digits10 = static_cast<int>(sizeof(int) * CHAR_BIT * 3 / 10);\n  if (num_digits <= digits10) return static_cast<int>(value);\n  // Check for overflow.\n  unsigned max = INT_MAX;\n  return num_digits == digits10 + 1 &&\n                 prev * 10ull + unsigned(p[-1] - '0') <= max\n             ? static_cast<int>(value)\n             : error_value;\n}\n\nFMT_CONSTEXPR inline auto parse_align(char c) -> align {\n  switch (c) {\n  case '<': return align::left;\n  case '>': return align::right;\n  case '^': return align::center;\n  }\n  return align::none;\n}\n\ntemplate <typename Char> constexpr auto is_name_start(Char c) -> bool {\n  return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_';\n}\n\ntemplate <typename Char, typename Handler>\nFMT_CONSTEXPR auto parse_arg_id(const Char* begin, const Char* end,\n                                Handler&& handler) -> const Char* {\n  Char c = *begin;\n  if (c >= '0' && c <= '9') {\n    int index = 0;\n    if (c != '0')\n      index = parse_nonnegative_int(begin, end, INT_MAX);\n    else\n      ++begin;\n    if (begin == end || (*begin != '}' && *begin != ':'))\n      report_error(\"invalid format string\");\n    else\n      handler.on_index(index);\n    return begin;\n  }\n  if (FMT_OPTIMIZE_SIZE > 1 || !is_name_start(c)) {\n    report_error(\"invalid format string\");\n    return begin;\n  }\n  auto it = begin;\n  do {\n    ++it;\n  } while (it != end && (is_name_start(*it) || ('0' <= *it && *it <= '9')));\n  handler.on_name({begin, to_unsigned(it - begin)});\n  return it;\n}\n\ntemplate <typename Char> struct dynamic_spec_handler {\n  parse_context<Char>& ctx;\n  arg_ref<Char>& ref;\n  arg_id_kind& kind;\n\n  FMT_CONSTEXPR void on_index(int id) {\n    ref = id;\n    kind = arg_id_kind::index;\n    ctx.check_arg_id(id);\n    ctx.check_dynamic_spec(id);\n  }\n  FMT_CONSTEXPR void on_name(basic_string_view<Char> id) {\n    ref = id;\n    kind = arg_id_kind::name;\n    ctx.check_arg_id(id);\n  }\n};\n\ntemplate <typename Char> struct parse_dynamic_spec_result {\n  const Char* end;\n  arg_id_kind kind;\n};\n\n// Parses integer | \"{\" [arg_id] \"}\".\ntemplate <typename Char>\nFMT_CONSTEXPR auto parse_dynamic_spec(const Char* begin, const Char* end,\n                                      int& value, arg_ref<Char>& ref,\n                                      parse_context<Char>& ctx)\n    -> parse_dynamic_spec_result<Char> {\n  FMT_ASSERT(begin != end, \"\");\n  auto kind = arg_id_kind::none;\n  if ('0' <= *begin && *begin <= '9') {\n    int val = parse_nonnegative_int(begin, end, -1);\n    if (val == -1) report_error(\"number is too big\");\n    value = val;\n  } else {\n    if (*begin == '{') {\n      ++begin;\n      if (begin != end) {\n        Char c = *begin;\n        if (c == '}' || c == ':') {\n          int id = ctx.next_arg_id();\n          ref = id;\n          kind = arg_id_kind::index;\n          ctx.check_dynamic_spec(id);\n        } else {\n          begin = parse_arg_id(begin, end,\n                               dynamic_spec_handler<Char>{ctx, ref, kind});\n        }\n      }\n      if (begin != end && *begin == '}') return {++begin, kind};\n    }\n    report_error(\"invalid format string\");\n  }\n  return {begin, kind};\n}\n\ntemplate <typename Char>\nFMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end,\n                               format_specs& specs, arg_ref<Char>& width_ref,\n                               parse_context<Char>& ctx) -> const Char* {\n  auto result = parse_dynamic_spec(begin, end, specs.width, width_ref, ctx);\n  specs.set_dynamic_width(result.kind);\n  return result.end;\n}\n\ntemplate <typename Char>\nFMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end,\n                                   format_specs& specs,\n                                   arg_ref<Char>& precision_ref,\n                                   parse_context<Char>& ctx) -> const Char* {\n  ++begin;\n  if (begin == end) {\n    report_error(\"invalid precision\");\n    return begin;\n  }\n  auto result =\n      parse_dynamic_spec(begin, end, specs.precision, precision_ref, ctx);\n  specs.set_dynamic_precision(result.kind);\n  return result.end;\n}\n\nenum class state { start, align, sign, hash, zero, width, precision, locale };\n\n// Parses standard format specifiers.\ntemplate <typename Char>\nFMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end,\n                                      dynamic_format_specs<Char>& specs,\n                                      parse_context<Char>& ctx, type arg_type)\n    -> const Char* {\n  auto c = '\\0';\n  if (end - begin > 1) {\n    auto next = to_ascii(begin[1]);\n    c = parse_align(next) == align::none ? to_ascii(*begin) : '\\0';\n  } else {\n    if (begin == end) return begin;\n    c = to_ascii(*begin);\n  }\n\n  struct {\n    state current_state = state::start;\n    FMT_CONSTEXPR void operator()(state s, bool valid = true) {\n      if (current_state >= s || !valid)\n        report_error(\"invalid format specifier\");\n      current_state = s;\n    }\n  } enter_state;\n\n  using pres = presentation_type;\n  constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;\n  struct {\n    const Char*& begin;\n    format_specs& specs;\n    type arg_type;\n\n    FMT_CONSTEXPR auto operator()(pres pres_type, int set) -> const Char* {\n      if (!in(arg_type, set)) report_error(\"invalid format specifier\");\n      specs.set_type(pres_type);\n      return begin + 1;\n    }\n  } parse_presentation_type{begin, specs, arg_type};\n\n  for (;;) {\n    switch (c) {\n    case '<':\n    case '>':\n    case '^':\n      enter_state(state::align);\n      specs.set_align(parse_align(c));\n      ++begin;\n      break;\n    case '+':\n    case ' ':\n      specs.set_sign(c == ' ' ? sign::space : sign::plus);\n      FMT_FALLTHROUGH;\n    case '-':\n      enter_state(state::sign, in(arg_type, sint_set | float_set));\n      ++begin;\n      break;\n    case '#':\n      enter_state(state::hash, is_arithmetic_type(arg_type));\n      specs.set_alt();\n      ++begin;\n      break;\n    case '0':\n      enter_state(state::zero);\n      if (!is_arithmetic_type(arg_type))\n        report_error(\"format specifier requires numeric argument\");\n      if (specs.align() == align::none) {\n        // Ignore 0 if align is specified for compatibility with std::format.\n        specs.set_align(align::numeric);\n        specs.set_fill('0');\n      }\n      ++begin;\n      break;\n      // clang-format off\n    case '1': case '2': case '3': case '4': case '5':\n    case '6': case '7': case '8': case '9': case '{':\n      // clang-format on\n      enter_state(state::width);\n      begin = parse_width(begin, end, specs, specs.width_ref, ctx);\n      break;\n    case '.':\n      enter_state(state::precision,\n                  in(arg_type, float_set | string_set | cstring_set));\n      begin = parse_precision(begin, end, specs, specs.precision_ref, ctx);\n      break;\n    case 'L':\n      enter_state(state::locale, is_arithmetic_type(arg_type));\n      specs.set_localized();\n      ++begin;\n      break;\n    case 'd': return parse_presentation_type(pres::dec, integral_set);\n    case 'X': specs.set_upper(); FMT_FALLTHROUGH;\n    case 'x': return parse_presentation_type(pres::hex, integral_set);\n    case 'o': return parse_presentation_type(pres::oct, integral_set);\n    case 'B': specs.set_upper(); FMT_FALLTHROUGH;\n    case 'b': return parse_presentation_type(pres::bin, integral_set);\n    case 'E': specs.set_upper(); FMT_FALLTHROUGH;\n    case 'e': return parse_presentation_type(pres::exp, float_set);\n    case 'F': specs.set_upper(); FMT_FALLTHROUGH;\n    case 'f': return parse_presentation_type(pres::fixed, float_set);\n    case 'G': specs.set_upper(); FMT_FALLTHROUGH;\n    case 'g': return parse_presentation_type(pres::general, float_set);\n    case 'A': specs.set_upper(); FMT_FALLTHROUGH;\n    case 'a': return parse_presentation_type(pres::hexfloat, float_set);\n    case 'c':\n      if (arg_type == type::bool_type) report_error(\"invalid format specifier\");\n      return parse_presentation_type(pres::chr, integral_set);\n    case 's':\n      return parse_presentation_type(pres::string,\n                                     bool_set | string_set | cstring_set);\n    case 'p':\n      return parse_presentation_type(pres::pointer, pointer_set | cstring_set);\n    case '?':\n      return parse_presentation_type(pres::debug,\n                                     char_set | string_set | cstring_set);\n    case '}': return begin;\n    default:  {\n      if (*begin == '}') return begin;\n      // Parse fill and alignment.\n      auto fill_end = begin + code_point_length(begin);\n      if (end - fill_end <= 0) {\n        report_error(\"invalid format specifier\");\n        return begin;\n      }\n      if (*begin == '{') {\n        report_error(\"invalid fill character '{'\");\n        return begin;\n      }\n      auto alignment = parse_align(to_ascii(*fill_end));\n      enter_state(state::align, alignment != align::none);\n      specs.set_fill(\n          basic_string_view<Char>(begin, to_unsigned(fill_end - begin)));\n      specs.set_align(alignment);\n      begin = fill_end + 1;\n    }\n    }\n    if (begin == end) return begin;\n    c = to_ascii(*begin);\n  }\n}\n\ntemplate <typename Char, typename Handler>\nFMT_CONSTEXPR FMT_INLINE auto parse_replacement_field(const Char* begin,\n                                                      const Char* end,\n                                                      Handler&& handler)\n    -> const Char* {\n  ++begin;\n  if (begin == end) {\n    handler.on_error(\"invalid format string\");\n    return end;\n  }\n  int arg_id = 0;\n  switch (*begin) {\n  case '}':\n    handler.on_replacement_field(handler.on_arg_id(), begin);\n    return begin + 1;\n  case '{': handler.on_text(begin, begin + 1); return begin + 1;\n  case ':': arg_id = handler.on_arg_id(); break;\n  default:  {\n    struct id_adapter {\n      Handler& handler;\n      int arg_id;\n\n      FMT_CONSTEXPR void on_index(int id) { arg_id = handler.on_arg_id(id); }\n      FMT_CONSTEXPR void on_name(basic_string_view<Char> id) {\n        arg_id = handler.on_arg_id(id);\n      }\n    } adapter = {handler, 0};\n    begin = parse_arg_id(begin, end, adapter);\n    arg_id = adapter.arg_id;\n    Char c = begin != end ? *begin : Char();\n    if (c == '}') {\n      handler.on_replacement_field(arg_id, begin);\n      return begin + 1;\n    }\n    if (c != ':') {\n      handler.on_error(\"missing '}' in format string\");\n      return end;\n    }\n    break;\n  }\n  }\n  begin = handler.on_format_specs(arg_id, begin + 1, end);\n  if (begin == end || *begin != '}')\n    return handler.on_error(\"unknown format specifier\"), end;\n  return begin + 1;\n}\n\ntemplate <typename Char, typename Handler>\nFMT_CONSTEXPR void parse_format_string(basic_string_view<Char> fmt,\n                                       Handler&& handler) {\n  auto begin = fmt.data(), end = begin + fmt.size();\n  auto p = begin;\n  while (p != end) {\n    auto c = *p++;\n    if (c == '{') {\n      handler.on_text(begin, p - 1);\n      begin = p = parse_replacement_field(p - 1, end, handler);\n    } else if (c == '}') {\n      if (p == end || *p != '}')\n        return handler.on_error(\"unmatched '}' in format string\");\n      handler.on_text(begin, p);\n      begin = ++p;\n    }\n  }\n  handler.on_text(begin, end);\n}\n\n// Checks char specs and returns true iff the presentation type is char-like.\nFMT_CONSTEXPR inline auto check_char_specs(const format_specs& specs) -> bool {\n  auto type = specs.type();\n  if (type != presentation_type::none && type != presentation_type::chr &&\n      type != presentation_type::debug) {\n    return false;\n  }\n  if (specs.align() == align::numeric || specs.sign() != sign::none ||\n      specs.alt()) {\n    report_error(\"invalid format specifier for char\");\n  }\n  return true;\n}\n\n// A base class for compile-time strings.\nstruct compile_string {};\n\ntemplate <typename T, typename Char>\nFMT_VISIBILITY(\"hidden\")  // Suppress an ld warning on macOS (#3769).\nFMT_CONSTEXPR auto invoke_parse(parse_context<Char>& ctx) -> const Char* {\n  using mapped_type = remove_cvref_t<mapped_t<T, Char>>;\n  constexpr bool formattable =\n      std::is_constructible<formatter<mapped_type, Char>>::value;\n  if (!formattable) return ctx.begin();  // Error is reported in the value ctor.\n  using formatted_type = conditional_t<formattable, mapped_type, int>;\n  return formatter<formatted_type, Char>().parse(ctx);\n}\n\ntemplate <typename... T> struct arg_pack {};\n\ntemplate <typename Char, int NUM_ARGS, int NUM_NAMED_ARGS, bool DYNAMIC_NAMES>\nclass format_string_checker {\n private:\n  type types_[max_of(1, NUM_ARGS)];\n  named_arg_info<Char> named_args_[max_of(1, NUM_NAMED_ARGS)];\n  compile_parse_context<Char> context_;\n\n  using parse_func = auto (*)(parse_context<Char>&) -> const Char*;\n  parse_func parse_funcs_[max_of(1, NUM_ARGS)];\n\n public:\n  template <typename... T>\n  FMT_CONSTEXPR explicit format_string_checker(basic_string_view<Char> fmt,\n                                               arg_pack<T...>)\n      : types_{mapped_type_constant<T, Char>::value...},\n        named_args_{},\n        context_(fmt, NUM_ARGS, types_),\n        parse_funcs_{&invoke_parse<T, Char>...} {\n    int arg_index = 0, named_arg_index = 0;\n    FMT_APPLY_VARIADIC(\n        init_static_named_arg<T>(named_args_, arg_index, named_arg_index));\n    ignore_unused(arg_index, named_arg_index);\n  }\n\n  FMT_CONSTEXPR void on_text(const Char*, const Char*) {}\n\n  FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); }\n  FMT_CONSTEXPR auto on_arg_id(int id) -> int {\n    context_.check_arg_id(id);\n    return id;\n  }\n  FMT_CONSTEXPR auto on_arg_id(basic_string_view<Char> id) -> int {\n    for (int i = 0; i < NUM_NAMED_ARGS; ++i) {\n      if (named_args_[i].name == id) return named_args_[i].id;\n    }\n    if (!DYNAMIC_NAMES) on_error(\"argument not found\");\n    return -1;\n  }\n\n  FMT_CONSTEXPR void on_replacement_field(int id, const Char* begin) {\n    on_format_specs(id, begin, begin);  // Call parse() on empty specs.\n  }\n\n  FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char* end)\n      -> const Char* {\n    context_.advance_to(begin);\n    if (id >= 0 && id < NUM_ARGS) return parse_funcs_[id](context_);\n    while (begin != end && *begin != '}') ++begin;\n    return begin;\n  }\n\n  FMT_NORETURN FMT_CONSTEXPR void on_error(const char* message) {\n    report_error(message);\n  }\n};\n\n/// A contiguous memory buffer with an optional growing ability. It is an\n/// internal class and shouldn't be used directly, only via `memory_buffer`.\ntemplate <typename T> class buffer {\n private:\n  T* ptr_;\n  size_t size_;\n  size_t capacity_;\n\n  using grow_fun = void (*)(buffer& buf, size_t capacity);\n  grow_fun grow_;\n\n protected:\n  // Don't initialize ptr_ since it is not accessed to save a few cycles.\n  FMT_MSC_WARNING(suppress : 26495)\n  FMT_CONSTEXPR buffer(grow_fun grow, size_t sz) noexcept\n      : size_(sz), capacity_(sz), grow_(grow) {}\n\n  constexpr buffer(grow_fun grow, T* p = nullptr, size_t sz = 0,\n                   size_t cap = 0) noexcept\n      : ptr_(p), size_(sz), capacity_(cap), grow_(grow) {}\n\n  FMT_CONSTEXPR20 ~buffer() = default;\n  buffer(buffer&&) = default;\n\n  /// Sets the buffer data and capacity.\n  FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) noexcept {\n    ptr_ = buf_data;\n    capacity_ = buf_capacity;\n  }\n\n public:\n  using value_type = T;\n  using const_reference = const T&;\n\n  buffer(const buffer&) = delete;\n  void operator=(const buffer&) = delete;\n\n  auto begin() noexcept -> T* { return ptr_; }\n  auto end() noexcept -> T* { return ptr_ + size_; }\n\n  auto begin() const noexcept -> const T* { return ptr_; }\n  auto end() const noexcept -> const T* { return ptr_ + size_; }\n\n  /// Returns the size of this buffer.\n  constexpr auto size() const noexcept -> size_t { return size_; }\n\n  /// Returns the capacity of this buffer.\n  constexpr auto capacity() const noexcept -> size_t { return capacity_; }\n\n  /// Returns a pointer to the buffer data (not null-terminated).\n  FMT_CONSTEXPR auto data() noexcept -> T* { return ptr_; }\n  FMT_CONSTEXPR auto data() const noexcept -> const T* { return ptr_; }\n\n  /// Clears this buffer.\n  FMT_CONSTEXPR void clear() { size_ = 0; }\n\n  // Tries resizing the buffer to contain `count` elements. If T is a POD type\n  // the new elements may not be initialized.\n  FMT_CONSTEXPR void try_resize(size_t count) {\n    try_reserve(count);\n    size_ = min_of(count, capacity_);\n  }\n\n  // Tries increasing the buffer capacity to `new_capacity`. It can increase the\n  // capacity by a smaller amount than requested but guarantees there is space\n  // for at least one additional element either by increasing the capacity or by\n  // flushing the buffer if it is full.\n  FMT_CONSTEXPR void try_reserve(size_t new_capacity) {\n    if (new_capacity > capacity_) grow_(*this, new_capacity);\n  }\n\n  FMT_CONSTEXPR void push_back(const T& value) {\n    try_reserve(size_ + 1);\n    ptr_[size_++] = value;\n  }\n\n  /// Appends data to the end of the buffer.\n  template <typename U>\n// Workaround for MSVC2019 to fix error C2893: Failed to specialize function\n// template 'void fmt::v11::detail::buffer<T>::append(const U *,const U *)'.\n#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940\n  FMT_CONSTEXPR20\n#endif\n      void\n      append(const U* begin, const U* end) {\n    while (begin != end) {\n      auto count = to_unsigned(end - begin);\n      try_reserve(size_ + count);\n      auto free_cap = capacity_ - size_;\n      if (free_cap < count) count = free_cap;\n      // A loop is faster than memcpy on small sizes.\n      T* out = ptr_ + size_;\n      for (size_t i = 0; i < count; ++i) out[i] = begin[i];\n      size_ += count;\n      begin += count;\n    }\n  }\n\n  template <typename Idx> FMT_CONSTEXPR auto operator[](Idx index) -> T& {\n    return ptr_[index];\n  }\n  template <typename Idx>\n  FMT_CONSTEXPR auto operator[](Idx index) const -> const T& {\n    return ptr_[index];\n  }\n};\n\nstruct buffer_traits {\n  constexpr explicit buffer_traits(size_t) {}\n  constexpr auto count() const -> size_t { return 0; }\n  constexpr auto limit(size_t size) const -> size_t { return size; }\n};\n\nclass fixed_buffer_traits {\n private:\n  size_t count_ = 0;\n  size_t limit_;\n\n public:\n  constexpr explicit fixed_buffer_traits(size_t limit) : limit_(limit) {}\n  constexpr auto count() const -> size_t { return count_; }\n  FMT_CONSTEXPR auto limit(size_t size) -> size_t {\n    size_t n = limit_ > count_ ? limit_ - count_ : 0;\n    count_ += size;\n    return min_of(size, n);\n  }\n};\n\n// A buffer that writes to an output iterator when flushed.\ntemplate <typename OutputIt, typename T, typename Traits = buffer_traits>\nclass iterator_buffer : public Traits, public buffer<T> {\n private:\n  OutputIt out_;\n  enum { buffer_size = 256 };\n  T data_[buffer_size];\n\n  static FMT_CONSTEXPR void grow(buffer<T>& buf, size_t) {\n    if (buf.size() == buffer_size) static_cast<iterator_buffer&>(buf).flush();\n  }\n\n  void flush() {\n    auto size = this->size();\n    this->clear();\n    const T* begin = data_;\n    const T* end = begin + this->limit(size);\n    while (begin != end) *out_++ = *begin++;\n  }\n\n public:\n  explicit iterator_buffer(OutputIt out, size_t n = buffer_size)\n      : Traits(n), buffer<T>(grow, data_, 0, buffer_size), out_(out) {}\n  iterator_buffer(iterator_buffer&& other) noexcept\n      : Traits(other),\n        buffer<T>(grow, data_, 0, buffer_size),\n        out_(other.out_) {}\n  ~iterator_buffer() {\n    // Don't crash if flush fails during unwinding.\n    FMT_TRY { flush(); }\n    FMT_CATCH(...) {}\n  }\n\n  auto out() -> OutputIt {\n    flush();\n    return out_;\n  }\n  auto count() const -> size_t { return Traits::count() + this->size(); }\n};\n\ntemplate <typename T>\nclass iterator_buffer<T*, T, fixed_buffer_traits> : public fixed_buffer_traits,\n                                                    public buffer<T> {\n private:\n  T* out_;\n  enum { buffer_size = 256 };\n  T data_[buffer_size];\n\n  static FMT_CONSTEXPR void grow(buffer<T>& buf, size_t) {\n    if (buf.size() == buf.capacity())\n      static_cast<iterator_buffer&>(buf).flush();\n  }\n\n  void flush() {\n    size_t n = this->limit(this->size());\n    if (this->data() == out_) {\n      out_ += n;\n      this->set(data_, buffer_size);\n    }\n    this->clear();\n  }\n\n public:\n  explicit iterator_buffer(T* out, size_t n = buffer_size)\n      : fixed_buffer_traits(n), buffer<T>(grow, out, 0, n), out_(out) {}\n  iterator_buffer(iterator_buffer&& other) noexcept\n      : fixed_buffer_traits(other),\n        buffer<T>(static_cast<iterator_buffer&&>(other)),\n        out_(other.out_) {\n    if (this->data() != out_) {\n      this->set(data_, buffer_size);\n      this->clear();\n    }\n  }\n  ~iterator_buffer() { flush(); }\n\n  auto out() -> T* {\n    flush();\n    return out_;\n  }\n  auto count() const -> size_t {\n    return fixed_buffer_traits::count() + this->size();\n  }\n};\n\ntemplate <typename T> class iterator_buffer<T*, T> : public buffer<T> {\n public:\n  explicit iterator_buffer(T* out, size_t = 0)\n      : buffer<T>([](buffer<T>&, size_t) {}, out, 0, ~size_t()) {}\n\n  auto out() -> T* { return &*this->end(); }\n};\n\ntemplate <typename Container>\nclass container_buffer : public buffer<typename Container::value_type> {\n private:\n  using value_type = typename Container::value_type;\n\n  static FMT_CONSTEXPR void grow(buffer<value_type>& buf, size_t capacity) {\n    auto& self = static_cast<container_buffer&>(buf);\n    self.container.resize(capacity);\n    self.set(&self.container[0], capacity);\n  }\n\n public:\n  Container& container;\n\n  explicit container_buffer(Container& c)\n      : buffer<value_type>(grow, c.size()), container(c) {}\n};\n\n// A buffer that writes to a container with the contiguous storage.\ntemplate <typename OutputIt>\nclass iterator_buffer<\n    OutputIt,\n    enable_if_t<is_back_insert_iterator<OutputIt>::value &&\n                    is_contiguous<typename OutputIt::container_type>::value,\n                typename OutputIt::container_type::value_type>>\n    : public container_buffer<typename OutputIt::container_type> {\n private:\n  using base = container_buffer<typename OutputIt::container_type>;\n\n public:\n  explicit iterator_buffer(typename OutputIt::container_type& c) : base(c) {}\n  explicit iterator_buffer(OutputIt out, size_t = 0)\n      : base(get_container(out)) {}\n\n  auto out() -> OutputIt { return OutputIt(this->container); }\n};\n\n// A buffer that counts the number of code units written discarding the output.\ntemplate <typename T = char> class counting_buffer : public buffer<T> {\n private:\n  enum { buffer_size = 256 };\n  T data_[buffer_size];\n  size_t count_ = 0;\n\n  static FMT_CONSTEXPR void grow(buffer<T>& buf, size_t) {\n    if (buf.size() != buffer_size) return;\n    static_cast<counting_buffer&>(buf).count_ += buf.size();\n    buf.clear();\n  }\n\n public:\n  FMT_CONSTEXPR counting_buffer() : buffer<T>(grow, data_, 0, buffer_size) {}\n\n  constexpr auto count() const noexcept -> size_t {\n    return count_ + this->size();\n  }\n};\n\ntemplate <typename T>\nstruct is_back_insert_iterator<basic_appender<T>> : std::true_type {};\n\ntemplate <typename OutputIt, typename InputIt, typename = void>\nstruct has_back_insert_iterator_container_append : std::false_type {};\ntemplate <typename OutputIt, typename InputIt>\nstruct has_back_insert_iterator_container_append<\n    OutputIt, InputIt,\n    void_t<decltype(get_container(std::declval<OutputIt>())\n                        .append(std::declval<InputIt>(),\n                                std::declval<InputIt>()))>> : std::true_type {};\n\n// An optimized version of std::copy with the output value type (T).\ntemplate <typename T, typename InputIt, typename OutputIt,\n          FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value&&\n                            has_back_insert_iterator_container_append<\n                                OutputIt, InputIt>::value)>\nFMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)\n    -> OutputIt {\n  get_container(out).append(begin, end);\n  return out;\n}\n\ntemplate <typename T, typename InputIt, typename OutputIt,\n          FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value &&\n                        !has_back_insert_iterator_container_append<\n                            OutputIt, InputIt>::value)>\nFMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)\n    -> OutputIt {\n  auto& c = get_container(out);\n  c.insert(c.end(), begin, end);\n  return out;\n}\n\ntemplate <typename T, typename InputIt, typename OutputIt,\n          FMT_ENABLE_IF(!is_back_insert_iterator<OutputIt>::value)>\nFMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt {\n  while (begin != end) *out++ = static_cast<T>(*begin++);\n  return out;\n}\n\ntemplate <typename T, typename V, typename OutputIt>\nFMT_CONSTEXPR auto copy(basic_string_view<V> s, OutputIt out) -> OutputIt {\n  return copy<T>(s.begin(), s.end(), out);\n}\n\ntemplate <typename It, typename Enable = std::true_type>\nstruct is_buffer_appender : std::false_type {};\ntemplate <typename It>\nstruct is_buffer_appender<\n    It, bool_constant<\n            is_back_insert_iterator<It>::value &&\n            std::is_base_of<buffer<typename It::container_type::value_type>,\n                            typename It::container_type>::value>>\n    : std::true_type {};\n\n// Maps an output iterator to a buffer.\ntemplate <typename T, typename OutputIt,\n          FMT_ENABLE_IF(!is_buffer_appender<OutputIt>::value)>\nauto get_buffer(OutputIt out) -> iterator_buffer<OutputIt, T> {\n  return iterator_buffer<OutputIt, T>(out);\n}\ntemplate <typename T, typename OutputIt,\n          FMT_ENABLE_IF(is_buffer_appender<OutputIt>::value)>\nauto get_buffer(OutputIt out) -> buffer<T>& {\n  return get_container(out);\n}\n\ntemplate <typename Buf, typename OutputIt>\nauto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) {\n  return buf.out();\n}\ntemplate <typename T, typename OutputIt>\nauto get_iterator(buffer<T>&, OutputIt out) -> OutputIt {\n  return out;\n}\n\n// This type is intentionally undefined, only used for errors.\ntemplate <typename T, typename Char> struct type_is_unformattable_for;\n\ntemplate <typename Char> struct string_value {\n  const Char* data;\n  size_t size;\n  auto str() const -> basic_string_view<Char> { return {data, size}; }\n};\n\ntemplate <typename Context> struct custom_value {\n  using char_type = typename Context::char_type;\n  void* value;\n  void (*format)(void* arg, parse_context<char_type>& parse_ctx, Context& ctx);\n};\n\ntemplate <typename Char> struct named_arg_value {\n  const named_arg_info<Char>* data;\n  size_t size;\n};\n\nstruct custom_tag {};\n\n#if !FMT_BUILTIN_TYPES\n#  define FMT_BUILTIN , monostate\n#else\n#  define FMT_BUILTIN\n#endif\n\n// A formatting argument value.\ntemplate <typename Context> class value {\n public:\n  using char_type = typename Context::char_type;\n\n  union {\n    monostate no_value;\n    int int_value;\n    unsigned uint_value;\n    long long long_long_value;\n    unsigned long long ulong_long_value;\n    int128_opt int128_value;\n    uint128_opt uint128_value;\n    bool bool_value;\n    char_type char_value;\n    float float_value;\n    double double_value;\n    long double long_double_value;\n    const void* pointer;\n    string_value<char_type> string;\n    custom_value<Context> custom;\n    named_arg_value<char_type> named_args;\n  };\n\n  constexpr FMT_INLINE value() : no_value() {}\n  constexpr FMT_INLINE value(signed char x) : int_value(x) {}\n  constexpr FMT_INLINE value(unsigned char x FMT_BUILTIN) : uint_value(x) {}\n  constexpr FMT_INLINE value(signed short x) : int_value(x) {}\n  constexpr FMT_INLINE value(unsigned short x FMT_BUILTIN) : uint_value(x) {}\n  constexpr FMT_INLINE value(int x) : int_value(x) {}\n  constexpr FMT_INLINE value(unsigned x FMT_BUILTIN) : uint_value(x) {}\n  FMT_CONSTEXPR FMT_INLINE value(long x FMT_BUILTIN) : value(long_type(x)) {}\n  FMT_CONSTEXPR FMT_INLINE value(unsigned long x FMT_BUILTIN)\n      : value(ulong_type(x)) {}\n  constexpr FMT_INLINE value(long long x FMT_BUILTIN) : long_long_value(x) {}\n  constexpr FMT_INLINE value(unsigned long long x FMT_BUILTIN)\n      : ulong_long_value(x) {}\n  FMT_INLINE value(int128_opt x FMT_BUILTIN) : int128_value(x) {}\n  FMT_INLINE value(uint128_opt x FMT_BUILTIN) : uint128_value(x) {}\n  constexpr FMT_INLINE value(bool x FMT_BUILTIN) : bool_value(x) {}\n\n  template <int N>\n  constexpr FMT_INLINE value(bitint<N> x FMT_BUILTIN) : long_long_value(x) {\n    static_assert(N <= 64, \"unsupported _BitInt\");\n  }\n  template <int N>\n  constexpr FMT_INLINE value(ubitint<N> x FMT_BUILTIN) : ulong_long_value(x) {\n    static_assert(N <= 64, \"unsupported _BitInt\");\n  }\n\n  template <typename T, FMT_ENABLE_IF(is_char<T>::value)>\n  constexpr FMT_INLINE value(T x FMT_BUILTIN) : char_value(x) {\n    static_assert(\n        std::is_same<T, char>::value || std::is_same<T, char_type>::value,\n        \"mixing character types is disallowed\");\n  }\n\n  constexpr FMT_INLINE value(float x FMT_BUILTIN) : float_value(x) {}\n  constexpr FMT_INLINE value(double x FMT_BUILTIN) : double_value(x) {}\n  FMT_INLINE value(long double x FMT_BUILTIN) : long_double_value(x) {}\n\n  FMT_CONSTEXPR FMT_INLINE value(char_type* x FMT_BUILTIN) {\n    string.data = x;\n    if (is_constant_evaluated()) string.size = 0;\n  }\n  FMT_CONSTEXPR FMT_INLINE value(const char_type* x FMT_BUILTIN) {\n    string.data = x;\n    if (is_constant_evaluated()) string.size = 0;\n  }\n  template <typename T, typename C = char_t<T>,\n            FMT_ENABLE_IF(!std::is_pointer<T>::value)>\n  FMT_CONSTEXPR value(const T& x FMT_BUILTIN) {\n    static_assert(std::is_same<C, char_type>::value,\n                  \"mixing character types is disallowed\");\n    auto sv = to_string_view(x);\n    string.data = sv.data();\n    string.size = sv.size();\n  }\n  FMT_INLINE value(void* x FMT_BUILTIN) : pointer(x) {}\n  FMT_INLINE value(const void* x FMT_BUILTIN) : pointer(x) {}\n  FMT_INLINE value(volatile void* x FMT_BUILTIN)\n      : pointer(const_cast<const void*>(x)) {}\n  FMT_INLINE value(const volatile void* x FMT_BUILTIN)\n      : pointer(const_cast<const void*>(x)) {}\n  FMT_INLINE value(nullptr_t) : pointer(nullptr) {}\n\n  template <typename T, FMT_ENABLE_IF(std::is_pointer<T>::value ||\n                                      std::is_member_pointer<T>::value)>\n  value(const T&) {\n    // Formatting of arbitrary pointers is disallowed. If you want to format a\n    // pointer cast it to `void*` or `const void*`. In particular, this forbids\n    // formatting of `[const] volatile char*` printed as bool by iostreams.\n    static_assert(sizeof(T) == 0,\n                  \"formatting of non-void pointers is disallowed\");\n  }\n\n  template <typename T, FMT_ENABLE_IF(use_format_as<T>::value)>\n  value(const T& x) : value(format_as(x)) {}\n  template <typename T, FMT_ENABLE_IF(use_format_as_member<T>::value)>\n  value(const T& x) : value(formatter<T>::format_as(x)) {}\n\n  template <typename T, FMT_ENABLE_IF(is_named_arg<T>::value)>\n  value(const T& named_arg) : value(named_arg.value) {}\n\n  template <typename T,\n            FMT_ENABLE_IF(use_formatter<T>::value || !FMT_BUILTIN_TYPES)>\n  FMT_CONSTEXPR20 FMT_INLINE value(T& x) : value(x, custom_tag()) {}\n\n  FMT_ALWAYS_INLINE value(const named_arg_info<char_type>* args, size_t size)\n      : named_args{args, size} {}\n\n private:\n  template <typename T, FMT_ENABLE_IF(has_formatter<T, char_type>())>\n  FMT_CONSTEXPR value(T& x, custom_tag) {\n    using value_type = remove_const_t<T>;\n    // T may overload operator& e.g. std::vector<bool>::reference in libc++.\n    if (!is_constant_evaluated()) {\n      custom.value =\n          const_cast<char*>(&reinterpret_cast<const volatile char&>(x));\n    } else {\n      custom.value = nullptr;\n#if defined(__cpp_if_constexpr)\n      if constexpr (std::is_same<decltype(&x), remove_reference_t<T>*>::value)\n        custom.value = const_cast<value_type*>(&x);\n#endif\n    }\n    custom.format = format_custom<value_type, formatter<value_type, char_type>>;\n  }\n\n  template <typename T, FMT_ENABLE_IF(!has_formatter<T, char_type>())>\n  FMT_CONSTEXPR value(const T&, custom_tag) {\n    // Cannot format an argument; to make type T formattable provide a\n    // formatter<T> specialization: https://fmt.dev/latest/api.html#udt.\n    type_is_unformattable_for<T, char_type> _;\n  }\n\n  // Formats an argument of a custom type, such as a user-defined class.\n  template <typename T, typename Formatter>\n  static void format_custom(void* arg, parse_context<char_type>& parse_ctx,\n                            Context& ctx) {\n    auto f = Formatter();\n    parse_ctx.advance_to(f.parse(parse_ctx));\n    using qualified_type =\n        conditional_t<has_formatter<const T, char_type>(), const T, T>;\n    // format must be const for compatibility with std::format and compilation.\n    const auto& cf = f;\n    ctx.advance_to(cf.format(*static_cast<qualified_type*>(arg), ctx));\n  }\n};\n\nenum { packed_arg_bits = 4 };\n// Maximum number of arguments with packed types.\nenum { max_packed_args = 62 / packed_arg_bits };\nenum : unsigned long long { is_unpacked_bit = 1ULL << 63 };\nenum : unsigned long long { has_named_args_bit = 1ULL << 62 };\n\ntemplate <typename It, typename T, typename Enable = void>\nstruct is_output_iterator : std::false_type {};\n\ntemplate <> struct is_output_iterator<appender, char> : std::true_type {};\n\ntemplate <typename It, typename T>\nstruct is_output_iterator<\n    It, T,\n    void_t<decltype(*std::declval<decay_t<It>&>()++ = std::declval<T>())>>\n    : std::true_type {};\n\n#ifndef FMT_USE_LOCALE\n#  define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1)\n#endif\n\n// A type-erased reference to an std::locale to avoid a heavy <locale> include.\nstruct locale_ref {\n#if FMT_USE_LOCALE\n private:\n  const void* locale_;  // A type-erased pointer to std::locale.\n\n public:\n  constexpr locale_ref() : locale_(nullptr) {}\n  template <typename Locale> locale_ref(const Locale& loc);\n\n  inline explicit operator bool() const noexcept { return locale_ != nullptr; }\n#endif  // FMT_USE_LOCALE\n\n  template <typename Locale> auto get() const -> Locale;\n};\n\ntemplate <typename> constexpr auto encode_types() -> unsigned long long {\n  return 0;\n}\n\ntemplate <typename Context, typename Arg, typename... Args>\nconstexpr auto encode_types() -> unsigned long long {\n  return static_cast<unsigned>(stored_type_constant<Arg, Context>::value) |\n         (encode_types<Context, Args...>() << packed_arg_bits);\n}\n\ntemplate <typename Context, typename... T, size_t NUM_ARGS = sizeof...(T)>\nconstexpr auto make_descriptor() -> unsigned long long {\n  return NUM_ARGS <= max_packed_args ? encode_types<Context, T...>()\n                                     : is_unpacked_bit | NUM_ARGS;\n}\n\ntemplate <typename Context, int NUM_ARGS>\nusing arg_t = conditional_t<NUM_ARGS <= max_packed_args, value<Context>,\n                            basic_format_arg<Context>>;\n\ntemplate <typename Context, int NUM_ARGS, int NUM_NAMED_ARGS,\n          unsigned long long DESC>\nstruct named_arg_store {\n  // args_[0].named_args points to named_args to avoid bloating format_args.\n  arg_t<Context, NUM_ARGS> args[1 + NUM_ARGS];\n  named_arg_info<typename Context::char_type> named_args[NUM_NAMED_ARGS];\n\n  template <typename... T>\n  FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values)\n      : args{{named_args, NUM_NAMED_ARGS}, values...} {\n    int arg_index = 0, named_arg_index = 0;\n    FMT_APPLY_VARIADIC(\n        init_named_arg(named_args, arg_index, named_arg_index, values));\n  }\n\n  named_arg_store(named_arg_store&& rhs) {\n    args[0] = {named_args, NUM_NAMED_ARGS};\n    for (size_t i = 1; i < sizeof(args) / sizeof(*args); ++i)\n      args[i] = rhs.args[i];\n    for (size_t i = 0; i < NUM_NAMED_ARGS; ++i)\n      named_args[i] = rhs.named_args[i];\n  }\n\n  named_arg_store(const named_arg_store& rhs) = delete;\n  named_arg_store& operator=(const named_arg_store& rhs) = delete;\n  named_arg_store& operator=(named_arg_store&& rhs) = delete;\n  operator const arg_t<Context, NUM_ARGS>*() const { return args + 1; }\n};\n\n// An array of references to arguments. It can be implicitly converted to\n// `basic_format_args` for passing into type-erased formatting functions\n// such as `vformat`. It is a plain struct to reduce binary size in debug mode.\ntemplate <typename Context, int NUM_ARGS, int NUM_NAMED_ARGS,\n          unsigned long long DESC>\nstruct format_arg_store {\n  // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.\n  using type =\n      conditional_t<NUM_NAMED_ARGS == 0,\n                    arg_t<Context, NUM_ARGS>[max_of(1, NUM_ARGS)],\n                    named_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC>>;\n  type args;\n};\n\n// TYPE can be different from type_constant<T>, e.g. for __float128.\ntemplate <typename T, typename Char, type TYPE> struct native_formatter {\n private:\n  dynamic_format_specs<Char> specs_;\n\n public:\n  using nonlocking = void;\n\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin();\n    auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, TYPE);\n    if (const_check(TYPE == type::char_type)) check_char_specs(specs_);\n    return end;\n  }\n\n  template <type U = TYPE,\n            FMT_ENABLE_IF(U == type::string_type || U == type::cstring_type ||\n                          U == type::char_type)>\n  FMT_CONSTEXPR void set_debug_format(bool set = true) {\n    specs_.set_type(set ? presentation_type::debug : presentation_type::none);\n  }\n\n  FMT_PRAGMA_CLANG(diagnostic ignored \"-Wundefined-inline\")\n  template <typename FormatContext>\n  FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const\n      -> decltype(ctx.out());\n};\n\ntemplate <typename T, typename Enable = void>\nstruct locking\n    : bool_constant<mapped_type_constant<T>::value == type::custom_type> {};\ntemplate <typename T>\nstruct locking<T, void_t<typename formatter<remove_cvref_t<T>>::nonlocking>>\n    : std::false_type {};\n\ntemplate <typename T = int> FMT_CONSTEXPR inline auto is_locking() -> bool {\n  return locking<T>::value;\n}\ntemplate <typename T1, typename T2, typename... Tail>\nFMT_CONSTEXPR inline auto is_locking() -> bool {\n  return locking<T1>::value || is_locking<T2, Tail...>();\n}\n\nFMT_API void vformat_to(buffer<char>& buf, string_view fmt, format_args args,\n                        locale_ref loc = {});\n\n#if FMT_WIN32\nFMT_API void vprint_mojibake(FILE*, string_view, format_args, bool);\n#else  // format_args is passed by reference since it is defined later.\ninline void vprint_mojibake(FILE*, string_view, const format_args&, bool) {}\n#endif\n}  // namespace detail\n\n// The main public API.\n\ntemplate <typename Char>\nFMT_CONSTEXPR void parse_context<Char>::do_check_arg_id(int arg_id) {\n  // Argument id is only checked at compile time during parsing because\n  // formatting has its own validation.\n  if (detail::is_constant_evaluated() && use_constexpr_cast) {\n    auto ctx = static_cast<detail::compile_parse_context<Char>*>(this);\n    if (arg_id >= ctx->num_args()) report_error(\"argument not found\");\n  }\n}\n\ntemplate <typename Char>\nFMT_CONSTEXPR void parse_context<Char>::check_dynamic_spec(int arg_id) {\n  using detail::compile_parse_context;\n  if (detail::is_constant_evaluated() && use_constexpr_cast)\n    static_cast<compile_parse_context<Char>*>(this)->check_dynamic_spec(arg_id);\n}\n\nFMT_BEGIN_EXPORT\n\n// An output iterator that appends to a buffer. It is used instead of\n// back_insert_iterator to reduce symbol sizes and avoid <iterator> dependency.\ntemplate <typename T> class basic_appender {\n protected:\n  detail::buffer<T>* container;\n\n public:\n  using container_type = detail::buffer<T>;\n\n  FMT_CONSTEXPR basic_appender(detail::buffer<T>& buf) : container(&buf) {}\n\n  FMT_CONSTEXPR20 auto operator=(T c) -> basic_appender& {\n    container->push_back(c);\n    return *this;\n  }\n  FMT_CONSTEXPR20 auto operator*() -> basic_appender& { return *this; }\n  FMT_CONSTEXPR20 auto operator++() -> basic_appender& { return *this; }\n  FMT_CONSTEXPR20 auto operator++(int) -> basic_appender { return *this; }\n};\n\n// A formatting argument. Context is a template parameter for the compiled API\n// where output can be unbuffered.\ntemplate <typename Context> class basic_format_arg {\n private:\n  detail::value<Context> value_;\n  detail::type type_;\n\n  friend class basic_format_args<Context>;\n\n  using char_type = typename Context::char_type;\n\n public:\n  class handle {\n   private:\n    detail::custom_value<Context> custom_;\n\n   public:\n    explicit handle(detail::custom_value<Context> custom) : custom_(custom) {}\n\n    void format(parse_context<char_type>& parse_ctx, Context& ctx) const {\n      custom_.format(custom_.value, parse_ctx, ctx);\n    }\n  };\n\n  constexpr basic_format_arg() : type_(detail::type::none_type) {}\n  basic_format_arg(const detail::named_arg_info<char_type>* args, size_t size)\n      : value_(args, size) {}\n  template <typename T>\n  basic_format_arg(T&& val)\n      : value_(val), type_(detail::stored_type_constant<T, Context>::value) {}\n\n  constexpr explicit operator bool() const noexcept {\n    return type_ != detail::type::none_type;\n  }\n  auto type() const -> detail::type { return type_; }\n\n  /**\n   * Visits an argument dispatching to the appropriate visit method based on\n   * the argument type. For example, if the argument type is `double` then\n   * `vis(value)` will be called with the value of type `double`.\n   */\n  template <typename Visitor>\n  FMT_CONSTEXPR FMT_INLINE auto visit(Visitor&& vis) const -> decltype(vis(0)) {\n    using detail::map;\n    switch (type_) {\n    case detail::type::none_type:        break;\n    case detail::type::int_type:         return vis(value_.int_value);\n    case detail::type::uint_type:        return vis(value_.uint_value);\n    case detail::type::long_long_type:   return vis(value_.long_long_value);\n    case detail::type::ulong_long_type:  return vis(value_.ulong_long_value);\n    case detail::type::int128_type:      return vis(map(value_.int128_value));\n    case detail::type::uint128_type:     return vis(map(value_.uint128_value));\n    case detail::type::bool_type:        return vis(value_.bool_value);\n    case detail::type::char_type:        return vis(value_.char_value);\n    case detail::type::float_type:       return vis(value_.float_value);\n    case detail::type::double_type:      return vis(value_.double_value);\n    case detail::type::long_double_type: return vis(value_.long_double_value);\n    case detail::type::cstring_type:     return vis(value_.string.data);\n    case detail::type::string_type:      return vis(value_.string.str());\n    case detail::type::pointer_type:     return vis(value_.pointer);\n    case detail::type::custom_type:      return vis(handle(value_.custom));\n    }\n    return vis(monostate());\n  }\n\n  auto format_custom(const char_type* parse_begin,\n                     parse_context<char_type>& parse_ctx, Context& ctx)\n      -> bool {\n    if (type_ != detail::type::custom_type) return false;\n    parse_ctx.advance_to(parse_begin);\n    value_.custom.format(value_.custom.value, parse_ctx, ctx);\n    return true;\n  }\n};\n\n/**\n * A view of a collection of formatting arguments. To avoid lifetime issues it\n * should only be used as a parameter type in type-erased functions such as\n * `vformat`:\n *\n *     void vlog(fmt::string_view fmt, fmt::format_args args);  // OK\n *     fmt::format_args args = fmt::make_format_args();  // Dangling reference\n */\ntemplate <typename Context> class basic_format_args {\n private:\n  // A descriptor that contains information about formatting arguments.\n  // If the number of arguments is less or equal to max_packed_args then\n  // argument types are passed in the descriptor. This reduces binary code size\n  // per formatting function call.\n  unsigned long long desc_;\n  union {\n    // If is_packed() returns true then argument values are stored in values_;\n    // otherwise they are stored in args_. This is done to improve cache\n    // locality and reduce compiled code size since storing larger objects\n    // may require more code (at least on x86-64) even if the same amount of\n    // data is actually copied to stack. It saves ~10% on the bloat test.\n    const detail::value<Context>* values_;\n    const basic_format_arg<Context>* args_;\n  };\n\n  constexpr auto is_packed() const -> bool {\n    return (desc_ & detail::is_unpacked_bit) == 0;\n  }\n  constexpr auto has_named_args() const -> bool {\n    return (desc_ & detail::has_named_args_bit) != 0;\n  }\n\n  FMT_CONSTEXPR auto type(int index) const -> detail::type {\n    int shift = index * detail::packed_arg_bits;\n    unsigned mask = (1 << detail::packed_arg_bits) - 1;\n    return static_cast<detail::type>((desc_ >> shift) & mask);\n  }\n\n  template <int NUM_ARGS, int NUM_NAMED_ARGS, unsigned long long DESC>\n  using store =\n      detail::format_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC>;\n\n public:\n  using format_arg = basic_format_arg<Context>;\n\n  constexpr basic_format_args() : desc_(0), args_(nullptr) {}\n\n  /// Constructs a `basic_format_args` object from `format_arg_store`.\n  template <int NUM_ARGS, int NUM_NAMED_ARGS, unsigned long long DESC,\n            FMT_ENABLE_IF(NUM_ARGS <= detail::max_packed_args)>\n  constexpr FMT_ALWAYS_INLINE basic_format_args(\n      const store<NUM_ARGS, NUM_NAMED_ARGS, DESC>& s)\n      : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)),\n        values_(s.args) {}\n\n  template <int NUM_ARGS, int NUM_NAMED_ARGS, unsigned long long DESC,\n            FMT_ENABLE_IF(NUM_ARGS > detail::max_packed_args)>\n  constexpr basic_format_args(const store<NUM_ARGS, NUM_NAMED_ARGS, DESC>& s)\n      : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)),\n        args_(s.args) {}\n\n  /// Constructs a `basic_format_args` object from a dynamic list of arguments.\n  constexpr basic_format_args(const format_arg* args, int count,\n                              bool has_named = false)\n      : desc_(detail::is_unpacked_bit | detail::to_unsigned(count) |\n              (has_named ? +detail::has_named_args_bit : 0)),\n        args_(args) {}\n\n  /// Returns the argument with the specified id.\n  FMT_CONSTEXPR auto get(int id) const -> format_arg {\n    auto arg = format_arg();\n    if (!is_packed()) {\n      if (id < max_size()) arg = args_[id];\n      return arg;\n    }\n    if (static_cast<unsigned>(id) >= detail::max_packed_args) return arg;\n    arg.type_ = type(id);\n    if (arg.type_ != detail::type::none_type) arg.value_ = values_[id];\n    return arg;\n  }\n\n  template <typename Char>\n  auto get(basic_string_view<Char> name) const -> format_arg {\n    int id = get_id(name);\n    return id >= 0 ? get(id) : format_arg();\n  }\n\n  template <typename Char>\n  FMT_CONSTEXPR auto get_id(basic_string_view<Char> name) const -> int {\n    if (!has_named_args()) return -1;\n    const auto& named_args =\n        (is_packed() ? values_[-1] : args_[-1].value_).named_args;\n    for (size_t i = 0; i < named_args.size; ++i) {\n      if (named_args.data[i].name == name) return named_args.data[i].id;\n    }\n    return -1;\n  }\n\n  auto max_size() const -> int {\n    unsigned long long max_packed = detail::max_packed_args;\n    return static_cast<int>(is_packed() ? max_packed\n                                        : desc_ & ~detail::is_unpacked_bit);\n  }\n};\n\n// A formatting context.\nclass context {\n private:\n  appender out_;\n  format_args args_;\n  FMT_NO_UNIQUE_ADDRESS detail::locale_ref loc_;\n\n public:\n  /// The character type for the output.\n  using char_type = char;\n\n  using iterator = appender;\n  using format_arg = basic_format_arg<context>;\n  using parse_context_type FMT_DEPRECATED = parse_context<>;\n  template <typename T> using formatter_type FMT_DEPRECATED = formatter<T>;\n  enum { builtin_types = FMT_BUILTIN_TYPES };\n\n  /// Constructs a `context` object. References to the arguments are stored\n  /// in the object so make sure they have appropriate lifetimes.\n  FMT_CONSTEXPR context(iterator out, format_args args,\n                        detail::locale_ref loc = {})\n      : out_(out), args_(args), loc_(loc) {}\n  context(context&&) = default;\n  context(const context&) = delete;\n  void operator=(const context&) = delete;\n\n  FMT_CONSTEXPR auto arg(int id) const -> format_arg { return args_.get(id); }\n  inline auto arg(string_view name) const -> format_arg {\n    return args_.get(name);\n  }\n  FMT_CONSTEXPR auto arg_id(string_view name) const -> int {\n    return args_.get_id(name);\n  }\n  auto args() const -> const format_args& { return args_; }\n\n  // Returns an iterator to the beginning of the output range.\n  FMT_CONSTEXPR auto out() const -> iterator { return out_; }\n\n  // Advances the begin iterator to `it`.\n  FMT_CONSTEXPR void advance_to(iterator) {}\n\n  FMT_CONSTEXPR auto locale() const -> detail::locale_ref { return loc_; }\n};\n\ntemplate <typename Char = char> struct runtime_format_string {\n  basic_string_view<Char> str;\n};\n\n/**\n * Creates a runtime format string.\n *\n * **Example**:\n *\n *     // Check format string at runtime instead of compile-time.\n *     fmt::print(fmt::runtime(\"{:d}\"), \"I am not a number\");\n */\ninline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; }\n\n/// A compile-time format string. Use `format_string` in the public API to\n/// prevent type deduction.\ntemplate <typename... T> struct fstring {\n private:\n  static constexpr int num_static_named_args =\n      detail::count_static_named_args<T...>();\n\n  using checker = detail::format_string_checker<\n      char, static_cast<int>(sizeof...(T)), num_static_named_args,\n      num_static_named_args != detail::count_named_args<T...>()>;\n\n  using arg_pack = detail::arg_pack<T...>;\n\n public:\n  string_view str;\n  using t = fstring;\n\n  // Reports a compile-time error if S is not a valid format string for T.\n  template <size_t N>\n  FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) {\n    using namespace detail;\n    static_assert(count<(std::is_base_of<view, remove_reference_t<T>>::value &&\n                         std::is_reference<T>::value)...>() == 0,\n                  \"passing views as lvalues is disallowed\");\n    if (FMT_USE_CONSTEVAL) parse_format_string<char>(s, checker(s, arg_pack()));\n#ifdef FMT_ENFORCE_COMPILE_STRING\n    static_assert(\n        FMT_USE_CONSTEVAL && sizeof(s) != 0,\n        \"FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING\");\n#endif\n  }\n  template <typename S,\n            FMT_ENABLE_IF(std::is_convertible<const S&, string_view>::value)>\n  FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const S& s) : str(s) {\n    auto sv = string_view(str);\n    if (FMT_USE_CONSTEVAL)\n      detail::parse_format_string<char>(sv, checker(sv, arg_pack()));\n#ifdef FMT_ENFORCE_COMPILE_STRING\n    static_assert(\n        FMT_USE_CONSTEVAL && sizeof(s) != 0,\n        \"FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING\");\n#endif\n  }\n  template <typename S,\n            FMT_ENABLE_IF(std::is_base_of<detail::compile_string, S>::value&&\n                              std::is_same<typename S::char_type, char>::value)>\n  FMT_ALWAYS_INLINE fstring(const S&) : str(S()) {\n    FMT_CONSTEXPR auto sv = string_view(S());\n    FMT_CONSTEXPR int ignore =\n        (parse_format_string(sv, checker(sv, arg_pack())), 0);\n    detail::ignore_unused(ignore);\n  }\n  fstring(runtime_format_string<> fmt) : str(fmt.str) {}\n\n  // Returning by reference generates better code in debug mode.\n  FMT_ALWAYS_INLINE operator const string_view&() const { return str; }\n  auto get() const -> string_view { return str; }\n};\n\ntemplate <typename... T> using format_string = typename fstring<T...>::t;\n\ntemplate <typename T, typename Char = char>\nusing is_formattable = bool_constant<!std::is_same<\n    detail::mapped_t<conditional_t<std::is_void<T>::value, int*, T>, Char>,\n    void>::value>;\n#ifdef __cpp_concepts\ntemplate <typename T, typename Char = char>\nconcept formattable = is_formattable<remove_reference_t<T>, Char>::value;\n#endif\n\ntemplate <typename T, typename Char>\nusing has_formatter FMT_DEPRECATED = std::is_constructible<formatter<T, Char>>;\n\n// A formatter specialization for natively supported types.\ntemplate <typename T, typename Char>\nstruct formatter<T, Char,\n                 enable_if_t<detail::type_constant<T, Char>::value !=\n                             detail::type::custom_type>>\n    : detail::native_formatter<T, Char, detail::type_constant<T, Char>::value> {\n};\n\n/**\n * Constructs an object that stores references to arguments and can be\n * implicitly converted to `format_args`. `Context` can be omitted in which case\n * it defaults to `context`. See `arg` for lifetime considerations.\n */\n// Take arguments by lvalue references to avoid some lifetime issues, e.g.\n//   auto args = make_format_args(std::string());\ntemplate <typename Context = context, typename... T,\n          int NUM_ARGS = sizeof...(T),\n          int NUM_NAMED_ARGS = detail::count_named_args<T...>(),\n          unsigned long long DESC = detail::make_descriptor<Context, T...>()>\nconstexpr FMT_ALWAYS_INLINE auto make_format_args(T&... args)\n    -> detail::format_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC> {\n  // Suppress warnings for pathological types convertible to detail::value.\n  FMT_PRAGMA_GCC(diagnostic ignored \"-Wconversion\")\n  return {{args...}};\n}\n\ntemplate <typename... T>\nusing vargs =\n    detail::format_arg_store<context, sizeof...(T),\n                             detail::count_named_args<T...>(),\n                             detail::make_descriptor<context, T...>()>;\n\n/**\n * Returns a named argument to be used in a formatting function.\n * It should only be used in a call to a formatting function.\n *\n * **Example**:\n *\n *     fmt::print(\"The answer is {answer}.\", fmt::arg(\"answer\", 42));\n */\ntemplate <typename Char, typename T>\ninline auto arg(const Char* name, const T& arg) -> detail::named_arg<Char, T> {\n  return {name, arg};\n}\n\n/// Formats a string and writes the output to `out`.\ntemplate <typename OutputIt,\n          FMT_ENABLE_IF(detail::is_output_iterator<remove_cvref_t<OutputIt>,\n                                                   char>::value)>\nauto vformat_to(OutputIt&& out, string_view fmt, format_args args)\n    -> remove_cvref_t<OutputIt> {\n  auto&& buf = detail::get_buffer<char>(out);\n  detail::vformat_to(buf, fmt, args, {});\n  return detail::get_iterator(buf, out);\n}\n\n/**\n * Formats `args` according to specifications in `fmt`, writes the result to\n * the output iterator `out` and returns the iterator past the end of the output\n * range. `format_to` does not append a terminating null character.\n *\n * **Example**:\n *\n *     auto out = std::vector<char>();\n *     fmt::format_to(std::back_inserter(out), \"{}\", 42);\n */\ntemplate <typename OutputIt, typename... T,\n          FMT_ENABLE_IF(detail::is_output_iterator<remove_cvref_t<OutputIt>,\n                                                   char>::value)>\nFMT_INLINE auto format_to(OutputIt&& out, format_string<T...> fmt, T&&... args)\n    -> remove_cvref_t<OutputIt> {\n  return vformat_to(out, fmt.str, vargs<T...>{{args...}});\n}\n\ntemplate <typename OutputIt> struct format_to_n_result {\n  /// Iterator past the end of the output range.\n  OutputIt out;\n  /// Total (not truncated) output size.\n  size_t size;\n};\n\ntemplate <typename OutputIt, typename... T,\n          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>\nauto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args)\n    -> format_to_n_result<OutputIt> {\n  using traits = detail::fixed_buffer_traits;\n  auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);\n  detail::vformat_to(buf, fmt, args, {});\n  return {buf.out(), buf.count()};\n}\n\n/**\n * Formats `args` according to specifications in `fmt`, writes up to `n`\n * characters of the result to the output iterator `out` and returns the total\n * (not truncated) output size and the iterator past the end of the output\n * range. `format_to_n` does not append a terminating null character.\n */\ntemplate <typename OutputIt, typename... T,\n          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>\nFMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string<T...> fmt,\n                            T&&... args) -> format_to_n_result<OutputIt> {\n  return vformat_to_n(out, n, fmt.str, vargs<T...>{{args...}});\n}\n\nstruct format_to_result {\n  /// Pointer to just after the last successful write in the array.\n  char* out;\n  /// Specifies if the output was truncated.\n  bool truncated;\n\n  FMT_CONSTEXPR operator char*() const {\n    // Report truncation to prevent silent data loss.\n    if (truncated) report_error(\"output is truncated\");\n    return out;\n  }\n};\n\ntemplate <size_t N>\nauto vformat_to(char (&out)[N], string_view fmt, format_args args)\n    -> format_to_result {\n  auto result = vformat_to_n(out, N, fmt, args);\n  return {result.out, result.size > N};\n}\n\ntemplate <size_t N, typename... T>\nFMT_INLINE auto format_to(char (&out)[N], format_string<T...> fmt, T&&... args)\n    -> format_to_result {\n  auto result = vformat_to_n(out, N, fmt.str, vargs<T...>{{args...}});\n  return {result.out, result.size > N};\n}\n\n/// Returns the number of chars in the output of `format(fmt, args...)`.\ntemplate <typename... T>\nFMT_NODISCARD FMT_INLINE auto formatted_size(format_string<T...> fmt,\n                                             T&&... args) -> size_t {\n  auto buf = detail::counting_buffer<>();\n  detail::vformat_to(buf, fmt.str, vargs<T...>{{args...}}, {});\n  return buf.count();\n}\n\nFMT_API void vprint(string_view fmt, format_args args);\nFMT_API void vprint(FILE* f, string_view fmt, format_args args);\nFMT_API void vprintln(FILE* f, string_view fmt, format_args args);\nFMT_API void vprint_buffered(FILE* f, string_view fmt, format_args args);\n\n/**\n * Formats `args` according to specifications in `fmt` and writes the output\n * to `stdout`.\n *\n * **Example**:\n *\n *     fmt::print(\"The answer is {}.\", 42);\n */\ntemplate <typename... T>\nFMT_INLINE void print(format_string<T...> fmt, T&&... args) {\n  vargs<T...> va = {{args...}};\n  if (detail::const_check(!detail::use_utf8))\n    return detail::vprint_mojibake(stdout, fmt.str, va, false);\n  return detail::is_locking<T...>() ? vprint_buffered(stdout, fmt.str, va)\n                                    : vprint(fmt.str, va);\n}\n\n/**\n * Formats `args` according to specifications in `fmt` and writes the\n * output to the file `f`.\n *\n * **Example**:\n *\n *     fmt::print(stderr, \"Don't {}!\", \"panic\");\n */\ntemplate <typename... T>\nFMT_INLINE void print(FILE* f, format_string<T...> fmt, T&&... args) {\n  vargs<T...> va = {{args...}};\n  if (detail::const_check(!detail::use_utf8))\n    return detail::vprint_mojibake(f, fmt.str, va, false);\n  return detail::is_locking<T...>() ? vprint_buffered(f, fmt.str, va)\n                                    : vprint(f, fmt.str, va);\n}\n\n/// Formats `args` according to specifications in `fmt` and writes the output\n/// to the file `f` followed by a newline.\ntemplate <typename... T>\nFMT_INLINE void println(FILE* f, format_string<T...> fmt, T&&... args) {\n  vargs<T...> va = {{args...}};\n  return detail::const_check(detail::use_utf8)\n             ? vprintln(f, fmt.str, va)\n             : detail::vprint_mojibake(f, fmt.str, va, true);\n}\n\n/// Formats `args` according to specifications in `fmt` and writes the output\n/// to `stdout` followed by a newline.\ntemplate <typename... T>\nFMT_INLINE void println(format_string<T...> fmt, T&&... args) {\n  return fmt::println(stdout, fmt, static_cast<T&&>(args)...);\n}\n\nFMT_END_EXPORT\nFMT_PRAGMA_CLANG(diagnostic pop)\nFMT_PRAGMA_GCC(pop_options)\nFMT_END_NAMESPACE\n\n#ifdef FMT_HEADER_ONLY\n#  include \"format.h\"\n#endif\n#endif  // FMT_BASE_H_\n"
  },
  {
    "path": "dependencies/fmt/fmt/include/fmt/chrono.h",
    "content": "// Formatting library for C++ - chrono support\n//\n// Copyright (c) 2012 - present, Victor Zverovich\n// All rights reserved.\n//\n// For the license information refer to format.h.\n\n#ifndef FMT_CHRONO_H_\n#define FMT_CHRONO_H_\n\n#ifndef FMT_MODULE\n#  include <algorithm>\n#  include <chrono>\n#  include <cmath>    // std::isfinite\n#  include <cstring>  // std::memcpy\n#  include <ctime>\n#  include <iterator>\n#  include <locale>\n#  include <ostream>\n#  include <type_traits>\n#endif\n\n#include \"format.h\"\n\nnamespace fmt_detail {\nstruct time_zone {\n  template <typename Duration, typename T>\n  auto to_sys(T)\n      -> std::chrono::time_point<std::chrono::system_clock, Duration> {\n    return {};\n  }\n};\ntemplate <typename... T> inline auto current_zone(T...) -> time_zone* {\n  return nullptr;\n}\n\ntemplate <typename... T> inline void _tzset(T...) {}\n}  // namespace fmt_detail\n\nFMT_BEGIN_NAMESPACE\n\n// Enable safe chrono durations, unless explicitly disabled.\n#ifndef FMT_SAFE_DURATION_CAST\n#  define FMT_SAFE_DURATION_CAST 1\n#endif\n#if FMT_SAFE_DURATION_CAST\n\n// For conversion between std::chrono::durations without undefined\n// behaviour or erroneous results.\n// This is a stripped down version of duration_cast, for inclusion in fmt.\n// See https://github.com/pauldreik/safe_duration_cast\n//\n// Copyright Paul Dreik 2019\nnamespace safe_duration_cast {\n\ntemplate <typename To, typename From,\n          FMT_ENABLE_IF(!std::is_same<From, To>::value &&\n                        std::numeric_limits<From>::is_signed ==\n                            std::numeric_limits<To>::is_signed)>\nFMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec)\n    -> To {\n  ec = 0;\n  using F = std::numeric_limits<From>;\n  using T = std::numeric_limits<To>;\n  static_assert(F::is_integer, \"From must be integral\");\n  static_assert(T::is_integer, \"To must be integral\");\n\n  // A and B are both signed, or both unsigned.\n  if (detail::const_check(F::digits <= T::digits)) {\n    // From fits in To without any problem.\n  } else {\n    // From does not always fit in To, resort to a dynamic check.\n    if (from < (T::min)() || from > (T::max)()) {\n      // outside range.\n      ec = 1;\n      return {};\n    }\n  }\n  return static_cast<To>(from);\n}\n\n/// Converts From to To, without loss. If the dynamic value of from\n/// can't be converted to To without loss, ec is set.\ntemplate <typename To, typename From,\n          FMT_ENABLE_IF(!std::is_same<From, To>::value &&\n                        std::numeric_limits<From>::is_signed !=\n                            std::numeric_limits<To>::is_signed)>\nFMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec)\n    -> To {\n  ec = 0;\n  using F = std::numeric_limits<From>;\n  using T = std::numeric_limits<To>;\n  static_assert(F::is_integer, \"From must be integral\");\n  static_assert(T::is_integer, \"To must be integral\");\n\n  if (detail::const_check(F::is_signed && !T::is_signed)) {\n    // From may be negative, not allowed!\n    if (fmt::detail::is_negative(from)) {\n      ec = 1;\n      return {};\n    }\n    // From is positive. Can it always fit in To?\n    if (detail::const_check(F::digits > T::digits) &&\n        from > static_cast<From>(detail::max_value<To>())) {\n      ec = 1;\n      return {};\n    }\n  }\n\n  if (detail::const_check(!F::is_signed && T::is_signed &&\n                          F::digits >= T::digits) &&\n      from > static_cast<From>(detail::max_value<To>())) {\n    ec = 1;\n    return {};\n  }\n  return static_cast<To>(from);  // Lossless conversion.\n}\n\ntemplate <typename To, typename From,\n          FMT_ENABLE_IF(std::is_same<From, To>::value)>\nFMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec)\n    -> To {\n  ec = 0;\n  return from;\n}  // function\n\n// clang-format off\n/**\n * converts From to To if possible, otherwise ec is set.\n *\n * input                            |    output\n * ---------------------------------|---------------\n * NaN                              | NaN\n * Inf                              | Inf\n * normal, fits in output           | converted (possibly lossy)\n * normal, does not fit in output   | ec is set\n * subnormal                        | best effort\n * -Inf                             | -Inf\n */\n// clang-format on\ntemplate <typename To, typename From,\n          FMT_ENABLE_IF(!std::is_same<From, To>::value)>\nFMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To {\n  ec = 0;\n  using T = std::numeric_limits<To>;\n  static_assert(std::is_floating_point<From>::value, \"From must be floating\");\n  static_assert(std::is_floating_point<To>::value, \"To must be floating\");\n\n  // catch the only happy case\n  if (std::isfinite(from)) {\n    if (from >= T::lowest() && from <= (T::max)()) {\n      return static_cast<To>(from);\n    }\n    // not within range.\n    ec = 1;\n    return {};\n  }\n\n  // nan and inf will be preserved\n  return static_cast<To>(from);\n}  // function\n\ntemplate <typename To, typename From,\n          FMT_ENABLE_IF(std::is_same<From, To>::value)>\nFMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To {\n  ec = 0;\n  static_assert(std::is_floating_point<From>::value, \"From must be floating\");\n  return from;\n}\n\n/// Safe duration_cast between floating point durations\ntemplate <typename To, typename FromRep, typename FromPeriod,\n          FMT_ENABLE_IF(std::is_floating_point<FromRep>::value),\n          FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)>\nauto safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,\n                        int& ec) -> To {\n  using From = std::chrono::duration<FromRep, FromPeriod>;\n  ec = 0;\n  if (std::isnan(from.count())) {\n    // nan in, gives nan out. easy.\n    return To{std::numeric_limits<typename To::rep>::quiet_NaN()};\n  }\n  // maybe we should also check if from is denormal, and decide what to do about\n  // it.\n\n  // +-inf should be preserved.\n  if (std::isinf(from.count())) {\n    return To{from.count()};\n  }\n\n  // the basic idea is that we need to convert from count() in the from type\n  // to count() in the To type, by multiplying it with this:\n  struct Factor\n      : std::ratio_divide<typename From::period, typename To::period> {};\n\n  static_assert(Factor::num > 0, \"num must be positive\");\n  static_assert(Factor::den > 0, \"den must be positive\");\n\n  // the conversion is like this: multiply from.count() with Factor::num\n  // /Factor::den and convert it to To::rep, all this without\n  // overflow/underflow. let's start by finding a suitable type that can hold\n  // both To, From and Factor::num\n  using IntermediateRep =\n      typename std::common_type<typename From::rep, typename To::rep,\n                                decltype(Factor::num)>::type;\n\n  // force conversion of From::rep -> IntermediateRep to be safe,\n  // even if it will never happen be narrowing in this context.\n  IntermediateRep count =\n      safe_float_conversion<IntermediateRep>(from.count(), ec);\n  if (ec) {\n    return {};\n  }\n\n  // multiply with Factor::num without overflow or underflow\n  if (detail::const_check(Factor::num != 1)) {\n    constexpr auto max1 = detail::max_value<IntermediateRep>() /\n                          static_cast<IntermediateRep>(Factor::num);\n    if (count > max1) {\n      ec = 1;\n      return {};\n    }\n    constexpr auto min1 = std::numeric_limits<IntermediateRep>::lowest() /\n                          static_cast<IntermediateRep>(Factor::num);\n    if (count < min1) {\n      ec = 1;\n      return {};\n    }\n    count *= static_cast<IntermediateRep>(Factor::num);\n  }\n\n  // this can't go wrong, right? den>0 is checked earlier.\n  if (detail::const_check(Factor::den != 1)) {\n    using common_t = typename std::common_type<IntermediateRep, intmax_t>::type;\n    count /= static_cast<common_t>(Factor::den);\n  }\n\n  // convert to the to type, safely\n  using ToRep = typename To::rep;\n\n  const ToRep tocount = safe_float_conversion<ToRep>(count, ec);\n  if (ec) {\n    return {};\n  }\n  return To{tocount};\n}\n}  // namespace safe_duration_cast\n#endif\n\nnamespace detail {\n\n// Check if std::chrono::utc_time is available.\n#ifdef FMT_USE_UTC_TIME\n// Use the provided definition.\n#elif defined(__cpp_lib_chrono)\n#  define FMT_USE_UTC_TIME (__cpp_lib_chrono >= 201907L)\n#else\n#  define FMT_USE_UTC_TIME 0\n#endif\n#if FMT_USE_UTC_TIME\nusing utc_clock = std::chrono::utc_clock;\n#else\nstruct utc_clock {\n  template <typename T> void to_sys(T);\n};\n#endif\n\n// Check if std::chrono::local_time is available.\n#ifdef FMT_USE_LOCAL_TIME\n// Use the provided definition.\n#elif defined(__cpp_lib_chrono)\n#  define FMT_USE_LOCAL_TIME (__cpp_lib_chrono >= 201907L)\n#else\n#  define FMT_USE_LOCAL_TIME 0\n#endif\n#if FMT_USE_LOCAL_TIME\nusing local_t = std::chrono::local_t;\n#else\nstruct local_t {};\n#endif\n\n}  // namespace detail\n\ntemplate <typename Duration>\nusing sys_time = std::chrono::time_point<std::chrono::system_clock, Duration>;\n\ntemplate <typename Duration>\nusing utc_time = std::chrono::time_point<detail::utc_clock, Duration>;\n\ntemplate <class Duration>\nusing local_time = std::chrono::time_point<detail::local_t, Duration>;\n\nnamespace detail {\n\n// Prevents expansion of a preceding token as a function-style macro.\n// Usage: f FMT_NOMACRO()\n#define FMT_NOMACRO\n\ntemplate <typename T = void> struct null {};\ninline auto localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); }\ninline auto localtime_s(...) -> null<> { return null<>(); }\ninline auto gmtime_r(...) -> null<> { return null<>(); }\ninline auto gmtime_s(...) -> null<> { return null<>(); }\n\n// It is defined here and not in ostream.h because the latter has expensive\n// includes.\ntemplate <typename StreamBuf> class formatbuf : public StreamBuf {\n private:\n  using char_type = typename StreamBuf::char_type;\n  using streamsize = decltype(std::declval<StreamBuf>().sputn(nullptr, 0));\n  using int_type = typename StreamBuf::int_type;\n  using traits_type = typename StreamBuf::traits_type;\n\n  buffer<char_type>& buffer_;\n\n public:\n  explicit formatbuf(buffer<char_type>& buf) : buffer_(buf) {}\n\n protected:\n  // The put area is always empty. This makes the implementation simpler and has\n  // the advantage that the streambuf and the buffer are always in sync and\n  // sputc never writes into uninitialized memory. A disadvantage is that each\n  // call to sputc always results in a (virtual) call to overflow. There is no\n  // disadvantage here for sputn since this always results in a call to xsputn.\n\n  auto overflow(int_type ch) -> int_type override {\n    if (!traits_type::eq_int_type(ch, traits_type::eof()))\n      buffer_.push_back(static_cast<char_type>(ch));\n    return ch;\n  }\n\n  auto xsputn(const char_type* s, streamsize count) -> streamsize override {\n    buffer_.append(s, s + count);\n    return count;\n  }\n};\n\ninline auto get_classic_locale() -> const std::locale& {\n  static const auto& locale = std::locale::classic();\n  return locale;\n}\n\ntemplate <typename CodeUnit> struct codecvt_result {\n  static constexpr const size_t max_size = 32;\n  CodeUnit buf[max_size];\n  CodeUnit* end;\n};\n\ntemplate <typename CodeUnit>\nvoid write_codecvt(codecvt_result<CodeUnit>& out, string_view in,\n                   const std::locale& loc) {\n  FMT_PRAGMA_CLANG(diagnostic push)\n  FMT_PRAGMA_CLANG(diagnostic ignored \"-Wdeprecated\")\n  auto& f = std::use_facet<std::codecvt<CodeUnit, char, std::mbstate_t>>(loc);\n  FMT_PRAGMA_CLANG(diagnostic pop)\n  auto mb = std::mbstate_t();\n  const char* from_next = nullptr;\n  auto result = f.in(mb, in.begin(), in.end(), from_next, std::begin(out.buf),\n                     std::end(out.buf), out.end);\n  if (result != std::codecvt_base::ok)\n    FMT_THROW(format_error(\"failed to format time\"));\n}\n\ntemplate <typename OutputIt>\nauto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc)\n    -> OutputIt {\n  if (const_check(detail::use_utf8) && loc != get_classic_locale()) {\n    // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and\n    // gcc-4.\n#if FMT_MSC_VERSION != 0 ||  \\\n    (defined(__GLIBCXX__) && \\\n     (!defined(_GLIBCXX_USE_DUAL_ABI) || _GLIBCXX_USE_DUAL_ABI == 0))\n    // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5\n    // and newer.\n    using code_unit = wchar_t;\n#else\n    using code_unit = char32_t;\n#endif\n\n    using unit_t = codecvt_result<code_unit>;\n    unit_t unit;\n    write_codecvt(unit, in, loc);\n    // In UTF-8 is used one to four one-byte code units.\n    auto u =\n        to_utf8<code_unit, basic_memory_buffer<char, unit_t::max_size * 4>>();\n    if (!u.convert({unit.buf, to_unsigned(unit.end - unit.buf)}))\n      FMT_THROW(format_error(\"failed to format time\"));\n    return copy<char>(u.c_str(), u.c_str() + u.size(), out);\n  }\n  return copy<char>(in.data(), in.data() + in.size(), out);\n}\n\ntemplate <typename Char, typename OutputIt,\n          FMT_ENABLE_IF(!std::is_same<Char, char>::value)>\nauto write_tm_str(OutputIt out, string_view sv, const std::locale& loc)\n    -> OutputIt {\n  codecvt_result<Char> unit;\n  write_codecvt(unit, sv, loc);\n  return copy<Char>(unit.buf, unit.end, out);\n}\n\ntemplate <typename Char, typename OutputIt,\n          FMT_ENABLE_IF(std::is_same<Char, char>::value)>\nauto write_tm_str(OutputIt out, string_view sv, const std::locale& loc)\n    -> OutputIt {\n  return write_encoded_tm_str(out, sv, loc);\n}\n\ntemplate <typename Char>\ninline void do_write(buffer<Char>& buf, const std::tm& time,\n                     const std::locale& loc, char format, char modifier) {\n  auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);\n  auto&& os = std::basic_ostream<Char>(&format_buf);\n  os.imbue(loc);\n  const auto& facet = std::use_facet<std::time_put<Char>>(loc);\n  auto end = facet.put(os, os, Char(' '), &time, format, modifier);\n  if (end.failed()) FMT_THROW(format_error(\"failed to format time\"));\n}\n\ntemplate <typename Char, typename OutputIt,\n          FMT_ENABLE_IF(!std::is_same<Char, char>::value)>\nauto write(OutputIt out, const std::tm& time, const std::locale& loc,\n           char format, char modifier = 0) -> OutputIt {\n  auto&& buf = get_buffer<Char>(out);\n  do_write<Char>(buf, time, loc, format, modifier);\n  return get_iterator(buf, out);\n}\n\ntemplate <typename Char, typename OutputIt,\n          FMT_ENABLE_IF(std::is_same<Char, char>::value)>\nauto write(OutputIt out, const std::tm& time, const std::locale& loc,\n           char format, char modifier = 0) -> OutputIt {\n  auto&& buf = basic_memory_buffer<Char>();\n  do_write<char>(buf, time, loc, format, modifier);\n  return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc);\n}\n\ntemplate <typename Rep1, typename Rep2>\nstruct is_same_arithmetic_type\n    : public std::integral_constant<bool,\n                                    (std::is_integral<Rep1>::value &&\n                                     std::is_integral<Rep2>::value) ||\n                                        (std::is_floating_point<Rep1>::value &&\n                                         std::is_floating_point<Rep2>::value)> {\n};\n\nFMT_NORETURN inline void throw_duration_error() {\n  FMT_THROW(format_error(\"cannot format duration\"));\n}\n\n// Cast one integral duration to another with an overflow check.\ntemplate <typename To, typename FromRep, typename FromPeriod,\n          FMT_ENABLE_IF(std::is_integral<FromRep>::value&&\n                            std::is_integral<typename To::rep>::value)>\nauto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {\n#if !FMT_SAFE_DURATION_CAST\n  return std::chrono::duration_cast<To>(from);\n#else\n  // The conversion factor: to.count() == factor * from.count().\n  using factor = std::ratio_divide<FromPeriod, typename To::period>;\n\n  using common_rep = typename std::common_type<FromRep, typename To::rep,\n                                               decltype(factor::num)>::type;\n\n  int ec = 0;\n  auto count = safe_duration_cast::lossless_integral_conversion<common_rep>(\n      from.count(), ec);\n  if (ec) throw_duration_error();\n\n  // Multiply from.count() by factor and check for overflow.\n  if (const_check(factor::num != 1)) {\n    if (count > max_value<common_rep>() / factor::num) throw_duration_error();\n    const auto min = (std::numeric_limits<common_rep>::min)() / factor::num;\n    if (const_check(!std::is_unsigned<common_rep>::value) && count < min)\n      throw_duration_error();\n    count *= factor::num;\n  }\n  if (const_check(factor::den != 1)) count /= factor::den;\n  auto to =\n      To(safe_duration_cast::lossless_integral_conversion<typename To::rep>(\n          count, ec));\n  if (ec) throw_duration_error();\n  return to;\n#endif\n}\n\ntemplate <typename To, typename FromRep, typename FromPeriod,\n          FMT_ENABLE_IF(std::is_floating_point<FromRep>::value&&\n                            std::is_floating_point<typename To::rep>::value)>\nauto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {\n#if FMT_SAFE_DURATION_CAST\n  // Throwing version of safe_duration_cast is only available for\n  // integer to integer or float to float casts.\n  int ec;\n  To to = safe_duration_cast::safe_duration_cast<To>(from, ec);\n  if (ec) throw_duration_error();\n  return to;\n#else\n  // Standard duration cast, may overflow.\n  return std::chrono::duration_cast<To>(from);\n#endif\n}\n\ntemplate <\n    typename To, typename FromRep, typename FromPeriod,\n    FMT_ENABLE_IF(!is_same_arithmetic_type<FromRep, typename To::rep>::value)>\nauto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {\n  // Mixed integer <-> float cast is not supported by safe_duration_cast.\n  return std::chrono::duration_cast<To>(from);\n}\n\ntemplate <typename Duration>\nauto to_time_t(sys_time<Duration> time_point) -> std::time_t {\n  // Cannot use std::chrono::system_clock::to_time_t since this would first\n  // require a cast to std::chrono::system_clock::time_point, which could\n  // overflow.\n  return detail::duration_cast<std::chrono::duration<std::time_t>>(\n             time_point.time_since_epoch())\n      .count();\n}\n\n// Workaround a bug in libstdc++ which sets __cpp_lib_chrono to 201907 without\n// providing current_zone(): https://github.com/fmtlib/fmt/issues/4160.\ntemplate <typename T> FMT_CONSTEXPR auto has_current_zone() -> bool {\n  using namespace std::chrono;\n  using namespace fmt_detail;\n  return !std::is_same<decltype(current_zone()), fmt_detail::time_zone*>::value;\n}\n}  // namespace detail\n\nFMT_BEGIN_EXPORT\n\n/**\n * Converts given time since epoch as `std::time_t` value into calendar time,\n * expressed in local time. Unlike `std::localtime`, this function is\n * thread-safe on most platforms.\n */\ninline auto localtime(std::time_t time) -> std::tm {\n  struct dispatcher {\n    std::time_t time_;\n    std::tm tm_;\n\n    inline dispatcher(std::time_t t) : time_(t) {}\n\n    inline auto run() -> bool {\n      using namespace fmt::detail;\n      return handle(localtime_r(&time_, &tm_));\n    }\n\n    inline auto handle(std::tm* tm) -> bool { return tm != nullptr; }\n\n    inline auto handle(detail::null<>) -> bool {\n      using namespace fmt::detail;\n      return fallback(localtime_s(&tm_, &time_));\n    }\n\n    inline auto fallback(int res) -> bool { return res == 0; }\n\n#if !FMT_MSC_VERSION\n    inline auto fallback(detail::null<>) -> bool {\n      using namespace fmt::detail;\n      std::tm* tm = std::localtime(&time_);\n      if (tm) tm_ = *tm;\n      return tm != nullptr;\n    }\n#endif\n  };\n  dispatcher lt(time);\n  // Too big time values may be unsupported.\n  if (!lt.run()) FMT_THROW(format_error(\"time_t value out of range\"));\n  return lt.tm_;\n}\n\n#if FMT_USE_LOCAL_TIME\ntemplate <typename Duration,\n          FMT_ENABLE_IF(detail::has_current_zone<Duration>())>\ninline auto localtime(std::chrono::local_time<Duration> time) -> std::tm {\n  using namespace std::chrono;\n  using namespace fmt_detail;\n  return localtime(detail::to_time_t(current_zone()->to_sys<Duration>(time)));\n}\n#endif\n\n/**\n * Converts given time since epoch as `std::time_t` value into calendar time,\n * expressed in Coordinated Universal Time (UTC). Unlike `std::gmtime`, this\n * function is thread-safe on most platforms.\n */\ninline auto gmtime(std::time_t time) -> std::tm {\n  struct dispatcher {\n    std::time_t time_;\n    std::tm tm_;\n\n    inline dispatcher(std::time_t t) : time_(t) {}\n\n    inline auto run() -> bool {\n      using namespace fmt::detail;\n      return handle(gmtime_r(&time_, &tm_));\n    }\n\n    inline auto handle(std::tm* tm) -> bool { return tm != nullptr; }\n\n    inline auto handle(detail::null<>) -> bool {\n      using namespace fmt::detail;\n      return fallback(gmtime_s(&tm_, &time_));\n    }\n\n    inline auto fallback(int res) -> bool { return res == 0; }\n\n#if !FMT_MSC_VERSION\n    inline auto fallback(detail::null<>) -> bool {\n      std::tm* tm = std::gmtime(&time_);\n      if (tm) tm_ = *tm;\n      return tm != nullptr;\n    }\n#endif\n  };\n  auto gt = dispatcher(time);\n  // Too big time values may be unsupported.\n  if (!gt.run()) FMT_THROW(format_error(\"time_t value out of range\"));\n  return gt.tm_;\n}\n\ntemplate <typename Duration>\ninline auto gmtime(sys_time<Duration> time_point) -> std::tm {\n  return gmtime(detail::to_time_t(time_point));\n}\n\nnamespace detail {\n\n// Writes two-digit numbers a, b and c separated by sep to buf.\n// The method by Pavel Novikov based on\n// https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/.\ninline void write_digit2_separated(char* buf, unsigned a, unsigned b,\n                                   unsigned c, char sep) {\n  unsigned long long digits =\n      a | (b << 24) | (static_cast<unsigned long long>(c) << 48);\n  // Convert each value to BCD.\n  // We have x = a * 10 + b and we want to convert it to BCD y = a * 16 + b.\n  // The difference is\n  //   y - x = a * 6\n  // a can be found from x:\n  //   a = floor(x / 10)\n  // then\n  //   y = x + a * 6 = x + floor(x / 10) * 6\n  // floor(x / 10) is (x * 205) >> 11 (needs 16 bits).\n  digits += (((digits * 205) >> 11) & 0x000f00000f00000f) * 6;\n  // Put low nibbles to high bytes and high nibbles to low bytes.\n  digits = ((digits & 0x00f00000f00000f0) >> 4) |\n           ((digits & 0x000f00000f00000f) << 8);\n  auto usep = static_cast<unsigned long long>(sep);\n  // Add ASCII '0' to each digit byte and insert separators.\n  digits |= 0x3030003030003030 | (usep << 16) | (usep << 40);\n\n  constexpr const size_t len = 8;\n  if (const_check(is_big_endian())) {\n    char tmp[len];\n    std::memcpy(tmp, &digits, len);\n    std::reverse_copy(tmp, tmp + len, buf);\n  } else {\n    std::memcpy(buf, &digits, len);\n  }\n}\n\ntemplate <typename Period>\nFMT_CONSTEXPR inline auto get_units() -> const char* {\n  if (std::is_same<Period, std::atto>::value) return \"as\";\n  if (std::is_same<Period, std::femto>::value) return \"fs\";\n  if (std::is_same<Period, std::pico>::value) return \"ps\";\n  if (std::is_same<Period, std::nano>::value) return \"ns\";\n  if (std::is_same<Period, std::micro>::value)\n    return detail::use_utf8 ? \"µs\" : \"us\";\n  if (std::is_same<Period, std::milli>::value) return \"ms\";\n  if (std::is_same<Period, std::centi>::value) return \"cs\";\n  if (std::is_same<Period, std::deci>::value) return \"ds\";\n  if (std::is_same<Period, std::ratio<1>>::value) return \"s\";\n  if (std::is_same<Period, std::deca>::value) return \"das\";\n  if (std::is_same<Period, std::hecto>::value) return \"hs\";\n  if (std::is_same<Period, std::kilo>::value) return \"ks\";\n  if (std::is_same<Period, std::mega>::value) return \"Ms\";\n  if (std::is_same<Period, std::giga>::value) return \"Gs\";\n  if (std::is_same<Period, std::tera>::value) return \"Ts\";\n  if (std::is_same<Period, std::peta>::value) return \"Ps\";\n  if (std::is_same<Period, std::exa>::value) return \"Es\";\n  if (std::is_same<Period, std::ratio<60>>::value) return \"min\";\n  if (std::is_same<Period, std::ratio<3600>>::value) return \"h\";\n  if (std::is_same<Period, std::ratio<86400>>::value) return \"d\";\n  return nullptr;\n}\n\nenum class numeric_system {\n  standard,\n  // Alternative numeric system, e.g. 十二 instead of 12 in ja_JP locale.\n  alternative\n};\n\n// Glibc extensions for formatting numeric values.\nenum class pad_type {\n  // Pad a numeric result string with zeros (the default).\n  zero,\n  // Do not pad a numeric result string.\n  none,\n  // Pad a numeric result string with spaces.\n  space,\n};\n\ntemplate <typename OutputIt>\nauto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt {\n  if (pad == pad_type::none) return out;\n  return detail::fill_n(out, width, pad == pad_type::space ? ' ' : '0');\n}\n\ntemplate <typename OutputIt>\nauto write_padding(OutputIt out, pad_type pad) -> OutputIt {\n  if (pad != pad_type::none) *out++ = pad == pad_type::space ? ' ' : '0';\n  return out;\n}\n\n// Parses a put_time-like format string and invokes handler actions.\ntemplate <typename Char, typename Handler>\nFMT_CONSTEXPR auto parse_chrono_format(const Char* begin, const Char* end,\n                                       Handler&& handler) -> const Char* {\n  if (begin == end || *begin == '}') return begin;\n  if (*begin != '%') FMT_THROW(format_error(\"invalid format\"));\n  auto ptr = begin;\n  while (ptr != end) {\n    pad_type pad = pad_type::zero;\n    auto c = *ptr;\n    if (c == '}') break;\n    if (c != '%') {\n      ++ptr;\n      continue;\n    }\n    if (begin != ptr) handler.on_text(begin, ptr);\n    ++ptr;  // consume '%'\n    if (ptr == end) FMT_THROW(format_error(\"invalid format\"));\n    c = *ptr;\n    switch (c) {\n    case '_':\n      pad = pad_type::space;\n      ++ptr;\n      break;\n    case '-':\n      pad = pad_type::none;\n      ++ptr;\n      break;\n    }\n    if (ptr == end) FMT_THROW(format_error(\"invalid format\"));\n    c = *ptr++;\n    switch (c) {\n    case '%': handler.on_text(ptr - 1, ptr); break;\n    case 'n': {\n      const Char newline[] = {'\\n'};\n      handler.on_text(newline, newline + 1);\n      break;\n    }\n    case 't': {\n      const Char tab[] = {'\\t'};\n      handler.on_text(tab, tab + 1);\n      break;\n    }\n    // Year:\n    case 'Y': handler.on_year(numeric_system::standard, pad); break;\n    case 'y': handler.on_short_year(numeric_system::standard); break;\n    case 'C': handler.on_century(numeric_system::standard); break;\n    case 'G': handler.on_iso_week_based_year(); break;\n    case 'g': handler.on_iso_week_based_short_year(); break;\n    // Day of the week:\n    case 'a': handler.on_abbr_weekday(); break;\n    case 'A': handler.on_full_weekday(); break;\n    case 'w': handler.on_dec0_weekday(numeric_system::standard); break;\n    case 'u': handler.on_dec1_weekday(numeric_system::standard); break;\n    // Month:\n    case 'b':\n    case 'h': handler.on_abbr_month(); break;\n    case 'B': handler.on_full_month(); break;\n    case 'm': handler.on_dec_month(numeric_system::standard, pad); break;\n    // Day of the year/month:\n    case 'U':\n      handler.on_dec0_week_of_year(numeric_system::standard, pad);\n      break;\n    case 'W':\n      handler.on_dec1_week_of_year(numeric_system::standard, pad);\n      break;\n    case 'V': handler.on_iso_week_of_year(numeric_system::standard, pad); break;\n    case 'j': handler.on_day_of_year(pad); break;\n    case 'd': handler.on_day_of_month(numeric_system::standard, pad); break;\n    case 'e':\n      handler.on_day_of_month(numeric_system::standard, pad_type::space);\n      break;\n    // Hour, minute, second:\n    case 'H': handler.on_24_hour(numeric_system::standard, pad); break;\n    case 'I': handler.on_12_hour(numeric_system::standard, pad); break;\n    case 'M': handler.on_minute(numeric_system::standard, pad); break;\n    case 'S': handler.on_second(numeric_system::standard, pad); break;\n    // Other:\n    case 'c': handler.on_datetime(numeric_system::standard); break;\n    case 'x': handler.on_loc_date(numeric_system::standard); break;\n    case 'X': handler.on_loc_time(numeric_system::standard); break;\n    case 'D': handler.on_us_date(); break;\n    case 'F': handler.on_iso_date(); break;\n    case 'r': handler.on_12_hour_time(); break;\n    case 'R': handler.on_24_hour_time(); break;\n    case 'T': handler.on_iso_time(); break;\n    case 'p': handler.on_am_pm(); break;\n    case 'Q': handler.on_duration_value(); break;\n    case 'q': handler.on_duration_unit(); break;\n    case 'z': handler.on_utc_offset(numeric_system::standard); break;\n    case 'Z': handler.on_tz_name(); break;\n    // Alternative representation:\n    case 'E': {\n      if (ptr == end) FMT_THROW(format_error(\"invalid format\"));\n      c = *ptr++;\n      switch (c) {\n      case 'Y': handler.on_year(numeric_system::alternative, pad); break;\n      case 'y': handler.on_offset_year(); break;\n      case 'C': handler.on_century(numeric_system::alternative); break;\n      case 'c': handler.on_datetime(numeric_system::alternative); break;\n      case 'x': handler.on_loc_date(numeric_system::alternative); break;\n      case 'X': handler.on_loc_time(numeric_system::alternative); break;\n      case 'z': handler.on_utc_offset(numeric_system::alternative); break;\n      default:  FMT_THROW(format_error(\"invalid format\"));\n      }\n      break;\n    }\n    case 'O':\n      if (ptr == end) FMT_THROW(format_error(\"invalid format\"));\n      c = *ptr++;\n      switch (c) {\n      case 'y': handler.on_short_year(numeric_system::alternative); break;\n      case 'm': handler.on_dec_month(numeric_system::alternative, pad); break;\n      case 'U':\n        handler.on_dec0_week_of_year(numeric_system::alternative, pad);\n        break;\n      case 'W':\n        handler.on_dec1_week_of_year(numeric_system::alternative, pad);\n        break;\n      case 'V':\n        handler.on_iso_week_of_year(numeric_system::alternative, pad);\n        break;\n      case 'd':\n        handler.on_day_of_month(numeric_system::alternative, pad);\n        break;\n      case 'e':\n        handler.on_day_of_month(numeric_system::alternative, pad_type::space);\n        break;\n      case 'w': handler.on_dec0_weekday(numeric_system::alternative); break;\n      case 'u': handler.on_dec1_weekday(numeric_system::alternative); break;\n      case 'H': handler.on_24_hour(numeric_system::alternative, pad); break;\n      case 'I': handler.on_12_hour(numeric_system::alternative, pad); break;\n      case 'M': handler.on_minute(numeric_system::alternative, pad); break;\n      case 'S': handler.on_second(numeric_system::alternative, pad); break;\n      case 'z': handler.on_utc_offset(numeric_system::alternative); break;\n      default:  FMT_THROW(format_error(\"invalid format\"));\n      }\n      break;\n    default: FMT_THROW(format_error(\"invalid format\"));\n    }\n    begin = ptr;\n  }\n  if (begin != ptr) handler.on_text(begin, ptr);\n  return ptr;\n}\n\ntemplate <typename Derived> struct null_chrono_spec_handler {\n  FMT_CONSTEXPR void unsupported() {\n    static_cast<Derived*>(this)->unsupported();\n  }\n  FMT_CONSTEXPR void on_year(numeric_system, pad_type) { unsupported(); }\n  FMT_CONSTEXPR void on_short_year(numeric_system) { unsupported(); }\n  FMT_CONSTEXPR void on_offset_year() { unsupported(); }\n  FMT_CONSTEXPR void on_century(numeric_system) { unsupported(); }\n  FMT_CONSTEXPR void on_iso_week_based_year() { unsupported(); }\n  FMT_CONSTEXPR void on_iso_week_based_short_year() { unsupported(); }\n  FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); }\n  FMT_CONSTEXPR void on_full_weekday() { unsupported(); }\n  FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); }\n  FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); }\n  FMT_CONSTEXPR void on_abbr_month() { unsupported(); }\n  FMT_CONSTEXPR void on_full_month() { unsupported(); }\n  FMT_CONSTEXPR void on_dec_month(numeric_system, pad_type) { unsupported(); }\n  FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) {\n    unsupported();\n  }\n  FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) {\n    unsupported();\n  }\n  FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) {\n    unsupported();\n  }\n  FMT_CONSTEXPR void on_day_of_year(pad_type) { unsupported(); }\n  FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) {\n    unsupported();\n  }\n  FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); }\n  FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); }\n  FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); }\n  FMT_CONSTEXPR void on_second(numeric_system) { unsupported(); }\n  FMT_CONSTEXPR void on_datetime(numeric_system) { unsupported(); }\n  FMT_CONSTEXPR void on_loc_date(numeric_system) { unsupported(); }\n  FMT_CONSTEXPR void on_loc_time(numeric_system) { unsupported(); }\n  FMT_CONSTEXPR void on_us_date() { unsupported(); }\n  FMT_CONSTEXPR void on_iso_date() { unsupported(); }\n  FMT_CONSTEXPR void on_12_hour_time() { unsupported(); }\n  FMT_CONSTEXPR void on_24_hour_time() { unsupported(); }\n  FMT_CONSTEXPR void on_iso_time() { unsupported(); }\n  FMT_CONSTEXPR void on_am_pm() { unsupported(); }\n  FMT_CONSTEXPR void on_duration_value() { unsupported(); }\n  FMT_CONSTEXPR void on_duration_unit() { unsupported(); }\n  FMT_CONSTEXPR void on_utc_offset(numeric_system) { unsupported(); }\n  FMT_CONSTEXPR void on_tz_name() { unsupported(); }\n};\n\nstruct tm_format_checker : null_chrono_spec_handler<tm_format_checker> {\n  FMT_NORETURN inline void unsupported() {\n    FMT_THROW(format_error(\"no format\"));\n  }\n\n  template <typename Char>\n  FMT_CONSTEXPR void on_text(const Char*, const Char*) {}\n  FMT_CONSTEXPR void on_year(numeric_system, pad_type) {}\n  FMT_CONSTEXPR void on_short_year(numeric_system) {}\n  FMT_CONSTEXPR void on_offset_year() {}\n  FMT_CONSTEXPR void on_century(numeric_system) {}\n  FMT_CONSTEXPR void on_iso_week_based_year() {}\n  FMT_CONSTEXPR void on_iso_week_based_short_year() {}\n  FMT_CONSTEXPR void on_abbr_weekday() {}\n  FMT_CONSTEXPR void on_full_weekday() {}\n  FMT_CONSTEXPR void on_dec0_weekday(numeric_system) {}\n  FMT_CONSTEXPR void on_dec1_weekday(numeric_system) {}\n  FMT_CONSTEXPR void on_abbr_month() {}\n  FMT_CONSTEXPR void on_full_month() {}\n  FMT_CONSTEXPR void on_dec_month(numeric_system, pad_type) {}\n  FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) {}\n  FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) {}\n  FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) {}\n  FMT_CONSTEXPR void on_day_of_year(pad_type) {}\n  FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) {}\n  FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {}\n  FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {}\n  FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {}\n  FMT_CONSTEXPR void on_second(numeric_system, pad_type) {}\n  FMT_CONSTEXPR void on_datetime(numeric_system) {}\n  FMT_CONSTEXPR void on_loc_date(numeric_system) {}\n  FMT_CONSTEXPR void on_loc_time(numeric_system) {}\n  FMT_CONSTEXPR void on_us_date() {}\n  FMT_CONSTEXPR void on_iso_date() {}\n  FMT_CONSTEXPR void on_12_hour_time() {}\n  FMT_CONSTEXPR void on_24_hour_time() {}\n  FMT_CONSTEXPR void on_iso_time() {}\n  FMT_CONSTEXPR void on_am_pm() {}\n  FMT_CONSTEXPR void on_utc_offset(numeric_system) {}\n  FMT_CONSTEXPR void on_tz_name() {}\n};\n\ninline auto tm_wday_full_name(int wday) -> const char* {\n  static constexpr const char* full_name_list[] = {\n      \"Sunday\",   \"Monday\", \"Tuesday\", \"Wednesday\",\n      \"Thursday\", \"Friday\", \"Saturday\"};\n  return wday >= 0 && wday <= 6 ? full_name_list[wday] : \"?\";\n}\ninline auto tm_wday_short_name(int wday) -> const char* {\n  static constexpr const char* short_name_list[] = {\"Sun\", \"Mon\", \"Tue\", \"Wed\",\n                                                    \"Thu\", \"Fri\", \"Sat\"};\n  return wday >= 0 && wday <= 6 ? short_name_list[wday] : \"???\";\n}\n\ninline auto tm_mon_full_name(int mon) -> const char* {\n  static constexpr const char* full_name_list[] = {\n      \"January\", \"February\", \"March\",     \"April\",   \"May\",      \"June\",\n      \"July\",    \"August\",   \"September\", \"October\", \"November\", \"December\"};\n  return mon >= 0 && mon <= 11 ? full_name_list[mon] : \"?\";\n}\ninline auto tm_mon_short_name(int mon) -> const char* {\n  static constexpr const char* short_name_list[] = {\n      \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n      \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\",\n  };\n  return mon >= 0 && mon <= 11 ? short_name_list[mon] : \"???\";\n}\n\ntemplate <typename T, typename = void>\nstruct has_member_data_tm_gmtoff : std::false_type {};\ntemplate <typename T>\nstruct has_member_data_tm_gmtoff<T, void_t<decltype(T::tm_gmtoff)>>\n    : std::true_type {};\n\ntemplate <typename T, typename = void>\nstruct has_member_data_tm_zone : std::false_type {};\ntemplate <typename T>\nstruct has_member_data_tm_zone<T, void_t<decltype(T::tm_zone)>>\n    : std::true_type {};\n\ninline void tzset_once() {\n  static bool init = []() {\n    using namespace fmt_detail;\n    _tzset();\n    return false;\n  }();\n  ignore_unused(init);\n}\n\n// Converts value to Int and checks that it's in the range [0, upper).\ntemplate <typename T, typename Int, FMT_ENABLE_IF(std::is_integral<T>::value)>\ninline auto to_nonnegative_int(T value, Int upper) -> Int {\n  if (!std::is_unsigned<Int>::value &&\n      (value < 0 || to_unsigned(value) > to_unsigned(upper))) {\n    FMT_THROW(fmt::format_error(\"chrono value is out of range\"));\n  }\n  return static_cast<Int>(value);\n}\ntemplate <typename T, typename Int, FMT_ENABLE_IF(!std::is_integral<T>::value)>\ninline auto to_nonnegative_int(T value, Int upper) -> Int {\n  auto int_value = static_cast<Int>(value);\n  if (int_value < 0 || value > static_cast<T>(upper))\n    FMT_THROW(format_error(\"invalid value\"));\n  return int_value;\n}\n\nconstexpr auto pow10(std::uint32_t n) -> long long {\n  return n == 0 ? 1 : 10 * pow10(n - 1);\n}\n\n// Counts the number of fractional digits in the range [0, 18] according to the\n// C++20 spec. If more than 18 fractional digits are required then returns 6 for\n// microseconds precision.\ntemplate <long long Num, long long Den, int N = 0,\n          bool Enabled = (N < 19) && (Num <= max_value<long long>() / 10)>\nstruct count_fractional_digits {\n  static constexpr int value =\n      Num % Den == 0 ? N : count_fractional_digits<Num * 10, Den, N + 1>::value;\n};\n\n// Base case that doesn't instantiate any more templates\n// in order to avoid overflow.\ntemplate <long long Num, long long Den, int N>\nstruct count_fractional_digits<Num, Den, N, false> {\n  static constexpr int value = (Num % Den == 0) ? N : 6;\n};\n\n// Format subseconds which are given as an integer type with an appropriate\n// number of digits.\ntemplate <typename Char, typename OutputIt, typename Duration>\nvoid write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) {\n  constexpr auto num_fractional_digits =\n      count_fractional_digits<Duration::period::num,\n                              Duration::period::den>::value;\n\n  using subsecond_precision = std::chrono::duration<\n      typename std::common_type<typename Duration::rep,\n                                std::chrono::seconds::rep>::type,\n      std::ratio<1, pow10(num_fractional_digits)>>;\n\n  const auto fractional = d - detail::duration_cast<std::chrono::seconds>(d);\n  const auto subseconds =\n      std::chrono::treat_as_floating_point<\n          typename subsecond_precision::rep>::value\n          ? fractional.count()\n          : detail::duration_cast<subsecond_precision>(fractional).count();\n  auto n = static_cast<uint32_or_64_or_128_t<long long>>(subseconds);\n  const int num_digits = count_digits(n);\n\n  int leading_zeroes = (std::max)(0, num_fractional_digits - num_digits);\n  if (precision < 0) {\n    FMT_ASSERT(!std::is_floating_point<typename Duration::rep>::value, \"\");\n    if (std::ratio_less<typename subsecond_precision::period,\n                        std::chrono::seconds::period>::value) {\n      *out++ = '.';\n      out = detail::fill_n(out, leading_zeroes, '0');\n      out = format_decimal<Char>(out, n, num_digits);\n    }\n  } else if (precision > 0) {\n    *out++ = '.';\n    leading_zeroes = min_of(leading_zeroes, precision);\n    int remaining = precision - leading_zeroes;\n    out = detail::fill_n(out, leading_zeroes, '0');\n    if (remaining < num_digits) {\n      int num_truncated_digits = num_digits - remaining;\n      n /= to_unsigned(pow10(to_unsigned(num_truncated_digits)));\n      if (n != 0) out = format_decimal<Char>(out, n, remaining);\n      return;\n    }\n    if (n != 0) {\n      out = format_decimal<Char>(out, n, num_digits);\n      remaining -= num_digits;\n    }\n    out = detail::fill_n(out, remaining, '0');\n  }\n}\n\n// Format subseconds which are given as a floating point type with an\n// appropriate number of digits. We cannot pass the Duration here, as we\n// explicitly need to pass the Rep value in the chrono_formatter.\ntemplate <typename Duration>\nvoid write_floating_seconds(memory_buffer& buf, Duration duration,\n                            int num_fractional_digits = -1) {\n  using rep = typename Duration::rep;\n  FMT_ASSERT(std::is_floating_point<rep>::value, \"\");\n\n  auto val = duration.count();\n\n  if (num_fractional_digits < 0) {\n    // For `std::round` with fallback to `round`:\n    // On some toolchains `std::round` is not available (e.g. GCC 6).\n    using namespace std;\n    num_fractional_digits =\n        count_fractional_digits<Duration::period::num,\n                                Duration::period::den>::value;\n    if (num_fractional_digits < 6 && static_cast<rep>(round(val)) != val)\n      num_fractional_digits = 6;\n  }\n\n  fmt::format_to(std::back_inserter(buf), FMT_STRING(\"{:.{}f}\"),\n                 std::fmod(val * static_cast<rep>(Duration::period::num) /\n                               static_cast<rep>(Duration::period::den),\n                           static_cast<rep>(60)),\n                 num_fractional_digits);\n}\n\ntemplate <typename OutputIt, typename Char,\n          typename Duration = std::chrono::seconds>\nclass tm_writer {\n private:\n  static constexpr int days_per_week = 7;\n\n  const std::locale& loc_;\n  const bool is_classic_;\n  OutputIt out_;\n  const Duration* subsecs_;\n  const std::tm& tm_;\n\n  auto tm_sec() const noexcept -> int {\n    FMT_ASSERT(tm_.tm_sec >= 0 && tm_.tm_sec <= 61, \"\");\n    return tm_.tm_sec;\n  }\n  auto tm_min() const noexcept -> int {\n    FMT_ASSERT(tm_.tm_min >= 0 && tm_.tm_min <= 59, \"\");\n    return tm_.tm_min;\n  }\n  auto tm_hour() const noexcept -> int {\n    FMT_ASSERT(tm_.tm_hour >= 0 && tm_.tm_hour <= 23, \"\");\n    return tm_.tm_hour;\n  }\n  auto tm_mday() const noexcept -> int {\n    FMT_ASSERT(tm_.tm_mday >= 1 && tm_.tm_mday <= 31, \"\");\n    return tm_.tm_mday;\n  }\n  auto tm_mon() const noexcept -> int {\n    FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon <= 11, \"\");\n    return tm_.tm_mon;\n  }\n  auto tm_year() const noexcept -> long long { return 1900ll + tm_.tm_year; }\n  auto tm_wday() const noexcept -> int {\n    FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday <= 6, \"\");\n    return tm_.tm_wday;\n  }\n  auto tm_yday() const noexcept -> int {\n    FMT_ASSERT(tm_.tm_yday >= 0 && tm_.tm_yday <= 365, \"\");\n    return tm_.tm_yday;\n  }\n\n  auto tm_hour12() const noexcept -> int {\n    const auto h = tm_hour();\n    const auto z = h < 12 ? h : h - 12;\n    return z == 0 ? 12 : z;\n  }\n\n  // POSIX and the C Standard are unclear or inconsistent about what %C and %y\n  // do if the year is negative or exceeds 9999. Use the convention that %C\n  // concatenated with %y yields the same output as %Y, and that %Y contains at\n  // least 4 characters, with more only if necessary.\n  auto split_year_lower(long long year) const noexcept -> int {\n    auto l = year % 100;\n    if (l < 0) l = -l;  // l in [0, 99]\n    return static_cast<int>(l);\n  }\n\n  // Algorithm: https://en.wikipedia.org/wiki/ISO_week_date.\n  auto iso_year_weeks(long long curr_year) const noexcept -> int {\n    const auto prev_year = curr_year - 1;\n    const auto curr_p =\n        (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) %\n        days_per_week;\n    const auto prev_p =\n        (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) %\n        days_per_week;\n    return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0);\n  }\n  auto iso_week_num(int tm_yday, int tm_wday) const noexcept -> int {\n    return (tm_yday + 11 - (tm_wday == 0 ? days_per_week : tm_wday)) /\n           days_per_week;\n  }\n  auto tm_iso_week_year() const noexcept -> long long {\n    const auto year = tm_year();\n    const auto w = iso_week_num(tm_yday(), tm_wday());\n    if (w < 1) return year - 1;\n    if (w > iso_year_weeks(year)) return year + 1;\n    return year;\n  }\n  auto tm_iso_week_of_year() const noexcept -> int {\n    const auto year = tm_year();\n    const auto w = iso_week_num(tm_yday(), tm_wday());\n    if (w < 1) return iso_year_weeks(year - 1);\n    if (w > iso_year_weeks(year)) return 1;\n    return w;\n  }\n\n  void write1(int value) {\n    *out_++ = static_cast<char>('0' + to_unsigned(value) % 10);\n  }\n  void write2(int value) {\n    const char* d = digits2(to_unsigned(value) % 100);\n    *out_++ = *d++;\n    *out_++ = *d;\n  }\n  void write2(int value, pad_type pad) {\n    unsigned int v = to_unsigned(value) % 100;\n    if (v >= 10) {\n      const char* d = digits2(v);\n      *out_++ = *d++;\n      *out_++ = *d;\n    } else {\n      out_ = detail::write_padding(out_, pad);\n      *out_++ = static_cast<char>('0' + v);\n    }\n  }\n\n  void write_year_extended(long long year, pad_type pad) {\n    // At least 4 characters.\n    int width = 4;\n    bool negative = year < 0;\n    if (negative) {\n      year = 0 - year;\n      --width;\n    }\n    uint32_or_64_or_128_t<long long> n = to_unsigned(year);\n    const int num_digits = count_digits(n);\n    if (negative && pad == pad_type::zero) *out_++ = '-';\n    if (width > num_digits) {\n      out_ = detail::write_padding(out_, pad, width - num_digits);\n    }\n    if (negative && pad != pad_type::zero) *out_++ = '-';\n    out_ = format_decimal<Char>(out_, n, num_digits);\n  }\n  void write_year(long long year, pad_type pad) {\n    write_year_extended(year, pad);\n  }\n\n  void write_utc_offset(long long offset, numeric_system ns) {\n    if (offset < 0) {\n      *out_++ = '-';\n      offset = -offset;\n    } else {\n      *out_++ = '+';\n    }\n    offset /= 60;\n    write2(static_cast<int>(offset / 60));\n    if (ns != numeric_system::standard) *out_++ = ':';\n    write2(static_cast<int>(offset % 60));\n  }\n\n  template <typename T, FMT_ENABLE_IF(has_member_data_tm_gmtoff<T>::value)>\n  void format_utc_offset_impl(const T& tm, numeric_system ns) {\n    write_utc_offset(tm.tm_gmtoff, ns);\n  }\n  template <typename T, FMT_ENABLE_IF(!has_member_data_tm_gmtoff<T>::value)>\n  void format_utc_offset_impl(const T& tm, numeric_system ns) {\n#if defined(_WIN32) && defined(_UCRT)\n    tzset_once();\n    long offset = 0;\n    _get_timezone(&offset);\n    if (tm.tm_isdst) {\n      long dstbias = 0;\n      _get_dstbias(&dstbias);\n      offset += dstbias;\n    }\n    write_utc_offset(-offset, ns);\n#else\n    if (ns == numeric_system::standard) return format_localized('z');\n\n    // Extract timezone offset from timezone conversion functions.\n    std::tm gtm = tm;\n    std::time_t gt = std::mktime(&gtm);\n    std::tm ltm = gmtime(gt);\n    std::time_t lt = std::mktime(&ltm);\n    long long offset = gt - lt;\n    write_utc_offset(offset, ns);\n#endif\n  }\n\n  template <typename T, FMT_ENABLE_IF(has_member_data_tm_zone<T>::value)>\n  void format_tz_name_impl(const T& tm) {\n    if (is_classic_)\n      out_ = write_tm_str<Char>(out_, tm.tm_zone, loc_);\n    else\n      format_localized('Z');\n  }\n  template <typename T, FMT_ENABLE_IF(!has_member_data_tm_zone<T>::value)>\n  void format_tz_name_impl(const T&) {\n    format_localized('Z');\n  }\n\n  void format_localized(char format, char modifier = 0) {\n    out_ = write<Char>(out_, tm_, loc_, format, modifier);\n  }\n\n public:\n  tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm,\n            const Duration* subsecs = nullptr)\n      : loc_(loc),\n        is_classic_(loc_ == get_classic_locale()),\n        out_(out),\n        subsecs_(subsecs),\n        tm_(tm) {}\n\n  auto out() const -> OutputIt { return out_; }\n\n  FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {\n    out_ = copy<Char>(begin, end, out_);\n  }\n\n  void on_abbr_weekday() {\n    if (is_classic_)\n      out_ = write(out_, tm_wday_short_name(tm_wday()));\n    else\n      format_localized('a');\n  }\n  void on_full_weekday() {\n    if (is_classic_)\n      out_ = write(out_, tm_wday_full_name(tm_wday()));\n    else\n      format_localized('A');\n  }\n  void on_dec0_weekday(numeric_system ns) {\n    if (is_classic_ || ns == numeric_system::standard) return write1(tm_wday());\n    format_localized('w', 'O');\n  }\n  void on_dec1_weekday(numeric_system ns) {\n    if (is_classic_ || ns == numeric_system::standard) {\n      auto wday = tm_wday();\n      write1(wday == 0 ? days_per_week : wday);\n    } else {\n      format_localized('u', 'O');\n    }\n  }\n\n  void on_abbr_month() {\n    if (is_classic_)\n      out_ = write(out_, tm_mon_short_name(tm_mon()));\n    else\n      format_localized('b');\n  }\n  void on_full_month() {\n    if (is_classic_)\n      out_ = write(out_, tm_mon_full_name(tm_mon()));\n    else\n      format_localized('B');\n  }\n\n  void on_datetime(numeric_system ns) {\n    if (is_classic_) {\n      on_abbr_weekday();\n      *out_++ = ' ';\n      on_abbr_month();\n      *out_++ = ' ';\n      on_day_of_month(numeric_system::standard, pad_type::space);\n      *out_++ = ' ';\n      on_iso_time();\n      *out_++ = ' ';\n      on_year(numeric_system::standard, pad_type::space);\n    } else {\n      format_localized('c', ns == numeric_system::standard ? '\\0' : 'E');\n    }\n  }\n  void on_loc_date(numeric_system ns) {\n    if (is_classic_)\n      on_us_date();\n    else\n      format_localized('x', ns == numeric_system::standard ? '\\0' : 'E');\n  }\n  void on_loc_time(numeric_system ns) {\n    if (is_classic_)\n      on_iso_time();\n    else\n      format_localized('X', ns == numeric_system::standard ? '\\0' : 'E');\n  }\n  void on_us_date() {\n    char buf[8];\n    write_digit2_separated(buf, to_unsigned(tm_mon() + 1),\n                           to_unsigned(tm_mday()),\n                           to_unsigned(split_year_lower(tm_year())), '/');\n    out_ = copy<Char>(std::begin(buf), std::end(buf), out_);\n  }\n  void on_iso_date() {\n    auto year = tm_year();\n    char buf[10];\n    size_t offset = 0;\n    if (year >= 0 && year < 10000) {\n      write2digits(buf, static_cast<size_t>(year / 100));\n    } else {\n      offset = 4;\n      write_year_extended(year, pad_type::zero);\n      year = 0;\n    }\n    write_digit2_separated(buf + 2, static_cast<unsigned>(year % 100),\n                           to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()),\n                           '-');\n    out_ = copy<Char>(std::begin(buf) + offset, std::end(buf), out_);\n  }\n\n  void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); }\n  void on_tz_name() { format_tz_name_impl(tm_); }\n\n  void on_year(numeric_system ns, pad_type pad) {\n    if (is_classic_ || ns == numeric_system::standard)\n      return write_year(tm_year(), pad);\n    format_localized('Y', 'E');\n  }\n  void on_short_year(numeric_system ns) {\n    if (is_classic_ || ns == numeric_system::standard)\n      return write2(split_year_lower(tm_year()));\n    format_localized('y', 'O');\n  }\n  void on_offset_year() {\n    if (is_classic_) return write2(split_year_lower(tm_year()));\n    format_localized('y', 'E');\n  }\n\n  void on_century(numeric_system ns) {\n    if (is_classic_ || ns == numeric_system::standard) {\n      auto year = tm_year();\n      auto upper = year / 100;\n      if (year >= -99 && year < 0) {\n        // Zero upper on negative year.\n        *out_++ = '-';\n        *out_++ = '0';\n      } else if (upper >= 0 && upper < 100) {\n        write2(static_cast<int>(upper));\n      } else {\n        out_ = write<Char>(out_, upper);\n      }\n    } else {\n      format_localized('C', 'E');\n    }\n  }\n\n  void on_dec_month(numeric_system ns, pad_type pad) {\n    if (is_classic_ || ns == numeric_system::standard)\n      return write2(tm_mon() + 1, pad);\n    format_localized('m', 'O');\n  }\n\n  void on_dec0_week_of_year(numeric_system ns, pad_type pad) {\n    if (is_classic_ || ns == numeric_system::standard)\n      return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week,\n                    pad);\n    format_localized('U', 'O');\n  }\n  void on_dec1_week_of_year(numeric_system ns, pad_type pad) {\n    if (is_classic_ || ns == numeric_system::standard) {\n      auto wday = tm_wday();\n      write2((tm_yday() + days_per_week -\n              (wday == 0 ? (days_per_week - 1) : (wday - 1))) /\n                 days_per_week,\n             pad);\n    } else {\n      format_localized('W', 'O');\n    }\n  }\n  void on_iso_week_of_year(numeric_system ns, pad_type pad) {\n    if (is_classic_ || ns == numeric_system::standard)\n      return write2(tm_iso_week_of_year(), pad);\n    format_localized('V', 'O');\n  }\n\n  void on_iso_week_based_year() {\n    write_year(tm_iso_week_year(), pad_type::zero);\n  }\n  void on_iso_week_based_short_year() {\n    write2(split_year_lower(tm_iso_week_year()));\n  }\n\n  void on_day_of_year(pad_type pad) {\n    auto yday = tm_yday() + 1;\n    auto digit1 = yday / 100;\n    if (digit1 != 0) {\n      write1(digit1);\n    } else {\n      out_ = detail::write_padding(out_, pad);\n    }\n    write2(yday % 100, pad);\n  }\n\n  void on_day_of_month(numeric_system ns, pad_type pad) {\n    if (is_classic_ || ns == numeric_system::standard)\n      return write2(tm_mday(), pad);\n    format_localized('d', 'O');\n  }\n\n  void on_24_hour(numeric_system ns, pad_type pad) {\n    if (is_classic_ || ns == numeric_system::standard)\n      return write2(tm_hour(), pad);\n    format_localized('H', 'O');\n  }\n  void on_12_hour(numeric_system ns, pad_type pad) {\n    if (is_classic_ || ns == numeric_system::standard)\n      return write2(tm_hour12(), pad);\n    format_localized('I', 'O');\n  }\n  void on_minute(numeric_system ns, pad_type pad) {\n    if (is_classic_ || ns == numeric_system::standard)\n      return write2(tm_min(), pad);\n    format_localized('M', 'O');\n  }\n\n  void on_second(numeric_system ns, pad_type pad) {\n    if (is_classic_ || ns == numeric_system::standard) {\n      write2(tm_sec(), pad);\n      if (subsecs_) {\n        if (std::is_floating_point<typename Duration::rep>::value) {\n          auto buf = memory_buffer();\n          write_floating_seconds(buf, *subsecs_);\n          if (buf.size() > 1) {\n            // Remove the leading \"0\", write something like \".123\".\n            out_ = copy<Char>(buf.begin() + 1, buf.end(), out_);\n          }\n        } else {\n          write_fractional_seconds<Char>(out_, *subsecs_);\n        }\n      }\n    } else {\n      // Currently no formatting of subseconds when a locale is set.\n      format_localized('S', 'O');\n    }\n  }\n\n  void on_12_hour_time() {\n    if (is_classic_) {\n      char buf[8];\n      write_digit2_separated(buf, to_unsigned(tm_hour12()),\n                             to_unsigned(tm_min()), to_unsigned(tm_sec()), ':');\n      out_ = copy<Char>(std::begin(buf), std::end(buf), out_);\n      *out_++ = ' ';\n      on_am_pm();\n    } else {\n      format_localized('r');\n    }\n  }\n  void on_24_hour_time() {\n    write2(tm_hour());\n    *out_++ = ':';\n    write2(tm_min());\n  }\n  void on_iso_time() {\n    on_24_hour_time();\n    *out_++ = ':';\n    on_second(numeric_system::standard, pad_type::zero);\n  }\n\n  void on_am_pm() {\n    if (is_classic_) {\n      *out_++ = tm_hour() < 12 ? 'A' : 'P';\n      *out_++ = 'M';\n    } else {\n      format_localized('p');\n    }\n  }\n\n  // These apply to chrono durations but not tm.\n  void on_duration_value() {}\n  void on_duration_unit() {}\n};\n\nstruct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {\n  bool has_precision_integral = false;\n\n  FMT_NORETURN inline void unsupported() { FMT_THROW(format_error(\"no date\")); }\n\n  template <typename Char>\n  FMT_CONSTEXPR void on_text(const Char*, const Char*) {}\n  FMT_CONSTEXPR void on_day_of_year(pad_type) {}\n  FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {}\n  FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {}\n  FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {}\n  FMT_CONSTEXPR void on_second(numeric_system, pad_type) {}\n  FMT_CONSTEXPR void on_12_hour_time() {}\n  FMT_CONSTEXPR void on_24_hour_time() {}\n  FMT_CONSTEXPR void on_iso_time() {}\n  FMT_CONSTEXPR void on_am_pm() {}\n  FMT_CONSTEXPR void on_duration_value() const {\n    if (has_precision_integral)\n      FMT_THROW(format_error(\"precision not allowed for this argument type\"));\n  }\n  FMT_CONSTEXPR void on_duration_unit() {}\n};\n\ntemplate <typename T,\n          FMT_ENABLE_IF(std::is_integral<T>::value&& has_isfinite<T>::value)>\ninline auto isfinite(T) -> bool {\n  return true;\n}\n\ntemplate <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>\ninline auto mod(T x, int y) -> T {\n  return x % static_cast<T>(y);\n}\ntemplate <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>\ninline auto mod(T x, int y) -> T {\n  return std::fmod(x, static_cast<T>(y));\n}\n\n// If T is an integral type, maps T to its unsigned counterpart, otherwise\n// leaves it unchanged (unlike std::make_unsigned).\ntemplate <typename T, bool INTEGRAL = std::is_integral<T>::value>\nstruct make_unsigned_or_unchanged {\n  using type = T;\n};\n\ntemplate <typename T> struct make_unsigned_or_unchanged<T, true> {\n  using type = typename std::make_unsigned<T>::type;\n};\n\ntemplate <typename Rep, typename Period,\n          FMT_ENABLE_IF(std::is_integral<Rep>::value)>\ninline auto get_milliseconds(std::chrono::duration<Rep, Period> d)\n    -> std::chrono::duration<Rep, std::milli> {\n  // this may overflow and/or the result may not fit in the\n  // target type.\n#if FMT_SAFE_DURATION_CAST\n  using CommonSecondsType =\n      typename std::common_type<decltype(d), std::chrono::seconds>::type;\n  const auto d_as_common = detail::duration_cast<CommonSecondsType>(d);\n  const auto d_as_whole_seconds =\n      detail::duration_cast<std::chrono::seconds>(d_as_common);\n  // this conversion should be nonproblematic\n  const auto diff = d_as_common - d_as_whole_seconds;\n  const auto ms =\n      detail::duration_cast<std::chrono::duration<Rep, std::milli>>(diff);\n  return ms;\n#else\n  auto s = detail::duration_cast<std::chrono::seconds>(d);\n  return detail::duration_cast<std::chrono::milliseconds>(d - s);\n#endif\n}\n\ntemplate <typename Char, typename Rep, typename OutputIt,\n          FMT_ENABLE_IF(std::is_integral<Rep>::value)>\nauto format_duration_value(OutputIt out, Rep val, int) -> OutputIt {\n  return write<Char>(out, val);\n}\n\ntemplate <typename Char, typename Rep, typename OutputIt,\n          FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>\nauto format_duration_value(OutputIt out, Rep val, int precision) -> OutputIt {\n  auto specs = format_specs();\n  specs.precision = precision;\n  specs.set_type(precision >= 0 ? presentation_type::fixed\n                                : presentation_type::general);\n  return write<Char>(out, val, specs);\n}\n\ntemplate <typename Char, typename OutputIt>\nauto copy_unit(string_view unit, OutputIt out, Char) -> OutputIt {\n  return copy<Char>(unit.begin(), unit.end(), out);\n}\n\ntemplate <typename OutputIt>\nauto copy_unit(string_view unit, OutputIt out, wchar_t) -> OutputIt {\n  // This works when wchar_t is UTF-32 because units only contain characters\n  // that have the same representation in UTF-16 and UTF-32.\n  utf8_to_utf16 u(unit);\n  return copy<wchar_t>(u.c_str(), u.c_str() + u.size(), out);\n}\n\ntemplate <typename Char, typename Period, typename OutputIt>\nauto format_duration_unit(OutputIt out) -> OutputIt {\n  if (const char* unit = get_units<Period>())\n    return copy_unit(string_view(unit), out, Char());\n  *out++ = '[';\n  out = write<Char>(out, Period::num);\n  if (const_check(Period::den != 1)) {\n    *out++ = '/';\n    out = write<Char>(out, Period::den);\n  }\n  *out++ = ']';\n  *out++ = 's';\n  return out;\n}\n\nclass get_locale {\n private:\n  union {\n    std::locale locale_;\n  };\n  bool has_locale_ = false;\n\n public:\n  inline get_locale(bool localized, locale_ref loc) : has_locale_(localized) {\n    if (localized)\n      ::new (&locale_) std::locale(loc.template get<std::locale>());\n  }\n  inline ~get_locale() {\n    if (has_locale_) locale_.~locale();\n  }\n  inline operator const std::locale&() const {\n    return has_locale_ ? locale_ : get_classic_locale();\n  }\n};\n\ntemplate <typename FormatContext, typename OutputIt, typename Rep,\n          typename Period>\nstruct chrono_formatter {\n  FormatContext& context;\n  OutputIt out;\n  int precision;\n  bool localized = false;\n  // rep is unsigned to avoid overflow.\n  using rep =\n      conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int),\n                    unsigned, typename make_unsigned_or_unchanged<Rep>::type>;\n  rep val;\n  using seconds = std::chrono::duration<rep>;\n  seconds s;\n  using milliseconds = std::chrono::duration<rep, std::milli>;\n  bool negative;\n\n  using char_type = typename FormatContext::char_type;\n  using tm_writer_type = tm_writer<OutputIt, char_type>;\n\n  chrono_formatter(FormatContext& ctx, OutputIt o,\n                   std::chrono::duration<Rep, Period> d)\n      : context(ctx),\n        out(o),\n        val(static_cast<rep>(d.count())),\n        negative(false) {\n    if (d.count() < 0) {\n      val = 0 - val;\n      negative = true;\n    }\n\n    // this may overflow and/or the result may not fit in the\n    // target type.\n    // might need checked conversion (rep!=Rep)\n    s = detail::duration_cast<seconds>(std::chrono::duration<rep, Period>(val));\n  }\n\n  // returns true if nan or inf, writes to out.\n  auto handle_nan_inf() -> bool {\n    if (isfinite(val)) {\n      return false;\n    }\n    if (isnan(val)) {\n      write_nan();\n      return true;\n    }\n    // must be +-inf\n    if (val > 0) {\n      write_pinf();\n    } else {\n      write_ninf();\n    }\n    return true;\n  }\n\n  auto days() const -> Rep { return static_cast<Rep>(s.count() / 86400); }\n  auto hour() const -> Rep {\n    return static_cast<Rep>(mod((s.count() / 3600), 24));\n  }\n\n  auto hour12() const -> Rep {\n    Rep hour = static_cast<Rep>(mod((s.count() / 3600), 12));\n    return hour <= 0 ? 12 : hour;\n  }\n\n  auto minute() const -> Rep {\n    return static_cast<Rep>(mod((s.count() / 60), 60));\n  }\n  auto second() const -> Rep { return static_cast<Rep>(mod(s.count(), 60)); }\n\n  auto time() const -> std::tm {\n    auto time = std::tm();\n    time.tm_hour = to_nonnegative_int(hour(), 24);\n    time.tm_min = to_nonnegative_int(minute(), 60);\n    time.tm_sec = to_nonnegative_int(second(), 60);\n    return time;\n  }\n\n  void write_sign() {\n    if (negative) {\n      *out++ = '-';\n      negative = false;\n    }\n  }\n\n  void write(Rep value, int width, pad_type pad = pad_type::zero) {\n    write_sign();\n    if (isnan(value)) return write_nan();\n    uint32_or_64_or_128_t<int> n =\n        to_unsigned(to_nonnegative_int(value, max_value<int>()));\n    int num_digits = detail::count_digits(n);\n    if (width > num_digits) {\n      out = detail::write_padding(out, pad, width - num_digits);\n    }\n    out = format_decimal<char_type>(out, n, num_digits);\n  }\n\n  void write_nan() { std::copy_n(\"nan\", 3, out); }\n  void write_pinf() { std::copy_n(\"inf\", 3, out); }\n  void write_ninf() { std::copy_n(\"-inf\", 4, out); }\n\n  template <typename Callback, typename... Args>\n  void format_tm(const tm& time, Callback cb, Args... args) {\n    if (isnan(val)) return write_nan();\n    get_locale loc(localized, context.locale());\n    auto w = tm_writer_type(loc, out, time);\n    (w.*cb)(args...);\n    out = w.out();\n  }\n\n  void on_text(const char_type* begin, const char_type* end) {\n    copy<char_type>(begin, end, out);\n  }\n\n  // These are not implemented because durations don't have date information.\n  void on_abbr_weekday() {}\n  void on_full_weekday() {}\n  void on_dec0_weekday(numeric_system) {}\n  void on_dec1_weekday(numeric_system) {}\n  void on_abbr_month() {}\n  void on_full_month() {}\n  void on_datetime(numeric_system) {}\n  void on_loc_date(numeric_system) {}\n  void on_loc_time(numeric_system) {}\n  void on_us_date() {}\n  void on_iso_date() {}\n  void on_utc_offset(numeric_system) {}\n  void on_tz_name() {}\n  void on_year(numeric_system, pad_type) {}\n  void on_short_year(numeric_system) {}\n  void on_offset_year() {}\n  void on_century(numeric_system) {}\n  void on_iso_week_based_year() {}\n  void on_iso_week_based_short_year() {}\n  void on_dec_month(numeric_system, pad_type) {}\n  void on_dec0_week_of_year(numeric_system, pad_type) {}\n  void on_dec1_week_of_year(numeric_system, pad_type) {}\n  void on_iso_week_of_year(numeric_system, pad_type) {}\n  void on_day_of_month(numeric_system, pad_type) {}\n\n  void on_day_of_year(pad_type) {\n    if (handle_nan_inf()) return;\n    write(days(), 0);\n  }\n\n  void on_24_hour(numeric_system ns, pad_type pad) {\n    if (handle_nan_inf()) return;\n\n    if (ns == numeric_system::standard) return write(hour(), 2, pad);\n    auto time = tm();\n    time.tm_hour = to_nonnegative_int(hour(), 24);\n    format_tm(time, &tm_writer_type::on_24_hour, ns, pad);\n  }\n\n  void on_12_hour(numeric_system ns, pad_type pad) {\n    if (handle_nan_inf()) return;\n\n    if (ns == numeric_system::standard) return write(hour12(), 2, pad);\n    auto time = tm();\n    time.tm_hour = to_nonnegative_int(hour12(), 12);\n    format_tm(time, &tm_writer_type::on_12_hour, ns, pad);\n  }\n\n  void on_minute(numeric_system ns, pad_type pad) {\n    if (handle_nan_inf()) return;\n\n    if (ns == numeric_system::standard) return write(minute(), 2, pad);\n    auto time = tm();\n    time.tm_min = to_nonnegative_int(minute(), 60);\n    format_tm(time, &tm_writer_type::on_minute, ns, pad);\n  }\n\n  void on_second(numeric_system ns, pad_type pad) {\n    if (handle_nan_inf()) return;\n\n    if (ns == numeric_system::standard) {\n      if (std::is_floating_point<rep>::value) {\n        auto buf = memory_buffer();\n        write_floating_seconds(buf, std::chrono::duration<rep, Period>(val),\n                               precision);\n        if (negative) *out++ = '-';\n        if (buf.size() < 2 || buf[1] == '.') {\n          out = detail::write_padding(out, pad);\n        }\n        out = copy<char_type>(buf.begin(), buf.end(), out);\n      } else {\n        write(second(), 2, pad);\n        write_fractional_seconds<char_type>(\n            out, std::chrono::duration<rep, Period>(val), precision);\n      }\n      return;\n    }\n    auto time = tm();\n    time.tm_sec = to_nonnegative_int(second(), 60);\n    format_tm(time, &tm_writer_type::on_second, ns, pad);\n  }\n\n  void on_12_hour_time() {\n    if (handle_nan_inf()) return;\n    format_tm(time(), &tm_writer_type::on_12_hour_time);\n  }\n\n  void on_24_hour_time() {\n    if (handle_nan_inf()) {\n      *out++ = ':';\n      handle_nan_inf();\n      return;\n    }\n\n    write(hour(), 2);\n    *out++ = ':';\n    write(minute(), 2);\n  }\n\n  void on_iso_time() {\n    on_24_hour_time();\n    *out++ = ':';\n    if (handle_nan_inf()) return;\n    on_second(numeric_system::standard, pad_type::zero);\n  }\n\n  void on_am_pm() {\n    if (handle_nan_inf()) return;\n    format_tm(time(), &tm_writer_type::on_am_pm);\n  }\n\n  void on_duration_value() {\n    if (handle_nan_inf()) return;\n    write_sign();\n    out = format_duration_value<char_type>(out, val, precision);\n  }\n\n  void on_duration_unit() {\n    out = format_duration_unit<char_type, Period>(out);\n  }\n};\n\n}  // namespace detail\n\n#if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907\nusing weekday = std::chrono::weekday;\nusing day = std::chrono::day;\nusing month = std::chrono::month;\nusing year = std::chrono::year;\nusing year_month_day = std::chrono::year_month_day;\n#else\n// A fallback version of weekday.\nclass weekday {\n private:\n  unsigned char value_;\n\n public:\n  weekday() = default;\n  constexpr explicit weekday(unsigned wd) noexcept\n      : value_(static_cast<unsigned char>(wd != 7 ? wd : 0)) {}\n  constexpr auto c_encoding() const noexcept -> unsigned { return value_; }\n};\n\nclass day {\n private:\n  unsigned char value_;\n\n public:\n  day() = default;\n  constexpr explicit day(unsigned d) noexcept\n      : value_(static_cast<unsigned char>(d)) {}\n  constexpr explicit operator unsigned() const noexcept { return value_; }\n};\n\nclass month {\n private:\n  unsigned char value_;\n\n public:\n  month() = default;\n  constexpr explicit month(unsigned m) noexcept\n      : value_(static_cast<unsigned char>(m)) {}\n  constexpr explicit operator unsigned() const noexcept { return value_; }\n};\n\nclass year {\n private:\n  int value_;\n\n public:\n  year() = default;\n  constexpr explicit year(int y) noexcept : value_(y) {}\n  constexpr explicit operator int() const noexcept { return value_; }\n};\n\nclass year_month_day {\n private:\n  fmt::year year_;\n  fmt::month month_;\n  fmt::day day_;\n\n public:\n  year_month_day() = default;\n  constexpr year_month_day(const year& y, const month& m, const day& d) noexcept\n      : year_(y), month_(m), day_(d) {}\n  constexpr auto year() const noexcept -> fmt::year { return year_; }\n  constexpr auto month() const noexcept -> fmt::month { return month_; }\n  constexpr auto day() const noexcept -> fmt::day { return day_; }\n};\n#endif\n\ntemplate <typename Char>\nstruct formatter<weekday, Char> : private formatter<std::tm, Char> {\n private:\n  bool localized_ = false;\n  bool use_tm_formatter_ = false;\n\n public:\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    auto it = ctx.begin(), end = ctx.end();\n    if (it != end && *it == 'L') {\n      ++it;\n      localized_ = true;\n      return it;\n    }\n    use_tm_formatter_ = it != end && *it != '}';\n    return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it;\n  }\n\n  template <typename FormatContext>\n  auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) {\n    auto time = std::tm();\n    time.tm_wday = static_cast<int>(wd.c_encoding());\n    if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);\n    detail::get_locale loc(localized_, ctx.locale());\n    auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);\n    w.on_abbr_weekday();\n    return w.out();\n  }\n};\n\ntemplate <typename Char>\nstruct formatter<day, Char> : private formatter<std::tm, Char> {\n private:\n  bool use_tm_formatter_ = false;\n\n public:\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    auto it = ctx.begin(), end = ctx.end();\n    use_tm_formatter_ = it != end && *it != '}';\n    return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it;\n  }\n\n  template <typename FormatContext>\n  auto format(day d, FormatContext& ctx) const -> decltype(ctx.out()) {\n    auto time = std::tm();\n    time.tm_mday = static_cast<int>(static_cast<unsigned>(d));\n    if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);\n    detail::get_locale loc(false, ctx.locale());\n    auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);\n    w.on_day_of_month(detail::numeric_system::standard, detail::pad_type::zero);\n    return w.out();\n  }\n};\n\ntemplate <typename Char>\nstruct formatter<month, Char> : private formatter<std::tm, Char> {\n private:\n  bool localized_ = false;\n  bool use_tm_formatter_ = false;\n\n public:\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    auto it = ctx.begin(), end = ctx.end();\n    if (it != end && *it == 'L') {\n      ++it;\n      localized_ = true;\n      return it;\n    }\n    use_tm_formatter_ = it != end && *it != '}';\n    return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it;\n  }\n\n  template <typename FormatContext>\n  auto format(month m, FormatContext& ctx) const -> decltype(ctx.out()) {\n    auto time = std::tm();\n    time.tm_mon = static_cast<int>(static_cast<unsigned>(m)) - 1;\n    if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);\n    detail::get_locale loc(localized_, ctx.locale());\n    auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);\n    w.on_abbr_month();\n    return w.out();\n  }\n};\n\ntemplate <typename Char>\nstruct formatter<year, Char> : private formatter<std::tm, Char> {\n private:\n  bool use_tm_formatter_ = false;\n\n public:\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    auto it = ctx.begin(), end = ctx.end();\n    use_tm_formatter_ = it != end && *it != '}';\n    return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it;\n  }\n\n  template <typename FormatContext>\n  auto format(year y, FormatContext& ctx) const -> decltype(ctx.out()) {\n    auto time = std::tm();\n    time.tm_year = static_cast<int>(y) - 1900;\n    if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);\n    detail::get_locale loc(false, ctx.locale());\n    auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);\n    w.on_year(detail::numeric_system::standard, detail::pad_type::zero);\n    return w.out();\n  }\n};\n\ntemplate <typename Char>\nstruct formatter<year_month_day, Char> : private formatter<std::tm, Char> {\n private:\n  bool use_tm_formatter_ = false;\n\n public:\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    auto it = ctx.begin(), end = ctx.end();\n    use_tm_formatter_ = it != end && *it != '}';\n    return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it;\n  }\n\n  template <typename FormatContext>\n  auto format(year_month_day val, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    auto time = std::tm();\n    time.tm_year = static_cast<int>(val.year()) - 1900;\n    time.tm_mon = static_cast<int>(static_cast<unsigned>(val.month())) - 1;\n    time.tm_mday = static_cast<int>(static_cast<unsigned>(val.day()));\n    if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);\n    detail::get_locale loc(true, ctx.locale());\n    auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);\n    w.on_iso_date();\n    return w.out();\n  }\n};\n\ntemplate <typename Rep, typename Period, typename Char>\nstruct formatter<std::chrono::duration<Rep, Period>, Char> {\n private:\n  format_specs specs_;\n  detail::arg_ref<Char> width_ref_;\n  detail::arg_ref<Char> precision_ref_;\n  bool localized_ = false;\n  basic_string_view<Char> fmt_;\n\n public:\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    auto it = ctx.begin(), end = ctx.end();\n    if (it == end || *it == '}') return it;\n\n    it = detail::parse_align(it, end, specs_);\n    if (it == end) return it;\n\n    Char c = *it;\n    if ((c >= '0' && c <= '9') || c == '{') {\n      it = detail::parse_width(it, end, specs_, width_ref_, ctx);\n      if (it == end) return it;\n    }\n\n    auto checker = detail::chrono_format_checker();\n    if (*it == '.') {\n      checker.has_precision_integral = !std::is_floating_point<Rep>::value;\n      it = detail::parse_precision(it, end, specs_, precision_ref_, ctx);\n    }\n    if (it != end && *it == 'L') {\n      localized_ = true;\n      ++it;\n    }\n    end = detail::parse_chrono_format(it, end, checker);\n    fmt_ = {it, detail::to_unsigned(end - it)};\n    return end;\n  }\n\n  template <typename FormatContext>\n  auto format(std::chrono::duration<Rep, Period> d, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    auto specs = specs_;\n    auto precision = specs.precision;\n    specs.precision = -1;\n    auto begin = fmt_.begin(), end = fmt_.end();\n    // As a possible future optimization, we could avoid extra copying if width\n    // is not specified.\n    auto buf = basic_memory_buffer<Char>();\n    auto out = basic_appender<Char>(buf);\n    detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,\n                                ctx);\n    detail::handle_dynamic_spec(specs.dynamic_precision(), precision,\n                                precision_ref_, ctx);\n    if (begin == end || *begin == '}') {\n      out = detail::format_duration_value<Char>(out, d.count(), precision);\n      detail::format_duration_unit<Char, Period>(out);\n    } else {\n      using chrono_formatter =\n          detail::chrono_formatter<FormatContext, decltype(out), Rep, Period>;\n      auto f = chrono_formatter(ctx, out, d);\n      f.precision = precision;\n      f.localized = localized_;\n      detail::parse_chrono_format(begin, end, f);\n    }\n    return detail::write(\n        ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);\n  }\n};\n\ntemplate <typename Char> struct formatter<std::tm, Char> {\n private:\n  format_specs specs_;\n  detail::arg_ref<Char> width_ref_;\n\n protected:\n  basic_string_view<Char> fmt_;\n\n  template <typename Duration, typename FormatContext>\n  auto do_format(const std::tm& tm, FormatContext& ctx,\n                 const Duration* subsecs) const -> decltype(ctx.out()) {\n    auto specs = specs_;\n    auto buf = basic_memory_buffer<Char>();\n    auto out = basic_appender<Char>(buf);\n    detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,\n                                ctx);\n\n    auto loc_ref = ctx.locale();\n    detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);\n    auto w =\n        detail::tm_writer<decltype(out), Char, Duration>(loc, out, tm, subsecs);\n    detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w);\n    return detail::write(\n        ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);\n  }\n\n public:\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    auto it = ctx.begin(), end = ctx.end();\n    if (it == end || *it == '}') return it;\n\n    it = detail::parse_align(it, end, specs_);\n    if (it == end) return it;\n\n    Char c = *it;\n    if ((c >= '0' && c <= '9') || c == '{') {\n      it = detail::parse_width(it, end, specs_, width_ref_, ctx);\n      if (it == end) return it;\n    }\n\n    end = detail::parse_chrono_format(it, end, detail::tm_format_checker());\n    // Replace the default format string only if the new spec is not empty.\n    if (end != it) fmt_ = {it, detail::to_unsigned(end - it)};\n    return end;\n  }\n\n  template <typename FormatContext>\n  auto format(const std::tm& tm, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    return do_format<std::chrono::seconds>(tm, ctx, nullptr);\n  }\n};\n\ntemplate <typename Char, typename Duration>\nstruct formatter<sys_time<Duration>, Char> : formatter<std::tm, Char> {\n  FMT_CONSTEXPR formatter() {\n    this->fmt_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>();\n  }\n\n  template <typename FormatContext>\n  auto format(sys_time<Duration> val, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    std::tm tm = gmtime(val);\n    using period = typename Duration::period;\n    if (detail::const_check(\n            period::num == 1 && period::den == 1 &&\n            !std::is_floating_point<typename Duration::rep>::value)) {\n      return formatter<std::tm, Char>::format(tm, ctx);\n    }\n    Duration epoch = val.time_since_epoch();\n    Duration subsecs = detail::duration_cast<Duration>(\n        epoch - detail::duration_cast<std::chrono::seconds>(epoch));\n    if (subsecs.count() < 0) {\n      auto second = detail::duration_cast<Duration>(std::chrono::seconds(1));\n      if (tm.tm_sec != 0)\n        --tm.tm_sec;\n      else\n        tm = gmtime(val - second);\n      subsecs += detail::duration_cast<Duration>(std::chrono::seconds(1));\n    }\n    return formatter<std::tm, Char>::do_format(tm, ctx, &subsecs);\n  }\n};\n\ntemplate <typename Duration, typename Char>\nstruct formatter<utc_time<Duration>, Char>\n    : formatter<sys_time<Duration>, Char> {\n  template <typename FormatContext>\n  auto format(utc_time<Duration> val, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    return formatter<sys_time<Duration>, Char>::format(\n        detail::utc_clock::to_sys(val), ctx);\n  }\n};\n\ntemplate <typename Duration, typename Char>\nstruct formatter<local_time<Duration>, Char> : formatter<std::tm, Char> {\n  FMT_CONSTEXPR formatter() {\n    this->fmt_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>();\n  }\n\n  template <typename FormatContext>\n  auto format(local_time<Duration> val, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    using period = typename Duration::period;\n    if (period::num == 1 && period::den == 1 &&\n        !std::is_floating_point<typename Duration::rep>::value) {\n      return formatter<std::tm, Char>::format(localtime(val), ctx);\n    }\n    auto epoch = val.time_since_epoch();\n    auto subsecs = detail::duration_cast<Duration>(\n        epoch - detail::duration_cast<std::chrono::seconds>(epoch));\n    return formatter<std::tm, Char>::do_format(localtime(val), ctx, &subsecs);\n  }\n};\n\nFMT_END_EXPORT\nFMT_END_NAMESPACE\n\n#endif  // FMT_CHRONO_H_\n"
  },
  {
    "path": "dependencies/fmt/fmt/include/fmt/color.h",
    "content": "// Formatting library for C++ - color support\n//\n// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors\n// All rights reserved.\n//\n// For the license information refer to format.h.\n\n#ifndef FMT_COLOR_H_\n#define FMT_COLOR_H_\n\n#include \"format.h\"\n\nFMT_BEGIN_NAMESPACE\nFMT_BEGIN_EXPORT\n\nenum class color : uint32_t {\n  alice_blue = 0xF0F8FF,               // rgb(240,248,255)\n  antique_white = 0xFAEBD7,            // rgb(250,235,215)\n  aqua = 0x00FFFF,                     // rgb(0,255,255)\n  aquamarine = 0x7FFFD4,               // rgb(127,255,212)\n  azure = 0xF0FFFF,                    // rgb(240,255,255)\n  beige = 0xF5F5DC,                    // rgb(245,245,220)\n  bisque = 0xFFE4C4,                   // rgb(255,228,196)\n  black = 0x000000,                    // rgb(0,0,0)\n  blanched_almond = 0xFFEBCD,          // rgb(255,235,205)\n  blue = 0x0000FF,                     // rgb(0,0,255)\n  blue_violet = 0x8A2BE2,              // rgb(138,43,226)\n  brown = 0xA52A2A,                    // rgb(165,42,42)\n  burly_wood = 0xDEB887,               // rgb(222,184,135)\n  cadet_blue = 0x5F9EA0,               // rgb(95,158,160)\n  chartreuse = 0x7FFF00,               // rgb(127,255,0)\n  chocolate = 0xD2691E,                // rgb(210,105,30)\n  coral = 0xFF7F50,                    // rgb(255,127,80)\n  cornflower_blue = 0x6495ED,          // rgb(100,149,237)\n  cornsilk = 0xFFF8DC,                 // rgb(255,248,220)\n  crimson = 0xDC143C,                  // rgb(220,20,60)\n  cyan = 0x00FFFF,                     // rgb(0,255,255)\n  dark_blue = 0x00008B,                // rgb(0,0,139)\n  dark_cyan = 0x008B8B,                // rgb(0,139,139)\n  dark_golden_rod = 0xB8860B,          // rgb(184,134,11)\n  dark_gray = 0xA9A9A9,                // rgb(169,169,169)\n  dark_green = 0x006400,               // rgb(0,100,0)\n  dark_khaki = 0xBDB76B,               // rgb(189,183,107)\n  dark_magenta = 0x8B008B,             // rgb(139,0,139)\n  dark_olive_green = 0x556B2F,         // rgb(85,107,47)\n  dark_orange = 0xFF8C00,              // rgb(255,140,0)\n  dark_orchid = 0x9932CC,              // rgb(153,50,204)\n  dark_red = 0x8B0000,                 // rgb(139,0,0)\n  dark_salmon = 0xE9967A,              // rgb(233,150,122)\n  dark_sea_green = 0x8FBC8F,           // rgb(143,188,143)\n  dark_slate_blue = 0x483D8B,          // rgb(72,61,139)\n  dark_slate_gray = 0x2F4F4F,          // rgb(47,79,79)\n  dark_turquoise = 0x00CED1,           // rgb(0,206,209)\n  dark_violet = 0x9400D3,              // rgb(148,0,211)\n  deep_pink = 0xFF1493,                // rgb(255,20,147)\n  deep_sky_blue = 0x00BFFF,            // rgb(0,191,255)\n  dim_gray = 0x696969,                 // rgb(105,105,105)\n  dodger_blue = 0x1E90FF,              // rgb(30,144,255)\n  fire_brick = 0xB22222,               // rgb(178,34,34)\n  floral_white = 0xFFFAF0,             // rgb(255,250,240)\n  forest_green = 0x228B22,             // rgb(34,139,34)\n  fuchsia = 0xFF00FF,                  // rgb(255,0,255)\n  gainsboro = 0xDCDCDC,                // rgb(220,220,220)\n  ghost_white = 0xF8F8FF,              // rgb(248,248,255)\n  gold = 0xFFD700,                     // rgb(255,215,0)\n  golden_rod = 0xDAA520,               // rgb(218,165,32)\n  gray = 0x808080,                     // rgb(128,128,128)\n  green = 0x008000,                    // rgb(0,128,0)\n  green_yellow = 0xADFF2F,             // rgb(173,255,47)\n  honey_dew = 0xF0FFF0,                // rgb(240,255,240)\n  hot_pink = 0xFF69B4,                 // rgb(255,105,180)\n  indian_red = 0xCD5C5C,               // rgb(205,92,92)\n  indigo = 0x4B0082,                   // rgb(75,0,130)\n  ivory = 0xFFFFF0,                    // rgb(255,255,240)\n  khaki = 0xF0E68C,                    // rgb(240,230,140)\n  lavender = 0xE6E6FA,                 // rgb(230,230,250)\n  lavender_blush = 0xFFF0F5,           // rgb(255,240,245)\n  lawn_green = 0x7CFC00,               // rgb(124,252,0)\n  lemon_chiffon = 0xFFFACD,            // rgb(255,250,205)\n  light_blue = 0xADD8E6,               // rgb(173,216,230)\n  light_coral = 0xF08080,              // rgb(240,128,128)\n  light_cyan = 0xE0FFFF,               // rgb(224,255,255)\n  light_golden_rod_yellow = 0xFAFAD2,  // rgb(250,250,210)\n  light_gray = 0xD3D3D3,               // rgb(211,211,211)\n  light_green = 0x90EE90,              // rgb(144,238,144)\n  light_pink = 0xFFB6C1,               // rgb(255,182,193)\n  light_salmon = 0xFFA07A,             // rgb(255,160,122)\n  light_sea_green = 0x20B2AA,          // rgb(32,178,170)\n  light_sky_blue = 0x87CEFA,           // rgb(135,206,250)\n  light_slate_gray = 0x778899,         // rgb(119,136,153)\n  light_steel_blue = 0xB0C4DE,         // rgb(176,196,222)\n  light_yellow = 0xFFFFE0,             // rgb(255,255,224)\n  lime = 0x00FF00,                     // rgb(0,255,0)\n  lime_green = 0x32CD32,               // rgb(50,205,50)\n  linen = 0xFAF0E6,                    // rgb(250,240,230)\n  magenta = 0xFF00FF,                  // rgb(255,0,255)\n  maroon = 0x800000,                   // rgb(128,0,0)\n  medium_aquamarine = 0x66CDAA,        // rgb(102,205,170)\n  medium_blue = 0x0000CD,              // rgb(0,0,205)\n  medium_orchid = 0xBA55D3,            // rgb(186,85,211)\n  medium_purple = 0x9370DB,            // rgb(147,112,219)\n  medium_sea_green = 0x3CB371,         // rgb(60,179,113)\n  medium_slate_blue = 0x7B68EE,        // rgb(123,104,238)\n  medium_spring_green = 0x00FA9A,      // rgb(0,250,154)\n  medium_turquoise = 0x48D1CC,         // rgb(72,209,204)\n  medium_violet_red = 0xC71585,        // rgb(199,21,133)\n  midnight_blue = 0x191970,            // rgb(25,25,112)\n  mint_cream = 0xF5FFFA,               // rgb(245,255,250)\n  misty_rose = 0xFFE4E1,               // rgb(255,228,225)\n  moccasin = 0xFFE4B5,                 // rgb(255,228,181)\n  navajo_white = 0xFFDEAD,             // rgb(255,222,173)\n  navy = 0x000080,                     // rgb(0,0,128)\n  old_lace = 0xFDF5E6,                 // rgb(253,245,230)\n  olive = 0x808000,                    // rgb(128,128,0)\n  olive_drab = 0x6B8E23,               // rgb(107,142,35)\n  orange = 0xFFA500,                   // rgb(255,165,0)\n  orange_red = 0xFF4500,               // rgb(255,69,0)\n  orchid = 0xDA70D6,                   // rgb(218,112,214)\n  pale_golden_rod = 0xEEE8AA,          // rgb(238,232,170)\n  pale_green = 0x98FB98,               // rgb(152,251,152)\n  pale_turquoise = 0xAFEEEE,           // rgb(175,238,238)\n  pale_violet_red = 0xDB7093,          // rgb(219,112,147)\n  papaya_whip = 0xFFEFD5,              // rgb(255,239,213)\n  peach_puff = 0xFFDAB9,               // rgb(255,218,185)\n  peru = 0xCD853F,                     // rgb(205,133,63)\n  pink = 0xFFC0CB,                     // rgb(255,192,203)\n  plum = 0xDDA0DD,                     // rgb(221,160,221)\n  powder_blue = 0xB0E0E6,              // rgb(176,224,230)\n  purple = 0x800080,                   // rgb(128,0,128)\n  rebecca_purple = 0x663399,           // rgb(102,51,153)\n  red = 0xFF0000,                      // rgb(255,0,0)\n  rosy_brown = 0xBC8F8F,               // rgb(188,143,143)\n  royal_blue = 0x4169E1,               // rgb(65,105,225)\n  saddle_brown = 0x8B4513,             // rgb(139,69,19)\n  salmon = 0xFA8072,                   // rgb(250,128,114)\n  sandy_brown = 0xF4A460,              // rgb(244,164,96)\n  sea_green = 0x2E8B57,                // rgb(46,139,87)\n  sea_shell = 0xFFF5EE,                // rgb(255,245,238)\n  sienna = 0xA0522D,                   // rgb(160,82,45)\n  silver = 0xC0C0C0,                   // rgb(192,192,192)\n  sky_blue = 0x87CEEB,                 // rgb(135,206,235)\n  slate_blue = 0x6A5ACD,               // rgb(106,90,205)\n  slate_gray = 0x708090,               // rgb(112,128,144)\n  snow = 0xFFFAFA,                     // rgb(255,250,250)\n  spring_green = 0x00FF7F,             // rgb(0,255,127)\n  steel_blue = 0x4682B4,               // rgb(70,130,180)\n  tan = 0xD2B48C,                      // rgb(210,180,140)\n  teal = 0x008080,                     // rgb(0,128,128)\n  thistle = 0xD8BFD8,                  // rgb(216,191,216)\n  tomato = 0xFF6347,                   // rgb(255,99,71)\n  turquoise = 0x40E0D0,                // rgb(64,224,208)\n  violet = 0xEE82EE,                   // rgb(238,130,238)\n  wheat = 0xF5DEB3,                    // rgb(245,222,179)\n  white = 0xFFFFFF,                    // rgb(255,255,255)\n  white_smoke = 0xF5F5F5,              // rgb(245,245,245)\n  yellow = 0xFFFF00,                   // rgb(255,255,0)\n  yellow_green = 0x9ACD32              // rgb(154,205,50)\n};                                     // enum class color\n\nenum class terminal_color : uint8_t {\n  black = 30,\n  red,\n  green,\n  yellow,\n  blue,\n  magenta,\n  cyan,\n  white,\n  bright_black = 90,\n  bright_red,\n  bright_green,\n  bright_yellow,\n  bright_blue,\n  bright_magenta,\n  bright_cyan,\n  bright_white\n};\n\nenum class emphasis : uint8_t {\n  bold = 1,\n  faint = 1 << 1,\n  italic = 1 << 2,\n  underline = 1 << 3,\n  blink = 1 << 4,\n  reverse = 1 << 5,\n  conceal = 1 << 6,\n  strikethrough = 1 << 7,\n};\n\n// rgb is a struct for red, green and blue colors.\n// Using the name \"rgb\" makes some editors show the color in a tooltip.\nstruct rgb {\n  FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}\n  FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}\n  FMT_CONSTEXPR rgb(uint32_t hex)\n      : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}\n  FMT_CONSTEXPR rgb(color hex)\n      : r((uint32_t(hex) >> 16) & 0xFF),\n        g((uint32_t(hex) >> 8) & 0xFF),\n        b(uint32_t(hex) & 0xFF) {}\n  uint8_t r;\n  uint8_t g;\n  uint8_t b;\n};\n\nnamespace detail {\n\n// color is a struct of either a rgb color or a terminal color.\nstruct color_type {\n  FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {}\n  FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} {\n    value.rgb_color = static_cast<uint32_t>(rgb_color);\n  }\n  FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} {\n    value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |\n                      (static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;\n  }\n  FMT_CONSTEXPR color_type(terminal_color term_color) noexcept\n      : is_rgb(), value{} {\n    value.term_color = static_cast<uint8_t>(term_color);\n  }\n  bool is_rgb;\n  union color_union {\n    uint8_t term_color;\n    uint32_t rgb_color;\n  } value;\n};\n}  // namespace detail\n\n/// A text style consisting of foreground and background colors and emphasis.\nclass text_style {\n public:\n  FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept\n      : set_foreground_color(), set_background_color(), ems(em) {}\n\n  FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& {\n    if (!set_foreground_color) {\n      set_foreground_color = rhs.set_foreground_color;\n      foreground_color = rhs.foreground_color;\n    } else if (rhs.set_foreground_color) {\n      if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)\n        report_error(\"can't OR a terminal color\");\n      foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;\n    }\n\n    if (!set_background_color) {\n      set_background_color = rhs.set_background_color;\n      background_color = rhs.background_color;\n    } else if (rhs.set_background_color) {\n      if (!background_color.is_rgb || !rhs.background_color.is_rgb)\n        report_error(\"can't OR a terminal color\");\n      background_color.value.rgb_color |= rhs.background_color.value.rgb_color;\n    }\n\n    ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |\n                                static_cast<uint8_t>(rhs.ems));\n    return *this;\n  }\n\n  friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs)\n      -> text_style {\n    return lhs |= rhs;\n  }\n\n  FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {\n    return set_foreground_color;\n  }\n  FMT_CONSTEXPR auto has_background() const noexcept -> bool {\n    return set_background_color;\n  }\n  FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {\n    return static_cast<uint8_t>(ems) != 0;\n  }\n  FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {\n    FMT_ASSERT(has_foreground(), \"no foreground specified for this style\");\n    return foreground_color;\n  }\n  FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {\n    FMT_ASSERT(has_background(), \"no background specified for this style\");\n    return background_color;\n  }\n  FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {\n    FMT_ASSERT(has_emphasis(), \"no emphasis specified for this style\");\n    return ems;\n  }\n\n private:\n  FMT_CONSTEXPR text_style(bool is_foreground,\n                           detail::color_type text_color) noexcept\n      : set_foreground_color(), set_background_color(), ems() {\n    if (is_foreground) {\n      foreground_color = text_color;\n      set_foreground_color = true;\n    } else {\n      background_color = text_color;\n      set_background_color = true;\n    }\n  }\n\n  friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept\n      -> text_style;\n\n  friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept\n      -> text_style;\n\n  detail::color_type foreground_color;\n  detail::color_type background_color;\n  bool set_foreground_color;\n  bool set_background_color;\n  emphasis ems;\n};\n\n/// Creates a text style from the foreground (text) color.\nFMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept\n    -> text_style {\n  return text_style(true, foreground);\n}\n\n/// Creates a text style from the background color.\nFMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept\n    -> text_style {\n  return text_style(false, background);\n}\n\nFMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept\n    -> text_style {\n  return text_style(lhs) | rhs;\n}\n\nnamespace detail {\n\ntemplate <typename Char> struct ansi_color_escape {\n  FMT_CONSTEXPR ansi_color_escape(color_type text_color,\n                                  const char* esc) noexcept {\n    // If we have a terminal color, we need to output another escape code\n    // sequence.\n    if (!text_color.is_rgb) {\n      bool is_background = esc == string_view(\"\\x1b[48;2;\");\n      uint32_t value = text_color.value.term_color;\n      // Background ASCII codes are the same as the foreground ones but with\n      // 10 more.\n      if (is_background) value += 10u;\n\n      size_t index = 0;\n      buffer[index++] = static_cast<Char>('\\x1b');\n      buffer[index++] = static_cast<Char>('[');\n\n      if (value >= 100u) {\n        buffer[index++] = static_cast<Char>('1');\n        value %= 100u;\n      }\n      buffer[index++] = static_cast<Char>('0' + value / 10u);\n      buffer[index++] = static_cast<Char>('0' + value % 10u);\n\n      buffer[index++] = static_cast<Char>('m');\n      buffer[index++] = static_cast<Char>('\\0');\n      return;\n    }\n\n    for (int i = 0; i < 7; i++) {\n      buffer[i] = static_cast<Char>(esc[i]);\n    }\n    rgb color(text_color.value.rgb_color);\n    to_esc(color.r, buffer + 7, ';');\n    to_esc(color.g, buffer + 11, ';');\n    to_esc(color.b, buffer + 15, 'm');\n    buffer[19] = static_cast<Char>(0);\n  }\n  FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {\n    uint8_t em_codes[num_emphases] = {};\n    if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1;\n    if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2;\n    if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3;\n    if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4;\n    if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5;\n    if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7;\n    if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;\n    if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;\n\n    size_t index = 0;\n    for (size_t i = 0; i < num_emphases; ++i) {\n      if (!em_codes[i]) continue;\n      buffer[index++] = static_cast<Char>('\\x1b');\n      buffer[index++] = static_cast<Char>('[');\n      buffer[index++] = static_cast<Char>('0' + em_codes[i]);\n      buffer[index++] = static_cast<Char>('m');\n    }\n    buffer[index++] = static_cast<Char>(0);\n  }\n  FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }\n\n  FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }\n  FMT_CONSTEXPR20 auto end() const noexcept -> const Char* {\n    return buffer + basic_string_view<Char>(buffer).size();\n  }\n\n private:\n  static constexpr size_t num_emphases = 8;\n  Char buffer[7u + 3u * num_emphases + 1u];\n\n  static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,\n                                   char delimiter) noexcept {\n    out[0] = static_cast<Char>('0' + c / 100);\n    out[1] = static_cast<Char>('0' + c / 10 % 10);\n    out[2] = static_cast<Char>('0' + c % 10);\n    out[3] = static_cast<Char>(delimiter);\n  }\n  static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept\n      -> bool {\n    return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);\n  }\n};\n\ntemplate <typename Char>\nFMT_CONSTEXPR auto make_foreground_color(color_type foreground) noexcept\n    -> ansi_color_escape<Char> {\n  return ansi_color_escape<Char>(foreground, \"\\x1b[38;2;\");\n}\n\ntemplate <typename Char>\nFMT_CONSTEXPR auto make_background_color(color_type background) noexcept\n    -> ansi_color_escape<Char> {\n  return ansi_color_escape<Char>(background, \"\\x1b[48;2;\");\n}\n\ntemplate <typename Char>\nFMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept\n    -> ansi_color_escape<Char> {\n  return ansi_color_escape<Char>(em);\n}\n\ntemplate <typename Char> inline void reset_color(buffer<Char>& buffer) {\n  auto reset_color = string_view(\"\\x1b[0m\");\n  buffer.append(reset_color.begin(), reset_color.end());\n}\n\ntemplate <typename T> struct styled_arg : view {\n  const T& value;\n  text_style style;\n  styled_arg(const T& v, text_style s) : value(v), style(s) {}\n};\n\ntemplate <typename Char>\nvoid vformat_to(buffer<Char>& buf, const text_style& ts,\n                basic_string_view<Char> fmt,\n                basic_format_args<buffered_context<Char>> args) {\n  bool has_style = false;\n  if (ts.has_emphasis()) {\n    has_style = true;\n    auto emphasis = make_emphasis<Char>(ts.get_emphasis());\n    buf.append(emphasis.begin(), emphasis.end());\n  }\n  if (ts.has_foreground()) {\n    has_style = true;\n    auto foreground = make_foreground_color<Char>(ts.get_foreground());\n    buf.append(foreground.begin(), foreground.end());\n  }\n  if (ts.has_background()) {\n    has_style = true;\n    auto background = make_background_color<Char>(ts.get_background());\n    buf.append(background.begin(), background.end());\n  }\n  vformat_to(buf, fmt, args);\n  if (has_style) reset_color<Char>(buf);\n}\n}  // namespace detail\n\ninline void vprint(FILE* f, const text_style& ts, string_view fmt,\n                   format_args args) {\n  auto buf = memory_buffer();\n  detail::vformat_to(buf, ts, fmt, args);\n  print(f, FMT_STRING(\"{}\"), string_view(buf.begin(), buf.size()));\n}\n\n/**\n * Formats a string and prints it to the specified file stream using ANSI\n * escape sequences to specify text formatting.\n *\n * **Example**:\n *\n *     fmt::print(fmt::emphasis::bold | fg(fmt::color::red),\n *                \"Elapsed time: {0:.2f} seconds\", 1.23);\n */\ntemplate <typename... T>\nvoid print(FILE* f, const text_style& ts, format_string<T...> fmt,\n           T&&... args) {\n  vprint(f, ts, fmt.str, vargs<T...>{{args...}});\n}\n\n/**\n * Formats a string and prints it to stdout using ANSI escape sequences to\n * specify text formatting.\n *\n * **Example**:\n *\n *     fmt::print(fmt::emphasis::bold | fg(fmt::color::red),\n *                \"Elapsed time: {0:.2f} seconds\", 1.23);\n */\ntemplate <typename... T>\nvoid print(const text_style& ts, format_string<T...> fmt, T&&... args) {\n  return print(stdout, ts, fmt, std::forward<T>(args)...);\n}\n\ninline auto vformat(const text_style& ts, string_view fmt, format_args args)\n    -> std::string {\n  auto buf = memory_buffer();\n  detail::vformat_to(buf, ts, fmt, args);\n  return fmt::to_string(buf);\n}\n\n/**\n * Formats arguments and returns the result as a string using ANSI escape\n * sequences to specify text formatting.\n *\n * **Example**:\n *\n * ```\n * #include <fmt/color.h>\n * std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),\n *                                   \"The answer is {}\", 42);\n * ```\n */\ntemplate <typename... T>\ninline auto format(const text_style& ts, format_string<T...> fmt, T&&... args)\n    -> std::string {\n  return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}});\n}\n\n/// Formats a string with the given text_style and writes the output to `out`.\ntemplate <typename OutputIt,\n          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>\nauto vformat_to(OutputIt out, const text_style& ts, string_view fmt,\n                format_args args) -> OutputIt {\n  auto&& buf = detail::get_buffer<char>(out);\n  detail::vformat_to(buf, ts, fmt, args);\n  return detail::get_iterator(buf, out);\n}\n\n/**\n * Formats arguments with the given text style, writes the result to the output\n * iterator `out` and returns the iterator past the end of the output range.\n *\n * **Example**:\n *\n *     std::vector<char> out;\n *     fmt::format_to(std::back_inserter(out),\n *                    fmt::emphasis::bold | fg(fmt::color::red), \"{}\", 42);\n */\ntemplate <typename OutputIt, typename... T,\n          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>\ninline auto format_to(OutputIt out, const text_style& ts,\n                      format_string<T...> fmt, T&&... args) -> OutputIt {\n  return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}});\n}\n\ntemplate <typename T, typename Char>\nstruct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {\n  template <typename FormatContext>\n  auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    const auto& ts = arg.style;\n    auto out = ctx.out();\n\n    bool has_style = false;\n    if (ts.has_emphasis()) {\n      has_style = true;\n      auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());\n      out = detail::copy<Char>(emphasis.begin(), emphasis.end(), out);\n    }\n    if (ts.has_foreground()) {\n      has_style = true;\n      auto foreground =\n          detail::make_foreground_color<Char>(ts.get_foreground());\n      out = detail::copy<Char>(foreground.begin(), foreground.end(), out);\n    }\n    if (ts.has_background()) {\n      has_style = true;\n      auto background =\n          detail::make_background_color<Char>(ts.get_background());\n      out = detail::copy<Char>(background.begin(), background.end(), out);\n    }\n    out = formatter<T, Char>::format(arg.value, ctx);\n    if (has_style) {\n      auto reset_color = string_view(\"\\x1b[0m\");\n      out = detail::copy<Char>(reset_color.begin(), reset_color.end(), out);\n    }\n    return out;\n  }\n};\n\n/**\n * Returns an argument that will be formatted using ANSI escape sequences,\n * to be used in a formatting function.\n *\n * **Example**:\n *\n *     fmt::print(\"Elapsed time: {0:.2f} seconds\",\n *                fmt::styled(1.23, fmt::fg(fmt::color::green) |\n *                                  fmt::bg(fmt::color::blue)));\n */\ntemplate <typename T>\nFMT_CONSTEXPR auto styled(const T& value, text_style ts)\n    -> detail::styled_arg<remove_cvref_t<T>> {\n  return detail::styled_arg<remove_cvref_t<T>>{value, ts};\n}\n\nFMT_END_EXPORT\nFMT_END_NAMESPACE\n\n#endif  // FMT_COLOR_H_\n"
  },
  {
    "path": "dependencies/fmt/fmt/include/fmt/compile.h",
    "content": "// Formatting library for C++ - experimental format string compilation\n//\n// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors\n// All rights reserved.\n//\n// For the license information refer to format.h.\n\n#ifndef FMT_COMPILE_H_\n#define FMT_COMPILE_H_\n\n#ifndef FMT_MODULE\n#  include <iterator>  // std::back_inserter\n#endif\n\n#include \"format.h\"\n\nFMT_BEGIN_NAMESPACE\n\n// A compile-time string which is compiled into fast formatting code.\nFMT_EXPORT class compiled_string {};\n\nnamespace detail {\n\ntemplate <typename S>\nstruct is_compiled_string : std::is_base_of<compiled_string, S> {};\n\n/**\n * Converts a string literal `s` into a format string that will be parsed at\n * compile time and converted into efficient formatting code. Requires C++17\n * `constexpr if` compiler support.\n *\n * **Example**:\n *\n *     // Converts 42 into std::string using the most efficient method and no\n *     // runtime format string processing.\n *     std::string s = fmt::format(FMT_COMPILE(\"{}\"), 42);\n */\n#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)\n#  define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string)\n#else\n#  define FMT_COMPILE(s) FMT_STRING(s)\n#endif\n\n#if FMT_USE_NONTYPE_TEMPLATE_ARGS\ntemplate <typename Char, size_t N, fmt::detail::fixed_string<Char, N> Str>\nstruct udl_compiled_string : compiled_string {\n  using char_type = Char;\n  constexpr explicit operator basic_string_view<char_type>() const {\n    return {Str.data, N - 1};\n  }\n};\n#endif\n\ntemplate <typename T, typename... Tail>\nauto first(const T& value, const Tail&...) -> const T& {\n  return value;\n}\n\n#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)\ntemplate <typename... Args> struct type_list {};\n\n// Returns a reference to the argument at index N from [first, rest...].\ntemplate <int N, typename T, typename... Args>\nconstexpr const auto& get([[maybe_unused]] const T& first,\n                          [[maybe_unused]] const Args&... rest) {\n  static_assert(N < 1 + sizeof...(Args), \"index is out of bounds\");\n  if constexpr (N == 0)\n    return first;\n  else\n    return detail::get<N - 1>(rest...);\n}\n\n#  if FMT_USE_NONTYPE_TEMPLATE_ARGS\ntemplate <int N, typename T, typename... Args, typename Char>\nconstexpr auto get_arg_index_by_name(basic_string_view<Char> name) -> int {\n  if constexpr (is_static_named_arg<T>()) {\n    if (name == T::name) return N;\n  }\n  if constexpr (sizeof...(Args) > 0)\n    return get_arg_index_by_name<N + 1, Args...>(name);\n  (void)name;  // Workaround an MSVC bug about \"unused\" parameter.\n  return -1;\n}\n#  endif\n\ntemplate <typename... Args, typename Char>\nFMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int {\n#  if FMT_USE_NONTYPE_TEMPLATE_ARGS\n  if constexpr (sizeof...(Args) > 0)\n    return get_arg_index_by_name<0, Args...>(name);\n#  endif\n  (void)name;\n  return -1;\n}\n\ntemplate <typename Char, typename... Args>\nconstexpr int get_arg_index_by_name(basic_string_view<Char> name,\n                                    type_list<Args...>) {\n  return get_arg_index_by_name<Args...>(name);\n}\n\ntemplate <int N, typename> struct get_type_impl;\n\ntemplate <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {\n  using type =\n      remove_cvref_t<decltype(detail::get<N>(std::declval<Args>()...))>;\n};\n\ntemplate <int N, typename T>\nusing get_type = typename get_type_impl<N, T>::type;\n\ntemplate <typename T> struct is_compiled_format : std::false_type {};\n\ntemplate <typename Char> struct text {\n  basic_string_view<Char> data;\n  using char_type = Char;\n\n  template <typename OutputIt, typename... Args>\n  constexpr OutputIt format(OutputIt out, const Args&...) const {\n    return write<Char>(out, data);\n  }\n};\n\ntemplate <typename Char>\nstruct is_compiled_format<text<Char>> : std::true_type {};\n\ntemplate <typename Char>\nconstexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,\n                               size_t size) {\n  return {{&s[pos], size}};\n}\n\ntemplate <typename Char> struct code_unit {\n  Char value;\n  using char_type = Char;\n\n  template <typename OutputIt, typename... Args>\n  constexpr OutputIt format(OutputIt out, const Args&...) const {\n    *out++ = value;\n    return out;\n  }\n};\n\n// This ensures that the argument type is convertible to `const T&`.\ntemplate <typename T, int N, typename... Args>\nconstexpr const T& get_arg_checked(const Args&... args) {\n  const auto& arg = detail::get<N>(args...);\n  if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {\n    return arg.value;\n  } else {\n    return arg;\n  }\n}\n\ntemplate <typename Char>\nstruct is_compiled_format<code_unit<Char>> : std::true_type {};\n\n// A replacement field that refers to argument N.\ntemplate <typename Char, typename T, int N> struct field {\n  using char_type = Char;\n\n  template <typename OutputIt, typename... Args>\n  constexpr OutputIt format(OutputIt out, const Args&... args) const {\n    const T& arg = get_arg_checked<T, N>(args...);\n    if constexpr (std::is_convertible<T, basic_string_view<Char>>::value) {\n      auto s = basic_string_view<Char>(arg);\n      return copy<Char>(s.begin(), s.end(), out);\n    } else {\n      return write<Char>(out, arg);\n    }\n  }\n};\n\ntemplate <typename Char, typename T, int N>\nstruct is_compiled_format<field<Char, T, N>> : std::true_type {};\n\n// A replacement field that refers to argument with name.\ntemplate <typename Char> struct runtime_named_field {\n  using char_type = Char;\n  basic_string_view<Char> name;\n\n  template <typename OutputIt, typename T>\n  constexpr static bool try_format_argument(\n      OutputIt& out,\n      // [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9\n      [[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {\n    if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {\n      if (arg_name == arg.name) {\n        out = write<Char>(out, arg.value);\n        return true;\n      }\n    }\n    return false;\n  }\n\n  template <typename OutputIt, typename... Args>\n  constexpr OutputIt format(OutputIt out, const Args&... args) const {\n    bool found = (try_format_argument(out, name, args) || ...);\n    if (!found) {\n      FMT_THROW(format_error(\"argument with specified name is not found\"));\n    }\n    return out;\n  }\n};\n\ntemplate <typename Char>\nstruct is_compiled_format<runtime_named_field<Char>> : std::true_type {};\n\n// A replacement field that refers to argument N and has format specifiers.\ntemplate <typename Char, typename T, int N> struct spec_field {\n  using char_type = Char;\n  formatter<T, Char> fmt;\n\n  template <typename OutputIt, typename... Args>\n  constexpr FMT_INLINE OutputIt format(OutputIt out,\n                                       const Args&... args) const {\n    const auto& vargs =\n        fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);\n    basic_format_context<OutputIt, Char> ctx(out, vargs);\n    return fmt.format(get_arg_checked<T, N>(args...), ctx);\n  }\n};\n\ntemplate <typename Char, typename T, int N>\nstruct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};\n\ntemplate <typename L, typename R> struct concat {\n  L lhs;\n  R rhs;\n  using char_type = typename L::char_type;\n\n  template <typename OutputIt, typename... Args>\n  constexpr OutputIt format(OutputIt out, const Args&... args) const {\n    out = lhs.format(out, args...);\n    return rhs.format(out, args...);\n  }\n};\n\ntemplate <typename L, typename R>\nstruct is_compiled_format<concat<L, R>> : std::true_type {};\n\ntemplate <typename L, typename R>\nconstexpr concat<L, R> make_concat(L lhs, R rhs) {\n  return {lhs, rhs};\n}\n\nstruct unknown_format {};\n\ntemplate <typename Char>\nconstexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {\n  for (size_t size = str.size(); pos != size; ++pos) {\n    if (str[pos] == '{' || str[pos] == '}') break;\n  }\n  return pos;\n}\n\ntemplate <typename Args, size_t POS, int ID, typename S>\nconstexpr auto compile_format_string(S fmt);\n\ntemplate <typename Args, size_t POS, int ID, typename T, typename S>\nconstexpr auto parse_tail(T head, S fmt) {\n  if constexpr (POS != basic_string_view<typename S::char_type>(fmt).size()) {\n    constexpr auto tail = compile_format_string<Args, POS, ID>(fmt);\n    if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,\n                               unknown_format>())\n      return tail;\n    else\n      return make_concat(head, tail);\n  } else {\n    return head;\n  }\n}\n\ntemplate <typename T, typename Char> struct parse_specs_result {\n  formatter<T, Char> fmt;\n  size_t end;\n  int next_arg_id;\n};\n\nenum { manual_indexing_id = -1 };\n\ntemplate <typename T, typename Char>\nconstexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,\n                                                  size_t pos, int next_arg_id) {\n  str.remove_prefix(pos);\n  auto ctx =\n      compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);\n  auto f = formatter<T, Char>();\n  auto end = f.parse(ctx);\n  return {f, pos + fmt::detail::to_unsigned(end - str.data()),\n          next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};\n}\n\ntemplate <typename Char> struct arg_id_handler {\n  arg_id_kind kind;\n  arg_ref<Char> arg_id;\n\n  constexpr int on_auto() {\n    FMT_ASSERT(false, \"handler cannot be used with automatic indexing\");\n    return 0;\n  }\n  constexpr int on_index(int id) {\n    kind = arg_id_kind::index;\n    arg_id = arg_ref<Char>(id);\n    return 0;\n  }\n  constexpr int on_name(basic_string_view<Char> id) {\n    kind = arg_id_kind::name;\n    arg_id = arg_ref<Char>(id);\n    return 0;\n  }\n};\n\ntemplate <typename Char> struct parse_arg_id_result {\n  arg_id_kind kind;\n  arg_ref<Char> arg_id;\n  const Char* arg_id_end;\n};\n\ntemplate <int ID, typename Char>\nconstexpr auto parse_arg_id(const Char* begin, const Char* end) {\n  auto handler = arg_id_handler<Char>{arg_id_kind::none, arg_ref<Char>{}};\n  auto arg_id_end = parse_arg_id(begin, end, handler);\n  return parse_arg_id_result<Char>{handler.kind, handler.arg_id, arg_id_end};\n}\n\ntemplate <typename T, typename Enable = void> struct field_type {\n  using type = remove_cvref_t<T>;\n};\n\ntemplate <typename T>\nstruct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {\n  using type = remove_cvref_t<decltype(T::value)>;\n};\n\ntemplate <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,\n          typename S>\nconstexpr auto parse_replacement_field_then_tail(S fmt) {\n  using char_type = typename S::char_type;\n  constexpr auto str = basic_string_view<char_type>(fmt);\n  constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();\n  if constexpr (c == '}') {\n    return parse_tail<Args, END_POS + 1, NEXT_ID>(\n        field<char_type, typename field_type<T>::type, ARG_INDEX>(), fmt);\n  } else if constexpr (c != ':') {\n    FMT_THROW(format_error(\"expected ':'\"));\n  } else {\n    constexpr auto result = parse_specs<typename field_type<T>::type>(\n        str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);\n    if constexpr (result.end >= str.size() || str[result.end] != '}') {\n      FMT_THROW(format_error(\"expected '}'\"));\n      return 0;\n    } else {\n      return parse_tail<Args, result.end + 1, result.next_arg_id>(\n          spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{\n              result.fmt},\n          fmt);\n    }\n  }\n}\n\n// Compiles a non-empty format string and returns the compiled representation\n// or unknown_format() on unrecognized input.\ntemplate <typename Args, size_t POS, int ID, typename S>\nconstexpr auto compile_format_string(S fmt) {\n  using char_type = typename S::char_type;\n  constexpr auto str = basic_string_view<char_type>(fmt);\n  if constexpr (str[POS] == '{') {\n    if constexpr (POS + 1 == str.size())\n      FMT_THROW(format_error(\"unmatched '{' in format string\"));\n    if constexpr (str[POS + 1] == '{') {\n      return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);\n    } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {\n      static_assert(ID != manual_indexing_id,\n                    \"cannot switch from manual to automatic argument indexing\");\n      constexpr auto next_id =\n          ID != manual_indexing_id ? ID + 1 : manual_indexing_id;\n      return parse_replacement_field_then_tail<get_type<ID, Args>, Args,\n                                               POS + 1, ID, next_id>(fmt);\n    } else {\n      constexpr auto arg_id_result =\n          parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());\n      constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data();\n      constexpr char_type c =\n          arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();\n      static_assert(c == '}' || c == ':', \"missing '}' in format string\");\n      if constexpr (arg_id_result.kind == arg_id_kind::index) {\n        static_assert(\n            ID == manual_indexing_id || ID == 0,\n            \"cannot switch from automatic to manual argument indexing\");\n        constexpr auto arg_index = arg_id_result.arg_id.index;\n        return parse_replacement_field_then_tail<get_type<arg_index, Args>,\n                                                 Args, arg_id_end_pos,\n                                                 arg_index, manual_indexing_id>(\n            fmt);\n      } else if constexpr (arg_id_result.kind == arg_id_kind::name) {\n        constexpr auto arg_index =\n            get_arg_index_by_name(arg_id_result.arg_id.name, Args{});\n        if constexpr (arg_index >= 0) {\n          constexpr auto next_id =\n              ID != manual_indexing_id ? ID + 1 : manual_indexing_id;\n          return parse_replacement_field_then_tail<\n              decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,\n              arg_index, next_id>(fmt);\n        } else if constexpr (c == '}') {\n          return parse_tail<Args, arg_id_end_pos + 1, ID>(\n              runtime_named_field<char_type>{arg_id_result.arg_id.name}, fmt);\n        } else if constexpr (c == ':') {\n          return unknown_format();  // no type info for specs parsing\n        }\n      }\n    }\n  } else if constexpr (str[POS] == '}') {\n    if constexpr (POS + 1 == str.size())\n      FMT_THROW(format_error(\"unmatched '}' in format string\"));\n    return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);\n  } else {\n    constexpr auto end = parse_text(str, POS + 1);\n    if constexpr (end - POS > 1) {\n      return parse_tail<Args, end, ID>(make_text(str, POS, end - POS), fmt);\n    } else {\n      return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]}, fmt);\n    }\n  }\n}\n\ntemplate <typename... Args, typename S,\n          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>\nconstexpr auto compile(S fmt) {\n  constexpr auto str = basic_string_view<typename S::char_type>(fmt);\n  if constexpr (str.size() == 0) {\n    return detail::make_text(str, 0, 0);\n  } else {\n    constexpr auto result =\n        detail::compile_format_string<detail::type_list<Args...>, 0, 0>(fmt);\n    return result;\n  }\n}\n#endif  // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)\n}  // namespace detail\n\nFMT_BEGIN_EXPORT\n\n#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)\n\ntemplate <typename CompiledFormat, typename... Args,\n          typename Char = typename CompiledFormat::char_type,\n          FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>\nFMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,\n                                          const Args&... args) {\n  auto s = std::basic_string<Char>();\n  cf.format(std::back_inserter(s), args...);\n  return s;\n}\n\ntemplate <typename OutputIt, typename CompiledFormat, typename... Args,\n          FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>\nconstexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,\n                                        const Args&... args) {\n  return cf.format(out, args...);\n}\n\ntemplate <typename S, typename... Args,\n          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>\nFMT_INLINE std::basic_string<typename S::char_type> format(const S&,\n                                                           Args&&... args) {\n  if constexpr (std::is_same<typename S::char_type, char>::value) {\n    constexpr auto str = basic_string_view<typename S::char_type>(S());\n    if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {\n      const auto& first = detail::first(args...);\n      if constexpr (detail::is_named_arg<\n                        remove_cvref_t<decltype(first)>>::value) {\n        return fmt::to_string(first.value);\n      } else {\n        return fmt::to_string(first);\n      }\n    }\n  }\n  constexpr auto compiled = detail::compile<Args...>(S());\n  if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,\n                             detail::unknown_format>()) {\n    return fmt::format(\n        static_cast<basic_string_view<typename S::char_type>>(S()),\n        std::forward<Args>(args)...);\n  } else {\n    return fmt::format(compiled, std::forward<Args>(args)...);\n  }\n}\n\ntemplate <typename OutputIt, typename S, typename... Args,\n          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>\nFMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {\n  constexpr auto compiled = detail::compile<Args...>(S());\n  if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,\n                             detail::unknown_format>()) {\n    return fmt::format_to(\n        out, static_cast<basic_string_view<typename S::char_type>>(S()),\n        std::forward<Args>(args)...);\n  } else {\n    return fmt::format_to(out, compiled, std::forward<Args>(args)...);\n  }\n}\n#endif\n\ntemplate <typename OutputIt, typename S, typename... Args,\n          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>\nauto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args)\n    -> format_to_n_result<OutputIt> {\n  using traits = detail::fixed_buffer_traits;\n  auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);\n  fmt::format_to(std::back_inserter(buf), fmt, std::forward<Args>(args)...);\n  return {buf.out(), buf.count()};\n}\n\ntemplate <typename S, typename... Args,\n          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>\nFMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args)\n    -> size_t {\n  auto buf = detail::counting_buffer<>();\n  fmt::format_to(appender(buf), fmt, args...);\n  return buf.count();\n}\n\ntemplate <typename S, typename... Args,\n          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>\nvoid print(std::FILE* f, const S& fmt, const Args&... args) {\n  auto buf = memory_buffer();\n  fmt::format_to(appender(buf), fmt, args...);\n  detail::print(f, {buf.data(), buf.size()});\n}\n\ntemplate <typename S, typename... Args,\n          FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>\nvoid print(const S& fmt, const Args&... args) {\n  print(stdout, fmt, args...);\n}\n\n#if FMT_USE_NONTYPE_TEMPLATE_ARGS\ninline namespace literals {\ntemplate <detail::fixed_string Str> constexpr auto operator\"\"_cf() {\n  using char_t = remove_cvref_t<decltype(Str.data[0])>;\n  return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t),\n                                     Str>();\n}\n}  // namespace literals\n#endif\n\nFMT_END_EXPORT\nFMT_END_NAMESPACE\n\n#endif  // FMT_COMPILE_H_\n"
  },
  {
    "path": "dependencies/fmt/fmt/include/fmt/core.h",
    "content": "// This file is only provided for compatibility and may be removed in future\n// versions. Use fmt/base.h if you don't need fmt::format and fmt/format.h\n// otherwise.\n\n#include \"format.h\"\n"
  },
  {
    "path": "dependencies/fmt/fmt/include/fmt/format-inl.h",
    "content": "// Formatting library for C++ - implementation\n//\n// Copyright (c) 2012 - 2016, Victor Zverovich\n// All rights reserved.\n//\n// For the license information refer to format.h.\n\n#ifndef FMT_FORMAT_INL_H_\n#define FMT_FORMAT_INL_H_\n\n#ifndef FMT_MODULE\n#  include <algorithm>\n#  include <cerrno>  // errno\n#  include <climits>\n#  include <cmath>\n#  include <exception>\n#endif\n\n#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE)\n#  include <io.h>  // _isatty\n#endif\n\n#include \"format.h\"\n\n#if FMT_USE_LOCALE\n#  include <locale>\n#endif\n\n#ifndef FMT_FUNC\n#  define FMT_FUNC\n#endif\n\nFMT_BEGIN_NAMESPACE\nnamespace detail {\n\nFMT_FUNC void assert_fail(const char* file, int line, const char* message) {\n  // Use unchecked std::fprintf to avoid triggering another assertion when\n  // writing to stderr fails.\n  fprintf(stderr, \"%s:%d: assertion failed: %s\", file, line, message);\n  abort();\n}\n\nFMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,\n                                string_view message) noexcept {\n  // Report error code making sure that the output fits into\n  // inline_buffer_size to avoid dynamic memory allocation and potential\n  // bad_alloc.\n  out.try_resize(0);\n  static const char SEP[] = \": \";\n  static const char ERROR_STR[] = \"error \";\n  // Subtract 2 to account for terminating null characters in SEP and ERROR_STR.\n  size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;\n  auto abs_value = static_cast<uint32_or_64_or_128_t<int>>(error_code);\n  if (detail::is_negative(error_code)) {\n    abs_value = 0 - abs_value;\n    ++error_code_size;\n  }\n  error_code_size += detail::to_unsigned(detail::count_digits(abs_value));\n  auto it = appender(out);\n  if (message.size() <= inline_buffer_size - error_code_size)\n    fmt::format_to(it, FMT_STRING(\"{}{}\"), message, SEP);\n  fmt::format_to(it, FMT_STRING(\"{}{}\"), ERROR_STR, error_code);\n  FMT_ASSERT(out.size() <= inline_buffer_size, \"\");\n}\n\nFMT_FUNC void do_report_error(format_func func, int error_code,\n                              const char* message) noexcept {\n  memory_buffer full_message;\n  func(full_message, error_code, message);\n  // Don't use fwrite_all because the latter may throw.\n  if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0)\n    std::fputc('\\n', stderr);\n}\n\n// A wrapper around fwrite that throws on error.\ninline void fwrite_all(const void* ptr, size_t count, FILE* stream) {\n  size_t written = std::fwrite(ptr, 1, count, stream);\n  if (written < count)\n    FMT_THROW(system_error(errno, FMT_STRING(\"cannot write to file\")));\n}\n\n#if FMT_USE_LOCALE\nusing std::locale;\nusing std::numpunct;\nusing std::use_facet;\n\ntemplate <typename Locale>\nlocale_ref::locale_ref(const Locale& loc) : locale_(&loc) {\n  static_assert(std::is_same<Locale, locale>::value, \"\");\n}\n#else\nstruct locale {};\ntemplate <typename Char> struct numpunct {\n  auto grouping() const -> std::string { return \"\\03\"; }\n  auto thousands_sep() const -> Char { return ','; }\n  auto decimal_point() const -> Char { return '.'; }\n};\ntemplate <typename Facet> Facet use_facet(locale) { return {}; }\n#endif  // FMT_USE_LOCALE\n\ntemplate <typename Locale> auto locale_ref::get() const -> Locale {\n  static_assert(std::is_same<Locale, locale>::value, \"\");\n#if FMT_USE_LOCALE\n  if (locale_) return *static_cast<const locale*>(locale_);\n#endif\n  return locale();\n}\n\ntemplate <typename Char>\nFMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char> {\n  auto&& facet = use_facet<numpunct<Char>>(loc.get<locale>());\n  auto grouping = facet.grouping();\n  auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep();\n  return {std::move(grouping), thousands_sep};\n}\ntemplate <typename Char>\nFMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char {\n  return use_facet<numpunct<Char>>(loc.get<locale>()).decimal_point();\n}\n\n#if FMT_USE_LOCALE\nFMT_FUNC auto write_loc(appender out, loc_value value,\n                        const format_specs& specs, locale_ref loc) -> bool {\n  auto locale = loc.get<std::locale>();\n  // We cannot use the num_put<char> facet because it may produce output in\n  // a wrong encoding.\n  using facet = format_facet<std::locale>;\n  if (std::has_facet<facet>(locale))\n    return use_facet<facet>(locale).put(out, value, specs);\n  return facet(locale).put(out, value, specs);\n}\n#endif\n}  // namespace detail\n\nFMT_FUNC void report_error(const char* message) {\n#if FMT_USE_EXCEPTIONS\n  // Use FMT_THROW instead of throw to avoid bogus unreachable code warnings\n  // from MSVC.\n  FMT_THROW(format_error(message));\n#else\n  fputs(message, stderr);\n  abort();\n#endif\n}\n\ntemplate <typename Locale> typename Locale::id format_facet<Locale>::id;\n\ntemplate <typename Locale> format_facet<Locale>::format_facet(Locale& loc) {\n  auto& np = detail::use_facet<detail::numpunct<char>>(loc);\n  grouping_ = np.grouping();\n  if (!grouping_.empty()) separator_ = std::string(1, np.thousands_sep());\n}\n\n#if FMT_USE_LOCALE\ntemplate <>\nFMT_API FMT_FUNC auto format_facet<std::locale>::do_put(\n    appender out, loc_value val, const format_specs& specs) const -> bool {\n  return val.visit(\n      detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_});\n}\n#endif\n\nFMT_FUNC auto vsystem_error(int error_code, string_view fmt, format_args args)\n    -> std::system_error {\n  auto ec = std::error_code(error_code, std::generic_category());\n  return std::system_error(ec, vformat(fmt, args));\n}\n\nnamespace detail {\n\ntemplate <typename F>\ninline auto operator==(basic_fp<F> x, basic_fp<F> y) -> bool {\n  return x.f == y.f && x.e == y.e;\n}\n\n// Compilers should be able to optimize this into the ror instruction.\nFMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t {\n  r &= 31;\n  return (n >> r) | (n << (32 - r));\n}\nFMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t {\n  r &= 63;\n  return (n >> r) | (n << (64 - r));\n}\n\n// Implementation of Dragonbox algorithm: https://github.com/jk-jeon/dragonbox.\nnamespace dragonbox {\n// Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a\n// 64-bit unsigned integer.\ninline auto umul96_upper64(uint32_t x, uint64_t y) noexcept -> uint64_t {\n  return umul128_upper64(static_cast<uint64_t>(x) << 32, y);\n}\n\n// Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a\n// 128-bit unsigned integer.\ninline auto umul192_lower128(uint64_t x, uint128_fallback y) noexcept\n    -> uint128_fallback {\n  uint64_t high = x * y.high();\n  uint128_fallback high_low = umul128(x, y.low());\n  return {high + high_low.high(), high_low.low()};\n}\n\n// Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a\n// 64-bit unsigned integer.\ninline auto umul96_lower64(uint32_t x, uint64_t y) noexcept -> uint64_t {\n  return x * y;\n}\n\n// Various fast log computations.\ninline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int {\n  FMT_ASSERT(e <= 2936 && e >= -2985, \"too large exponent\");\n  return (e * 631305 - 261663) >> 21;\n}\n\nFMT_INLINE_VARIABLE constexpr struct {\n  uint32_t divisor;\n  int shift_amount;\n} div_small_pow10_infos[] = {{10, 16}, {100, 16}};\n\n// Replaces n by floor(n / pow(10, N)) returning true if and only if n is\n// divisible by pow(10, N).\n// Precondition: n <= pow(10, N + 1).\ntemplate <int N>\nauto check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept -> bool {\n  // The numbers below are chosen such that:\n  //   1. floor(n/d) = floor(nm / 2^k) where d=10 or d=100,\n  //   2. nm mod 2^k < m if and only if n is divisible by d,\n  // where m is magic_number, k is shift_amount\n  // and d is divisor.\n  //\n  // Item 1 is a common technique of replacing division by a constant with\n  // multiplication, see e.g. \"Division by Invariant Integers Using\n  // Multiplication\" by Granlund and Montgomery (1994). magic_number (m) is set\n  // to ceil(2^k/d) for large enough k.\n  // The idea for item 2 originates from Schubfach.\n  constexpr auto info = div_small_pow10_infos[N - 1];\n  FMT_ASSERT(n <= info.divisor * 10, \"n is too large\");\n  constexpr uint32_t magic_number =\n      (1u << info.shift_amount) / info.divisor + 1;\n  n *= magic_number;\n  const uint32_t comparison_mask = (1u << info.shift_amount) - 1;\n  bool result = (n & comparison_mask) < magic_number;\n  n >>= info.shift_amount;\n  return result;\n}\n\n// Computes floor(n / pow(10, N)) for small n and N.\n// Precondition: n <= pow(10, N + 1).\ntemplate <int N> auto small_division_by_pow10(uint32_t n) noexcept -> uint32_t {\n  constexpr auto info = div_small_pow10_infos[N - 1];\n  FMT_ASSERT(n <= info.divisor * 10, \"n is too large\");\n  constexpr uint32_t magic_number =\n      (1u << info.shift_amount) / info.divisor + 1;\n  return (n * magic_number) >> info.shift_amount;\n}\n\n// Computes floor(n / 10^(kappa + 1)) (float)\ninline auto divide_by_10_to_kappa_plus_1(uint32_t n) noexcept -> uint32_t {\n  // 1374389535 = ceil(2^37/100)\n  return static_cast<uint32_t>((static_cast<uint64_t>(n) * 1374389535) >> 37);\n}\n// Computes floor(n / 10^(kappa + 1)) (double)\ninline auto divide_by_10_to_kappa_plus_1(uint64_t n) noexcept -> uint64_t {\n  // 2361183241434822607 = ceil(2^(64+7)/1000)\n  return umul128_upper64(n, 2361183241434822607ull) >> 7;\n}\n\n// Various subroutines using pow10 cache\ntemplate <typename T> struct cache_accessor;\n\ntemplate <> struct cache_accessor<float> {\n  using carrier_uint = float_info<float>::carrier_uint;\n  using cache_entry_type = uint64_t;\n\n  static auto get_cached_power(int k) noexcept -> uint64_t {\n    FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,\n               \"k is out of range\");\n    static constexpr const uint64_t pow10_significands[] = {\n        0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f,\n        0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb,\n        0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28,\n        0xf1c90080baf72cb2, 0x971da05074da7bef, 0xbce5086492111aeb,\n        0xec1e4a7db69561a6, 0x9392ee8e921d5d08, 0xb877aa3236a4b44a,\n        0xe69594bec44de15c, 0x901d7cf73ab0acda, 0xb424dc35095cd810,\n        0xe12e13424bb40e14, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff,\n        0xdbe6fecebdedd5bf, 0x89705f4136b4a598, 0xabcc77118461cefd,\n        0xd6bf94d5e57a42bd, 0x8637bd05af6c69b6, 0xa7c5ac471b478424,\n        0xd1b71758e219652c, 0x83126e978d4fdf3c, 0xa3d70a3d70a3d70b,\n        0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000,\n        0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000,\n        0xc350000000000000, 0xf424000000000000, 0x9896800000000000,\n        0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000,\n        0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000,\n        0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000,\n        0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000,\n        0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000,\n        0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0,\n        0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940985,\n        0xa18f07d736b90be6, 0xc9f2c9cd04674edf, 0xfc6f7c4045812297,\n        0x9dc5ada82b70b59e, 0xc5371912364ce306, 0xf684df56c3e01bc7,\n        0x9a130b963a6c115d, 0xc097ce7bc90715b4, 0xf0bdc21abb48db21,\n        0x96769950b50d88f5, 0xbc143fa4e250eb32, 0xeb194f8e1ae525fe,\n        0x92efd1b8d0cf37bf, 0xb7abc627050305ae, 0xe596b7b0c643c71a,\n        0x8f7e32ce7bea5c70, 0xb35dbf821ae4f38c, 0xe0352f62a19e306f};\n    return pow10_significands[k - float_info<float>::min_k];\n  }\n\n  struct compute_mul_result {\n    carrier_uint result;\n    bool is_integer;\n  };\n  struct compute_mul_parity_result {\n    bool parity;\n    bool is_integer;\n  };\n\n  static auto compute_mul(carrier_uint u,\n                          const cache_entry_type& cache) noexcept\n      -> compute_mul_result {\n    auto r = umul96_upper64(u, cache);\n    return {static_cast<carrier_uint>(r >> 32),\n            static_cast<carrier_uint>(r) == 0};\n  }\n\n  static auto compute_delta(const cache_entry_type& cache, int beta) noexcept\n      -> uint32_t {\n    return static_cast<uint32_t>(cache >> (64 - 1 - beta));\n  }\n\n  static auto compute_mul_parity(carrier_uint two_f,\n                                 const cache_entry_type& cache,\n                                 int beta) noexcept\n      -> compute_mul_parity_result {\n    FMT_ASSERT(beta >= 1, \"\");\n    FMT_ASSERT(beta < 64, \"\");\n\n    auto r = umul96_lower64(two_f, cache);\n    return {((r >> (64 - beta)) & 1) != 0,\n            static_cast<uint32_t>(r >> (32 - beta)) == 0};\n  }\n\n  static auto compute_left_endpoint_for_shorter_interval_case(\n      const cache_entry_type& cache, int beta) noexcept -> carrier_uint {\n    return static_cast<carrier_uint>(\n        (cache - (cache >> (num_significand_bits<float>() + 2))) >>\n        (64 - num_significand_bits<float>() - 1 - beta));\n  }\n\n  static auto compute_right_endpoint_for_shorter_interval_case(\n      const cache_entry_type& cache, int beta) noexcept -> carrier_uint {\n    return static_cast<carrier_uint>(\n        (cache + (cache >> (num_significand_bits<float>() + 1))) >>\n        (64 - num_significand_bits<float>() - 1 - beta));\n  }\n\n  static auto compute_round_up_for_shorter_interval_case(\n      const cache_entry_type& cache, int beta) noexcept -> carrier_uint {\n    return (static_cast<carrier_uint>(\n                cache >> (64 - num_significand_bits<float>() - 2 - beta)) +\n            1) /\n           2;\n  }\n};\n\ntemplate <> struct cache_accessor<double> {\n  using carrier_uint = float_info<double>::carrier_uint;\n  using cache_entry_type = uint128_fallback;\n\n  static auto get_cached_power(int k) noexcept -> uint128_fallback {\n    FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k,\n               \"k is out of range\");\n\n    static constexpr const uint128_fallback pow10_significands[] = {\n#if FMT_USE_FULL_CACHE_DRAGONBOX\n      {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},\n      {0x9faacf3df73609b1, 0x77b191618c54e9ad},\n      {0xc795830d75038c1d, 0xd59df5b9ef6a2418},\n      {0xf97ae3d0d2446f25, 0x4b0573286b44ad1e},\n      {0x9becce62836ac577, 0x4ee367f9430aec33},\n      {0xc2e801fb244576d5, 0x229c41f793cda740},\n      {0xf3a20279ed56d48a, 0x6b43527578c11110},\n      {0x9845418c345644d6, 0x830a13896b78aaaa},\n      {0xbe5691ef416bd60c, 0x23cc986bc656d554},\n      {0xedec366b11c6cb8f, 0x2cbfbe86b7ec8aa9},\n      {0x94b3a202eb1c3f39, 0x7bf7d71432f3d6aa},\n      {0xb9e08a83a5e34f07, 0xdaf5ccd93fb0cc54},\n      {0xe858ad248f5c22c9, 0xd1b3400f8f9cff69},\n      {0x91376c36d99995be, 0x23100809b9c21fa2},\n      {0xb58547448ffffb2d, 0xabd40a0c2832a78b},\n      {0xe2e69915b3fff9f9, 0x16c90c8f323f516d},\n      {0x8dd01fad907ffc3b, 0xae3da7d97f6792e4},\n      {0xb1442798f49ffb4a, 0x99cd11cfdf41779d},\n      {0xdd95317f31c7fa1d, 0x40405643d711d584},\n      {0x8a7d3eef7f1cfc52, 0x482835ea666b2573},\n      {0xad1c8eab5ee43b66, 0xda3243650005eed0},\n      {0xd863b256369d4a40, 0x90bed43e40076a83},\n      {0x873e4f75e2224e68, 0x5a7744a6e804a292},\n      {0xa90de3535aaae202, 0x711515d0a205cb37},\n      {0xd3515c2831559a83, 0x0d5a5b44ca873e04},\n      {0x8412d9991ed58091, 0xe858790afe9486c3},\n      {0xa5178fff668ae0b6, 0x626e974dbe39a873},\n      {0xce5d73ff402d98e3, 0xfb0a3d212dc81290},\n      {0x80fa687f881c7f8e, 0x7ce66634bc9d0b9a},\n      {0xa139029f6a239f72, 0x1c1fffc1ebc44e81},\n      {0xc987434744ac874e, 0xa327ffb266b56221},\n      {0xfbe9141915d7a922, 0x4bf1ff9f0062baa9},\n      {0x9d71ac8fada6c9b5, 0x6f773fc3603db4aa},\n      {0xc4ce17b399107c22, 0xcb550fb4384d21d4},\n      {0xf6019da07f549b2b, 0x7e2a53a146606a49},\n      {0x99c102844f94e0fb, 0x2eda7444cbfc426e},\n      {0xc0314325637a1939, 0xfa911155fefb5309},\n      {0xf03d93eebc589f88, 0x793555ab7eba27cb},\n      {0x96267c7535b763b5, 0x4bc1558b2f3458df},\n      {0xbbb01b9283253ca2, 0x9eb1aaedfb016f17},\n      {0xea9c227723ee8bcb, 0x465e15a979c1cadd},\n      {0x92a1958a7675175f, 0x0bfacd89ec191eca},\n      {0xb749faed14125d36, 0xcef980ec671f667c},\n      {0xe51c79a85916f484, 0x82b7e12780e7401b},\n      {0x8f31cc0937ae58d2, 0xd1b2ecb8b0908811},\n      {0xb2fe3f0b8599ef07, 0x861fa7e6dcb4aa16},\n      {0xdfbdcece67006ac9, 0x67a791e093e1d49b},\n      {0x8bd6a141006042bd, 0xe0c8bb2c5c6d24e1},\n      {0xaecc49914078536d, 0x58fae9f773886e19},\n      {0xda7f5bf590966848, 0xaf39a475506a899f},\n      {0x888f99797a5e012d, 0x6d8406c952429604},\n      {0xaab37fd7d8f58178, 0xc8e5087ba6d33b84},\n      {0xd5605fcdcf32e1d6, 0xfb1e4a9a90880a65},\n      {0x855c3be0a17fcd26, 0x5cf2eea09a550680},\n      {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f},\n      {0xd0601d8efc57b08b, 0xf13b94daf124da27},\n      {0x823c12795db6ce57, 0x76c53d08d6b70859},\n      {0xa2cb1717b52481ed, 0x54768c4b0c64ca6f},\n      {0xcb7ddcdda26da268, 0xa9942f5dcf7dfd0a},\n      {0xfe5d54150b090b02, 0xd3f93b35435d7c4d},\n      {0x9efa548d26e5a6e1, 0xc47bc5014a1a6db0},\n      {0xc6b8e9b0709f109a, 0x359ab6419ca1091c},\n      {0xf867241c8cc6d4c0, 0xc30163d203c94b63},\n      {0x9b407691d7fc44f8, 0x79e0de63425dcf1e},\n      {0xc21094364dfb5636, 0x985915fc12f542e5},\n      {0xf294b943e17a2bc4, 0x3e6f5b7b17b2939e},\n      {0x979cf3ca6cec5b5a, 0xa705992ceecf9c43},\n      {0xbd8430bd08277231, 0x50c6ff782a838354},\n      {0xece53cec4a314ebd, 0xa4f8bf5635246429},\n      {0x940f4613ae5ed136, 0x871b7795e136be9a},\n      {0xb913179899f68584, 0x28e2557b59846e40},\n      {0xe757dd7ec07426e5, 0x331aeada2fe589d0},\n      {0x9096ea6f3848984f, 0x3ff0d2c85def7622},\n      {0xb4bca50b065abe63, 0x0fed077a756b53aa},\n      {0xe1ebce4dc7f16dfb, 0xd3e8495912c62895},\n      {0x8d3360f09cf6e4bd, 0x64712dd7abbbd95d},\n      {0xb080392cc4349dec, 0xbd8d794d96aacfb4},\n      {0xdca04777f541c567, 0xecf0d7a0fc5583a1},\n      {0x89e42caaf9491b60, 0xf41686c49db57245},\n      {0xac5d37d5b79b6239, 0x311c2875c522ced6},\n      {0xd77485cb25823ac7, 0x7d633293366b828c},\n      {0x86a8d39ef77164bc, 0xae5dff9c02033198},\n      {0xa8530886b54dbdeb, 0xd9f57f830283fdfd},\n      {0xd267caa862a12d66, 0xd072df63c324fd7c},\n      {0x8380dea93da4bc60, 0x4247cb9e59f71e6e},\n      {0xa46116538d0deb78, 0x52d9be85f074e609},\n      {0xcd795be870516656, 0x67902e276c921f8c},\n      {0x806bd9714632dff6, 0x00ba1cd8a3db53b7},\n      {0xa086cfcd97bf97f3, 0x80e8a40eccd228a5},\n      {0xc8a883c0fdaf7df0, 0x6122cd128006b2ce},\n      {0xfad2a4b13d1b5d6c, 0x796b805720085f82},\n      {0x9cc3a6eec6311a63, 0xcbe3303674053bb1},\n      {0xc3f490aa77bd60fc, 0xbedbfc4411068a9d},\n      {0xf4f1b4d515acb93b, 0xee92fb5515482d45},\n      {0x991711052d8bf3c5, 0x751bdd152d4d1c4b},\n      {0xbf5cd54678eef0b6, 0xd262d45a78a0635e},\n      {0xef340a98172aace4, 0x86fb897116c87c35},\n      {0x9580869f0e7aac0e, 0xd45d35e6ae3d4da1},\n      {0xbae0a846d2195712, 0x8974836059cca10a},\n      {0xe998d258869facd7, 0x2bd1a438703fc94c},\n      {0x91ff83775423cc06, 0x7b6306a34627ddd0},\n      {0xb67f6455292cbf08, 0x1a3bc84c17b1d543},\n      {0xe41f3d6a7377eeca, 0x20caba5f1d9e4a94},\n      {0x8e938662882af53e, 0x547eb47b7282ee9d},\n      {0xb23867fb2a35b28d, 0xe99e619a4f23aa44},\n      {0xdec681f9f4c31f31, 0x6405fa00e2ec94d5},\n      {0x8b3c113c38f9f37e, 0xde83bc408dd3dd05},\n      {0xae0b158b4738705e, 0x9624ab50b148d446},\n      {0xd98ddaee19068c76, 0x3badd624dd9b0958},\n      {0x87f8a8d4cfa417c9, 0xe54ca5d70a80e5d7},\n      {0xa9f6d30a038d1dbc, 0x5e9fcf4ccd211f4d},\n      {0xd47487cc8470652b, 0x7647c32000696720},\n      {0x84c8d4dfd2c63f3b, 0x29ecd9f40041e074},\n      {0xa5fb0a17c777cf09, 0xf468107100525891},\n      {0xcf79cc9db955c2cc, 0x7182148d4066eeb5},\n      {0x81ac1fe293d599bf, 0xc6f14cd848405531},\n      {0xa21727db38cb002f, 0xb8ada00e5a506a7d},\n      {0xca9cf1d206fdc03b, 0xa6d90811f0e4851d},\n      {0xfd442e4688bd304a, 0x908f4a166d1da664},\n      {0x9e4a9cec15763e2e, 0x9a598e4e043287ff},\n      {0xc5dd44271ad3cdba, 0x40eff1e1853f29fe},\n      {0xf7549530e188c128, 0xd12bee59e68ef47d},\n      {0x9a94dd3e8cf578b9, 0x82bb74f8301958cf},\n      {0xc13a148e3032d6e7, 0xe36a52363c1faf02},\n      {0xf18899b1bc3f8ca1, 0xdc44e6c3cb279ac2},\n      {0x96f5600f15a7b7e5, 0x29ab103a5ef8c0ba},\n      {0xbcb2b812db11a5de, 0x7415d448f6b6f0e8},\n      {0xebdf661791d60f56, 0x111b495b3464ad22},\n      {0x936b9fcebb25c995, 0xcab10dd900beec35},\n      {0xb84687c269ef3bfb, 0x3d5d514f40eea743},\n      {0xe65829b3046b0afa, 0x0cb4a5a3112a5113},\n      {0x8ff71a0fe2c2e6dc, 0x47f0e785eaba72ac},\n      {0xb3f4e093db73a093, 0x59ed216765690f57},\n      {0xe0f218b8d25088b8, 0x306869c13ec3532d},\n      {0x8c974f7383725573, 0x1e414218c73a13fc},\n      {0xafbd2350644eeacf, 0xe5d1929ef90898fb},\n      {0xdbac6c247d62a583, 0xdf45f746b74abf3a},\n      {0x894bc396ce5da772, 0x6b8bba8c328eb784},\n      {0xab9eb47c81f5114f, 0x066ea92f3f326565},\n      {0xd686619ba27255a2, 0xc80a537b0efefebe},\n      {0x8613fd0145877585, 0xbd06742ce95f5f37},\n      {0xa798fc4196e952e7, 0x2c48113823b73705},\n      {0xd17f3b51fca3a7a0, 0xf75a15862ca504c6},\n      {0x82ef85133de648c4, 0x9a984d73dbe722fc},\n      {0xa3ab66580d5fdaf5, 0xc13e60d0d2e0ebbb},\n      {0xcc963fee10b7d1b3, 0x318df905079926a9},\n      {0xffbbcfe994e5c61f, 0xfdf17746497f7053},\n      {0x9fd561f1fd0f9bd3, 0xfeb6ea8bedefa634},\n      {0xc7caba6e7c5382c8, 0xfe64a52ee96b8fc1},\n      {0xf9bd690a1b68637b, 0x3dfdce7aa3c673b1},\n      {0x9c1661a651213e2d, 0x06bea10ca65c084f},\n      {0xc31bfa0fe5698db8, 0x486e494fcff30a63},\n      {0xf3e2f893dec3f126, 0x5a89dba3c3efccfb},\n      {0x986ddb5c6b3a76b7, 0xf89629465a75e01d},\n      {0xbe89523386091465, 0xf6bbb397f1135824},\n      {0xee2ba6c0678b597f, 0x746aa07ded582e2d},\n      {0x94db483840b717ef, 0xa8c2a44eb4571cdd},\n      {0xba121a4650e4ddeb, 0x92f34d62616ce414},\n      {0xe896a0d7e51e1566, 0x77b020baf9c81d18},\n      {0x915e2486ef32cd60, 0x0ace1474dc1d122f},\n      {0xb5b5ada8aaff80b8, 0x0d819992132456bb},\n      {0xe3231912d5bf60e6, 0x10e1fff697ed6c6a},\n      {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2},\n      {0xb1736b96b6fd83b3, 0xbd308ff8a6b17cb3},\n      {0xddd0467c64bce4a0, 0xac7cb3f6d05ddbdf},\n      {0x8aa22c0dbef60ee4, 0x6bcdf07a423aa96c},\n      {0xad4ab7112eb3929d, 0x86c16c98d2c953c7},\n      {0xd89d64d57a607744, 0xe871c7bf077ba8b8},\n      {0x87625f056c7c4a8b, 0x11471cd764ad4973},\n      {0xa93af6c6c79b5d2d, 0xd598e40d3dd89bd0},\n      {0xd389b47879823479, 0x4aff1d108d4ec2c4},\n      {0x843610cb4bf160cb, 0xcedf722a585139bb},\n      {0xa54394fe1eedb8fe, 0xc2974eb4ee658829},\n      {0xce947a3da6a9273e, 0x733d226229feea33},\n      {0x811ccc668829b887, 0x0806357d5a3f5260},\n      {0xa163ff802a3426a8, 0xca07c2dcb0cf26f8},\n      {0xc9bcff6034c13052, 0xfc89b393dd02f0b6},\n      {0xfc2c3f3841f17c67, 0xbbac2078d443ace3},\n      {0x9d9ba7832936edc0, 0xd54b944b84aa4c0e},\n      {0xc5029163f384a931, 0x0a9e795e65d4df12},\n      {0xf64335bcf065d37d, 0x4d4617b5ff4a16d6},\n      {0x99ea0196163fa42e, 0x504bced1bf8e4e46},\n      {0xc06481fb9bcf8d39, 0xe45ec2862f71e1d7},\n      {0xf07da27a82c37088, 0x5d767327bb4e5a4d},\n      {0x964e858c91ba2655, 0x3a6a07f8d510f870},\n      {0xbbe226efb628afea, 0x890489f70a55368c},\n      {0xeadab0aba3b2dbe5, 0x2b45ac74ccea842f},\n      {0x92c8ae6b464fc96f, 0x3b0b8bc90012929e},\n      {0xb77ada0617e3bbcb, 0x09ce6ebb40173745},\n      {0xe55990879ddcaabd, 0xcc420a6a101d0516},\n      {0x8f57fa54c2a9eab6, 0x9fa946824a12232e},\n      {0xb32df8e9f3546564, 0x47939822dc96abfa},\n      {0xdff9772470297ebd, 0x59787e2b93bc56f8},\n      {0x8bfbea76c619ef36, 0x57eb4edb3c55b65b},\n      {0xaefae51477a06b03, 0xede622920b6b23f2},\n      {0xdab99e59958885c4, 0xe95fab368e45ecee},\n      {0x88b402f7fd75539b, 0x11dbcb0218ebb415},\n      {0xaae103b5fcd2a881, 0xd652bdc29f26a11a},\n      {0xd59944a37c0752a2, 0x4be76d3346f04960},\n      {0x857fcae62d8493a5, 0x6f70a4400c562ddc},\n      {0xa6dfbd9fb8e5b88e, 0xcb4ccd500f6bb953},\n      {0xd097ad07a71f26b2, 0x7e2000a41346a7a8},\n      {0x825ecc24c873782f, 0x8ed400668c0c28c9},\n      {0xa2f67f2dfa90563b, 0x728900802f0f32fb},\n      {0xcbb41ef979346bca, 0x4f2b40a03ad2ffba},\n      {0xfea126b7d78186bc, 0xe2f610c84987bfa9},\n      {0x9f24b832e6b0f436, 0x0dd9ca7d2df4d7ca},\n      {0xc6ede63fa05d3143, 0x91503d1c79720dbc},\n      {0xf8a95fcf88747d94, 0x75a44c6397ce912b},\n      {0x9b69dbe1b548ce7c, 0xc986afbe3ee11abb},\n      {0xc24452da229b021b, 0xfbe85badce996169},\n      {0xf2d56790ab41c2a2, 0xfae27299423fb9c4},\n      {0x97c560ba6b0919a5, 0xdccd879fc967d41b},\n      {0xbdb6b8e905cb600f, 0x5400e987bbc1c921},\n      {0xed246723473e3813, 0x290123e9aab23b69},\n      {0x9436c0760c86e30b, 0xf9a0b6720aaf6522},\n      {0xb94470938fa89bce, 0xf808e40e8d5b3e6a},\n      {0xe7958cb87392c2c2, 0xb60b1d1230b20e05},\n      {0x90bd77f3483bb9b9, 0xb1c6f22b5e6f48c3},\n      {0xb4ecd5f01a4aa828, 0x1e38aeb6360b1af4},\n      {0xe2280b6c20dd5232, 0x25c6da63c38de1b1},\n      {0x8d590723948a535f, 0x579c487e5a38ad0f},\n      {0xb0af48ec79ace837, 0x2d835a9df0c6d852},\n      {0xdcdb1b2798182244, 0xf8e431456cf88e66},\n      {0x8a08f0f8bf0f156b, 0x1b8e9ecb641b5900},\n      {0xac8b2d36eed2dac5, 0xe272467e3d222f40},\n      {0xd7adf884aa879177, 0x5b0ed81dcc6abb10},\n      {0x86ccbb52ea94baea, 0x98e947129fc2b4ea},\n      {0xa87fea27a539e9a5, 0x3f2398d747b36225},\n      {0xd29fe4b18e88640e, 0x8eec7f0d19a03aae},\n      {0x83a3eeeef9153e89, 0x1953cf68300424ad},\n      {0xa48ceaaab75a8e2b, 0x5fa8c3423c052dd8},\n      {0xcdb02555653131b6, 0x3792f412cb06794e},\n      {0x808e17555f3ebf11, 0xe2bbd88bbee40bd1},\n      {0xa0b19d2ab70e6ed6, 0x5b6aceaeae9d0ec5},\n      {0xc8de047564d20a8b, 0xf245825a5a445276},\n      {0xfb158592be068d2e, 0xeed6e2f0f0d56713},\n      {0x9ced737bb6c4183d, 0x55464dd69685606c},\n      {0xc428d05aa4751e4c, 0xaa97e14c3c26b887},\n      {0xf53304714d9265df, 0xd53dd99f4b3066a9},\n      {0x993fe2c6d07b7fab, 0xe546a8038efe402a},\n      {0xbf8fdb78849a5f96, 0xde98520472bdd034},\n      {0xef73d256a5c0f77c, 0x963e66858f6d4441},\n      {0x95a8637627989aad, 0xdde7001379a44aa9},\n      {0xbb127c53b17ec159, 0x5560c018580d5d53},\n      {0xe9d71b689dde71af, 0xaab8f01e6e10b4a7},\n      {0x9226712162ab070d, 0xcab3961304ca70e9},\n      {0xb6b00d69bb55c8d1, 0x3d607b97c5fd0d23},\n      {0xe45c10c42a2b3b05, 0x8cb89a7db77c506b},\n      {0x8eb98a7a9a5b04e3, 0x77f3608e92adb243},\n      {0xb267ed1940f1c61c, 0x55f038b237591ed4},\n      {0xdf01e85f912e37a3, 0x6b6c46dec52f6689},\n      {0x8b61313bbabce2c6, 0x2323ac4b3b3da016},\n      {0xae397d8aa96c1b77, 0xabec975e0a0d081b},\n      {0xd9c7dced53c72255, 0x96e7bd358c904a22},\n      {0x881cea14545c7575, 0x7e50d64177da2e55},\n      {0xaa242499697392d2, 0xdde50bd1d5d0b9ea},\n      {0xd4ad2dbfc3d07787, 0x955e4ec64b44e865},\n      {0x84ec3c97da624ab4, 0xbd5af13bef0b113f},\n      {0xa6274bbdd0fadd61, 0xecb1ad8aeacdd58f},\n      {0xcfb11ead453994ba, 0x67de18eda5814af3},\n      {0x81ceb32c4b43fcf4, 0x80eacf948770ced8},\n      {0xa2425ff75e14fc31, 0xa1258379a94d028e},\n      {0xcad2f7f5359a3b3e, 0x096ee45813a04331},\n      {0xfd87b5f28300ca0d, 0x8bca9d6e188853fd},\n      {0x9e74d1b791e07e48, 0x775ea264cf55347e},\n      {0xc612062576589dda, 0x95364afe032a819e},\n      {0xf79687aed3eec551, 0x3a83ddbd83f52205},\n      {0x9abe14cd44753b52, 0xc4926a9672793543},\n      {0xc16d9a0095928a27, 0x75b7053c0f178294},\n      {0xf1c90080baf72cb1, 0x5324c68b12dd6339},\n      {0x971da05074da7bee, 0xd3f6fc16ebca5e04},\n      {0xbce5086492111aea, 0x88f4bb1ca6bcf585},\n      {0xec1e4a7db69561a5, 0x2b31e9e3d06c32e6},\n      {0x9392ee8e921d5d07, 0x3aff322e62439fd0},\n      {0xb877aa3236a4b449, 0x09befeb9fad487c3},\n      {0xe69594bec44de15b, 0x4c2ebe687989a9b4},\n      {0x901d7cf73ab0acd9, 0x0f9d37014bf60a11},\n      {0xb424dc35095cd80f, 0x538484c19ef38c95},\n      {0xe12e13424bb40e13, 0x2865a5f206b06fba},\n      {0x8cbccc096f5088cb, 0xf93f87b7442e45d4},\n      {0xafebff0bcb24aafe, 0xf78f69a51539d749},\n      {0xdbe6fecebdedd5be, 0xb573440e5a884d1c},\n      {0x89705f4136b4a597, 0x31680a88f8953031},\n      {0xabcc77118461cefc, 0xfdc20d2b36ba7c3e},\n      {0xd6bf94d5e57a42bc, 0x3d32907604691b4d},\n      {0x8637bd05af6c69b5, 0xa63f9a49c2c1b110},\n      {0xa7c5ac471b478423, 0x0fcf80dc33721d54},\n      {0xd1b71758e219652b, 0xd3c36113404ea4a9},\n      {0x83126e978d4fdf3b, 0x645a1cac083126ea},\n      {0xa3d70a3d70a3d70a, 0x3d70a3d70a3d70a4},\n      {0xcccccccccccccccc, 0xcccccccccccccccd},\n      {0x8000000000000000, 0x0000000000000000},\n      {0xa000000000000000, 0x0000000000000000},\n      {0xc800000000000000, 0x0000000000000000},\n      {0xfa00000000000000, 0x0000000000000000},\n      {0x9c40000000000000, 0x0000000000000000},\n      {0xc350000000000000, 0x0000000000000000},\n      {0xf424000000000000, 0x0000000000000000},\n      {0x9896800000000000, 0x0000000000000000},\n      {0xbebc200000000000, 0x0000000000000000},\n      {0xee6b280000000000, 0x0000000000000000},\n      {0x9502f90000000000, 0x0000000000000000},\n      {0xba43b74000000000, 0x0000000000000000},\n      {0xe8d4a51000000000, 0x0000000000000000},\n      {0x9184e72a00000000, 0x0000000000000000},\n      {0xb5e620f480000000, 0x0000000000000000},\n      {0xe35fa931a0000000, 0x0000000000000000},\n      {0x8e1bc9bf04000000, 0x0000000000000000},\n      {0xb1a2bc2ec5000000, 0x0000000000000000},\n      {0xde0b6b3a76400000, 0x0000000000000000},\n      {0x8ac7230489e80000, 0x0000000000000000},\n      {0xad78ebc5ac620000, 0x0000000000000000},\n      {0xd8d726b7177a8000, 0x0000000000000000},\n      {0x878678326eac9000, 0x0000000000000000},\n      {0xa968163f0a57b400, 0x0000000000000000},\n      {0xd3c21bcecceda100, 0x0000000000000000},\n      {0x84595161401484a0, 0x0000000000000000},\n      {0xa56fa5b99019a5c8, 0x0000000000000000},\n      {0xcecb8f27f4200f3a, 0x0000000000000000},\n      {0x813f3978f8940984, 0x4000000000000000},\n      {0xa18f07d736b90be5, 0x5000000000000000},\n      {0xc9f2c9cd04674ede, 0xa400000000000000},\n      {0xfc6f7c4045812296, 0x4d00000000000000},\n      {0x9dc5ada82b70b59d, 0xf020000000000000},\n      {0xc5371912364ce305, 0x6c28000000000000},\n      {0xf684df56c3e01bc6, 0xc732000000000000},\n      {0x9a130b963a6c115c, 0x3c7f400000000000},\n      {0xc097ce7bc90715b3, 0x4b9f100000000000},\n      {0xf0bdc21abb48db20, 0x1e86d40000000000},\n      {0x96769950b50d88f4, 0x1314448000000000},\n      {0xbc143fa4e250eb31, 0x17d955a000000000},\n      {0xeb194f8e1ae525fd, 0x5dcfab0800000000},\n      {0x92efd1b8d0cf37be, 0x5aa1cae500000000},\n      {0xb7abc627050305ad, 0xf14a3d9e40000000},\n      {0xe596b7b0c643c719, 0x6d9ccd05d0000000},\n      {0x8f7e32ce7bea5c6f, 0xe4820023a2000000},\n      {0xb35dbf821ae4f38b, 0xdda2802c8a800000},\n      {0xe0352f62a19e306e, 0xd50b2037ad200000},\n      {0x8c213d9da502de45, 0x4526f422cc340000},\n      {0xaf298d050e4395d6, 0x9670b12b7f410000},\n      {0xdaf3f04651d47b4c, 0x3c0cdd765f114000},\n      {0x88d8762bf324cd0f, 0xa5880a69fb6ac800},\n      {0xab0e93b6efee0053, 0x8eea0d047a457a00},\n      {0xd5d238a4abe98068, 0x72a4904598d6d880},\n      {0x85a36366eb71f041, 0x47a6da2b7f864750},\n      {0xa70c3c40a64e6c51, 0x999090b65f67d924},\n      {0xd0cf4b50cfe20765, 0xfff4b4e3f741cf6d},\n      {0x82818f1281ed449f, 0xbff8f10e7a8921a5},\n      {0xa321f2d7226895c7, 0xaff72d52192b6a0e},\n      {0xcbea6f8ceb02bb39, 0x9bf4f8a69f764491},\n      {0xfee50b7025c36a08, 0x02f236d04753d5b5},\n      {0x9f4f2726179a2245, 0x01d762422c946591},\n      {0xc722f0ef9d80aad6, 0x424d3ad2b7b97ef6},\n      {0xf8ebad2b84e0d58b, 0xd2e0898765a7deb3},\n      {0x9b934c3b330c8577, 0x63cc55f49f88eb30},\n      {0xc2781f49ffcfa6d5, 0x3cbf6b71c76b25fc},\n      {0xf316271c7fc3908a, 0x8bef464e3945ef7b},\n      {0x97edd871cfda3a56, 0x97758bf0e3cbb5ad},\n      {0xbde94e8e43d0c8ec, 0x3d52eeed1cbea318},\n      {0xed63a231d4c4fb27, 0x4ca7aaa863ee4bde},\n      {0x945e455f24fb1cf8, 0x8fe8caa93e74ef6b},\n      {0xb975d6b6ee39e436, 0xb3e2fd538e122b45},\n      {0xe7d34c64a9c85d44, 0x60dbbca87196b617},\n      {0x90e40fbeea1d3a4a, 0xbc8955e946fe31ce},\n      {0xb51d13aea4a488dd, 0x6babab6398bdbe42},\n      {0xe264589a4dcdab14, 0xc696963c7eed2dd2},\n      {0x8d7eb76070a08aec, 0xfc1e1de5cf543ca3},\n      {0xb0de65388cc8ada8, 0x3b25a55f43294bcc},\n      {0xdd15fe86affad912, 0x49ef0eb713f39ebf},\n      {0x8a2dbf142dfcc7ab, 0x6e3569326c784338},\n      {0xacb92ed9397bf996, 0x49c2c37f07965405},\n      {0xd7e77a8f87daf7fb, 0xdc33745ec97be907},\n      {0x86f0ac99b4e8dafd, 0x69a028bb3ded71a4},\n      {0xa8acd7c0222311bc, 0xc40832ea0d68ce0d},\n      {0xd2d80db02aabd62b, 0xf50a3fa490c30191},\n      {0x83c7088e1aab65db, 0x792667c6da79e0fb},\n      {0xa4b8cab1a1563f52, 0x577001b891185939},\n      {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87},\n      {0x80b05e5ac60b6178, 0x544f8158315b05b5},\n      {0xa0dc75f1778e39d6, 0x696361ae3db1c722},\n      {0xc913936dd571c84c, 0x03bc3a19cd1e38ea},\n      {0xfb5878494ace3a5f, 0x04ab48a04065c724},\n      {0x9d174b2dcec0e47b, 0x62eb0d64283f9c77},\n      {0xc45d1df942711d9a, 0x3ba5d0bd324f8395},\n      {0xf5746577930d6500, 0xca8f44ec7ee3647a},\n      {0x9968bf6abbe85f20, 0x7e998b13cf4e1ecc},\n      {0xbfc2ef456ae276e8, 0x9e3fedd8c321a67f},\n      {0xefb3ab16c59b14a2, 0xc5cfe94ef3ea101f},\n      {0x95d04aee3b80ece5, 0xbba1f1d158724a13},\n      {0xbb445da9ca61281f, 0x2a8a6e45ae8edc98},\n      {0xea1575143cf97226, 0xf52d09d71a3293be},\n      {0x924d692ca61be758, 0x593c2626705f9c57},\n      {0xb6e0c377cfa2e12e, 0x6f8b2fb00c77836d},\n      {0xe498f455c38b997a, 0x0b6dfb9c0f956448},\n      {0x8edf98b59a373fec, 0x4724bd4189bd5ead},\n      {0xb2977ee300c50fe7, 0x58edec91ec2cb658},\n      {0xdf3d5e9bc0f653e1, 0x2f2967b66737e3ee},\n      {0x8b865b215899f46c, 0xbd79e0d20082ee75},\n      {0xae67f1e9aec07187, 0xecd8590680a3aa12},\n      {0xda01ee641a708de9, 0xe80e6f4820cc9496},\n      {0x884134fe908658b2, 0x3109058d147fdcde},\n      {0xaa51823e34a7eede, 0xbd4b46f0599fd416},\n      {0xd4e5e2cdc1d1ea96, 0x6c9e18ac7007c91b},\n      {0x850fadc09923329e, 0x03e2cf6bc604ddb1},\n      {0xa6539930bf6bff45, 0x84db8346b786151d},\n      {0xcfe87f7cef46ff16, 0xe612641865679a64},\n      {0x81f14fae158c5f6e, 0x4fcb7e8f3f60c07f},\n      {0xa26da3999aef7749, 0xe3be5e330f38f09e},\n      {0xcb090c8001ab551c, 0x5cadf5bfd3072cc6},\n      {0xfdcb4fa002162a63, 0x73d9732fc7c8f7f7},\n      {0x9e9f11c4014dda7e, 0x2867e7fddcdd9afb},\n      {0xc646d63501a1511d, 0xb281e1fd541501b9},\n      {0xf7d88bc24209a565, 0x1f225a7ca91a4227},\n      {0x9ae757596946075f, 0x3375788de9b06959},\n      {0xc1a12d2fc3978937, 0x0052d6b1641c83af},\n      {0xf209787bb47d6b84, 0xc0678c5dbd23a49b},\n      {0x9745eb4d50ce6332, 0xf840b7ba963646e1},\n      {0xbd176620a501fbff, 0xb650e5a93bc3d899},\n      {0xec5d3fa8ce427aff, 0xa3e51f138ab4cebf},\n      {0x93ba47c980e98cdf, 0xc66f336c36b10138},\n      {0xb8a8d9bbe123f017, 0xb80b0047445d4185},\n      {0xe6d3102ad96cec1d, 0xa60dc059157491e6},\n      {0x9043ea1ac7e41392, 0x87c89837ad68db30},\n      {0xb454e4a179dd1877, 0x29babe4598c311fc},\n      {0xe16a1dc9d8545e94, 0xf4296dd6fef3d67b},\n      {0x8ce2529e2734bb1d, 0x1899e4a65f58660d},\n      {0xb01ae745b101e9e4, 0x5ec05dcff72e7f90},\n      {0xdc21a1171d42645d, 0x76707543f4fa1f74},\n      {0x899504ae72497eba, 0x6a06494a791c53a9},\n      {0xabfa45da0edbde69, 0x0487db9d17636893},\n      {0xd6f8d7509292d603, 0x45a9d2845d3c42b7},\n      {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3},\n      {0xa7f26836f282b732, 0x8e6cac7768d7141f},\n      {0xd1ef0244af2364ff, 0x3207d795430cd927},\n      {0x8335616aed761f1f, 0x7f44e6bd49e807b9},\n      {0xa402b9c5a8d3a6e7, 0x5f16206c9c6209a7},\n      {0xcd036837130890a1, 0x36dba887c37a8c10},\n      {0x802221226be55a64, 0xc2494954da2c978a},\n      {0xa02aa96b06deb0fd, 0xf2db9baa10b7bd6d},\n      {0xc83553c5c8965d3d, 0x6f92829494e5acc8},\n      {0xfa42a8b73abbf48c, 0xcb772339ba1f17fa},\n      {0x9c69a97284b578d7, 0xff2a760414536efc},\n      {0xc38413cf25e2d70d, 0xfef5138519684abb},\n      {0xf46518c2ef5b8cd1, 0x7eb258665fc25d6a},\n      {0x98bf2f79d5993802, 0xef2f773ffbd97a62},\n      {0xbeeefb584aff8603, 0xaafb550ffacfd8fb},\n      {0xeeaaba2e5dbf6784, 0x95ba2a53f983cf39},\n      {0x952ab45cfa97a0b2, 0xdd945a747bf26184},\n      {0xba756174393d88df, 0x94f971119aeef9e5},\n      {0xe912b9d1478ceb17, 0x7a37cd5601aab85e},\n      {0x91abb422ccb812ee, 0xac62e055c10ab33b},\n      {0xb616a12b7fe617aa, 0x577b986b314d600a},\n      {0xe39c49765fdf9d94, 0xed5a7e85fda0b80c},\n      {0x8e41ade9fbebc27d, 0x14588f13be847308},\n      {0xb1d219647ae6b31c, 0x596eb2d8ae258fc9},\n      {0xde469fbd99a05fe3, 0x6fca5f8ed9aef3bc},\n      {0x8aec23d680043bee, 0x25de7bb9480d5855},\n      {0xada72ccc20054ae9, 0xaf561aa79a10ae6b},\n      {0xd910f7ff28069da4, 0x1b2ba1518094da05},\n      {0x87aa9aff79042286, 0x90fb44d2f05d0843},\n      {0xa99541bf57452b28, 0x353a1607ac744a54},\n      {0xd3fa922f2d1675f2, 0x42889b8997915ce9},\n      {0x847c9b5d7c2e09b7, 0x69956135febada12},\n      {0xa59bc234db398c25, 0x43fab9837e699096},\n      {0xcf02b2c21207ef2e, 0x94f967e45e03f4bc},\n      {0x8161afb94b44f57d, 0x1d1be0eebac278f6},\n      {0xa1ba1ba79e1632dc, 0x6462d92a69731733},\n      {0xca28a291859bbf93, 0x7d7b8f7503cfdcff},\n      {0xfcb2cb35e702af78, 0x5cda735244c3d43f},\n      {0x9defbf01b061adab, 0x3a0888136afa64a8},\n      {0xc56baec21c7a1916, 0x088aaa1845b8fdd1},\n      {0xf6c69a72a3989f5b, 0x8aad549e57273d46},\n      {0x9a3c2087a63f6399, 0x36ac54e2f678864c},\n      {0xc0cb28a98fcf3c7f, 0x84576a1bb416a7de},\n      {0xf0fdf2d3f3c30b9f, 0x656d44a2a11c51d6},\n      {0x969eb7c47859e743, 0x9f644ae5a4b1b326},\n      {0xbc4665b596706114, 0x873d5d9f0dde1fef},\n      {0xeb57ff22fc0c7959, 0xa90cb506d155a7eb},\n      {0x9316ff75dd87cbd8, 0x09a7f12442d588f3},\n      {0xb7dcbf5354e9bece, 0x0c11ed6d538aeb30},\n      {0xe5d3ef282a242e81, 0x8f1668c8a86da5fb},\n      {0x8fa475791a569d10, 0xf96e017d694487bd},\n      {0xb38d92d760ec4455, 0x37c981dcc395a9ad},\n      {0xe070f78d3927556a, 0x85bbe253f47b1418},\n      {0x8c469ab843b89562, 0x93956d7478ccec8f},\n      {0xaf58416654a6babb, 0x387ac8d1970027b3},\n      {0xdb2e51bfe9d0696a, 0x06997b05fcc0319f},\n      {0x88fcf317f22241e2, 0x441fece3bdf81f04},\n      {0xab3c2fddeeaad25a, 0xd527e81cad7626c4},\n      {0xd60b3bd56a5586f1, 0x8a71e223d8d3b075},\n      {0x85c7056562757456, 0xf6872d5667844e4a},\n      {0xa738c6bebb12d16c, 0xb428f8ac016561dc},\n      {0xd106f86e69d785c7, 0xe13336d701beba53},\n      {0x82a45b450226b39c, 0xecc0024661173474},\n      {0xa34d721642b06084, 0x27f002d7f95d0191},\n      {0xcc20ce9bd35c78a5, 0x31ec038df7b441f5},\n      {0xff290242c83396ce, 0x7e67047175a15272},\n      {0x9f79a169bd203e41, 0x0f0062c6e984d387},\n      {0xc75809c42c684dd1, 0x52c07b78a3e60869},\n      {0xf92e0c3537826145, 0xa7709a56ccdf8a83},\n      {0x9bbcc7a142b17ccb, 0x88a66076400bb692},\n      {0xc2abf989935ddbfe, 0x6acff893d00ea436},\n      {0xf356f7ebf83552fe, 0x0583f6b8c4124d44},\n      {0x98165af37b2153de, 0xc3727a337a8b704b},\n      {0xbe1bf1b059e9a8d6, 0x744f18c0592e4c5d},\n      {0xeda2ee1c7064130c, 0x1162def06f79df74},\n      {0x9485d4d1c63e8be7, 0x8addcb5645ac2ba9},\n      {0xb9a74a0637ce2ee1, 0x6d953e2bd7173693},\n      {0xe8111c87c5c1ba99, 0xc8fa8db6ccdd0438},\n      {0x910ab1d4db9914a0, 0x1d9c9892400a22a3},\n      {0xb54d5e4a127f59c8, 0x2503beb6d00cab4c},\n      {0xe2a0b5dc971f303a, 0x2e44ae64840fd61e},\n      {0x8da471a9de737e24, 0x5ceaecfed289e5d3},\n      {0xb10d8e1456105dad, 0x7425a83e872c5f48},\n      {0xdd50f1996b947518, 0xd12f124e28f7771a},\n      {0x8a5296ffe33cc92f, 0x82bd6b70d99aaa70},\n      {0xace73cbfdc0bfb7b, 0x636cc64d1001550c},\n      {0xd8210befd30efa5a, 0x3c47f7e05401aa4f},\n      {0x8714a775e3e95c78, 0x65acfaec34810a72},\n      {0xa8d9d1535ce3b396, 0x7f1839a741a14d0e},\n      {0xd31045a8341ca07c, 0x1ede48111209a051},\n      {0x83ea2b892091e44d, 0x934aed0aab460433},\n      {0xa4e4b66b68b65d60, 0xf81da84d56178540},\n      {0xce1de40642e3f4b9, 0x36251260ab9d668f},\n      {0x80d2ae83e9ce78f3, 0xc1d72b7c6b42601a},\n      {0xa1075a24e4421730, 0xb24cf65b8612f820},\n      {0xc94930ae1d529cfc, 0xdee033f26797b628},\n      {0xfb9b7cd9a4a7443c, 0x169840ef017da3b2},\n      {0x9d412e0806e88aa5, 0x8e1f289560ee864f},\n      {0xc491798a08a2ad4e, 0xf1a6f2bab92a27e3},\n      {0xf5b5d7ec8acb58a2, 0xae10af696774b1dc},\n      {0x9991a6f3d6bf1765, 0xacca6da1e0a8ef2a},\n      {0xbff610b0cc6edd3f, 0x17fd090a58d32af4},\n      {0xeff394dcff8a948e, 0xddfc4b4cef07f5b1},\n      {0x95f83d0a1fb69cd9, 0x4abdaf101564f98f},\n      {0xbb764c4ca7a4440f, 0x9d6d1ad41abe37f2},\n      {0xea53df5fd18d5513, 0x84c86189216dc5ee},\n      {0x92746b9be2f8552c, 0x32fd3cf5b4e49bb5},\n      {0xb7118682dbb66a77, 0x3fbc8c33221dc2a2},\n      {0xe4d5e82392a40515, 0x0fabaf3feaa5334b},\n      {0x8f05b1163ba6832d, 0x29cb4d87f2a7400f},\n      {0xb2c71d5bca9023f8, 0x743e20e9ef511013},\n      {0xdf78e4b2bd342cf6, 0x914da9246b255417},\n      {0x8bab8eefb6409c1a, 0x1ad089b6c2f7548f},\n      {0xae9672aba3d0c320, 0xa184ac2473b529b2},\n      {0xda3c0f568cc4f3e8, 0xc9e5d72d90a2741f},\n      {0x8865899617fb1871, 0x7e2fa67c7a658893},\n      {0xaa7eebfb9df9de8d, 0xddbb901b98feeab8},\n      {0xd51ea6fa85785631, 0x552a74227f3ea566},\n      {0x8533285c936b35de, 0xd53a88958f872760},\n      {0xa67ff273b8460356, 0x8a892abaf368f138},\n      {0xd01fef10a657842c, 0x2d2b7569b0432d86},\n      {0x8213f56a67f6b29b, 0x9c3b29620e29fc74},\n      {0xa298f2c501f45f42, 0x8349f3ba91b47b90},\n      {0xcb3f2f7642717713, 0x241c70a936219a74},\n      {0xfe0efb53d30dd4d7, 0xed238cd383aa0111},\n      {0x9ec95d1463e8a506, 0xf4363804324a40ab},\n      {0xc67bb4597ce2ce48, 0xb143c6053edcd0d6},\n      {0xf81aa16fdc1b81da, 0xdd94b7868e94050b},\n      {0x9b10a4e5e9913128, 0xca7cf2b4191c8327},\n      {0xc1d4ce1f63f57d72, 0xfd1c2f611f63a3f1},\n      {0xf24a01a73cf2dccf, 0xbc633b39673c8ced},\n      {0x976e41088617ca01, 0xd5be0503e085d814},\n      {0xbd49d14aa79dbc82, 0x4b2d8644d8a74e19},\n      {0xec9c459d51852ba2, 0xddf8e7d60ed1219f},\n      {0x93e1ab8252f33b45, 0xcabb90e5c942b504},\n      {0xb8da1662e7b00a17, 0x3d6a751f3b936244},\n      {0xe7109bfba19c0c9d, 0x0cc512670a783ad5},\n      {0x906a617d450187e2, 0x27fb2b80668b24c6},\n      {0xb484f9dc9641e9da, 0xb1f9f660802dedf7},\n      {0xe1a63853bbd26451, 0x5e7873f8a0396974},\n      {0x8d07e33455637eb2, 0xdb0b487b6423e1e9},\n      {0xb049dc016abc5e5f, 0x91ce1a9a3d2cda63},\n      {0xdc5c5301c56b75f7, 0x7641a140cc7810fc},\n      {0x89b9b3e11b6329ba, 0xa9e904c87fcb0a9e},\n      {0xac2820d9623bf429, 0x546345fa9fbdcd45},\n      {0xd732290fbacaf133, 0xa97c177947ad4096},\n      {0x867f59a9d4bed6c0, 0x49ed8eabcccc485e},\n      {0xa81f301449ee8c70, 0x5c68f256bfff5a75},\n      {0xd226fc195c6a2f8c, 0x73832eec6fff3112},\n      {0x83585d8fd9c25db7, 0xc831fd53c5ff7eac},\n      {0xa42e74f3d032f525, 0xba3e7ca8b77f5e56},\n      {0xcd3a1230c43fb26f, 0x28ce1bd2e55f35ec},\n      {0x80444b5e7aa7cf85, 0x7980d163cf5b81b4},\n      {0xa0555e361951c366, 0xd7e105bcc3326220},\n      {0xc86ab5c39fa63440, 0x8dd9472bf3fefaa8},\n      {0xfa856334878fc150, 0xb14f98f6f0feb952},\n      {0x9c935e00d4b9d8d2, 0x6ed1bf9a569f33d4},\n      {0xc3b8358109e84f07, 0x0a862f80ec4700c9},\n      {0xf4a642e14c6262c8, 0xcd27bb612758c0fb},\n      {0x98e7e9cccfbd7dbd, 0x8038d51cb897789d},\n      {0xbf21e44003acdd2c, 0xe0470a63e6bd56c4},\n      {0xeeea5d5004981478, 0x1858ccfce06cac75},\n      {0x95527a5202df0ccb, 0x0f37801e0c43ebc9},\n      {0xbaa718e68396cffd, 0xd30560258f54e6bb},\n      {0xe950df20247c83fd, 0x47c6b82ef32a206a},\n      {0x91d28b7416cdd27e, 0x4cdc331d57fa5442},\n      {0xb6472e511c81471d, 0xe0133fe4adf8e953},\n      {0xe3d8f9e563a198e5, 0x58180fddd97723a7},\n      {0x8e679c2f5e44ff8f, 0x570f09eaa7ea7649},\n      {0xb201833b35d63f73, 0x2cd2cc6551e513db},\n      {0xde81e40a034bcf4f, 0xf8077f7ea65e58d2},\n      {0x8b112e86420f6191, 0xfb04afaf27faf783},\n      {0xadd57a27d29339f6, 0x79c5db9af1f9b564},\n      {0xd94ad8b1c7380874, 0x18375281ae7822bd},\n      {0x87cec76f1c830548, 0x8f2293910d0b15b6},\n      {0xa9c2794ae3a3c69a, 0xb2eb3875504ddb23},\n      {0xd433179d9c8cb841, 0x5fa60692a46151ec},\n      {0x849feec281d7f328, 0xdbc7c41ba6bcd334},\n      {0xa5c7ea73224deff3, 0x12b9b522906c0801},\n      {0xcf39e50feae16bef, 0xd768226b34870a01},\n      {0x81842f29f2cce375, 0xe6a1158300d46641},\n      {0xa1e53af46f801c53, 0x60495ae3c1097fd1},\n      {0xca5e89b18b602368, 0x385bb19cb14bdfc5},\n      {0xfcf62c1dee382c42, 0x46729e03dd9ed7b6},\n      {0x9e19db92b4e31ba9, 0x6c07a2c26a8346d2},\n      {0xc5a05277621be293, 0xc7098b7305241886},\n      {0xf70867153aa2db38, 0xb8cbee4fc66d1ea8},\n      {0x9a65406d44a5c903, 0x737f74f1dc043329},\n      {0xc0fe908895cf3b44, 0x505f522e53053ff3},\n      {0xf13e34aabb430a15, 0x647726b9e7c68ff0},\n      {0x96c6e0eab509e64d, 0x5eca783430dc19f6},\n      {0xbc789925624c5fe0, 0xb67d16413d132073},\n      {0xeb96bf6ebadf77d8, 0xe41c5bd18c57e890},\n      {0x933e37a534cbaae7, 0x8e91b962f7b6f15a},\n      {0xb80dc58e81fe95a1, 0x723627bbb5a4adb1},\n      {0xe61136f2227e3b09, 0xcec3b1aaa30dd91d},\n      {0x8fcac257558ee4e6, 0x213a4f0aa5e8a7b2},\n      {0xb3bd72ed2af29e1f, 0xa988e2cd4f62d19e},\n      {0xe0accfa875af45a7, 0x93eb1b80a33b8606},\n      {0x8c6c01c9498d8b88, 0xbc72f130660533c4},\n      {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5},\n      {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2},\n#else\n      {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},\n      {0xce5d73ff402d98e3, 0xfb0a3d212dc81290},\n      {0xa6b34ad8c9dfc06f, 0xf42faa48c0ea481f},\n      {0x86a8d39ef77164bc, 0xae5dff9c02033198},\n      {0xd98ddaee19068c76, 0x3badd624dd9b0958},\n      {0xafbd2350644eeacf, 0xe5d1929ef90898fb},\n      {0x8df5efabc5979c8f, 0xca8d3ffa1ef463c2},\n      {0xe55990879ddcaabd, 0xcc420a6a101d0516},\n      {0xb94470938fa89bce, 0xf808e40e8d5b3e6a},\n      {0x95a8637627989aad, 0xdde7001379a44aa9},\n      {0xf1c90080baf72cb1, 0x5324c68b12dd6339},\n      {0xc350000000000000, 0x0000000000000000},\n      {0x9dc5ada82b70b59d, 0xf020000000000000},\n      {0xfee50b7025c36a08, 0x02f236d04753d5b5},\n      {0xcde6fd5e09abcf26, 0xed4c0226b55e6f87},\n      {0xa6539930bf6bff45, 0x84db8346b786151d},\n      {0x865b86925b9bc5c2, 0x0b8a2392ba45a9b3},\n      {0xd910f7ff28069da4, 0x1b2ba1518094da05},\n      {0xaf58416654a6babb, 0x387ac8d1970027b3},\n      {0x8da471a9de737e24, 0x5ceaecfed289e5d3},\n      {0xe4d5e82392a40515, 0x0fabaf3feaa5334b},\n      {0xb8da1662e7b00a17, 0x3d6a751f3b936244},\n      {0x95527a5202df0ccb, 0x0f37801e0c43ebc9},\n      {0xf13e34aabb430a15, 0x647726b9e7c68ff0}\n#endif\n    };\n\n#if FMT_USE_FULL_CACHE_DRAGONBOX\n    return pow10_significands[k - float_info<double>::min_k];\n#else\n    static constexpr const uint64_t powers_of_5_64[] = {\n        0x0000000000000001, 0x0000000000000005, 0x0000000000000019,\n        0x000000000000007d, 0x0000000000000271, 0x0000000000000c35,\n        0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1,\n        0x00000000001dcd65, 0x00000000009502f9, 0x0000000002e90edd,\n        0x000000000e8d4a51, 0x0000000048c27395, 0x000000016bcc41e9,\n        0x000000071afd498d, 0x0000002386f26fc1, 0x000000b1a2bc2ec5,\n        0x000003782dace9d9, 0x00001158e460913d, 0x000056bc75e2d631,\n        0x0001b1ae4d6e2ef5, 0x000878678326eac9, 0x002a5a058fc295ed,\n        0x00d3c21bcecceda1, 0x0422ca8b0a00a425, 0x14adf4b7320334b9};\n\n    static const int compression_ratio = 27;\n\n    // Compute base index.\n    int cache_index = (k - float_info<double>::min_k) / compression_ratio;\n    int kb = cache_index * compression_ratio + float_info<double>::min_k;\n    int offset = k - kb;\n\n    // Get base cache.\n    uint128_fallback base_cache = pow10_significands[cache_index];\n    if (offset == 0) return base_cache;\n\n    // Compute the required amount of bit-shift.\n    int alpha = floor_log2_pow10(kb + offset) - floor_log2_pow10(kb) - offset;\n    FMT_ASSERT(alpha > 0 && alpha < 64, \"shifting error detected\");\n\n    // Try to recover the real cache.\n    uint64_t pow5 = powers_of_5_64[offset];\n    uint128_fallback recovered_cache = umul128(base_cache.high(), pow5);\n    uint128_fallback middle_low = umul128(base_cache.low(), pow5);\n\n    recovered_cache += middle_low.high();\n\n    uint64_t high_to_middle = recovered_cache.high() << (64 - alpha);\n    uint64_t middle_to_low = recovered_cache.low() << (64 - alpha);\n\n    recovered_cache =\n        uint128_fallback{(recovered_cache.low() >> alpha) | high_to_middle,\n                         ((middle_low.low() >> alpha) | middle_to_low)};\n    FMT_ASSERT(recovered_cache.low() + 1 != 0, \"\");\n    return {recovered_cache.high(), recovered_cache.low() + 1};\n#endif\n  }\n\n  struct compute_mul_result {\n    carrier_uint result;\n    bool is_integer;\n  };\n  struct compute_mul_parity_result {\n    bool parity;\n    bool is_integer;\n  };\n\n  static auto compute_mul(carrier_uint u,\n                          const cache_entry_type& cache) noexcept\n      -> compute_mul_result {\n    auto r = umul192_upper128(u, cache);\n    return {r.high(), r.low() == 0};\n  }\n\n  static auto compute_delta(cache_entry_type const& cache, int beta) noexcept\n      -> uint32_t {\n    return static_cast<uint32_t>(cache.high() >> (64 - 1 - beta));\n  }\n\n  static auto compute_mul_parity(carrier_uint two_f,\n                                 const cache_entry_type& cache,\n                                 int beta) noexcept\n      -> compute_mul_parity_result {\n    FMT_ASSERT(beta >= 1, \"\");\n    FMT_ASSERT(beta < 64, \"\");\n\n    auto r = umul192_lower128(two_f, cache);\n    return {((r.high() >> (64 - beta)) & 1) != 0,\n            ((r.high() << beta) | (r.low() >> (64 - beta))) == 0};\n  }\n\n  static auto compute_left_endpoint_for_shorter_interval_case(\n      const cache_entry_type& cache, int beta) noexcept -> carrier_uint {\n    return (cache.high() -\n            (cache.high() >> (num_significand_bits<double>() + 2))) >>\n           (64 - num_significand_bits<double>() - 1 - beta);\n  }\n\n  static auto compute_right_endpoint_for_shorter_interval_case(\n      const cache_entry_type& cache, int beta) noexcept -> carrier_uint {\n    return (cache.high() +\n            (cache.high() >> (num_significand_bits<double>() + 1))) >>\n           (64 - num_significand_bits<double>() - 1 - beta);\n  }\n\n  static auto compute_round_up_for_shorter_interval_case(\n      const cache_entry_type& cache, int beta) noexcept -> carrier_uint {\n    return ((cache.high() >> (64 - num_significand_bits<double>() - 2 - beta)) +\n            1) /\n           2;\n  }\n};\n\nFMT_FUNC auto get_cached_power(int k) noexcept -> uint128_fallback {\n  return cache_accessor<double>::get_cached_power(k);\n}\n\n// Various integer checks\ntemplate <typename T>\nauto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool {\n  const int case_shorter_interval_left_endpoint_lower_threshold = 2;\n  const int case_shorter_interval_left_endpoint_upper_threshold = 3;\n  return exponent >= case_shorter_interval_left_endpoint_lower_threshold &&\n         exponent <= case_shorter_interval_left_endpoint_upper_threshold;\n}\n\n// Remove trailing zeros from n and return the number of zeros removed (float)\nFMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept {\n  FMT_ASSERT(n != 0, \"\");\n  // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1.\n  constexpr uint32_t mod_inv_5 = 0xcccccccd;\n  constexpr uint32_t mod_inv_25 = 0xc28f5c29;  // = mod_inv_5 * mod_inv_5\n\n  while (true) {\n    auto q = rotr(n * mod_inv_25, 2);\n    if (q > max_value<uint32_t>() / 100) break;\n    n = q;\n    s += 2;\n  }\n  auto q = rotr(n * mod_inv_5, 1);\n  if (q <= max_value<uint32_t>() / 10) {\n    n = q;\n    s |= 1;\n  }\n  return s;\n}\n\n// Removes trailing zeros and returns the number of zeros removed (double)\nFMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {\n  FMT_ASSERT(n != 0, \"\");\n\n  // This magic number is ceil(2^90 / 10^8).\n  constexpr uint64_t magic_number = 12379400392853802749ull;\n  auto nm = umul128(n, magic_number);\n\n  // Is n is divisible by 10^8?\n  if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) {\n    // If yes, work with the quotient...\n    auto n32 = static_cast<uint32_t>(nm.high() >> (90 - 64));\n    // ... and use the 32 bit variant of the function\n    int s = remove_trailing_zeros(n32, 8);\n    n = n32;\n    return s;\n  }\n\n  // If n is not divisible by 10^8, work with n itself.\n  constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd;\n  constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29;  // mod_inv_5 * mod_inv_5\n\n  int s = 0;\n  while (true) {\n    auto q = rotr(n * mod_inv_25, 2);\n    if (q > max_value<uint64_t>() / 100) break;\n    n = q;\n    s += 2;\n  }\n  auto q = rotr(n * mod_inv_5, 1);\n  if (q <= max_value<uint64_t>() / 10) {\n    n = q;\n    s |= 1;\n  }\n\n  return s;\n}\n\n// The main algorithm for shorter interval case\ntemplate <typename T>\nFMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept {\n  decimal_fp<T> ret_value;\n  // Compute k and beta\n  const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent);\n  const int beta = exponent + floor_log2_pow10(-minus_k);\n\n  // Compute xi and zi\n  using cache_entry_type = typename cache_accessor<T>::cache_entry_type;\n  const cache_entry_type cache = cache_accessor<T>::get_cached_power(-minus_k);\n\n  auto xi = cache_accessor<T>::compute_left_endpoint_for_shorter_interval_case(\n      cache, beta);\n  auto zi = cache_accessor<T>::compute_right_endpoint_for_shorter_interval_case(\n      cache, beta);\n\n  // If the left endpoint is not an integer, increase it\n  if (!is_left_endpoint_integer_shorter_interval<T>(exponent)) ++xi;\n\n  // Try bigger divisor\n  ret_value.significand = zi / 10;\n\n  // If succeed, remove trailing zeros if necessary and return\n  if (ret_value.significand * 10 >= xi) {\n    ret_value.exponent = minus_k + 1;\n    ret_value.exponent += remove_trailing_zeros(ret_value.significand);\n    return ret_value;\n  }\n\n  // Otherwise, compute the round-up of y\n  ret_value.significand =\n      cache_accessor<T>::compute_round_up_for_shorter_interval_case(cache,\n                                                                    beta);\n  ret_value.exponent = minus_k;\n\n  // When tie occurs, choose one of them according to the rule\n  if (exponent >= float_info<T>::shorter_interval_tie_lower_threshold &&\n      exponent <= float_info<T>::shorter_interval_tie_upper_threshold) {\n    ret_value.significand = ret_value.significand % 2 == 0\n                                ? ret_value.significand\n                                : ret_value.significand - 1;\n  } else if (ret_value.significand < xi) {\n    ++ret_value.significand;\n  }\n  return ret_value;\n}\n\ntemplate <typename T> auto to_decimal(T x) noexcept -> decimal_fp<T> {\n  // Step 1: integer promotion & Schubfach multiplier calculation.\n\n  using carrier_uint = typename float_info<T>::carrier_uint;\n  using cache_entry_type = typename cache_accessor<T>::cache_entry_type;\n  auto br = bit_cast<carrier_uint>(x);\n\n  // Extract significand bits and exponent bits.\n  const carrier_uint significand_mask =\n      (static_cast<carrier_uint>(1) << num_significand_bits<T>()) - 1;\n  carrier_uint significand = (br & significand_mask);\n  int exponent =\n      static_cast<int>((br & exponent_mask<T>()) >> num_significand_bits<T>());\n\n  if (exponent != 0) {  // Check if normal.\n    exponent -= exponent_bias<T>() + num_significand_bits<T>();\n\n    // Shorter interval case; proceed like Schubfach.\n    // In fact, when exponent == 1 and significand == 0, the interval is\n    // regular. However, it can be shown that the end-results are anyway same.\n    if (significand == 0) return shorter_interval_case<T>(exponent);\n\n    significand |= (static_cast<carrier_uint>(1) << num_significand_bits<T>());\n  } else {\n    // Subnormal case; the interval is always regular.\n    if (significand == 0) return {0, 0};\n    exponent =\n        std::numeric_limits<T>::min_exponent - num_significand_bits<T>() - 1;\n  }\n\n  const bool include_left_endpoint = (significand % 2 == 0);\n  const bool include_right_endpoint = include_left_endpoint;\n\n  // Compute k and beta.\n  const int minus_k = floor_log10_pow2(exponent) - float_info<T>::kappa;\n  const cache_entry_type cache = cache_accessor<T>::get_cached_power(-minus_k);\n  const int beta = exponent + floor_log2_pow10(-minus_k);\n\n  // Compute zi and deltai.\n  // 10^kappa <= deltai < 10^(kappa + 1)\n  const uint32_t deltai = cache_accessor<T>::compute_delta(cache, beta);\n  const carrier_uint two_fc = significand << 1;\n\n  // For the case of binary32, the result of integer check is not correct for\n  // 29711844 * 2^-82\n  // = 6.1442653300000000008655037797566933477355632930994033813476... * 10^-18\n  // and 29711844 * 2^-81\n  // = 1.2288530660000000001731007559513386695471126586198806762695... * 10^-17,\n  // and they are the unique counterexamples. However, since 29711844 is even,\n  // this does not cause any problem for the endpoints calculations; it can only\n  // cause a problem when we need to perform integer check for the center.\n  // Fortunately, with these inputs, that branch is never executed, so we are\n  // fine.\n  const typename cache_accessor<T>::compute_mul_result z_mul =\n      cache_accessor<T>::compute_mul((two_fc | 1) << beta, cache);\n\n  // Step 2: Try larger divisor; remove trailing zeros if necessary.\n\n  // Using an upper bound on zi, we might be able to optimize the division\n  // better than the compiler; we are computing zi / big_divisor here.\n  decimal_fp<T> ret_value;\n  ret_value.significand = divide_by_10_to_kappa_plus_1(z_mul.result);\n  uint32_t r = static_cast<uint32_t>(z_mul.result - float_info<T>::big_divisor *\n                                                        ret_value.significand);\n\n  if (r < deltai) {\n    // Exclude the right endpoint if necessary.\n    if (r == 0 && (z_mul.is_integer & !include_right_endpoint)) {\n      --ret_value.significand;\n      r = float_info<T>::big_divisor;\n      goto small_divisor_case_label;\n    }\n  } else if (r > deltai) {\n    goto small_divisor_case_label;\n  } else {\n    // r == deltai; compare fractional parts.\n    const typename cache_accessor<T>::compute_mul_parity_result x_mul =\n        cache_accessor<T>::compute_mul_parity(two_fc - 1, cache, beta);\n\n    if (!(x_mul.parity | (x_mul.is_integer & include_left_endpoint)))\n      goto small_divisor_case_label;\n  }\n  ret_value.exponent = minus_k + float_info<T>::kappa + 1;\n\n  // We may need to remove trailing zeros.\n  ret_value.exponent += remove_trailing_zeros(ret_value.significand);\n  return ret_value;\n\n  // Step 3: Find the significand with the smaller divisor.\n\nsmall_divisor_case_label:\n  ret_value.significand *= 10;\n  ret_value.exponent = minus_k + float_info<T>::kappa;\n\n  uint32_t dist = r - (deltai / 2) + (float_info<T>::small_divisor / 2);\n  const bool approx_y_parity =\n      ((dist ^ (float_info<T>::small_divisor / 2)) & 1) != 0;\n\n  // Is dist divisible by 10^kappa?\n  const bool divisible_by_small_divisor =\n      check_divisibility_and_divide_by_pow10<float_info<T>::kappa>(dist);\n\n  // Add dist / 10^kappa to the significand.\n  ret_value.significand += dist;\n\n  if (!divisible_by_small_divisor) return ret_value;\n\n  // Check z^(f) >= epsilon^(f).\n  // We have either yi == zi - epsiloni or yi == (zi - epsiloni) - 1,\n  // where yi == zi - epsiloni if and only if z^(f) >= epsilon^(f).\n  // Since there are only 2 possibilities, we only need to care about the\n  // parity. Also, zi and r should have the same parity since the divisor\n  // is an even number.\n  const auto y_mul = cache_accessor<T>::compute_mul_parity(two_fc, cache, beta);\n\n  // If z^(f) >= epsilon^(f), we might have a tie when z^(f) == epsilon^(f),\n  // or equivalently, when y is an integer.\n  if (y_mul.parity != approx_y_parity)\n    --ret_value.significand;\n  else if (y_mul.is_integer & (ret_value.significand % 2 != 0))\n    --ret_value.significand;\n  return ret_value;\n}\n}  // namespace dragonbox\n}  // namespace detail\n\ntemplate <> struct formatter<detail::bigint> {\n  FMT_CONSTEXPR auto parse(format_parse_context& ctx)\n      -> format_parse_context::iterator {\n    return ctx.begin();\n  }\n\n  auto format(const detail::bigint& n, format_context& ctx) const\n      -> format_context::iterator {\n    auto out = ctx.out();\n    bool first = true;\n    for (auto i = n.bigits_.size(); i > 0; --i) {\n      auto value = n.bigits_[i - 1u];\n      if (first) {\n        out = fmt::format_to(out, FMT_STRING(\"{:x}\"), value);\n        first = false;\n        continue;\n      }\n      out = fmt::format_to(out, FMT_STRING(\"{:08x}\"), value);\n    }\n    if (n.exp_ > 0)\n      out = fmt::format_to(out, FMT_STRING(\"p{}\"),\n                           n.exp_ * detail::bigint::bigit_bits);\n    return out;\n  }\n};\n\nFMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) {\n  for_each_codepoint(s, [this](uint32_t cp, string_view) {\n    if (cp == invalid_code_point) FMT_THROW(std::runtime_error(\"invalid utf8\"));\n    if (cp <= 0xFFFF) {\n      buffer_.push_back(static_cast<wchar_t>(cp));\n    } else {\n      cp -= 0x10000;\n      buffer_.push_back(static_cast<wchar_t>(0xD800 + (cp >> 10)));\n      buffer_.push_back(static_cast<wchar_t>(0xDC00 + (cp & 0x3FF)));\n    }\n    return true;\n  });\n  buffer_.push_back(0);\n}\n\nFMT_FUNC void format_system_error(detail::buffer<char>& out, int error_code,\n                                  const char* message) noexcept {\n  FMT_TRY {\n    auto ec = std::error_code(error_code, std::generic_category());\n    detail::write(appender(out), std::system_error(ec, message).what());\n    return;\n  }\n  FMT_CATCH(...) {}\n  format_error_code(out, error_code, message);\n}\n\nFMT_FUNC void report_system_error(int error_code,\n                                  const char* message) noexcept {\n  do_report_error(format_system_error, error_code, message);\n}\n\nFMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string {\n  // Don't optimize the \"{}\" case to keep the binary size small and because it\n  // can be better optimized in fmt::format anyway.\n  auto buffer = memory_buffer();\n  detail::vformat_to(buffer, fmt, args);\n  return to_string(buffer);\n}\n\nnamespace detail {\n\nFMT_FUNC void vformat_to(buffer<char>& buf, string_view fmt, format_args args,\n                         locale_ref loc) {\n  auto out = appender(buf);\n  if (fmt.size() == 2 && equal2(fmt.data(), \"{}\"))\n    return args.get(0).visit(default_arg_formatter<char>{out});\n  parse_format_string(\n      fmt, format_handler<char>{parse_context<char>(fmt), {out, args, loc}});\n}\n\ntemplate <typename T> struct span {\n  T* data;\n  size_t size;\n};\n\ntemplate <typename F> auto flockfile(F* f) -> decltype(_lock_file(f)) {\n  _lock_file(f);\n}\ntemplate <typename F> auto funlockfile(F* f) -> decltype(_unlock_file(f)) {\n  _unlock_file(f);\n}\n\n#ifndef getc_unlocked\ntemplate <typename F> auto getc_unlocked(F* f) -> decltype(_fgetc_nolock(f)) {\n  return _fgetc_nolock(f);\n}\n#endif\n\ntemplate <typename F = FILE, typename Enable = void>\nstruct has_flockfile : std::false_type {};\n\ntemplate <typename F>\nstruct has_flockfile<F, void_t<decltype(flockfile(&std::declval<F&>()))>>\n    : std::true_type {};\n\n// A FILE wrapper. F is FILE defined as a template parameter to make system API\n// detection work.\ntemplate <typename F> class file_base {\n public:\n  F* file_;\n\n public:\n  file_base(F* file) : file_(file) {}\n  operator F*() const { return file_; }\n\n  // Reads a code unit from the stream.\n  auto get() -> int {\n    int result = getc_unlocked(file_);\n    if (result == EOF && ferror(file_) != 0)\n      FMT_THROW(system_error(errno, FMT_STRING(\"getc failed\")));\n    return result;\n  }\n\n  // Puts the code unit back into the stream buffer.\n  void unget(char c) {\n    if (ungetc(c, file_) == EOF)\n      FMT_THROW(system_error(errno, FMT_STRING(\"ungetc failed\")));\n  }\n\n  void flush() { fflush(this->file_); }\n};\n\n// A FILE wrapper for glibc.\ntemplate <typename F> class glibc_file : public file_base<F> {\n private:\n  enum {\n    line_buffered = 0x200,  // _IO_LINE_BUF\n    unbuffered = 2          // _IO_UNBUFFERED\n  };\n\n public:\n  using file_base<F>::file_base;\n\n  auto is_buffered() const -> bool {\n    return (this->file_->_flags & unbuffered) == 0;\n  }\n\n  void init_buffer() {\n    if (this->file_->_IO_write_ptr) return;\n    // Force buffer initialization by placing and removing a char in a buffer.\n    assume(this->file_->_IO_write_ptr >= this->file_->_IO_write_end);\n    putc_unlocked(0, this->file_);\n    --this->file_->_IO_write_ptr;\n  }\n\n  // Returns the file's read buffer.\n  auto get_read_buffer() const -> span<const char> {\n    auto ptr = this->file_->_IO_read_ptr;\n    return {ptr, to_unsigned(this->file_->_IO_read_end - ptr)};\n  }\n\n  // Returns the file's write buffer.\n  auto get_write_buffer() const -> span<char> {\n    auto ptr = this->file_->_IO_write_ptr;\n    return {ptr, to_unsigned(this->file_->_IO_buf_end - ptr)};\n  }\n\n  void advance_write_buffer(size_t size) { this->file_->_IO_write_ptr += size; }\n\n  bool needs_flush() const {\n    if ((this->file_->_flags & line_buffered) == 0) return false;\n    char* end = this->file_->_IO_write_end;\n    return memchr(end, '\\n', to_unsigned(this->file_->_IO_write_ptr - end));\n  }\n\n  void flush() { fflush_unlocked(this->file_); }\n};\n\n// A FILE wrapper for Apple's libc.\ntemplate <typename F> class apple_file : public file_base<F> {\n private:\n  enum {\n    line_buffered = 1,  // __SNBF\n    unbuffered = 2      // __SLBF\n  };\n\n public:\n  using file_base<F>::file_base;\n\n  auto is_buffered() const -> bool {\n    return (this->file_->_flags & unbuffered) == 0;\n  }\n\n  void init_buffer() {\n    if (this->file_->_p) return;\n    // Force buffer initialization by placing and removing a char in a buffer.\n    putc_unlocked(0, this->file_);\n    --this->file_->_p;\n    ++this->file_->_w;\n  }\n\n  auto get_read_buffer() const -> span<const char> {\n    return {reinterpret_cast<char*>(this->file_->_p),\n            to_unsigned(this->file_->_r)};\n  }\n\n  auto get_write_buffer() const -> span<char> {\n    return {reinterpret_cast<char*>(this->file_->_p),\n            to_unsigned(this->file_->_bf._base + this->file_->_bf._size -\n                        this->file_->_p)};\n  }\n\n  void advance_write_buffer(size_t size) {\n    this->file_->_p += size;\n    this->file_->_w -= size;\n  }\n\n  bool needs_flush() const {\n    if ((this->file_->_flags & line_buffered) == 0) return false;\n    return memchr(this->file_->_p + this->file_->_w, '\\n',\n                  to_unsigned(-this->file_->_w));\n  }\n};\n\n// A fallback FILE wrapper.\ntemplate <typename F> class fallback_file : public file_base<F> {\n private:\n  char next_;  // The next unconsumed character in the buffer.\n  bool has_next_ = false;\n\n public:\n  using file_base<F>::file_base;\n\n  auto is_buffered() const -> bool { return false; }\n  auto needs_flush() const -> bool { return false; }\n  void init_buffer() {}\n\n  auto get_read_buffer() const -> span<const char> {\n    return {&next_, has_next_ ? 1u : 0u};\n  }\n\n  auto get_write_buffer() const -> span<char> { return {nullptr, 0}; }\n\n  void advance_write_buffer(size_t) {}\n\n  auto get() -> int {\n    has_next_ = false;\n    return file_base<F>::get();\n  }\n\n  void unget(char c) {\n    file_base<F>::unget(c);\n    next_ = c;\n    has_next_ = true;\n  }\n};\n\n#ifndef FMT_USE_FALLBACK_FILE\n#  define FMT_USE_FALLBACK_FILE 0\n#endif\n\ntemplate <typename F,\n          FMT_ENABLE_IF(sizeof(F::_p) != 0 && !FMT_USE_FALLBACK_FILE)>\nauto get_file(F* f, int) -> apple_file<F> {\n  return f;\n}\ntemplate <typename F,\n          FMT_ENABLE_IF(sizeof(F::_IO_read_ptr) != 0 && !FMT_USE_FALLBACK_FILE)>\ninline auto get_file(F* f, int) -> glibc_file<F> {\n  return f;\n}\n\ninline auto get_file(FILE* f, ...) -> fallback_file<FILE> { return f; }\n\nusing file_ref = decltype(get_file(static_cast<FILE*>(nullptr), 0));\n\ntemplate <typename F = FILE, typename Enable = void>\nclass file_print_buffer : public buffer<char> {\n public:\n  explicit file_print_buffer(F*) : buffer(nullptr, size_t()) {}\n};\n\ntemplate <typename F>\nclass file_print_buffer<F, enable_if_t<has_flockfile<F>::value>>\n    : public buffer<char> {\n private:\n  file_ref file_;\n\n  static void grow(buffer<char>& base, size_t) {\n    auto& self = static_cast<file_print_buffer&>(base);\n    self.file_.advance_write_buffer(self.size());\n    if (self.file_.get_write_buffer().size == 0) self.file_.flush();\n    auto buf = self.file_.get_write_buffer();\n    FMT_ASSERT(buf.size > 0, \"\");\n    self.set(buf.data, buf.size);\n    self.clear();\n  }\n\n public:\n  explicit file_print_buffer(F* f) : buffer(grow, size_t()), file_(f) {\n    flockfile(f);\n    file_.init_buffer();\n    auto buf = file_.get_write_buffer();\n    set(buf.data, buf.size);\n  }\n  ~file_print_buffer() {\n    file_.advance_write_buffer(size());\n    bool flush = file_.needs_flush();\n    F* f = file_;    // Make funlockfile depend on the template parameter F\n    funlockfile(f);  // for the system API detection to work.\n    if (flush) fflush(file_);\n  }\n};\n\n#if !defined(_WIN32) || defined(FMT_USE_WRITE_CONSOLE)\nFMT_FUNC auto write_console(int, string_view) -> bool { return false; }\n#else\nusing dword = conditional_t<sizeof(long) == 4, unsigned long, unsigned>;\nextern \"C\" __declspec(dllimport) int __stdcall WriteConsoleW(  //\n    void*, const void*, dword, dword*, void*);\n\nFMT_FUNC bool write_console(int fd, string_view text) {\n  auto u16 = utf8_to_utf16(text);\n  return WriteConsoleW(reinterpret_cast<void*>(_get_osfhandle(fd)), u16.c_str(),\n                       static_cast<dword>(u16.size()), nullptr, nullptr) != 0;\n}\n#endif\n\n#ifdef _WIN32\n// Print assuming legacy (non-Unicode) encoding.\nFMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args,\n                              bool newline) {\n  auto buffer = memory_buffer();\n  detail::vformat_to(buffer, fmt, args);\n  if (newline) buffer.push_back('\\n');\n  fwrite_all(buffer.data(), buffer.size(), f);\n}\n#endif\n\nFMT_FUNC void print(std::FILE* f, string_view text) {\n#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE)\n  int fd = _fileno(f);\n  if (_isatty(fd)) {\n    std::fflush(f);\n    if (write_console(fd, text)) return;\n  }\n#endif\n  fwrite_all(text.data(), text.size(), f);\n}\n}  // namespace detail\n\nFMT_FUNC void vprint_buffered(std::FILE* f, string_view fmt, format_args args) {\n  auto buffer = memory_buffer();\n  detail::vformat_to(buffer, fmt, args);\n  detail::print(f, {buffer.data(), buffer.size()});\n}\n\nFMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) {\n  if (!detail::file_ref(f).is_buffered() || !detail::has_flockfile<>())\n    return vprint_buffered(f, fmt, args);\n  auto&& buffer = detail::file_print_buffer<>(f);\n  return detail::vformat_to(buffer, fmt, args);\n}\n\nFMT_FUNC void vprintln(std::FILE* f, string_view fmt, format_args args) {\n  auto buffer = memory_buffer();\n  detail::vformat_to(buffer, fmt, args);\n  buffer.push_back('\\n');\n  detail::print(f, {buffer.data(), buffer.size()});\n}\n\nFMT_FUNC void vprint(string_view fmt, format_args args) {\n  vprint(stdout, fmt, args);\n}\n\nnamespace detail {\n\nstruct singleton {\n  unsigned char upper;\n  unsigned char lower_count;\n};\n\ninline auto is_printable(uint16_t x, const singleton* singletons,\n                         size_t singletons_size,\n                         const unsigned char* singleton_lowers,\n                         const unsigned char* normal, size_t normal_size)\n    -> bool {\n  auto upper = x >> 8;\n  auto lower_start = 0;\n  for (size_t i = 0; i < singletons_size; ++i) {\n    auto s = singletons[i];\n    auto lower_end = lower_start + s.lower_count;\n    if (upper < s.upper) break;\n    if (upper == s.upper) {\n      for (auto j = lower_start; j < lower_end; ++j) {\n        if (singleton_lowers[j] == (x & 0xff)) return false;\n      }\n    }\n    lower_start = lower_end;\n  }\n\n  auto xsigned = static_cast<int>(x);\n  auto current = true;\n  for (size_t i = 0; i < normal_size; ++i) {\n    auto v = static_cast<int>(normal[i]);\n    auto len = (v & 0x80) != 0 ? (v & 0x7f) << 8 | normal[++i] : v;\n    xsigned -= len;\n    if (xsigned < 0) break;\n    current = !current;\n  }\n  return current;\n}\n\n// This code is generated by support/printable.py.\nFMT_FUNC auto is_printable(uint32_t cp) -> bool {\n  static constexpr singleton singletons0[] = {\n      {0x00, 1},  {0x03, 5},  {0x05, 6},  {0x06, 3},  {0x07, 6},  {0x08, 8},\n      {0x09, 17}, {0x0a, 28}, {0x0b, 25}, {0x0c, 20}, {0x0d, 16}, {0x0e, 13},\n      {0x0f, 4},  {0x10, 3},  {0x12, 18}, {0x13, 9},  {0x16, 1},  {0x17, 5},\n      {0x18, 2},  {0x19, 3},  {0x1a, 7},  {0x1c, 2},  {0x1d, 1},  {0x1f, 22},\n      {0x20, 3},  {0x2b, 3},  {0x2c, 2},  {0x2d, 11}, {0x2e, 1},  {0x30, 3},\n      {0x31, 2},  {0x32, 1},  {0xa7, 2},  {0xa9, 2},  {0xaa, 4},  {0xab, 8},\n      {0xfa, 2},  {0xfb, 5},  {0xfd, 4},  {0xfe, 3},  {0xff, 9},\n  };\n  static constexpr unsigned char singletons0_lower[] = {\n      0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90,\n      0x1c, 0x1d, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f,\n      0x5c, 0x5d, 0x5f, 0xb5, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1,\n      0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04,\n      0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d,\n      0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf,\n      0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a,\n      0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d,\n      0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x45, 0x49, 0x57, 0x64, 0x65, 0x8d,\n      0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d,\n      0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5,\n      0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7,\n      0xce, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49,\n      0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7,\n      0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7,\n      0xfe, 0xff, 0x80, 0x0d, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x0f, 0x1f, 0x6e,\n      0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0xbb, 0xbc, 0xfa, 0x16,\n      0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e,\n      0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f,\n      0x74, 0x75, 0x96, 0x2f, 0x5f, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf,\n      0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xc0,\n      0xc1, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27,\n      0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91,\n      0xfe, 0xff, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7,\n      0xfe, 0xff,\n  };\n  static constexpr singleton singletons1[] = {\n      {0x00, 6},  {0x01, 1}, {0x03, 1},  {0x04, 2}, {0x08, 8},  {0x09, 2},\n      {0x0a, 5},  {0x0b, 2}, {0x0e, 4},  {0x10, 1}, {0x11, 2},  {0x12, 5},\n      {0x13, 17}, {0x14, 1}, {0x15, 2},  {0x17, 2}, {0x19, 13}, {0x1c, 5},\n      {0x1d, 8},  {0x24, 1}, {0x6a, 3},  {0x6b, 2}, {0xbc, 2},  {0xd1, 2},\n      {0xd4, 12}, {0xd5, 9}, {0xd6, 2},  {0xd7, 2}, {0xda, 1},  {0xe0, 5},\n      {0xe1, 2},  {0xe8, 2}, {0xee, 32}, {0xf0, 4}, {0xf8, 2},  {0xf9, 2},\n      {0xfa, 2},  {0xfb, 1},\n  };\n  static constexpr unsigned char singletons1_lower[] = {\n      0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x06, 0x07,\n      0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36,\n      0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87,\n      0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a,\n      0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x5c, 0xb6, 0xb7, 0x1b,\n      0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9,\n      0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66,\n      0x69, 0x8f, 0x92, 0x6f, 0x5f, 0xee, 0xef, 0x5a, 0x62, 0x9a, 0x9b, 0x27,\n      0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc,\n      0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7,\n      0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xc5, 0xc6,\n      0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c,\n      0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66,\n      0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0,\n      0xae, 0xaf, 0x79, 0xcc, 0x6e, 0x6f, 0x93,\n  };\n  static constexpr unsigned char normal0[] = {\n      0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04,\n      0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x35, 0x28, 0x0b, 0x80, 0xe0,\n      0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01,\n      0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03,\n      0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03,\n      0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x3a,\n      0x03, 0x11, 0x07, 0x06, 0x05, 0x10, 0x07, 0x57, 0x07, 0x02, 0x07, 0x15,\n      0x0d, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f,\n      0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80,\n      0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07,\n      0x15, 0x0b, 0x17, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06,\n      0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04,\n      0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac,\n      0x06, 0x0a, 0x06, 0x21, 0x3f, 0x4c, 0x04, 0x2d, 0x03, 0x74, 0x08, 0x3c,\n      0x03, 0x0f, 0x03, 0x3c, 0x07, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11,\n      0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x20, 0x10, 0x21, 0x0f, 0x80, 0x8c,\n      0x04, 0x82, 0x97, 0x19, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b,\n      0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xb3, 0x2d, 0x74, 0x0c, 0x80, 0xd6,\n      0x1a, 0x0c, 0x05, 0x80, 0xff, 0x05, 0x80, 0xdf, 0x0c, 0xee, 0x0d, 0x03,\n      0x84, 0x8d, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80,\n      0xcb, 0x2a, 0x38, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06,\n      0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c,\n      0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17,\n      0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80,\n      0xa5, 0x11, 0x81, 0x6d, 0x10, 0x78, 0x28, 0x2a, 0x06, 0x4c, 0x04, 0x80,\n      0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d,\n  };\n  static constexpr unsigned char normal1[] = {\n      0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f,\n      0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e,\n      0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04,\n      0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x01, 0x80, 0x90, 0x81, 0x37, 0x09,\n      0x16, 0x0a, 0x08, 0x80, 0x98, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16,\n      0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f,\n      0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36,\n      0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33,\n      0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x81, 0x26, 0x52, 0x4e, 0x28, 0x08,\n      0x2a, 0x56, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x1e, 0x0f, 0x43, 0x0e,\n      0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x3f, 0x41,\n      0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03,\n      0x05, 0x80, 0x8b, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22,\n      0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x39, 0x07, 0x0a, 0x36, 0x2c, 0x04,\n      0x10, 0x80, 0xc0, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45,\n      0x1b, 0x48, 0x08, 0x53, 0x1d, 0x39, 0x81, 0x07, 0x46, 0x0a, 0x1d, 0x03,\n      0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81,\n      0x36, 0x19, 0x80, 0xb7, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75,\n      0x0b, 0x80, 0xc4, 0x8a, 0xbc, 0x84, 0x2f, 0x8f, 0xd1, 0x82, 0x47, 0xa1,\n      0xb9, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x02, 0x60, 0x26, 0x0a, 0x46, 0x0a,\n      0x28, 0x05, 0x13, 0x82, 0xb0, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11,\n      0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x2a, 0x09,\n      0xa2, 0xf7, 0x81, 0x1f, 0x31, 0x03, 0x11, 0x04, 0x08, 0x81, 0x8c, 0x89,\n      0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x93, 0x60, 0x80, 0xf6,\n      0x0a, 0x73, 0x08, 0x6e, 0x17, 0x46, 0x80, 0x9a, 0x14, 0x0c, 0x57, 0x09,\n      0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x85, 0x50,\n      0x2b, 0x80, 0xd5, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x70, 0x3a, 0x05,\n      0x01, 0x85, 0x00, 0x80, 0xd7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83,\n      0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05,\n      0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80,\n      0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80,\n      0x9a, 0x83, 0xd8, 0x08, 0x0d, 0x03, 0x0d, 0x03, 0x74, 0x0c, 0x59, 0x07,\n      0x0c, 0x14, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x22, 0x4e,\n      0x81, 0x54, 0x0c, 0x15, 0x03, 0x03, 0x05, 0x07, 0x09, 0x19, 0x07, 0x07,\n      0x09, 0x03, 0x0d, 0x07, 0x29, 0x80, 0xcb, 0x25, 0x0a, 0x84, 0x06,\n  };\n  auto lower = static_cast<uint16_t>(cp);\n  if (cp < 0x10000) {\n    return is_printable(lower, singletons0,\n                        sizeof(singletons0) / sizeof(*singletons0),\n                        singletons0_lower, normal0, sizeof(normal0));\n  }\n  if (cp < 0x20000) {\n    return is_printable(lower, singletons1,\n                        sizeof(singletons1) / sizeof(*singletons1),\n                        singletons1_lower, normal1, sizeof(normal1));\n  }\n  if (0x2a6de <= cp && cp < 0x2a700) return false;\n  if (0x2b735 <= cp && cp < 0x2b740) return false;\n  if (0x2b81e <= cp && cp < 0x2b820) return false;\n  if (0x2cea2 <= cp && cp < 0x2ceb0) return false;\n  if (0x2ebe1 <= cp && cp < 0x2f800) return false;\n  if (0x2fa1e <= cp && cp < 0x30000) return false;\n  if (0x3134b <= cp && cp < 0xe0100) return false;\n  if (0xe01f0 <= cp && cp < 0x110000) return false;\n  return cp < 0x110000;\n}\n\n}  // namespace detail\n\nFMT_END_NAMESPACE\n\n#endif  // FMT_FORMAT_INL_H_\n"
  },
  {
    "path": "dependencies/fmt/fmt/include/fmt/format.h",
    "content": "/*\n  Formatting library for C++\n\n  Copyright (c) 2012 - present, Victor Zverovich\n\n  Permission is hereby granted, free of charge, to any person obtaining\n  a copy of this software and associated documentation files (the\n  \"Software\"), to deal in the Software without restriction, including\n  without limitation the rights to use, copy, modify, merge, publish,\n  distribute, sublicense, and/or sell copies of the Software, and to\n  permit persons to whom the Software is furnished to do so, subject to\n  the following conditions:\n\n  The above copyright notice and this permission notice shall be\n  included in all copies or substantial portions of the Software.\n\n  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n  --- Optional exception to the license ---\n\n  As an exception, if, as a result of your compiling your source code, portions\n  of this Software are embedded into a machine-executable object form of such\n  source code, you may redistribute such embedded portions in such object form\n  without including the above copyright and permission notices.\n */\n\n#ifndef FMT_FORMAT_H_\n#define FMT_FORMAT_H_\n\n#ifndef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES\n#  define _LIBCPP_REMOVE_TRANSITIVE_INCLUDES\n#  define FMT_REMOVE_TRANSITIVE_INCLUDES\n#endif\n\n#include \"base.h\"\n\n#ifndef FMT_MODULE\n#  include <cmath>    // std::signbit\n#  include <cstddef>  // std::byte\n#  include <cstdint>  // uint32_t\n#  include <cstring>  // std::memcpy\n#  include <limits>   // std::numeric_limits\n#  include <new>      // std::bad_alloc\n#  if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI)\n// Workaround for pre gcc 5 libstdc++.\n#    include <memory>  // std::allocator_traits\n#  endif\n#  include <stdexcept>     // std::runtime_error\n#  include <string>        // std::string\n#  include <system_error>  // std::system_error\n\n// Check FMT_CPLUSPLUS to avoid a warning in MSVC.\n#  if FMT_HAS_INCLUDE(<bit>) && FMT_CPLUSPLUS > 201703L\n#    include <bit>  // std::bit_cast\n#  endif\n\n// libc++ supports string_view in pre-c++17.\n#  if FMT_HAS_INCLUDE(<string_view>) && \\\n      (FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION))\n#    include <string_view>\n#    define FMT_USE_STRING_VIEW\n#  endif\n\n#  if FMT_MSC_VERSION\n#    include <intrin.h>  // _BitScanReverse[64], _umul128\n#  endif\n#endif  // FMT_MODULE\n\n#if defined(FMT_USE_NONTYPE_TEMPLATE_ARGS)\n// Use the provided definition.\n#elif defined(__NVCOMPILER)\n#  define FMT_USE_NONTYPE_TEMPLATE_ARGS 0\n#elif FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L\n#  define FMT_USE_NONTYPE_TEMPLATE_ARGS 1\n#elif defined(__cpp_nontype_template_args) && \\\n    __cpp_nontype_template_args >= 201911L\n#  define FMT_USE_NONTYPE_TEMPLATE_ARGS 1\n#elif FMT_CLANG_VERSION >= 1200 && FMT_CPLUSPLUS >= 202002L\n#  define FMT_USE_NONTYPE_TEMPLATE_ARGS 1\n#else\n#  define FMT_USE_NONTYPE_TEMPLATE_ARGS 0\n#endif\n\n#if defined __cpp_inline_variables && __cpp_inline_variables >= 201606L\n#  define FMT_INLINE_VARIABLE inline\n#else\n#  define FMT_INLINE_VARIABLE\n#endif\n\n// Check if RTTI is disabled.\n#ifdef FMT_USE_RTTI\n// Use the provided definition.\n#elif defined(__GXX_RTTI) || FMT_HAS_FEATURE(cxx_rtti) || defined(_CPPRTTI) || \\\n    defined(__INTEL_RTTI__) || defined(__RTTI)\n// __RTTI is for EDG compilers. _CPPRTTI is for MSVC.\n#  define FMT_USE_RTTI 1\n#else\n#  define FMT_USE_RTTI 0\n#endif\n\n// Visibility when compiled as a shared library/object.\n#if defined(FMT_LIB_EXPORT) || defined(FMT_SHARED)\n#  define FMT_SO_VISIBILITY(value) FMT_VISIBILITY(value)\n#else\n#  define FMT_SO_VISIBILITY(value)\n#endif\n\n#if FMT_GCC_VERSION || FMT_CLANG_VERSION\n#  define FMT_NOINLINE __attribute__((noinline))\n#else\n#  define FMT_NOINLINE\n#endif\n\nnamespace std {\ntemplate <typename T> struct iterator_traits<fmt::basic_appender<T>> {\n  using iterator_category = output_iterator_tag;\n  using value_type = T;\n  using difference_type =\n      decltype(static_cast<int*>(nullptr) - static_cast<int*>(nullptr));\n  using pointer = void;\n  using reference = void;\n};\n}  // namespace std\n\n#ifndef FMT_THROW\n#  if FMT_USE_EXCEPTIONS\n#    if FMT_MSC_VERSION || defined(__NVCC__)\nFMT_BEGIN_NAMESPACE\nnamespace detail {\ntemplate <typename Exception> inline void do_throw(const Exception& x) {\n  // Silence unreachable code warnings in MSVC and NVCC because these\n  // are nearly impossible to fix in a generic code.\n  volatile bool b = true;\n  if (b) throw x;\n}\n}  // namespace detail\nFMT_END_NAMESPACE\n#      define FMT_THROW(x) detail::do_throw(x)\n#    else\n#      define FMT_THROW(x) throw x\n#    endif\n#  else\n#    define FMT_THROW(x) \\\n      ::fmt::detail::assert_fail(__FILE__, __LINE__, (x).what())\n#  endif  // FMT_USE_EXCEPTIONS\n#endif    // FMT_THROW\n\n// Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of\n// integer formatter template instantiations to just one by only using the\n// largest integer type. This results in a reduction in binary size but will\n// cause a decrease in integer formatting performance.\n#if !defined(FMT_REDUCE_INT_INSTANTIATIONS)\n#  define FMT_REDUCE_INT_INSTANTIATIONS 0\n#endif\n\nFMT_BEGIN_NAMESPACE\n\ntemplate <typename Char, typename Traits, typename Allocator>\nstruct is_contiguous<std::basic_string<Char, Traits, Allocator>>\n    : std::true_type {};\n\nnamespace detail {\n\n// __builtin_clz is broken in clang with Microsoft codegen:\n// https://github.com/fmtlib/fmt/issues/519.\n#if !FMT_MSC_VERSION\n#  if FMT_HAS_BUILTIN(__builtin_clz) || FMT_GCC_VERSION || FMT_ICC_VERSION\n#    define FMT_BUILTIN_CLZ(n) __builtin_clz(n)\n#  endif\n#  if FMT_HAS_BUILTIN(__builtin_clzll) || FMT_GCC_VERSION || FMT_ICC_VERSION\n#    define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n)\n#  endif\n#endif\n\n// Some compilers masquerade as both MSVC and GCC but otherwise support\n// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the\n// MSVC intrinsics if the clz and clzll builtins are not available.\n#if FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL)\n// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning.\n#  ifndef __clang__\n#    pragma intrinsic(_BitScanReverse)\n#    ifdef _WIN64\n#      pragma intrinsic(_BitScanReverse64)\n#    endif\n#  endif\n\ninline auto clz(uint32_t x) -> int {\n  FMT_ASSERT(x != 0, \"\");\n  FMT_MSC_WARNING(suppress : 6102)  // Suppress a bogus static analysis warning.\n  unsigned long r = 0;\n  _BitScanReverse(&r, x);\n  return 31 ^ static_cast<int>(r);\n}\n#  define FMT_BUILTIN_CLZ(n) detail::clz(n)\n\ninline auto clzll(uint64_t x) -> int {\n  FMT_ASSERT(x != 0, \"\");\n  FMT_MSC_WARNING(suppress : 6102)  // Suppress a bogus static analysis warning.\n  unsigned long r = 0;\n#  ifdef _WIN64\n  _BitScanReverse64(&r, x);\n#  else\n  // Scan the high 32 bits.\n  if (_BitScanReverse(&r, static_cast<uint32_t>(x >> 32)))\n    return 63 ^ static_cast<int>(r + 32);\n  // Scan the low 32 bits.\n  _BitScanReverse(&r, static_cast<uint32_t>(x));\n#  endif\n  return 63 ^ static_cast<int>(r);\n}\n#  define FMT_BUILTIN_CLZLL(n) detail::clzll(n)\n#endif  // FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL)\n\nFMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) {\n  ignore_unused(condition);\n#ifdef FMT_FUZZ\n  if (condition) throw std::runtime_error(\"fuzzing limit reached\");\n#endif\n}\n\n#if defined(FMT_USE_STRING_VIEW)\ntemplate <typename Char> using std_string_view = std::basic_string_view<Char>;\n#else\ntemplate <typename Char> struct std_string_view {\n  operator basic_string_view<Char>() const;\n};\n#endif\n\ntemplate <typename Char, Char... C> struct string_literal {\n  static constexpr Char value[sizeof...(C)] = {C...};\n  constexpr operator basic_string_view<Char>() const {\n    return {value, sizeof...(C)};\n  }\n};\n#if FMT_CPLUSPLUS < 201703L\ntemplate <typename Char, Char... C>\nconstexpr Char string_literal<Char, C...>::value[sizeof...(C)];\n#endif\n\n// Implementation of std::bit_cast for pre-C++20.\ntemplate <typename To, typename From, FMT_ENABLE_IF(sizeof(To) == sizeof(From))>\nFMT_CONSTEXPR20 auto bit_cast(const From& from) -> To {\n#ifdef __cpp_lib_bit_cast\n  if (is_constant_evaluated()) return std::bit_cast<To>(from);\n#endif\n  auto to = To();\n  // The cast suppresses a bogus -Wclass-memaccess on GCC.\n  std::memcpy(static_cast<void*>(&to), &from, sizeof(to));\n  return to;\n}\n\ninline auto is_big_endian() -> bool {\n#ifdef _WIN32\n  return false;\n#elif defined(__BIG_ENDIAN__)\n  return true;\n#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__)\n  return __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__;\n#else\n  struct bytes {\n    char data[sizeof(int)];\n  };\n  return bit_cast<bytes>(1).data[0] == 0;\n#endif\n}\n\nclass uint128_fallback {\n private:\n  uint64_t lo_, hi_;\n\n public:\n  constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {}\n  constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {}\n\n  constexpr auto high() const noexcept -> uint64_t { return hi_; }\n  constexpr auto low() const noexcept -> uint64_t { return lo_; }\n\n  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>\n  constexpr explicit operator T() const {\n    return static_cast<T>(lo_);\n  }\n\n  friend constexpr auto operator==(const uint128_fallback& lhs,\n                                   const uint128_fallback& rhs) -> bool {\n    return lhs.hi_ == rhs.hi_ && lhs.lo_ == rhs.lo_;\n  }\n  friend constexpr auto operator!=(const uint128_fallback& lhs,\n                                   const uint128_fallback& rhs) -> bool {\n    return !(lhs == rhs);\n  }\n  friend constexpr auto operator>(const uint128_fallback& lhs,\n                                  const uint128_fallback& rhs) -> bool {\n    return lhs.hi_ != rhs.hi_ ? lhs.hi_ > rhs.hi_ : lhs.lo_ > rhs.lo_;\n  }\n  friend constexpr auto operator|(const uint128_fallback& lhs,\n                                  const uint128_fallback& rhs)\n      -> uint128_fallback {\n    return {lhs.hi_ | rhs.hi_, lhs.lo_ | rhs.lo_};\n  }\n  friend constexpr auto operator&(const uint128_fallback& lhs,\n                                  const uint128_fallback& rhs)\n      -> uint128_fallback {\n    return {lhs.hi_ & rhs.hi_, lhs.lo_ & rhs.lo_};\n  }\n  friend constexpr auto operator~(const uint128_fallback& n)\n      -> uint128_fallback {\n    return {~n.hi_, ~n.lo_};\n  }\n  friend FMT_CONSTEXPR auto operator+(const uint128_fallback& lhs,\n                                      const uint128_fallback& rhs)\n      -> uint128_fallback {\n    auto result = uint128_fallback(lhs);\n    result += rhs;\n    return result;\n  }\n  friend FMT_CONSTEXPR auto operator*(const uint128_fallback& lhs, uint32_t rhs)\n      -> uint128_fallback {\n    FMT_ASSERT(lhs.hi_ == 0, \"\");\n    uint64_t hi = (lhs.lo_ >> 32) * rhs;\n    uint64_t lo = (lhs.lo_ & ~uint32_t()) * rhs;\n    uint64_t new_lo = (hi << 32) + lo;\n    return {(hi >> 32) + (new_lo < lo ? 1 : 0), new_lo};\n  }\n  friend constexpr auto operator-(const uint128_fallback& lhs, uint64_t rhs)\n      -> uint128_fallback {\n    return {lhs.hi_ - (lhs.lo_ < rhs ? 1 : 0), lhs.lo_ - rhs};\n  }\n  FMT_CONSTEXPR auto operator>>(int shift) const -> uint128_fallback {\n    if (shift == 64) return {0, hi_};\n    if (shift > 64) return uint128_fallback(0, hi_) >> (shift - 64);\n    return {hi_ >> shift, (hi_ << (64 - shift)) | (lo_ >> shift)};\n  }\n  FMT_CONSTEXPR auto operator<<(int shift) const -> uint128_fallback {\n    if (shift == 64) return {lo_, 0};\n    if (shift > 64) return uint128_fallback(lo_, 0) << (shift - 64);\n    return {hi_ << shift | (lo_ >> (64 - shift)), (lo_ << shift)};\n  }\n  FMT_CONSTEXPR auto operator>>=(int shift) -> uint128_fallback& {\n    return *this = *this >> shift;\n  }\n  FMT_CONSTEXPR void operator+=(uint128_fallback n) {\n    uint64_t new_lo = lo_ + n.lo_;\n    uint64_t new_hi = hi_ + n.hi_ + (new_lo < lo_ ? 1 : 0);\n    FMT_ASSERT(new_hi >= hi_, \"\");\n    lo_ = new_lo;\n    hi_ = new_hi;\n  }\n  FMT_CONSTEXPR void operator&=(uint128_fallback n) {\n    lo_ &= n.lo_;\n    hi_ &= n.hi_;\n  }\n\n  FMT_CONSTEXPR20 auto operator+=(uint64_t n) noexcept -> uint128_fallback& {\n    if (is_constant_evaluated()) {\n      lo_ += n;\n      hi_ += (lo_ < n ? 1 : 0);\n      return *this;\n    }\n#if FMT_HAS_BUILTIN(__builtin_addcll) && !defined(__ibmxl__)\n    unsigned long long carry;\n    lo_ = __builtin_addcll(lo_, n, 0, &carry);\n    hi_ += carry;\n#elif FMT_HAS_BUILTIN(__builtin_ia32_addcarryx_u64) && !defined(__ibmxl__)\n    unsigned long long result;\n    auto carry = __builtin_ia32_addcarryx_u64(0, lo_, n, &result);\n    lo_ = result;\n    hi_ += carry;\n#elif defined(_MSC_VER) && defined(_M_X64)\n    auto carry = _addcarry_u64(0, lo_, n, &lo_);\n    _addcarry_u64(carry, hi_, 0, &hi_);\n#else\n    lo_ += n;\n    hi_ += (lo_ < n ? 1 : 0);\n#endif\n    return *this;\n  }\n};\n\nusing uint128_t = conditional_t<FMT_USE_INT128, uint128_opt, uint128_fallback>;\n\n#ifdef UINTPTR_MAX\nusing uintptr_t = ::uintptr_t;\n#else\nusing uintptr_t = uint128_t;\n#endif\n\n// Returns the largest possible value for type T. Same as\n// std::numeric_limits<T>::max() but shorter and not affected by the max macro.\ntemplate <typename T> constexpr auto max_value() -> T {\n  return (std::numeric_limits<T>::max)();\n}\ntemplate <typename T> constexpr auto num_bits() -> int {\n  return std::numeric_limits<T>::digits;\n}\n// std::numeric_limits<T>::digits may return 0 for 128-bit ints.\ntemplate <> constexpr auto num_bits<int128_opt>() -> int { return 128; }\ntemplate <> constexpr auto num_bits<uint128_opt>() -> int { return 128; }\ntemplate <> constexpr auto num_bits<uint128_fallback>() -> int { return 128; }\n\n// A heterogeneous bit_cast used for converting 96-bit long double to uint128_t\n// and 128-bit pointers to uint128_fallback.\ntemplate <typename To, typename From, FMT_ENABLE_IF(sizeof(To) > sizeof(From))>\ninline auto bit_cast(const From& from) -> To {\n  constexpr auto size = static_cast<int>(sizeof(From) / sizeof(unsigned short));\n  struct data_t {\n    unsigned short value[static_cast<unsigned>(size)];\n  } data = bit_cast<data_t>(from);\n  auto result = To();\n  if (const_check(is_big_endian())) {\n    for (int i = 0; i < size; ++i)\n      result = (result << num_bits<unsigned short>()) | data.value[i];\n  } else {\n    for (int i = size - 1; i >= 0; --i)\n      result = (result << num_bits<unsigned short>()) | data.value[i];\n  }\n  return result;\n}\n\ntemplate <typename UInt>\nFMT_CONSTEXPR20 inline auto countl_zero_fallback(UInt n) -> int {\n  int lz = 0;\n  constexpr UInt msb_mask = static_cast<UInt>(1) << (num_bits<UInt>() - 1);\n  for (; (n & msb_mask) == 0; n <<= 1) lz++;\n  return lz;\n}\n\nFMT_CONSTEXPR20 inline auto countl_zero(uint32_t n) -> int {\n#ifdef FMT_BUILTIN_CLZ\n  if (!is_constant_evaluated()) return FMT_BUILTIN_CLZ(n);\n#endif\n  return countl_zero_fallback(n);\n}\n\nFMT_CONSTEXPR20 inline auto countl_zero(uint64_t n) -> int {\n#ifdef FMT_BUILTIN_CLZLL\n  if (!is_constant_evaluated()) return FMT_BUILTIN_CLZLL(n);\n#endif\n  return countl_zero_fallback(n);\n}\n\nFMT_INLINE void assume(bool condition) {\n  (void)condition;\n#if FMT_HAS_BUILTIN(__builtin_assume) && !FMT_ICC_VERSION\n  __builtin_assume(condition);\n#elif FMT_GCC_VERSION\n  if (!condition) __builtin_unreachable();\n#endif\n}\n\n// Attempts to reserve space for n extra characters in the output range.\n// Returns a pointer to the reserved range or a reference to it.\ntemplate <typename OutputIt,\n          FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value&&\n                            is_contiguous<typename OutputIt::container>::value)>\n#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION\n__attribute__((no_sanitize(\"undefined\")))\n#endif\nFMT_CONSTEXPR20 inline auto\nreserve(OutputIt it, size_t n) -> typename OutputIt::value_type* {\n  auto& c = get_container(it);\n  size_t size = c.size();\n  c.resize(size + n);\n  return &c[size];\n}\n\ntemplate <typename T>\nFMT_CONSTEXPR20 inline auto reserve(basic_appender<T> it, size_t n)\n    -> basic_appender<T> {\n  buffer<T>& buf = get_container(it);\n  buf.try_reserve(buf.size() + n);\n  return it;\n}\n\ntemplate <typename Iterator>\nconstexpr auto reserve(Iterator& it, size_t) -> Iterator& {\n  return it;\n}\n\ntemplate <typename OutputIt>\nusing reserve_iterator =\n    remove_reference_t<decltype(reserve(std::declval<OutputIt&>(), 0))>;\n\ntemplate <typename T, typename OutputIt>\nconstexpr auto to_pointer(OutputIt, size_t) -> T* {\n  return nullptr;\n}\ntemplate <typename T>\nFMT_CONSTEXPR20 auto to_pointer(basic_appender<T> it, size_t n) -> T* {\n  buffer<T>& buf = get_container(it);\n  buf.try_reserve(buf.size() + n);\n  auto size = buf.size();\n  if (buf.capacity() < size + n) return nullptr;\n  buf.try_resize(size + n);\n  return buf.data() + size;\n}\n\ntemplate <typename OutputIt,\n          FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value&&\n                            is_contiguous<typename OutputIt::container>::value)>\ninline auto base_iterator(OutputIt it,\n                          typename OutputIt::container_type::value_type*)\n    -> OutputIt {\n  return it;\n}\n\ntemplate <typename Iterator>\nconstexpr auto base_iterator(Iterator, Iterator it) -> Iterator {\n  return it;\n}\n\n// <algorithm> is spectacularly slow to compile in C++20 so use a simple fill_n\n// instead (#1998).\ntemplate <typename OutputIt, typename Size, typename T>\nFMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value)\n    -> OutputIt {\n  for (Size i = 0; i < count; ++i) *out++ = value;\n  return out;\n}\ntemplate <typename T, typename Size>\nFMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* {\n  if (is_constant_evaluated()) return fill_n<T*, Size, T>(out, count, value);\n  std::memset(out, value, to_unsigned(count));\n  return out + count;\n}\n\ntemplate <typename OutChar, typename InputIt, typename OutputIt>\nFMT_CONSTEXPR FMT_NOINLINE auto copy_noinline(InputIt begin, InputIt end,\n                                              OutputIt out) -> OutputIt {\n  return copy<OutChar>(begin, end, out);\n}\n\n// A public domain branchless UTF-8 decoder by Christopher Wellons:\n// https://github.com/skeeto/branchless-utf8\n/* Decode the next character, c, from s, reporting errors in e.\n *\n * Since this is a branchless decoder, four bytes will be read from the\n * buffer regardless of the actual length of the next character. This\n * means the buffer _must_ have at least three bytes of zero padding\n * following the end of the data stream.\n *\n * Errors are reported in e, which will be non-zero if the parsed\n * character was somehow invalid: invalid byte sequence, non-canonical\n * encoding, or a surrogate half.\n *\n * The function returns a pointer to the next character. When an error\n * occurs, this pointer will be a guess that depends on the particular\n * error, but it will always advance at least one byte.\n */\nFMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e)\n    -> const char* {\n  constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07};\n  constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536};\n  constexpr const int shiftc[] = {0, 18, 12, 6, 0};\n  constexpr const int shifte[] = {0, 6, 4, 2, 0};\n\n  int len = \"\\1\\1\\1\\1\\1\\1\\1\\1\\1\\1\\1\\1\\1\\1\\1\\1\\0\\0\\0\\0\\0\\0\\0\\0\\2\\2\\2\\2\\3\\3\\4\"\n      [static_cast<unsigned char>(*s) >> 3];\n  // Compute the pointer to the next character early so that the next\n  // iteration can start working on the next character. Neither Clang\n  // nor GCC figure out this reordering on their own.\n  const char* next = s + len + !len;\n\n  using uchar = unsigned char;\n\n  // Assume a four-byte character and load four bytes. Unused bits are\n  // shifted out.\n  *c = uint32_t(uchar(s[0]) & masks[len]) << 18;\n  *c |= uint32_t(uchar(s[1]) & 0x3f) << 12;\n  *c |= uint32_t(uchar(s[2]) & 0x3f) << 6;\n  *c |= uint32_t(uchar(s[3]) & 0x3f) << 0;\n  *c >>= shiftc[len];\n\n  // Accumulate the various error conditions.\n  *e = (*c < mins[len]) << 6;       // non-canonical encoding\n  *e |= ((*c >> 11) == 0x1b) << 7;  // surrogate half?\n  *e |= (*c > 0x10FFFF) << 8;       // out of range?\n  *e |= (uchar(s[1]) & 0xc0) >> 2;\n  *e |= (uchar(s[2]) & 0xc0) >> 4;\n  *e |= uchar(s[3]) >> 6;\n  *e ^= 0x2a;  // top two bits of each tail byte correct?\n  *e >>= shifte[len];\n\n  return next;\n}\n\nconstexpr FMT_INLINE_VARIABLE uint32_t invalid_code_point = ~uint32_t();\n\n// Invokes f(cp, sv) for every code point cp in s with sv being the string view\n// corresponding to the code point. cp is invalid_code_point on error.\ntemplate <typename F>\nFMT_CONSTEXPR void for_each_codepoint(string_view s, F f) {\n  auto decode = [f](const char* buf_ptr, const char* ptr) {\n    auto cp = uint32_t();\n    auto error = 0;\n    auto end = utf8_decode(buf_ptr, &cp, &error);\n    bool result = f(error ? invalid_code_point : cp,\n                    string_view(ptr, error ? 1 : to_unsigned(end - buf_ptr)));\n    return result ? (error ? buf_ptr + 1 : end) : nullptr;\n  };\n\n  auto p = s.data();\n  const size_t block_size = 4;  // utf8_decode always reads blocks of 4 chars.\n  if (s.size() >= block_size) {\n    for (auto end = p + s.size() - block_size + 1; p < end;) {\n      p = decode(p, p);\n      if (!p) return;\n    }\n  }\n  auto num_chars_left = to_unsigned(s.data() + s.size() - p);\n  if (num_chars_left == 0) return;\n\n  // Suppress bogus -Wstringop-overflow.\n  if (FMT_GCC_VERSION) num_chars_left &= 3;\n  char buf[2 * block_size - 1] = {};\n  copy<char>(p, p + num_chars_left, buf);\n  const char* buf_ptr = buf;\n  do {\n    auto end = decode(buf_ptr, p);\n    if (!end) return;\n    p += end - buf_ptr;\n    buf_ptr = end;\n  } while (buf_ptr < buf + num_chars_left);\n}\n\ntemplate <typename Char>\ninline auto compute_width(basic_string_view<Char> s) -> size_t {\n  return s.size();\n}\n\n// Computes approximate display width of a UTF-8 string.\nFMT_CONSTEXPR inline auto compute_width(string_view s) -> size_t {\n  size_t num_code_points = 0;\n  // It is not a lambda for compatibility with C++14.\n  struct count_code_points {\n    size_t* count;\n    FMT_CONSTEXPR auto operator()(uint32_t cp, string_view) const -> bool {\n      *count += to_unsigned(\n          1 +\n          (cp >= 0x1100 &&\n           (cp <= 0x115f ||  // Hangul Jamo init. consonants\n            cp == 0x2329 ||  // LEFT-POINTING ANGLE BRACKET\n            cp == 0x232a ||  // RIGHT-POINTING ANGLE BRACKET\n            // CJK ... Yi except IDEOGRAPHIC HALF FILL SPACE:\n            (cp >= 0x2e80 && cp <= 0xa4cf && cp != 0x303f) ||\n            (cp >= 0xac00 && cp <= 0xd7a3) ||    // Hangul Syllables\n            (cp >= 0xf900 && cp <= 0xfaff) ||    // CJK Compatibility Ideographs\n            (cp >= 0xfe10 && cp <= 0xfe19) ||    // Vertical Forms\n            (cp >= 0xfe30 && cp <= 0xfe6f) ||    // CJK Compatibility Forms\n            (cp >= 0xff00 && cp <= 0xff60) ||    // Fullwidth Forms\n            (cp >= 0xffe0 && cp <= 0xffe6) ||    // Fullwidth Forms\n            (cp >= 0x20000 && cp <= 0x2fffd) ||  // CJK\n            (cp >= 0x30000 && cp <= 0x3fffd) ||\n            // Miscellaneous Symbols and Pictographs + Emoticons:\n            (cp >= 0x1f300 && cp <= 0x1f64f) ||\n            // Supplemental Symbols and Pictographs:\n            (cp >= 0x1f900 && cp <= 0x1f9ff))));\n      return true;\n    }\n  };\n  // We could avoid branches by using utf8_decode directly.\n  for_each_codepoint(s, count_code_points{&num_code_points});\n  return num_code_points;\n}\n\ntemplate <typename Char>\ninline auto code_point_index(basic_string_view<Char> s, size_t n) -> size_t {\n  return min_of(n, s.size());\n}\n\n// Calculates the index of the nth code point in a UTF-8 string.\ninline auto code_point_index(string_view s, size_t n) -> size_t {\n  size_t result = s.size();\n  const char* begin = s.begin();\n  for_each_codepoint(s, [begin, &n, &result](uint32_t, string_view sv) {\n    if (n != 0) {\n      --n;\n      return true;\n    }\n    result = to_unsigned(sv.begin() - begin);\n    return false;\n  });\n  return result;\n}\n\ntemplate <typename T> struct is_integral : std::is_integral<T> {};\ntemplate <> struct is_integral<int128_opt> : std::true_type {};\ntemplate <> struct is_integral<uint128_t> : std::true_type {};\n\ntemplate <typename T>\nusing is_signed =\n    std::integral_constant<bool, std::numeric_limits<T>::is_signed ||\n                                     std::is_same<T, int128_opt>::value>;\n\ntemplate <typename T>\nusing is_integer =\n    bool_constant<is_integral<T>::value && !std::is_same<T, bool>::value &&\n                  !std::is_same<T, char>::value &&\n                  !std::is_same<T, wchar_t>::value>;\n\n#if defined(FMT_USE_FLOAT128)\n// Use the provided definition.\n#elif FMT_CLANG_VERSION && FMT_HAS_INCLUDE(<quadmath.h>)\n#  define FMT_USE_FLOAT128 1\n#elif FMT_GCC_VERSION && defined(_GLIBCXX_USE_FLOAT128) && \\\n    !defined(__STRICT_ANSI__)\n#  define FMT_USE_FLOAT128 1\n#else\n#  define FMT_USE_FLOAT128 0\n#endif\n#if FMT_USE_FLOAT128\nusing float128 = __float128;\n#else\nstruct float128 {};\n#endif\n\ntemplate <typename T> using is_float128 = std::is_same<T, float128>;\n\ntemplate <typename T>\nusing is_floating_point =\n    bool_constant<std::is_floating_point<T>::value || is_float128<T>::value>;\n\ntemplate <typename T, bool = std::is_floating_point<T>::value>\nstruct is_fast_float : bool_constant<std::numeric_limits<T>::is_iec559 &&\n                                     sizeof(T) <= sizeof(double)> {};\ntemplate <typename T> struct is_fast_float<T, false> : std::false_type {};\n\ntemplate <typename T>\nusing is_double_double = bool_constant<std::numeric_limits<T>::digits == 106>;\n\n#ifndef FMT_USE_FULL_CACHE_DRAGONBOX\n#  define FMT_USE_FULL_CACHE_DRAGONBOX 0\n#endif\n\n// An allocator that uses malloc/free to allow removing dependency on the C++\n// standard libary runtime.\ntemplate <typename T> struct allocator {\n  using value_type = T;\n\n  T* allocate(size_t n) {\n    FMT_ASSERT(n <= max_value<size_t>() / sizeof(T), \"\");\n    T* p = static_cast<T*>(malloc(n * sizeof(T)));\n    if (!p) FMT_THROW(std::bad_alloc());\n    return p;\n  }\n\n  void deallocate(T* p, size_t) { free(p); }\n};\n\n}  // namespace detail\n\nFMT_BEGIN_EXPORT\n\n// The number of characters to store in the basic_memory_buffer object itself\n// to avoid dynamic memory allocation.\nenum { inline_buffer_size = 500 };\n\n/**\n * A dynamically growing memory buffer for trivially copyable/constructible\n * types with the first `SIZE` elements stored in the object itself. Most\n * commonly used via the `memory_buffer` alias for `char`.\n *\n * **Example**:\n *\n *     auto out = fmt::memory_buffer();\n *     fmt::format_to(std::back_inserter(out), \"The answer is {}.\", 42);\n *\n * This will append \"The answer is 42.\" to `out`. The buffer content can be\n * converted to `std::string` with `to_string(out)`.\n */\ntemplate <typename T, size_t SIZE = inline_buffer_size,\n          typename Allocator = detail::allocator<T>>\nclass basic_memory_buffer : public detail::buffer<T> {\n private:\n  T store_[SIZE];\n\n  // Don't inherit from Allocator to avoid generating type_info for it.\n  FMT_NO_UNIQUE_ADDRESS Allocator alloc_;\n\n  // Deallocate memory allocated by the buffer.\n  FMT_CONSTEXPR20 void deallocate() {\n    T* data = this->data();\n    if (data != store_) alloc_.deallocate(data, this->capacity());\n  }\n\n  static FMT_CONSTEXPR20 void grow(detail::buffer<T>& buf, size_t size) {\n    detail::abort_fuzzing_if(size > 5000);\n    auto& self = static_cast<basic_memory_buffer&>(buf);\n    const size_t max_size =\n        std::allocator_traits<Allocator>::max_size(self.alloc_);\n    size_t old_capacity = buf.capacity();\n    size_t new_capacity = old_capacity + old_capacity / 2;\n    if (size > new_capacity)\n      new_capacity = size;\n    else if (new_capacity > max_size)\n      new_capacity = max_of(size, max_size);\n    T* old_data = buf.data();\n    T* new_data = self.alloc_.allocate(new_capacity);\n    // Suppress a bogus -Wstringop-overflow in gcc 13.1 (#3481).\n    detail::assume(buf.size() <= new_capacity);\n    // The following code doesn't throw, so the raw pointer above doesn't leak.\n    memcpy(new_data, old_data, buf.size() * sizeof(T));\n    self.set(new_data, new_capacity);\n    // deallocate must not throw according to the standard, but even if it does,\n    // the buffer already uses the new storage and will deallocate it in\n    // destructor.\n    if (old_data != self.store_) self.alloc_.deallocate(old_data, old_capacity);\n  }\n\n public:\n  using value_type = T;\n  using const_reference = const T&;\n\n  FMT_CONSTEXPR explicit basic_memory_buffer(\n      const Allocator& alloc = Allocator())\n      : detail::buffer<T>(grow), alloc_(alloc) {\n    this->set(store_, SIZE);\n    if (detail::is_constant_evaluated()) detail::fill_n(store_, SIZE, T());\n  }\n  FMT_CONSTEXPR20 ~basic_memory_buffer() { deallocate(); }\n\n private:\n  // Move data from other to this buffer.\n  FMT_CONSTEXPR20 void move(basic_memory_buffer& other) {\n    alloc_ = std::move(other.alloc_);\n    T* data = other.data();\n    size_t size = other.size(), capacity = other.capacity();\n    if (data == other.store_) {\n      this->set(store_, capacity);\n      detail::copy<T>(other.store_, other.store_ + size, store_);\n    } else {\n      this->set(data, capacity);\n      // Set pointer to the inline array so that delete is not called\n      // when deallocating.\n      other.set(other.store_, 0);\n      other.clear();\n    }\n    this->resize(size);\n  }\n\n public:\n  /// Constructs a `basic_memory_buffer` object moving the content of the other\n  /// object to it.\n  FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other) noexcept\n      : detail::buffer<T>(grow) {\n    move(other);\n  }\n\n  /// Moves the content of the other `basic_memory_buffer` object to this one.\n  auto operator=(basic_memory_buffer&& other) noexcept -> basic_memory_buffer& {\n    FMT_ASSERT(this != &other, \"\");\n    deallocate();\n    move(other);\n    return *this;\n  }\n\n  // Returns a copy of the allocator associated with this buffer.\n  auto get_allocator() const -> Allocator { return alloc_; }\n\n  /// Resizes the buffer to contain `count` elements. If T is a POD type new\n  /// elements may not be initialized.\n  FMT_CONSTEXPR void resize(size_t count) { this->try_resize(count); }\n\n  /// Increases the buffer capacity to `new_capacity`.\n  void reserve(size_t new_capacity) { this->try_reserve(new_capacity); }\n\n  using detail::buffer<T>::append;\n  template <typename ContiguousRange>\n  FMT_CONSTEXPR20 void append(const ContiguousRange& range) {\n    append(range.data(), range.data() + range.size());\n  }\n};\n\nusing memory_buffer = basic_memory_buffer<char>;\n\ntemplate <size_t SIZE>\nFMT_NODISCARD auto to_string(const basic_memory_buffer<char, SIZE>& buf)\n    -> std::string {\n  auto size = buf.size();\n  detail::assume(size < std::string().max_size());\n  return {buf.data(), size};\n}\n\n// A writer to a buffered stream. It doesn't own the underlying stream.\nclass writer {\n private:\n  detail::buffer<char>* buf_;\n\n  // We cannot create a file buffer in advance because any write to a FILE may\n  // invalidate it.\n  FILE* file_;\n\n public:\n  inline writer(FILE* f) : buf_(nullptr), file_(f) {}\n  inline writer(detail::buffer<char>& buf) : buf_(&buf) {}\n\n  /// Formats `args` according to specifications in `fmt` and writes the\n  /// output to the file.\n  template <typename... T> void print(format_string<T...> fmt, T&&... args) {\n    if (buf_)\n      fmt::format_to(appender(*buf_), fmt, std::forward<T>(args)...);\n    else\n      fmt::print(file_, fmt, std::forward<T>(args)...);\n  }\n};\n\nclass string_buffer {\n private:\n  std::string str_;\n  detail::container_buffer<std::string> buf_;\n\n public:\n  inline string_buffer() : buf_(str_) {}\n\n  inline operator writer() { return buf_; }\n  inline std::string& str() { return str_; }\n};\n\ntemplate <typename T, size_t SIZE, typename Allocator>\nstruct is_contiguous<basic_memory_buffer<T, SIZE, Allocator>> : std::true_type {\n};\n\n// Suppress a misleading warning in older versions of clang.\nFMT_PRAGMA_CLANG(diagnostic ignored \"-Wweak-vtables\")\n\n/// An error reported from a formatting function.\nclass FMT_SO_VISIBILITY(\"default\") format_error : public std::runtime_error {\n public:\n  using std::runtime_error::runtime_error;\n};\n\nclass loc_value;\n\nFMT_END_EXPORT\nnamespace detail {\nFMT_API auto write_console(int fd, string_view text) -> bool;\nFMT_API void print(FILE*, string_view);\n}  // namespace detail\n\nnamespace detail {\ntemplate <typename Char, size_t N> struct fixed_string {\n  FMT_CONSTEXPR20 fixed_string(const Char (&s)[N]) {\n    detail::copy<Char, const Char*, Char*>(static_cast<const Char*>(s), s + N,\n                                           data);\n  }\n  Char data[N] = {};\n};\n\n// Converts a compile-time string to basic_string_view.\nFMT_EXPORT template <typename Char, size_t N>\nconstexpr auto compile_string_to_view(const Char (&s)[N])\n    -> basic_string_view<Char> {\n  // Remove trailing NUL character if needed. Won't be present if this is used\n  // with a raw character array (i.e. not defined as a string).\n  return {s, N - (std::char_traits<Char>::to_int_type(s[N - 1]) == 0 ? 1 : 0)};\n}\nFMT_EXPORT template <typename Char>\nconstexpr auto compile_string_to_view(basic_string_view<Char> s)\n    -> basic_string_view<Char> {\n  return s;\n}\n\n// Returns true if value is negative, false otherwise.\n// Same as `value < 0` but doesn't produce warnings if T is an unsigned type.\ntemplate <typename T, FMT_ENABLE_IF(is_signed<T>::value)>\nconstexpr auto is_negative(T value) -> bool {\n  return value < 0;\n}\ntemplate <typename T, FMT_ENABLE_IF(!is_signed<T>::value)>\nconstexpr auto is_negative(T) -> bool {\n  return false;\n}\n\n// Smallest of uint32_t, uint64_t, uint128_t that is large enough to\n// represent all values of an integral type T.\ntemplate <typename T>\nusing uint32_or_64_or_128_t =\n    conditional_t<num_bits<T>() <= 32 && !FMT_REDUCE_INT_INSTANTIATIONS,\n                  uint32_t,\n                  conditional_t<num_bits<T>() <= 64, uint64_t, uint128_t>>;\ntemplate <typename T>\nusing uint64_or_128_t = conditional_t<num_bits<T>() <= 64, uint64_t, uint128_t>;\n\n#define FMT_POWERS_OF_10(factor)                                  \\\n  factor * 10, (factor) * 100, (factor) * 1000, (factor) * 10000, \\\n      (factor) * 100000, (factor) * 1000000, (factor) * 10000000, \\\n      (factor) * 100000000, (factor) * 1000000000\n\n// Converts value in the range [0, 100) to a string.\n// GCC generates slightly better code when value is pointer-size.\ninline auto digits2(size_t value) -> const char* {\n  // Align data since unaligned access may be slower when crossing a\n  // hardware-specific boundary.\n  alignas(2) static const char data[] =\n      \"0001020304050607080910111213141516171819\"\n      \"2021222324252627282930313233343536373839\"\n      \"4041424344454647484950515253545556575859\"\n      \"6061626364656667686970717273747576777879\"\n      \"8081828384858687888990919293949596979899\";\n  return &data[value * 2];\n}\n\ntemplate <typename Char> constexpr auto getsign(sign s) -> Char {\n  return static_cast<char>(((' ' << 24) | ('+' << 16) | ('-' << 8)) >>\n                           (static_cast<int>(s) * 8));\n}\n\ntemplate <typename T> FMT_CONSTEXPR auto count_digits_fallback(T n) -> int {\n  int count = 1;\n  for (;;) {\n    // Integer division is slow so do it for a group of four digits instead\n    // of for every digit. The idea comes from the talk by Alexandrescu\n    // \"Three Optimization Tips for C++\". See speed-test for a comparison.\n    if (n < 10) return count;\n    if (n < 100) return count + 1;\n    if (n < 1000) return count + 2;\n    if (n < 10000) return count + 3;\n    n /= 10000u;\n    count += 4;\n  }\n}\n#if FMT_USE_INT128\nFMT_CONSTEXPR inline auto count_digits(uint128_opt n) -> int {\n  return count_digits_fallback(n);\n}\n#endif\n\n#ifdef FMT_BUILTIN_CLZLL\n// It is a separate function rather than a part of count_digits to workaround\n// the lack of static constexpr in constexpr functions.\ninline auto do_count_digits(uint64_t n) -> int {\n  // This has comparable performance to the version by Kendall Willets\n  // (https://github.com/fmtlib/format-benchmark/blob/master/digits10)\n  // but uses smaller tables.\n  // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)).\n  static constexpr uint8_t bsr2log10[] = {\n      1,  1,  1,  2,  2,  2,  3,  3,  3,  4,  4,  4,  4,  5,  5,  5,\n      6,  6,  6,  7,  7,  7,  7,  8,  8,  8,  9,  9,  9,  10, 10, 10,\n      10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15,\n      15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20};\n  auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63];\n  static constexpr const uint64_t zero_or_powers_of_10[] = {\n      0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL),\n      10000000000000000000ULL};\n  return t - (n < zero_or_powers_of_10[t]);\n}\n#endif\n\n// Returns the number of decimal digits in n. Leading zeros are not counted\n// except for n == 0 in which case count_digits returns 1.\nFMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int {\n#ifdef FMT_BUILTIN_CLZLL\n  if (!is_constant_evaluated() && !FMT_OPTIMIZE_SIZE) return do_count_digits(n);\n#endif\n  return count_digits_fallback(n);\n}\n\n// Counts the number of digits in n. BITS = log2(radix).\ntemplate <int BITS, typename UInt>\nFMT_CONSTEXPR auto count_digits(UInt n) -> int {\n#ifdef FMT_BUILTIN_CLZ\n  if (!is_constant_evaluated() && num_bits<UInt>() == 32)\n    return (FMT_BUILTIN_CLZ(static_cast<uint32_t>(n) | 1) ^ 31) / BITS + 1;\n#endif\n  // Lambda avoids unreachable code warnings from NVHPC.\n  return [](UInt m) {\n    int num_digits = 0;\n    do {\n      ++num_digits;\n    } while ((m >>= BITS) != 0);\n    return num_digits;\n  }(n);\n}\n\n#ifdef FMT_BUILTIN_CLZ\n// It is a separate function rather than a part of count_digits to workaround\n// the lack of static constexpr in constexpr functions.\nFMT_INLINE auto do_count_digits(uint32_t n) -> int {\n// An optimization by Kendall Willets from https://bit.ly/3uOIQrB.\n// This increments the upper 32 bits (log10(T) - 1) when >= T is added.\n#  define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T)\n  static constexpr uint64_t table[] = {\n      FMT_INC(0),          FMT_INC(0),          FMT_INC(0),           // 8\n      FMT_INC(10),         FMT_INC(10),         FMT_INC(10),          // 64\n      FMT_INC(100),        FMT_INC(100),        FMT_INC(100),         // 512\n      FMT_INC(1000),       FMT_INC(1000),       FMT_INC(1000),        // 4096\n      FMT_INC(10000),      FMT_INC(10000),      FMT_INC(10000),       // 32k\n      FMT_INC(100000),     FMT_INC(100000),     FMT_INC(100000),      // 256k\n      FMT_INC(1000000),    FMT_INC(1000000),    FMT_INC(1000000),     // 2048k\n      FMT_INC(10000000),   FMT_INC(10000000),   FMT_INC(10000000),    // 16M\n      FMT_INC(100000000),  FMT_INC(100000000),  FMT_INC(100000000),   // 128M\n      FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000),  // 1024M\n      FMT_INC(1000000000), FMT_INC(1000000000)                        // 4B\n  };\n  auto inc = table[FMT_BUILTIN_CLZ(n | 1) ^ 31];\n  return static_cast<int>((n + inc) >> 32);\n}\n#endif\n\n// Optional version of count_digits for better performance on 32-bit platforms.\nFMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int {\n#ifdef FMT_BUILTIN_CLZ\n  if (!is_constant_evaluated() && !FMT_OPTIMIZE_SIZE) return do_count_digits(n);\n#endif\n  return count_digits_fallback(n);\n}\n\ntemplate <typename Int> constexpr auto digits10() noexcept -> int {\n  return std::numeric_limits<Int>::digits10;\n}\ntemplate <> constexpr auto digits10<int128_opt>() noexcept -> int { return 38; }\ntemplate <> constexpr auto digits10<uint128_t>() noexcept -> int { return 38; }\n\ntemplate <typename Char> struct thousands_sep_result {\n  std::string grouping;\n  Char thousands_sep;\n};\n\ntemplate <typename Char>\nFMT_API auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char>;\ntemplate <typename Char>\ninline auto thousands_sep(locale_ref loc) -> thousands_sep_result<Char> {\n  auto result = thousands_sep_impl<char>(loc);\n  return {result.grouping, Char(result.thousands_sep)};\n}\ntemplate <>\ninline auto thousands_sep(locale_ref loc) -> thousands_sep_result<wchar_t> {\n  return thousands_sep_impl<wchar_t>(loc);\n}\n\ntemplate <typename Char>\nFMT_API auto decimal_point_impl(locale_ref loc) -> Char;\ntemplate <typename Char> inline auto decimal_point(locale_ref loc) -> Char {\n  return Char(decimal_point_impl<char>(loc));\n}\ntemplate <> inline auto decimal_point(locale_ref loc) -> wchar_t {\n  return decimal_point_impl<wchar_t>(loc);\n}\n\n#ifndef FMT_HEADER_ONLY\nFMT_BEGIN_EXPORT\nextern template FMT_API auto thousands_sep_impl<char>(locale_ref)\n    -> thousands_sep_result<char>;\nextern template FMT_API auto thousands_sep_impl<wchar_t>(locale_ref)\n    -> thousands_sep_result<wchar_t>;\nextern template FMT_API auto decimal_point_impl(locale_ref) -> char;\nextern template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;\nFMT_END_EXPORT\n#endif  // FMT_HEADER_ONLY\n\n// Compares two characters for equality.\ntemplate <typename Char> auto equal2(const Char* lhs, const char* rhs) -> bool {\n  return lhs[0] == Char(rhs[0]) && lhs[1] == Char(rhs[1]);\n}\ninline auto equal2(const char* lhs, const char* rhs) -> bool {\n  return memcmp(lhs, rhs, 2) == 0;\n}\n\n// Writes a two-digit value to out.\ntemplate <typename Char>\nFMT_CONSTEXPR20 FMT_INLINE void write2digits(Char* out, size_t value) {\n  if (!is_constant_evaluated() && std::is_same<Char, char>::value &&\n      !FMT_OPTIMIZE_SIZE) {\n    memcpy(out, digits2(value), 2);\n    return;\n  }\n  *out++ = static_cast<Char>('0' + value / 10);\n  *out = static_cast<Char>('0' + value % 10);\n}\n\n// Formats a decimal unsigned integer value writing to out pointing to a buffer\n// of specified size. The caller must ensure that the buffer is large enough.\ntemplate <typename Char, typename UInt>\nFMT_CONSTEXPR20 auto do_format_decimal(Char* out, UInt value, int size)\n    -> Char* {\n  FMT_ASSERT(size >= count_digits(value), \"invalid digit count\");\n  unsigned n = to_unsigned(size);\n  while (value >= 100) {\n    // Integer division is slow so do it for a group of two digits instead\n    // of for every digit. The idea comes from the talk by Alexandrescu\n    // \"Three Optimization Tips for C++\". See speed-test for a comparison.\n    n -= 2;\n    write2digits(out + n, static_cast<unsigned>(value % 100));\n    value /= 100;\n  }\n  if (value >= 10) {\n    n -= 2;\n    write2digits(out + n, static_cast<unsigned>(value));\n  } else {\n    out[--n] = static_cast<Char>('0' + value);\n  }\n  return out + n;\n}\n\ntemplate <typename Char, typename UInt>\nFMT_CONSTEXPR FMT_INLINE auto format_decimal(Char* out, UInt value,\n                                             int num_digits) -> Char* {\n  do_format_decimal(out, value, num_digits);\n  return out + num_digits;\n}\n\ntemplate <typename Char, typename UInt, typename OutputIt,\n          FMT_ENABLE_IF(!std::is_pointer<remove_cvref_t<OutputIt>>::value)>\nFMT_CONSTEXPR auto format_decimal(OutputIt out, UInt value, int num_digits)\n    -> OutputIt {\n  if (auto ptr = to_pointer<Char>(out, to_unsigned(num_digits))) {\n    do_format_decimal(ptr, value, num_digits);\n    return out;\n  }\n  // Buffer is large enough to hold all digits (digits10 + 1).\n  char buffer[digits10<UInt>() + 1];\n  if (is_constant_evaluated()) fill_n(buffer, sizeof(buffer), '\\0');\n  do_format_decimal(buffer, value, num_digits);\n  return copy_noinline<Char>(buffer, buffer + num_digits, out);\n}\n\ntemplate <typename Char, typename UInt>\nFMT_CONSTEXPR auto do_format_base2e(int base_bits, Char* out, UInt value,\n                                    int size, bool upper = false) -> Char* {\n  out += size;\n  do {\n    const char* digits = upper ? \"0123456789ABCDEF\" : \"0123456789abcdef\";\n    unsigned digit = static_cast<unsigned>(value & ((1 << base_bits) - 1));\n    *--out = static_cast<Char>(base_bits < 4 ? static_cast<char>('0' + digit)\n                                             : digits[digit]);\n  } while ((value >>= base_bits) != 0);\n  return out;\n}\n\n// Formats an unsigned integer in the power of two base (binary, octal, hex).\ntemplate <typename Char, typename UInt>\nFMT_CONSTEXPR auto format_base2e(int base_bits, Char* out, UInt value,\n                                 int num_digits, bool upper = false) -> Char* {\n  do_format_base2e(base_bits, out, value, num_digits, upper);\n  return out + num_digits;\n}\n\ntemplate <typename Char, typename OutputIt, typename UInt,\n          FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value)>\nFMT_CONSTEXPR inline auto format_base2e(int base_bits, OutputIt out, UInt value,\n                                        int num_digits, bool upper = false)\n    -> OutputIt {\n  if (auto ptr = to_pointer<Char>(out, to_unsigned(num_digits))) {\n    format_base2e(base_bits, ptr, value, num_digits, upper);\n    return out;\n  }\n  // Make buffer large enough for any base.\n  char buffer[num_bits<UInt>()];\n  if (is_constant_evaluated()) fill_n(buffer, sizeof(buffer), '\\0');\n  format_base2e(base_bits, buffer, value, num_digits, upper);\n  return detail::copy_noinline<Char>(buffer, buffer + num_digits, out);\n}\n\n// A converter from UTF-8 to UTF-16.\nclass utf8_to_utf16 {\n private:\n  basic_memory_buffer<wchar_t> buffer_;\n\n public:\n  FMT_API explicit utf8_to_utf16(string_view s);\n  inline operator basic_string_view<wchar_t>() const {\n    return {&buffer_[0], size()};\n  }\n  inline auto size() const -> size_t { return buffer_.size() - 1; }\n  inline auto c_str() const -> const wchar_t* { return &buffer_[0]; }\n  inline auto str() const -> std::wstring { return {&buffer_[0], size()}; }\n};\n\nenum class to_utf8_error_policy { abort, replace };\n\n// A converter from UTF-16/UTF-32 (host endian) to UTF-8.\ntemplate <typename WChar, typename Buffer = memory_buffer> class to_utf8 {\n private:\n  Buffer buffer_;\n\n public:\n  to_utf8() {}\n  explicit to_utf8(basic_string_view<WChar> s,\n                   to_utf8_error_policy policy = to_utf8_error_policy::abort) {\n    static_assert(sizeof(WChar) == 2 || sizeof(WChar) == 4,\n                  \"Expect utf16 or utf32\");\n    if (!convert(s, policy))\n      FMT_THROW(std::runtime_error(sizeof(WChar) == 2 ? \"invalid utf16\"\n                                                      : \"invalid utf32\"));\n  }\n  operator string_view() const { return string_view(&buffer_[0], size()); }\n  auto size() const -> size_t { return buffer_.size() - 1; }\n  auto c_str() const -> const char* { return &buffer_[0]; }\n  auto str() const -> std::string { return std::string(&buffer_[0], size()); }\n\n  // Performs conversion returning a bool instead of throwing exception on\n  // conversion error. This method may still throw in case of memory allocation\n  // error.\n  auto convert(basic_string_view<WChar> s,\n               to_utf8_error_policy policy = to_utf8_error_policy::abort)\n      -> bool {\n    if (!convert(buffer_, s, policy)) return false;\n    buffer_.push_back(0);\n    return true;\n  }\n  static auto convert(Buffer& buf, basic_string_view<WChar> s,\n                      to_utf8_error_policy policy = to_utf8_error_policy::abort)\n      -> bool {\n    for (auto p = s.begin(); p != s.end(); ++p) {\n      uint32_t c = static_cast<uint32_t>(*p);\n      if (sizeof(WChar) == 2 && c >= 0xd800 && c <= 0xdfff) {\n        // Handle a surrogate pair.\n        ++p;\n        if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) {\n          if (policy == to_utf8_error_policy::abort) return false;\n          buf.append(string_view(\"\\xEF\\xBF\\xBD\"));\n          --p;\n          continue;\n        } else {\n          c = (c << 10) + static_cast<uint32_t>(*p) - 0x35fdc00;\n        }\n      }\n      if (c < 0x80) {\n        buf.push_back(static_cast<char>(c));\n      } else if (c < 0x800) {\n        buf.push_back(static_cast<char>(0xc0 | (c >> 6)));\n        buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));\n      } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {\n        buf.push_back(static_cast<char>(0xe0 | (c >> 12)));\n        buf.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));\n        buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));\n      } else if (c >= 0x10000 && c <= 0x10ffff) {\n        buf.push_back(static_cast<char>(0xf0 | (c >> 18)));\n        buf.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12)));\n        buf.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));\n        buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));\n      } else {\n        return false;\n      }\n    }\n    return true;\n  }\n};\n\n// Computes 128-bit result of multiplication of two 64-bit unsigned integers.\ninline auto umul128(uint64_t x, uint64_t y) noexcept -> uint128_fallback {\n#if FMT_USE_INT128\n  auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y);\n  return {static_cast<uint64_t>(p >> 64), static_cast<uint64_t>(p)};\n#elif defined(_MSC_VER) && defined(_M_X64)\n  auto hi = uint64_t();\n  auto lo = _umul128(x, y, &hi);\n  return {hi, lo};\n#else\n  const uint64_t mask = static_cast<uint64_t>(max_value<uint32_t>());\n\n  uint64_t a = x >> 32;\n  uint64_t b = x & mask;\n  uint64_t c = y >> 32;\n  uint64_t d = y & mask;\n\n  uint64_t ac = a * c;\n  uint64_t bc = b * c;\n  uint64_t ad = a * d;\n  uint64_t bd = b * d;\n\n  uint64_t intermediate = (bd >> 32) + (ad & mask) + (bc & mask);\n\n  return {ac + (intermediate >> 32) + (ad >> 32) + (bc >> 32),\n          (intermediate << 32) + (bd & mask)};\n#endif\n}\n\nnamespace dragonbox {\n// Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from\n// https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1.\ninline auto floor_log10_pow2(int e) noexcept -> int {\n  FMT_ASSERT(e <= 2620 && e >= -2620, \"too large exponent\");\n  static_assert((-1 >> 1) == -1, \"right shift is not arithmetic\");\n  return (e * 315653) >> 20;\n}\n\ninline auto floor_log2_pow10(int e) noexcept -> int {\n  FMT_ASSERT(e <= 1233 && e >= -1233, \"too large exponent\");\n  return (e * 1741647) >> 19;\n}\n\n// Computes upper 64 bits of multiplication of two 64-bit unsigned integers.\ninline auto umul128_upper64(uint64_t x, uint64_t y) noexcept -> uint64_t {\n#if FMT_USE_INT128\n  auto p = static_cast<uint128_opt>(x) * static_cast<uint128_opt>(y);\n  return static_cast<uint64_t>(p >> 64);\n#elif defined(_MSC_VER) && defined(_M_X64)\n  return __umulh(x, y);\n#else\n  return umul128(x, y).high();\n#endif\n}\n\n// Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a\n// 128-bit unsigned integer.\ninline auto umul192_upper128(uint64_t x, uint128_fallback y) noexcept\n    -> uint128_fallback {\n  uint128_fallback r = umul128(x, y.high());\n  r += umul128_upper64(x, y.low());\n  return r;\n}\n\nFMT_API auto get_cached_power(int k) noexcept -> uint128_fallback;\n\n// Type-specific information that Dragonbox uses.\ntemplate <typename T, typename Enable = void> struct float_info;\n\ntemplate <> struct float_info<float> {\n  using carrier_uint = uint32_t;\n  static const int exponent_bits = 8;\n  static const int kappa = 1;\n  static const int big_divisor = 100;\n  static const int small_divisor = 10;\n  static const int min_k = -31;\n  static const int max_k = 46;\n  static const int shorter_interval_tie_lower_threshold = -35;\n  static const int shorter_interval_tie_upper_threshold = -35;\n};\n\ntemplate <> struct float_info<double> {\n  using carrier_uint = uint64_t;\n  static const int exponent_bits = 11;\n  static const int kappa = 2;\n  static const int big_divisor = 1000;\n  static const int small_divisor = 100;\n  static const int min_k = -292;\n  static const int max_k = 341;\n  static const int shorter_interval_tie_lower_threshold = -77;\n  static const int shorter_interval_tie_upper_threshold = -77;\n};\n\n// An 80- or 128-bit floating point number.\ntemplate <typename T>\nstruct float_info<T, enable_if_t<std::numeric_limits<T>::digits == 64 ||\n                                 std::numeric_limits<T>::digits == 113 ||\n                                 is_float128<T>::value>> {\n  using carrier_uint = detail::uint128_t;\n  static const int exponent_bits = 15;\n};\n\n// A double-double floating point number.\ntemplate <typename T>\nstruct float_info<T, enable_if_t<is_double_double<T>::value>> {\n  using carrier_uint = detail::uint128_t;\n};\n\ntemplate <typename T> struct decimal_fp {\n  using significand_type = typename float_info<T>::carrier_uint;\n  significand_type significand;\n  int exponent;\n};\n\ntemplate <typename T> FMT_API auto to_decimal(T x) noexcept -> decimal_fp<T>;\n}  // namespace dragonbox\n\n// Returns true iff Float has the implicit bit which is not stored.\ntemplate <typename Float> constexpr auto has_implicit_bit() -> bool {\n  // An 80-bit FP number has a 64-bit significand an no implicit bit.\n  return std::numeric_limits<Float>::digits != 64;\n}\n\n// Returns the number of significand bits stored in Float. The implicit bit is\n// not counted since it is not stored.\ntemplate <typename Float> constexpr auto num_significand_bits() -> int {\n  // std::numeric_limits may not support __float128.\n  return is_float128<Float>() ? 112\n                              : (std::numeric_limits<Float>::digits -\n                                 (has_implicit_bit<Float>() ? 1 : 0));\n}\n\ntemplate <typename Float>\nconstexpr auto exponent_mask() ->\n    typename dragonbox::float_info<Float>::carrier_uint {\n  using float_uint = typename dragonbox::float_info<Float>::carrier_uint;\n  return ((float_uint(1) << dragonbox::float_info<Float>::exponent_bits) - 1)\n         << num_significand_bits<Float>();\n}\ntemplate <typename Float> constexpr auto exponent_bias() -> int {\n  // std::numeric_limits may not support __float128.\n  return is_float128<Float>() ? 16383\n                              : std::numeric_limits<Float>::max_exponent - 1;\n}\n\n// Writes the exponent exp in the form \"[+-]d{2,3}\" to buffer.\ntemplate <typename Char, typename OutputIt>\nFMT_CONSTEXPR auto write_exponent(int exp, OutputIt out) -> OutputIt {\n  FMT_ASSERT(-10000 < exp && exp < 10000, \"exponent out of range\");\n  if (exp < 0) {\n    *out++ = static_cast<Char>('-');\n    exp = -exp;\n  } else {\n    *out++ = static_cast<Char>('+');\n  }\n  auto uexp = static_cast<uint32_t>(exp);\n  if (is_constant_evaluated()) {\n    if (uexp < 10) *out++ = '0';\n    return format_decimal<Char>(out, uexp, count_digits(uexp));\n  }\n  if (uexp >= 100u) {\n    const char* top = digits2(uexp / 100);\n    if (uexp >= 1000u) *out++ = static_cast<Char>(top[0]);\n    *out++ = static_cast<Char>(top[1]);\n    uexp %= 100;\n  }\n  const char* d = digits2(uexp);\n  *out++ = static_cast<Char>(d[0]);\n  *out++ = static_cast<Char>(d[1]);\n  return out;\n}\n\n// A floating-point number f * pow(2, e) where F is an unsigned type.\ntemplate <typename F> struct basic_fp {\n  F f;\n  int e;\n\n  static constexpr const int num_significand_bits =\n      static_cast<int>(sizeof(F) * num_bits<unsigned char>());\n\n  constexpr basic_fp() : f(0), e(0) {}\n  constexpr basic_fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {}\n\n  // Constructs fp from an IEEE754 floating-point number.\n  template <typename Float> FMT_CONSTEXPR basic_fp(Float n) { assign(n); }\n\n  // Assigns n to this and return true iff predecessor is closer than successor.\n  template <typename Float, FMT_ENABLE_IF(!is_double_double<Float>::value)>\n  FMT_CONSTEXPR auto assign(Float n) -> bool {\n    static_assert(std::numeric_limits<Float>::digits <= 113, \"unsupported FP\");\n    // Assume Float is in the format [sign][exponent][significand].\n    using carrier_uint = typename dragonbox::float_info<Float>::carrier_uint;\n    const auto num_float_significand_bits =\n        detail::num_significand_bits<Float>();\n    const auto implicit_bit = carrier_uint(1) << num_float_significand_bits;\n    const auto significand_mask = implicit_bit - 1;\n    auto u = bit_cast<carrier_uint>(n);\n    f = static_cast<F>(u & significand_mask);\n    auto biased_e = static_cast<int>((u & exponent_mask<Float>()) >>\n                                     num_float_significand_bits);\n    // The predecessor is closer if n is a normalized power of 2 (f == 0)\n    // other than the smallest normalized number (biased_e > 1).\n    auto is_predecessor_closer = f == 0 && biased_e > 1;\n    if (biased_e == 0)\n      biased_e = 1;  // Subnormals use biased exponent 1 (min exponent).\n    else if (has_implicit_bit<Float>())\n      f += static_cast<F>(implicit_bit);\n    e = biased_e - exponent_bias<Float>() - num_float_significand_bits;\n    if (!has_implicit_bit<Float>()) ++e;\n    return is_predecessor_closer;\n  }\n\n  template <typename Float, FMT_ENABLE_IF(is_double_double<Float>::value)>\n  FMT_CONSTEXPR auto assign(Float n) -> bool {\n    static_assert(std::numeric_limits<double>::is_iec559, \"unsupported FP\");\n    return assign(static_cast<double>(n));\n  }\n};\n\nusing fp = basic_fp<unsigned long long>;\n\n// Normalizes the value converted from double and multiplied by (1 << SHIFT).\ntemplate <int SHIFT = 0, typename F>\nFMT_CONSTEXPR auto normalize(basic_fp<F> value) -> basic_fp<F> {\n  // Handle subnormals.\n  const auto implicit_bit = F(1) << num_significand_bits<double>();\n  const auto shifted_implicit_bit = implicit_bit << SHIFT;\n  while ((value.f & shifted_implicit_bit) == 0) {\n    value.f <<= 1;\n    --value.e;\n  }\n  // Subtract 1 to account for hidden bit.\n  const auto offset = basic_fp<F>::num_significand_bits -\n                      num_significand_bits<double>() - SHIFT - 1;\n  value.f <<= offset;\n  value.e -= offset;\n  return value;\n}\n\n// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking.\nFMT_CONSTEXPR inline auto multiply(uint64_t lhs, uint64_t rhs) -> uint64_t {\n#if FMT_USE_INT128\n  auto product = static_cast<__uint128_t>(lhs) * rhs;\n  auto f = static_cast<uint64_t>(product >> 64);\n  return (static_cast<uint64_t>(product) & (1ULL << 63)) != 0 ? f + 1 : f;\n#else\n  // Multiply 32-bit parts of significands.\n  uint64_t mask = (1ULL << 32) - 1;\n  uint64_t a = lhs >> 32, b = lhs & mask;\n  uint64_t c = rhs >> 32, d = rhs & mask;\n  uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d;\n  // Compute mid 64-bit of result and round.\n  uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31);\n  return ac + (ad >> 32) + (bc >> 32) + (mid >> 32);\n#endif\n}\n\nFMT_CONSTEXPR inline auto operator*(fp x, fp y) -> fp {\n  return {multiply(x.f, y.f), x.e + y.e + 64};\n}\n\ntemplate <typename T, bool doublish = num_bits<T>() == num_bits<double>()>\nusing convert_float_result =\n    conditional_t<std::is_same<T, float>::value || doublish, double, T>;\n\ntemplate <typename T>\nconstexpr auto convert_float(T value) -> convert_float_result<T> {\n  return static_cast<convert_float_result<T>>(value);\n}\n\ntemplate <typename Char, typename OutputIt>\nFMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n,\n                                     const basic_specs& specs) -> OutputIt {\n  auto fill_size = specs.fill_size();\n  if (fill_size == 1) return detail::fill_n(it, n, specs.fill_unit<Char>());\n  if (const Char* data = specs.fill<Char>()) {\n    for (size_t i = 0; i < n; ++i) it = copy<Char>(data, data + fill_size, it);\n  }\n  return it;\n}\n\n// Writes the output of f, padded according to format specifications in specs.\n// size: output size in code units.\n// width: output display width in (terminal) column positions.\ntemplate <typename Char, align default_align = align::left, typename OutputIt,\n          typename F>\nFMT_CONSTEXPR auto write_padded(OutputIt out, const format_specs& specs,\n                                size_t size, size_t width, F&& f) -> OutputIt {\n  static_assert(default_align == align::left || default_align == align::right,\n                \"\");\n  unsigned spec_width = to_unsigned(specs.width);\n  size_t padding = spec_width > width ? spec_width - width : 0;\n  // Shifts are encoded as string literals because static constexpr is not\n  // supported in constexpr functions.\n  auto* shifts =\n      default_align == align::left ? \"\\x1f\\x1f\\x00\\x01\" : \"\\x00\\x1f\\x00\\x01\";\n  size_t left_padding = padding >> shifts[static_cast<int>(specs.align())];\n  size_t right_padding = padding - left_padding;\n  auto it = reserve(out, size + padding * specs.fill_size());\n  if (left_padding != 0) it = fill<Char>(it, left_padding, specs);\n  it = f(it);\n  if (right_padding != 0) it = fill<Char>(it, right_padding, specs);\n  return base_iterator(out, it);\n}\n\ntemplate <typename Char, align default_align = align::left, typename OutputIt,\n          typename F>\nconstexpr auto write_padded(OutputIt out, const format_specs& specs,\n                            size_t size, F&& f) -> OutputIt {\n  return write_padded<Char, default_align>(out, specs, size, size, f);\n}\n\ntemplate <typename Char, align default_align = align::left, typename OutputIt>\nFMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes,\n                               const format_specs& specs = {}) -> OutputIt {\n  return write_padded<Char, default_align>(\n      out, specs, bytes.size(), [bytes](reserve_iterator<OutputIt> it) {\n        const char* data = bytes.data();\n        return copy<Char>(data, data + bytes.size(), it);\n      });\n}\n\ntemplate <typename Char, typename OutputIt, typename UIntPtr>\nauto write_ptr(OutputIt out, UIntPtr value, const format_specs* specs)\n    -> OutputIt {\n  int num_digits = count_digits<4>(value);\n  auto size = to_unsigned(num_digits) + size_t(2);\n  auto write = [=](reserve_iterator<OutputIt> it) {\n    *it++ = static_cast<Char>('0');\n    *it++ = static_cast<Char>('x');\n    return format_base2e<Char>(4, it, value, num_digits);\n  };\n  return specs ? write_padded<Char, align::right>(out, *specs, size, write)\n               : base_iterator(out, write(reserve(out, size)));\n}\n\n// Returns true iff the code point cp is printable.\nFMT_API auto is_printable(uint32_t cp) -> bool;\n\ninline auto needs_escape(uint32_t cp) -> bool {\n  if (cp < 0x20 || cp == 0x7f || cp == '\"' || cp == '\\\\') return true;\n  if (const_check(FMT_OPTIMIZE_SIZE > 1)) return false;\n  return !is_printable(cp);\n}\n\ntemplate <typename Char> struct find_escape_result {\n  const Char* begin;\n  const Char* end;\n  uint32_t cp;\n};\n\ntemplate <typename Char>\nauto find_escape(const Char* begin, const Char* end)\n    -> find_escape_result<Char> {\n  for (; begin != end; ++begin) {\n    uint32_t cp = static_cast<unsigned_char<Char>>(*begin);\n    if (const_check(sizeof(Char) == 1) && cp >= 0x80) continue;\n    if (needs_escape(cp)) return {begin, begin + 1, cp};\n  }\n  return {begin, nullptr, 0};\n}\n\ninline auto find_escape(const char* begin, const char* end)\n    -> find_escape_result<char> {\n  if (const_check(!use_utf8)) return find_escape<char>(begin, end);\n  auto result = find_escape_result<char>{end, nullptr, 0};\n  for_each_codepoint(string_view(begin, to_unsigned(end - begin)),\n                     [&](uint32_t cp, string_view sv) {\n                       if (needs_escape(cp)) {\n                         result = {sv.begin(), sv.end(), cp};\n                         return false;\n                       }\n                       return true;\n                     });\n  return result;\n}\n\ntemplate <size_t width, typename Char, typename OutputIt>\nauto write_codepoint(OutputIt out, char prefix, uint32_t cp) -> OutputIt {\n  *out++ = static_cast<Char>('\\\\');\n  *out++ = static_cast<Char>(prefix);\n  Char buf[width];\n  fill_n(buf, width, static_cast<Char>('0'));\n  format_base2e(4, buf, cp, width);\n  return copy<Char>(buf, buf + width, out);\n}\n\ntemplate <typename OutputIt, typename Char>\nauto write_escaped_cp(OutputIt out, const find_escape_result<Char>& escape)\n    -> OutputIt {\n  auto c = static_cast<Char>(escape.cp);\n  switch (escape.cp) {\n  case '\\n':\n    *out++ = static_cast<Char>('\\\\');\n    c = static_cast<Char>('n');\n    break;\n  case '\\r':\n    *out++ = static_cast<Char>('\\\\');\n    c = static_cast<Char>('r');\n    break;\n  case '\\t':\n    *out++ = static_cast<Char>('\\\\');\n    c = static_cast<Char>('t');\n    break;\n  case '\"':  FMT_FALLTHROUGH;\n  case '\\'': FMT_FALLTHROUGH;\n  case '\\\\': *out++ = static_cast<Char>('\\\\'); break;\n  default:\n    if (escape.cp < 0x100) return write_codepoint<2, Char>(out, 'x', escape.cp);\n    if (escape.cp < 0x10000)\n      return write_codepoint<4, Char>(out, 'u', escape.cp);\n    if (escape.cp < 0x110000)\n      return write_codepoint<8, Char>(out, 'U', escape.cp);\n    for (Char escape_char : basic_string_view<Char>(\n             escape.begin, to_unsigned(escape.end - escape.begin))) {\n      out = write_codepoint<2, Char>(out, 'x',\n                                     static_cast<uint32_t>(escape_char) & 0xFF);\n    }\n    return out;\n  }\n  *out++ = c;\n  return out;\n}\n\ntemplate <typename Char, typename OutputIt>\nauto write_escaped_string(OutputIt out, basic_string_view<Char> str)\n    -> OutputIt {\n  *out++ = static_cast<Char>('\"');\n  auto begin = str.begin(), end = str.end();\n  do {\n    auto escape = find_escape(begin, end);\n    out = copy<Char>(begin, escape.begin, out);\n    begin = escape.end;\n    if (!begin) break;\n    out = write_escaped_cp<OutputIt, Char>(out, escape);\n  } while (begin != end);\n  *out++ = static_cast<Char>('\"');\n  return out;\n}\n\ntemplate <typename Char, typename OutputIt>\nauto write_escaped_char(OutputIt out, Char v) -> OutputIt {\n  Char v_array[1] = {v};\n  *out++ = static_cast<Char>('\\'');\n  if ((needs_escape(static_cast<uint32_t>(v)) && v != static_cast<Char>('\"')) ||\n      v == static_cast<Char>('\\'')) {\n    out = write_escaped_cp(out,\n                           find_escape_result<Char>{v_array, v_array + 1,\n                                                    static_cast<uint32_t>(v)});\n  } else {\n    *out++ = v;\n  }\n  *out++ = static_cast<Char>('\\'');\n  return out;\n}\n\ntemplate <typename Char, typename OutputIt>\nFMT_CONSTEXPR auto write_char(OutputIt out, Char value,\n                              const format_specs& specs) -> OutputIt {\n  bool is_debug = specs.type() == presentation_type::debug;\n  return write_padded<Char>(out, specs, 1, [=](reserve_iterator<OutputIt> it) {\n    if (is_debug) return write_escaped_char(it, value);\n    *it++ = value;\n    return it;\n  });\n}\ntemplate <typename Char, typename OutputIt>\nFMT_CONSTEXPR auto write(OutputIt out, Char value, const format_specs& specs,\n                         locale_ref loc = {}) -> OutputIt {\n  // char is formatted as unsigned char for consistency across platforms.\n  using unsigned_type =\n      conditional_t<std::is_same<Char, char>::value, unsigned char, unsigned>;\n  return check_char_specs(specs)\n             ? write_char<Char>(out, value, specs)\n             : write<Char>(out, static_cast<unsigned_type>(value), specs, loc);\n}\n\ntemplate <typename Char> class digit_grouping {\n private:\n  std::string grouping_;\n  std::basic_string<Char> thousands_sep_;\n\n  struct next_state {\n    std::string::const_iterator group;\n    int pos;\n  };\n  auto initial_state() const -> next_state { return {grouping_.begin(), 0}; }\n\n  // Returns the next digit group separator position.\n  auto next(next_state& state) const -> int {\n    if (thousands_sep_.empty()) return max_value<int>();\n    if (state.group == grouping_.end()) return state.pos += grouping_.back();\n    if (*state.group <= 0 || *state.group == max_value<char>())\n      return max_value<int>();\n    state.pos += *state.group++;\n    return state.pos;\n  }\n\n public:\n  template <typename Locale,\n            FMT_ENABLE_IF(std::is_same<Locale, locale_ref>::value)>\n  explicit digit_grouping(Locale loc, bool localized = true) {\n    if (!localized) return;\n    auto sep = thousands_sep<Char>(loc);\n    grouping_ = sep.grouping;\n    if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep);\n  }\n  digit_grouping(std::string grouping, std::basic_string<Char> sep)\n      : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {}\n\n  auto has_separator() const -> bool { return !thousands_sep_.empty(); }\n\n  auto count_separators(int num_digits) const -> int {\n    int count = 0;\n    auto state = initial_state();\n    while (num_digits > next(state)) ++count;\n    return count;\n  }\n\n  // Applies grouping to digits and write the output to out.\n  template <typename Out, typename C>\n  auto apply(Out out, basic_string_view<C> digits) const -> Out {\n    auto num_digits = static_cast<int>(digits.size());\n    auto separators = basic_memory_buffer<int>();\n    separators.push_back(0);\n    auto state = initial_state();\n    while (int i = next(state)) {\n      if (i >= num_digits) break;\n      separators.push_back(i);\n    }\n    for (int i = 0, sep_index = static_cast<int>(separators.size() - 1);\n         i < num_digits; ++i) {\n      if (num_digits - i == separators[sep_index]) {\n        out = copy<Char>(thousands_sep_.data(),\n                         thousands_sep_.data() + thousands_sep_.size(), out);\n        --sep_index;\n      }\n      *out++ = static_cast<Char>(digits[to_unsigned(i)]);\n    }\n    return out;\n  }\n};\n\nFMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) {\n  prefix |= prefix != 0 ? value << 8 : value;\n  prefix += (1u + (value > 0xff ? 1 : 0)) << 24;\n}\n\n// Writes a decimal integer with digit grouping.\ntemplate <typename OutputIt, typename UInt, typename Char>\nauto write_int(OutputIt out, UInt value, unsigned prefix,\n               const format_specs& specs, const digit_grouping<Char>& grouping)\n    -> OutputIt {\n  static_assert(std::is_same<uint64_or_128_t<UInt>, UInt>::value, \"\");\n  int num_digits = 0;\n  auto buffer = memory_buffer();\n  switch (specs.type()) {\n  default: FMT_ASSERT(false, \"\"); FMT_FALLTHROUGH;\n  case presentation_type::none:\n  case presentation_type::dec:\n    num_digits = count_digits(value);\n    format_decimal<char>(appender(buffer), value, num_digits);\n    break;\n  case presentation_type::hex:\n    if (specs.alt())\n      prefix_append(prefix, unsigned(specs.upper() ? 'X' : 'x') << 8 | '0');\n    num_digits = count_digits<4>(value);\n    format_base2e<char>(4, appender(buffer), value, num_digits, specs.upper());\n    break;\n  case presentation_type::oct:\n    num_digits = count_digits<3>(value);\n    // Octal prefix '0' is counted as a digit, so only add it if precision\n    // is not greater than the number of digits.\n    if (specs.alt() && specs.precision <= num_digits && value != 0)\n      prefix_append(prefix, '0');\n    format_base2e<char>(3, appender(buffer), value, num_digits);\n    break;\n  case presentation_type::bin:\n    if (specs.alt())\n      prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0');\n    num_digits = count_digits<1>(value);\n    format_base2e<char>(1, appender(buffer), value, num_digits);\n    break;\n  case presentation_type::chr:\n    return write_char<Char>(out, static_cast<Char>(value), specs);\n  }\n\n  unsigned size = (prefix != 0 ? prefix >> 24 : 0) + to_unsigned(num_digits) +\n                  to_unsigned(grouping.count_separators(num_digits));\n  return write_padded<Char, align::right>(\n      out, specs, size, size, [&](reserve_iterator<OutputIt> it) {\n        for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8)\n          *it++ = static_cast<Char>(p & 0xff);\n        return grouping.apply(it, string_view(buffer.data(), buffer.size()));\n      });\n}\n\n#if FMT_USE_LOCALE\n// Writes a localized value.\nFMT_API auto write_loc(appender out, loc_value value, const format_specs& specs,\n                       locale_ref loc) -> bool;\n#endif\ntemplate <typename OutputIt>\ninline auto write_loc(OutputIt, const loc_value&, const format_specs&,\n                      locale_ref) -> bool {\n  return false;\n}\n\ntemplate <typename UInt> struct write_int_arg {\n  UInt abs_value;\n  unsigned prefix;\n};\n\ntemplate <typename T>\nFMT_CONSTEXPR auto make_write_int_arg(T value, sign s)\n    -> write_int_arg<uint32_or_64_or_128_t<T>> {\n  auto prefix = 0u;\n  auto abs_value = static_cast<uint32_or_64_or_128_t<T>>(value);\n  if (is_negative(value)) {\n    prefix = 0x01000000 | '-';\n    abs_value = 0 - abs_value;\n  } else {\n    constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+',\n                                            0x1000000u | ' '};\n    prefix = prefixes[static_cast<int>(s)];\n  }\n  return {abs_value, prefix};\n}\n\ntemplate <typename Char = char> struct loc_writer {\n  basic_appender<Char> out;\n  const format_specs& specs;\n  std::basic_string<Char> sep;\n  std::string grouping;\n  std::basic_string<Char> decimal_point;\n\n  template <typename T, FMT_ENABLE_IF(is_integer<T>::value)>\n  auto operator()(T value) -> bool {\n    auto arg = make_write_int_arg(value, specs.sign());\n    write_int(out, static_cast<uint64_or_128_t<T>>(arg.abs_value), arg.prefix,\n              specs, digit_grouping<Char>(grouping, sep));\n    return true;\n  }\n\n  template <typename T, FMT_ENABLE_IF(!is_integer<T>::value)>\n  auto operator()(T) -> bool {\n    return false;\n  }\n};\n\n// Size and padding computation separate from write_int to avoid template bloat.\nstruct size_padding {\n  unsigned size;\n  unsigned padding;\n\n  FMT_CONSTEXPR size_padding(int num_digits, unsigned prefix,\n                             const format_specs& specs)\n      : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) {\n    if (specs.align() == align::numeric) {\n      auto width = to_unsigned(specs.width);\n      if (width > size) {\n        padding = width - size;\n        size = width;\n      }\n    } else if (specs.precision > num_digits) {\n      size = (prefix >> 24) + to_unsigned(specs.precision);\n      padding = to_unsigned(specs.precision - num_digits);\n    }\n  }\n};\n\ntemplate <typename Char, typename OutputIt, typename T>\nFMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg<T> arg,\n                                        const format_specs& specs) -> OutputIt {\n  static_assert(std::is_same<T, uint32_or_64_or_128_t<T>>::value, \"\");\n\n  constexpr int buffer_size = num_bits<T>();\n  char buffer[buffer_size];\n  if (is_constant_evaluated()) fill_n(buffer, buffer_size, '\\0');\n  const char* begin = nullptr;\n  const char* end = buffer + buffer_size;\n\n  auto abs_value = arg.abs_value;\n  auto prefix = arg.prefix;\n  switch (specs.type()) {\n  default: FMT_ASSERT(false, \"\"); FMT_FALLTHROUGH;\n  case presentation_type::none:\n  case presentation_type::dec:\n    begin = do_format_decimal(buffer, abs_value, buffer_size);\n    break;\n  case presentation_type::hex:\n    begin = do_format_base2e(4, buffer, abs_value, buffer_size, specs.upper());\n    if (specs.alt())\n      prefix_append(prefix, unsigned(specs.upper() ? 'X' : 'x') << 8 | '0');\n    break;\n  case presentation_type::oct: {\n    begin = do_format_base2e(3, buffer, abs_value, buffer_size);\n    // Octal prefix '0' is counted as a digit, so only add it if precision\n    // is not greater than the number of digits.\n    auto num_digits = end - begin;\n    if (specs.alt() && specs.precision <= num_digits && abs_value != 0)\n      prefix_append(prefix, '0');\n    break;\n  }\n  case presentation_type::bin:\n    begin = do_format_base2e(1, buffer, abs_value, buffer_size);\n    if (specs.alt())\n      prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0');\n    break;\n  case presentation_type::chr:\n    return write_char<Char>(out, static_cast<Char>(abs_value), specs);\n  }\n\n  // Write an integer in the format\n  //   <left-padding><prefix><numeric-padding><digits><right-padding>\n  // prefix contains chars in three lower bytes and the size in the fourth byte.\n  int num_digits = static_cast<int>(end - begin);\n  // Slightly faster check for specs.width == 0 && specs.precision == -1.\n  if ((specs.width | (specs.precision + 1)) == 0) {\n    auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24));\n    for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8)\n      *it++ = static_cast<Char>(p & 0xff);\n    return base_iterator(out, copy<Char>(begin, end, it));\n  }\n  auto sp = size_padding(num_digits, prefix, specs);\n  unsigned padding = sp.padding;\n  return write_padded<Char, align::right>(\n      out, specs, sp.size, [=](reserve_iterator<OutputIt> it) {\n        for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8)\n          *it++ = static_cast<Char>(p & 0xff);\n        it = detail::fill_n(it, padding, static_cast<Char>('0'));\n        return copy<Char>(begin, end, it);\n      });\n}\n\ntemplate <typename Char, typename OutputIt, typename T>\nFMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline(OutputIt out,\n                                                   write_int_arg<T> arg,\n                                                   const format_specs& specs)\n    -> OutputIt {\n  return write_int<Char>(out, arg, specs);\n}\n\ntemplate <typename Char, typename T,\n          FMT_ENABLE_IF(is_integral<T>::value &&\n                        !std::is_same<T, bool>::value &&\n                        !std::is_same<T, Char>::value)>\nFMT_CONSTEXPR FMT_INLINE auto write(basic_appender<Char> out, T value,\n                                    const format_specs& specs, locale_ref loc)\n    -> basic_appender<Char> {\n  if (specs.localized() && write_loc(out, value, specs, loc)) return out;\n  return write_int_noinline<Char>(out, make_write_int_arg(value, specs.sign()),\n                                  specs);\n}\n\n// An inlined version of write used in format string compilation.\ntemplate <typename Char, typename OutputIt, typename T,\n          FMT_ENABLE_IF(is_integral<T>::value &&\n                        !std::is_same<T, bool>::value &&\n                        !std::is_same<T, Char>::value &&\n                        !std::is_same<OutputIt, basic_appender<Char>>::value)>\nFMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value,\n                                    const format_specs& specs, locale_ref loc)\n    -> OutputIt {\n  if (specs.localized() && write_loc(out, value, specs, loc)) return out;\n  return write_int<Char>(out, make_write_int_arg(value, specs.sign()), specs);\n}\n\ntemplate <typename Char, typename OutputIt>\nFMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> s,\n                         const format_specs& specs) -> OutputIt {\n  auto data = s.data();\n  auto size = s.size();\n  if (specs.precision >= 0 && to_unsigned(specs.precision) < size)\n    size = code_point_index(s, to_unsigned(specs.precision));\n\n  bool is_debug = specs.type() == presentation_type::debug;\n  if (is_debug) {\n    auto buf = counting_buffer<Char>();\n    write_escaped_string(basic_appender<Char>(buf), s);\n    size = buf.count();\n  }\n\n  size_t width = 0;\n  if (specs.width != 0) {\n    width =\n        is_debug ? size : compute_width(basic_string_view<Char>(data, size));\n  }\n  return write_padded<Char>(\n      out, specs, size, width, [=](reserve_iterator<OutputIt> it) {\n        return is_debug ? write_escaped_string(it, s)\n                        : copy<Char>(data, data + size, it);\n      });\n}\ntemplate <typename Char, typename OutputIt>\nFMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> s,\n                         const format_specs& specs, locale_ref) -> OutputIt {\n  return write<Char>(out, s, specs);\n}\ntemplate <typename Char, typename OutputIt>\nFMT_CONSTEXPR auto write(OutputIt out, const Char* s, const format_specs& specs,\n                         locale_ref) -> OutputIt {\n  if (specs.type() == presentation_type::pointer)\n    return write_ptr<Char>(out, bit_cast<uintptr_t>(s), &specs);\n  if (!s) report_error(\"string pointer is null\");\n  return write<Char>(out, basic_string_view<Char>(s), specs, {});\n}\n\ntemplate <typename Char, typename OutputIt, typename T,\n          FMT_ENABLE_IF(is_integral<T>::value &&\n                        !std::is_same<T, bool>::value &&\n                        !std::is_same<T, Char>::value)>\nFMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt {\n  auto abs_value = static_cast<uint32_or_64_or_128_t<T>>(value);\n  bool negative = is_negative(value);\n  // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer.\n  if (negative) abs_value = ~abs_value + 1;\n  int num_digits = count_digits(abs_value);\n  auto size = (negative ? 1 : 0) + static_cast<size_t>(num_digits);\n  if (auto ptr = to_pointer<Char>(out, size)) {\n    if (negative) *ptr++ = static_cast<Char>('-');\n    format_decimal<Char>(ptr, abs_value, num_digits);\n    return out;\n  }\n  if (negative) *out++ = static_cast<Char>('-');\n  return format_decimal<Char>(out, abs_value, num_digits);\n}\n\ntemplate <typename Char>\nFMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end,\n                               format_specs& specs) -> const Char* {\n  FMT_ASSERT(begin != end, \"\");\n  auto alignment = align::none;\n  auto p = begin + code_point_length(begin);\n  if (end - p <= 0) p = begin;\n  for (;;) {\n    switch (to_ascii(*p)) {\n    case '<': alignment = align::left; break;\n    case '>': alignment = align::right; break;\n    case '^': alignment = align::center; break;\n    }\n    if (alignment != align::none) {\n      if (p != begin) {\n        auto c = *begin;\n        if (c == '}') return begin;\n        if (c == '{') {\n          report_error(\"invalid fill character '{'\");\n          return begin;\n        }\n        specs.set_fill(basic_string_view<Char>(begin, to_unsigned(p - begin)));\n        begin = p + 1;\n      } else {\n        ++begin;\n      }\n      break;\n    } else if (p == begin) {\n      break;\n    }\n    p = begin;\n  }\n  specs.set_align(alignment);\n  return begin;\n}\n\ntemplate <typename Char, typename OutputIt>\nFMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan,\n                                     format_specs specs, sign s) -> OutputIt {\n  auto str =\n      isnan ? (specs.upper() ? \"NAN\" : \"nan\") : (specs.upper() ? \"INF\" : \"inf\");\n  constexpr size_t str_size = 3;\n  auto size = str_size + (s != sign::none ? 1 : 0);\n  // Replace '0'-padding with space for non-finite values.\n  const bool is_zero_fill =\n      specs.fill_size() == 1 && specs.fill_unit<Char>() == '0';\n  if (is_zero_fill) specs.set_fill(' ');\n  return write_padded<Char>(out, specs, size,\n                            [=](reserve_iterator<OutputIt> it) {\n                              if (s != sign::none)\n                                *it++ = detail::getsign<Char>(s);\n                              return copy<Char>(str, str + str_size, it);\n                            });\n}\n\n// A decimal floating-point number significand * pow(10, exp).\nstruct big_decimal_fp {\n  const char* significand;\n  int significand_size;\n  int exponent;\n};\n\nconstexpr auto get_significand_size(const big_decimal_fp& f) -> int {\n  return f.significand_size;\n}\ntemplate <typename T>\ninline auto get_significand_size(const dragonbox::decimal_fp<T>& f) -> int {\n  return count_digits(f.significand);\n}\n\ntemplate <typename Char, typename OutputIt>\nconstexpr auto write_significand(OutputIt out, const char* significand,\n                                 int significand_size) -> OutputIt {\n  return copy<Char>(significand, significand + significand_size, out);\n}\ntemplate <typename Char, typename OutputIt, typename UInt>\ninline auto write_significand(OutputIt out, UInt significand,\n                              int significand_size) -> OutputIt {\n  return format_decimal<Char>(out, significand, significand_size);\n}\ntemplate <typename Char, typename OutputIt, typename T, typename Grouping>\nFMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand,\n                                       int significand_size, int exponent,\n                                       const Grouping& grouping) -> OutputIt {\n  if (!grouping.has_separator()) {\n    out = write_significand<Char>(out, significand, significand_size);\n    return detail::fill_n(out, exponent, static_cast<Char>('0'));\n  }\n  auto buffer = memory_buffer();\n  write_significand<char>(appender(buffer), significand, significand_size);\n  detail::fill_n(appender(buffer), exponent, '0');\n  return grouping.apply(out, string_view(buffer.data(), buffer.size()));\n}\n\ntemplate <typename Char, typename UInt,\n          FMT_ENABLE_IF(std::is_integral<UInt>::value)>\ninline auto write_significand(Char* out, UInt significand, int significand_size,\n                              int integral_size, Char decimal_point) -> Char* {\n  if (!decimal_point) return format_decimal(out, significand, significand_size);\n  out += significand_size + 1;\n  Char* end = out;\n  int floating_size = significand_size - integral_size;\n  for (int i = floating_size / 2; i > 0; --i) {\n    out -= 2;\n    write2digits(out, static_cast<std::size_t>(significand % 100));\n    significand /= 100;\n  }\n  if (floating_size % 2 != 0) {\n    *--out = static_cast<Char>('0' + significand % 10);\n    significand /= 10;\n  }\n  *--out = decimal_point;\n  format_decimal(out - integral_size, significand, integral_size);\n  return end;\n}\n\ntemplate <typename OutputIt, typename UInt, typename Char,\n          FMT_ENABLE_IF(!std::is_pointer<remove_cvref_t<OutputIt>>::value)>\ninline auto write_significand(OutputIt out, UInt significand,\n                              int significand_size, int integral_size,\n                              Char decimal_point) -> OutputIt {\n  // Buffer is large enough to hold digits (digits10 + 1) and a decimal point.\n  Char buffer[digits10<UInt>() + 2];\n  auto end = write_significand(buffer, significand, significand_size,\n                               integral_size, decimal_point);\n  return detail::copy_noinline<Char>(buffer, end, out);\n}\n\ntemplate <typename OutputIt, typename Char>\nFMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand,\n                                     int significand_size, int integral_size,\n                                     Char decimal_point) -> OutputIt {\n  out = detail::copy_noinline<Char>(significand, significand + integral_size,\n                                    out);\n  if (!decimal_point) return out;\n  *out++ = decimal_point;\n  return detail::copy_noinline<Char>(significand + integral_size,\n                                     significand + significand_size, out);\n}\n\ntemplate <typename OutputIt, typename Char, typename T, typename Grouping>\nFMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand,\n                                       int significand_size, int integral_size,\n                                       Char decimal_point,\n                                       const Grouping& grouping) -> OutputIt {\n  if (!grouping.has_separator()) {\n    return write_significand(out, significand, significand_size, integral_size,\n                             decimal_point);\n  }\n  auto buffer = basic_memory_buffer<Char>();\n  write_significand(basic_appender<Char>(buffer), significand, significand_size,\n                    integral_size, decimal_point);\n  grouping.apply(\n      out, basic_string_view<Char>(buffer.data(), to_unsigned(integral_size)));\n  return detail::copy_noinline<Char>(buffer.data() + integral_size,\n                                     buffer.end(), out);\n}\n\ntemplate <typename Char, typename OutputIt, typename DecimalFP,\n          typename Grouping = digit_grouping<Char>>\nFMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f,\n                                    const format_specs& specs, sign s,\n                                    locale_ref loc) -> OutputIt {\n  auto significand = f.significand;\n  int significand_size = get_significand_size(f);\n  const Char zero = static_cast<Char>('0');\n  size_t size = to_unsigned(significand_size) + (s != sign::none ? 1 : 0);\n  using iterator = reserve_iterator<OutputIt>;\n\n  Char decimal_point = specs.localized() ? detail::decimal_point<Char>(loc)\n                                         : static_cast<Char>('.');\n\n  int output_exp = f.exponent + significand_size - 1;\n  auto use_exp_format = [=]() {\n    if (specs.type() == presentation_type::exp) return true;\n    if (specs.type() == presentation_type::fixed) return false;\n    // Use the fixed notation if the exponent is in [exp_lower, exp_upper),\n    // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation.\n    const int exp_lower = -4, exp_upper = 16;\n    return output_exp < exp_lower ||\n           output_exp >= (specs.precision > 0 ? specs.precision : exp_upper);\n  };\n  if (use_exp_format()) {\n    int num_zeros = 0;\n    if (specs.alt()) {\n      num_zeros = specs.precision - significand_size;\n      if (num_zeros < 0) num_zeros = 0;\n      size += to_unsigned(num_zeros);\n    } else if (significand_size == 1) {\n      decimal_point = Char();\n    }\n    auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp;\n    int exp_digits = 2;\n    if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3;\n\n    size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits);\n    char exp_char = specs.upper() ? 'E' : 'e';\n    auto write = [=](iterator it) {\n      if (s != sign::none) *it++ = detail::getsign<Char>(s);\n      // Insert a decimal point after the first digit and add an exponent.\n      it = write_significand(it, significand, significand_size, 1,\n                             decimal_point);\n      if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero);\n      *it++ = static_cast<Char>(exp_char);\n      return write_exponent<Char>(output_exp, it);\n    };\n    return specs.width > 0\n               ? write_padded<Char, align::right>(out, specs, size, write)\n               : base_iterator(out, write(reserve(out, size)));\n  }\n\n  int exp = f.exponent + significand_size;\n  if (f.exponent >= 0) {\n    // 1234e5 -> 123400000[.0+]\n    size += to_unsigned(f.exponent);\n    int num_zeros = specs.precision - exp;\n    abort_fuzzing_if(num_zeros > 5000);\n    if (specs.alt()) {\n      ++size;\n      if (num_zeros <= 0 && specs.type() != presentation_type::fixed)\n        num_zeros = 0;\n      if (num_zeros > 0) size += to_unsigned(num_zeros);\n    }\n    auto grouping = Grouping(loc, specs.localized());\n    size += to_unsigned(grouping.count_separators(exp));\n    return write_padded<Char, align::right>(out, specs, size, [&](iterator it) {\n      if (s != sign::none) *it++ = detail::getsign<Char>(s);\n      it = write_significand<Char>(it, significand, significand_size,\n                                   f.exponent, grouping);\n      if (!specs.alt()) return it;\n      *it++ = decimal_point;\n      return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;\n    });\n  } else if (exp > 0) {\n    // 1234e-2 -> 12.34[0+]\n    int num_zeros = specs.alt() ? specs.precision - significand_size : 0;\n    size += 1 + static_cast<unsigned>(max_of(num_zeros, 0));\n    auto grouping = Grouping(loc, specs.localized());\n    size += to_unsigned(grouping.count_separators(exp));\n    return write_padded<Char, align::right>(out, specs, size, [&](iterator it) {\n      if (s != sign::none) *it++ = detail::getsign<Char>(s);\n      it = write_significand(it, significand, significand_size, exp,\n                             decimal_point, grouping);\n      return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;\n    });\n  }\n  // 1234e-6 -> 0.001234\n  int num_zeros = -exp;\n  if (significand_size == 0 && specs.precision >= 0 &&\n      specs.precision < num_zeros) {\n    num_zeros = specs.precision;\n  }\n  bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt();\n  size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros);\n  return write_padded<Char, align::right>(out, specs, size, [&](iterator it) {\n    if (s != sign::none) *it++ = detail::getsign<Char>(s);\n    *it++ = zero;\n    if (!pointy) return it;\n    *it++ = decimal_point;\n    it = detail::fill_n(it, num_zeros, zero);\n    return write_significand<Char>(it, significand, significand_size);\n  });\n}\n\ntemplate <typename Char> class fallback_digit_grouping {\n public:\n  constexpr fallback_digit_grouping(locale_ref, bool) {}\n\n  constexpr auto has_separator() const -> bool { return false; }\n\n  constexpr auto count_separators(int) const -> int { return 0; }\n\n  template <typename Out, typename C>\n  constexpr auto apply(Out out, basic_string_view<C>) const -> Out {\n    return out;\n  }\n};\n\ntemplate <typename Char, typename OutputIt, typename DecimalFP>\nFMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f,\n                                 const format_specs& specs, sign s,\n                                 locale_ref loc) -> OutputIt {\n  if (is_constant_evaluated()) {\n    return do_write_float<Char, OutputIt, DecimalFP,\n                          fallback_digit_grouping<Char>>(out, f, specs, s, loc);\n  } else {\n    return do_write_float<Char>(out, f, specs, s, loc);\n  }\n}\n\ntemplate <typename T> constexpr auto isnan(T value) -> bool {\n  return value != value;  // std::isnan doesn't support __float128.\n}\n\ntemplate <typename T, typename Enable = void>\nstruct has_isfinite : std::false_type {};\n\ntemplate <typename T>\nstruct has_isfinite<T, enable_if_t<sizeof(std::isfinite(T())) != 0>>\n    : std::true_type {};\n\ntemplate <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value&&\n                                        has_isfinite<T>::value)>\nFMT_CONSTEXPR20 auto isfinite(T value) -> bool {\n  constexpr T inf = T(std::numeric_limits<double>::infinity());\n  if (is_constant_evaluated())\n    return !detail::isnan(value) && value < inf && value > -inf;\n  return std::isfinite(value);\n}\ntemplate <typename T, FMT_ENABLE_IF(!has_isfinite<T>::value)>\nFMT_CONSTEXPR auto isfinite(T value) -> bool {\n  T inf = T(std::numeric_limits<double>::infinity());\n  // std::isfinite doesn't support __float128.\n  return !detail::isnan(value) && value < inf && value > -inf;\n}\n\ntemplate <typename T, FMT_ENABLE_IF(is_floating_point<T>::value)>\nFMT_INLINE FMT_CONSTEXPR bool signbit(T value) {\n  if (is_constant_evaluated()) {\n#ifdef __cpp_if_constexpr\n    if constexpr (std::numeric_limits<double>::is_iec559) {\n      auto bits = detail::bit_cast<uint64_t>(static_cast<double>(value));\n      return (bits >> (num_bits<uint64_t>() - 1)) != 0;\n    }\n#endif\n  }\n  return std::signbit(static_cast<double>(value));\n}\n\ninline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) {\n  // Adjust fixed precision by exponent because it is relative to decimal\n  // point.\n  if (exp10 > 0 && precision > max_value<int>() - exp10)\n    FMT_THROW(format_error(\"number is too big\"));\n  precision += exp10;\n}\n\nclass bigint {\n private:\n  // A bigint is a number in the form bigit_[N - 1] ... bigit_[0] * 32^exp_.\n  using bigit = uint32_t;  // A big digit.\n  using double_bigit = uint64_t;\n  enum { bigit_bits = num_bits<bigit>() };\n  enum { bigits_capacity = 32 };\n  basic_memory_buffer<bigit, bigits_capacity> bigits_;\n  int exp_;\n\n  friend struct formatter<bigint>;\n\n  FMT_CONSTEXPR auto get_bigit(int i) const -> bigit {\n    return i >= exp_ && i < num_bigits() ? bigits_[i - exp_] : 0;\n  }\n\n  FMT_CONSTEXPR void subtract_bigits(int index, bigit other, bigit& borrow) {\n    auto result = double_bigit(bigits_[index]) - other - borrow;\n    bigits_[index] = static_cast<bigit>(result);\n    borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1));\n  }\n\n  FMT_CONSTEXPR void remove_leading_zeros() {\n    int num_bigits = static_cast<int>(bigits_.size()) - 1;\n    while (num_bigits > 0 && bigits_[num_bigits] == 0) --num_bigits;\n    bigits_.resize(to_unsigned(num_bigits + 1));\n  }\n\n  // Computes *this -= other assuming aligned bigints and *this >= other.\n  FMT_CONSTEXPR void subtract_aligned(const bigint& other) {\n    FMT_ASSERT(other.exp_ >= exp_, \"unaligned bigints\");\n    FMT_ASSERT(compare(*this, other) >= 0, \"\");\n    bigit borrow = 0;\n    int i = other.exp_ - exp_;\n    for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j)\n      subtract_bigits(i, other.bigits_[j], borrow);\n    if (borrow != 0) subtract_bigits(i, 0, borrow);\n    FMT_ASSERT(borrow == 0, \"\");\n    remove_leading_zeros();\n  }\n\n  FMT_CONSTEXPR void multiply(uint32_t value) {\n    bigit carry = 0;\n    const double_bigit wide_value = value;\n    for (size_t i = 0, n = bigits_.size(); i < n; ++i) {\n      double_bigit result = bigits_[i] * wide_value + carry;\n      bigits_[i] = static_cast<bigit>(result);\n      carry = static_cast<bigit>(result >> bigit_bits);\n    }\n    if (carry != 0) bigits_.push_back(carry);\n  }\n\n  template <typename UInt, FMT_ENABLE_IF(std::is_same<UInt, uint64_t>::value ||\n                                         std::is_same<UInt, uint128_t>::value)>\n  FMT_CONSTEXPR void multiply(UInt value) {\n    using half_uint =\n        conditional_t<std::is_same<UInt, uint128_t>::value, uint64_t, uint32_t>;\n    const int shift = num_bits<half_uint>() - bigit_bits;\n    const UInt lower = static_cast<half_uint>(value);\n    const UInt upper = value >> num_bits<half_uint>();\n    UInt carry = 0;\n    for (size_t i = 0, n = bigits_.size(); i < n; ++i) {\n      UInt result = lower * bigits_[i] + static_cast<bigit>(carry);\n      carry = (upper * bigits_[i] << shift) + (result >> bigit_bits) +\n              (carry >> bigit_bits);\n      bigits_[i] = static_cast<bigit>(result);\n    }\n    while (carry != 0) {\n      bigits_.push_back(static_cast<bigit>(carry));\n      carry >>= bigit_bits;\n    }\n  }\n\n  template <typename UInt, FMT_ENABLE_IF(std::is_same<UInt, uint64_t>::value ||\n                                         std::is_same<UInt, uint128_t>::value)>\n  FMT_CONSTEXPR void assign(UInt n) {\n    size_t num_bigits = 0;\n    do {\n      bigits_[num_bigits++] = static_cast<bigit>(n);\n      n >>= bigit_bits;\n    } while (n != 0);\n    bigits_.resize(num_bigits);\n    exp_ = 0;\n  }\n\n public:\n  FMT_CONSTEXPR bigint() : exp_(0) {}\n  explicit bigint(uint64_t n) { assign(n); }\n\n  bigint(const bigint&) = delete;\n  void operator=(const bigint&) = delete;\n\n  FMT_CONSTEXPR void assign(const bigint& other) {\n    auto size = other.bigits_.size();\n    bigits_.resize(size);\n    auto data = other.bigits_.data();\n    copy<bigit>(data, data + size, bigits_.data());\n    exp_ = other.exp_;\n  }\n\n  template <typename Int> FMT_CONSTEXPR void operator=(Int n) {\n    FMT_ASSERT(n > 0, \"\");\n    assign(uint64_or_128_t<Int>(n));\n  }\n\n  FMT_CONSTEXPR auto num_bigits() const -> int {\n    return static_cast<int>(bigits_.size()) + exp_;\n  }\n\n  FMT_CONSTEXPR auto operator<<=(int shift) -> bigint& {\n    FMT_ASSERT(shift >= 0, \"\");\n    exp_ += shift / bigit_bits;\n    shift %= bigit_bits;\n    if (shift == 0) return *this;\n    bigit carry = 0;\n    for (size_t i = 0, n = bigits_.size(); i < n; ++i) {\n      bigit c = bigits_[i] >> (bigit_bits - shift);\n      bigits_[i] = (bigits_[i] << shift) + carry;\n      carry = c;\n    }\n    if (carry != 0) bigits_.push_back(carry);\n    return *this;\n  }\n\n  template <typename Int> FMT_CONSTEXPR auto operator*=(Int value) -> bigint& {\n    FMT_ASSERT(value > 0, \"\");\n    multiply(uint32_or_64_or_128_t<Int>(value));\n    return *this;\n  }\n\n  friend FMT_CONSTEXPR auto compare(const bigint& b1, const bigint& b2) -> int {\n    int num_bigits1 = b1.num_bigits(), num_bigits2 = b2.num_bigits();\n    if (num_bigits1 != num_bigits2) return num_bigits1 > num_bigits2 ? 1 : -1;\n    int i = static_cast<int>(b1.bigits_.size()) - 1;\n    int j = static_cast<int>(b2.bigits_.size()) - 1;\n    int end = i - j;\n    if (end < 0) end = 0;\n    for (; i >= end; --i, --j) {\n      bigit b1_bigit = b1.bigits_[i], b2_bigit = b2.bigits_[j];\n      if (b1_bigit != b2_bigit) return b1_bigit > b2_bigit ? 1 : -1;\n    }\n    if (i != j) return i > j ? 1 : -1;\n    return 0;\n  }\n\n  // Returns compare(lhs1 + lhs2, rhs).\n  friend FMT_CONSTEXPR auto add_compare(const bigint& lhs1, const bigint& lhs2,\n                                        const bigint& rhs) -> int {\n    int max_lhs_bigits = max_of(lhs1.num_bigits(), lhs2.num_bigits());\n    int num_rhs_bigits = rhs.num_bigits();\n    if (max_lhs_bigits + 1 < num_rhs_bigits) return -1;\n    if (max_lhs_bigits > num_rhs_bigits) return 1;\n    double_bigit borrow = 0;\n    int min_exp = min_of(min_of(lhs1.exp_, lhs2.exp_), rhs.exp_);\n    for (int i = num_rhs_bigits - 1; i >= min_exp; --i) {\n      double_bigit sum = double_bigit(lhs1.get_bigit(i)) + lhs2.get_bigit(i);\n      bigit rhs_bigit = rhs.get_bigit(i);\n      if (sum > rhs_bigit + borrow) return 1;\n      borrow = rhs_bigit + borrow - sum;\n      if (borrow > 1) return -1;\n      borrow <<= bigit_bits;\n    }\n    return borrow != 0 ? -1 : 0;\n  }\n\n  // Assigns pow(10, exp) to this bigint.\n  FMT_CONSTEXPR20 void assign_pow10(int exp) {\n    FMT_ASSERT(exp >= 0, \"\");\n    if (exp == 0) return *this = 1;\n    int bitmask = 1 << (num_bits<unsigned>() -\n                        countl_zero(static_cast<uint32_t>(exp)) - 1);\n    // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by\n    // repeated squaring and multiplication.\n    *this = 5;\n    bitmask >>= 1;\n    while (bitmask != 0) {\n      square();\n      if ((exp & bitmask) != 0) *this *= 5;\n      bitmask >>= 1;\n    }\n    *this <<= exp;  // Multiply by pow(2, exp) by shifting.\n  }\n\n  FMT_CONSTEXPR20 void square() {\n    int num_bigits = static_cast<int>(bigits_.size());\n    int num_result_bigits = 2 * num_bigits;\n    basic_memory_buffer<bigit, bigits_capacity> n(std::move(bigits_));\n    bigits_.resize(to_unsigned(num_result_bigits));\n    auto sum = uint128_t();\n    for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) {\n      // Compute bigit at position bigit_index of the result by adding\n      // cross-product terms n[i] * n[j] such that i + j == bigit_index.\n      for (int i = 0, j = bigit_index; j >= 0; ++i, --j) {\n        // Most terms are multiplied twice which can be optimized in the future.\n        sum += double_bigit(n[i]) * n[j];\n      }\n      bigits_[bigit_index] = static_cast<bigit>(sum);\n      sum >>= num_bits<bigit>();  // Compute the carry.\n    }\n    // Do the same for the top half.\n    for (int bigit_index = num_bigits; bigit_index < num_result_bigits;\n         ++bigit_index) {\n      for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;)\n        sum += double_bigit(n[i++]) * n[j--];\n      bigits_[bigit_index] = static_cast<bigit>(sum);\n      sum >>= num_bits<bigit>();\n    }\n    remove_leading_zeros();\n    exp_ *= 2;\n  }\n\n  // If this bigint has a bigger exponent than other, adds trailing zero to make\n  // exponents equal. This simplifies some operations such as subtraction.\n  FMT_CONSTEXPR void align(const bigint& other) {\n    int exp_difference = exp_ - other.exp_;\n    if (exp_difference <= 0) return;\n    int num_bigits = static_cast<int>(bigits_.size());\n    bigits_.resize(to_unsigned(num_bigits + exp_difference));\n    for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j)\n      bigits_[j] = bigits_[i];\n    memset(bigits_.data(), 0, to_unsigned(exp_difference) * sizeof(bigit));\n    exp_ -= exp_difference;\n  }\n\n  // Divides this bignum by divisor, assigning the remainder to this and\n  // returning the quotient.\n  FMT_CONSTEXPR auto divmod_assign(const bigint& divisor) -> int {\n    FMT_ASSERT(this != &divisor, \"\");\n    if (compare(*this, divisor) < 0) return 0;\n    FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, \"\");\n    align(divisor);\n    int quotient = 0;\n    do {\n      subtract_aligned(divisor);\n      ++quotient;\n    } while (compare(*this, divisor) >= 0);\n    return quotient;\n  }\n};\n\n// format_dragon flags.\nenum dragon {\n  predecessor_closer = 1,\n  fixup = 2,  // Run fixup to correct exp10 which can be off by one.\n  fixed = 4,\n};\n\n// Formats a floating-point number using a variation of the Fixed-Precision\n// Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White:\n// https://fmt.dev/papers/p372-steele.pdf.\nFMT_CONSTEXPR20 inline void format_dragon(basic_fp<uint128_t> value,\n                                          unsigned flags, int num_digits,\n                                          buffer<char>& buf, int& exp10) {\n  bigint numerator;    // 2 * R in (FPP)^2.\n  bigint denominator;  // 2 * S in (FPP)^2.\n  // lower and upper are differences between value and corresponding boundaries.\n  bigint lower;             // (M^- in (FPP)^2).\n  bigint upper_store;       // upper's value if different from lower.\n  bigint* upper = nullptr;  // (M^+ in (FPP)^2).\n  // Shift numerator and denominator by an extra bit or two (if lower boundary\n  // is closer) to make lower and upper integers. This eliminates multiplication\n  // by 2 during later computations.\n  bool is_predecessor_closer = (flags & dragon::predecessor_closer) != 0;\n  int shift = is_predecessor_closer ? 2 : 1;\n  if (value.e >= 0) {\n    numerator = value.f;\n    numerator <<= value.e + shift;\n    lower = 1;\n    lower <<= value.e;\n    if (is_predecessor_closer) {\n      upper_store = 1;\n      upper_store <<= value.e + 1;\n      upper = &upper_store;\n    }\n    denominator.assign_pow10(exp10);\n    denominator <<= shift;\n  } else if (exp10 < 0) {\n    numerator.assign_pow10(-exp10);\n    lower.assign(numerator);\n    if (is_predecessor_closer) {\n      upper_store.assign(numerator);\n      upper_store <<= 1;\n      upper = &upper_store;\n    }\n    numerator *= value.f;\n    numerator <<= shift;\n    denominator = 1;\n    denominator <<= shift - value.e;\n  } else {\n    numerator = value.f;\n    numerator <<= shift;\n    denominator.assign_pow10(exp10);\n    denominator <<= shift - value.e;\n    lower = 1;\n    if (is_predecessor_closer) {\n      upper_store = 1ULL << 1;\n      upper = &upper_store;\n    }\n  }\n  int even = static_cast<int>((value.f & 1) == 0);\n  if (!upper) upper = &lower;\n  bool shortest = num_digits < 0;\n  if ((flags & dragon::fixup) != 0) {\n    if (add_compare(numerator, *upper, denominator) + even <= 0) {\n      --exp10;\n      numerator *= 10;\n      if (num_digits < 0) {\n        lower *= 10;\n        if (upper != &lower) *upper *= 10;\n      }\n    }\n    if ((flags & dragon::fixed) != 0) adjust_precision(num_digits, exp10 + 1);\n  }\n  // Invariant: value == (numerator / denominator) * pow(10, exp10).\n  if (shortest) {\n    // Generate the shortest representation.\n    num_digits = 0;\n    char* data = buf.data();\n    for (;;) {\n      int digit = numerator.divmod_assign(denominator);\n      bool low = compare(numerator, lower) - even < 0;  // numerator <[=] lower.\n      // numerator + upper >[=] pow10:\n      bool high = add_compare(numerator, *upper, denominator) + even > 0;\n      data[num_digits++] = static_cast<char>('0' + digit);\n      if (low || high) {\n        if (!low) {\n          ++data[num_digits - 1];\n        } else if (high) {\n          int result = add_compare(numerator, numerator, denominator);\n          // Round half to even.\n          if (result > 0 || (result == 0 && (digit % 2) != 0))\n            ++data[num_digits - 1];\n        }\n        buf.try_resize(to_unsigned(num_digits));\n        exp10 -= num_digits - 1;\n        return;\n      }\n      numerator *= 10;\n      lower *= 10;\n      if (upper != &lower) *upper *= 10;\n    }\n  }\n  // Generate the given number of digits.\n  exp10 -= num_digits - 1;\n  if (num_digits <= 0) {\n    auto digit = '0';\n    if (num_digits == 0) {\n      denominator *= 10;\n      digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0';\n    }\n    buf.push_back(digit);\n    return;\n  }\n  buf.try_resize(to_unsigned(num_digits));\n  for (int i = 0; i < num_digits - 1; ++i) {\n    int digit = numerator.divmod_assign(denominator);\n    buf[i] = static_cast<char>('0' + digit);\n    numerator *= 10;\n  }\n  int digit = numerator.divmod_assign(denominator);\n  auto result = add_compare(numerator, numerator, denominator);\n  if (result > 0 || (result == 0 && (digit % 2) != 0)) {\n    if (digit == 9) {\n      const auto overflow = '0' + 10;\n      buf[num_digits - 1] = overflow;\n      // Propagate the carry.\n      for (int i = num_digits - 1; i > 0 && buf[i] == overflow; --i) {\n        buf[i] = '0';\n        ++buf[i - 1];\n      }\n      if (buf[0] == overflow) {\n        buf[0] = '1';\n        if ((flags & dragon::fixed) != 0)\n          buf.push_back('0');\n        else\n          ++exp10;\n      }\n      return;\n    }\n    ++digit;\n  }\n  buf[num_digits - 1] = static_cast<char>('0' + digit);\n}\n\n// Formats a floating-point number using the hexfloat format.\ntemplate <typename Float, FMT_ENABLE_IF(!is_double_double<Float>::value)>\nFMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs,\n                                     buffer<char>& buf) {\n  // float is passed as double to reduce the number of instantiations and to\n  // simplify implementation.\n  static_assert(!std::is_same<Float, float>::value, \"\");\n\n  using info = dragonbox::float_info<Float>;\n\n  // Assume Float is in the format [sign][exponent][significand].\n  using carrier_uint = typename info::carrier_uint;\n\n  const auto num_float_significand_bits = detail::num_significand_bits<Float>();\n\n  basic_fp<carrier_uint> f(value);\n  f.e += num_float_significand_bits;\n  if (!has_implicit_bit<Float>()) --f.e;\n\n  const auto num_fraction_bits =\n      num_float_significand_bits + (has_implicit_bit<Float>() ? 1 : 0);\n  const auto num_xdigits = (num_fraction_bits + 3) / 4;\n\n  const auto leading_shift = ((num_xdigits - 1) * 4);\n  const auto leading_mask = carrier_uint(0xF) << leading_shift;\n  const auto leading_xdigit =\n      static_cast<uint32_t>((f.f & leading_mask) >> leading_shift);\n  if (leading_xdigit > 1) f.e -= (32 - countl_zero(leading_xdigit) - 1);\n\n  int print_xdigits = num_xdigits - 1;\n  if (specs.precision >= 0 && print_xdigits > specs.precision) {\n    const int shift = ((print_xdigits - specs.precision - 1) * 4);\n    const auto mask = carrier_uint(0xF) << shift;\n    const auto v = static_cast<uint32_t>((f.f & mask) >> shift);\n\n    if (v >= 8) {\n      const auto inc = carrier_uint(1) << (shift + 4);\n      f.f += inc;\n      f.f &= ~(inc - 1);\n    }\n\n    // Check long double overflow\n    if (!has_implicit_bit<Float>()) {\n      const auto implicit_bit = carrier_uint(1) << num_float_significand_bits;\n      if ((f.f & implicit_bit) == implicit_bit) {\n        f.f >>= 4;\n        f.e += 4;\n      }\n    }\n\n    print_xdigits = specs.precision;\n  }\n\n  char xdigits[num_bits<carrier_uint>() / 4];\n  detail::fill_n(xdigits, sizeof(xdigits), '0');\n  format_base2e(4, xdigits, f.f, num_xdigits, specs.upper());\n\n  // Remove zero tail\n  while (print_xdigits > 0 && xdigits[print_xdigits] == '0') --print_xdigits;\n\n  buf.push_back('0');\n  buf.push_back(specs.upper() ? 'X' : 'x');\n  buf.push_back(xdigits[0]);\n  if (specs.alt() || print_xdigits > 0 || print_xdigits < specs.precision)\n    buf.push_back('.');\n  buf.append(xdigits + 1, xdigits + 1 + print_xdigits);\n  for (; print_xdigits < specs.precision; ++print_xdigits) buf.push_back('0');\n\n  buf.push_back(specs.upper() ? 'P' : 'p');\n\n  uint32_t abs_e;\n  if (f.e < 0) {\n    buf.push_back('-');\n    abs_e = static_cast<uint32_t>(-f.e);\n  } else {\n    buf.push_back('+');\n    abs_e = static_cast<uint32_t>(f.e);\n  }\n  format_decimal<char>(appender(buf), abs_e, detail::count_digits(abs_e));\n}\n\ntemplate <typename Float, FMT_ENABLE_IF(is_double_double<Float>::value)>\nFMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs,\n                                     buffer<char>& buf) {\n  format_hexfloat(static_cast<double>(value), specs, buf);\n}\n\nconstexpr auto fractional_part_rounding_thresholds(int index) -> uint32_t {\n  // For checking rounding thresholds.\n  // The kth entry is chosen to be the smallest integer such that the\n  // upper 32-bits of 10^(k+1) times it is strictly bigger than 5 * 10^k.\n  // It is equal to ceil(2^31 + 2^32/10^(k + 1)).\n  // These are stored in a string literal because we cannot have static arrays\n  // in constexpr functions and non-static ones are poorly optimized.\n  return U\"\\x9999999a\\x828f5c29\\x80418938\\x80068db9\\x8000a7c6\\x800010c7\"\n         U\"\\x800001ae\\x8000002b\"[index];\n}\n\ntemplate <typename Float>\nFMT_CONSTEXPR20 auto format_float(Float value, int precision,\n                                  const format_specs& specs, bool binary32,\n                                  buffer<char>& buf) -> int {\n  // float is passed as double to reduce the number of instantiations.\n  static_assert(!std::is_same<Float, float>::value, \"\");\n  auto converted_value = convert_float(value);\n\n  const bool fixed = specs.type() == presentation_type::fixed;\n  if (value == 0) {\n    if (precision <= 0 || !fixed) {\n      buf.push_back('0');\n      return 0;\n    }\n    buf.try_resize(to_unsigned(precision));\n    fill_n(buf.data(), precision, '0');\n    return -precision;\n  }\n\n  int exp = 0;\n  bool use_dragon = true;\n  unsigned dragon_flags = 0;\n  if (!is_fast_float<Float>() || is_constant_evaluated()) {\n    const auto inv_log2_10 = 0.3010299956639812;  // 1 / log2(10)\n    using info = dragonbox::float_info<decltype(converted_value)>;\n    const auto f = basic_fp<typename info::carrier_uint>(converted_value);\n    // Compute exp, an approximate power of 10, such that\n    //   10^(exp - 1) <= value < 10^exp or 10^exp <= value < 10^(exp + 1).\n    // This is based on log10(value) == log2(value) / log2(10) and approximation\n    // of log2(value) by e + num_fraction_bits idea from double-conversion.\n    auto e = (f.e + count_digits<1>(f.f) - 1) * inv_log2_10 - 1e-10;\n    exp = static_cast<int>(e);\n    if (e > exp) ++exp;  // Compute ceil.\n    dragon_flags = dragon::fixup;\n  } else {\n    // Extract significand bits and exponent bits.\n    using info = dragonbox::float_info<double>;\n    auto br = bit_cast<uint64_t>(static_cast<double>(value));\n\n    const uint64_t significand_mask =\n        (static_cast<uint64_t>(1) << num_significand_bits<double>()) - 1;\n    uint64_t significand = (br & significand_mask);\n    int exponent = static_cast<int>((br & exponent_mask<double>()) >>\n                                    num_significand_bits<double>());\n\n    if (exponent != 0) {  // Check if normal.\n      exponent -= exponent_bias<double>() + num_significand_bits<double>();\n      significand |=\n          (static_cast<uint64_t>(1) << num_significand_bits<double>());\n      significand <<= 1;\n    } else {\n      // Normalize subnormal inputs.\n      FMT_ASSERT(significand != 0, \"zeros should not appear here\");\n      int shift = countl_zero(significand);\n      FMT_ASSERT(shift >= num_bits<uint64_t>() - num_significand_bits<double>(),\n                 \"\");\n      shift -= (num_bits<uint64_t>() - num_significand_bits<double>() - 2);\n      exponent = (std::numeric_limits<double>::min_exponent -\n                  num_significand_bits<double>()) -\n                 shift;\n      significand <<= shift;\n    }\n\n    // Compute the first several nonzero decimal significand digits.\n    // We call the number we get the first segment.\n    const int k = info::kappa - dragonbox::floor_log10_pow2(exponent);\n    exp = -k;\n    const int beta = exponent + dragonbox::floor_log2_pow10(k);\n    uint64_t first_segment;\n    bool has_more_segments;\n    int digits_in_the_first_segment;\n    {\n      const auto r = dragonbox::umul192_upper128(\n          significand << beta, dragonbox::get_cached_power(k));\n      first_segment = r.high();\n      has_more_segments = r.low() != 0;\n\n      // The first segment can have 18 ~ 19 digits.\n      if (first_segment >= 1000000000000000000ULL) {\n        digits_in_the_first_segment = 19;\n      } else {\n        // When it is of 18-digits, we align it to 19-digits by adding a bogus\n        // zero at the end.\n        digits_in_the_first_segment = 18;\n        first_segment *= 10;\n      }\n    }\n\n    // Compute the actual number of decimal digits to print.\n    if (fixed) adjust_precision(precision, exp + digits_in_the_first_segment);\n\n    // Use Dragon4 only when there might be not enough digits in the first\n    // segment.\n    if (digits_in_the_first_segment > precision) {\n      use_dragon = false;\n\n      if (precision <= 0) {\n        exp += digits_in_the_first_segment;\n\n        if (precision < 0) {\n          // Nothing to do, since all we have are just leading zeros.\n          buf.try_resize(0);\n        } else {\n          // We may need to round-up.\n          buf.try_resize(1);\n          if ((first_segment | static_cast<uint64_t>(has_more_segments)) >\n              5000000000000000000ULL) {\n            buf[0] = '1';\n          } else {\n            buf[0] = '0';\n          }\n        }\n      }  // precision <= 0\n      else {\n        exp += digits_in_the_first_segment - precision;\n\n        // When precision > 0, we divide the first segment into three\n        // subsegments, each with 9, 9, and 0 ~ 1 digits so that each fits\n        // in 32-bits which usually allows faster calculation than in\n        // 64-bits. Since some compiler (e.g. MSVC) doesn't know how to optimize\n        // division-by-constant for large 64-bit divisors, we do it here\n        // manually. The magic number 7922816251426433760 below is equal to\n        // ceil(2^(64+32) / 10^10).\n        const uint32_t first_subsegment = static_cast<uint32_t>(\n            dragonbox::umul128_upper64(first_segment, 7922816251426433760ULL) >>\n            32);\n        const uint64_t second_third_subsegments =\n            first_segment - first_subsegment * 10000000000ULL;\n\n        uint64_t prod;\n        uint32_t digits;\n        bool should_round_up;\n        int number_of_digits_to_print = min_of(precision, 9);\n\n        // Print a 9-digits subsegment, either the first or the second.\n        auto print_subsegment = [&](uint32_t subsegment, char* buffer) {\n          int number_of_digits_printed = 0;\n\n          // If we want to print an odd number of digits from the subsegment,\n          if ((number_of_digits_to_print & 1) != 0) {\n            // Convert to 64-bit fixed-point fractional form with 1-digit\n            // integer part. The magic number 720575941 is a good enough\n            // approximation of 2^(32 + 24) / 10^8; see\n            // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case\n            // for details.\n            prod = ((subsegment * static_cast<uint64_t>(720575941)) >> 24) + 1;\n            digits = static_cast<uint32_t>(prod >> 32);\n            *buffer = static_cast<char>('0' + digits);\n            number_of_digits_printed++;\n          }\n          // If we want to print an even number of digits from the\n          // first_subsegment,\n          else {\n            // Convert to 64-bit fixed-point fractional form with 2-digits\n            // integer part. The magic number 450359963 is a good enough\n            // approximation of 2^(32 + 20) / 10^7; see\n            // https://jk-jeon.github.io/posts/2022/12/fixed-precision-formatting/#fixed-length-case\n            // for details.\n            prod = ((subsegment * static_cast<uint64_t>(450359963)) >> 20) + 1;\n            digits = static_cast<uint32_t>(prod >> 32);\n            write2digits(buffer, digits);\n            number_of_digits_printed += 2;\n          }\n\n          // Print all digit pairs.\n          while (number_of_digits_printed < number_of_digits_to_print) {\n            prod = static_cast<uint32_t>(prod) * static_cast<uint64_t>(100);\n            digits = static_cast<uint32_t>(prod >> 32);\n            write2digits(buffer + number_of_digits_printed, digits);\n            number_of_digits_printed += 2;\n          }\n        };\n\n        // Print first subsegment.\n        print_subsegment(first_subsegment, buf.data());\n\n        // Perform rounding if the first subsegment is the last subsegment to\n        // print.\n        if (precision <= 9) {\n          // Rounding inside the subsegment.\n          // We round-up if:\n          //  - either the fractional part is strictly larger than 1/2, or\n          //  - the fractional part is exactly 1/2 and the last digit is odd.\n          // We rely on the following observations:\n          //  - If fractional_part >= threshold, then the fractional part is\n          //    strictly larger than 1/2.\n          //  - If the MSB of fractional_part is set, then the fractional part\n          //    must be at least 1/2.\n          //  - When the MSB of fractional_part is set, either\n          //    second_third_subsegments being nonzero or has_more_segments\n          //    being true means there are further digits not printed, so the\n          //    fractional part is strictly larger than 1/2.\n          if (precision < 9) {\n            uint32_t fractional_part = static_cast<uint32_t>(prod);\n            should_round_up =\n                fractional_part >= fractional_part_rounding_thresholds(\n                                       8 - number_of_digits_to_print) ||\n                ((fractional_part >> 31) &\n                 ((digits & 1) | (second_third_subsegments != 0) |\n                  has_more_segments)) != 0;\n          }\n          // Rounding at the subsegment boundary.\n          // In this case, the fractional part is at least 1/2 if and only if\n          // second_third_subsegments >= 5000000000ULL, and is strictly larger\n          // than 1/2 if we further have either second_third_subsegments >\n          // 5000000000ULL or has_more_segments == true.\n          else {\n            should_round_up = second_third_subsegments > 5000000000ULL ||\n                              (second_third_subsegments == 5000000000ULL &&\n                               ((digits & 1) != 0 || has_more_segments));\n          }\n        }\n        // Otherwise, print the second subsegment.\n        else {\n          // Compilers are not aware of how to leverage the maximum value of\n          // second_third_subsegments to find out a better magic number which\n          // allows us to eliminate an additional shift. 1844674407370955162 =\n          // ceil(2^64/10) < ceil(2^64*(10^9/(10^10 - 1))).\n          const uint32_t second_subsegment =\n              static_cast<uint32_t>(dragonbox::umul128_upper64(\n                  second_third_subsegments, 1844674407370955162ULL));\n          const uint32_t third_subsegment =\n              static_cast<uint32_t>(second_third_subsegments) -\n              second_subsegment * 10;\n\n          number_of_digits_to_print = precision - 9;\n          print_subsegment(second_subsegment, buf.data() + 9);\n\n          // Rounding inside the subsegment.\n          if (precision < 18) {\n            // The condition third_subsegment != 0 implies that the segment was\n            // of 19 digits, so in this case the third segment should be\n            // consisting of a genuine digit from the input.\n            uint32_t fractional_part = static_cast<uint32_t>(prod);\n            should_round_up =\n                fractional_part >= fractional_part_rounding_thresholds(\n                                       8 - number_of_digits_to_print) ||\n                ((fractional_part >> 31) &\n                 ((digits & 1) | (third_subsegment != 0) |\n                  has_more_segments)) != 0;\n          }\n          // Rounding at the subsegment boundary.\n          else {\n            // In this case, the segment must be of 19 digits, thus\n            // the third subsegment should be consisting of a genuine digit from\n            // the input.\n            should_round_up = third_subsegment > 5 ||\n                              (third_subsegment == 5 &&\n                               ((digits & 1) != 0 || has_more_segments));\n          }\n        }\n\n        // Round-up if necessary.\n        if (should_round_up) {\n          ++buf[precision - 1];\n          for (int i = precision - 1; i > 0 && buf[i] > '9'; --i) {\n            buf[i] = '0';\n            ++buf[i - 1];\n          }\n          if (buf[0] > '9') {\n            buf[0] = '1';\n            if (fixed)\n              buf[precision++] = '0';\n            else\n              ++exp;\n          }\n        }\n        buf.try_resize(to_unsigned(precision));\n      }\n    }  // if (digits_in_the_first_segment > precision)\n    else {\n      // Adjust the exponent for its use in Dragon4.\n      exp += digits_in_the_first_segment - 1;\n    }\n  }\n  if (use_dragon) {\n    auto f = basic_fp<uint128_t>();\n    bool is_predecessor_closer = binary32 ? f.assign(static_cast<float>(value))\n                                          : f.assign(converted_value);\n    if (is_predecessor_closer) dragon_flags |= dragon::predecessor_closer;\n    if (fixed) dragon_flags |= dragon::fixed;\n    // Limit precision to the maximum possible number of significant digits in\n    // an IEEE754 double because we don't need to generate zeros.\n    const int max_double_digits = 767;\n    if (precision > max_double_digits) precision = max_double_digits;\n    format_dragon(f, dragon_flags, precision, buf, exp);\n  }\n  if (!fixed && !specs.alt()) {\n    // Remove trailing zeros.\n    auto num_digits = buf.size();\n    while (num_digits > 0 && buf[num_digits - 1] == '0') {\n      --num_digits;\n      ++exp;\n    }\n    buf.try_resize(num_digits);\n  }\n  return exp;\n}\n\ntemplate <typename Char, typename OutputIt, typename T>\nFMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs,\n                                 locale_ref loc) -> OutputIt {\n  // Use signbit because value < 0 is false for NaN.\n  sign s = detail::signbit(value) ? sign::minus : specs.sign();\n\n  if (!detail::isfinite(value))\n    return write_nonfinite<Char>(out, detail::isnan(value), specs, s);\n\n  if (specs.align() == align::numeric && s != sign::none) {\n    *out++ = detail::getsign<Char>(s);\n    s = sign::none;\n    if (specs.width != 0) --specs.width;\n  }\n\n  int precision = specs.precision;\n  if (precision < 0) {\n    if (specs.type() != presentation_type::none) {\n      precision = 6;\n    } else if (is_fast_float<T>::value && !is_constant_evaluated()) {\n      // Use Dragonbox for the shortest format.\n      using floaty = conditional_t<sizeof(T) >= sizeof(double), double, float>;\n      auto dec = dragonbox::to_decimal(static_cast<floaty>(value));\n      return write_float<Char>(out, dec, specs, s, loc);\n    }\n  }\n\n  memory_buffer buffer;\n  if (specs.type() == presentation_type::hexfloat) {\n    if (s != sign::none) buffer.push_back(detail::getsign<char>(s));\n    format_hexfloat(convert_float(value), specs, buffer);\n    return write_bytes<Char, align::right>(out, {buffer.data(), buffer.size()},\n                                           specs);\n  }\n\n  if (specs.type() == presentation_type::exp) {\n    if (precision == max_value<int>())\n      report_error(\"number is too big\");\n    else\n      ++precision;\n    if (specs.precision != 0) specs.set_alt();\n  } else if (specs.type() == presentation_type::fixed) {\n    if (specs.precision != 0) specs.set_alt();\n  } else if (precision == 0) {\n    precision = 1;\n  }\n  int exp = format_float(convert_float(value), precision, specs,\n                         std::is_same<T, float>(), buffer);\n\n  specs.precision = precision;\n  auto f = big_decimal_fp{buffer.data(), static_cast<int>(buffer.size()), exp};\n  return write_float<Char>(out, f, specs, s, loc);\n}\n\ntemplate <typename Char, typename OutputIt, typename T,\n          FMT_ENABLE_IF(is_floating_point<T>::value)>\nFMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs,\n                           locale_ref loc = {}) -> OutputIt {\n  return specs.localized() && write_loc(out, value, specs, loc)\n             ? out\n             : write_float<Char>(out, value, specs, loc);\n}\n\ntemplate <typename Char, typename OutputIt, typename T,\n          FMT_ENABLE_IF(is_fast_float<T>::value)>\nFMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt {\n  if (is_constant_evaluated()) return write<Char>(out, value, format_specs());\n\n  auto s = detail::signbit(value) ? sign::minus : sign::none;\n\n  constexpr auto specs = format_specs();\n  using floaty = conditional_t<sizeof(T) >= sizeof(double), double, float>;\n  using floaty_uint = typename dragonbox::float_info<floaty>::carrier_uint;\n  floaty_uint mask = exponent_mask<floaty>();\n  if ((bit_cast<floaty_uint>(value) & mask) == mask)\n    return write_nonfinite<Char>(out, std::isnan(value), specs, s);\n\n  auto dec = dragonbox::to_decimal(static_cast<floaty>(value));\n  return write_float<Char>(out, dec, specs, s, {});\n}\n\ntemplate <typename Char, typename OutputIt, typename T,\n          FMT_ENABLE_IF(is_floating_point<T>::value &&\n                        !is_fast_float<T>::value)>\ninline auto write(OutputIt out, T value) -> OutputIt {\n  return write<Char>(out, value, format_specs());\n}\n\ntemplate <typename Char, typename OutputIt>\nauto write(OutputIt out, monostate, format_specs = {}, locale_ref = {})\n    -> OutputIt {\n  FMT_ASSERT(false, \"\");\n  return out;\n}\n\ntemplate <typename Char, typename OutputIt>\nFMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> value)\n    -> OutputIt {\n  return copy_noinline<Char>(value.begin(), value.end(), out);\n}\n\ntemplate <typename Char, typename OutputIt, typename T,\n          FMT_ENABLE_IF(has_to_string_view<T>::value)>\nconstexpr auto write(OutputIt out, const T& value) -> OutputIt {\n  return write<Char>(out, to_string_view(value));\n}\n\n// FMT_ENABLE_IF() condition separated to workaround an MSVC bug.\ntemplate <\n    typename Char, typename OutputIt, typename T,\n    bool check = std::is_enum<T>::value && !std::is_same<T, Char>::value &&\n                 mapped_type_constant<T, Char>::value != type::custom_type,\n    FMT_ENABLE_IF(check)>\nFMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt {\n  return write<Char>(out, static_cast<underlying_t<T>>(value));\n}\n\ntemplate <typename Char, typename OutputIt, typename T,\n          FMT_ENABLE_IF(std::is_same<T, bool>::value)>\nFMT_CONSTEXPR auto write(OutputIt out, T value, const format_specs& specs = {},\n                         locale_ref = {}) -> OutputIt {\n  return specs.type() != presentation_type::none &&\n                 specs.type() != presentation_type::string\n             ? write<Char>(out, value ? 1 : 0, specs, {})\n             : write_bytes<Char>(out, value ? \"true\" : \"false\", specs);\n}\n\ntemplate <typename Char, typename OutputIt>\nFMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt {\n  auto it = reserve(out, 1);\n  *it++ = value;\n  return base_iterator(out, it);\n}\n\ntemplate <typename Char, typename OutputIt>\nFMT_CONSTEXPR20 auto write(OutputIt out, const Char* value) -> OutputIt {\n  if (value) return write(out, basic_string_view<Char>(value));\n  report_error(\"string pointer is null\");\n  return out;\n}\n\ntemplate <typename Char, typename OutputIt, typename T,\n          FMT_ENABLE_IF(std::is_same<T, void>::value)>\nauto write(OutputIt out, const T* value, const format_specs& specs = {},\n           locale_ref = {}) -> OutputIt {\n  return write_ptr<Char>(out, bit_cast<uintptr_t>(value), &specs);\n}\n\ntemplate <typename Char, typename OutputIt, typename T,\n          FMT_ENABLE_IF(mapped_type_constant<T, Char>::value ==\n                            type::custom_type &&\n                        !std::is_fundamental<T>::value)>\nFMT_CONSTEXPR auto write(OutputIt out, const T& value) -> OutputIt {\n  auto f = formatter<T, Char>();\n  auto parse_ctx = parse_context<Char>({});\n  f.parse(parse_ctx);\n  auto ctx = basic_format_context<OutputIt, Char>(out, {}, {});\n  return f.format(value, ctx);\n}\n\ntemplate <typename T>\nusing is_builtin =\n    bool_constant<std::is_same<T, int>::value || FMT_BUILTIN_TYPES>;\n\n// An argument visitor that formats the argument and writes it via the output\n// iterator. It's a class and not a generic lambda for compatibility with C++11.\ntemplate <typename Char> struct default_arg_formatter {\n  using context = buffered_context<Char>;\n\n  basic_appender<Char> out;\n\n  void operator()(monostate) { report_error(\"argument not found\"); }\n\n  template <typename T, FMT_ENABLE_IF(is_builtin<T>::value)>\n  void operator()(T value) {\n    write<Char>(out, value);\n  }\n\n  template <typename T, FMT_ENABLE_IF(!is_builtin<T>::value)>\n  void operator()(T) {\n    FMT_ASSERT(false, \"\");\n  }\n\n  void operator()(typename basic_format_arg<context>::handle h) {\n    // Use a null locale since the default format must be unlocalized.\n    auto parse_ctx = parse_context<Char>({});\n    auto format_ctx = context(out, {}, {});\n    h.format(parse_ctx, format_ctx);\n  }\n};\n\ntemplate <typename Char> struct arg_formatter {\n  basic_appender<Char> out;\n  const format_specs& specs;\n  FMT_NO_UNIQUE_ADDRESS locale_ref locale;\n\n  template <typename T, FMT_ENABLE_IF(is_builtin<T>::value)>\n  FMT_CONSTEXPR FMT_INLINE void operator()(T value) {\n    detail::write<Char>(out, value, specs, locale);\n  }\n\n  template <typename T, FMT_ENABLE_IF(!is_builtin<T>::value)>\n  void operator()(T) {\n    FMT_ASSERT(false, \"\");\n  }\n\n  void operator()(typename basic_format_arg<buffered_context<Char>>::handle) {\n    // User-defined types are handled separately because they require access\n    // to the parse context.\n  }\n};\n\nstruct dynamic_spec_getter {\n  template <typename T, FMT_ENABLE_IF(is_integer<T>::value)>\n  FMT_CONSTEXPR auto operator()(T value) -> unsigned long long {\n    return is_negative(value) ? ~0ull : static_cast<unsigned long long>(value);\n  }\n\n  template <typename T, FMT_ENABLE_IF(!is_integer<T>::value)>\n  FMT_CONSTEXPR auto operator()(T) -> unsigned long long {\n    report_error(\"width/precision is not integer\");\n    return 0;\n  }\n};\n\ntemplate <typename Context, typename ID>\nFMT_CONSTEXPR auto get_arg(Context& ctx, ID id) -> basic_format_arg<Context> {\n  auto arg = ctx.arg(id);\n  if (!arg) report_error(\"argument not found\");\n  return arg;\n}\n\ntemplate <typename Context>\nFMT_CONSTEXPR int get_dynamic_spec(\n    arg_id_kind kind, const arg_ref<typename Context::char_type>& ref,\n    Context& ctx) {\n  FMT_ASSERT(kind != arg_id_kind::none, \"\");\n  auto arg =\n      kind == arg_id_kind::index ? ctx.arg(ref.index) : ctx.arg(ref.name);\n  if (!arg) report_error(\"argument not found\");\n  unsigned long long value = arg.visit(dynamic_spec_getter());\n  if (value > to_unsigned(max_value<int>()))\n    report_error(\"width/precision is out of range\");\n  return static_cast<int>(value);\n}\n\ntemplate <typename Context>\nFMT_CONSTEXPR void handle_dynamic_spec(\n    arg_id_kind kind, int& value,\n    const arg_ref<typename Context::char_type>& ref, Context& ctx) {\n  if (kind != arg_id_kind::none) value = get_dynamic_spec(kind, ref, ctx);\n}\n\n#if FMT_USE_NONTYPE_TEMPLATE_ARGS\ntemplate <typename T, typename Char, size_t N,\n          fmt::detail::fixed_string<Char, N> Str>\nstruct static_named_arg : view {\n  static constexpr auto name = Str.data;\n\n  const T& value;\n  static_named_arg(const T& v) : value(v) {}\n};\n\ntemplate <typename T, typename Char, size_t N,\n          fmt::detail::fixed_string<Char, N> Str>\nstruct is_named_arg<static_named_arg<T, Char, N, Str>> : std::true_type {};\n\ntemplate <typename T, typename Char, size_t N,\n          fmt::detail::fixed_string<Char, N> Str>\nstruct is_static_named_arg<static_named_arg<T, Char, N, Str>> : std::true_type {\n};\n\ntemplate <typename Char, size_t N, fmt::detail::fixed_string<Char, N> Str>\nstruct udl_arg {\n  template <typename T> auto operator=(T&& value) const {\n    return static_named_arg<T, Char, N, Str>(std::forward<T>(value));\n  }\n};\n#else\ntemplate <typename Char> struct udl_arg {\n  const Char* str;\n\n  template <typename T> auto operator=(T&& value) const -> named_arg<Char, T> {\n    return {str, std::forward<T>(value)};\n  }\n};\n#endif  // FMT_USE_NONTYPE_TEMPLATE_ARGS\n\ntemplate <typename Char> struct format_handler {\n  parse_context<Char> parse_ctx;\n  buffered_context<Char> ctx;\n\n  void on_text(const Char* begin, const Char* end) {\n    copy_noinline<Char>(begin, end, ctx.out());\n  }\n\n  FMT_CONSTEXPR auto on_arg_id() -> int { return parse_ctx.next_arg_id(); }\n  FMT_CONSTEXPR auto on_arg_id(int id) -> int {\n    parse_ctx.check_arg_id(id);\n    return id;\n  }\n  FMT_CONSTEXPR auto on_arg_id(basic_string_view<Char> id) -> int {\n    parse_ctx.check_arg_id(id);\n    int arg_id = ctx.arg_id(id);\n    if (arg_id < 0) report_error(\"argument not found\");\n    return arg_id;\n  }\n\n  FMT_INLINE void on_replacement_field(int id, const Char*) {\n    ctx.arg(id).visit(default_arg_formatter<Char>{ctx.out()});\n  }\n\n  auto on_format_specs(int id, const Char* begin, const Char* end)\n      -> const Char* {\n    auto arg = get_arg(ctx, id);\n    // Not using a visitor for custom types gives better codegen.\n    if (arg.format_custom(begin, parse_ctx, ctx)) return parse_ctx.begin();\n\n    auto specs = dynamic_format_specs<Char>();\n    begin = parse_format_specs(begin, end, specs, parse_ctx, arg.type());\n    if (specs.dynamic()) {\n      handle_dynamic_spec(specs.dynamic_width(), specs.width, specs.width_ref,\n                          ctx);\n      handle_dynamic_spec(specs.dynamic_precision(), specs.precision,\n                          specs.precision_ref, ctx);\n    }\n\n    arg.visit(arg_formatter<Char>{ctx.out(), specs, ctx.locale()});\n    return begin;\n  }\n\n  FMT_NORETURN void on_error(const char* message) { report_error(message); }\n};\n\nusing format_func = void (*)(detail::buffer<char>&, int, const char*);\nFMT_API void do_report_error(format_func func, int error_code,\n                             const char* message) noexcept;\n\nFMT_API void format_error_code(buffer<char>& out, int error_code,\n                               string_view message) noexcept;\n\ntemplate <typename T, typename Char, type TYPE>\ntemplate <typename FormatContext>\nFMT_CONSTEXPR auto native_formatter<T, Char, TYPE>::format(\n    const T& val, FormatContext& ctx) const -> decltype(ctx.out()) {\n  if (!specs_.dynamic())\n    return write<Char>(ctx.out(), val, specs_, ctx.locale());\n  auto specs = format_specs(specs_);\n  handle_dynamic_spec(specs.dynamic_width(), specs.width, specs_.width_ref,\n                      ctx);\n  handle_dynamic_spec(specs.dynamic_precision(), specs.precision,\n                      specs_.precision_ref, ctx);\n  return write<Char>(ctx.out(), val, specs, ctx.locale());\n}\n\n// DEPRECATED! https://github.com/fmtlib/fmt/issues/4292.\ntemplate <typename T, typename Enable = void>\nstruct is_locale : std::false_type {};\ntemplate <typename T>\nstruct is_locale<T, void_t<decltype(T::classic())>> : std::true_type {};\n\n// DEPRECATED!\ntemplate <typename Char = char> struct vformat_args {\n  using type = basic_format_args<buffered_context<Char>>;\n};\ntemplate <> struct vformat_args<char> {\n  using type = format_args;\n};\n\ntemplate <typename Char>\nvoid vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt,\n                typename vformat_args<Char>::type args, locale_ref loc = {}) {\n  auto out = basic_appender<Char>(buf);\n  parse_format_string(\n      fmt, format_handler<Char>{parse_context<Char>(fmt), {out, args, loc}});\n}\n}  // namespace detail\n\nFMT_BEGIN_EXPORT\n\n// A generic formatting context with custom output iterator and character\n// (code unit) support. Char is the format string code unit type which can be\n// different from OutputIt::value_type.\ntemplate <typename OutputIt, typename Char> class generic_context {\n private:\n  OutputIt out_;\n  basic_format_args<generic_context> args_;\n  detail::locale_ref loc_;\n\n public:\n  using char_type = Char;\n  using iterator = OutputIt;\n  using parse_context_type FMT_DEPRECATED = parse_context<Char>;\n  template <typename T>\n  using formatter_type FMT_DEPRECATED = formatter<T, Char>;\n  enum { builtin_types = FMT_BUILTIN_TYPES };\n\n  constexpr generic_context(OutputIt out,\n                            basic_format_args<generic_context> args,\n                            detail::locale_ref loc = {})\n      : out_(out), args_(args), loc_(loc) {}\n  generic_context(generic_context&&) = default;\n  generic_context(const generic_context&) = delete;\n  void operator=(const generic_context&) = delete;\n\n  constexpr auto arg(int id) const -> basic_format_arg<generic_context> {\n    return args_.get(id);\n  }\n  auto arg(basic_string_view<Char> name) const\n      -> basic_format_arg<generic_context> {\n    return args_.get(name);\n  }\n  constexpr auto arg_id(basic_string_view<Char> name) const -> int {\n    return args_.get_id(name);\n  }\n\n  constexpr auto out() const -> iterator { return out_; }\n\n  void advance_to(iterator it) {\n    if (!detail::is_back_insert_iterator<iterator>()) out_ = it;\n  }\n\n  constexpr auto locale() const -> detail::locale_ref { return loc_; }\n};\n\nclass loc_value {\n private:\n  basic_format_arg<context> value_;\n\n public:\n  template <typename T, FMT_ENABLE_IF(!detail::is_float128<T>::value)>\n  loc_value(T value) : value_(value) {}\n\n  template <typename T, FMT_ENABLE_IF(detail::is_float128<T>::value)>\n  loc_value(T) {}\n\n  template <typename Visitor> auto visit(Visitor&& vis) -> decltype(vis(0)) {\n    return value_.visit(vis);\n  }\n};\n\n// A locale facet that formats values in UTF-8.\n// It is parameterized on the locale to avoid the heavy <locale> include.\ntemplate <typename Locale> class format_facet : public Locale::facet {\n private:\n  std::string separator_;\n  std::string grouping_;\n  std::string decimal_point_;\n\n protected:\n  virtual auto do_put(appender out, loc_value val,\n                      const format_specs& specs) const -> bool;\n\n public:\n  static FMT_API typename Locale::id id;\n\n  explicit format_facet(Locale& loc);\n  explicit format_facet(string_view sep = \"\", std::string grouping = \"\\3\",\n                        std::string decimal_point = \".\")\n      : separator_(sep.data(), sep.size()),\n        grouping_(grouping),\n        decimal_point_(decimal_point) {}\n\n  auto put(appender out, loc_value val, const format_specs& specs) const\n      -> bool {\n    return do_put(out, val, specs);\n  }\n};\n\n#define FMT_FORMAT_AS(Type, Base)                                   \\\n  template <typename Char>                                          \\\n  struct formatter<Type, Char> : formatter<Base, Char> {            \\\n    template <typename FormatContext>                               \\\n    FMT_CONSTEXPR auto format(Type value, FormatContext& ctx) const \\\n        -> decltype(ctx.out()) {                                    \\\n      return formatter<Base, Char>::format(value, ctx);             \\\n    }                                                               \\\n  }\n\nFMT_FORMAT_AS(signed char, int);\nFMT_FORMAT_AS(unsigned char, unsigned);\nFMT_FORMAT_AS(short, int);\nFMT_FORMAT_AS(unsigned short, unsigned);\nFMT_FORMAT_AS(long, detail::long_type);\nFMT_FORMAT_AS(unsigned long, detail::ulong_type);\nFMT_FORMAT_AS(Char*, const Char*);\nFMT_FORMAT_AS(detail::std_string_view<Char>, basic_string_view<Char>);\nFMT_FORMAT_AS(std::nullptr_t, const void*);\nFMT_FORMAT_AS(void*, const void*);\n\ntemplate <typename Char, size_t N>\nstruct formatter<Char[N], Char> : formatter<basic_string_view<Char>, Char> {};\n\ntemplate <typename Char, typename Traits, typename Allocator>\nclass formatter<std::basic_string<Char, Traits, Allocator>, Char>\n    : public formatter<basic_string_view<Char>, Char> {};\n\ntemplate <int N, typename Char>\nstruct formatter<detail::bitint<N>, Char> : formatter<long long, Char> {};\ntemplate <int N, typename Char>\nstruct formatter<detail::ubitint<N>, Char>\n    : formatter<unsigned long long, Char> {};\n\ntemplate <typename Char>\nstruct formatter<detail::float128, Char>\n    : detail::native_formatter<detail::float128, Char,\n                               detail::type::float_type> {};\n\ntemplate <typename T, typename Char>\nstruct formatter<T, Char, void_t<detail::format_as_result<T>>>\n    : formatter<detail::format_as_result<T>, Char> {\n  template <typename FormatContext>\n  FMT_CONSTEXPR auto format(const T& value, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    auto&& val = format_as(value);  // Make an lvalue reference for format.\n    return formatter<detail::format_as_result<T>, Char>::format(val, ctx);\n  }\n};\n\n/**\n * Converts `p` to `const void*` for pointer formatting.\n *\n * **Example**:\n *\n *     auto s = fmt::format(\"{}\", fmt::ptr(p));\n */\ntemplate <typename T> auto ptr(T p) -> const void* {\n  static_assert(std::is_pointer<T>::value, \"\");\n  return detail::bit_cast<const void*>(p);\n}\n\n/**\n * Converts `e` to the underlying type.\n *\n * **Example**:\n *\n *     enum class color { red, green, blue };\n *     auto s = fmt::format(\"{}\", fmt::underlying(color::red));  // s == \"0\"\n */\ntemplate <typename Enum>\nconstexpr auto underlying(Enum e) noexcept -> underlying_t<Enum> {\n  return static_cast<underlying_t<Enum>>(e);\n}\n\nnamespace enums {\ntemplate <typename Enum, FMT_ENABLE_IF(std::is_enum<Enum>::value)>\nconstexpr auto format_as(Enum e) noexcept -> underlying_t<Enum> {\n  return static_cast<underlying_t<Enum>>(e);\n}\n}  // namespace enums\n\n#ifdef __cpp_lib_byte\ntemplate <> struct formatter<std::byte> : formatter<unsigned> {\n  static auto format_as(std::byte b) -> unsigned char {\n    return static_cast<unsigned char>(b);\n  }\n  template <typename Context>\n  auto format(std::byte b, Context& ctx) const -> decltype(ctx.out()) {\n    return formatter<unsigned>::format(format_as(b), ctx);\n  }\n};\n#endif\n\nstruct bytes {\n  string_view data;\n\n  inline explicit bytes(string_view s) : data(s) {}\n};\n\ntemplate <> struct formatter<bytes> {\n private:\n  detail::dynamic_format_specs<> specs_;\n\n public:\n  FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {\n    return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,\n                              detail::type::string_type);\n  }\n\n  template <typename FormatContext>\n  auto format(bytes b, FormatContext& ctx) const -> decltype(ctx.out()) {\n    auto specs = specs_;\n    detail::handle_dynamic_spec(specs.dynamic_width(), specs.width,\n                                specs.width_ref, ctx);\n    detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision,\n                                specs.precision_ref, ctx);\n    return detail::write_bytes<char>(ctx.out(), b.data, specs);\n  }\n};\n\n// group_digits_view is not derived from view because it copies the argument.\ntemplate <typename T> struct group_digits_view {\n  T value;\n};\n\n/**\n * Returns a view that formats an integer value using ',' as a\n * locale-independent thousands separator.\n *\n * **Example**:\n *\n *     fmt::print(\"{}\", fmt::group_digits(12345));\n *     // Output: \"12,345\"\n */\ntemplate <typename T> auto group_digits(T value) -> group_digits_view<T> {\n  return {value};\n}\n\ntemplate <typename T> struct formatter<group_digits_view<T>> : formatter<T> {\n private:\n  detail::dynamic_format_specs<> specs_;\n\n public:\n  FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {\n    return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,\n                              detail::type::int_type);\n  }\n\n  template <typename FormatContext>\n  auto format(group_digits_view<T> view, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    auto specs = specs_;\n    detail::handle_dynamic_spec(specs.dynamic_width(), specs.width,\n                                specs.width_ref, ctx);\n    detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision,\n                                specs.precision_ref, ctx);\n    auto arg = detail::make_write_int_arg(view.value, specs.sign());\n    return detail::write_int(\n        ctx.out(), static_cast<detail::uint64_or_128_t<T>>(arg.abs_value),\n        arg.prefix, specs, detail::digit_grouping<char>(\"\\3\", \",\"));\n  }\n};\n\ntemplate <typename T, typename Char> struct nested_view {\n  const formatter<T, Char>* fmt;\n  const T* value;\n};\n\ntemplate <typename T, typename Char>\nstruct formatter<nested_view<T, Char>, Char> {\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    return ctx.begin();\n  }\n  template <typename FormatContext>\n  auto format(nested_view<T, Char> view, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    return view.fmt->format(*view.value, ctx);\n  }\n};\n\ntemplate <typename T, typename Char = char> struct nested_formatter {\n private:\n  basic_specs specs_;\n  int width_;\n  formatter<T, Char> formatter_;\n\n public:\n  constexpr nested_formatter() : width_(0) {}\n\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    auto it = ctx.begin(), end = ctx.end();\n    if (it == end) return it;\n    auto specs = format_specs();\n    it = detail::parse_align(it, end, specs);\n    specs_ = specs;\n    Char c = *it;\n    auto width_ref = detail::arg_ref<Char>();\n    if ((c >= '0' && c <= '9') || c == '{') {\n      it = detail::parse_width(it, end, specs, width_ref, ctx);\n      width_ = specs.width;\n    }\n    ctx.advance_to(it);\n    return formatter_.parse(ctx);\n  }\n\n  template <typename FormatContext, typename F>\n  auto write_padded(FormatContext& ctx, F write) const -> decltype(ctx.out()) {\n    if (width_ == 0) return write(ctx.out());\n    auto buf = basic_memory_buffer<Char>();\n    write(basic_appender<Char>(buf));\n    auto specs = format_specs();\n    specs.width = width_;\n    specs.copy_fill_from(specs_);\n    specs.set_align(specs_.align());\n    return detail::write<Char>(\n        ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);\n  }\n\n  auto nested(const T& value) const -> nested_view<T, Char> {\n    return nested_view<T, Char>{&formatter_, &value};\n  }\n};\n\ninline namespace literals {\n#if FMT_USE_NONTYPE_TEMPLATE_ARGS\ntemplate <detail::fixed_string S> constexpr auto operator\"\"_a() {\n  using char_t = remove_cvref_t<decltype(*S.data)>;\n  return detail::udl_arg<char_t, sizeof(S.data) / sizeof(char_t), S>();\n}\n#else\n/**\n * User-defined literal equivalent of `fmt::arg`.\n *\n * **Example**:\n *\n *     using namespace fmt::literals;\n *     fmt::print(\"The answer is {answer}.\", \"answer\"_a=42);\n */\nconstexpr auto operator\"\"_a(const char* s, size_t) -> detail::udl_arg<char> {\n  return {s};\n}\n#endif  // FMT_USE_NONTYPE_TEMPLATE_ARGS\n}  // namespace literals\n\n/// A fast integer formatter.\nclass format_int {\n private:\n  // Buffer should be large enough to hold all digits (digits10 + 1),\n  // a sign and a null character.\n  enum { buffer_size = std::numeric_limits<unsigned long long>::digits10 + 3 };\n  mutable char buffer_[buffer_size];\n  char* str_;\n\n  template <typename UInt>\n  FMT_CONSTEXPR20 auto format_unsigned(UInt value) -> char* {\n    auto n = static_cast<detail::uint32_or_64_or_128_t<UInt>>(value);\n    return detail::do_format_decimal(buffer_, n, buffer_size - 1);\n  }\n\n  template <typename Int>\n  FMT_CONSTEXPR20 auto format_signed(Int value) -> char* {\n    auto abs_value = static_cast<detail::uint32_or_64_or_128_t<Int>>(value);\n    bool negative = value < 0;\n    if (negative) abs_value = 0 - abs_value;\n    auto begin = format_unsigned(abs_value);\n    if (negative) *--begin = '-';\n    return begin;\n  }\n\n public:\n  FMT_CONSTEXPR20 explicit format_int(int value) : str_(format_signed(value)) {}\n  FMT_CONSTEXPR20 explicit format_int(long value)\n      : str_(format_signed(value)) {}\n  FMT_CONSTEXPR20 explicit format_int(long long value)\n      : str_(format_signed(value)) {}\n  FMT_CONSTEXPR20 explicit format_int(unsigned value)\n      : str_(format_unsigned(value)) {}\n  FMT_CONSTEXPR20 explicit format_int(unsigned long value)\n      : str_(format_unsigned(value)) {}\n  FMT_CONSTEXPR20 explicit format_int(unsigned long long value)\n      : str_(format_unsigned(value)) {}\n\n  /// Returns the number of characters written to the output buffer.\n  FMT_CONSTEXPR20 auto size() const -> size_t {\n    return detail::to_unsigned(buffer_ - str_ + buffer_size - 1);\n  }\n\n  /// Returns a pointer to the output buffer content. No terminating null\n  /// character is appended.\n  FMT_CONSTEXPR20 auto data() const -> const char* { return str_; }\n\n  /// Returns a pointer to the output buffer content with terminating null\n  /// character appended.\n  FMT_CONSTEXPR20 auto c_str() const -> const char* {\n    buffer_[buffer_size - 1] = '\\0';\n    return str_;\n  }\n\n  /// Returns the content of the output buffer as an `std::string`.\n  inline auto str() const -> std::string { return {str_, size()}; }\n};\n\n#define FMT_STRING_IMPL(s, base)                                              \\\n  [] {                                                                        \\\n    /* Use the hidden visibility as a workaround for a GCC bug (#1973). */    \\\n    /* Use a macro-like name to avoid shadowing warnings. */                  \\\n    struct FMT_VISIBILITY(\"hidden\") FMT_COMPILE_STRING : base {               \\\n      using char_type = fmt::remove_cvref_t<decltype(s[0])>;                  \\\n      constexpr explicit operator fmt::basic_string_view<char_type>() const { \\\n        return fmt::detail::compile_string_to_view<char_type>(s);             \\\n      }                                                                       \\\n    };                                                                        \\\n    using FMT_STRING_VIEW =                                                   \\\n        fmt::basic_string_view<typename FMT_COMPILE_STRING::char_type>;       \\\n    fmt::detail::ignore_unused(FMT_STRING_VIEW(FMT_COMPILE_STRING()));        \\\n    return FMT_COMPILE_STRING();                                              \\\n  }()\n\n/**\n * Constructs a legacy compile-time format string from a string literal `s`.\n *\n * **Example**:\n *\n *     // A compile-time error because 'd' is an invalid specifier for strings.\n *     std::string s = fmt::format(FMT_STRING(\"{:d}\"), \"foo\");\n */\n#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string)\n\nFMT_API auto vsystem_error(int error_code, string_view fmt, format_args args)\n    -> std::system_error;\n\n/**\n * Constructs `std::system_error` with a message formatted with\n * `fmt::format(fmt, args...)`.\n * `error_code` is a system error code as given by `errno`.\n *\n * **Example**:\n *\n *     // This throws std::system_error with the description\n *     //   cannot open file 'madeup': No such file or directory\n *     // or similar (system message may vary).\n *     const char* filename = \"madeup\";\n *     FILE* file = fopen(filename, \"r\");\n *     if (!file)\n *       throw fmt::system_error(errno, \"cannot open file '{}'\", filename);\n */\ntemplate <typename... T>\nauto system_error(int error_code, format_string<T...> fmt, T&&... args)\n    -> std::system_error {\n  return vsystem_error(error_code, fmt.str, vargs<T...>{{args...}});\n}\n\n/**\n * Formats an error message for an error returned by an operating system or a\n * language runtime, for example a file opening error, and writes it to `out`.\n * The format is the same as the one used by `std::system_error(ec, message)`\n * where `ec` is `std::error_code(error_code, std::generic_category())`.\n * It is implementation-defined but normally looks like:\n *\n *     <message>: <system-message>\n *\n * where `<message>` is the passed message and `<system-message>` is the system\n * message corresponding to the error code.\n * `error_code` is a system error code as given by `errno`.\n */\nFMT_API void format_system_error(detail::buffer<char>& out, int error_code,\n                                 const char* message) noexcept;\n\n// Reports a system error without throwing an exception.\n// Can be used to report errors from destructors.\nFMT_API void report_system_error(int error_code, const char* message) noexcept;\n\ntemplate <typename Locale, FMT_ENABLE_IF(detail::is_locale<Locale>::value)>\ninline auto vformat(const Locale& loc, string_view fmt, format_args args)\n    -> std::string {\n  auto buf = memory_buffer();\n  detail::vformat_to(buf, fmt, args, detail::locale_ref(loc));\n  return {buf.data(), buf.size()};\n}\n\ntemplate <typename Locale, typename... T,\n          FMT_ENABLE_IF(detail::is_locale<Locale>::value)>\nFMT_INLINE auto format(const Locale& loc, format_string<T...> fmt, T&&... args)\n    -> std::string {\n  return vformat(loc, fmt.str, vargs<T...>{{args...}});\n}\n\ntemplate <typename OutputIt, typename Locale,\n          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>\nauto vformat_to(OutputIt out, const Locale& loc, string_view fmt,\n                format_args args) -> OutputIt {\n  auto&& buf = detail::get_buffer<char>(out);\n  detail::vformat_to(buf, fmt, args, detail::locale_ref(loc));\n  return detail::get_iterator(buf, out);\n}\n\ntemplate <typename OutputIt, typename Locale, typename... T,\n          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value&&\n                            detail::is_locale<Locale>::value)>\nFMT_INLINE auto format_to(OutputIt out, const Locale& loc,\n                          format_string<T...> fmt, T&&... args) -> OutputIt {\n  return fmt::vformat_to(out, loc, fmt.str, vargs<T...>{{args...}});\n}\n\ntemplate <typename Locale, typename... T,\n          FMT_ENABLE_IF(detail::is_locale<Locale>::value)>\nFMT_NODISCARD FMT_INLINE auto formatted_size(const Locale& loc,\n                                             format_string<T...> fmt,\n                                             T&&... args) -> size_t {\n  auto buf = detail::counting_buffer<>();\n  detail::vformat_to(buf, fmt.str, vargs<T...>{{args...}},\n                     detail::locale_ref(loc));\n  return buf.count();\n}\n\nFMT_API auto vformat(string_view fmt, format_args args) -> std::string;\n\n/**\n * Formats `args` according to specifications in `fmt` and returns the result\n * as a string.\n *\n * **Example**:\n *\n *     #include <fmt/format.h>\n *     std::string message = fmt::format(\"The answer is {}.\", 42);\n */\ntemplate <typename... T>\nFMT_NODISCARD FMT_INLINE auto format(format_string<T...> fmt, T&&... args)\n    -> std::string {\n  return vformat(fmt.str, vargs<T...>{{args...}});\n}\n\n/**\n * Converts `value` to `std::string` using the default format for type `T`.\n *\n * **Example**:\n *\n *     std::string answer = fmt::to_string(42);\n */\ntemplate <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>\nFMT_NODISCARD auto to_string(T value) -> std::string {\n  // The buffer should be large enough to store the number including the sign\n  // or \"false\" for bool.\n  char buffer[max_of(detail::digits10<T>() + 2, 5)];\n  return {buffer, detail::write<char>(buffer, value)};\n}\n\ntemplate <typename T, FMT_ENABLE_IF(detail::use_format_as<T>::value)>\nFMT_NODISCARD auto to_string(const T& value) -> std::string {\n  return to_string(format_as(value));\n}\n\ntemplate <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value &&\n                                    !detail::use_format_as<T>::value)>\nFMT_NODISCARD auto to_string(const T& value) -> std::string {\n  auto buffer = memory_buffer();\n  detail::write<char>(appender(buffer), value);\n  return {buffer.data(), buffer.size()};\n}\n\nFMT_END_EXPORT\nFMT_END_NAMESPACE\n\n#ifdef FMT_HEADER_ONLY\n#  define FMT_FUNC inline\n#  include \"format-inl.h\"\n#endif\n\n// Restore _LIBCPP_REMOVE_TRANSITIVE_INCLUDES.\n#ifdef FMT_REMOVE_TRANSITIVE_INCLUDES\n#  undef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES\n#endif\n\n#endif  // FMT_FORMAT_H_\n"
  },
  {
    "path": "dependencies/fmt/fmt/include/fmt/os.h",
    "content": "// Formatting library for C++ - optional OS-specific functionality\n//\n// Copyright (c) 2012 - present, Victor Zverovich\n// All rights reserved.\n//\n// For the license information refer to format.h.\n\n#ifndef FMT_OS_H_\n#define FMT_OS_H_\n\n#include \"format.h\"\n\n#ifndef FMT_MODULE\n#  include <cerrno>\n#  include <cstddef>\n#  include <cstdio>\n#  include <system_error>  // std::system_error\n\n#  if FMT_HAS_INCLUDE(<xlocale.h>)\n#    include <xlocale.h>  // LC_NUMERIC_MASK on macOS\n#  endif\n#endif  // FMT_MODULE\n\n#ifndef FMT_USE_FCNTL\n// UWP doesn't provide _pipe.\n#  if FMT_HAS_INCLUDE(\"winapifamily.h\")\n#    include <winapifamily.h>\n#  endif\n#  if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \\\n       defined(__linux__)) &&                              \\\n      (!defined(WINAPI_FAMILY) ||                          \\\n       (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))\n#    include <fcntl.h>  // for O_RDONLY\n#    define FMT_USE_FCNTL 1\n#  else\n#    define FMT_USE_FCNTL 0\n#  endif\n#endif\n\n#ifndef FMT_POSIX\n#  if defined(_WIN32) && !defined(__MINGW32__)\n// Fix warnings about deprecated symbols.\n#    define FMT_POSIX(call) _##call\n#  else\n#    define FMT_POSIX(call) call\n#  endif\n#endif\n\n// Calls to system functions are wrapped in FMT_SYSTEM for testability.\n#ifdef FMT_SYSTEM\n#  define FMT_HAS_SYSTEM\n#  define FMT_POSIX_CALL(call) FMT_SYSTEM(call)\n#else\n#  define FMT_SYSTEM(call) ::call\n#  ifdef _WIN32\n// Fix warnings about deprecated symbols.\n#    define FMT_POSIX_CALL(call) ::_##call\n#  else\n#    define FMT_POSIX_CALL(call) ::call\n#  endif\n#endif\n\n// Retries the expression while it evaluates to error_result and errno\n// equals to EINTR.\n#ifndef _WIN32\n#  define FMT_RETRY_VAL(result, expression, error_result) \\\n    do {                                                  \\\n      (result) = (expression);                            \\\n    } while ((result) == (error_result) && errno == EINTR)\n#else\n#  define FMT_RETRY_VAL(result, expression, error_result) result = (expression)\n#endif\n\n#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)\n\nFMT_BEGIN_NAMESPACE\nFMT_BEGIN_EXPORT\n\n/**\n * A reference to a null-terminated string. It can be constructed from a C\n * string or `std::string`.\n *\n * You can use one of the following type aliases for common character types:\n *\n * +---------------+-----------------------------+\n * | Type          | Definition                  |\n * +===============+=============================+\n * | cstring_view  | basic_cstring_view<char>    |\n * +---------------+-----------------------------+\n * | wcstring_view | basic_cstring_view<wchar_t> |\n * +---------------+-----------------------------+\n *\n * This class is most useful as a parameter type for functions that wrap C APIs.\n */\ntemplate <typename Char> class basic_cstring_view {\n private:\n  const Char* data_;\n\n public:\n  /// Constructs a string reference object from a C string.\n  basic_cstring_view(const Char* s) : data_(s) {}\n\n  /// Constructs a string reference from an `std::string` object.\n  basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}\n\n  /// Returns the pointer to a C string.\n  auto c_str() const -> const Char* { return data_; }\n};\n\nusing cstring_view = basic_cstring_view<char>;\nusing wcstring_view = basic_cstring_view<wchar_t>;\n\n#ifdef _WIN32\nFMT_API const std::error_category& system_category() noexcept;\n\nnamespace detail {\nFMT_API void format_windows_error(buffer<char>& out, int error_code,\n                                  const char* message) noexcept;\n}\n\nFMT_API std::system_error vwindows_error(int error_code, string_view fmt,\n                                         format_args args);\n\n/**\n * Constructs a `std::system_error` object with the description of the form\n *\n *     <message>: <system-message>\n *\n * where `<message>` is the formatted message and `<system-message>` is the\n * system message corresponding to the error code.\n * `error_code` is a Windows error code as given by `GetLastError`.\n * If `error_code` is not a valid error code such as -1, the system message\n * will look like \"error -1\".\n *\n * **Example**:\n *\n *     // This throws a system_error with the description\n *     //   cannot open file 'madeup': The system cannot find the file\n * specified.\n *     // or similar (system message may vary).\n *     const char *filename = \"madeup\";\n *     LPOFSTRUCT of = LPOFSTRUCT();\n *     HFILE file = OpenFile(filename, &of, OF_READ);\n *     if (file == HFILE_ERROR) {\n *       throw fmt::windows_error(GetLastError(),\n *                                \"cannot open file '{}'\", filename);\n *     }\n */\ntemplate <typename... T>\nauto windows_error(int error_code, string_view message, const T&... args)\n    -> std::system_error {\n  return vwindows_error(error_code, message, vargs<T...>{{args...}});\n}\n\n// Reports a Windows error without throwing an exception.\n// Can be used to report errors from destructors.\nFMT_API void report_windows_error(int error_code, const char* message) noexcept;\n#else\ninline auto system_category() noexcept -> const std::error_category& {\n  return std::system_category();\n}\n#endif  // _WIN32\n\n// std::system is not available on some platforms such as iOS (#2248).\n#ifdef __OSX__\ntemplate <typename S, typename... Args, typename Char = char_t<S>>\nvoid say(const S& fmt, Args&&... args) {\n  std::system(format(\"say \\\"{}\\\"\", format(fmt, args...)).c_str());\n}\n#endif\n\n// A buffered file.\nclass buffered_file {\n private:\n  FILE* file_;\n\n  friend class file;\n\n  inline explicit buffered_file(FILE* f) : file_(f) {}\n\n public:\n  buffered_file(const buffered_file&) = delete;\n  void operator=(const buffered_file&) = delete;\n\n  // Constructs a buffered_file object which doesn't represent any file.\n  inline buffered_file() noexcept : file_(nullptr) {}\n\n  // Destroys the object closing the file it represents if any.\n  FMT_API ~buffered_file() noexcept;\n\n public:\n  inline buffered_file(buffered_file&& other) noexcept : file_(other.file_) {\n    other.file_ = nullptr;\n  }\n\n  inline auto operator=(buffered_file&& other) -> buffered_file& {\n    close();\n    file_ = other.file_;\n    other.file_ = nullptr;\n    return *this;\n  }\n\n  // Opens a file.\n  FMT_API buffered_file(cstring_view filename, cstring_view mode);\n\n  // Closes the file.\n  FMT_API void close();\n\n  // Returns the pointer to a FILE object representing this file.\n  inline auto get() const noexcept -> FILE* { return file_; }\n\n  FMT_API auto descriptor() const -> int;\n\n  template <typename... T>\n  inline void print(string_view fmt, const T&... args) {\n    fmt::vargs<T...> vargs = {{args...}};\n    detail::is_locking<T...>() ? fmt::vprint_buffered(file_, fmt, vargs)\n                               : fmt::vprint(file_, fmt, vargs);\n  }\n};\n\n#if FMT_USE_FCNTL\n\n// A file. Closed file is represented by a file object with descriptor -1.\n// Methods that are not declared with noexcept may throw\n// fmt::system_error in case of failure. Note that some errors such as\n// closing the file multiple times will cause a crash on Windows rather\n// than an exception. You can get standard behavior by overriding the\n// invalid parameter handler with _set_invalid_parameter_handler.\nclass FMT_API file {\n private:\n  int fd_;  // File descriptor.\n\n  // Constructs a file object with a given descriptor.\n  explicit file(int fd) : fd_(fd) {}\n\n  friend struct pipe;\n\n public:\n  // Possible values for the oflag argument to the constructor.\n  enum {\n    RDONLY = FMT_POSIX(O_RDONLY),  // Open for reading only.\n    WRONLY = FMT_POSIX(O_WRONLY),  // Open for writing only.\n    RDWR = FMT_POSIX(O_RDWR),      // Open for reading and writing.\n    CREATE = FMT_POSIX(O_CREAT),   // Create if the file doesn't exist.\n    APPEND = FMT_POSIX(O_APPEND),  // Open in append mode.\n    TRUNC = FMT_POSIX(O_TRUNC)     // Truncate the content of the file.\n  };\n\n  // Constructs a file object which doesn't represent any file.\n  inline file() noexcept : fd_(-1) {}\n\n  // Opens a file and constructs a file object representing this file.\n  file(cstring_view path, int oflag);\n\n public:\n  file(const file&) = delete;\n  void operator=(const file&) = delete;\n\n  inline file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }\n\n  // Move assignment is not noexcept because close may throw.\n  inline auto operator=(file&& other) -> file& {\n    close();\n    fd_ = other.fd_;\n    other.fd_ = -1;\n    return *this;\n  }\n\n  // Destroys the object closing the file it represents if any.\n  ~file() noexcept;\n\n  // Returns the file descriptor.\n  inline auto descriptor() const noexcept -> int { return fd_; }\n\n  // Closes the file.\n  void close();\n\n  // Returns the file size. The size has signed type for consistency with\n  // stat::st_size.\n  auto size() const -> long long;\n\n  // Attempts to read count bytes from the file into the specified buffer.\n  auto read(void* buffer, size_t count) -> size_t;\n\n  // Attempts to write count bytes from the specified buffer to the file.\n  auto write(const void* buffer, size_t count) -> size_t;\n\n  // Duplicates a file descriptor with the dup function and returns\n  // the duplicate as a file object.\n  static auto dup(int fd) -> file;\n\n  // Makes fd be the copy of this file descriptor, closing fd first if\n  // necessary.\n  void dup2(int fd);\n\n  // Makes fd be the copy of this file descriptor, closing fd first if\n  // necessary.\n  void dup2(int fd, std::error_code& ec) noexcept;\n\n  // Creates a buffered_file object associated with this file and detaches\n  // this file object from the file.\n  auto fdopen(const char* mode) -> buffered_file;\n\n#  if defined(_WIN32) && !defined(__MINGW32__)\n  // Opens a file and constructs a file object representing this file by\n  // wcstring_view filename. Windows only.\n  static file open_windows_file(wcstring_view path, int oflag);\n#  endif\n};\n\nstruct FMT_API pipe {\n  file read_end;\n  file write_end;\n\n  // Creates a pipe setting up read_end and write_end file objects for reading\n  // and writing respectively.\n  pipe();\n};\n\n// Returns the memory page size.\nauto getpagesize() -> long;\n\nnamespace detail {\n\nstruct buffer_size {\n  constexpr buffer_size() = default;\n  size_t value = 0;\n  FMT_CONSTEXPR auto operator=(size_t val) const -> buffer_size {\n    auto bs = buffer_size();\n    bs.value = val;\n    return bs;\n  }\n};\n\nstruct ostream_params {\n  int oflag = file::WRONLY | file::CREATE | file::TRUNC;\n  size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;\n\n  constexpr ostream_params() {}\n\n  template <typename... T>\n  ostream_params(T... params, int new_oflag) : ostream_params(params...) {\n    oflag = new_oflag;\n  }\n\n  template <typename... T>\n  ostream_params(T... params, detail::buffer_size bs)\n      : ostream_params(params...) {\n    this->buffer_size = bs.value;\n  }\n\n// Intel has a bug that results in failure to deduce a constructor\n// for empty parameter packs.\n#  if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000\n  ostream_params(int new_oflag) : oflag(new_oflag) {}\n  ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {}\n#  endif\n};\n\n}  // namespace detail\n\nFMT_INLINE_VARIABLE constexpr auto buffer_size = detail::buffer_size();\n\n/// A fast buffered output stream for writing from a single thread. Writing from\n/// multiple threads without external synchronization may result in a data race.\nclass FMT_API ostream : private detail::buffer<char> {\n private:\n  file file_;\n\n  ostream(cstring_view path, const detail::ostream_params& params);\n\n  static void grow(buffer<char>& buf, size_t);\n\n public:\n  ostream(ostream&& other) noexcept;\n  ~ostream();\n\n  operator writer() {\n    detail::buffer<char>& buf = *this;\n    return buf;\n  }\n\n  inline void flush() {\n    if (size() == 0) return;\n    file_.write(data(), size() * sizeof(data()[0]));\n    clear();\n  }\n\n  template <typename... T>\n  friend auto output_file(cstring_view path, T... params) -> ostream;\n\n  inline void close() {\n    flush();\n    file_.close();\n  }\n\n  /// Formats `args` according to specifications in `fmt` and writes the\n  /// output to the file.\n  template <typename... T> void print(format_string<T...> fmt, T&&... args) {\n    vformat_to(appender(*this), fmt.str, vargs<T...>{{args...}});\n  }\n};\n\n/**\n * Opens a file for writing. Supported parameters passed in `params`:\n *\n * - `<integer>`: Flags passed to [open](\n *   https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html)\n *   (`file::WRONLY | file::CREATE | file::TRUNC` by default)\n * - `buffer_size=<integer>`: Output buffer size\n *\n * **Example**:\n *\n *     auto out = fmt::output_file(\"guide.txt\");\n *     out.print(\"Don't {}\", \"Panic\");\n */\ntemplate <typename... T>\ninline auto output_file(cstring_view path, T... params) -> ostream {\n  return {path, detail::ostream_params(params...)};\n}\n#endif  // FMT_USE_FCNTL\n\nFMT_END_EXPORT\nFMT_END_NAMESPACE\n\n#endif  // FMT_OS_H_\n"
  },
  {
    "path": "dependencies/fmt/fmt/include/fmt/ostream.h",
    "content": "// Formatting library for C++ - std::ostream support\n//\n// Copyright (c) 2012 - present, Victor Zverovich\n// All rights reserved.\n//\n// For the license information refer to format.h.\n\n#ifndef FMT_OSTREAM_H_\n#define FMT_OSTREAM_H_\n\n#ifndef FMT_MODULE\n#  include <fstream>  // std::filebuf\n#endif\n\n#ifdef _WIN32\n#  ifdef __GLIBCXX__\n#    include <ext/stdio_filebuf.h>\n#    include <ext/stdio_sync_filebuf.h>\n#  endif\n#  include <io.h>\n#endif\n\n#include \"chrono.h\"  // formatbuf\n\n#ifdef _MSVC_STL_UPDATE\n#  define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE\n#elif defined(_MSC_VER) && _MSC_VER < 1912  // VS 15.5\n#  define FMT_MSVC_STL_UPDATE _MSVC_LANG\n#else\n#  define FMT_MSVC_STL_UPDATE 0\n#endif\n\nFMT_BEGIN_NAMESPACE\nnamespace detail {\n\n// Generate a unique explicit instantion in every translation unit using a tag\n// type in an anonymous namespace.\nnamespace {\nstruct file_access_tag {};\n}  // namespace\ntemplate <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>\nclass file_access {\n  friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }\n};\n\n#if FMT_MSVC_STL_UPDATE\ntemplate class file_access<file_access_tag, std::filebuf,\n                           &std::filebuf::_Myfile>;\nauto get_file(std::filebuf&) -> FILE*;\n#endif\n\n// Write the content of buf to os.\n// It is a separate function rather than a part of vprint to simplify testing.\ntemplate <typename Char>\nvoid write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {\n  const Char* buf_data = buf.data();\n  using unsigned_streamsize = make_unsigned_t<std::streamsize>;\n  unsigned_streamsize size = buf.size();\n  unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());\n  do {\n    unsigned_streamsize n = size <= max_size ? size : max_size;\n    os.write(buf_data, static_cast<std::streamsize>(n));\n    buf_data += n;\n    size -= n;\n  } while (size != 0);\n}\n\ntemplate <typename T> struct streamed_view {\n  const T& value;\n};\n}  // namespace detail\n\n// Formats an object of type T that has an overloaded ostream operator<<.\ntemplate <typename Char>\nstruct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {\n  void set_debug_format() = delete;\n\n  template <typename T, typename Context>\n  auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) {\n    auto buffer = basic_memory_buffer<Char>();\n    auto&& formatbuf = detail::formatbuf<std::basic_streambuf<Char>>(buffer);\n    auto&& output = std::basic_ostream<Char>(&formatbuf);\n    output.imbue(std::locale::classic());  // The default is always unlocalized.\n    output << value;\n    output.exceptions(std::ios_base::failbit | std::ios_base::badbit);\n    return formatter<basic_string_view<Char>, Char>::format(\n        {buffer.data(), buffer.size()}, ctx);\n  }\n};\n\nusing ostream_formatter = basic_ostream_formatter<char>;\n\ntemplate <typename T, typename Char>\nstruct formatter<detail::streamed_view<T>, Char>\n    : basic_ostream_formatter<Char> {\n  template <typename Context>\n  auto format(detail::streamed_view<T> view, Context& ctx) const\n      -> decltype(ctx.out()) {\n    return basic_ostream_formatter<Char>::format(view.value, ctx);\n  }\n};\n\n/**\n * Returns a view that formats `value` via an ostream `operator<<`.\n *\n * **Example**:\n *\n *     fmt::print(\"Current thread id: {}\\n\",\n *                fmt::streamed(std::this_thread::get_id()));\n */\ntemplate <typename T>\nconstexpr auto streamed(const T& value) -> detail::streamed_view<T> {\n  return {value};\n}\n\ninline void vprint(std::ostream& os, string_view fmt, format_args args) {\n  auto buffer = memory_buffer();\n  detail::vformat_to(buffer, fmt, args);\n  FILE* f = nullptr;\n#if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI\n  if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))\n    f = detail::get_file(*buf);\n#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI\n  auto* rdbuf = os.rdbuf();\n  if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))\n    f = sfbuf->file();\n  else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))\n    f = fbuf->file();\n#endif\n#ifdef _WIN32\n  if (f) {\n    int fd = _fileno(f);\n    if (_isatty(fd)) {\n      os.flush();\n      if (detail::write_console(fd, {buffer.data(), buffer.size()})) return;\n    }\n  }\n#endif\n  detail::ignore_unused(f);\n  detail::write_buffer(os, buffer);\n}\n\n/**\n * Prints formatted data to the stream `os`.\n *\n * **Example**:\n *\n *     fmt::print(cerr, \"Don't {}!\", \"panic\");\n */\nFMT_EXPORT template <typename... T>\nvoid print(std::ostream& os, format_string<T...> fmt, T&&... args) {\n  fmt::vargs<T...> vargs = {{args...}};\n  if (detail::const_check(detail::use_utf8)) return vprint(os, fmt.str, vargs);\n  auto buffer = memory_buffer();\n  detail::vformat_to(buffer, fmt.str, vargs);\n  detail::write_buffer(os, buffer);\n}\n\nFMT_EXPORT template <typename... T>\nvoid println(std::ostream& os, format_string<T...> fmt, T&&... args) {\n  fmt::print(os, \"{}\\n\", fmt::format(fmt, std::forward<T>(args)...));\n}\n\nFMT_END_NAMESPACE\n\n#endif  // FMT_OSTREAM_H_\n"
  },
  {
    "path": "dependencies/fmt/fmt/include/fmt/printf.h",
    "content": "// Formatting library for C++ - legacy printf implementation\n//\n// Copyright (c) 2012 - 2016, Victor Zverovich\n// All rights reserved.\n//\n// For the license information refer to format.h.\n\n#ifndef FMT_PRINTF_H_\n#define FMT_PRINTF_H_\n\n#ifndef FMT_MODULE\n#  include <algorithm>  // std::max\n#  include <limits>     // std::numeric_limits\n#endif\n\n#include \"format.h\"\n\nFMT_BEGIN_NAMESPACE\nFMT_BEGIN_EXPORT\n\ntemplate <typename T> struct printf_formatter {\n  printf_formatter() = delete;\n};\n\ntemplate <typename Char> class basic_printf_context {\n private:\n  basic_appender<Char> out_;\n  basic_format_args<basic_printf_context> args_;\n\n  static_assert(std::is_same<Char, char>::value ||\n                    std::is_same<Char, wchar_t>::value,\n                \"Unsupported code unit type.\");\n\n public:\n  using char_type = Char;\n  using parse_context_type = parse_context<Char>;\n  template <typename T> using formatter_type = printf_formatter<T>;\n  enum { builtin_types = 1 };\n\n  /// Constructs a `printf_context` object. References to the arguments are\n  /// stored in the context object so make sure they have appropriate lifetimes.\n  basic_printf_context(basic_appender<Char> out,\n                       basic_format_args<basic_printf_context> args)\n      : out_(out), args_(args) {}\n\n  auto out() -> basic_appender<Char> { return out_; }\n  void advance_to(basic_appender<Char>) {}\n\n  auto locale() -> detail::locale_ref { return {}; }\n\n  auto arg(int id) const -> basic_format_arg<basic_printf_context> {\n    return args_.get(id);\n  }\n};\n\nnamespace detail {\n\n// Return the result via the out param to workaround gcc bug 77539.\ntemplate <bool IS_CONSTEXPR, typename T, typename Ptr = const T*>\nFMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool {\n  for (out = first; out != last; ++out) {\n    if (*out == value) return true;\n  }\n  return false;\n}\n\ntemplate <>\ninline auto find<false, char>(const char* first, const char* last, char value,\n                              const char*& out) -> bool {\n  out =\n      static_cast<const char*>(memchr(first, value, to_unsigned(last - first)));\n  return out != nullptr;\n}\n\n// Checks if a value fits in int - used to avoid warnings about comparing\n// signed and unsigned integers.\ntemplate <bool IsSigned> struct int_checker {\n  template <typename T> static auto fits_in_int(T value) -> bool {\n    unsigned max = to_unsigned(max_value<int>());\n    return value <= max;\n  }\n  inline static auto fits_in_int(bool) -> bool { return true; }\n};\n\ntemplate <> struct int_checker<true> {\n  template <typename T> static auto fits_in_int(T value) -> bool {\n    return value >= (std::numeric_limits<int>::min)() &&\n           value <= max_value<int>();\n  }\n  inline static auto fits_in_int(int) -> bool { return true; }\n};\n\nstruct printf_precision_handler {\n  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>\n  auto operator()(T value) -> int {\n    if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))\n      report_error(\"number is too big\");\n    return (std::max)(static_cast<int>(value), 0);\n  }\n\n  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>\n  auto operator()(T) -> int {\n    report_error(\"precision is not integer\");\n    return 0;\n  }\n};\n\n// An argument visitor that returns true iff arg is a zero integer.\nstruct is_zero_int {\n  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>\n  auto operator()(T value) -> bool {\n    return value == 0;\n  }\n\n  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>\n  auto operator()(T) -> bool {\n    return false;\n  }\n};\n\ntemplate <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};\n\ntemplate <> struct make_unsigned_or_bool<bool> {\n  using type = bool;\n};\n\ntemplate <typename T, typename Context> class arg_converter {\n private:\n  using char_type = typename Context::char_type;\n\n  basic_format_arg<Context>& arg_;\n  char_type type_;\n\n public:\n  arg_converter(basic_format_arg<Context>& arg, char_type type)\n      : arg_(arg), type_(type) {}\n\n  void operator()(bool value) {\n    if (type_ != 's') operator()<bool>(value);\n  }\n\n  template <typename U, FMT_ENABLE_IF(std::is_integral<U>::value)>\n  void operator()(U value) {\n    bool is_signed = type_ == 'd' || type_ == 'i';\n    using target_type = conditional_t<std::is_same<T, void>::value, U, T>;\n    if (const_check(sizeof(target_type) <= sizeof(int))) {\n      // Extra casts are used to silence warnings.\n      using unsigned_type = typename make_unsigned_or_bool<target_type>::type;\n      if (is_signed)\n        arg_ = static_cast<int>(static_cast<target_type>(value));\n      else\n        arg_ = static_cast<unsigned>(static_cast<unsigned_type>(value));\n    } else {\n      // glibc's printf doesn't sign extend arguments of smaller types:\n      //   std::printf(\"%lld\", -42);  // prints \"4294967254\"\n      // but we don't have to do the same because it's a UB.\n      if (is_signed)\n        arg_ = static_cast<long long>(value);\n      else\n        arg_ = static_cast<typename make_unsigned_or_bool<U>::type>(value);\n    }\n  }\n\n  template <typename U, FMT_ENABLE_IF(!std::is_integral<U>::value)>\n  void operator()(U) {}  // No conversion needed for non-integral types.\n};\n\n// Converts an integer argument to T for printf, if T is an integral type.\n// If T is void, the argument is converted to corresponding signed or unsigned\n// type depending on the type specifier: 'd' and 'i' - signed, other -\n// unsigned).\ntemplate <typename T, typename Context, typename Char>\nvoid convert_arg(basic_format_arg<Context>& arg, Char type) {\n  arg.visit(arg_converter<T, Context>(arg, type));\n}\n\n// Converts an integer argument to char for printf.\ntemplate <typename Context> class char_converter {\n private:\n  basic_format_arg<Context>& arg_;\n\n public:\n  explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {}\n\n  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>\n  void operator()(T value) {\n    arg_ = static_cast<typename Context::char_type>(value);\n  }\n\n  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>\n  void operator()(T) {}  // No conversion needed for non-integral types.\n};\n\n// An argument visitor that return a pointer to a C string if argument is a\n// string or null otherwise.\ntemplate <typename Char> struct get_cstring {\n  template <typename T> auto operator()(T) -> const Char* { return nullptr; }\n  auto operator()(const Char* s) -> const Char* { return s; }\n};\n\n// Checks if an argument is a valid printf width specifier and sets\n// left alignment if it is negative.\nclass printf_width_handler {\n private:\n  format_specs& specs_;\n\n public:\n  inline explicit printf_width_handler(format_specs& specs) : specs_(specs) {}\n\n  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>\n  auto operator()(T value) -> unsigned {\n    auto width = static_cast<uint32_or_64_or_128_t<T>>(value);\n    if (detail::is_negative(value)) {\n      specs_.set_align(align::left);\n      width = 0 - width;\n    }\n    unsigned int_max = to_unsigned(max_value<int>());\n    if (width > int_max) report_error(\"number is too big\");\n    return static_cast<unsigned>(width);\n  }\n\n  template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>\n  auto operator()(T) -> unsigned {\n    report_error(\"width is not integer\");\n    return 0;\n  }\n};\n\n// Workaround for a bug with the XL compiler when initializing\n// printf_arg_formatter's base class.\ntemplate <typename Char>\nauto make_arg_formatter(basic_appender<Char> iter, format_specs& s)\n    -> arg_formatter<Char> {\n  return {iter, s, locale_ref()};\n}\n\n// The `printf` argument formatter.\ntemplate <typename Char>\nclass printf_arg_formatter : public arg_formatter<Char> {\n private:\n  using base = arg_formatter<Char>;\n  using context_type = basic_printf_context<Char>;\n\n  context_type& context_;\n\n  void write_null_pointer(bool is_string = false) {\n    auto s = this->specs;\n    s.set_type(presentation_type::none);\n    write_bytes<Char>(this->out, is_string ? \"(null)\" : \"(nil)\", s);\n  }\n\n  template <typename T> void write(T value) {\n    detail::write<Char>(this->out, value, this->specs, this->locale);\n  }\n\n public:\n  printf_arg_formatter(basic_appender<Char> iter, format_specs& s,\n                       context_type& ctx)\n      : base(make_arg_formatter(iter, s)), context_(ctx) {}\n\n  void operator()(monostate value) { write(value); }\n\n  template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>\n  void operator()(T value) {\n    // MSVC2013 fails to compile separate overloads for bool and Char so use\n    // std::is_same instead.\n    if (!std::is_same<T, Char>::value) {\n      write(value);\n      return;\n    }\n    format_specs s = this->specs;\n    if (s.type() != presentation_type::none &&\n        s.type() != presentation_type::chr) {\n      return (*this)(static_cast<int>(value));\n    }\n    s.set_sign(sign::none);\n    s.clear_alt();\n    s.set_fill(' ');  // Ignore '0' flag for char types.\n    // align::numeric needs to be overwritten here since the '0' flag is\n    // ignored for non-numeric types\n    if (s.align() == align::none || s.align() == align::numeric)\n      s.set_align(align::right);\n    detail::write<Char>(this->out, static_cast<Char>(value), s);\n  }\n\n  template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>\n  void operator()(T value) {\n    write(value);\n  }\n\n  void operator()(const char* value) {\n    if (value)\n      write(value);\n    else\n      write_null_pointer(this->specs.type() != presentation_type::pointer);\n  }\n\n  void operator()(const wchar_t* value) {\n    if (value)\n      write(value);\n    else\n      write_null_pointer(this->specs.type() != presentation_type::pointer);\n  }\n\n  void operator()(basic_string_view<Char> value) { write(value); }\n\n  void operator()(const void* value) {\n    if (value)\n      write(value);\n    else\n      write_null_pointer();\n  }\n\n  void operator()(typename basic_format_arg<context_type>::handle handle) {\n    auto parse_ctx = parse_context<Char>({});\n    handle.format(parse_ctx, context_);\n  }\n};\n\ntemplate <typename Char>\nvoid parse_flags(format_specs& specs, const Char*& it, const Char* end) {\n  for (; it != end; ++it) {\n    switch (*it) {\n    case '-': specs.set_align(align::left); break;\n    case '+': specs.set_sign(sign::plus); break;\n    case '0': specs.set_fill('0'); break;\n    case ' ':\n      if (specs.sign() != sign::plus) specs.set_sign(sign::space);\n      break;\n    case '#': specs.set_alt(); break;\n    default:  return;\n    }\n  }\n}\n\ntemplate <typename Char, typename GetArg>\nauto parse_header(const Char*& it, const Char* end, format_specs& specs,\n                  GetArg get_arg) -> int {\n  int arg_index = -1;\n  Char c = *it;\n  if (c >= '0' && c <= '9') {\n    // Parse an argument index (if followed by '$') or a width possibly\n    // preceded with '0' flag(s).\n    int value = parse_nonnegative_int(it, end, -1);\n    if (it != end && *it == '$') {  // value is an argument index\n      ++it;\n      arg_index = value != -1 ? value : max_value<int>();\n    } else {\n      if (c == '0') specs.set_fill('0');\n      if (value != 0) {\n        // Nonzero value means that we parsed width and don't need to\n        // parse it or flags again, so return now.\n        if (value == -1) report_error(\"number is too big\");\n        specs.width = value;\n        return arg_index;\n      }\n    }\n  }\n  parse_flags(specs, it, end);\n  // Parse width.\n  if (it != end) {\n    if (*it >= '0' && *it <= '9') {\n      specs.width = parse_nonnegative_int(it, end, -1);\n      if (specs.width == -1) report_error(\"number is too big\");\n    } else if (*it == '*') {\n      ++it;\n      specs.width = static_cast<int>(\n          get_arg(-1).visit(detail::printf_width_handler(specs)));\n    }\n  }\n  return arg_index;\n}\n\ninline auto parse_printf_presentation_type(char c, type t, bool& upper)\n    -> presentation_type {\n  using pt = presentation_type;\n  constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;\n  switch (c) {\n  case 'd': return in(t, integral_set) ? pt::dec : pt::none;\n  case 'o': return in(t, integral_set) ? pt::oct : pt::none;\n  case 'X': upper = true; FMT_FALLTHROUGH;\n  case 'x': return in(t, integral_set) ? pt::hex : pt::none;\n  case 'E': upper = true; FMT_FALLTHROUGH;\n  case 'e': return in(t, float_set) ? pt::exp : pt::none;\n  case 'F': upper = true; FMT_FALLTHROUGH;\n  case 'f': return in(t, float_set) ? pt::fixed : pt::none;\n  case 'G': upper = true; FMT_FALLTHROUGH;\n  case 'g': return in(t, float_set) ? pt::general : pt::none;\n  case 'A': upper = true; FMT_FALLTHROUGH;\n  case 'a': return in(t, float_set) ? pt::hexfloat : pt::none;\n  case 'c': return in(t, integral_set) ? pt::chr : pt::none;\n  case 's': return in(t, string_set | cstring_set) ? pt::string : pt::none;\n  case 'p': return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;\n  default:  return pt::none;\n  }\n}\n\ntemplate <typename Char, typename Context>\nvoid vprintf(buffer<Char>& buf, basic_string_view<Char> format,\n             basic_format_args<Context> args) {\n  using iterator = basic_appender<Char>;\n  auto out = iterator(buf);\n  auto context = basic_printf_context<Char>(out, args);\n  auto parse_ctx = parse_context<Char>(format);\n\n  // Returns the argument with specified index or, if arg_index is -1, the next\n  // argument.\n  auto get_arg = [&](int arg_index) {\n    if (arg_index < 0)\n      arg_index = parse_ctx.next_arg_id();\n    else\n      parse_ctx.check_arg_id(--arg_index);\n    return detail::get_arg(context, arg_index);\n  };\n\n  const Char* start = parse_ctx.begin();\n  const Char* end = parse_ctx.end();\n  auto it = start;\n  while (it != end) {\n    if (!find<false, Char>(it, end, '%', it)) {\n      it = end;  // find leaves it == nullptr if it doesn't find '%'.\n      break;\n    }\n    Char c = *it++;\n    if (it != end && *it == c) {\n      write(out, basic_string_view<Char>(start, to_unsigned(it - start)));\n      start = ++it;\n      continue;\n    }\n    write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));\n\n    auto specs = format_specs();\n    specs.set_align(align::right);\n\n    // Parse argument index, flags and width.\n    int arg_index = parse_header(it, end, specs, get_arg);\n    if (arg_index == 0) report_error(\"argument not found\");\n\n    // Parse precision.\n    if (it != end && *it == '.') {\n      ++it;\n      c = it != end ? *it : 0;\n      if ('0' <= c && c <= '9') {\n        specs.precision = parse_nonnegative_int(it, end, 0);\n      } else if (c == '*') {\n        ++it;\n        specs.precision =\n            static_cast<int>(get_arg(-1).visit(printf_precision_handler()));\n      } else {\n        specs.precision = 0;\n      }\n    }\n\n    auto arg = get_arg(arg_index);\n    // For d, i, o, u, x, and X conversion specifiers, if a precision is\n    // specified, the '0' flag is ignored\n    if (specs.precision >= 0 && is_integral_type(arg.type())) {\n      // Ignore '0' for non-numeric types or if '-' present.\n      specs.set_fill(' ');\n    }\n    if (specs.precision >= 0 && arg.type() == type::cstring_type) {\n      auto str = arg.visit(get_cstring<Char>());\n      auto str_end = str + specs.precision;\n      auto nul = std::find(str, str_end, Char());\n      auto sv = basic_string_view<Char>(\n          str, to_unsigned(nul != str_end ? nul - str : specs.precision));\n      arg = sv;\n    }\n    if (specs.alt() && arg.visit(is_zero_int())) specs.clear_alt();\n    if (specs.fill_unit<Char>() == '0') {\n      if (is_arithmetic_type(arg.type()) && specs.align() != align::left) {\n        specs.set_align(align::numeric);\n      } else {\n        // Ignore '0' flag for non-numeric types or if '-' flag is also present.\n        specs.set_fill(' ');\n      }\n    }\n\n    // Parse length and convert the argument to the required type.\n    c = it != end ? *it++ : 0;\n    Char t = it != end ? *it : 0;\n    switch (c) {\n    case 'h':\n      if (t == 'h') {\n        ++it;\n        t = it != end ? *it : 0;\n        convert_arg<signed char>(arg, t);\n      } else {\n        convert_arg<short>(arg, t);\n      }\n      break;\n    case 'l':\n      if (t == 'l') {\n        ++it;\n        t = it != end ? *it : 0;\n        convert_arg<long long>(arg, t);\n      } else {\n        convert_arg<long>(arg, t);\n      }\n      break;\n    case 'j': convert_arg<intmax_t>(arg, t); break;\n    case 'z': convert_arg<size_t>(arg, t); break;\n    case 't': convert_arg<std::ptrdiff_t>(arg, t); break;\n    case 'L':\n      // printf produces garbage when 'L' is omitted for long double, no\n      // need to do the same.\n      break;\n    default: --it; convert_arg<void>(arg, c);\n    }\n\n    // Parse type.\n    if (it == end) report_error(\"invalid format string\");\n    char type = static_cast<char>(*it++);\n    if (is_integral_type(arg.type())) {\n      // Normalize type.\n      switch (type) {\n      case 'i':\n      case 'u': type = 'd'; break;\n      case 'c':\n        arg.visit(char_converter<basic_printf_context<Char>>(arg));\n        break;\n      }\n    }\n    bool upper = false;\n    specs.set_type(parse_printf_presentation_type(type, arg.type(), upper));\n    if (specs.type() == presentation_type::none)\n      report_error(\"invalid format specifier\");\n    if (upper) specs.set_upper();\n\n    start = it;\n\n    // Format argument.\n    arg.visit(printf_arg_formatter<Char>(out, specs, context));\n  }\n  write(out, basic_string_view<Char>(start, to_unsigned(it - start)));\n}\n}  // namespace detail\n\nusing printf_context = basic_printf_context<char>;\nusing wprintf_context = basic_printf_context<wchar_t>;\n\nusing printf_args = basic_format_args<printf_context>;\nusing wprintf_args = basic_format_args<wprintf_context>;\n\n/// Constructs an `format_arg_store` object that contains references to\n/// arguments and can be implicitly converted to `printf_args`.\ntemplate <typename Char = char, typename... T>\ninline auto make_printf_args(T&... args)\n    -> decltype(fmt::make_format_args<basic_printf_context<Char>>(args...)) {\n  return fmt::make_format_args<basic_printf_context<Char>>(args...);\n}\n\ntemplate <typename Char> struct vprintf_args {\n  using type = basic_format_args<basic_printf_context<Char>>;\n};\n\ntemplate <typename Char>\ninline auto vsprintf(basic_string_view<Char> fmt,\n                     typename vprintf_args<Char>::type args)\n    -> std::basic_string<Char> {\n  auto buf = basic_memory_buffer<Char>();\n  detail::vprintf(buf, fmt, args);\n  return {buf.data(), buf.size()};\n}\n\n/**\n * Formats `args` according to specifications in `fmt` and returns the result\n * as as string.\n *\n * **Example**:\n *\n *     std::string message = fmt::sprintf(\"The answer is %d\", 42);\n */\ntemplate <typename S, typename... T, typename Char = detail::char_t<S>>\ninline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {\n  return vsprintf(detail::to_string_view(fmt),\n                  fmt::make_format_args<basic_printf_context<Char>>(args...));\n}\n\ntemplate <typename Char>\ninline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,\n                     typename vprintf_args<Char>::type args) -> int {\n  auto buf = basic_memory_buffer<Char>();\n  detail::vprintf(buf, fmt, args);\n  size_t size = buf.size();\n  return std::fwrite(buf.data(), sizeof(Char), size, f) < size\n             ? -1\n             : static_cast<int>(size);\n}\n\n/**\n * Formats `args` according to specifications in `fmt` and writes the output\n * to `f`.\n *\n * **Example**:\n *\n *     fmt::fprintf(stderr, \"Don't %s!\", \"panic\");\n */\ntemplate <typename S, typename... T, typename Char = detail::char_t<S>>\ninline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {\n  return vfprintf(f, detail::to_string_view(fmt),\n                  make_printf_args<Char>(args...));\n}\n\ntemplate <typename Char>\nFMT_DEPRECATED inline auto vprintf(basic_string_view<Char> fmt,\n                                   typename vprintf_args<Char>::type args)\n    -> int {\n  return vfprintf(stdout, fmt, args);\n}\n\n/**\n * Formats `args` according to specifications in `fmt` and writes the output\n * to `stdout`.\n *\n * **Example**:\n *\n *   fmt::printf(\"Elapsed time: %.2f seconds\", 1.23);\n */\ntemplate <typename... T>\ninline auto printf(string_view fmt, const T&... args) -> int {\n  return vfprintf(stdout, fmt, make_printf_args(args...));\n}\ntemplate <typename... T>\nFMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,\n                                  const T&... args) -> int {\n  return vfprintf(stdout, fmt, make_printf_args<wchar_t>(args...));\n}\n\nFMT_END_EXPORT\nFMT_END_NAMESPACE\n\n#endif  // FMT_PRINTF_H_\n"
  },
  {
    "path": "dependencies/fmt/fmt/include/fmt/ranges.h",
    "content": "// Formatting library for C++ - range and tuple support\n//\n// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors\n// All rights reserved.\n//\n// For the license information refer to format.h.\n\n#ifndef FMT_RANGES_H_\n#define FMT_RANGES_H_\n\n#ifndef FMT_MODULE\n#  include <initializer_list>\n#  include <iterator>\n#  include <string>\n#  include <tuple>\n#  include <type_traits>\n#  include <utility>\n#endif\n\n#include \"format.h\"\n\nFMT_BEGIN_NAMESPACE\n\nFMT_EXPORT\nenum class range_format { disabled, map, set, sequence, string, debug_string };\n\nnamespace detail {\n\ntemplate <typename T> class is_map {\n  template <typename U> static auto check(U*) -> typename U::mapped_type;\n  template <typename> static void check(...);\n\n public:\n  static constexpr const bool value =\n      !std::is_void<decltype(check<T>(nullptr))>::value;\n};\n\ntemplate <typename T> class is_set {\n  template <typename U> static auto check(U*) -> typename U::key_type;\n  template <typename> static void check(...);\n\n public:\n  static constexpr const bool value =\n      !std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;\n};\n\n// C array overload\ntemplate <typename T, std::size_t N>\nauto range_begin(const T (&arr)[N]) -> const T* {\n  return arr;\n}\ntemplate <typename T, std::size_t N>\nauto range_end(const T (&arr)[N]) -> const T* {\n  return arr + N;\n}\n\ntemplate <typename T, typename Enable = void>\nstruct has_member_fn_begin_end_t : std::false_type {};\n\ntemplate <typename T>\nstruct has_member_fn_begin_end_t<T, void_t<decltype(*std::declval<T>().begin()),\n                                           decltype(std::declval<T>().end())>>\n    : std::true_type {};\n\n// Member function overloads.\ntemplate <typename T>\nauto range_begin(T&& rng) -> decltype(static_cast<T&&>(rng).begin()) {\n  return static_cast<T&&>(rng).begin();\n}\ntemplate <typename T>\nauto range_end(T&& rng) -> decltype(static_cast<T&&>(rng).end()) {\n  return static_cast<T&&>(rng).end();\n}\n\n// ADL overloads. Only participate in overload resolution if member functions\n// are not found.\ntemplate <typename T>\nauto range_begin(T&& rng)\n    -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,\n                   decltype(begin(static_cast<T&&>(rng)))> {\n  return begin(static_cast<T&&>(rng));\n}\ntemplate <typename T>\nauto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,\n                                       decltype(end(static_cast<T&&>(rng)))> {\n  return end(static_cast<T&&>(rng));\n}\n\ntemplate <typename T, typename Enable = void>\nstruct has_const_begin_end : std::false_type {};\ntemplate <typename T, typename Enable = void>\nstruct has_mutable_begin_end : std::false_type {};\n\ntemplate <typename T>\nstruct has_const_begin_end<\n    T, void_t<decltype(*detail::range_begin(\n                  std::declval<const remove_cvref_t<T>&>())),\n              decltype(detail::range_end(\n                  std::declval<const remove_cvref_t<T>&>()))>>\n    : std::true_type {};\n\ntemplate <typename T>\nstruct has_mutable_begin_end<\n    T, void_t<decltype(*detail::range_begin(std::declval<T&>())),\n              decltype(detail::range_end(std::declval<T&>())),\n              // the extra int here is because older versions of MSVC don't\n              // SFINAE properly unless there are distinct types\n              int>> : std::true_type {};\n\ntemplate <typename T, typename _ = void> struct is_range_ : std::false_type {};\ntemplate <typename T>\nstruct is_range_<T, void>\n    : std::integral_constant<bool, (has_const_begin_end<T>::value ||\n                                    has_mutable_begin_end<T>::value)> {};\n\n// tuple_size and tuple_element check.\ntemplate <typename T> class is_tuple_like_ {\n  template <typename U, typename V = typename std::remove_cv<U>::type>\n  static auto check(U* p) -> decltype(std::tuple_size<V>::value, 0);\n  template <typename> static void check(...);\n\n public:\n  static constexpr const bool value =\n      !std::is_void<decltype(check<T>(nullptr))>::value;\n};\n\n// Check for integer_sequence\n#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VERSION >= 1900\ntemplate <typename T, T... N>\nusing integer_sequence = std::integer_sequence<T, N...>;\ntemplate <size_t... N> using index_sequence = std::index_sequence<N...>;\ntemplate <size_t N> using make_index_sequence = std::make_index_sequence<N>;\n#else\ntemplate <typename T, T... N> struct integer_sequence {\n  using value_type = T;\n\n  static FMT_CONSTEXPR auto size() -> size_t { return sizeof...(N); }\n};\n\ntemplate <size_t... N> using index_sequence = integer_sequence<size_t, N...>;\n\ntemplate <typename T, size_t N, T... Ns>\nstruct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};\ntemplate <typename T, T... Ns>\nstruct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};\n\ntemplate <size_t N>\nusing make_index_sequence = make_integer_sequence<size_t, N>;\n#endif\n\ntemplate <typename T>\nusing tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>;\n\ntemplate <typename T, typename C, bool = is_tuple_like_<T>::value>\nclass is_tuple_formattable_ {\n public:\n  static constexpr const bool value = false;\n};\ntemplate <typename T, typename C> class is_tuple_formattable_<T, C, true> {\n  template <size_t... Is>\n  static auto all_true(index_sequence<Is...>,\n                       integer_sequence<bool, (Is >= 0)...>) -> std::true_type;\n  static auto all_true(...) -> std::false_type;\n\n  template <size_t... Is>\n  static auto check(index_sequence<Is...>) -> decltype(all_true(\n      index_sequence<Is...>{},\n      integer_sequence<bool,\n                       (is_formattable<typename std::tuple_element<Is, T>::type,\n                                       C>::value)...>{}));\n\n public:\n  static constexpr const bool value =\n      decltype(check(tuple_index_sequence<T>{}))::value;\n};\n\ntemplate <typename Tuple, typename F, size_t... Is>\nFMT_CONSTEXPR void for_each(index_sequence<Is...>, Tuple&& t, F&& f) {\n  using std::get;\n  // Using a free function get<Is>(Tuple) now.\n  const int unused[] = {0, ((void)f(get<Is>(t)), 0)...};\n  ignore_unused(unused);\n}\n\ntemplate <typename Tuple, typename F>\nFMT_CONSTEXPR void for_each(Tuple&& t, F&& f) {\n  for_each(tuple_index_sequence<remove_cvref_t<Tuple>>(),\n           std::forward<Tuple>(t), std::forward<F>(f));\n}\n\ntemplate <typename Tuple1, typename Tuple2, typename F, size_t... Is>\nvoid for_each2(index_sequence<Is...>, Tuple1&& t1, Tuple2&& t2, F&& f) {\n  using std::get;\n  const int unused[] = {0, ((void)f(get<Is>(t1), get<Is>(t2)), 0)...};\n  ignore_unused(unused);\n}\n\ntemplate <typename Tuple1, typename Tuple2, typename F>\nvoid for_each2(Tuple1&& t1, Tuple2&& t2, F&& f) {\n  for_each2(tuple_index_sequence<remove_cvref_t<Tuple1>>(),\n            std::forward<Tuple1>(t1), std::forward<Tuple2>(t2),\n            std::forward<F>(f));\n}\n\nnamespace tuple {\n// Workaround a bug in MSVC 2019 (v140).\ntemplate <typename Char, typename... T>\nusing result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;\n\nusing std::get;\ntemplate <typename Tuple, typename Char, std::size_t... Is>\nauto get_formatters(index_sequence<Is...>)\n    -> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;\n}  // namespace tuple\n\n#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920\n// Older MSVC doesn't get the reference type correctly for arrays.\ntemplate <typename R> struct range_reference_type_impl {\n  using type = decltype(*detail::range_begin(std::declval<R&>()));\n};\n\ntemplate <typename T, std::size_t N> struct range_reference_type_impl<T[N]> {\n  using type = T&;\n};\n\ntemplate <typename T>\nusing range_reference_type = typename range_reference_type_impl<T>::type;\n#else\ntemplate <typename Range>\nusing range_reference_type =\n    decltype(*detail::range_begin(std::declval<Range&>()));\n#endif\n\n// We don't use the Range's value_type for anything, but we do need the Range's\n// reference type, with cv-ref stripped.\ntemplate <typename Range>\nusing uncvref_type = remove_cvref_t<range_reference_type<Range>>;\n\ntemplate <typename Formatter>\nFMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)\n    -> decltype(f.set_debug_format(set)) {\n  f.set_debug_format(set);\n}\ntemplate <typename Formatter>\nFMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}\n\ntemplate <typename T>\nstruct range_format_kind_\n    : std::integral_constant<range_format,\n                             std::is_same<uncvref_type<T>, T>::value\n                                 ? range_format::disabled\n                             : is_map<T>::value ? range_format::map\n                             : is_set<T>::value ? range_format::set\n                                                : range_format::sequence> {};\n\ntemplate <range_format K>\nusing range_format_constant = std::integral_constant<range_format, K>;\n\n// These are not generic lambdas for compatibility with C++11.\ntemplate <typename Char> struct parse_empty_specs {\n  template <typename Formatter> FMT_CONSTEXPR void operator()(Formatter& f) {\n    f.parse(ctx);\n    detail::maybe_set_debug_format(f, true);\n  }\n  parse_context<Char>& ctx;\n};\ntemplate <typename FormatContext> struct format_tuple_element {\n  using char_type = typename FormatContext::char_type;\n\n  template <typename T>\n  void operator()(const formatter<T, char_type>& f, const T& v) {\n    if (i > 0) ctx.advance_to(detail::copy<char_type>(separator, ctx.out()));\n    ctx.advance_to(f.format(v, ctx));\n    ++i;\n  }\n\n  int i;\n  FormatContext& ctx;\n  basic_string_view<char_type> separator;\n};\n\n}  // namespace detail\n\ntemplate <typename T> struct is_tuple_like {\n  static constexpr const bool value =\n      detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;\n};\n\ntemplate <typename T, typename C> struct is_tuple_formattable {\n  static constexpr const bool value =\n      detail::is_tuple_formattable_<T, C>::value;\n};\n\ntemplate <typename Tuple, typename Char>\nstruct formatter<Tuple, Char,\n                 enable_if_t<fmt::is_tuple_like<Tuple>::value &&\n                             fmt::is_tuple_formattable<Tuple, Char>::value>> {\n private:\n  decltype(detail::tuple::get_formatters<Tuple, Char>(\n      detail::tuple_index_sequence<Tuple>())) formatters_;\n\n  basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};\n  basic_string_view<Char> opening_bracket_ =\n      detail::string_literal<Char, '('>{};\n  basic_string_view<Char> closing_bracket_ =\n      detail::string_literal<Char, ')'>{};\n\n public:\n  FMT_CONSTEXPR formatter() {}\n\n  FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {\n    separator_ = sep;\n  }\n\n  FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,\n                                  basic_string_view<Char> close) {\n    opening_bracket_ = open;\n    closing_bracket_ = close;\n  }\n\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    auto it = ctx.begin();\n    auto end = ctx.end();\n    if (it != end && detail::to_ascii(*it) == 'n') {\n      ++it;\n      set_brackets({}, {});\n      set_separator({});\n    }\n    if (it != end && *it != '}') report_error(\"invalid format specifier\");\n    ctx.advance_to(it);\n    detail::for_each(formatters_, detail::parse_empty_specs<Char>{ctx});\n    return it;\n  }\n\n  template <typename FormatContext>\n  auto format(const Tuple& value, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    ctx.advance_to(detail::copy<Char>(opening_bracket_, ctx.out()));\n    detail::for_each2(\n        formatters_, value,\n        detail::format_tuple_element<FormatContext>{0, ctx, separator_});\n    return detail::copy<Char>(closing_bracket_, ctx.out());\n  }\n};\n\ntemplate <typename T, typename Char> struct is_range {\n  static constexpr const bool value =\n      detail::is_range_<T>::value && !detail::has_to_string_view<T>::value;\n};\n\nnamespace detail {\n\ntemplate <typename Char, typename Element>\nusing range_formatter_type = formatter<remove_cvref_t<Element>, Char>;\n\ntemplate <typename R>\nusing maybe_const_range =\n    conditional_t<has_const_begin_end<R>::value, const R, R>;\n\ntemplate <typename R, typename Char>\nstruct is_formattable_delayed\n    : is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};\n}  // namespace detail\n\ntemplate <typename...> struct conjunction : std::true_type {};\ntemplate <typename P> struct conjunction<P> : P {};\ntemplate <typename P1, typename... Pn>\nstruct conjunction<P1, Pn...>\n    : conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};\n\ntemplate <typename T, typename Char, typename Enable = void>\nstruct range_formatter;\n\ntemplate <typename T, typename Char>\nstruct range_formatter<\n    T, Char,\n    enable_if_t<conjunction<std::is_same<T, remove_cvref_t<T>>,\n                            is_formattable<T, Char>>::value>> {\n private:\n  detail::range_formatter_type<Char, T> underlying_;\n  basic_string_view<Char> separator_ = detail::string_literal<Char, ',', ' '>{};\n  basic_string_view<Char> opening_bracket_ =\n      detail::string_literal<Char, '['>{};\n  basic_string_view<Char> closing_bracket_ =\n      detail::string_literal<Char, ']'>{};\n  bool is_debug = false;\n\n  template <typename Output, typename It, typename Sentinel, typename U = T,\n            FMT_ENABLE_IF(std::is_same<U, Char>::value)>\n  auto write_debug_string(Output& out, It it, Sentinel end) const -> Output {\n    auto buf = basic_memory_buffer<Char>();\n    for (; it != end; ++it) buf.push_back(*it);\n    auto specs = format_specs();\n    specs.set_type(presentation_type::debug);\n    return detail::write<Char>(\n        out, basic_string_view<Char>(buf.data(), buf.size()), specs);\n  }\n\n  template <typename Output, typename It, typename Sentinel, typename U = T,\n            FMT_ENABLE_IF(!std::is_same<U, Char>::value)>\n  auto write_debug_string(Output& out, It, Sentinel) const -> Output {\n    return out;\n  }\n\n public:\n  FMT_CONSTEXPR range_formatter() {}\n\n  FMT_CONSTEXPR auto underlying() -> detail::range_formatter_type<Char, T>& {\n    return underlying_;\n  }\n\n  FMT_CONSTEXPR void set_separator(basic_string_view<Char> sep) {\n    separator_ = sep;\n  }\n\n  FMT_CONSTEXPR void set_brackets(basic_string_view<Char> open,\n                                  basic_string_view<Char> close) {\n    opening_bracket_ = open;\n    closing_bracket_ = close;\n  }\n\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    auto it = ctx.begin();\n    auto end = ctx.end();\n    detail::maybe_set_debug_format(underlying_, true);\n    if (it == end) return underlying_.parse(ctx);\n\n    switch (detail::to_ascii(*it)) {\n    case 'n':\n      set_brackets({}, {});\n      ++it;\n      break;\n    case '?':\n      is_debug = true;\n      set_brackets({}, {});\n      ++it;\n      if (it == end || *it != 's') report_error(\"invalid format specifier\");\n      FMT_FALLTHROUGH;\n    case 's':\n      if (!std::is_same<T, Char>::value)\n        report_error(\"invalid format specifier\");\n      if (!is_debug) {\n        set_brackets(detail::string_literal<Char, '\"'>{},\n                     detail::string_literal<Char, '\"'>{});\n        set_separator({});\n        detail::maybe_set_debug_format(underlying_, false);\n      }\n      ++it;\n      return it;\n    }\n\n    if (it != end && *it != '}') {\n      if (*it != ':') report_error(\"invalid format specifier\");\n      detail::maybe_set_debug_format(underlying_, false);\n      ++it;\n    }\n\n    ctx.advance_to(it);\n    return underlying_.parse(ctx);\n  }\n\n  template <typename R, typename FormatContext>\n  auto format(R&& range, FormatContext& ctx) const -> decltype(ctx.out()) {\n    auto out = ctx.out();\n    auto it = detail::range_begin(range);\n    auto end = detail::range_end(range);\n    if (is_debug) return write_debug_string(out, std::move(it), end);\n\n    out = detail::copy<Char>(opening_bracket_, out);\n    int i = 0;\n    for (; it != end; ++it) {\n      if (i > 0) out = detail::copy<Char>(separator_, out);\n      ctx.advance_to(out);\n      auto&& item = *it;  // Need an lvalue\n      out = underlying_.format(item, ctx);\n      ++i;\n    }\n    out = detail::copy<Char>(closing_bracket_, out);\n    return out;\n  }\n};\n\nFMT_EXPORT\ntemplate <typename T, typename Char, typename Enable = void>\nstruct range_format_kind\n    : conditional_t<\n          is_range<T, Char>::value, detail::range_format_kind_<T>,\n          std::integral_constant<range_format, range_format::disabled>> {};\n\ntemplate <typename R, typename Char>\nstruct formatter<\n    R, Char,\n    enable_if_t<conjunction<\n        bool_constant<\n            range_format_kind<R, Char>::value != range_format::disabled &&\n            range_format_kind<R, Char>::value != range_format::map &&\n            range_format_kind<R, Char>::value != range_format::string &&\n            range_format_kind<R, Char>::value != range_format::debug_string>,\n        detail::is_formattable_delayed<R, Char>>::value>> {\n private:\n  using range_type = detail::maybe_const_range<R>;\n  range_formatter<detail::uncvref_type<range_type>, Char> range_formatter_;\n\n public:\n  using nonlocking = void;\n\n  FMT_CONSTEXPR formatter() {\n    if (detail::const_check(range_format_kind<R, Char>::value !=\n                            range_format::set))\n      return;\n    range_formatter_.set_brackets(detail::string_literal<Char, '{'>{},\n                                  detail::string_literal<Char, '}'>{});\n  }\n\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    return range_formatter_.parse(ctx);\n  }\n\n  template <typename FormatContext>\n  auto format(range_type& range, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    return range_formatter_.format(range, ctx);\n  }\n};\n\n// A map formatter.\ntemplate <typename R, typename Char>\nstruct formatter<\n    R, Char,\n    enable_if_t<conjunction<\n        bool_constant<range_format_kind<R, Char>::value == range_format::map>,\n        detail::is_formattable_delayed<R, Char>>::value>> {\n private:\n  using map_type = detail::maybe_const_range<R>;\n  using element_type = detail::uncvref_type<map_type>;\n\n  decltype(detail::tuple::get_formatters<element_type, Char>(\n      detail::tuple_index_sequence<element_type>())) formatters_;\n  bool no_delimiters_ = false;\n\n public:\n  FMT_CONSTEXPR formatter() {}\n\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    auto it = ctx.begin();\n    auto end = ctx.end();\n    if (it != end) {\n      if (detail::to_ascii(*it) == 'n') {\n        no_delimiters_ = true;\n        ++it;\n      }\n      if (it != end && *it != '}') {\n        if (*it != ':') report_error(\"invalid format specifier\");\n        ++it;\n      }\n      ctx.advance_to(it);\n    }\n    detail::for_each(formatters_, detail::parse_empty_specs<Char>{ctx});\n    return it;\n  }\n\n  template <typename FormatContext>\n  auto format(map_type& map, FormatContext& ctx) const -> decltype(ctx.out()) {\n    auto out = ctx.out();\n    basic_string_view<Char> open = detail::string_literal<Char, '{'>{};\n    if (!no_delimiters_) out = detail::copy<Char>(open, out);\n    int i = 0;\n    basic_string_view<Char> sep = detail::string_literal<Char, ',', ' '>{};\n    for (auto&& value : map) {\n      if (i > 0) out = detail::copy<Char>(sep, out);\n      ctx.advance_to(out);\n      detail::for_each2(formatters_, value,\n                        detail::format_tuple_element<FormatContext>{\n                            0, ctx, detail::string_literal<Char, ':', ' '>{}});\n      ++i;\n    }\n    basic_string_view<Char> close = detail::string_literal<Char, '}'>{};\n    if (!no_delimiters_) out = detail::copy<Char>(close, out);\n    return out;\n  }\n};\n\n// A (debug_)string formatter.\ntemplate <typename R, typename Char>\nstruct formatter<\n    R, Char,\n    enable_if_t<range_format_kind<R, Char>::value == range_format::string ||\n                range_format_kind<R, Char>::value ==\n                    range_format::debug_string>> {\n private:\n  using range_type = detail::maybe_const_range<R>;\n  using string_type =\n      conditional_t<std::is_constructible<\n                        detail::std_string_view<Char>,\n                        decltype(detail::range_begin(std::declval<R>())),\n                        decltype(detail::range_end(std::declval<R>()))>::value,\n                    detail::std_string_view<Char>, std::basic_string<Char>>;\n\n  formatter<string_type, Char> underlying_;\n\n public:\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    return underlying_.parse(ctx);\n  }\n\n  template <typename FormatContext>\n  auto format(range_type& range, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    auto out = ctx.out();\n    if (detail::const_check(range_format_kind<R, Char>::value ==\n                            range_format::debug_string))\n      *out++ = '\"';\n    out = underlying_.format(\n        string_type{detail::range_begin(range), detail::range_end(range)}, ctx);\n    if (detail::const_check(range_format_kind<R, Char>::value ==\n                            range_format::debug_string))\n      *out++ = '\"';\n    return out;\n  }\n};\n\ntemplate <typename It, typename Sentinel, typename Char = char>\nstruct join_view : detail::view {\n  It begin;\n  Sentinel end;\n  basic_string_view<Char> sep;\n\n  join_view(It b, Sentinel e, basic_string_view<Char> s)\n      : begin(std::move(b)), end(e), sep(s) {}\n};\n\ntemplate <typename It, typename Sentinel, typename Char>\nstruct formatter<join_view<It, Sentinel, Char>, Char> {\n private:\n  using value_type =\n#ifdef __cpp_lib_ranges\n      std::iter_value_t<It>;\n#else\n      typename std::iterator_traits<It>::value_type;\n#endif\n  formatter<remove_cvref_t<value_type>, Char> value_formatter_;\n\n  using view = conditional_t<std::is_copy_constructible<It>::value,\n                             const join_view<It, Sentinel, Char>,\n                             join_view<It, Sentinel, Char>>;\n\n public:\n  using nonlocking = void;\n\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    return value_formatter_.parse(ctx);\n  }\n\n  template <typename FormatContext>\n  auto format(view& value, FormatContext& ctx) const -> decltype(ctx.out()) {\n    using iter =\n        conditional_t<std::is_copy_constructible<view>::value, It, It&>;\n    iter it = value.begin;\n    auto out = ctx.out();\n    if (it == value.end) return out;\n    out = value_formatter_.format(*it, ctx);\n    ++it;\n    while (it != value.end) {\n      out = detail::copy<Char>(value.sep.begin(), value.sep.end(), out);\n      ctx.advance_to(out);\n      out = value_formatter_.format(*it, ctx);\n      ++it;\n    }\n    return out;\n  }\n};\n\ntemplate <typename Char, typename Tuple> struct tuple_join_view : detail::view {\n  const Tuple& tuple;\n  basic_string_view<Char> sep;\n\n  tuple_join_view(const Tuple& t, basic_string_view<Char> s)\n      : tuple(t), sep{s} {}\n};\n\n// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers\n// support in tuple_join. It is disabled by default because of issues with\n// the dynamic width and precision.\n#ifndef FMT_TUPLE_JOIN_SPECIFIERS\n#  define FMT_TUPLE_JOIN_SPECIFIERS 0\n#endif\n\ntemplate <typename Char, typename Tuple>\nstruct formatter<tuple_join_view<Char, Tuple>, Char,\n                 enable_if_t<is_tuple_like<Tuple>::value>> {\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    return do_parse(ctx, std::tuple_size<Tuple>());\n  }\n\n  template <typename FormatContext>\n  auto format(const tuple_join_view<Char, Tuple>& value,\n              FormatContext& ctx) const -> typename FormatContext::iterator {\n    return do_format(value, ctx, std::tuple_size<Tuple>());\n  }\n\n private:\n  decltype(detail::tuple::get_formatters<Tuple, Char>(\n      detail::tuple_index_sequence<Tuple>())) formatters_;\n\n  FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,\n                              std::integral_constant<size_t, 0>)\n      -> const Char* {\n    return ctx.begin();\n  }\n\n  template <size_t N>\n  FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,\n                              std::integral_constant<size_t, N>)\n      -> const Char* {\n    auto end = ctx.begin();\n#if FMT_TUPLE_JOIN_SPECIFIERS\n    end = std::get<std::tuple_size<Tuple>::value - N>(formatters_).parse(ctx);\n    if (N > 1) {\n      auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());\n      if (end != end1)\n        report_error(\"incompatible format specs for tuple elements\");\n    }\n#endif\n    return end;\n  }\n\n  template <typename FormatContext>\n  auto do_format(const tuple_join_view<Char, Tuple>&, FormatContext& ctx,\n                 std::integral_constant<size_t, 0>) const ->\n      typename FormatContext::iterator {\n    return ctx.out();\n  }\n\n  template <typename FormatContext, size_t N>\n  auto do_format(const tuple_join_view<Char, Tuple>& value, FormatContext& ctx,\n                 std::integral_constant<size_t, N>) const ->\n      typename FormatContext::iterator {\n    using std::get;\n    auto out =\n        std::get<std::tuple_size<Tuple>::value - N>(formatters_)\n            .format(get<std::tuple_size<Tuple>::value - N>(value.tuple), ctx);\n    if (N <= 1) return out;\n    out = detail::copy<Char>(value.sep, out);\n    ctx.advance_to(out);\n    return do_format(value, ctx, std::integral_constant<size_t, N - 1>());\n  }\n};\n\nnamespace detail {\n// Check if T has an interface like a container adaptor (e.g. std::stack,\n// std::queue, std::priority_queue).\ntemplate <typename T> class is_container_adaptor_like {\n  template <typename U> static auto check(U* p) -> typename U::container_type;\n  template <typename> static void check(...);\n\n public:\n  static constexpr const bool value =\n      !std::is_void<decltype(check<T>(nullptr))>::value;\n};\n\ntemplate <typename Container> struct all {\n  const Container& c;\n  auto begin() const -> typename Container::const_iterator { return c.begin(); }\n  auto end() const -> typename Container::const_iterator { return c.end(); }\n};\n}  // namespace detail\n\ntemplate <typename T, typename Char>\nstruct formatter<\n    T, Char,\n    enable_if_t<conjunction<detail::is_container_adaptor_like<T>,\n                            bool_constant<range_format_kind<T, Char>::value ==\n                                          range_format::disabled>>::value>>\n    : formatter<detail::all<typename T::container_type>, Char> {\n  using all = detail::all<typename T::container_type>;\n  template <typename FormatContext>\n  auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {\n    struct getter : T {\n      static auto get(const T& t) -> all {\n        return {t.*(&getter::c)};  // Access c through the derived class.\n      }\n    };\n    return formatter<all>::format(getter::get(t), ctx);\n  }\n};\n\nFMT_BEGIN_EXPORT\n\n/// Returns a view that formats the iterator range `[begin, end)` with elements\n/// separated by `sep`.\ntemplate <typename It, typename Sentinel>\nauto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {\n  return {std::move(begin), end, sep};\n}\n\n/**\n * Returns a view that formats `range` with elements separated by `sep`.\n *\n * **Example**:\n *\n *     auto v = std::vector<int>{1, 2, 3};\n *     fmt::print(\"{}\", fmt::join(v, \", \"));\n *     // Output: 1, 2, 3\n *\n * `fmt::join` applies passed format specifiers to the range elements:\n *\n *     fmt::print(\"{:02}\", fmt::join(v, \", \"));\n *     // Output: 01, 02, 03\n */\ntemplate <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>\nauto join(Range&& r, string_view sep)\n    -> join_view<decltype(detail::range_begin(r)),\n                 decltype(detail::range_end(r))> {\n  return {detail::range_begin(r), detail::range_end(r), sep};\n}\n\n/**\n * Returns an object that formats `std::tuple` with elements separated by `sep`.\n *\n * **Example**:\n *\n *     auto t = std::tuple<int, char>{1, 'a'};\n *     fmt::print(\"{}\", fmt::join(t, \", \"));\n *     // Output: 1, a\n */\ntemplate <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>\nFMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep)\n    -> tuple_join_view<char, Tuple> {\n  return {tuple, sep};\n}\n\n/**\n * Returns an object that formats `std::initializer_list` with elements\n * separated by `sep`.\n *\n * **Example**:\n *\n *     fmt::print(\"{}\", fmt::join({1, 2, 3}, \", \"));\n *     // Output: \"1, 2, 3\"\n */\ntemplate <typename T>\nauto join(std::initializer_list<T> list, string_view sep)\n    -> join_view<const T*, const T*> {\n  return join(std::begin(list), std::end(list), sep);\n}\n\nFMT_END_EXPORT\nFMT_END_NAMESPACE\n\n#endif  // FMT_RANGES_H_\n"
  },
  {
    "path": "dependencies/fmt/fmt/include/fmt/std.h",
    "content": "// Formatting library for C++ - formatters for standard library types\n//\n// Copyright (c) 2012 - present, Victor Zverovich\n// All rights reserved.\n//\n// For the license information refer to format.h.\n\n#ifndef FMT_STD_H_\n#define FMT_STD_H_\n\n#include \"format.h\"\n#include \"ostream.h\"\n\n#ifndef FMT_MODULE\n#  include <atomic>\n#  include <bitset>\n#  include <complex>\n#  include <cstdlib>\n#  include <exception>\n#  include <functional>\n#  include <memory>\n#  include <thread>\n#  include <type_traits>\n#  include <typeinfo>\n#  include <utility>\n#  include <vector>\n\n// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.\n#  if FMT_CPLUSPLUS >= 201703L\n#    if FMT_HAS_INCLUDE(<filesystem>) && \\\n        (!defined(FMT_CPP_LIB_FILESYSTEM) || FMT_CPP_LIB_FILESYSTEM != 0)\n#      include <filesystem>\n#    endif\n#    if FMT_HAS_INCLUDE(<variant>)\n#      include <variant>\n#    endif\n#    if FMT_HAS_INCLUDE(<optional>)\n#      include <optional>\n#    endif\n#  endif\n// Use > instead of >= in the version check because <source_location> may be\n// available after C++17 but before C++20 is marked as implemented.\n#  if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE(<source_location>)\n#    include <source_location>\n#  endif\n#  if FMT_CPLUSPLUS > 202002L && FMT_HAS_INCLUDE(<expected>)\n#    include <expected>\n#  endif\n#endif  // FMT_MODULE\n\n#if FMT_HAS_INCLUDE(<version>)\n#  include <version>\n#endif\n\n// GCC 4 does not support FMT_HAS_INCLUDE.\n#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)\n#  include <cxxabi.h>\n// Android NDK with gabi++ library on some architectures does not implement\n// abi::__cxa_demangle().\n#  ifndef __GABIXX_CXXABI_H__\n#    define FMT_HAS_ABI_CXA_DEMANGLE\n#  endif\n#endif\n\n// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined.\n#ifndef FMT_CPP_LIB_FILESYSTEM\n#  ifdef __cpp_lib_filesystem\n#    define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem\n#  else\n#    define FMT_CPP_LIB_FILESYSTEM 0\n#  endif\n#endif\n\n#ifndef FMT_CPP_LIB_VARIANT\n#  ifdef __cpp_lib_variant\n#    define FMT_CPP_LIB_VARIANT __cpp_lib_variant\n#  else\n#    define FMT_CPP_LIB_VARIANT 0\n#  endif\n#endif\n\n#if FMT_CPP_LIB_FILESYSTEM\nFMT_BEGIN_NAMESPACE\n\nnamespace detail {\n\ntemplate <typename Char, typename PathChar>\nauto get_path_string(const std::filesystem::path& p,\n                     const std::basic_string<PathChar>& native) {\n  if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)\n    return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);\n  else\n    return p.string<Char>();\n}\n\ntemplate <typename Char, typename PathChar>\nvoid write_escaped_path(basic_memory_buffer<Char>& quoted,\n                        const std::filesystem::path& p,\n                        const std::basic_string<PathChar>& native) {\n  if constexpr (std::is_same_v<Char, char> &&\n                std::is_same_v<PathChar, wchar_t>) {\n    auto buf = basic_memory_buffer<wchar_t>();\n    write_escaped_string<wchar_t>(std::back_inserter(buf), native);\n    bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()});\n    FMT_ASSERT(valid, \"invalid utf16\");\n  } else if constexpr (std::is_same_v<Char, PathChar>) {\n    write_escaped_string<std::filesystem::path::value_type>(\n        std::back_inserter(quoted), native);\n  } else {\n    write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());\n  }\n}\n\n}  // namespace detail\n\nFMT_EXPORT\ntemplate <typename Char> struct formatter<std::filesystem::path, Char> {\n private:\n  format_specs specs_;\n  detail::arg_ref<Char> width_ref_;\n  bool debug_ = false;\n  char path_type_ = 0;\n\n public:\n  FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }\n\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {\n    auto it = ctx.begin(), end = ctx.end();\n    if (it == end) return it;\n\n    it = detail::parse_align(it, end, specs_);\n    if (it == end) return it;\n\n    Char c = *it;\n    if ((c >= '0' && c <= '9') || c == '{')\n      it = detail::parse_width(it, end, specs_, width_ref_, ctx);\n    if (it != end && *it == '?') {\n      debug_ = true;\n      ++it;\n    }\n    if (it != end && (*it == 'g')) path_type_ = detail::to_ascii(*it++);\n    return it;\n  }\n\n  template <typename FormatContext>\n  auto format(const std::filesystem::path& p, FormatContext& ctx) const {\n    auto specs = specs_;\n    auto path_string =\n        !path_type_ ? p.native()\n                    : p.generic_string<std::filesystem::path::value_type>();\n\n    detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,\n                                ctx);\n    if (!debug_) {\n      auto s = detail::get_path_string<Char>(p, path_string);\n      return detail::write(ctx.out(), basic_string_view<Char>(s), specs);\n    }\n    auto quoted = basic_memory_buffer<Char>();\n    detail::write_escaped_path(quoted, p, path_string);\n    return detail::write(ctx.out(),\n                         basic_string_view<Char>(quoted.data(), quoted.size()),\n                         specs);\n  }\n};\n\nclass path : public std::filesystem::path {\n public:\n  auto display_string() const -> std::string {\n    const std::filesystem::path& base = *this;\n    return fmt::format(FMT_STRING(\"{}\"), base);\n  }\n  auto system_string() const -> std::string { return string(); }\n\n  auto generic_display_string() const -> std::string {\n    const std::filesystem::path& base = *this;\n    return fmt::format(FMT_STRING(\"{:g}\"), base);\n  }\n  auto generic_system_string() const -> std::string { return generic_string(); }\n};\n\nFMT_END_NAMESPACE\n#endif  // FMT_CPP_LIB_FILESYSTEM\n\nFMT_BEGIN_NAMESPACE\nFMT_EXPORT\ntemplate <std::size_t N, typename Char>\nstruct formatter<std::bitset<N>, Char>\n    : nested_formatter<basic_string_view<Char>, Char> {\n private:\n  // Functor because C++11 doesn't support generic lambdas.\n  struct writer {\n    const std::bitset<N>& bs;\n\n    template <typename OutputIt>\n    FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {\n      for (auto pos = N; pos > 0; --pos) {\n        out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));\n      }\n\n      return out;\n    }\n  };\n\n public:\n  template <typename FormatContext>\n  auto format(const std::bitset<N>& bs, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    return this->write_padded(ctx, writer{bs});\n  }\n};\n\nFMT_EXPORT\ntemplate <typename Char>\nstruct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};\nFMT_END_NAMESPACE\n\n#ifdef __cpp_lib_optional\nFMT_BEGIN_NAMESPACE\nFMT_EXPORT\ntemplate <typename T, typename Char>\nstruct formatter<std::optional<T>, Char,\n                 std::enable_if_t<is_formattable<T, Char>::value>> {\n private:\n  formatter<T, Char> underlying_;\n  static constexpr basic_string_view<Char> optional =\n      detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',\n                             '('>{};\n  static constexpr basic_string_view<Char> none =\n      detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};\n\n  template <class U>\n  FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)\n      -> decltype(u.set_debug_format(set)) {\n    u.set_debug_format(set);\n  }\n\n  template <class U>\n  FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}\n\n public:\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {\n    maybe_set_debug_format(underlying_, true);\n    return underlying_.parse(ctx);\n  }\n\n  template <typename FormatContext>\n  auto format(const std::optional<T>& opt, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    if (!opt) return detail::write<Char>(ctx.out(), none);\n\n    auto out = ctx.out();\n    out = detail::write<Char>(out, optional);\n    ctx.advance_to(out);\n    out = underlying_.format(*opt, ctx);\n    return detail::write(out, ')');\n  }\n};\nFMT_END_NAMESPACE\n#endif  // __cpp_lib_optional\n\n#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT\n\nFMT_BEGIN_NAMESPACE\nnamespace detail {\n\ntemplate <typename Char, typename OutputIt, typename T>\nauto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {\n  if constexpr (has_to_string_view<T>::value)\n    return write_escaped_string<Char>(out, detail::to_string_view(v));\n  if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);\n  return write<Char>(out, v);\n}\n\n}  // namespace detail\n\nFMT_END_NAMESPACE\n#endif\n\n#ifdef __cpp_lib_expected\nFMT_BEGIN_NAMESPACE\n\nFMT_EXPORT\ntemplate <typename T, typename E, typename Char>\nstruct formatter<std::expected<T, E>, Char,\n                 std::enable_if_t<(std::is_void<T>::value ||\n                                   is_formattable<T, Char>::value) &&\n                                  is_formattable<E, Char>::value>> {\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    return ctx.begin();\n  }\n\n  template <typename FormatContext>\n  auto format(const std::expected<T, E>& value, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    auto out = ctx.out();\n\n    if (value.has_value()) {\n      out = detail::write<Char>(out, \"expected(\");\n      if constexpr (!std::is_void<T>::value)\n        out = detail::write_escaped_alternative<Char>(out, *value);\n    } else {\n      out = detail::write<Char>(out, \"unexpected(\");\n      out = detail::write_escaped_alternative<Char>(out, value.error());\n    }\n    *out++ = ')';\n    return out;\n  }\n};\nFMT_END_NAMESPACE\n#endif  // __cpp_lib_expected\n\n#ifdef __cpp_lib_source_location\nFMT_BEGIN_NAMESPACE\nFMT_EXPORT\ntemplate <> struct formatter<std::source_location> {\n  FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); }\n\n  template <typename FormatContext>\n  auto format(const std::source_location& loc, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    auto out = ctx.out();\n    out = detail::write(out, loc.file_name());\n    out = detail::write(out, ':');\n    out = detail::write<char>(out, loc.line());\n    out = detail::write(out, ':');\n    out = detail::write<char>(out, loc.column());\n    out = detail::write(out, \": \");\n    out = detail::write(out, loc.function_name());\n    return out;\n  }\n};\nFMT_END_NAMESPACE\n#endif\n\n#if FMT_CPP_LIB_VARIANT\nFMT_BEGIN_NAMESPACE\nnamespace detail {\n\ntemplate <typename T>\nusing variant_index_sequence =\n    std::make_index_sequence<std::variant_size<T>::value>;\n\ntemplate <typename> struct is_variant_like_ : std::false_type {};\ntemplate <typename... Types>\nstruct is_variant_like_<std::variant<Types...>> : std::true_type {};\n\n// formattable element check.\ntemplate <typename T, typename C> class is_variant_formattable_ {\n  template <std::size_t... Is>\n  static std::conjunction<\n      is_formattable<std::variant_alternative_t<Is, T>, C>...>\n      check(std::index_sequence<Is...>);\n\n public:\n  static constexpr const bool value =\n      decltype(check(variant_index_sequence<T>{}))::value;\n};\n\n}  // namespace detail\n\ntemplate <typename T> struct is_variant_like {\n  static constexpr const bool value = detail::is_variant_like_<T>::value;\n};\n\ntemplate <typename T, typename C> struct is_variant_formattable {\n  static constexpr const bool value =\n      detail::is_variant_formattable_<T, C>::value;\n};\n\nFMT_EXPORT\ntemplate <typename Char> struct formatter<std::monostate, Char> {\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    return ctx.begin();\n  }\n\n  template <typename FormatContext>\n  auto format(const std::monostate&, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    return detail::write<Char>(ctx.out(), \"monostate\");\n  }\n};\n\nFMT_EXPORT\ntemplate <typename Variant, typename Char>\nstruct formatter<\n    Variant, Char,\n    std::enable_if_t<std::conjunction_v<\n        is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> {\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    return ctx.begin();\n  }\n\n  template <typename FormatContext>\n  auto format(const Variant& value, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    auto out = ctx.out();\n\n    out = detail::write<Char>(out, \"variant(\");\n    FMT_TRY {\n      std::visit(\n          [&](const auto& v) {\n            out = detail::write_escaped_alternative<Char>(out, v);\n          },\n          value);\n    }\n    FMT_CATCH(const std::bad_variant_access&) {\n      detail::write<Char>(out, \"valueless by exception\");\n    }\n    *out++ = ')';\n    return out;\n  }\n};\nFMT_END_NAMESPACE\n#endif  // FMT_CPP_LIB_VARIANT\n\nFMT_BEGIN_NAMESPACE\nFMT_EXPORT\ntemplate <> struct formatter<std::error_code> {\n private:\n  format_specs specs_;\n  detail::arg_ref<char> width_ref_;\n\n public:\n  FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {\n    auto it = ctx.begin(), end = ctx.end();\n    if (it == end) return it;\n\n    it = detail::parse_align(it, end, specs_);\n    if (it == end) return it;\n\n    char c = *it;\n    if ((c >= '0' && c <= '9') || c == '{')\n      it = detail::parse_width(it, end, specs_, width_ref_, ctx);\n    return it;\n  }\n\n  template <typename FormatContext>\n  FMT_CONSTEXPR20 auto format(const std::error_code& ec,\n                              FormatContext& ctx) const -> decltype(ctx.out()) {\n    auto specs = specs_;\n    detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,\n                                ctx);\n    memory_buffer buf;\n    buf.append(string_view(ec.category().name()));\n    buf.push_back(':');\n    detail::write<char>(appender(buf), ec.value());\n    return detail::write<char>(ctx.out(), string_view(buf.data(), buf.size()),\n                               specs);\n  }\n};\n\n#if FMT_USE_RTTI\nnamespace detail {\n\ntemplate <typename Char, typename OutputIt>\nauto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {\n#  ifdef FMT_HAS_ABI_CXA_DEMANGLE\n  int status = 0;\n  std::size_t size = 0;\n  std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(\n      abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);\n\n  string_view demangled_name_view;\n  if (demangled_name_ptr) {\n    demangled_name_view = demangled_name_ptr.get();\n\n    // Normalization of stdlib inline namespace names.\n    // libc++ inline namespaces.\n    //  std::__1::*       -> std::*\n    //  std::__1::__fs::* -> std::*\n    // libstdc++ inline namespaces.\n    //  std::__cxx11::*             -> std::*\n    //  std::filesystem::__cxx11::* -> std::filesystem::*\n    if (demangled_name_view.starts_with(\"std::\")) {\n      char* begin = demangled_name_ptr.get();\n      char* to = begin + 5;  // std::\n      for (char *from = to, *end = begin + demangled_name_view.size();\n           from < end;) {\n        // This is safe, because demangled_name is NUL-terminated.\n        if (from[0] == '_' && from[1] == '_') {\n          char* next = from + 1;\n          while (next < end && *next != ':') next++;\n          if (next[0] == ':' && next[1] == ':') {\n            from = next + 2;\n            continue;\n          }\n        }\n        *to++ = *from++;\n      }\n      demangled_name_view = {begin, detail::to_unsigned(to - begin)};\n    }\n  } else {\n    demangled_name_view = string_view(ti.name());\n  }\n  return detail::write_bytes<Char>(out, demangled_name_view);\n#  elif FMT_MSC_VERSION\n  const string_view demangled_name(ti.name());\n  for (std::size_t i = 0; i < demangled_name.size(); ++i) {\n    auto sub = demangled_name;\n    sub.remove_prefix(i);\n    if (sub.starts_with(\"enum \")) {\n      i += 4;\n      continue;\n    }\n    if (sub.starts_with(\"class \") || sub.starts_with(\"union \")) {\n      i += 5;\n      continue;\n    }\n    if (sub.starts_with(\"struct \")) {\n      i += 6;\n      continue;\n    }\n    if (*sub.begin() != ' ') *out++ = *sub.begin();\n  }\n  return out;\n#  else\n  return detail::write_bytes<Char>(out, string_view(ti.name()));\n#  endif\n}\n\n}  // namespace detail\n\nFMT_EXPORT\ntemplate <typename Char>\nstruct formatter<std::type_info, Char  // DEPRECATED! Mixing code unit types.\n                 > {\n public:\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    return ctx.begin();\n  }\n\n  template <typename Context>\n  auto format(const std::type_info& ti, Context& ctx) const\n      -> decltype(ctx.out()) {\n    return detail::write_demangled_name<Char>(ctx.out(), ti);\n  }\n};\n#endif\n\nFMT_EXPORT\ntemplate <typename T, typename Char>\nstruct formatter<\n    T, Char,  // DEPRECATED! Mixing code unit types.\n    typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {\n private:\n  bool with_typename_ = false;\n\n public:\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    auto it = ctx.begin();\n    auto end = ctx.end();\n    if (it == end || *it == '}') return it;\n    if (*it == 't') {\n      ++it;\n      with_typename_ = FMT_USE_RTTI != 0;\n    }\n    return it;\n  }\n\n  template <typename Context>\n  auto format(const std::exception& ex, Context& ctx) const\n      -> decltype(ctx.out()) {\n    auto out = ctx.out();\n#if FMT_USE_RTTI\n    if (with_typename_) {\n      out = detail::write_demangled_name<Char>(out, typeid(ex));\n      *out++ = ':';\n      *out++ = ' ';\n    }\n#endif\n    return detail::write_bytes<Char>(out, string_view(ex.what()));\n  }\n};\n\nnamespace detail {\n\ntemplate <typename T, typename Enable = void>\nstruct has_flip : std::false_type {};\n\ntemplate <typename T>\nstruct has_flip<T, void_t<decltype(std::declval<T>().flip())>>\n    : std::true_type {};\n\ntemplate <typename T> struct is_bit_reference_like {\n  static constexpr const bool value =\n      std::is_convertible<T, bool>::value &&\n      std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;\n};\n\n#ifdef _LIBCPP_VERSION\n\n// Workaround for libc++ incompatibility with C++ standard.\n// According to the Standard, `bitset::operator[] const` returns bool.\ntemplate <typename C>\nstruct is_bit_reference_like<std::__bit_const_reference<C>> {\n  static constexpr const bool value = true;\n};\n\n#endif\n\n}  // namespace detail\n\n// We can't use std::vector<bool, Allocator>::reference and\n// std::bitset<N>::reference because the compiler can't deduce Allocator and N\n// in partial specialization.\nFMT_EXPORT\ntemplate <typename BitRef, typename Char>\nstruct formatter<BitRef, Char,\n                 enable_if_t<detail::is_bit_reference_like<BitRef>::value>>\n    : formatter<bool, Char> {\n  template <typename FormatContext>\n  FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    return formatter<bool, Char>::format(v, ctx);\n  }\n};\n\ntemplate <typename T, typename Deleter>\nauto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {\n  return p.get();\n}\ntemplate <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {\n  return p.get();\n}\n\nFMT_EXPORT\ntemplate <typename T, typename Char>\nstruct formatter<std::atomic<T>, Char,\n                 enable_if_t<is_formattable<T, Char>::value>>\n    : formatter<T, Char> {\n  template <typename FormatContext>\n  auto format(const std::atomic<T>& v, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    return formatter<T, Char>::format(v.load(), ctx);\n  }\n};\n\n#ifdef __cpp_lib_atomic_flag_test\nFMT_EXPORT\ntemplate <typename Char>\nstruct formatter<std::atomic_flag, Char> : formatter<bool, Char> {\n  template <typename FormatContext>\n  auto format(const std::atomic_flag& v, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    return formatter<bool, Char>::format(v.test(), ctx);\n  }\n};\n#endif  // __cpp_lib_atomic_flag_test\n\nFMT_EXPORT\ntemplate <typename T, typename Char> struct formatter<std::complex<T>, Char> {\n private:\n  detail::dynamic_format_specs<Char> specs_;\n\n  template <typename FormatContext, typename OutputIt>\n  FMT_CONSTEXPR auto do_format(const std::complex<T>& c,\n                               detail::dynamic_format_specs<Char>& specs,\n                               FormatContext& ctx, OutputIt out) const\n      -> OutputIt {\n    if (c.real() != 0) {\n      *out++ = Char('(');\n      out = detail::write<Char>(out, c.real(), specs, ctx.locale());\n      specs.set_sign(sign::plus);\n      out = detail::write<Char>(out, c.imag(), specs, ctx.locale());\n      if (!detail::isfinite(c.imag())) *out++ = Char(' ');\n      *out++ = Char('i');\n      *out++ = Char(')');\n      return out;\n    }\n    out = detail::write<Char>(out, c.imag(), specs, ctx.locale());\n    if (!detail::isfinite(c.imag())) *out++ = Char(' ');\n    *out++ = Char('i');\n    return out;\n  }\n\n public:\n  FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {\n    if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin();\n    return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,\n                              detail::type_constant<T, Char>::value);\n  }\n\n  template <typename FormatContext>\n  auto format(const std::complex<T>& c, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    auto specs = specs_;\n    if (specs.dynamic()) {\n      detail::handle_dynamic_spec(specs.dynamic_width(), specs.width,\n                                  specs.width_ref, ctx);\n      detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision,\n                                  specs.precision_ref, ctx);\n    }\n\n    if (specs.width == 0) return do_format(c, specs, ctx, ctx.out());\n    auto buf = basic_memory_buffer<Char>();\n\n    auto outer_specs = format_specs();\n    outer_specs.width = specs.width;\n    outer_specs.copy_fill_from(specs);\n    outer_specs.set_align(specs.align());\n\n    specs.width = 0;\n    specs.set_fill({});\n    specs.set_align(align::none);\n\n    do_format(c, specs, ctx, basic_appender<Char>(buf));\n    return detail::write<Char>(ctx.out(),\n                               basic_string_view<Char>(buf.data(), buf.size()),\n                               outer_specs);\n  }\n};\n\nFMT_EXPORT\ntemplate <typename T, typename Char>\nstruct formatter<std::reference_wrapper<T>, Char,\n                 enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value>>\n    : formatter<remove_cvref_t<T>, Char> {\n  template <typename FormatContext>\n  auto format(std::reference_wrapper<T> ref, FormatContext& ctx) const\n      -> decltype(ctx.out()) {\n    return formatter<remove_cvref_t<T>, Char>::format(ref.get(), ctx);\n  }\n};\n\nFMT_END_NAMESPACE\n#endif  // FMT_STD_H_\n"
  },
  {
    "path": "dependencies/fmt/fmt/include/fmt/xchar.h",
    "content": "// Formatting library for C++ - optional wchar_t and exotic character support\n//\n// Copyright (c) 2012 - present, Victor Zverovich\n// All rights reserved.\n//\n// For the license information refer to format.h.\n\n#ifndef FMT_XCHAR_H_\n#define FMT_XCHAR_H_\n\n#include \"color.h\"\n#include \"format.h\"\n#include \"ostream.h\"\n#include \"ranges.h\"\n\n#ifndef FMT_MODULE\n#  include <cwchar>\n#  if FMT_USE_LOCALE\n#    include <locale>\n#  endif\n#endif\n\nFMT_BEGIN_NAMESPACE\nnamespace detail {\n\ntemplate <typename T>\nusing is_exotic_char = bool_constant<!std::is_same<T, char>::value>;\n\ntemplate <typename S, typename = void> struct format_string_char {};\n\ntemplate <typename S>\nstruct format_string_char<\n    S, void_t<decltype(sizeof(detail::to_string_view(std::declval<S>())))>> {\n  using type = char_t<S>;\n};\n\ntemplate <typename S>\nstruct format_string_char<\n    S, enable_if_t<std::is_base_of<detail::compile_string, S>::value>> {\n  using type = typename S::char_type;\n};\n\ntemplate <typename S>\nusing format_string_char_t = typename format_string_char<S>::type;\n\ninline auto write_loc(basic_appender<wchar_t> out, loc_value value,\n                      const format_specs& specs, locale_ref loc) -> bool {\n#if FMT_USE_LOCALE\n  auto& numpunct =\n      std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());\n  auto separator = std::wstring();\n  auto grouping = numpunct.grouping();\n  if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep());\n  return value.visit(loc_writer<wchar_t>{out, specs, separator, grouping, {}});\n#endif\n  return false;\n}\n}  // namespace detail\n\nFMT_BEGIN_EXPORT\n\nusing wstring_view = basic_string_view<wchar_t>;\nusing wformat_parse_context = parse_context<wchar_t>;\nusing wformat_context = buffered_context<wchar_t>;\nusing wformat_args = basic_format_args<wformat_context>;\nusing wmemory_buffer = basic_memory_buffer<wchar_t>;\n\ntemplate <typename Char, typename... T> struct basic_fstring {\n private:\n  basic_string_view<Char> str_;\n\n  static constexpr int num_static_named_args =\n      detail::count_static_named_args<T...>();\n\n  using checker = detail::format_string_checker<\n      Char, static_cast<int>(sizeof...(T)), num_static_named_args,\n      num_static_named_args != detail::count_named_args<T...>()>;\n\n  using arg_pack = detail::arg_pack<T...>;\n\n public:\n  using t = basic_fstring;\n\n  template <typename S,\n            FMT_ENABLE_IF(\n                std::is_convertible<const S&, basic_string_view<Char>>::value)>\n  FMT_CONSTEVAL FMT_ALWAYS_INLINE basic_fstring(const S& s) : str_(s) {\n    if (FMT_USE_CONSTEVAL)\n      detail::parse_format_string<Char>(s, checker(s, arg_pack()));\n  }\n  template <typename S,\n            FMT_ENABLE_IF(std::is_base_of<detail::compile_string, S>::value&&\n                              std::is_same<typename S::char_type, Char>::value)>\n  FMT_ALWAYS_INLINE basic_fstring(const S&) : str_(S()) {\n    FMT_CONSTEXPR auto sv = basic_string_view<Char>(S());\n    FMT_CONSTEXPR int ignore =\n        (parse_format_string(sv, checker(sv, arg_pack())), 0);\n    detail::ignore_unused(ignore);\n  }\n  basic_fstring(runtime_format_string<Char> fmt) : str_(fmt.str) {}\n\n  operator basic_string_view<Char>() const { return str_; }\n  auto get() const -> basic_string_view<Char> { return str_; }\n};\n\ntemplate <typename Char, typename... T>\nusing basic_format_string = basic_fstring<Char, T...>;\n\ntemplate <typename... T>\nusing wformat_string = typename basic_format_string<wchar_t, T...>::t;\ninline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {\n  return {{s}};\n}\n\ntemplate <> struct is_char<wchar_t> : std::true_type {};\ntemplate <> struct is_char<char16_t> : std::true_type {};\ntemplate <> struct is_char<char32_t> : std::true_type {};\n\n#ifdef __cpp_char8_t\ntemplate <> struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled> {};\n#endif\n\ntemplate <typename... T>\nconstexpr auto make_wformat_args(T&... args)\n    -> decltype(fmt::make_format_args<wformat_context>(args...)) {\n  return fmt::make_format_args<wformat_context>(args...);\n}\n\n#if !FMT_USE_NONTYPE_TEMPLATE_ARGS\ninline namespace literals {\ninline auto operator\"\"_a(const wchar_t* s, size_t) -> detail::udl_arg<wchar_t> {\n  return {s};\n}\n}  // namespace literals\n#endif\n\ntemplate <typename It, typename Sentinel>\nauto join(It begin, Sentinel end, wstring_view sep)\n    -> join_view<It, Sentinel, wchar_t> {\n  return {begin, end, sep};\n}\n\ntemplate <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>\nauto join(Range&& range, wstring_view sep)\n    -> join_view<decltype(std::begin(range)), decltype(std::end(range)),\n                 wchar_t> {\n  return join(std::begin(range), std::end(range), sep);\n}\n\ntemplate <typename T>\nauto join(std::initializer_list<T> list, wstring_view sep)\n    -> join_view<const T*, const T*, wchar_t> {\n  return join(std::begin(list), std::end(list), sep);\n}\n\ntemplate <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>\nauto join(const Tuple& tuple, basic_string_view<wchar_t> sep)\n    -> tuple_join_view<wchar_t, Tuple> {\n  return {tuple, sep};\n}\n\ntemplate <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>\nauto vformat(basic_string_view<Char> fmt,\n             typename detail::vformat_args<Char>::type args)\n    -> std::basic_string<Char> {\n  auto buf = basic_memory_buffer<Char>();\n  detail::vformat_to(buf, fmt, args);\n  return {buf.data(), buf.size()};\n}\n\ntemplate <typename... T>\nauto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {\n  return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...));\n}\n\ntemplate <typename OutputIt, typename... T>\nauto format_to(OutputIt out, wformat_string<T...> fmt, T&&... args)\n    -> OutputIt {\n  return vformat_to(out, fmt::wstring_view(fmt),\n                    fmt::make_wformat_args(args...));\n}\n\n// Pass char_t as a default template parameter instead of using\n// std::basic_string<char_t<S>> to reduce the symbol size.\ntemplate <typename S, typename... T,\n          typename Char = detail::format_string_char_t<S>,\n          FMT_ENABLE_IF(!std::is_same<Char, char>::value &&\n                        !std::is_same<Char, wchar_t>::value)>\nauto format(const S& fmt, T&&... args) -> std::basic_string<Char> {\n  return vformat(detail::to_string_view(fmt),\n                 fmt::make_format_args<buffered_context<Char>>(args...));\n}\n\ntemplate <typename Locale, typename S,\n          typename Char = detail::format_string_char_t<S>,\n          FMT_ENABLE_IF(detail::is_locale<Locale>::value&&\n                            detail::is_exotic_char<Char>::value)>\ninline auto vformat(const Locale& loc, const S& fmt,\n                    typename detail::vformat_args<Char>::type args)\n    -> std::basic_string<Char> {\n  auto buf = basic_memory_buffer<Char>();\n  detail::vformat_to(buf, detail::to_string_view(fmt), args,\n                     detail::locale_ref(loc));\n  return {buf.data(), buf.size()};\n}\n\ntemplate <typename Locale, typename S, typename... T,\n          typename Char = detail::format_string_char_t<S>,\n          FMT_ENABLE_IF(detail::is_locale<Locale>::value&&\n                            detail::is_exotic_char<Char>::value)>\ninline auto format(const Locale& loc, const S& fmt, T&&... args)\n    -> std::basic_string<Char> {\n  return vformat(loc, detail::to_string_view(fmt),\n                 fmt::make_format_args<buffered_context<Char>>(args...));\n}\n\ntemplate <typename OutputIt, typename S,\n          typename Char = detail::format_string_char_t<S>,\n          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&\n                            detail::is_exotic_char<Char>::value)>\nauto vformat_to(OutputIt out, const S& fmt,\n                typename detail::vformat_args<Char>::type args) -> OutputIt {\n  auto&& buf = detail::get_buffer<Char>(out);\n  detail::vformat_to(buf, detail::to_string_view(fmt), args);\n  return detail::get_iterator(buf, out);\n}\n\ntemplate <typename OutputIt, typename S, typename... T,\n          typename Char = detail::format_string_char_t<S>,\n          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value &&\n                        !std::is_same<Char, char>::value &&\n                        !std::is_same<Char, wchar_t>::value)>\ninline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {\n  return vformat_to(out, detail::to_string_view(fmt),\n                    fmt::make_format_args<buffered_context<Char>>(args...));\n}\n\ntemplate <typename Locale, typename S, typename OutputIt, typename... Args,\n          typename Char = detail::format_string_char_t<S>,\n          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&\n                            detail::is_locale<Locale>::value&&\n                                detail::is_exotic_char<Char>::value)>\ninline auto vformat_to(OutputIt out, const Locale& loc, const S& fmt,\n                       typename detail::vformat_args<Char>::type args)\n    -> OutputIt {\n  auto&& buf = detail::get_buffer<Char>(out);\n  vformat_to(buf, detail::to_string_view(fmt), args, detail::locale_ref(loc));\n  return detail::get_iterator(buf, out);\n}\n\ntemplate <typename Locale, typename OutputIt, typename S, typename... T,\n          typename Char = detail::format_string_char_t<S>,\n          bool enable = detail::is_output_iterator<OutputIt, Char>::value &&\n                        detail::is_locale<Locale>::value &&\n                        detail::is_exotic_char<Char>::value>\ninline auto format_to(OutputIt out, const Locale& loc, const S& fmt,\n                      T&&... args) ->\n    typename std::enable_if<enable, OutputIt>::type {\n  return vformat_to(out, loc, detail::to_string_view(fmt),\n                    fmt::make_format_args<buffered_context<Char>>(args...));\n}\n\ntemplate <typename OutputIt, typename Char, typename... Args,\n          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&\n                            detail::is_exotic_char<Char>::value)>\ninline auto vformat_to_n(OutputIt out, size_t n, basic_string_view<Char> fmt,\n                         typename detail::vformat_args<Char>::type args)\n    -> format_to_n_result<OutputIt> {\n  using traits = detail::fixed_buffer_traits;\n  auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);\n  detail::vformat_to(buf, fmt, args);\n  return {buf.out(), buf.count()};\n}\n\ntemplate <typename OutputIt, typename S, typename... T,\n          typename Char = detail::format_string_char_t<S>,\n          FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&\n                            detail::is_exotic_char<Char>::value)>\ninline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)\n    -> format_to_n_result<OutputIt> {\n  return vformat_to_n(out, n, fmt::basic_string_view<Char>(fmt),\n                      fmt::make_format_args<buffered_context<Char>>(args...));\n}\n\ntemplate <typename S, typename... T,\n          typename Char = detail::format_string_char_t<S>,\n          FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>\ninline auto formatted_size(const S& fmt, T&&... args) -> size_t {\n  auto buf = detail::counting_buffer<Char>();\n  detail::vformat_to(buf, detail::to_string_view(fmt),\n                     fmt::make_format_args<buffered_context<Char>>(args...));\n  return buf.count();\n}\n\ninline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {\n  auto buf = wmemory_buffer();\n  detail::vformat_to(buf, fmt, args);\n  buf.push_back(L'\\0');\n  if (std::fputws(buf.data(), f) == -1)\n    FMT_THROW(system_error(errno, FMT_STRING(\"cannot write to file\")));\n}\n\ninline void vprint(wstring_view fmt, wformat_args args) {\n  vprint(stdout, fmt, args);\n}\n\ntemplate <typename... T>\nvoid print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {\n  return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...));\n}\n\ntemplate <typename... T> void print(wformat_string<T...> fmt, T&&... args) {\n  return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));\n}\n\ntemplate <typename... T>\nvoid println(std::FILE* f, wformat_string<T...> fmt, T&&... args) {\n  return print(f, L\"{}\\n\", fmt::format(fmt, std::forward<T>(args)...));\n}\n\ntemplate <typename... T> void println(wformat_string<T...> fmt, T&&... args) {\n  return print(L\"{}\\n\", fmt::format(fmt, std::forward<T>(args)...));\n}\n\ninline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args)\n    -> std::wstring {\n  auto buf = wmemory_buffer();\n  detail::vformat_to(buf, ts, fmt, args);\n  return {buf.data(), buf.size()};\n}\n\ntemplate <typename... T>\ninline auto format(const text_style& ts, wformat_string<T...> fmt, T&&... args)\n    -> std::wstring {\n  return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));\n}\n\ntemplate <typename... T>\nFMT_DEPRECATED void print(std::FILE* f, const text_style& ts,\n                          wformat_string<T...> fmt, const T&... args) {\n  vprint(f, ts, fmt, fmt::make_wformat_args(args...));\n}\n\ntemplate <typename... T>\nFMT_DEPRECATED void print(const text_style& ts, wformat_string<T...> fmt,\n                          const T&... args) {\n  return print(stdout, ts, fmt, args...);\n}\n\ninline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) {\n  auto buffer = basic_memory_buffer<wchar_t>();\n  detail::vformat_to(buffer, fmt, args);\n  detail::write_buffer(os, buffer);\n}\n\ntemplate <typename... T>\nvoid print(std::wostream& os, wformat_string<T...> fmt, T&&... args) {\n  vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_t>>(args...));\n}\n\ntemplate <typename... T>\nvoid println(std::wostream& os, wformat_string<T...> fmt, T&&... args) {\n  print(os, L\"{}\\n\", fmt::format(fmt, std::forward<T>(args)...));\n}\n\n/// Converts `value` to `std::wstring` using the default format for type `T`.\ntemplate <typename T> inline auto to_wstring(const T& value) -> std::wstring {\n  return format(FMT_STRING(L\"{}\"), value);\n}\nFMT_END_EXPORT\nFMT_END_NAMESPACE\n\n#endif  // FMT_XCHAR_H_\n"
  },
  {
    "path": "dependencies/fmt/fmt/src/format.cc",
    "content": "// Formatting library for C++\n//\n// Copyright (c) 2012 - 2016, Victor Zverovich\n// All rights reserved.\n//\n// For the license information refer to format.h.\n\n#include \"fmt/format-inl.h\"\n\nFMT_BEGIN_NAMESPACE\nnamespace detail {\n\ntemplate FMT_API auto dragonbox::to_decimal(float x) noexcept\n    -> dragonbox::decimal_fp<float>;\ntemplate FMT_API auto dragonbox::to_decimal(double x) noexcept\n    -> dragonbox::decimal_fp<double>;\n\n#if FMT_USE_LOCALE\n// DEPRECATED! locale_ref in the detail namespace\ntemplate FMT_API locale_ref::locale_ref(const std::locale& loc);\ntemplate FMT_API auto locale_ref::get<std::locale>() const -> std::locale;\n#endif\n\n// Explicit instantiations for char.\n\ntemplate FMT_API auto thousands_sep_impl(locale_ref)\n    -> thousands_sep_result<char>;\ntemplate FMT_API auto decimal_point_impl(locale_ref) -> char;\n\n// DEPRECATED!\ntemplate FMT_API void buffer<char>::append(const char*, const char*);\n\n// DEPRECATED!\ntemplate FMT_API void vformat_to(buffer<char>&, string_view,\n                                 typename vformat_args<>::type, locale_ref);\n\n// Explicit instantiations for wchar_t.\n\ntemplate FMT_API auto thousands_sep_impl(locale_ref)\n    -> thousands_sep_result<wchar_t>;\ntemplate FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;\n\ntemplate FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);\n\n}  // namespace detail\nFMT_END_NAMESPACE\n"
  },
  {
    "path": "dependencies/fmt/version.txt",
    "content": "{fmt}\n11.1.3\nhttps://github.com/fmtlib/fmt\n\nonly required files are present.\n"
  },
  {
    "path": "dependencies/nlohmann_json/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nadd_library(nlohmann_json INTERFACE)\n\n# Relative sources are allowed only since cmake 3.13.\ntarget_sources(nlohmann_json INTERFACE\n\t${CMAKE_CURRENT_SOURCE_DIR}/nlohmann/json.hpp\n)\n\ntarget_include_directories(nlohmann_json\n\tSYSTEM INTERFACE\n\t\t\"${CMAKE_SOURCE_DIR}/dependencies/nlohmann_json\"\n)\n\n"
  },
  {
    "path": "dependencies/nlohmann_json/nlohmann/json.hpp",
    "content": "//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n/****************************************************************************\\\n * Note on documentation: The source files contain links to the online      *\n * documentation of the public API at https://json.nlohmann.me. This URL    *\n * contains the most recent documentation and should also be applicable to  *\n * previous versions; documentation for deprecated functions is not         *\n * removed, but marked deprecated. See \"Generate documentation\" section in  *\n * file docs/README.md.                                                     *\n\\****************************************************************************/\n\n#ifndef INCLUDE_NLOHMANN_JSON_HPP_\n#define INCLUDE_NLOHMANN_JSON_HPP_\n\n#include <algorithm> // all_of, find, for_each\n#include <cstddef> // nullptr_t, ptrdiff_t, size_t\n#include <functional> // hash, less\n#include <initializer_list> // initializer_list\n#ifndef JSON_NO_IO\n    #include <iosfwd> // istream, ostream\n#endif  // JSON_NO_IO\n#include <iterator> // random_access_iterator_tag\n#include <memory> // unique_ptr\n#include <string> // string, stoi, to_string\n#include <utility> // declval, forward, move, pair, swap\n#include <vector> // vector\n\n// #include <nlohmann/adl_serializer.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <utility>\n\n// #include <nlohmann/detail/abi_macros.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// This file contains all macro definitions affecting or depending on the ABI\n\n#ifndef JSON_SKIP_LIBRARY_VERSION_CHECK\n    #if defined(NLOHMANN_JSON_VERSION_MAJOR) && defined(NLOHMANN_JSON_VERSION_MINOR) && defined(NLOHMANN_JSON_VERSION_PATCH)\n        #if NLOHMANN_JSON_VERSION_MAJOR != 3 || NLOHMANN_JSON_VERSION_MINOR != 12 || NLOHMANN_JSON_VERSION_PATCH != 0\n            #warning \"Already included a different version of the library!\"\n        #endif\n    #endif\n#endif\n\n#define NLOHMANN_JSON_VERSION_MAJOR 3   // NOLINT(modernize-macro-to-enum)\n#define NLOHMANN_JSON_VERSION_MINOR 12  // NOLINT(modernize-macro-to-enum)\n#define NLOHMANN_JSON_VERSION_PATCH 0   // NOLINT(modernize-macro-to-enum)\n\n#ifndef JSON_DIAGNOSTICS\n    #define JSON_DIAGNOSTICS 0\n#endif\n\n#ifndef JSON_DIAGNOSTIC_POSITIONS\n    #define JSON_DIAGNOSTIC_POSITIONS 0\n#endif\n\n#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON\n    #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0\n#endif\n\n#if JSON_DIAGNOSTICS\n    #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS _diag\n#else\n    #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS\n#endif\n\n#if JSON_DIAGNOSTIC_POSITIONS\n    #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS _dp\n#else\n    #define NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS\n#endif\n\n#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON\n    #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON _ldvcmp\n#else\n    #define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON\n#endif\n\n#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION\n    #define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0\n#endif\n\n// Construct the namespace ABI tags component\n#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c\n#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \\\n    NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)\n\n#define NLOHMANN_JSON_ABI_TAGS                                       \\\n    NLOHMANN_JSON_ABI_TAGS_CONCAT(                                   \\\n            NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS,                       \\\n            NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \\\n            NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)\n\n// Construct the namespace version component\n#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \\\n    _v ## major ## _ ## minor ## _ ## patch\n#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(major, minor, patch) \\\n    NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch)\n\n#if NLOHMANN_JSON_NAMESPACE_NO_VERSION\n#define NLOHMANN_JSON_NAMESPACE_VERSION\n#else\n#define NLOHMANN_JSON_NAMESPACE_VERSION                                 \\\n    NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT(NLOHMANN_JSON_VERSION_MAJOR, \\\n                                           NLOHMANN_JSON_VERSION_MINOR, \\\n                                           NLOHMANN_JSON_VERSION_PATCH)\n#endif\n\n// Combine namespace components\n#define NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b) a ## b\n#define NLOHMANN_JSON_NAMESPACE_CONCAT(a, b) \\\n    NLOHMANN_JSON_NAMESPACE_CONCAT_EX(a, b)\n\n#ifndef NLOHMANN_JSON_NAMESPACE\n#define NLOHMANN_JSON_NAMESPACE               \\\n    nlohmann::NLOHMANN_JSON_NAMESPACE_CONCAT( \\\n            NLOHMANN_JSON_ABI_TAGS,           \\\n            NLOHMANN_JSON_NAMESPACE_VERSION)\n#endif\n\n#ifndef NLOHMANN_JSON_NAMESPACE_BEGIN\n#define NLOHMANN_JSON_NAMESPACE_BEGIN                \\\n    namespace nlohmann                               \\\n    {                                                \\\n    inline namespace NLOHMANN_JSON_NAMESPACE_CONCAT( \\\n                NLOHMANN_JSON_ABI_TAGS,              \\\n                NLOHMANN_JSON_NAMESPACE_VERSION)     \\\n    {\n#endif\n\n#ifndef NLOHMANN_JSON_NAMESPACE_END\n#define NLOHMANN_JSON_NAMESPACE_END                                     \\\n    }  /* namespace (inline namespace) NOLINT(readability/namespace) */ \\\n    }  // namespace nlohmann\n#endif\n\n// #include <nlohmann/detail/conversions/from_json.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // transform\n#include <array> // array\n#include <forward_list> // forward_list\n#include <iterator> // inserter, front_inserter, end\n#include <map> // map\n#ifdef JSON_HAS_CPP_17\n    #include <optional> // optional\n#endif\n#include <string> // string\n#include <tuple> // tuple, make_tuple\n#include <type_traits> // is_arithmetic, is_same, is_enum, underlying_type, is_convertible\n#include <unordered_map> // unordered_map\n#include <utility> // pair, declval\n#include <valarray> // valarray\n\n// #include <nlohmann/detail/exceptions.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // nullptr_t\n#include <exception> // exception\n#if JSON_DIAGNOSTICS\n    #include <numeric> // accumulate\n#endif\n#include <stdexcept> // runtime_error\n#include <string> // to_string\n#include <vector> // vector\n\n// #include <nlohmann/detail/value_t.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <array> // array\n#include <cstddef> // size_t\n#include <cstdint> // uint8_t\n#include <string> // string\n\n// #include <nlohmann/detail/macro_scope.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <utility> // declval, pair\n// #include <nlohmann/detail/meta/detected.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <type_traits>\n\n// #include <nlohmann/detail/meta/void_t.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename ...Ts> struct make_void\n{\n    using type = void;\n};\ntemplate<typename ...Ts> using void_t = typename make_void<Ts...>::type;\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n// https://en.cppreference.com/w/cpp/experimental/is_detected\nstruct nonesuch\n{\n    nonesuch() = delete;\n    ~nonesuch() = delete;\n    nonesuch(nonesuch const&) = delete;\n    nonesuch(nonesuch const&&) = delete;\n    void operator=(nonesuch const&) = delete;\n    void operator=(nonesuch&&) = delete;\n};\n\ntemplate<class Default,\n         class AlwaysVoid,\n         template<class...> class Op,\n         class... Args>\nstruct detector\n{\n    using value_t = std::false_type;\n    using type = Default;\n};\n\ntemplate<class Default, template<class...> class Op, class... Args>\nstruct detector<Default, void_t<Op<Args...>>, Op, Args...>\n{\n    using value_t = std::true_type;\n    using type = Op<Args...>;\n};\n\ntemplate<template<class...> class Op, class... Args>\nusing is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;\n\ntemplate<template<class...> class Op, class... Args>\nstruct is_detected_lazy : is_detected<Op, Args...> { };\n\ntemplate<template<class...> class Op, class... Args>\nusing detected_t = typename detector<nonesuch, void, Op, Args...>::type;\n\ntemplate<class Default, template<class...> class Op, class... Args>\nusing detected_or = detector<Default, void, Op, Args...>;\n\ntemplate<class Default, template<class...> class Op, class... Args>\nusing detected_or_t = typename detected_or<Default, Op, Args...>::type;\n\ntemplate<class Expected, template<class...> class Op, class... Args>\nusing is_detected_exact = std::is_same<Expected, detected_t<Op, Args...>>;\n\ntemplate<class To, template<class...> class Op, class... Args>\nusing is_detected_convertible =\n    std::is_convertible<detected_t<Op, Args...>, To>;\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/thirdparty/hedley/hedley.hpp>\n\n\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-FileCopyrightText: 2016 - 2021 Evan Nemerson <evan@nemerson.com>\n// SPDX-License-Identifier: MIT\n\n/* Hedley - https://nemequ.github.io/hedley\n * Created by Evan Nemerson <evan@nemerson.com>\n */\n\n#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15)\n#if defined(JSON_HEDLEY_VERSION)\n    #undef JSON_HEDLEY_VERSION\n#endif\n#define JSON_HEDLEY_VERSION 15\n\n#if defined(JSON_HEDLEY_STRINGIFY_EX)\n    #undef JSON_HEDLEY_STRINGIFY_EX\n#endif\n#define JSON_HEDLEY_STRINGIFY_EX(x) #x\n\n#if defined(JSON_HEDLEY_STRINGIFY)\n    #undef JSON_HEDLEY_STRINGIFY\n#endif\n#define JSON_HEDLEY_STRINGIFY(x) JSON_HEDLEY_STRINGIFY_EX(x)\n\n#if defined(JSON_HEDLEY_CONCAT_EX)\n    #undef JSON_HEDLEY_CONCAT_EX\n#endif\n#define JSON_HEDLEY_CONCAT_EX(a,b) a##b\n\n#if defined(JSON_HEDLEY_CONCAT)\n    #undef JSON_HEDLEY_CONCAT\n#endif\n#define JSON_HEDLEY_CONCAT(a,b) JSON_HEDLEY_CONCAT_EX(a,b)\n\n#if defined(JSON_HEDLEY_CONCAT3_EX)\n    #undef JSON_HEDLEY_CONCAT3_EX\n#endif\n#define JSON_HEDLEY_CONCAT3_EX(a,b,c) a##b##c\n\n#if defined(JSON_HEDLEY_CONCAT3)\n    #undef JSON_HEDLEY_CONCAT3\n#endif\n#define JSON_HEDLEY_CONCAT3(a,b,c) JSON_HEDLEY_CONCAT3_EX(a,b,c)\n\n#if defined(JSON_HEDLEY_VERSION_ENCODE)\n    #undef JSON_HEDLEY_VERSION_ENCODE\n#endif\n#define JSON_HEDLEY_VERSION_ENCODE(major,minor,revision) (((major) * 1000000) + ((minor) * 1000) + (revision))\n\n#if defined(JSON_HEDLEY_VERSION_DECODE_MAJOR)\n    #undef JSON_HEDLEY_VERSION_DECODE_MAJOR\n#endif\n#define JSON_HEDLEY_VERSION_DECODE_MAJOR(version) ((version) / 1000000)\n\n#if defined(JSON_HEDLEY_VERSION_DECODE_MINOR)\n    #undef JSON_HEDLEY_VERSION_DECODE_MINOR\n#endif\n#define JSON_HEDLEY_VERSION_DECODE_MINOR(version) (((version) % 1000000) / 1000)\n\n#if defined(JSON_HEDLEY_VERSION_DECODE_REVISION)\n    #undef JSON_HEDLEY_VERSION_DECODE_REVISION\n#endif\n#define JSON_HEDLEY_VERSION_DECODE_REVISION(version) ((version) % 1000)\n\n#if defined(JSON_HEDLEY_GNUC_VERSION)\n    #undef JSON_HEDLEY_GNUC_VERSION\n#endif\n#if defined(__GNUC__) && defined(__GNUC_PATCHLEVEL__)\n    #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__)\n#elif defined(__GNUC__)\n    #define JSON_HEDLEY_GNUC_VERSION JSON_HEDLEY_VERSION_ENCODE(__GNUC__, __GNUC_MINOR__, 0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_VERSION_CHECK)\n    #undef JSON_HEDLEY_GNUC_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_GNUC_VERSION)\n    #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GNUC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_MSVC_VERSION)\n    #undef JSON_HEDLEY_MSVC_VERSION\n#endif\n#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL)\n    #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100)\n#elif defined(_MSC_FULL_VER) && !defined(__ICL)\n    #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10)\n#elif defined(_MSC_VER) && !defined(__ICL)\n    #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0)\n#endif\n\n#if defined(JSON_HEDLEY_MSVC_VERSION_CHECK)\n    #undef JSON_HEDLEY_MSVC_VERSION_CHECK\n#endif\n#if !defined(JSON_HEDLEY_MSVC_VERSION)\n    #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0)\n#elif defined(_MSC_VER) && (_MSC_VER >= 1400)\n    #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch)))\n#elif defined(_MSC_VER) && (_MSC_VER >= 1200)\n    #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 1000000) + (minor * 10000) + (patch)))\n#else\n    #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_VER >= ((major * 100) + (minor)))\n#endif\n\n#if defined(JSON_HEDLEY_INTEL_VERSION)\n    #undef JSON_HEDLEY_INTEL_VERSION\n#endif\n#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL)\n    #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE)\n#elif defined(__INTEL_COMPILER) && !defined(__ICL)\n    #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0)\n#endif\n\n#if defined(JSON_HEDLEY_INTEL_VERSION_CHECK)\n    #undef JSON_HEDLEY_INTEL_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_INTEL_VERSION)\n    #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_INTEL_CL_VERSION)\n    #undef JSON_HEDLEY_INTEL_CL_VERSION\n#endif\n#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL)\n    #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0)\n#endif\n\n#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK)\n    #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_INTEL_CL_VERSION)\n    #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_PGI_VERSION)\n    #undef JSON_HEDLEY_PGI_VERSION\n#endif\n#if defined(__PGI) && defined(__PGIC__) && defined(__PGIC_MINOR__) && defined(__PGIC_PATCHLEVEL__)\n    #define JSON_HEDLEY_PGI_VERSION JSON_HEDLEY_VERSION_ENCODE(__PGIC__, __PGIC_MINOR__, __PGIC_PATCHLEVEL__)\n#endif\n\n#if defined(JSON_HEDLEY_PGI_VERSION_CHECK)\n    #undef JSON_HEDLEY_PGI_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_PGI_VERSION)\n    #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PGI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_PGI_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_SUNPRO_VERSION)\n    #undef JSON_HEDLEY_SUNPRO_VERSION\n#endif\n#if defined(__SUNPRO_C) && (__SUNPRO_C > 0x1000)\n    #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_C >> 16) & 0xf) * 10) + ((__SUNPRO_C >> 12) & 0xf), (((__SUNPRO_C >> 8) & 0xf) * 10) + ((__SUNPRO_C >> 4) & 0xf), (__SUNPRO_C & 0xf) * 10)\n#elif defined(__SUNPRO_C)\n    #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_C >> 8) & 0xf, (__SUNPRO_C >> 4) & 0xf, (__SUNPRO_C) & 0xf)\n#elif defined(__SUNPRO_CC) && (__SUNPRO_CC > 0x1000)\n    #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((((__SUNPRO_CC >> 16) & 0xf) * 10) + ((__SUNPRO_CC >> 12) & 0xf), (((__SUNPRO_CC >> 8) & 0xf) * 10) + ((__SUNPRO_CC >> 4) & 0xf), (__SUNPRO_CC & 0xf) * 10)\n#elif defined(__SUNPRO_CC)\n    #define JSON_HEDLEY_SUNPRO_VERSION JSON_HEDLEY_VERSION_ENCODE((__SUNPRO_CC >> 8) & 0xf, (__SUNPRO_CC >> 4) & 0xf, (__SUNPRO_CC) & 0xf)\n#endif\n\n#if defined(JSON_HEDLEY_SUNPRO_VERSION_CHECK)\n    #undef JSON_HEDLEY_SUNPRO_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_SUNPRO_VERSION)\n    #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_SUNPRO_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_SUNPRO_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION)\n    #undef JSON_HEDLEY_EMSCRIPTEN_VERSION\n#endif\n#if defined(__EMSCRIPTEN__)\n    #define JSON_HEDLEY_EMSCRIPTEN_VERSION JSON_HEDLEY_VERSION_ENCODE(__EMSCRIPTEN_major__, __EMSCRIPTEN_minor__, __EMSCRIPTEN_tiny__)\n#endif\n\n#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK)\n    #undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_EMSCRIPTEN_VERSION)\n    #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_EMSCRIPTEN_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_ARM_VERSION)\n    #undef JSON_HEDLEY_ARM_VERSION\n#endif\n#if defined(__CC_ARM) && defined(__ARMCOMPILER_VERSION)\n    #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCOMPILER_VERSION / 1000000, (__ARMCOMPILER_VERSION % 1000000) / 10000, (__ARMCOMPILER_VERSION % 10000) / 100)\n#elif defined(__CC_ARM) && defined(__ARMCC_VERSION)\n    #define JSON_HEDLEY_ARM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ARMCC_VERSION / 1000000, (__ARMCC_VERSION % 1000000) / 10000, (__ARMCC_VERSION % 10000) / 100)\n#endif\n\n#if defined(JSON_HEDLEY_ARM_VERSION_CHECK)\n    #undef JSON_HEDLEY_ARM_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_ARM_VERSION)\n    #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_ARM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_ARM_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_IBM_VERSION)\n    #undef JSON_HEDLEY_IBM_VERSION\n#endif\n#if defined(__ibmxl__)\n    #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__ibmxl_version__, __ibmxl_release__, __ibmxl_modification__)\n#elif defined(__xlC__) && defined(__xlC_ver__)\n    #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, (__xlC_ver__ >> 8) & 0xff)\n#elif defined(__xlC__)\n    #define JSON_HEDLEY_IBM_VERSION JSON_HEDLEY_VERSION_ENCODE(__xlC__ >> 8, __xlC__ & 0xff, 0)\n#endif\n\n#if defined(JSON_HEDLEY_IBM_VERSION_CHECK)\n    #undef JSON_HEDLEY_IBM_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_IBM_VERSION)\n    #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IBM_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_IBM_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_VERSION)\n    #undef JSON_HEDLEY_TI_VERSION\n#endif\n#if \\\n    defined(__TI_COMPILER_VERSION__) && \\\n    ( \\\n      defined(__TMS470__) || defined(__TI_ARM__) || \\\n      defined(__MSP430__) || \\\n      defined(__TMS320C2000__) \\\n    )\n#if (__TI_COMPILER_VERSION__ >= 16000000)\n    #define JSON_HEDLEY_TI_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n#endif\n\n#if defined(JSON_HEDLEY_TI_VERSION_CHECK)\n    #undef JSON_HEDLEY_TI_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_VERSION)\n    #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TI_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL2000_VERSION)\n    #undef JSON_HEDLEY_TI_CL2000_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C2000__)\n    #define JSON_HEDLEY_TI_CL2000_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL2000_VERSION_CHECK)\n    #undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_CL2000_VERSION)\n    #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL2000_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TI_CL2000_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL430_VERSION)\n    #undef JSON_HEDLEY_TI_CL430_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && defined(__MSP430__)\n    #define JSON_HEDLEY_TI_CL430_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL430_VERSION_CHECK)\n    #undef JSON_HEDLEY_TI_CL430_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_CL430_VERSION)\n    #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL430_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TI_CL430_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_ARMCL_VERSION)\n    #undef JSON_HEDLEY_TI_ARMCL_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && (defined(__TMS470__) || defined(__TI_ARM__))\n    #define JSON_HEDLEY_TI_ARMCL_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_ARMCL_VERSION_CHECK)\n    #undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_ARMCL_VERSION)\n    #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_ARMCL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL6X_VERSION)\n    #undef JSON_HEDLEY_TI_CL6X_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && defined(__TMS320C6X__)\n    #define JSON_HEDLEY_TI_CL6X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL6X_VERSION_CHECK)\n    #undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_CL6X_VERSION)\n    #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL6X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TI_CL6X_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL7X_VERSION)\n    #undef JSON_HEDLEY_TI_CL7X_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && defined(__C7000__)\n    #define JSON_HEDLEY_TI_CL7X_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_CL7X_VERSION_CHECK)\n    #undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_CL7X_VERSION)\n    #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CL7X_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TI_CL7X_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TI_CLPRU_VERSION)\n    #undef JSON_HEDLEY_TI_CLPRU_VERSION\n#endif\n#if defined(__TI_COMPILER_VERSION__) && defined(__PRU__)\n    #define JSON_HEDLEY_TI_CLPRU_VERSION JSON_HEDLEY_VERSION_ENCODE(__TI_COMPILER_VERSION__ / 1000000, (__TI_COMPILER_VERSION__ % 1000000) / 1000, (__TI_COMPILER_VERSION__ % 1000))\n#endif\n\n#if defined(JSON_HEDLEY_TI_CLPRU_VERSION_CHECK)\n    #undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TI_CLPRU_VERSION)\n    #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TI_CLPRU_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_CRAY_VERSION)\n    #undef JSON_HEDLEY_CRAY_VERSION\n#endif\n#if defined(_CRAYC)\n    #if defined(_RELEASE_PATCHLEVEL)\n        #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, _RELEASE_PATCHLEVEL)\n    #else\n        #define JSON_HEDLEY_CRAY_VERSION JSON_HEDLEY_VERSION_ENCODE(_RELEASE_MAJOR, _RELEASE_MINOR, 0)\n    #endif\n#endif\n\n#if defined(JSON_HEDLEY_CRAY_VERSION_CHECK)\n    #undef JSON_HEDLEY_CRAY_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_CRAY_VERSION)\n    #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_CRAY_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_CRAY_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_IAR_VERSION)\n    #undef JSON_HEDLEY_IAR_VERSION\n#endif\n#if defined(__IAR_SYSTEMS_ICC__)\n    #if __VER__ > 1000\n        #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000))\n    #else\n        #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0)\n    #endif\n#endif\n\n#if defined(JSON_HEDLEY_IAR_VERSION_CHECK)\n    #undef JSON_HEDLEY_IAR_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_IAR_VERSION)\n    #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_IAR_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_IAR_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_TINYC_VERSION)\n    #undef JSON_HEDLEY_TINYC_VERSION\n#endif\n#if defined(__TINYC__)\n    #define JSON_HEDLEY_TINYC_VERSION JSON_HEDLEY_VERSION_ENCODE(__TINYC__ / 1000, (__TINYC__ / 100) % 10, __TINYC__ % 100)\n#endif\n\n#if defined(JSON_HEDLEY_TINYC_VERSION_CHECK)\n    #undef JSON_HEDLEY_TINYC_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_TINYC_VERSION)\n    #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_TINYC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_TINYC_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_DMC_VERSION)\n    #undef JSON_HEDLEY_DMC_VERSION\n#endif\n#if defined(__DMC__)\n    #define JSON_HEDLEY_DMC_VERSION JSON_HEDLEY_VERSION_ENCODE(__DMC__ >> 8, (__DMC__ >> 4) & 0xf, __DMC__ & 0xf)\n#endif\n\n#if defined(JSON_HEDLEY_DMC_VERSION_CHECK)\n    #undef JSON_HEDLEY_DMC_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_DMC_VERSION)\n    #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_DMC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_DMC_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_COMPCERT_VERSION)\n    #undef JSON_HEDLEY_COMPCERT_VERSION\n#endif\n#if defined(__COMPCERT_VERSION__)\n    #define JSON_HEDLEY_COMPCERT_VERSION JSON_HEDLEY_VERSION_ENCODE(__COMPCERT_VERSION__ / 10000, (__COMPCERT_VERSION__ / 100) % 100, __COMPCERT_VERSION__ % 100)\n#endif\n\n#if defined(JSON_HEDLEY_COMPCERT_VERSION_CHECK)\n    #undef JSON_HEDLEY_COMPCERT_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_COMPCERT_VERSION)\n    #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_COMPCERT_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_COMPCERT_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_PELLES_VERSION)\n    #undef JSON_HEDLEY_PELLES_VERSION\n#endif\n#if defined(__POCC__)\n    #define JSON_HEDLEY_PELLES_VERSION JSON_HEDLEY_VERSION_ENCODE(__POCC__ / 100, __POCC__ % 100, 0)\n#endif\n\n#if defined(JSON_HEDLEY_PELLES_VERSION_CHECK)\n    #undef JSON_HEDLEY_PELLES_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_PELLES_VERSION)\n    #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_PELLES_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_MCST_LCC_VERSION)\n    #undef JSON_HEDLEY_MCST_LCC_VERSION\n#endif\n#if defined(__LCC__) && defined(__LCC_MINOR__)\n    #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__)\n#endif\n\n#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK)\n    #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_MCST_LCC_VERSION)\n    #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_VERSION)\n    #undef JSON_HEDLEY_GCC_VERSION\n#endif\n#if \\\n    defined(JSON_HEDLEY_GNUC_VERSION) && \\\n    !defined(__clang__) && \\\n    !defined(JSON_HEDLEY_INTEL_VERSION) && \\\n    !defined(JSON_HEDLEY_PGI_VERSION) && \\\n    !defined(JSON_HEDLEY_ARM_VERSION) && \\\n    !defined(JSON_HEDLEY_CRAY_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_CL430_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_CL2000_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \\\n    !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \\\n    !defined(__COMPCERT__) && \\\n    !defined(JSON_HEDLEY_MCST_LCC_VERSION)\n    #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION\n#endif\n\n#if defined(JSON_HEDLEY_GCC_VERSION_CHECK)\n    #undef JSON_HEDLEY_GCC_VERSION_CHECK\n#endif\n#if defined(JSON_HEDLEY_GCC_VERSION)\n    #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_GCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch))\n#else\n    #define JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) (0)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_ATTRIBUTE)\n    #undef JSON_HEDLEY_HAS_ATTRIBUTE\n#endif\n#if \\\n  defined(__has_attribute) && \\\n  ( \\\n    (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \\\n  )\n#  define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute)\n#else\n#  define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE)\n    #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE\n#endif\n#if defined(__has_attribute)\n    #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute)\n#else\n    #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_ATTRIBUTE)\n    #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE\n#endif\n#if defined(__has_attribute)\n    #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute)\n#else\n    #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE)\n    #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE\n#endif\n#if \\\n    defined(__has_cpp_attribute) && \\\n    defined(__cplusplus) && \\\n    (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0))\n    #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) __has_cpp_attribute(attribute)\n#else\n    #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute) (0)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS)\n    #undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS\n#endif\n#if !defined(__cplusplus) || !defined(__has_cpp_attribute)\n    #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0)\n#elif \\\n    !defined(JSON_HEDLEY_PGI_VERSION) && \\\n    !defined(JSON_HEDLEY_IAR_VERSION) && \\\n    (!defined(JSON_HEDLEY_SUNPRO_VERSION) || JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0)) && \\\n    (!defined(JSON_HEDLEY_MSVC_VERSION) || JSON_HEDLEY_MSVC_VERSION_CHECK(19,20,0))\n    #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(ns::attribute)\n#else\n    #define JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(ns,attribute) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE)\n    #undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE\n#endif\n#if defined(__has_cpp_attribute) && defined(__cplusplus)\n    #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute)\n#else\n    #define JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE)\n    #undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE\n#endif\n#if defined(__has_cpp_attribute) && defined(__cplusplus)\n    #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) __has_cpp_attribute(attribute)\n#else\n    #define JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_BUILTIN)\n    #undef JSON_HEDLEY_HAS_BUILTIN\n#endif\n#if defined(__has_builtin)\n    #define JSON_HEDLEY_HAS_BUILTIN(builtin) __has_builtin(builtin)\n#else\n    #define JSON_HEDLEY_HAS_BUILTIN(builtin) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_BUILTIN)\n    #undef JSON_HEDLEY_GNUC_HAS_BUILTIN\n#endif\n#if defined(__has_builtin)\n    #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin)\n#else\n    #define JSON_HEDLEY_GNUC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_BUILTIN)\n    #undef JSON_HEDLEY_GCC_HAS_BUILTIN\n#endif\n#if defined(__has_builtin)\n    #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) __has_builtin(builtin)\n#else\n    #define JSON_HEDLEY_GCC_HAS_BUILTIN(builtin,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_FEATURE)\n    #undef JSON_HEDLEY_HAS_FEATURE\n#endif\n#if defined(__has_feature)\n    #define JSON_HEDLEY_HAS_FEATURE(feature) __has_feature(feature)\n#else\n    #define JSON_HEDLEY_HAS_FEATURE(feature) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_FEATURE)\n    #undef JSON_HEDLEY_GNUC_HAS_FEATURE\n#endif\n#if defined(__has_feature)\n    #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature)\n#else\n    #define JSON_HEDLEY_GNUC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_FEATURE)\n    #undef JSON_HEDLEY_GCC_HAS_FEATURE\n#endif\n#if defined(__has_feature)\n    #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) __has_feature(feature)\n#else\n    #define JSON_HEDLEY_GCC_HAS_FEATURE(feature,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_EXTENSION)\n    #undef JSON_HEDLEY_HAS_EXTENSION\n#endif\n#if defined(__has_extension)\n    #define JSON_HEDLEY_HAS_EXTENSION(extension) __has_extension(extension)\n#else\n    #define JSON_HEDLEY_HAS_EXTENSION(extension) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_EXTENSION)\n    #undef JSON_HEDLEY_GNUC_HAS_EXTENSION\n#endif\n#if defined(__has_extension)\n    #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension)\n#else\n    #define JSON_HEDLEY_GNUC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_EXTENSION)\n    #undef JSON_HEDLEY_GCC_HAS_EXTENSION\n#endif\n#if defined(__has_extension)\n    #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) __has_extension(extension)\n#else\n    #define JSON_HEDLEY_GCC_HAS_EXTENSION(extension,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE)\n    #undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE\n#endif\n#if defined(__has_declspec_attribute)\n    #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) __has_declspec_attribute(attribute)\n#else\n    #define JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE)\n    #undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE\n#endif\n#if defined(__has_declspec_attribute)\n    #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute)\n#else\n    #define JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE)\n    #undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE\n#endif\n#if defined(__has_declspec_attribute)\n    #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) __has_declspec_attribute(attribute)\n#else\n    #define JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_HAS_WARNING)\n    #undef JSON_HEDLEY_HAS_WARNING\n#endif\n#if defined(__has_warning)\n    #define JSON_HEDLEY_HAS_WARNING(warning) __has_warning(warning)\n#else\n    #define JSON_HEDLEY_HAS_WARNING(warning) (0)\n#endif\n\n#if defined(JSON_HEDLEY_GNUC_HAS_WARNING)\n    #undef JSON_HEDLEY_GNUC_HAS_WARNING\n#endif\n#if defined(__has_warning)\n    #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning)\n#else\n    #define JSON_HEDLEY_GNUC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_GCC_HAS_WARNING)\n    #undef JSON_HEDLEY_GCC_HAS_WARNING\n#endif\n#if defined(__has_warning)\n    #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) __has_warning(warning)\n#else\n    #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if \\\n    (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \\\n    defined(__clang__) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \\\n    JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \\\n    (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR))\n    #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value)\n#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0)\n    #define JSON_HEDLEY_PRAGMA(value) __pragma(value)\n#else\n    #define JSON_HEDLEY_PRAGMA(value)\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH)\n    #undef JSON_HEDLEY_DIAGNOSTIC_PUSH\n#endif\n#if defined(JSON_HEDLEY_DIAGNOSTIC_POP)\n    #undef JSON_HEDLEY_DIAGNOSTIC_POP\n#endif\n#if defined(__clang__)\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"clang diagnostic push\")\n    #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"clang diagnostic pop\")\n#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"warning(push)\")\n    #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"warning(pop)\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"GCC diagnostic push\")\n    #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"GCC diagnostic pop\")\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push))\n    #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop))\n#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"push\")\n    #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"pop\")\n#elif \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"diag_push\")\n    #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"diag_pop\")\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma(\"warning(push)\")\n    #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma(\"warning(pop)\")\n#else\n    #define JSON_HEDLEY_DIAGNOSTIC_PUSH\n    #define JSON_HEDLEY_DIAGNOSTIC_POP\n#endif\n\n/* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for\n   HEDLEY INTERNAL USE ONLY.  API subject to change without notice. */\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_)\n    #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_\n#endif\n#if defined(__cplusplus)\n#  if JSON_HEDLEY_HAS_WARNING(\"-Wc++98-compat\")\n#    if JSON_HEDLEY_HAS_WARNING(\"-Wc++17-extensions\")\n#      if JSON_HEDLEY_HAS_WARNING(\"-Wc++1z-extensions\")\n#        define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++98-compat\\\"\") \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++17-extensions\\\"\") \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++1z-extensions\\\"\") \\\n    xpr \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#      else\n#        define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++98-compat\\\"\") \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++17-extensions\\\"\") \\\n    xpr \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#      endif\n#    else\n#      define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wc++98-compat\\\"\") \\\n    xpr \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#    endif\n#  endif\n#endif\n#if !defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(x) x\n#endif\n\n#if defined(JSON_HEDLEY_CONST_CAST)\n    #undef JSON_HEDLEY_CONST_CAST\n#endif\n#if defined(__cplusplus)\n#  define JSON_HEDLEY_CONST_CAST(T, expr) (const_cast<T>(expr))\n#elif \\\n  JSON_HEDLEY_HAS_WARNING(\"-Wcast-qual\") || \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n#  define JSON_HEDLEY_CONST_CAST(T, expr) (__extension__ ({ \\\n        JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n        JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL \\\n        ((T) (expr)); \\\n        JSON_HEDLEY_DIAGNOSTIC_POP \\\n    }))\n#else\n#  define JSON_HEDLEY_CONST_CAST(T, expr) ((T) (expr))\n#endif\n\n#if defined(JSON_HEDLEY_REINTERPRET_CAST)\n    #undef JSON_HEDLEY_REINTERPRET_CAST\n#endif\n#if defined(__cplusplus)\n    #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) (reinterpret_cast<T>(expr))\n#else\n    #define JSON_HEDLEY_REINTERPRET_CAST(T, expr) ((T) (expr))\n#endif\n\n#if defined(JSON_HEDLEY_STATIC_CAST)\n    #undef JSON_HEDLEY_STATIC_CAST\n#endif\n#if defined(__cplusplus)\n    #define JSON_HEDLEY_STATIC_CAST(T, expr) (static_cast<T>(expr))\n#else\n    #define JSON_HEDLEY_STATIC_CAST(T, expr) ((T) (expr))\n#endif\n\n#if defined(JSON_HEDLEY_CPP_CAST)\n    #undef JSON_HEDLEY_CPP_CAST\n#endif\n#if defined(__cplusplus)\n#  if JSON_HEDLEY_HAS_WARNING(\"-Wold-style-cast\")\n#    define JSON_HEDLEY_CPP_CAST(T, expr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wold-style-cast\\\"\") \\\n    ((T) (expr)) \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#  elif JSON_HEDLEY_IAR_VERSION_CHECK(8,3,0)\n#    define JSON_HEDLEY_CPP_CAST(T, expr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"diag_suppress=Pe137\") \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#  else\n#    define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr))\n#  endif\n#else\n#  define JSON_HEDLEY_CPP_CAST(T, expr) (expr)\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED)\n    #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wdeprecated-declarations\")\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"clang diagnostic ignored \\\"-Wdeprecated-declarations\\\"\")\n#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"warning(disable:1478 1786)\")\n#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786))\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"diag_suppress 1215,1216,1444,1445\")\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"diag_suppress 1215,1444\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"GCC diagnostic ignored \\\"-Wdeprecated-declarations\\\"\")\n#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996))\n#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"diag_suppress 1215,1444\")\n#elif \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"diag_suppress 1291,1718\")\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && !defined(__cplusplus)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"error_messages(off,E_DEPRECATED_ATT,E_DEPRECATED_ATT_MESS)\")\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) && defined(__cplusplus)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"error_messages(off,symdeprecated,symdeprecated2)\")\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"diag_suppress=Pe1444,Pe1215\")\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma(\"warn(disable:2241)\")\n#else\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS)\n    #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wunknown-pragmas\")\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"clang diagnostic ignored \\\"-Wunknown-pragmas\\\"\")\n#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"warning(disable:161)\")\n#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161))\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"diag_suppress 1675\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"GCC diagnostic ignored \\\"-Wunknown-pragmas\\\"\")\n#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:4068))\n#elif \\\n    JSON_HEDLEY_TI_VERSION_CHECK(16,9,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"diag_suppress 163\")\n#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"diag_suppress 163\")\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"diag_suppress=Pe161\")\n#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma(\"diag_suppress 161\")\n#else\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES)\n    #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wunknown-attributes\")\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"clang diagnostic ignored \\\"-Wunknown-attributes\\\"\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"GCC diagnostic ignored \\\"-Wdeprecated-declarations\\\"\")\n#elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"warning(disable:1292)\")\n#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292))\n#elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030))\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"diag_suppress 1097,1098\")\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"diag_suppress 1097\")\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"error_messages(off,attrskipunsup)\")\n#elif \\\n    JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"diag_suppress 1173\")\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"diag_suppress=Pe1097\")\n#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma(\"diag_suppress 1097\")\n#else\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL)\n    #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wcast-qual\")\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma(\"clang diagnostic ignored \\\"-Wcast-qual\\\"\")\n#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma(\"warning(disable:2203 2331)\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL _Pragma(\"GCC diagnostic ignored \\\"-Wcast-qual\\\"\")\n#else\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL\n#endif\n\n#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION)\n    #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wunused-function\")\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma(\"clang diagnostic ignored \\\"-Wunused-function\\\"\")\n#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma(\"GCC diagnostic ignored \\\"-Wunused-function\\\"\")\n#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505))\n#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma(\"diag_suppress 3142\")\n#else\n    #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION\n#endif\n\n#if defined(JSON_HEDLEY_DEPRECATED)\n    #undef JSON_HEDLEY_DEPRECATED\n#endif\n#if defined(JSON_HEDLEY_DEPRECATED_FOR)\n    #undef JSON_HEDLEY_DEPRECATED_FOR\n#endif\n#if \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated(\"Since \" # since))\n    #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated(\"Since \" #since \"; use \" #replacement))\n#elif \\\n    (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,13,0) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(18,1,0) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__(\"Since \" #since)))\n    #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__(\"Since \" #since \"; use \" #replacement)))\n#elif defined(__cplusplus) && (__cplusplus >= 201402L)\n    #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated(\"Since \" #since)]])\n    #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated(\"Since \" #since \"; use \" #replacement)]])\n#elif \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \\\n    JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)\n    #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__))\n    #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__))\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \\\n    JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated)\n    #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated)\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n    #define JSON_HEDLEY_DEPRECATED(since) _Pragma(\"deprecated\")\n    #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) _Pragma(\"deprecated\")\n#else\n    #define JSON_HEDLEY_DEPRECATED(since)\n    #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement)\n#endif\n\n#if defined(JSON_HEDLEY_UNAVAILABLE)\n    #undef JSON_HEDLEY_UNAVAILABLE\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__(\"Not available until \" #available_since)))\n#else\n    #define JSON_HEDLEY_UNAVAILABLE(available_since)\n#endif\n\n#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT)\n    #undef JSON_HEDLEY_WARN_UNUSED_RESULT\n#endif\n#if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG)\n    #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__))\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__))\n#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L)\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]])\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]])\n#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard)\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]])\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]])\n#elif defined(_Check_return_) /* SAL */\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_\n#else\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT\n    #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg)\n#endif\n\n#if defined(JSON_HEDLEY_SENTINEL)\n    #undef JSON_HEDLEY_SENTINEL\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position)))\n#else\n    #define JSON_HEDLEY_SENTINEL(position)\n#endif\n\n#if defined(JSON_HEDLEY_NO_RETURN)\n    #undef JSON_HEDLEY_NO_RETURN\n#endif\n#if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n    #define JSON_HEDLEY_NO_RETURN __noreturn\n#elif \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__))\n#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L\n    #define JSON_HEDLEY_NO_RETURN _Noreturn\n#elif defined(__cplusplus) && (__cplusplus >= 201103L)\n    #define JSON_HEDLEY_NO_RETURN JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[noreturn]])\n#elif \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(noreturn) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,2,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)\n    #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__))\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)\n    #define JSON_HEDLEY_NO_RETURN _Pragma(\"does_not_return\")\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_NO_RETURN __declspec(noreturn)\n#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus)\n    #define JSON_HEDLEY_NO_RETURN _Pragma(\"FUNC_NEVER_RETURNS;\")\n#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0)\n    #define JSON_HEDLEY_NO_RETURN __attribute((noreturn))\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0)\n    #define JSON_HEDLEY_NO_RETURN __declspec(noreturn)\n#else\n    #define JSON_HEDLEY_NO_RETURN\n#endif\n\n#if defined(JSON_HEDLEY_NO_ESCAPE)\n    #undef JSON_HEDLEY_NO_ESCAPE\n#endif\n#if JSON_HEDLEY_HAS_ATTRIBUTE(noescape)\n    #define JSON_HEDLEY_NO_ESCAPE __attribute__((__noescape__))\n#else\n    #define JSON_HEDLEY_NO_ESCAPE\n#endif\n\n#if defined(JSON_HEDLEY_UNREACHABLE)\n    #undef JSON_HEDLEY_UNREACHABLE\n#endif\n#if defined(JSON_HEDLEY_UNREACHABLE_RETURN)\n    #undef JSON_HEDLEY_UNREACHABLE_RETURN\n#endif\n#if defined(JSON_HEDLEY_ASSUME)\n    #undef JSON_HEDLEY_ASSUME\n#endif\n#if \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_ASSUME(expr) __assume(expr)\n#elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume)\n    #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr)\n#elif \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0)\n    #if defined(__cplusplus)\n        #define JSON_HEDLEY_ASSUME(expr) std::_nassert(expr)\n    #else\n        #define JSON_HEDLEY_ASSUME(expr) _nassert(expr)\n    #endif\n#endif\n#if \\\n    (JSON_HEDLEY_HAS_BUILTIN(__builtin_unreachable) && (!defined(JSON_HEDLEY_ARM_VERSION))) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \\\n    JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable()\n#elif defined(JSON_HEDLEY_ASSUME)\n    #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0)\n#endif\n#if !defined(JSON_HEDLEY_ASSUME)\n    #if defined(JSON_HEDLEY_UNREACHABLE)\n        #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, ((expr) ? 1 : (JSON_HEDLEY_UNREACHABLE(), 1)))\n    #else\n        #define JSON_HEDLEY_ASSUME(expr) JSON_HEDLEY_STATIC_CAST(void, expr)\n    #endif\n#endif\n#if defined(JSON_HEDLEY_UNREACHABLE)\n    #if  \\\n        JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \\\n        JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0)\n        #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (JSON_HEDLEY_STATIC_CAST(void, JSON_HEDLEY_ASSUME(0)), (value))\n    #else\n        #define JSON_HEDLEY_UNREACHABLE_RETURN(value) JSON_HEDLEY_UNREACHABLE()\n    #endif\n#else\n    #define JSON_HEDLEY_UNREACHABLE_RETURN(value) return (value)\n#endif\n#if !defined(JSON_HEDLEY_UNREACHABLE)\n    #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0)\n#endif\n\nJSON_HEDLEY_DIAGNOSTIC_PUSH\n#if JSON_HEDLEY_HAS_WARNING(\"-Wpedantic\")\n    #pragma clang diagnostic ignored \"-Wpedantic\"\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wc++98-compat-pedantic\") && defined(__cplusplus)\n    #pragma clang diagnostic ignored \"-Wc++98-compat-pedantic\"\n#endif\n#if JSON_HEDLEY_GCC_HAS_WARNING(\"-Wvariadic-macros\",4,0,0)\n    #if defined(__clang__)\n        #pragma clang diagnostic ignored \"-Wvariadic-macros\"\n    #elif defined(JSON_HEDLEY_GCC_VERSION)\n        #pragma GCC diagnostic ignored \"-Wvariadic-macros\"\n    #endif\n#endif\n#if defined(JSON_HEDLEY_NON_NULL)\n    #undef JSON_HEDLEY_NON_NULL\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(nonnull) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0)\n    #define JSON_HEDLEY_NON_NULL(...) __attribute__((__nonnull__(__VA_ARGS__)))\n#else\n    #define JSON_HEDLEY_NON_NULL(...)\n#endif\nJSON_HEDLEY_DIAGNOSTIC_POP\n\n#if defined(JSON_HEDLEY_PRINTF_FORMAT)\n    #undef JSON_HEDLEY_PRINTF_FORMAT\n#endif\n#if defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && !defined(__USE_MINGW_ANSI_STDIO)\n    #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(ms_printf, string_idx, first_to_check)))\n#elif defined(__MINGW32__) && JSON_HEDLEY_GCC_HAS_ATTRIBUTE(format,4,4,0) && defined(__USE_MINGW_ANSI_STDIO)\n    #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(gnu_printf, string_idx, first_to_check)))\n#elif \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(format) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check)))\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0)\n    #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check))\n#else\n    #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check)\n#endif\n\n#if defined(JSON_HEDLEY_CONSTEXPR)\n    #undef JSON_HEDLEY_CONSTEXPR\n#endif\n#if defined(__cplusplus)\n    #if __cplusplus >= 201103L\n        #define JSON_HEDLEY_CONSTEXPR JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(constexpr)\n    #endif\n#endif\n#if !defined(JSON_HEDLEY_CONSTEXPR)\n    #define JSON_HEDLEY_CONSTEXPR\n#endif\n\n#if defined(JSON_HEDLEY_PREDICT)\n    #undef JSON_HEDLEY_PREDICT\n#endif\n#if defined(JSON_HEDLEY_LIKELY)\n    #undef JSON_HEDLEY_LIKELY\n#endif\n#if defined(JSON_HEDLEY_UNLIKELY)\n    #undef JSON_HEDLEY_UNLIKELY\n#endif\n#if defined(JSON_HEDLEY_UNPREDICTABLE)\n    #undef JSON_HEDLEY_UNPREDICTABLE\n#endif\n#if JSON_HEDLEY_HAS_BUILTIN(__builtin_unpredictable)\n    #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr))\n#endif\n#if \\\n  (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \\\n  JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#  define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability(  (expr), (value), (probability))\n#  define JSON_HEDLEY_PREDICT_TRUE(expr, probability)   __builtin_expect_with_probability(!!(expr),    1   , (probability))\n#  define JSON_HEDLEY_PREDICT_FALSE(expr, probability)  __builtin_expect_with_probability(!!(expr),    0   , (probability))\n#  define JSON_HEDLEY_LIKELY(expr)                      __builtin_expect                 (!!(expr),    1                  )\n#  define JSON_HEDLEY_UNLIKELY(expr)                    __builtin_expect                 (!!(expr),    0                  )\n#elif \\\n  (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n  (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \\\n  JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n  JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n  JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n  JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \\\n  JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \\\n  JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \\\n  JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \\\n  JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n  JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n  JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \\\n  JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \\\n  JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#  define JSON_HEDLEY_PREDICT(expr, expected, probability) \\\n    (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr)))\n#  define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \\\n    (__extension__ ({ \\\n        double hedley_probability_ = (probability); \\\n        ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 1) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 0) : !!(expr))); \\\n    }))\n#  define JSON_HEDLEY_PREDICT_FALSE(expr, probability) \\\n    (__extension__ ({ \\\n        double hedley_probability_ = (probability); \\\n        ((hedley_probability_ >= 0.9) ? __builtin_expect(!!(expr), 0) : ((hedley_probability_ <= 0.1) ? __builtin_expect(!!(expr), 1) : !!(expr))); \\\n    }))\n#  define JSON_HEDLEY_LIKELY(expr)   __builtin_expect(!!(expr), 1)\n#  define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect(!!(expr), 0)\n#else\n#  define JSON_HEDLEY_PREDICT(expr, expected, probability) (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))\n#  define JSON_HEDLEY_PREDICT_TRUE(expr, probability) (!!(expr))\n#  define JSON_HEDLEY_PREDICT_FALSE(expr, probability) (!!(expr))\n#  define JSON_HEDLEY_LIKELY(expr) (!!(expr))\n#  define JSON_HEDLEY_UNLIKELY(expr) (!!(expr))\n#endif\n#if !defined(JSON_HEDLEY_UNPREDICTABLE)\n    #define JSON_HEDLEY_UNPREDICTABLE(expr) JSON_HEDLEY_PREDICT(expr, 1, 0.5)\n#endif\n\n#if defined(JSON_HEDLEY_MALLOC)\n    #undef JSON_HEDLEY_MALLOC\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(malloc) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_MALLOC __attribute__((__malloc__))\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)\n    #define JSON_HEDLEY_MALLOC _Pragma(\"returns_new_memory\")\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_MALLOC __declspec(restrict)\n#else\n    #define JSON_HEDLEY_MALLOC\n#endif\n\n#if defined(JSON_HEDLEY_PURE)\n    #undef JSON_HEDLEY_PURE\n#endif\n#if \\\n  JSON_HEDLEY_HAS_ATTRIBUTE(pure) || \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(2,96,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n  JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n  JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n  JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n  JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n  (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n  (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n  (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n  (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n  JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n  JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n  JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \\\n  JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#  define JSON_HEDLEY_PURE __attribute__((__pure__))\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)\n#  define JSON_HEDLEY_PURE _Pragma(\"does_not_write_global_data\")\n#elif defined(__cplusplus) && \\\n    ( \\\n      JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \\\n      JSON_HEDLEY_TI_CL6X_VERSION_CHECK(4,0,0) || \\\n      JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) \\\n    )\n#  define JSON_HEDLEY_PURE _Pragma(\"FUNC_IS_PURE;\")\n#else\n#  define JSON_HEDLEY_PURE\n#endif\n\n#if defined(JSON_HEDLEY_CONST)\n    #undef JSON_HEDLEY_CONST\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(const) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(2,5,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_CONST __attribute__((__const__))\n#elif \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0)\n    #define JSON_HEDLEY_CONST _Pragma(\"no_side_effect\")\n#else\n    #define JSON_HEDLEY_CONST JSON_HEDLEY_PURE\n#endif\n\n#if defined(JSON_HEDLEY_RESTRICT)\n    #undef JSON_HEDLEY_RESTRICT\n#endif\n#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__cplusplus)\n    #define JSON_HEDLEY_RESTRICT restrict\n#elif \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n    JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,4) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \\\n    JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \\\n    defined(__clang__) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_RESTRICT __restrict\n#elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus)\n    #define JSON_HEDLEY_RESTRICT _Restrict\n#else\n    #define JSON_HEDLEY_RESTRICT\n#endif\n\n#if defined(JSON_HEDLEY_INLINE)\n    #undef JSON_HEDLEY_INLINE\n#endif\n#if \\\n    (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \\\n    (defined(__cplusplus) && (__cplusplus >= 199711L))\n    #define JSON_HEDLEY_INLINE inline\n#elif \\\n    defined(JSON_HEDLEY_GCC_VERSION) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(6,2,0)\n    #define JSON_HEDLEY_INLINE __inline__\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_INLINE __inline\n#else\n    #define JSON_HEDLEY_INLINE\n#endif\n\n#if defined(JSON_HEDLEY_ALWAYS_INLINE)\n    #undef JSON_HEDLEY_ALWAYS_INLINE\n#endif\n#if \\\n  JSON_HEDLEY_HAS_ATTRIBUTE(always_inline) || \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n  JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n  JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n  JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n  JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n  (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n  (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n  (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n  (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n  JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n  JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n  JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n  JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \\\n  JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)\n#  define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE\n#elif \\\n  JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \\\n  JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#  define JSON_HEDLEY_ALWAYS_INLINE __forceinline\n#elif defined(__cplusplus) && \\\n    ( \\\n      JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n      JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n      JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n      JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \\\n      JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n      JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) \\\n    )\n#  define JSON_HEDLEY_ALWAYS_INLINE _Pragma(\"FUNC_ALWAYS_INLINE;\")\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n#  define JSON_HEDLEY_ALWAYS_INLINE _Pragma(\"inline=forced\")\n#else\n#  define JSON_HEDLEY_ALWAYS_INLINE JSON_HEDLEY_INLINE\n#endif\n\n#if defined(JSON_HEDLEY_NEVER_INLINE)\n    #undef JSON_HEDLEY_NEVER_INLINE\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(noinline) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \\\n    JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \\\n    (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \\\n    (JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,4,0) || \\\n    (JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,0,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,3,0) || \\\n    (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \\\n    JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \\\n    JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \\\n    JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0)\n    #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__))\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline)\n#elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0)\n    #define JSON_HEDLEY_NEVER_INLINE _Pragma(\"noinline\")\n#elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus)\n    #define JSON_HEDLEY_NEVER_INLINE _Pragma(\"FUNC_CANNOT_INLINE;\")\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n    #define JSON_HEDLEY_NEVER_INLINE _Pragma(\"inline=never\")\n#elif JSON_HEDLEY_COMPCERT_VERSION_CHECK(3,2,0)\n    #define JSON_HEDLEY_NEVER_INLINE __attribute((noinline))\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(9,0,0)\n    #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline)\n#else\n    #define JSON_HEDLEY_NEVER_INLINE\n#endif\n\n#if defined(JSON_HEDLEY_PRIVATE)\n    #undef JSON_HEDLEY_PRIVATE\n#endif\n#if defined(JSON_HEDLEY_PUBLIC)\n    #undef JSON_HEDLEY_PUBLIC\n#endif\n#if defined(JSON_HEDLEY_IMPORT)\n    #undef JSON_HEDLEY_IMPORT\n#endif\n#if defined(_WIN32) || defined(__CYGWIN__)\n#  define JSON_HEDLEY_PRIVATE\n#  define JSON_HEDLEY_PUBLIC   __declspec(dllexport)\n#  define JSON_HEDLEY_IMPORT   __declspec(dllimport)\n#else\n#  if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(visibility) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \\\n    JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,11,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \\\n    ( \\\n      defined(__TI_EABI__) && \\\n      ( \\\n        (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \\\n        JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \\\n      ) \\\n    ) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n#    define JSON_HEDLEY_PRIVATE __attribute__((__visibility__(\"hidden\")))\n#    define JSON_HEDLEY_PUBLIC  __attribute__((__visibility__(\"default\")))\n#  else\n#    define JSON_HEDLEY_PRIVATE\n#    define JSON_HEDLEY_PUBLIC\n#  endif\n#  define JSON_HEDLEY_IMPORT    extern\n#endif\n\n#if defined(JSON_HEDLEY_NO_THROW)\n    #undef JSON_HEDLEY_NO_THROW\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__))\n#elif \\\n    JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0)\n    #define JSON_HEDLEY_NO_THROW __declspec(nothrow)\n#else\n    #define JSON_HEDLEY_NO_THROW\n#endif\n\n#if defined(JSON_HEDLEY_FALL_THROUGH)\n    #undef JSON_HEDLEY_FALL_THROUGH\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__))\n#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough)\n    #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]])\n#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(fallthrough)\n    #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[fallthrough]])\n#elif defined(__fallthrough) /* SAL */\n    #define JSON_HEDLEY_FALL_THROUGH __fallthrough\n#else\n    #define JSON_HEDLEY_FALL_THROUGH\n#endif\n\n#if defined(JSON_HEDLEY_RETURNS_NON_NULL)\n    #undef JSON_HEDLEY_RETURNS_NON_NULL\n#endif\n#if \\\n    JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__))\n#elif defined(_Ret_notnull_) /* SAL */\n    #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_\n#else\n    #define JSON_HEDLEY_RETURNS_NON_NULL\n#endif\n\n#if defined(JSON_HEDLEY_ARRAY_PARAM)\n    #undef JSON_HEDLEY_ARRAY_PARAM\n#endif\n#if \\\n    defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \\\n    !defined(__STDC_NO_VLA__) && \\\n    !defined(__cplusplus) && \\\n    !defined(JSON_HEDLEY_PGI_VERSION) && \\\n    !defined(JSON_HEDLEY_TINYC_VERSION)\n    #define JSON_HEDLEY_ARRAY_PARAM(name) (name)\n#else\n    #define JSON_HEDLEY_ARRAY_PARAM(name)\n#endif\n\n#if defined(JSON_HEDLEY_IS_CONSTANT)\n    #undef JSON_HEDLEY_IS_CONSTANT\n#endif\n#if defined(JSON_HEDLEY_REQUIRE_CONSTEXPR)\n    #undef JSON_HEDLEY_REQUIRE_CONSTEXPR\n#endif\n/* JSON_HEDLEY_IS_CONSTEXPR_ is for\n   HEDLEY INTERNAL USE ONLY.  API subject to change without notice. */\n#if defined(JSON_HEDLEY_IS_CONSTEXPR_)\n    #undef JSON_HEDLEY_IS_CONSTEXPR_\n#endif\n#if \\\n    JSON_HEDLEY_HAS_BUILTIN(__builtin_constant_p) || \\\n    JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \\\n    JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n    JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,19) || \\\n    JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \\\n    JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \\\n    JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \\\n    (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \\\n    JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \\\n    JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10)\n    #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr)\n#endif\n#if !defined(__cplusplus)\n#  if \\\n       JSON_HEDLEY_HAS_BUILTIN(__builtin_types_compatible_p) || \\\n       JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \\\n       JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n       JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \\\n       JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \\\n       JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \\\n       JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,24)\n#if defined(__INTPTR_TYPE__)\n    #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0)), int*)\n#else\n    #include <stdint.h>\n    #define JSON_HEDLEY_IS_CONSTEXPR_(expr) __builtin_types_compatible_p(__typeof__((1 ? (void*) ((intptr_t) ((expr) * 0)) : (int*) 0)), int*)\n#endif\n#  elif \\\n       ( \\\n          defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && \\\n          !defined(JSON_HEDLEY_SUNPRO_VERSION) && \\\n          !defined(JSON_HEDLEY_PGI_VERSION) && \\\n          !defined(JSON_HEDLEY_IAR_VERSION)) || \\\n       (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \\\n       JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \\\n       JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \\\n       JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \\\n       JSON_HEDLEY_ARM_VERSION_CHECK(5,3,0)\n#if defined(__INTPTR_TYPE__)\n    #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((__INTPTR_TYPE__) ((expr) * 0)) : (int*) 0), int*: 1, void*: 0)\n#else\n    #include <stdint.h>\n    #define JSON_HEDLEY_IS_CONSTEXPR_(expr) _Generic((1 ? (void*) ((intptr_t) * 0) : (int*) 0), int*: 1, void*: 0)\n#endif\n#  elif \\\n       defined(JSON_HEDLEY_GCC_VERSION) || \\\n       defined(JSON_HEDLEY_INTEL_VERSION) || \\\n       defined(JSON_HEDLEY_TINYC_VERSION) || \\\n       defined(JSON_HEDLEY_TI_ARMCL_VERSION) || \\\n       JSON_HEDLEY_TI_CL430_VERSION_CHECK(18,12,0) || \\\n       defined(JSON_HEDLEY_TI_CL2000_VERSION) || \\\n       defined(JSON_HEDLEY_TI_CL6X_VERSION) || \\\n       defined(JSON_HEDLEY_TI_CL7X_VERSION) || \\\n       defined(JSON_HEDLEY_TI_CLPRU_VERSION) || \\\n       defined(__clang__)\n#    define JSON_HEDLEY_IS_CONSTEXPR_(expr) ( \\\n        sizeof(void) != \\\n        sizeof(*( \\\n                  1 ? \\\n                  ((void*) ((expr) * 0L) ) : \\\n((struct { char v[sizeof(void) * 2]; } *) 1) \\\n                ) \\\n              ) \\\n                                            )\n#  endif\n#endif\n#if defined(JSON_HEDLEY_IS_CONSTEXPR_)\n    #if !defined(JSON_HEDLEY_IS_CONSTANT)\n        #define JSON_HEDLEY_IS_CONSTANT(expr) JSON_HEDLEY_IS_CONSTEXPR_(expr)\n    #endif\n    #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (JSON_HEDLEY_IS_CONSTEXPR_(expr) ? (expr) : (-1))\n#else\n    #if !defined(JSON_HEDLEY_IS_CONSTANT)\n        #define JSON_HEDLEY_IS_CONSTANT(expr) (0)\n    #endif\n    #define JSON_HEDLEY_REQUIRE_CONSTEXPR(expr) (expr)\n#endif\n\n#if defined(JSON_HEDLEY_BEGIN_C_DECLS)\n    #undef JSON_HEDLEY_BEGIN_C_DECLS\n#endif\n#if defined(JSON_HEDLEY_END_C_DECLS)\n    #undef JSON_HEDLEY_END_C_DECLS\n#endif\n#if defined(JSON_HEDLEY_C_DECL)\n    #undef JSON_HEDLEY_C_DECL\n#endif\n#if defined(__cplusplus)\n    #define JSON_HEDLEY_BEGIN_C_DECLS extern \"C\" {\n    #define JSON_HEDLEY_END_C_DECLS }\n    #define JSON_HEDLEY_C_DECL extern \"C\"\n#else\n    #define JSON_HEDLEY_BEGIN_C_DECLS\n    #define JSON_HEDLEY_END_C_DECLS\n    #define JSON_HEDLEY_C_DECL\n#endif\n\n#if defined(JSON_HEDLEY_STATIC_ASSERT)\n    #undef JSON_HEDLEY_STATIC_ASSERT\n#endif\n#if \\\n  !defined(__cplusplus) && ( \\\n      (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \\\n      (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \\\n      JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \\\n      JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \\\n      defined(_Static_assert) \\\n    )\n#  define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message)\n#elif \\\n  (defined(__cplusplus) && (__cplusplus >= 201103L)) || \\\n  JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \\\n  JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#  define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message))\n#else\n#  define JSON_HEDLEY_STATIC_ASSERT(expr, message)\n#endif\n\n#if defined(JSON_HEDLEY_NULL)\n    #undef JSON_HEDLEY_NULL\n#endif\n#if defined(__cplusplus)\n    #if __cplusplus >= 201103L\n        #define JSON_HEDLEY_NULL JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(nullptr)\n    #elif defined(NULL)\n        #define JSON_HEDLEY_NULL NULL\n    #else\n        #define JSON_HEDLEY_NULL JSON_HEDLEY_STATIC_CAST(void*, 0)\n    #endif\n#elif defined(NULL)\n    #define JSON_HEDLEY_NULL NULL\n#else\n    #define JSON_HEDLEY_NULL ((void*) 0)\n#endif\n\n#if defined(JSON_HEDLEY_MESSAGE)\n    #undef JSON_HEDLEY_MESSAGE\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wunknown-pragmas\")\n#  define JSON_HEDLEY_MESSAGE(msg) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \\\n    JSON_HEDLEY_PRAGMA(message msg) \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#elif \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(4,4,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n#  define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message msg)\n#elif JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0)\n#  define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(_CRI message msg)\n#elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0)\n#  define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg))\n#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,0,0)\n#  define JSON_HEDLEY_MESSAGE(msg) JSON_HEDLEY_PRAGMA(message(msg))\n#else\n#  define JSON_HEDLEY_MESSAGE(msg)\n#endif\n\n#if defined(JSON_HEDLEY_WARNING)\n    #undef JSON_HEDLEY_WARNING\n#endif\n#if JSON_HEDLEY_HAS_WARNING(\"-Wunknown-pragmas\")\n#  define JSON_HEDLEY_WARNING(msg) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS \\\n    JSON_HEDLEY_PRAGMA(clang warning msg) \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#elif \\\n  JSON_HEDLEY_GCC_VERSION_CHECK(4,8,0) || \\\n  JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \\\n  JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0)\n#  define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg)\n#elif \\\n  JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \\\n  JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n#  define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg))\n#else\n#  define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg)\n#endif\n\n#if defined(JSON_HEDLEY_REQUIRE)\n    #undef JSON_HEDLEY_REQUIRE\n#endif\n#if defined(JSON_HEDLEY_REQUIRE_MSG)\n    #undef JSON_HEDLEY_REQUIRE_MSG\n#endif\n#if JSON_HEDLEY_HAS_ATTRIBUTE(diagnose_if)\n#  if JSON_HEDLEY_HAS_WARNING(\"-Wgcc-compat\")\n#    define JSON_HEDLEY_REQUIRE(expr) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wgcc-compat\\\"\") \\\n    __attribute__((diagnose_if(!(expr), #expr, \"error\"))) \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#    define JSON_HEDLEY_REQUIRE_MSG(expr,msg) \\\n    JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n    _Pragma(\"clang diagnostic ignored \\\"-Wgcc-compat\\\"\") \\\n    __attribute__((diagnose_if(!(expr), msg, \"error\"))) \\\n    JSON_HEDLEY_DIAGNOSTIC_POP\n#  else\n#    define JSON_HEDLEY_REQUIRE(expr) __attribute__((diagnose_if(!(expr), #expr, \"error\")))\n#    define JSON_HEDLEY_REQUIRE_MSG(expr,msg) __attribute__((diagnose_if(!(expr), msg, \"error\")))\n#  endif\n#else\n#  define JSON_HEDLEY_REQUIRE(expr)\n#  define JSON_HEDLEY_REQUIRE_MSG(expr,msg)\n#endif\n\n#if defined(JSON_HEDLEY_FLAGS)\n    #undef JSON_HEDLEY_FLAGS\n#endif\n#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING(\"-Wbitfield-enum-conversion\"))\n    #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__))\n#else\n    #define JSON_HEDLEY_FLAGS\n#endif\n\n#if defined(JSON_HEDLEY_FLAGS_CAST)\n    #undef JSON_HEDLEY_FLAGS_CAST\n#endif\n#if JSON_HEDLEY_INTEL_VERSION_CHECK(19,0,0)\n#  define JSON_HEDLEY_FLAGS_CAST(T, expr) (__extension__ ({ \\\n        JSON_HEDLEY_DIAGNOSTIC_PUSH \\\n        _Pragma(\"warning(disable:188)\") \\\n        ((T) (expr)); \\\n        JSON_HEDLEY_DIAGNOSTIC_POP \\\n    }))\n#else\n#  define JSON_HEDLEY_FLAGS_CAST(T, expr) JSON_HEDLEY_STATIC_CAST(T, expr)\n#endif\n\n#if defined(JSON_HEDLEY_EMPTY_BASES)\n    #undef JSON_HEDLEY_EMPTY_BASES\n#endif\n#if \\\n    (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \\\n    JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0)\n    #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases)\n#else\n    #define JSON_HEDLEY_EMPTY_BASES\n#endif\n\n/* Remaining macros are deprecated. */\n\n#if defined(JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK)\n    #undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK\n#endif\n#if defined(__clang__)\n    #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) (0)\n#else\n    #define JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK(major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch)\n#endif\n\n#if defined(JSON_HEDLEY_CLANG_HAS_ATTRIBUTE)\n    #undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE\n#endif\n#define JSON_HEDLEY_CLANG_HAS_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_ATTRIBUTE(attribute)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE)\n    #undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE\n#endif\n#define JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_CPP_ATTRIBUTE(attribute)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_BUILTIN)\n    #undef JSON_HEDLEY_CLANG_HAS_BUILTIN\n#endif\n#define JSON_HEDLEY_CLANG_HAS_BUILTIN(builtin) JSON_HEDLEY_HAS_BUILTIN(builtin)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_FEATURE)\n    #undef JSON_HEDLEY_CLANG_HAS_FEATURE\n#endif\n#define JSON_HEDLEY_CLANG_HAS_FEATURE(feature) JSON_HEDLEY_HAS_FEATURE(feature)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_EXTENSION)\n    #undef JSON_HEDLEY_CLANG_HAS_EXTENSION\n#endif\n#define JSON_HEDLEY_CLANG_HAS_EXTENSION(extension) JSON_HEDLEY_HAS_EXTENSION(extension)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE)\n    #undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE\n#endif\n#define JSON_HEDLEY_CLANG_HAS_DECLSPEC_ATTRIBUTE(attribute) JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE(attribute)\n\n#if defined(JSON_HEDLEY_CLANG_HAS_WARNING)\n    #undef JSON_HEDLEY_CLANG_HAS_WARNING\n#endif\n#define JSON_HEDLEY_CLANG_HAS_WARNING(warning) JSON_HEDLEY_HAS_WARNING(warning)\n\n#endif /* !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < X) */\n\n\n// This file contains all internal macro definitions (except those affecting ABI)\n// You MUST include macro_unscope.hpp at the end of json.hpp to undef all of them\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\n// exclude unsupported compilers\n#if !defined(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK)\n    #if defined(__clang__)\n        #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400\n            #error \"unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers\"\n        #endif\n    #elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER))\n        #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40800\n            #error \"unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers\"\n        #endif\n    #endif\n#endif\n\n// C++ language standard detection\n// if the user manually specified the used c++ version this is skipped\n#if !defined(JSON_HAS_CPP_23) && !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11)\n    #if (defined(__cplusplus) && __cplusplus > 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG > 202002L)\n        #define JSON_HAS_CPP_23\n        #define JSON_HAS_CPP_20\n        #define JSON_HAS_CPP_17\n        #define JSON_HAS_CPP_14\n    #elif (defined(__cplusplus) && __cplusplus > 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG > 201703L)\n        #define JSON_HAS_CPP_20\n        #define JSON_HAS_CPP_17\n        #define JSON_HAS_CPP_14\n    #elif (defined(__cplusplus) && __cplusplus > 201402L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464\n        #define JSON_HAS_CPP_17\n        #define JSON_HAS_CPP_14\n    #elif (defined(__cplusplus) && __cplusplus > 201103L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1)\n        #define JSON_HAS_CPP_14\n    #endif\n    // the cpp 11 flag is always specified because it is the minimal required version\n    #define JSON_HAS_CPP_11\n#endif\n\n#ifdef __has_include\n    #if __has_include(<version>)\n        #include <version>\n    #endif\n#endif\n\n#if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM)\n    #ifdef JSON_HAS_CPP_17\n        #if defined(__cpp_lib_filesystem)\n            #define JSON_HAS_FILESYSTEM 1\n        #elif defined(__cpp_lib_experimental_filesystem)\n            #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1\n        #elif !defined(__has_include)\n            #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1\n        #elif __has_include(<filesystem>)\n            #define JSON_HAS_FILESYSTEM 1\n        #elif __has_include(<experimental/filesystem>)\n            #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 1\n        #endif\n\n        // std::filesystem does not work on MinGW GCC 8: https://sourceforge.net/p/mingw-w64/bugs/737/\n        #if defined(__MINGW32__) && defined(__GNUC__) && __GNUC__ == 8\n            #undef JSON_HAS_FILESYSTEM\n            #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n        #endif\n\n        // no filesystem support before GCC 8: https://en.cppreference.com/w/cpp/compiler_support\n        #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 8\n            #undef JSON_HAS_FILESYSTEM\n            #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n        #endif\n\n        // no filesystem support before Clang 7: https://en.cppreference.com/w/cpp/compiler_support\n        #if defined(__clang_major__) && __clang_major__ < 7\n            #undef JSON_HAS_FILESYSTEM\n            #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n        #endif\n\n        // no filesystem support before MSVC 19.14: https://en.cppreference.com/w/cpp/compiler_support\n        #if defined(_MSC_VER) && _MSC_VER < 1914\n            #undef JSON_HAS_FILESYSTEM\n            #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n        #endif\n\n        // no filesystem support before iOS 13\n        #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 130000\n            #undef JSON_HAS_FILESYSTEM\n            #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n        #endif\n\n        // no filesystem support before macOS Catalina\n        #if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500\n            #undef JSON_HAS_FILESYSTEM\n            #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n        #endif\n    #endif\n#endif\n\n#ifndef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n    #define JSON_HAS_EXPERIMENTAL_FILESYSTEM 0\n#endif\n\n#ifndef JSON_HAS_FILESYSTEM\n    #define JSON_HAS_FILESYSTEM 0\n#endif\n\n#ifndef JSON_HAS_THREE_WAY_COMPARISON\n    #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \\\n        && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L\n        #define JSON_HAS_THREE_WAY_COMPARISON 1\n    #else\n        #define JSON_HAS_THREE_WAY_COMPARISON 0\n    #endif\n#endif\n\n#ifndef JSON_HAS_RANGES\n    // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error\n    #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427\n        #define JSON_HAS_RANGES 0\n    #elif defined(__cpp_lib_ranges)\n        #define JSON_HAS_RANGES 1\n    #else\n        #define JSON_HAS_RANGES 0\n    #endif\n#endif\n\n#ifndef JSON_HAS_STATIC_RTTI\n    #if !defined(_HAS_STATIC_RTTI) || _HAS_STATIC_RTTI != 0\n        #define JSON_HAS_STATIC_RTTI 1\n    #else\n        #define JSON_HAS_STATIC_RTTI 0\n    #endif\n#endif\n\n#ifdef JSON_HAS_CPP_17\n    #define JSON_INLINE_VARIABLE inline\n#else\n    #define JSON_INLINE_VARIABLE\n#endif\n\n#if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address)\n    #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]]\n#else\n    #define JSON_NO_UNIQUE_ADDRESS\n#endif\n\n// disable documentation warnings on clang\n#if defined(__clang__)\n    #pragma clang diagnostic push\n    #pragma clang diagnostic ignored \"-Wdocumentation\"\n    #pragma clang diagnostic ignored \"-Wdocumentation-unknown-command\"\n#endif\n\n// allow disabling exceptions\n#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)\n    #define JSON_THROW(exception) throw exception\n    #define JSON_TRY try\n    #define JSON_CATCH(exception) catch(exception)\n    #define JSON_INTERNAL_CATCH(exception) catch(exception)\n#else\n    #include <cstdlib>\n    #define JSON_THROW(exception) std::abort()\n    #define JSON_TRY if(true)\n    #define JSON_CATCH(exception) if(false)\n    #define JSON_INTERNAL_CATCH(exception) if(false)\n#endif\n\n// override exception macros\n#if defined(JSON_THROW_USER)\n    #undef JSON_THROW\n    #define JSON_THROW JSON_THROW_USER\n#endif\n#if defined(JSON_TRY_USER)\n    #undef JSON_TRY\n    #define JSON_TRY JSON_TRY_USER\n#endif\n#if defined(JSON_CATCH_USER)\n    #undef JSON_CATCH\n    #define JSON_CATCH JSON_CATCH_USER\n    #undef JSON_INTERNAL_CATCH\n    #define JSON_INTERNAL_CATCH JSON_CATCH_USER\n#endif\n#if defined(JSON_INTERNAL_CATCH_USER)\n    #undef JSON_INTERNAL_CATCH\n    #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER\n#endif\n\n// allow overriding assert\n#if !defined(JSON_ASSERT)\n    #include <cassert> // assert\n    #define JSON_ASSERT(x) assert(x)\n#endif\n\n// allow to access some private functions (needed by the test suite)\n#if defined(JSON_TESTS_PRIVATE)\n    #define JSON_PRIVATE_UNLESS_TESTED public\n#else\n    #define JSON_PRIVATE_UNLESS_TESTED private\n#endif\n\n/*!\n@brief macro to briefly define a mapping between an enum and JSON\n@def NLOHMANN_JSON_SERIALIZE_ENUM\n@since version 3.4.0\n*/\n#define NLOHMANN_JSON_SERIALIZE_ENUM(ENUM_TYPE, ...)                                            \\\n    template<typename BasicJsonType>                                                            \\\n    inline void to_json(BasicJsonType& j, const ENUM_TYPE& e)                                   \\\n    {                                                                                           \\\n        /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */                                \\\n        static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE \" must be an enum!\");          \\\n        /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on <array> */       \\\n        static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__;                     \\\n        auto it = std::find_if(std::begin(m), std::end(m),                                      \\\n                               [e](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool  \\\n        {                                                                                       \\\n            return ej_pair.first == e;                                                          \\\n        });                                                                                     \\\n        j = ((it != std::end(m)) ? it : std::begin(m))->second;                                 \\\n    }                                                                                           \\\n    template<typename BasicJsonType>                                                            \\\n    inline void from_json(const BasicJsonType& j, ENUM_TYPE& e)                                 \\\n    {                                                                                           \\\n        /* NOLINTNEXTLINE(modernize-type-traits) we use C++11 */                                \\\n        static_assert(std::is_enum<ENUM_TYPE>::value, #ENUM_TYPE \" must be an enum!\");          \\\n        /* NOLINTNEXTLINE(modernize-avoid-c-arrays) we don't want to depend on <array> */       \\\n        static const std::pair<ENUM_TYPE, BasicJsonType> m[] = __VA_ARGS__;                     \\\n        auto it = std::find_if(std::begin(m), std::end(m),                                      \\\n                               [&j](const std::pair<ENUM_TYPE, BasicJsonType>& ej_pair) -> bool \\\n        {                                                                                       \\\n            return ej_pair.second == j;                                                         \\\n        });                                                                                     \\\n        e = ((it != std::end(m)) ? it : std::begin(m))->first;                                  \\\n    }\n\n// Ugly macros to avoid uglier copy-paste when specializing basic_json. They\n// may be removed in the future once the class is split.\n\n#define NLOHMANN_BASIC_JSON_TPL_DECLARATION                                \\\n    template<template<typename, typename, typename...> class ObjectType,   \\\n             template<typename, typename...> class ArrayType,              \\\n             class StringType, class BooleanType, class NumberIntegerType, \\\n             class NumberUnsignedType, class NumberFloatType,              \\\n             template<typename> class AllocatorType,                       \\\n             template<typename, typename = void> class JSONSerializer,     \\\n             class BinaryType,                                             \\\n             class CustomBaseClass>\n\n#define NLOHMANN_BASIC_JSON_TPL                                            \\\n    basic_json<ObjectType, ArrayType, StringType, BooleanType,             \\\n    NumberIntegerType, NumberUnsignedType, NumberFloatType,                \\\n    AllocatorType, JSONSerializer, BinaryType, CustomBaseClass>\n\n// Macros to simplify conversion from/to types\n\n#define NLOHMANN_JSON_EXPAND( x ) x\n#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME\n#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \\\n        NLOHMANN_JSON_PASTE64, \\\n        NLOHMANN_JSON_PASTE63, \\\n        NLOHMANN_JSON_PASTE62, \\\n        NLOHMANN_JSON_PASTE61, \\\n        NLOHMANN_JSON_PASTE60, \\\n        NLOHMANN_JSON_PASTE59, \\\n        NLOHMANN_JSON_PASTE58, \\\n        NLOHMANN_JSON_PASTE57, \\\n        NLOHMANN_JSON_PASTE56, \\\n        NLOHMANN_JSON_PASTE55, \\\n        NLOHMANN_JSON_PASTE54, \\\n        NLOHMANN_JSON_PASTE53, \\\n        NLOHMANN_JSON_PASTE52, \\\n        NLOHMANN_JSON_PASTE51, \\\n        NLOHMANN_JSON_PASTE50, \\\n        NLOHMANN_JSON_PASTE49, \\\n        NLOHMANN_JSON_PASTE48, \\\n        NLOHMANN_JSON_PASTE47, \\\n        NLOHMANN_JSON_PASTE46, \\\n        NLOHMANN_JSON_PASTE45, \\\n        NLOHMANN_JSON_PASTE44, \\\n        NLOHMANN_JSON_PASTE43, \\\n        NLOHMANN_JSON_PASTE42, \\\n        NLOHMANN_JSON_PASTE41, \\\n        NLOHMANN_JSON_PASTE40, \\\n        NLOHMANN_JSON_PASTE39, \\\n        NLOHMANN_JSON_PASTE38, \\\n        NLOHMANN_JSON_PASTE37, \\\n        NLOHMANN_JSON_PASTE36, \\\n        NLOHMANN_JSON_PASTE35, \\\n        NLOHMANN_JSON_PASTE34, \\\n        NLOHMANN_JSON_PASTE33, \\\n        NLOHMANN_JSON_PASTE32, \\\n        NLOHMANN_JSON_PASTE31, \\\n        NLOHMANN_JSON_PASTE30, \\\n        NLOHMANN_JSON_PASTE29, \\\n        NLOHMANN_JSON_PASTE28, \\\n        NLOHMANN_JSON_PASTE27, \\\n        NLOHMANN_JSON_PASTE26, \\\n        NLOHMANN_JSON_PASTE25, \\\n        NLOHMANN_JSON_PASTE24, \\\n        NLOHMANN_JSON_PASTE23, \\\n        NLOHMANN_JSON_PASTE22, \\\n        NLOHMANN_JSON_PASTE21, \\\n        NLOHMANN_JSON_PASTE20, \\\n        NLOHMANN_JSON_PASTE19, \\\n        NLOHMANN_JSON_PASTE18, \\\n        NLOHMANN_JSON_PASTE17, \\\n        NLOHMANN_JSON_PASTE16, \\\n        NLOHMANN_JSON_PASTE15, \\\n        NLOHMANN_JSON_PASTE14, \\\n        NLOHMANN_JSON_PASTE13, \\\n        NLOHMANN_JSON_PASTE12, \\\n        NLOHMANN_JSON_PASTE11, \\\n        NLOHMANN_JSON_PASTE10, \\\n        NLOHMANN_JSON_PASTE9, \\\n        NLOHMANN_JSON_PASTE8, \\\n        NLOHMANN_JSON_PASTE7, \\\n        NLOHMANN_JSON_PASTE6, \\\n        NLOHMANN_JSON_PASTE5, \\\n        NLOHMANN_JSON_PASTE4, \\\n        NLOHMANN_JSON_PASTE3, \\\n        NLOHMANN_JSON_PASTE2, \\\n        NLOHMANN_JSON_PASTE1)(__VA_ARGS__))\n#define NLOHMANN_JSON_PASTE2(func, v1) func(v1)\n#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2)\n#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3)\n#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4)\n#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5)\n#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6)\n#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7)\n#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8)\n#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9)\n#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10)\n#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11)\n#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12)\n#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13)\n#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14)\n#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15)\n#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16)\n#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17)\n#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18)\n#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19)\n#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20)\n#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21)\n#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22)\n#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23)\n#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24)\n#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25)\n#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26)\n#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27)\n#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28)\n#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29)\n#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30)\n#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31)\n#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32)\n#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33)\n#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34)\n#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35)\n#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36)\n#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37)\n#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38)\n#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39)\n#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40)\n#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41)\n#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42)\n#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43)\n#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44)\n#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45)\n#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46)\n#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47)\n#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48)\n#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49)\n#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50)\n#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51)\n#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52)\n#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53)\n#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54)\n#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55)\n#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56)\n#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57)\n#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58)\n#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59)\n#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60)\n#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61)\n#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62)\n#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63)\n\n#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1;\n#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1);\n#define NLOHMANN_JSON_FROM_WITH_DEFAULT(v1) nlohmann_json_t.v1 = !nlohmann_json_j.is_null() ? nlohmann_json_j.value(#v1, nlohmann_json_default_obj.v1) : nlohmann_json_default_obj.v1;\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_TYPE_INTRUSIVE\n@since version 3.9.0\n@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/\n*/\n#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...)  \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT\n@since version 3.11.0\n@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/\n*/\n#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...)  \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE\n@since version 3.11.3\n@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_intrusive/\n*/\n#define NLOHMANN_DEFINE_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, ...)  \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE\n@since version 3.9.0\n@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/\n*/\n#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...)  \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT\n@since version 3.11.0\n@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/\n*/\n#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...)  \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE\n@since version 3.11.3\n@sa https://json.nlohmann.me/api/macros/nlohmann_define_type_non_intrusive/\n*/\n#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, ...)  \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE\n@since version 3.12.0\n@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/\n*/\n#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE(Type, BaseType, ...)  \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT\n@since version 3.12.0\n@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/\n*/\n#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...)  \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    friend void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE\n@since version 3.12.0\n@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/\n*/\n#define NLOHMANN_DEFINE_DERIVED_TYPE_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...)  \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    friend void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE\n@since version 3.12.0\n@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/\n*/\n#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE(Type, BaseType, ...)  \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) }\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT\n@since version 3.12.0\n@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/\n*/\n#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, BaseType, ...)  \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    void from_json(const BasicJsonType& nlohmann_json_j, Type& nlohmann_json_t) { nlohmann::from_json(nlohmann_json_j, static_cast<BaseType&>(nlohmann_json_t)); const Type nlohmann_json_default_obj{}; NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) }\n\n/*!\n@brief macro\n@def NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE\n@since version 3.12.0\n@sa https://json.nlohmann.me/api/macros/nlohmann_define_derived_type/\n*/\n#define NLOHMANN_DEFINE_DERIVED_TYPE_NON_INTRUSIVE_ONLY_SERIALIZE(Type, BaseType, ...)  \\\n    template<typename BasicJsonType, nlohmann::detail::enable_if_t<nlohmann::detail::is_basic_json<BasicJsonType>::value, int> = 0> \\\n    void to_json(BasicJsonType& nlohmann_json_j, const Type& nlohmann_json_t) { nlohmann::to_json(nlohmann_json_j, static_cast<const BaseType &>(nlohmann_json_t)); NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) }\n\n// inspired from https://stackoverflow.com/a/26745591\n// allows calling any std function as if (e.g., with begin):\n// using std::begin; begin(x);\n//\n// it allows using the detected idiom to retrieve the return type\n// of such an expression\n#define NLOHMANN_CAN_CALL_STD_FUNC_IMPL(std_name)                                 \\\n    namespace detail {                                                            \\\n    using std::std_name;                                                          \\\n    \\\n    template<typename... T>                                                       \\\n    using result_of_##std_name = decltype(std_name(std::declval<T>()...));        \\\n    }                                                                             \\\n    \\\n    namespace detail2 {                                                           \\\n    struct std_name##_tag                                                         \\\n    {                                                                             \\\n    };                                                                            \\\n    \\\n    template<typename... T>                                                       \\\n    std_name##_tag std_name(T&&...);                                              \\\n    \\\n    template<typename... T>                                                       \\\n    using result_of_##std_name = decltype(std_name(std::declval<T>()...));        \\\n    \\\n    template<typename... T>                                                       \\\n    struct would_call_std_##std_name                                              \\\n    {                                                                             \\\n        static constexpr auto const value = ::nlohmann::detail::                  \\\n                                            is_detected_exact<std_name##_tag, result_of_##std_name, T...>::value; \\\n    };                                                                            \\\n    } /* namespace detail2 */ \\\n    \\\n    template<typename... T>                                                       \\\n    struct would_call_std_##std_name : detail2::would_call_std_##std_name<T...>   \\\n    {                                                                             \\\n    }\n\n#ifndef JSON_USE_IMPLICIT_CONVERSIONS\n    #define JSON_USE_IMPLICIT_CONVERSIONS 1\n#endif\n\n#if JSON_USE_IMPLICIT_CONVERSIONS\n    #define JSON_EXPLICIT\n#else\n    #define JSON_EXPLICIT explicit\n#endif\n\n#ifndef JSON_DISABLE_ENUM_SERIALIZATION\n    #define JSON_DISABLE_ENUM_SERIALIZATION 0\n#endif\n\n#ifndef JSON_USE_GLOBAL_UDLS\n    #define JSON_USE_GLOBAL_UDLS 1\n#endif\n\n#if JSON_HAS_THREE_WAY_COMPARISON\n    #include <compare> // partial_ordering\n#endif\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n///////////////////////////\n// JSON type enumeration //\n///////////////////////////\n\n/*!\n@brief the JSON type enumeration\n\nThis enumeration collects the different JSON types. It is internally used to\ndistinguish the stored values, and the functions @ref basic_json::is_null(),\n@ref basic_json::is_object(), @ref basic_json::is_array(),\n@ref basic_json::is_string(), @ref basic_json::is_boolean(),\n@ref basic_json::is_number() (with @ref basic_json::is_number_integer(),\n@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()),\n@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and\n@ref basic_json::is_structured() rely on it.\n\n@note There are three enumeration entries (number_integer, number_unsigned, and\nnumber_float), because the library distinguishes these three types for numbers:\n@ref basic_json::number_unsigned_t is used for unsigned integers,\n@ref basic_json::number_integer_t is used for signed integers, and\n@ref basic_json::number_float_t is used for floating-point numbers or to\napproximate integers which do not fit in the limits of their respective type.\n\n@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON\nvalue with the default value for a given type\n\n@since version 1.0.0\n*/\nenum class value_t : std::uint8_t\n{\n    null,             ///< null value\n    object,           ///< object (unordered set of name/value pairs)\n    array,            ///< array (ordered collection of values)\n    string,           ///< string value\n    boolean,          ///< boolean value\n    number_integer,   ///< number value (signed integer)\n    number_unsigned,  ///< number value (unsigned integer)\n    number_float,     ///< number value (floating-point)\n    binary,           ///< binary array (ordered collection of bytes)\n    discarded         ///< discarded by the parser callback function\n};\n\n/*!\n@brief comparison operator for JSON types\n\nReturns an ordering that is similar to Python:\n- order: null < boolean < number < object < array < string < binary\n- furthermore, each type is not smaller than itself\n- discarded values are not comparable\n- binary is represented as a b\"\" string in python and directly comparable to a\n  string; however, making a binary array directly comparable with a string would\n  be surprising behavior in a JSON file.\n\n@since version 1.0.0\n*/\n#if JSON_HAS_THREE_WAY_COMPARISON\n    inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD*\n#else\n    inline bool operator<(const value_t lhs, const value_t rhs) noexcept\n#endif\n{\n    static constexpr std::array<std::uint8_t, 9> order = {{\n            0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */,\n            1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */,\n            6 /* binary */\n        }\n    };\n\n    const auto l_index = static_cast<std::size_t>(lhs);\n    const auto r_index = static_cast<std::size_t>(rhs);\n#if JSON_HAS_THREE_WAY_COMPARISON\n    if (l_index < order.size() && r_index < order.size())\n    {\n        return order[l_index] <=> order[r_index]; // *NOPAD*\n    }\n    return std::partial_ordering::unordered;\n#else\n    return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index];\n#endif\n}\n\n// GCC selects the built-in operator< over an operator rewritten from\n// a user-defined spaceship operator\n// Clang, MSVC, and ICC select the rewritten candidate\n// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200)\n#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__)\ninline bool operator<(const value_t lhs, const value_t rhs) noexcept\n{\n    return std::is_lt(lhs <=> rhs); // *NOPAD*\n}\n#endif\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/string_escape.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/*!\n@brief replace all occurrences of a substring by another string\n\n@param[in,out] s  the string to manipulate; changed so that all\n               occurrences of @a f are replaced with @a t\n@param[in]     f  the substring to replace with @a t\n@param[in]     t  the string to replace @a f\n\n@pre The search string @a f must not be empty. **This precondition is\nenforced with an assertion.**\n\n@since version 2.0.0\n*/\ntemplate<typename StringType>\ninline void replace_substring(StringType& s, const StringType& f,\n                              const StringType& t)\n{\n    JSON_ASSERT(!f.empty());\n    for (auto pos = s.find(f);                // find first occurrence of f\n            pos != StringType::npos;          // make sure f was found\n            s.replace(pos, f.size(), t),      // replace with t, and\n            pos = s.find(f, pos + t.size()))  // find next occurrence of f\n    {}\n}\n\n/*!\n * @brief string escaping as described in RFC 6901 (Sect. 4)\n * @param[in] s string to escape\n * @return    escaped string\n *\n * Note the order of escaping \"~\" to \"~0\" and \"/\" to \"~1\" is important.\n */\ntemplate<typename StringType>\ninline StringType escape(StringType s)\n{\n    replace_substring(s, StringType{\"~\"}, StringType{\"~0\"});\n    replace_substring(s, StringType{\"/\"}, StringType{\"~1\"});\n    return s;\n}\n\n/*!\n * @brief string unescaping as described in RFC 6901 (Sect. 4)\n * @param[in] s string to unescape\n * @return    unescaped string\n *\n * Note the order of escaping \"~1\" to \"/\" and \"~0\" to \"~\" is important.\n */\ntemplate<typename StringType>\nstatic void unescape(StringType& s)\n{\n    replace_substring(s, StringType{\"~1\"}, StringType{\"/\"});\n    replace_substring(s, StringType{\"~0\"}, StringType{\"~\"});\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/input/position_t.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // size_t\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/// struct to capture the start position of the current token\nstruct position_t\n{\n    /// the total number of characters read\n    std::size_t chars_read_total = 0;\n    /// the number of characters read in the current line\n    std::size_t chars_read_current_line = 0;\n    /// the number of lines read\n    std::size_t lines_read = 0;\n\n    /// conversion to size_t to preserve SAX interface\n    constexpr operator size_t() const\n    {\n        return chars_read_total;\n    }\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-FileCopyrightText: 2018 The Abseil Authors\n// SPDX-License-Identifier: MIT\n\n\n\n#include <array> // array\n#include <cstddef> // size_t\n#include <type_traits> // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type\n#include <utility> // index_sequence, make_index_sequence, index_sequence_for\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename T>\nusing uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type;\n\n#ifdef JSON_HAS_CPP_14\n\n// the following utilities are natively available in C++14\nusing std::enable_if_t;\nusing std::index_sequence;\nusing std::make_index_sequence;\nusing std::index_sequence_for;\n\n#else\n\n// alias templates to reduce boilerplate\ntemplate<bool B, typename T = void>\nusing enable_if_t = typename std::enable_if<B, T>::type;\n\n// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h\n// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0.\n\n//// START OF CODE FROM GOOGLE ABSEIL\n\n// integer_sequence\n//\n// Class template representing a compile-time integer sequence. An instantiation\n// of `integer_sequence<T, Ints...>` has a sequence of integers encoded in its\n// type through its template arguments (which is a common need when\n// working with C++11 variadic templates). `absl::integer_sequence` is designed\n// to be a drop-in replacement for C++14's `std::integer_sequence`.\n//\n// Example:\n//\n//   template< class T, T... Ints >\n//   void user_function(integer_sequence<T, Ints...>);\n//\n//   int main()\n//   {\n//     // user_function's `T` will be deduced to `int` and `Ints...`\n//     // will be deduced to `0, 1, 2, 3, 4`.\n//     user_function(make_integer_sequence<int, 5>());\n//   }\ntemplate <typename T, T... Ints>\nstruct integer_sequence\n{\n    using value_type = T;\n    static constexpr std::size_t size() noexcept\n    {\n        return sizeof...(Ints);\n    }\n};\n\n// index_sequence\n//\n// A helper template for an `integer_sequence` of `size_t`,\n// `absl::index_sequence` is designed to be a drop-in replacement for C++14's\n// `std::index_sequence`.\ntemplate <size_t... Ints>\nusing index_sequence = integer_sequence<size_t, Ints...>;\n\nnamespace utility_internal\n{\n\ntemplate <typename Seq, size_t SeqSize, size_t Rem>\nstruct Extend;\n\n// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency.\ntemplate <typename T, T... Ints, size_t SeqSize>\nstruct Extend<integer_sequence<T, Ints...>, SeqSize, 0>\n{\n    using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >;\n};\n\ntemplate <typename T, T... Ints, size_t SeqSize>\nstruct Extend<integer_sequence<T, Ints...>, SeqSize, 1>\n{\n    using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >;\n};\n\n// Recursion helper for 'make_integer_sequence<T, N>'.\n// 'Gen<T, N>::type' is an alias for 'integer_sequence<T, 0, 1, ... N-1>'.\ntemplate <typename T, size_t N>\nstruct Gen\n{\n    using type =\n        typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type;\n};\n\ntemplate <typename T>\nstruct Gen<T, 0>\n{\n    using type = integer_sequence<T>;\n};\n\n}  // namespace utility_internal\n\n// Compile-time sequences of integers\n\n// make_integer_sequence\n//\n// This template alias is equivalent to\n// `integer_sequence<int, 0, 1, ..., N-1>`, and is designed to be a drop-in\n// replacement for C++14's `std::make_integer_sequence`.\ntemplate <typename T, T N>\nusing make_integer_sequence = typename utility_internal::Gen<T, N>::type;\n\n// make_index_sequence\n//\n// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`,\n// and is designed to be a drop-in replacement for C++14's\n// `std::make_index_sequence`.\ntemplate <size_t N>\nusing make_index_sequence = make_integer_sequence<size_t, N>;\n\n// index_sequence_for\n//\n// Converts a typename pack into an index sequence of the same length, and\n// is designed to be a drop-in replacement for C++14's\n// `std::index_sequence_for()`\ntemplate <typename... Ts>\nusing index_sequence_for = make_index_sequence<sizeof...(Ts)>;\n\n//// END OF CODE FROM GOOGLE ABSEIL\n\n#endif\n\n// dispatch utility (taken from ranges-v3)\ntemplate<unsigned N> struct priority_tag : priority_tag < N - 1 > {};\ntemplate<> struct priority_tag<0> {};\n\n// taken from ranges-v3\ntemplate<typename T>\nstruct static_const\n{\n    static JSON_INLINE_VARIABLE constexpr T value{};\n};\n\n#ifndef JSON_HAS_CPP_17\n    template<typename T>\n    constexpr T static_const<T>::value;\n#endif\n\ntemplate<typename T, typename... Args>\nconstexpr std::array<T, sizeof...(Args)> make_array(Args&& ... args)\n{\n    return std::array<T, sizeof...(Args)> {{static_cast<T>(std::forward<Args>(args))...}};\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <limits> // numeric_limits\n#include <string> // char_traits\n#include <tuple> // tuple\n#include <type_traits> // false_type, is_constructible, is_integral, is_same, true_type\n#include <utility> // declval\n\n// #include <nlohmann/detail/iterators/iterator_traits.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <iterator> // random_access_iterator_tag\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/meta/void_t.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename It, typename = void>\nstruct iterator_types {};\n\ntemplate<typename It>\nstruct iterator_types <\n    It,\n    void_t<typename It::difference_type, typename It::value_type, typename It::pointer,\n    typename It::reference, typename It::iterator_category >>\n{\n    using difference_type = typename It::difference_type;\n    using value_type = typename It::value_type;\n    using pointer = typename It::pointer;\n    using reference = typename It::reference;\n    using iterator_category = typename It::iterator_category;\n};\n\n// This is required as some compilers implement std::iterator_traits in a way that\n// doesn't work with SFINAE. See https://github.com/nlohmann/json/issues/1341.\ntemplate<typename T, typename = void>\nstruct iterator_traits\n{\n};\n\ntemplate<typename T>\nstruct iterator_traits < T, enable_if_t < !std::is_pointer<T>::value >>\n    : iterator_types<T>\n{\n};\n\ntemplate<typename T>\nstruct iterator_traits<T*, enable_if_t<std::is_object<T>::value>>\n{\n    using iterator_category = std::random_access_iterator_tag;\n    using value_type = T;\n    using difference_type = ptrdiff_t;\n    using pointer = T*;\n    using reference = T&;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/call_std/begin.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\nNLOHMANN_CAN_CALL_STD_FUNC_IMPL(begin);\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/call_std/end.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\nNLOHMANN_CAN_CALL_STD_FUNC_IMPL(end);\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/detected.hpp>\n\n// #include <nlohmann/json_fwd.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_\n    #define INCLUDE_NLOHMANN_JSON_FWD_HPP_\n\n    #include <cstdint> // int64_t, uint64_t\n    #include <map> // map\n    #include <memory> // allocator\n    #include <string> // string\n    #include <vector> // vector\n\n    // #include <nlohmann/detail/abi_macros.hpp>\n\n\n    /*!\n    @brief namespace for Niels Lohmann\n    @see https://github.com/nlohmann\n    @since version 1.0.0\n    */\n    NLOHMANN_JSON_NAMESPACE_BEGIN\n\n    /*!\n    @brief default JSONSerializer template argument\n\n    This serializer ignores the template arguments and uses ADL\n    ([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))\n    for serialization.\n    */\n    template<typename T = void, typename SFINAE = void>\n    struct adl_serializer;\n\n    /// a class to store JSON values\n    /// @sa https://json.nlohmann.me/api/basic_json/\n    template<template<typename U, typename V, typename... Args> class ObjectType =\n    std::map,\n    template<typename U, typename... Args> class ArrayType = std::vector,\n    class StringType = std::string, class BooleanType = bool,\n    class NumberIntegerType = std::int64_t,\n    class NumberUnsignedType = std::uint64_t,\n    class NumberFloatType = double,\n    template<typename U> class AllocatorType = std::allocator,\n    template<typename T, typename SFINAE = void> class JSONSerializer =\n    adl_serializer,\n    class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError\n    class CustomBaseClass = void>\n    class basic_json;\n\n    /// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document\n    /// @sa https://json.nlohmann.me/api/json_pointer/\n    template<typename RefStringType>\n    class json_pointer;\n\n    /*!\n    @brief default specialization\n    @sa https://json.nlohmann.me/api/json/\n    */\n    using json = basic_json<>;\n\n    /// @brief a minimal map-like container that preserves insertion order\n    /// @sa https://json.nlohmann.me/api/ordered_map/\n    template<class Key, class T, class IgnoredLess, class Allocator>\n    struct ordered_map;\n\n    /// @brief specialization that maintains the insertion order of object keys\n    /// @sa https://json.nlohmann.me/api/ordered_json/\n    using ordered_json = basic_json<nlohmann::ordered_map>;\n\n    NLOHMANN_JSON_NAMESPACE_END\n\n#endif  // INCLUDE_NLOHMANN_JSON_FWD_HPP_\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n/*!\n@brief detail namespace with internal helper functions\n\nThis namespace collects functions that should not be exposed,\nimplementations of some @ref basic_json methods, and meta-programming helpers.\n\n@since version 2.1.0\n*/\nnamespace detail\n{\n\n/////////////\n// helpers //\n/////////////\n\n// Note to maintainers:\n//\n// Every trait in this file expects a non CV-qualified type.\n// The only exceptions are in the 'aliases for detected' section\n// (i.e. those of the form: decltype(T::member_function(std::declval<T>())))\n//\n// In this case, T has to be properly CV-qualified to constraint the function arguments\n// (e.g. to_json(BasicJsonType&, const T&))\n\ntemplate<typename> struct is_basic_json : std::false_type {};\n\nNLOHMANN_BASIC_JSON_TPL_DECLARATION\nstruct is_basic_json<NLOHMANN_BASIC_JSON_TPL> : std::true_type {};\n\n// used by exceptions create() member functions\n// true_type for pointer to possibly cv-qualified basic_json or std::nullptr_t\n// false_type otherwise\ntemplate<typename BasicJsonContext>\nstruct is_basic_json_context :\n    std::integral_constant < bool,\n    is_basic_json<typename std::remove_cv<typename std::remove_pointer<BasicJsonContext>::type>::type>::value\n    || std::is_same<BasicJsonContext, std::nullptr_t>::value >\n{};\n\n//////////////////////\n// json_ref helpers //\n//////////////////////\n\ntemplate<typename>\nclass json_ref;\n\ntemplate<typename>\nstruct is_json_ref : std::false_type {};\n\ntemplate<typename T>\nstruct is_json_ref<json_ref<T>> : std::true_type {};\n\n//////////////////////////\n// aliases for detected //\n//////////////////////////\n\ntemplate<typename T>\nusing mapped_type_t = typename T::mapped_type;\n\ntemplate<typename T>\nusing key_type_t = typename T::key_type;\n\ntemplate<typename T>\nusing value_type_t = typename T::value_type;\n\ntemplate<typename T>\nusing difference_type_t = typename T::difference_type;\n\ntemplate<typename T>\nusing pointer_t = typename T::pointer;\n\ntemplate<typename T>\nusing reference_t = typename T::reference;\n\ntemplate<typename T>\nusing iterator_category_t = typename T::iterator_category;\n\ntemplate<typename T, typename... Args>\nusing to_json_function = decltype(T::to_json(std::declval<Args>()...));\n\ntemplate<typename T, typename... Args>\nusing from_json_function = decltype(T::from_json(std::declval<Args>()...));\n\ntemplate<typename T, typename U>\nusing get_template_function = decltype(std::declval<T>().template get<U>());\n\n// trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists\ntemplate<typename BasicJsonType, typename T, typename = void>\nstruct has_from_json : std::false_type {};\n\n// trait checking if j.get<T> is valid\n// use this trait instead of std::is_constructible or std::is_convertible,\n// both rely on, or make use of implicit conversions, and thus fail when T\n// has several constructors/operator= (see https://github.com/nlohmann/json/issues/958)\ntemplate <typename BasicJsonType, typename T>\nstruct is_getable\n{\n    static constexpr bool value = is_detected<get_template_function, const BasicJsonType&, T>::value;\n};\n\ntemplate<typename BasicJsonType, typename T>\nstruct has_from_json < BasicJsonType, T, enable_if_t < !is_basic_json<T>::value >>\n{\n    using serializer = typename BasicJsonType::template json_serializer<T, void>;\n\n    static constexpr bool value =\n        is_detected_exact<void, from_json_function, serializer,\n        const BasicJsonType&, T&>::value;\n};\n\n// This trait checks if JSONSerializer<T>::from_json(json const&) exists\n// this overload is used for non-default-constructible user-defined-types\ntemplate<typename BasicJsonType, typename T, typename = void>\nstruct has_non_default_from_json : std::false_type {};\n\ntemplate<typename BasicJsonType, typename T>\nstruct has_non_default_from_json < BasicJsonType, T, enable_if_t < !is_basic_json<T>::value >>\n{\n    using serializer = typename BasicJsonType::template json_serializer<T, void>;\n\n    static constexpr bool value =\n        is_detected_exact<T, from_json_function, serializer,\n        const BasicJsonType&>::value;\n};\n\n// This trait checks if BasicJsonType::json_serializer<T>::to_json exists\n// Do not evaluate the trait when T is a basic_json type, to avoid template instantiation infinite recursion.\ntemplate<typename BasicJsonType, typename T, typename = void>\nstruct has_to_json : std::false_type {};\n\ntemplate<typename BasicJsonType, typename T>\nstruct has_to_json < BasicJsonType, T, enable_if_t < !is_basic_json<T>::value >>\n{\n    using serializer = typename BasicJsonType::template json_serializer<T, void>;\n\n    static constexpr bool value =\n        is_detected_exact<void, to_json_function, serializer, BasicJsonType&,\n        T>::value;\n};\n\ntemplate<typename T>\nusing detect_key_compare = typename T::key_compare;\n\ntemplate<typename T>\nstruct has_key_compare : std::integral_constant<bool, is_detected<detect_key_compare, T>::value> {};\n\n// obtains the actual object key comparator\ntemplate<typename BasicJsonType>\nstruct actual_object_comparator\n{\n    using object_t = typename BasicJsonType::object_t;\n    using object_comparator_t = typename BasicJsonType::default_object_comparator_t;\n    using type = typename std::conditional < has_key_compare<object_t>::value,\n          typename object_t::key_compare, object_comparator_t>::type;\n};\n\ntemplate<typename BasicJsonType>\nusing actual_object_comparator_t = typename actual_object_comparator<BasicJsonType>::type;\n\n/////////////////\n// char_traits //\n/////////////////\n\n// Primary template of char_traits calls std char_traits\ntemplate<typename T>\nstruct char_traits : std::char_traits<T>\n{};\n\n// Explicitly define char traits for unsigned char since it is not standard\ntemplate<>\nstruct char_traits<unsigned char> : std::char_traits<char>\n{\n    using char_type = unsigned char;\n    using int_type = uint64_t;\n\n    // Redefine to_int_type function\n    static int_type to_int_type(char_type c) noexcept\n    {\n        return static_cast<int_type>(c);\n    }\n\n    static char_type to_char_type(int_type i) noexcept\n    {\n        return static_cast<char_type>(i);\n    }\n\n    static constexpr int_type eof() noexcept\n    {\n        return static_cast<int_type>(std::char_traits<char>::eof());\n    }\n};\n\n// Explicitly define char traits for signed char since it is not standard\ntemplate<>\nstruct char_traits<signed char> : std::char_traits<char>\n{\n    using char_type = signed char;\n    using int_type = uint64_t;\n\n    // Redefine to_int_type function\n    static int_type to_int_type(char_type c) noexcept\n    {\n        return static_cast<int_type>(c);\n    }\n\n    static char_type to_char_type(int_type i) noexcept\n    {\n        return static_cast<char_type>(i);\n    }\n\n    static constexpr int_type eof() noexcept\n    {\n        return static_cast<int_type>(std::char_traits<char>::eof());\n    }\n};\n\n///////////////////\n// is_ functions //\n///////////////////\n\n// https://en.cppreference.com/w/cpp/types/conjunction\ntemplate<class...> struct conjunction : std::true_type { };\ntemplate<class B> struct conjunction<B> : B { };\ntemplate<class B, class... Bn>\nstruct conjunction<B, Bn...>\n: std::conditional<static_cast<bool>(B::value), conjunction<Bn...>, B>::type {};\n\n// https://en.cppreference.com/w/cpp/types/negation\ntemplate<class B> struct negation : std::integral_constant < bool, !B::value > { };\n\n// Reimplementation of is_constructible and is_default_constructible, due to them being broken for\n// std::pair and std::tuple until LWG 2367 fix (see https://cplusplus.github.io/LWG/lwg-defects.html#2367).\n// This causes compile errors in e.g. clang 3.5 or gcc 4.9.\ntemplate <typename T>\nstruct is_default_constructible : std::is_default_constructible<T> {};\n\ntemplate <typename T1, typename T2>\nstruct is_default_constructible<std::pair<T1, T2>>\n    : conjunction<is_default_constructible<T1>, is_default_constructible<T2>> {};\n\ntemplate <typename T1, typename T2>\nstruct is_default_constructible<const std::pair<T1, T2>>\n    : conjunction<is_default_constructible<T1>, is_default_constructible<T2>> {};\n\ntemplate <typename... Ts>\nstruct is_default_constructible<std::tuple<Ts...>>\n    : conjunction<is_default_constructible<Ts>...> {};\n\ntemplate <typename... Ts>\nstruct is_default_constructible<const std::tuple<Ts...>>\n    : conjunction<is_default_constructible<Ts>...> {};\n\ntemplate <typename T, typename... Args>\nstruct is_constructible : std::is_constructible<T, Args...> {};\n\ntemplate <typename T1, typename T2>\nstruct is_constructible<std::pair<T1, T2>> : is_default_constructible<std::pair<T1, T2>> {};\n\ntemplate <typename T1, typename T2>\nstruct is_constructible<const std::pair<T1, T2>> : is_default_constructible<const std::pair<T1, T2>> {};\n\ntemplate <typename... Ts>\nstruct is_constructible<std::tuple<Ts...>> : is_default_constructible<std::tuple<Ts...>> {};\n\ntemplate <typename... Ts>\nstruct is_constructible<const std::tuple<Ts...>> : is_default_constructible<const std::tuple<Ts...>> {};\n\ntemplate<typename T, typename = void>\nstruct is_iterator_traits : std::false_type {};\n\ntemplate<typename T>\nstruct is_iterator_traits<iterator_traits<T>>\n{\n  private:\n    using traits = iterator_traits<T>;\n\n  public:\n    static constexpr auto value =\n        is_detected<value_type_t, traits>::value &&\n        is_detected<difference_type_t, traits>::value &&\n        is_detected<pointer_t, traits>::value &&\n        is_detected<iterator_category_t, traits>::value &&\n        is_detected<reference_t, traits>::value;\n};\n\ntemplate<typename T>\nstruct is_range\n{\n  private:\n    using t_ref = typename std::add_lvalue_reference<T>::type;\n\n    using iterator = detected_t<result_of_begin, t_ref>;\n    using sentinel = detected_t<result_of_end, t_ref>;\n\n    // to be 100% correct, it should use https://en.cppreference.com/w/cpp/iterator/input_or_output_iterator\n    // and https://en.cppreference.com/w/cpp/iterator/sentinel_for\n    // but reimplementing these would be too much work, as a lot of other concepts are used underneath\n    static constexpr auto is_iterator_begin =\n        is_iterator_traits<iterator_traits<iterator>>::value;\n\n  public:\n    static constexpr bool value = !std::is_same<iterator, nonesuch>::value && !std::is_same<sentinel, nonesuch>::value && is_iterator_begin;\n};\n\ntemplate<typename R>\nusing iterator_t = enable_if_t<is_range<R>::value, result_of_begin<decltype(std::declval<R&>())>>;\n\ntemplate<typename T>\nusing range_value_t = value_type_t<iterator_traits<iterator_t<T>>>;\n\n// The following implementation of is_complete_type is taken from\n// https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/\n// and is written by Xiang Fan who agreed to using it in this library.\n\ntemplate<typename T, typename = void>\nstruct is_complete_type : std::false_type {};\n\ntemplate<typename T>\nstruct is_complete_type<T, decltype(void(sizeof(T)))> : std::true_type {};\n\ntemplate<typename BasicJsonType, typename CompatibleObjectType,\n         typename = void>\nstruct is_compatible_object_type_impl : std::false_type {};\n\ntemplate<typename BasicJsonType, typename CompatibleObjectType>\nstruct is_compatible_object_type_impl <\n    BasicJsonType, CompatibleObjectType,\n    enable_if_t < is_detected<mapped_type_t, CompatibleObjectType>::value&&\n    is_detected<key_type_t, CompatibleObjectType>::value >>\n{\n    using object_t = typename BasicJsonType::object_t;\n\n    // macOS's is_constructible does not play well with nonesuch...\n    static constexpr bool value =\n        is_constructible<typename object_t::key_type,\n        typename CompatibleObjectType::key_type>::value &&\n        is_constructible<typename object_t::mapped_type,\n        typename CompatibleObjectType::mapped_type>::value;\n};\n\ntemplate<typename BasicJsonType, typename CompatibleObjectType>\nstruct is_compatible_object_type\n    : is_compatible_object_type_impl<BasicJsonType, CompatibleObjectType> {};\n\ntemplate<typename BasicJsonType, typename ConstructibleObjectType,\n         typename = void>\nstruct is_constructible_object_type_impl : std::false_type {};\n\ntemplate<typename BasicJsonType, typename ConstructibleObjectType>\nstruct is_constructible_object_type_impl <\n    BasicJsonType, ConstructibleObjectType,\n    enable_if_t < is_detected<mapped_type_t, ConstructibleObjectType>::value&&\n    is_detected<key_type_t, ConstructibleObjectType>::value >>\n{\n    using object_t = typename BasicJsonType::object_t;\n\n    static constexpr bool value =\n        (is_default_constructible<ConstructibleObjectType>::value &&\n         (std::is_move_assignable<ConstructibleObjectType>::value ||\n          std::is_copy_assignable<ConstructibleObjectType>::value) &&\n         (is_constructible<typename ConstructibleObjectType::key_type,\n          typename object_t::key_type>::value &&\n          std::is_same <\n          typename object_t::mapped_type,\n          typename ConstructibleObjectType::mapped_type >::value)) ||\n        (has_from_json<BasicJsonType,\n         typename ConstructibleObjectType::mapped_type>::value ||\n         has_non_default_from_json <\n         BasicJsonType,\n         typename ConstructibleObjectType::mapped_type >::value);\n};\n\ntemplate<typename BasicJsonType, typename ConstructibleObjectType>\nstruct is_constructible_object_type\n    : is_constructible_object_type_impl<BasicJsonType,\n      ConstructibleObjectType> {};\n\ntemplate<typename BasicJsonType, typename CompatibleStringType>\nstruct is_compatible_string_type\n{\n    static constexpr auto value =\n        is_constructible<typename BasicJsonType::string_t, CompatibleStringType>::value;\n};\n\ntemplate<typename BasicJsonType, typename ConstructibleStringType>\nstruct is_constructible_string_type\n{\n    // launder type through decltype() to fix compilation failure on ICPC\n#ifdef __INTEL_COMPILER\n    using laundered_type = decltype(std::declval<ConstructibleStringType>());\n#else\n    using laundered_type = ConstructibleStringType;\n#endif\n\n    static constexpr auto value =\n        conjunction <\n        is_constructible<laundered_type, typename BasicJsonType::string_t>,\n        is_detected_exact<typename BasicJsonType::string_t::value_type,\n        value_type_t, laundered_type >>::value;\n};\n\ntemplate<typename BasicJsonType, typename CompatibleArrayType, typename = void>\nstruct is_compatible_array_type_impl : std::false_type {};\n\ntemplate<typename BasicJsonType, typename CompatibleArrayType>\nstruct is_compatible_array_type_impl <\n    BasicJsonType, CompatibleArrayType,\n    enable_if_t <\n    is_detected<iterator_t, CompatibleArrayType>::value&&\n    is_iterator_traits<iterator_traits<detected_t<iterator_t, CompatibleArrayType>>>::value&&\n// special case for types like std::filesystem::path whose iterator's value_type are themselves\n// c.f. https://github.com/nlohmann/json/pull/3073\n    !std::is_same<CompatibleArrayType, detected_t<range_value_t, CompatibleArrayType>>::value >>\n{\n    static constexpr bool value =\n        is_constructible<BasicJsonType,\n        range_value_t<CompatibleArrayType>>::value;\n};\n\ntemplate<typename BasicJsonType, typename CompatibleArrayType>\nstruct is_compatible_array_type\n    : is_compatible_array_type_impl<BasicJsonType, CompatibleArrayType> {};\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType, typename = void>\nstruct is_constructible_array_type_impl : std::false_type {};\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType>\nstruct is_constructible_array_type_impl <\n    BasicJsonType, ConstructibleArrayType,\n    enable_if_t<std::is_same<ConstructibleArrayType,\n    typename BasicJsonType::value_type>::value >>\n            : std::true_type {};\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType>\nstruct is_constructible_array_type_impl <\n    BasicJsonType, ConstructibleArrayType,\n    enable_if_t < !std::is_same<ConstructibleArrayType,\n    typename BasicJsonType::value_type>::value&&\n    !is_compatible_string_type<BasicJsonType, ConstructibleArrayType>::value&&\n    is_default_constructible<ConstructibleArrayType>::value&&\n(std::is_move_assignable<ConstructibleArrayType>::value ||\n std::is_copy_assignable<ConstructibleArrayType>::value)&&\nis_detected<iterator_t, ConstructibleArrayType>::value&&\nis_iterator_traits<iterator_traits<detected_t<iterator_t, ConstructibleArrayType>>>::value&&\nis_detected<range_value_t, ConstructibleArrayType>::value&&\n// special case for types like std::filesystem::path whose iterator's value_type are themselves\n// c.f. https://github.com/nlohmann/json/pull/3073\n!std::is_same<ConstructibleArrayType, detected_t<range_value_t, ConstructibleArrayType>>::value&&\nis_complete_type <\ndetected_t<range_value_t, ConstructibleArrayType >>::value >>\n{\n    using value_type = range_value_t<ConstructibleArrayType>;\n\n    static constexpr bool value =\n        std::is_same<value_type,\n        typename BasicJsonType::array_t::value_type>::value ||\n        has_from_json<BasicJsonType,\n        value_type>::value ||\n        has_non_default_from_json <\n        BasicJsonType,\n        value_type >::value;\n};\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType>\nstruct is_constructible_array_type\n    : is_constructible_array_type_impl<BasicJsonType, ConstructibleArrayType> {};\n\ntemplate<typename RealIntegerType, typename CompatibleNumberIntegerType,\n         typename = void>\nstruct is_compatible_integer_type_impl : std::false_type {};\n\ntemplate<typename RealIntegerType, typename CompatibleNumberIntegerType>\nstruct is_compatible_integer_type_impl <\n    RealIntegerType, CompatibleNumberIntegerType,\n    enable_if_t < std::is_integral<RealIntegerType>::value&&\n    std::is_integral<CompatibleNumberIntegerType>::value&&\n    !std::is_same<bool, CompatibleNumberIntegerType>::value >>\n{\n    // is there an assert somewhere on overflows?\n    using RealLimits = std::numeric_limits<RealIntegerType>;\n    using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>;\n\n    static constexpr auto value =\n        is_constructible<RealIntegerType,\n        CompatibleNumberIntegerType>::value &&\n        CompatibleLimits::is_integer &&\n        RealLimits::is_signed == CompatibleLimits::is_signed;\n};\n\ntemplate<typename RealIntegerType, typename CompatibleNumberIntegerType>\nstruct is_compatible_integer_type\n    : is_compatible_integer_type_impl<RealIntegerType,\n      CompatibleNumberIntegerType> {};\n\ntemplate<typename BasicJsonType, typename CompatibleType, typename = void>\nstruct is_compatible_type_impl: std::false_type {};\n\ntemplate<typename BasicJsonType, typename CompatibleType>\nstruct is_compatible_type_impl <\n    BasicJsonType, CompatibleType,\n    enable_if_t<is_complete_type<CompatibleType>::value >>\n{\n    static constexpr bool value =\n        has_to_json<BasicJsonType, CompatibleType>::value;\n};\n\ntemplate<typename BasicJsonType, typename CompatibleType>\nstruct is_compatible_type\n    : is_compatible_type_impl<BasicJsonType, CompatibleType> {};\n\ntemplate<typename T1, typename T2>\nstruct is_constructible_tuple : std::false_type {};\n\ntemplate<typename T1, typename... Args>\nstruct is_constructible_tuple<T1, std::tuple<Args...>> : conjunction<is_constructible<T1, Args>...> {};\n\ntemplate<typename BasicJsonType, typename T>\nstruct is_json_iterator_of : std::false_type {};\n\ntemplate<typename BasicJsonType>\nstruct is_json_iterator_of<BasicJsonType, typename BasicJsonType::iterator> : std::true_type {};\n\ntemplate<typename BasicJsonType>\nstruct is_json_iterator_of<BasicJsonType, typename BasicJsonType::const_iterator> : std::true_type\n{};\n\n// checks if a given type T is a template specialization of Primary\ntemplate<template <typename...> class Primary, typename T>\nstruct is_specialization_of : std::false_type {};\n\ntemplate<template <typename...> class Primary, typename... Args>\nstruct is_specialization_of<Primary, Primary<Args...>> : std::true_type {};\n\ntemplate<typename T>\nusing is_json_pointer = is_specialization_of<::nlohmann::json_pointer, uncvref_t<T>>;\n\n// checks if A and B are comparable using Compare functor\ntemplate<typename Compare, typename A, typename B, typename = void>\nstruct is_comparable : std::false_type {};\n\ntemplate<typename Compare, typename A, typename B>\nstruct is_comparable<Compare, A, B, void_t<\ndecltype(std::declval<Compare>()(std::declval<A>(), std::declval<B>())),\ndecltype(std::declval<Compare>()(std::declval<B>(), std::declval<A>()))\n>> : std::true_type {};\n\ntemplate<typename T>\nusing detect_is_transparent = typename T::is_transparent;\n\n// type trait to check if KeyType can be used as object key (without a BasicJsonType)\n// see is_usable_as_basic_json_key_type below\ntemplate<typename Comparator, typename ObjectKeyType, typename KeyTypeCVRef, bool RequireTransparentComparator = true,\n         bool ExcludeObjectKeyType = RequireTransparentComparator, typename KeyType = uncvref_t<KeyTypeCVRef>>\nusing is_usable_as_key_type = typename std::conditional <\n                              is_comparable<Comparator, ObjectKeyType, KeyTypeCVRef>::value\n                              && !(ExcludeObjectKeyType && std::is_same<KeyType,\n                                   ObjectKeyType>::value)\n                              && (!RequireTransparentComparator\n                                  || is_detected <detect_is_transparent, Comparator>::value)\n                              && !is_json_pointer<KeyType>::value,\n                              std::true_type,\n                              std::false_type >::type;\n\n// type trait to check if KeyType can be used as object key\n// true if:\n//   - KeyType is comparable with BasicJsonType::object_t::key_type\n//   - if ExcludeObjectKeyType is true, KeyType is not BasicJsonType::object_t::key_type\n//   - the comparator is transparent or RequireTransparentComparator is false\n//   - KeyType is not a JSON iterator or json_pointer\ntemplate<typename BasicJsonType, typename KeyTypeCVRef, bool RequireTransparentComparator = true,\n         bool ExcludeObjectKeyType = RequireTransparentComparator, typename KeyType = uncvref_t<KeyTypeCVRef>>\nusing is_usable_as_basic_json_key_type = typename std::conditional <\n    is_usable_as_key_type<typename BasicJsonType::object_comparator_t,\n    typename BasicJsonType::object_t::key_type, KeyTypeCVRef,\n    RequireTransparentComparator, ExcludeObjectKeyType>::value\n    && !is_json_iterator_of<BasicJsonType, KeyType>::value,\n    std::true_type,\n    std::false_type >::type;\n\ntemplate<typename ObjectType, typename KeyType>\nusing detect_erase_with_key_type = decltype(std::declval<ObjectType&>().erase(std::declval<KeyType>()));\n\n// type trait to check if object_t has an erase() member functions accepting KeyType\ntemplate<typename BasicJsonType, typename KeyType>\nusing has_erase_with_key_type = typename std::conditional <\n                                is_detected <\n                                detect_erase_with_key_type,\n                                typename BasicJsonType::object_t, KeyType >::value,\n                                std::true_type,\n                                std::false_type >::type;\n\n// a naive helper to check if a type is an ordered_map (exploits the fact that\n// ordered_map inherits capacity() from std::vector)\ntemplate <typename T>\nstruct is_ordered_map\n{\n    using one = char;\n\n    struct two\n    {\n        char x[2]; // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n    };\n\n    template <typename C> static one test( decltype(&C::capacity) ) ;\n    template <typename C> static two test(...);\n\n    enum { value = sizeof(test<T>(nullptr)) == sizeof(char) }; // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n};\n\n// to avoid useless casts (see https://github.com/nlohmann/json/issues/2893#issuecomment-889152324)\ntemplate < typename T, typename U, enable_if_t < !std::is_same<T, U>::value, int > = 0 >\nT conditional_static_cast(U value)\n{\n    return static_cast<T>(value);\n}\n\ntemplate<typename T, typename U, enable_if_t<std::is_same<T, U>::value, int> = 0>\nT conditional_static_cast(U value)\n{\n    return value;\n}\n\ntemplate<typename... Types>\nusing all_integral = conjunction<std::is_integral<Types>...>;\n\ntemplate<typename... Types>\nusing all_signed = conjunction<std::is_signed<Types>...>;\n\ntemplate<typename... Types>\nusing all_unsigned = conjunction<std::is_unsigned<Types>...>;\n\n// there's a disjunction trait in another PR; replace when merged\ntemplate<typename... Types>\nusing same_sign = std::integral_constant < bool,\n      all_signed<Types...>::value || all_unsigned<Types...>::value >;\n\ntemplate<typename OfType, typename T>\nusing never_out_of_range = std::integral_constant < bool,\n      (std::is_signed<OfType>::value && (sizeof(T) < sizeof(OfType)))\n      || (same_sign<OfType, T>::value && sizeof(OfType) == sizeof(T)) >;\n\ntemplate<typename OfType, typename T,\n         bool OfTypeSigned = std::is_signed<OfType>::value,\n         bool TSigned = std::is_signed<T>::value>\nstruct value_in_range_of_impl2;\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl2<OfType, T, false, false>\n{\n    static constexpr bool test(T val)\n    {\n        using CommonType = typename std::common_type<OfType, T>::type;\n        return static_cast<CommonType>(val) <= static_cast<CommonType>((std::numeric_limits<OfType>::max)());\n    }\n};\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl2<OfType, T, true, false>\n{\n    static constexpr bool test(T val)\n    {\n        using CommonType = typename std::common_type<OfType, T>::type;\n        return static_cast<CommonType>(val) <= static_cast<CommonType>((std::numeric_limits<OfType>::max)());\n    }\n};\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl2<OfType, T, false, true>\n{\n    static constexpr bool test(T val)\n    {\n        using CommonType = typename std::common_type<OfType, T>::type;\n        return val >= 0 && static_cast<CommonType>(val) <= static_cast<CommonType>((std::numeric_limits<OfType>::max)());\n    }\n};\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl2<OfType, T, true, true>\n{\n    static constexpr bool test(T val)\n    {\n        using CommonType = typename std::common_type<OfType, T>::type;\n        return static_cast<CommonType>(val) >= static_cast<CommonType>((std::numeric_limits<OfType>::min)())\n               && static_cast<CommonType>(val) <= static_cast<CommonType>((std::numeric_limits<OfType>::max)());\n    }\n};\n\ntemplate<typename OfType, typename T,\n         bool NeverOutOfRange = never_out_of_range<OfType, T>::value,\n         typename = detail::enable_if_t<all_integral<OfType, T>::value>>\nstruct value_in_range_of_impl1;\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl1<OfType, T, false>\n{\n    static constexpr bool test(T val)\n    {\n        return value_in_range_of_impl2<OfType, T>::test(val);\n    }\n};\n\ntemplate<typename OfType, typename T>\nstruct value_in_range_of_impl1<OfType, T, true>\n{\n    static constexpr bool test(T /*val*/)\n    {\n        return true;\n    }\n};\n\ntemplate<typename OfType, typename T>\nconstexpr bool value_in_range_of(T val)\n{\n    return value_in_range_of_impl1<OfType, T>::test(val);\n}\n\ntemplate<bool Value>\nusing bool_constant = std::integral_constant<bool, Value>;\n\n///////////////////////////////////////////////////////////////////////////////\n// is_c_string\n///////////////////////////////////////////////////////////////////////////////\n\nnamespace impl\n{\n\ntemplate<typename T>\nconstexpr bool is_c_string()\n{\n    using TUnExt = typename std::remove_extent<T>::type;\n    using TUnCVExt = typename std::remove_cv<TUnExt>::type;\n    using TUnPtr = typename std::remove_pointer<T>::type;\n    using TUnCVPtr = typename std::remove_cv<TUnPtr>::type;\n    return\n        (std::is_array<T>::value && std::is_same<TUnCVExt, char>::value)\n        || (std::is_pointer<T>::value && std::is_same<TUnCVPtr, char>::value);\n}\n\n}  // namespace impl\n\n// checks whether T is a [cv] char */[cv] char[] C string\ntemplate<typename T>\nstruct is_c_string : bool_constant<impl::is_c_string<T>()> {};\n\ntemplate<typename T>\nusing is_c_string_uncvref = is_c_string<uncvref_t<T>>;\n\n///////////////////////////////////////////////////////////////////////////////\n// is_transparent\n///////////////////////////////////////////////////////////////////////////////\n\nnamespace impl\n{\n\ntemplate<typename T>\nconstexpr bool is_transparent()\n{\n    return is_detected<detect_is_transparent, T>::value;\n}\n\n}  // namespace impl\n\n// checks whether T has a member named is_transparent\ntemplate<typename T>\nstruct is_transparent : bool_constant<impl::is_transparent<T>()> {};\n\n///////////////////////////////////////////////////////////////////////////////\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/string_concat.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstring> // strlen\n#include <string> // string\n#include <utility> // forward\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/detected.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ninline std::size_t concat_length()\n{\n    return 0;\n}\n\ntemplate<typename... Args>\ninline std::size_t concat_length(const char* cstr, const Args& ... rest);\n\ntemplate<typename StringType, typename... Args>\ninline std::size_t concat_length(const StringType& str, const Args& ... rest);\n\ntemplate<typename... Args>\ninline std::size_t concat_length(const char /*c*/, const Args& ... rest)\n{\n    return 1 + concat_length(rest...);\n}\n\ntemplate<typename... Args>\ninline std::size_t concat_length(const char* cstr, const Args& ... rest)\n{\n    // cppcheck-suppress ignoredReturnValue\n    return ::strlen(cstr) + concat_length(rest...);\n}\n\ntemplate<typename StringType, typename... Args>\ninline std::size_t concat_length(const StringType& str, const Args& ... rest)\n{\n    return str.size() + concat_length(rest...);\n}\n\ntemplate<typename OutStringType>\ninline void concat_into(OutStringType& /*out*/)\n{}\n\ntemplate<typename StringType, typename Arg>\nusing string_can_append = decltype(std::declval<StringType&>().append(std::declval < Arg && > ()));\n\ntemplate<typename StringType, typename Arg>\nusing detect_string_can_append = is_detected<string_can_append, StringType, Arg>;\n\ntemplate<typename StringType, typename Arg>\nusing string_can_append_op = decltype(std::declval<StringType&>() += std::declval < Arg && > ());\n\ntemplate<typename StringType, typename Arg>\nusing detect_string_can_append_op = is_detected<string_can_append_op, StringType, Arg>;\n\ntemplate<typename StringType, typename Arg>\nusing string_can_append_iter = decltype(std::declval<StringType&>().append(std::declval<const Arg&>().begin(), std::declval<const Arg&>().end()));\n\ntemplate<typename StringType, typename Arg>\nusing detect_string_can_append_iter = is_detected<string_can_append_iter, StringType, Arg>;\n\ntemplate<typename StringType, typename Arg>\nusing string_can_append_data = decltype(std::declval<StringType&>().append(std::declval<const Arg&>().data(), std::declval<const Arg&>().size()));\n\ntemplate<typename StringType, typename Arg>\nusing detect_string_can_append_data = is_detected<string_can_append_data, StringType, Arg>;\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && detect_string_can_append_op<OutStringType, Arg>::value, int > = 0 >\ninline void concat_into(OutStringType& out, Arg && arg, Args && ... rest);\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && !detect_string_can_append_op<OutStringType, Arg>::value\n                         && detect_string_can_append_iter<OutStringType, Arg>::value, int > = 0 >\ninline void concat_into(OutStringType& out, const Arg& arg, Args && ... rest);\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && !detect_string_can_append_op<OutStringType, Arg>::value\n                         && !detect_string_can_append_iter<OutStringType, Arg>::value\n                         && detect_string_can_append_data<OutStringType, Arg>::value, int > = 0 >\ninline void concat_into(OutStringType& out, const Arg& arg, Args && ... rest);\n\ntemplate<typename OutStringType, typename Arg, typename... Args,\n         enable_if_t<detect_string_can_append<OutStringType, Arg>::value, int> = 0>\ninline void concat_into(OutStringType& out, Arg && arg, Args && ... rest)\n{\n    out.append(std::forward<Arg>(arg));\n    concat_into(out, std::forward<Args>(rest)...);\n}\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && detect_string_can_append_op<OutStringType, Arg>::value, int > >\ninline void concat_into(OutStringType& out, Arg&& arg, Args&& ... rest)\n{\n    out += std::forward<Arg>(arg);\n    concat_into(out, std::forward<Args>(rest)...);\n}\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && !detect_string_can_append_op<OutStringType, Arg>::value\n                         && detect_string_can_append_iter<OutStringType, Arg>::value, int > >\ninline void concat_into(OutStringType& out, const Arg& arg, Args&& ... rest)\n{\n    out.append(arg.begin(), arg.end());\n    concat_into(out, std::forward<Args>(rest)...);\n}\n\ntemplate < typename OutStringType, typename Arg, typename... Args,\n           enable_if_t < !detect_string_can_append<OutStringType, Arg>::value\n                         && !detect_string_can_append_op<OutStringType, Arg>::value\n                         && !detect_string_can_append_iter<OutStringType, Arg>::value\n                         && detect_string_can_append_data<OutStringType, Arg>::value, int > >\ninline void concat_into(OutStringType& out, const Arg& arg, Args&& ... rest)\n{\n    out.append(arg.data(), arg.size());\n    concat_into(out, std::forward<Args>(rest)...);\n}\n\ntemplate<typename OutStringType = std::string, typename... Args>\ninline OutStringType concat(Args && ... args)\n{\n    OutStringType str;\n    str.reserve(concat_length(args...));\n    concat_into(str, std::forward<Args>(args)...);\n    return str;\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n\n// With -Wweak-vtables, Clang will complain about the exception classes as they\n// have no out-of-line virtual method definitions and their vtable will be\n// emitted in every translation unit. This issue cannot be fixed with a\n// header-only library as there is no implementation file to move these\n// functions to. As a result, we suppress this warning here to avoid client\n// code to stumble over this. See https://github.com/nlohmann/json/issues/4087\n// for a discussion.\n#if defined(__clang__)\n    #pragma clang diagnostic push\n    #pragma clang diagnostic ignored \"-Wweak-vtables\"\n#endif\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n////////////////\n// exceptions //\n////////////////\n\n/// @brief general exception of the @ref basic_json class\n/// @sa https://json.nlohmann.me/api/basic_json/exception/\nclass exception : public std::exception\n{\n  public:\n    /// returns the explanatory string\n    const char* what() const noexcept override\n    {\n        return m.what();\n    }\n\n    /// the id of the exception\n    const int id; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)\n\n  protected:\n    JSON_HEDLEY_NON_NULL(3)\n    exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} // NOLINT(bugprone-throw-keyword-missing)\n\n    static std::string name(const std::string& ename, int id_)\n    {\n        return concat(\"[json.exception.\", ename, '.', std::to_string(id_), \"] \");\n    }\n\n    static std::string diagnostics(std::nullptr_t /*leaf_element*/)\n    {\n        return \"\";\n    }\n\n    template<typename BasicJsonType>\n    static std::string diagnostics(const BasicJsonType* leaf_element)\n    {\n#if JSON_DIAGNOSTICS\n        std::vector<std::string> tokens;\n        for (const auto* current = leaf_element; current != nullptr && current->m_parent != nullptr; current = current->m_parent)\n        {\n            switch (current->m_parent->type())\n            {\n                case value_t::array:\n                {\n                    for (std::size_t i = 0; i < current->m_parent->m_data.m_value.array->size(); ++i)\n                    {\n                        if (&current->m_parent->m_data.m_value.array->operator[](i) == current)\n                        {\n                            tokens.emplace_back(std::to_string(i));\n                            break;\n                        }\n                    }\n                    break;\n                }\n\n                case value_t::object:\n                {\n                    for (const auto& element : *current->m_parent->m_data.m_value.object)\n                    {\n                        if (&element.second == current)\n                        {\n                            tokens.emplace_back(element.first.c_str());\n                            break;\n                        }\n                    }\n                    break;\n                }\n\n                case value_t::null: // LCOV_EXCL_LINE\n                case value_t::string: // LCOV_EXCL_LINE\n                case value_t::boolean: // LCOV_EXCL_LINE\n                case value_t::number_integer: // LCOV_EXCL_LINE\n                case value_t::number_unsigned: // LCOV_EXCL_LINE\n                case value_t::number_float: // LCOV_EXCL_LINE\n                case value_t::binary: // LCOV_EXCL_LINE\n                case value_t::discarded: // LCOV_EXCL_LINE\n                default:   // LCOV_EXCL_LINE\n                    break; // LCOV_EXCL_LINE\n            }\n        }\n\n        if (tokens.empty())\n        {\n            return \"\";\n        }\n\n        auto str = std::accumulate(tokens.rbegin(), tokens.rend(), std::string{},\n                                   [](const std::string & a, const std::string & b)\n        {\n            return concat(a, '/', detail::escape(b));\n        });\n\n        return concat('(', str, \") \", get_byte_positions(leaf_element));\n#else\n        return get_byte_positions(leaf_element);\n#endif\n    }\n\n  private:\n    /// an exception object as storage for error messages\n    std::runtime_error m;\n#if JSON_DIAGNOSTIC_POSITIONS\n    template<typename BasicJsonType>\n    static std::string get_byte_positions(const BasicJsonType* leaf_element)\n    {\n        if ((leaf_element->start_pos() != std::string::npos) && (leaf_element->end_pos() != std::string::npos))\n        {\n            return concat(\"(bytes \", std::to_string(leaf_element->start_pos()), \"-\", std::to_string(leaf_element->end_pos()), \") \");\n        }\n        return \"\";\n    }\n#else\n    template<typename BasicJsonType>\n    static std::string get_byte_positions(const BasicJsonType* leaf_element)\n    {\n        static_cast<void>(leaf_element);\n        return \"\";\n    }\n#endif\n};\n\n/// @brief exception indicating a parse error\n/// @sa https://json.nlohmann.me/api/basic_json/parse_error/\nclass parse_error : public exception\n{\n  public:\n    /*!\n    @brief create a parse error exception\n    @param[in] id_       the id of the exception\n    @param[in] pos       the position where the error occurred (or with\n                         chars_read_total=0 if the position cannot be\n                         determined)\n    @param[in] what_arg  the explanatory string\n    @return parse_error object\n    */\n    template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n    static parse_error create(int id_, const position_t& pos, const std::string& what_arg, BasicJsonContext context)\n    {\n        const std::string w = concat(exception::name(\"parse_error\", id_), \"parse error\",\n                                     position_string(pos), \": \", exception::diagnostics(context), what_arg);\n        return {id_, pos.chars_read_total, w.c_str()};\n    }\n\n    template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n    static parse_error create(int id_, std::size_t byte_, const std::string& what_arg, BasicJsonContext context)\n    {\n        const std::string w = concat(exception::name(\"parse_error\", id_), \"parse error\",\n                                     (byte_ != 0 ? (concat(\" at byte \", std::to_string(byte_))) : \"\"),\n                                     \": \", exception::diagnostics(context), what_arg);\n        return {id_, byte_, w.c_str()};\n    }\n\n    /*!\n    @brief byte index of the parse error\n\n    The byte index of the last read character in the input file.\n\n    @note For an input with n bytes, 1 is the index of the first character and\n          n+1 is the index of the terminating null byte or the end of file.\n          This also holds true when reading a byte vector (CBOR or MessagePack).\n    */\n    const std::size_t byte;\n\n  private:\n    parse_error(int id_, std::size_t byte_, const char* what_arg)\n        : exception(id_, what_arg), byte(byte_) {}\n\n    static std::string position_string(const position_t& pos)\n    {\n        return concat(\" at line \", std::to_string(pos.lines_read + 1),\n                      \", column \", std::to_string(pos.chars_read_current_line));\n    }\n};\n\n/// @brief exception indicating errors with iterators\n/// @sa https://json.nlohmann.me/api/basic_json/invalid_iterator/\nclass invalid_iterator : public exception\n{\n  public:\n    template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n    static invalid_iterator create(int id_, const std::string& what_arg, BasicJsonContext context)\n    {\n        const std::string w = concat(exception::name(\"invalid_iterator\", id_), exception::diagnostics(context), what_arg);\n        return {id_, w.c_str()};\n    }\n\n  private:\n    JSON_HEDLEY_NON_NULL(3)\n    invalid_iterator(int id_, const char* what_arg)\n        : exception(id_, what_arg) {}\n};\n\n/// @brief exception indicating executing a member function with a wrong type\n/// @sa https://json.nlohmann.me/api/basic_json/type_error/\nclass type_error : public exception\n{\n  public:\n    template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n    static type_error create(int id_, const std::string& what_arg, BasicJsonContext context)\n    {\n        const std::string w = concat(exception::name(\"type_error\", id_), exception::diagnostics(context), what_arg);\n        return {id_, w.c_str()};\n    }\n\n  private:\n    JSON_HEDLEY_NON_NULL(3)\n    type_error(int id_, const char* what_arg) : exception(id_, what_arg) {}\n};\n\n/// @brief exception indicating access out of the defined range\n/// @sa https://json.nlohmann.me/api/basic_json/out_of_range/\nclass out_of_range : public exception\n{\n  public:\n    template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n    static out_of_range create(int id_, const std::string& what_arg, BasicJsonContext context)\n    {\n        const std::string w = concat(exception::name(\"out_of_range\", id_), exception::diagnostics(context), what_arg);\n        return {id_, w.c_str()};\n    }\n\n  private:\n    JSON_HEDLEY_NON_NULL(3)\n    out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {}\n};\n\n/// @brief exception indicating other library errors\n/// @sa https://json.nlohmann.me/api/basic_json/other_error/\nclass other_error : public exception\n{\n  public:\n    template<typename BasicJsonContext, enable_if_t<is_basic_json_context<BasicJsonContext>::value, int> = 0>\n    static other_error create(int id_, const std::string& what_arg, BasicJsonContext context)\n    {\n        const std::string w = concat(exception::name(\"other_error\", id_), exception::diagnostics(context), what_arg);\n        return {id_, w.c_str()};\n    }\n\n  private:\n    JSON_HEDLEY_NON_NULL(3)\n    other_error(int id_, const char* what_arg) : exception(id_, what_arg) {}\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n#if defined(__clang__)\n    #pragma clang diagnostic pop\n#endif\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/identity_tag.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n// dispatching helper struct\ntemplate <class T> struct identity_tag {};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/std_fs.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\n#if JSON_HAS_EXPERIMENTAL_FILESYSTEM\n#include <experimental/filesystem>\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\nnamespace std_fs = std::experimental::filesystem;\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n#elif JSON_HAS_FILESYSTEM\n#include <filesystem> // NOLINT(build/c++17)\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\nnamespace std_fs = std::filesystem;\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n#endif\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, typename std::nullptr_t& n)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_null()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be null, but is \", j.type_name()), &j));\n    }\n    n = nullptr;\n}\n\n#ifdef JSON_HAS_CPP_17\n#ifndef JSON_USE_IMPLICIT_CONVERSIONS\ntemplate<typename BasicJsonType, typename T>\nvoid from_json(const BasicJsonType& j, std::optional<T>& opt)\n{\n    if (j.is_null())\n    {\n        opt = std::nullopt;\n    }\n    else\n    {\n        opt.emplace(j.template get<T>());\n    }\n}\n\n#endif // JSON_USE_IMPLICIT_CONVERSIONS\n#endif // JSON_HAS_CPP_17\n\n// overloads for basic_json template parameters\ntemplate < typename BasicJsonType, typename ArithmeticType,\n           enable_if_t < std::is_arithmetic<ArithmeticType>::value&&\n                         !std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,\n                         int > = 0 >\nvoid get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val)\n{\n    switch (static_cast<value_t>(j))\n    {\n        case value_t::number_unsigned:\n        {\n            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());\n            break;\n        }\n        case value_t::number_integer:\n        {\n            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());\n            break;\n        }\n        case value_t::number_float:\n        {\n            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());\n            break;\n        }\n\n        case value_t::null:\n        case value_t::object:\n        case value_t::array:\n        case value_t::string:\n        case value_t::boolean:\n        case value_t::binary:\n        case value_t::discarded:\n        default:\n            JSON_THROW(type_error::create(302, concat(\"type must be number, but is \", j.type_name()), &j));\n    }\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_boolean()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be boolean, but is \", j.type_name()), &j));\n    }\n    b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>();\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_string()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be string, but is \", j.type_name()), &j));\n    }\n    s = *j.template get_ptr<const typename BasicJsonType::string_t*>();\n}\n\ntemplate <\n    typename BasicJsonType, typename StringType,\n    enable_if_t <\n        std::is_assignable<StringType&, const typename BasicJsonType::string_t>::value\n        && is_detected_exact<typename BasicJsonType::string_t::value_type, value_type_t, StringType>::value\n        && !std::is_same<typename BasicJsonType::string_t, StringType>::value\n        && !is_json_ref<StringType>::value, int > = 0 >\ninline void from_json(const BasicJsonType& j, StringType& s)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_string()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be string, but is \", j.type_name()), &j));\n    }\n\n    s = *j.template get_ptr<const typename BasicJsonType::string_t*>();\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val)\n{\n    get_arithmetic_value(j, val);\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val)\n{\n    get_arithmetic_value(j, val);\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val)\n{\n    get_arithmetic_value(j, val);\n}\n\n#if !JSON_DISABLE_ENUM_SERIALIZATION\ntemplate<typename BasicJsonType, typename EnumType,\n         enable_if_t<std::is_enum<EnumType>::value, int> = 0>\ninline void from_json(const BasicJsonType& j, EnumType& e)\n{\n    typename std::underlying_type<EnumType>::type val;\n    get_arithmetic_value(j, val);\n    e = static_cast<EnumType>(val);\n}\n#endif  // JSON_DISABLE_ENUM_SERIALIZATION\n\n// forward_list doesn't have an insert method\ntemplate<typename BasicJsonType, typename T, typename Allocator,\n         enable_if_t<is_getable<BasicJsonType, T>::value, int> = 0>\ninline void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& l)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_array()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", j.type_name()), &j));\n    }\n    l.clear();\n    std::transform(j.rbegin(), j.rend(),\n                   std::front_inserter(l), [](const BasicJsonType & i)\n    {\n        return i.template get<T>();\n    });\n}\n\n// valarray doesn't have an insert method\ntemplate<typename BasicJsonType, typename T,\n         enable_if_t<is_getable<BasicJsonType, T>::value, int> = 0>\ninline void from_json(const BasicJsonType& j, std::valarray<T>& l)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_array()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", j.type_name()), &j));\n    }\n    l.resize(j.size());\n    std::transform(j.begin(), j.end(), std::begin(l),\n                   [](const BasicJsonType & elem)\n    {\n        return elem.template get<T>();\n    });\n}\n\ntemplate<typename BasicJsonType, typename T, std::size_t N>\nauto from_json(const BasicJsonType& j, T (&arr)[N])  // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n-> decltype(j.template get<T>(), void())\n{\n    for (std::size_t i = 0; i < N; ++i)\n    {\n        arr[i] = j.at(i).template get<T>();\n    }\n}\n\ntemplate<typename BasicJsonType, typename T, std::size_t N1, std::size_t N2>\nauto from_json(const BasicJsonType& j, T (&arr)[N1][N2])  // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n-> decltype(j.template get<T>(), void())\n{\n    for (std::size_t i1 = 0; i1 < N1; ++i1)\n    {\n        for (std::size_t i2 = 0; i2 < N2; ++i2)\n        {\n            arr[i1][i2] = j.at(i1).at(i2).template get<T>();\n        }\n    }\n}\n\ntemplate<typename BasicJsonType, typename T, std::size_t N1, std::size_t N2, std::size_t N3>\nauto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3])  // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n-> decltype(j.template get<T>(), void())\n{\n    for (std::size_t i1 = 0; i1 < N1; ++i1)\n    {\n        for (std::size_t i2 = 0; i2 < N2; ++i2)\n        {\n            for (std::size_t i3 = 0; i3 < N3; ++i3)\n            {\n                arr[i1][i2][i3] = j.at(i1).at(i2).at(i3).template get<T>();\n            }\n        }\n    }\n}\n\ntemplate<typename BasicJsonType, typename T, std::size_t N1, std::size_t N2, std::size_t N3, std::size_t N4>\nauto from_json(const BasicJsonType& j, T (&arr)[N1][N2][N3][N4])  // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n-> decltype(j.template get<T>(), void())\n{\n    for (std::size_t i1 = 0; i1 < N1; ++i1)\n    {\n        for (std::size_t i2 = 0; i2 < N2; ++i2)\n        {\n            for (std::size_t i3 = 0; i3 < N3; ++i3)\n            {\n                for (std::size_t i4 = 0; i4 < N4; ++i4)\n                {\n                    arr[i1][i2][i3][i4] = j.at(i1).at(i2).at(i3).at(i4).template get<T>();\n                }\n            }\n        }\n    }\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/)\n{\n    arr = *j.template get_ptr<const typename BasicJsonType::array_t*>();\n}\n\ntemplate<typename BasicJsonType, typename T, std::size_t N>\nauto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,\n                          priority_tag<2> /*unused*/)\n-> decltype(j.template get<T>(), void())\n{\n    for (std::size_t i = 0; i < N; ++i)\n    {\n        arr[i] = j.at(i).template get<T>();\n    }\n}\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType,\n         enable_if_t<\n             std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value,\n             int> = 0>\nauto from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, priority_tag<1> /*unused*/)\n-> decltype(\n    arr.reserve(std::declval<typename ConstructibleArrayType::size_type>()),\n    j.template get<typename ConstructibleArrayType::value_type>(),\n    void())\n{\n    using std::end;\n\n    ConstructibleArrayType ret;\n    ret.reserve(j.size());\n    std::transform(j.begin(), j.end(),\n                   std::inserter(ret, end(ret)), [](const BasicJsonType & i)\n    {\n        // get<BasicJsonType>() returns *this, this won't call a from_json\n        // method when value_type is BasicJsonType\n        return i.template get<typename ConstructibleArrayType::value_type>();\n    });\n    arr = std::move(ret);\n}\n\ntemplate<typename BasicJsonType, typename ConstructibleArrayType,\n         enable_if_t<\n             std::is_assignable<ConstructibleArrayType&, ConstructibleArrayType>::value,\n             int> = 0>\ninline void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr,\n                                 priority_tag<0> /*unused*/)\n{\n    using std::end;\n\n    ConstructibleArrayType ret;\n    std::transform(\n        j.begin(), j.end(), std::inserter(ret, end(ret)),\n        [](const BasicJsonType & i)\n    {\n        // get<BasicJsonType>() returns *this, this won't call a from_json\n        // method when value_type is BasicJsonType\n        return i.template get<typename ConstructibleArrayType::value_type>();\n    });\n    arr = std::move(ret);\n}\n\ntemplate < typename BasicJsonType, typename ConstructibleArrayType,\n           enable_if_t <\n               is_constructible_array_type<BasicJsonType, ConstructibleArrayType>::value&&\n               !is_constructible_object_type<BasicJsonType, ConstructibleArrayType>::value&&\n               !is_constructible_string_type<BasicJsonType, ConstructibleArrayType>::value&&\n               !std::is_same<ConstructibleArrayType, typename BasicJsonType::binary_t>::value&&\n               !is_basic_json<ConstructibleArrayType>::value,\n               int > = 0 >\nauto from_json(const BasicJsonType& j, ConstructibleArrayType& arr)\n-> decltype(from_json_array_impl(j, arr, priority_tag<3> {}),\nj.template get<typename ConstructibleArrayType::value_type>(),\nvoid())\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_array()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", j.type_name()), &j));\n    }\n\n    from_json_array_impl(j, arr, priority_tag<3> {});\n}\n\ntemplate < typename BasicJsonType, typename T, std::size_t... Idx >\nstd::array<T, sizeof...(Idx)> from_json_inplace_array_impl(BasicJsonType&& j,\n                     identity_tag<std::array<T, sizeof...(Idx)>> /*unused*/, index_sequence<Idx...> /*unused*/)\n{\n    return { { std::forward<BasicJsonType>(j).at(Idx).template get<T>()... } };\n}\n\ntemplate < typename BasicJsonType, typename T, std::size_t N >\nauto from_json(BasicJsonType&& j, identity_tag<std::array<T, N>> tag)\n-> decltype(from_json_inplace_array_impl(std::forward<BasicJsonType>(j), tag, make_index_sequence<N> {}))\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_array()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", j.type_name()), &j));\n    }\n\n    return from_json_inplace_array_impl(std::forward<BasicJsonType>(j), tag, make_index_sequence<N> {});\n}\n\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_binary()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be binary, but is \", j.type_name()), &j));\n    }\n\n    bin = *j.template get_ptr<const typename BasicJsonType::binary_t*>();\n}\n\ntemplate<typename BasicJsonType, typename ConstructibleObjectType,\n         enable_if_t<is_constructible_object_type<BasicJsonType, ConstructibleObjectType>::value, int> = 0>\ninline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_object()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be object, but is \", j.type_name()), &j));\n    }\n\n    ConstructibleObjectType ret;\n    const auto* inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>();\n    using value_type = typename ConstructibleObjectType::value_type;\n    std::transform(\n        inner_object->begin(), inner_object->end(),\n        std::inserter(ret, ret.begin()),\n        [](typename BasicJsonType::object_t::value_type const & p)\n    {\n        return value_type(p.first, p.second.template get<typename ConstructibleObjectType::mapped_type>());\n    });\n    obj = std::move(ret);\n}\n\n// overload for arithmetic types, not chosen for basic_json template arguments\n// (BooleanType, etc..); note: Is it really necessary to provide explicit\n// overloads for boolean_t etc. in case of a custom BooleanType which is not\n// an arithmetic type?\ntemplate < typename BasicJsonType, typename ArithmeticType,\n           enable_if_t <\n               std::is_arithmetic<ArithmeticType>::value&&\n               !std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value&&\n               !std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value&&\n               !std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value&&\n               !std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value,\n               int > = 0 >\ninline void from_json(const BasicJsonType& j, ArithmeticType& val)\n{\n    switch (static_cast<value_t>(j))\n    {\n        case value_t::number_unsigned:\n        {\n            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>());\n            break;\n        }\n        case value_t::number_integer:\n        {\n            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>());\n            break;\n        }\n        case value_t::number_float:\n        {\n            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>());\n            break;\n        }\n        case value_t::boolean:\n        {\n            val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>());\n            break;\n        }\n\n        case value_t::null:\n        case value_t::object:\n        case value_t::array:\n        case value_t::string:\n        case value_t::binary:\n        case value_t::discarded:\n        default:\n            JSON_THROW(type_error::create(302, concat(\"type must be number, but is \", j.type_name()), &j));\n    }\n}\n\ntemplate<typename BasicJsonType, typename... Args, std::size_t... Idx>\nstd::tuple<Args...> from_json_tuple_impl_base(BasicJsonType&& j, index_sequence<Idx...> /*unused*/)\n{\n    return std::make_tuple(std::forward<BasicJsonType>(j).at(Idx).template get<Args>()...);\n}\n\ntemplate<typename BasicJsonType>\nstd::tuple<> from_json_tuple_impl_base(BasicJsonType& /*unused*/, index_sequence<> /*unused*/)\n{\n    return {};\n}\n\ntemplate < typename BasicJsonType, class A1, class A2 >\nstd::pair<A1, A2> from_json_tuple_impl(BasicJsonType&& j, identity_tag<std::pair<A1, A2>> /*unused*/, priority_tag<0> /*unused*/)\n{\n    return {std::forward<BasicJsonType>(j).at(0).template get<A1>(),\n            std::forward<BasicJsonType>(j).at(1).template get<A2>()};\n}\n\ntemplate<typename BasicJsonType, typename A1, typename A2>\ninline void from_json_tuple_impl(BasicJsonType&& j, std::pair<A1, A2>& p, priority_tag<1> /*unused*/)\n{\n    p = from_json_tuple_impl(std::forward<BasicJsonType>(j), identity_tag<std::pair<A1, A2>> {}, priority_tag<0> {});\n}\n\ntemplate<typename BasicJsonType, typename... Args>\nstd::tuple<Args...> from_json_tuple_impl(BasicJsonType&& j, identity_tag<std::tuple<Args...>> /*unused*/, priority_tag<2> /*unused*/)\n{\n    return from_json_tuple_impl_base<BasicJsonType, Args...>(std::forward<BasicJsonType>(j), index_sequence_for<Args...> {});\n}\n\ntemplate<typename BasicJsonType, typename... Args>\ninline void from_json_tuple_impl(BasicJsonType&& j, std::tuple<Args...>& t, priority_tag<3> /*unused*/)\n{\n    t = from_json_tuple_impl_base<BasicJsonType, Args...>(std::forward<BasicJsonType>(j), index_sequence_for<Args...> {});\n}\n\ntemplate<typename BasicJsonType, typename TupleRelated>\nauto from_json(BasicJsonType&& j, TupleRelated&& t)\n-> decltype(from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(t), priority_tag<3> {}))\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_array()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", j.type_name()), &j));\n    }\n\n    return from_json_tuple_impl(std::forward<BasicJsonType>(j), std::forward<TupleRelated>(t), priority_tag<3> {});\n}\n\ntemplate < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator,\n           typename = enable_if_t < !std::is_constructible <\n                                        typename BasicJsonType::string_t, Key >::value >>\ninline void from_json(const BasicJsonType& j, std::map<Key, Value, Compare, Allocator>& m)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_array()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", j.type_name()), &j));\n    }\n    m.clear();\n    for (const auto& p : j)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!p.is_array()))\n        {\n            JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", p.type_name()), &j));\n        }\n        m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());\n    }\n}\n\ntemplate < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator,\n           typename = enable_if_t < !std::is_constructible <\n                                        typename BasicJsonType::string_t, Key >::value >>\ninline void from_json(const BasicJsonType& j, std::unordered_map<Key, Value, Hash, KeyEqual, Allocator>& m)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_array()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", j.type_name()), &j));\n    }\n    m.clear();\n    for (const auto& p : j)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!p.is_array()))\n        {\n            JSON_THROW(type_error::create(302, concat(\"type must be array, but is \", p.type_name()), &j));\n        }\n        m.emplace(p.at(0).template get<Key>(), p.at(1).template get<Value>());\n    }\n}\n\n#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM\ntemplate<typename BasicJsonType>\ninline void from_json(const BasicJsonType& j, std_fs::path& p)\n{\n    if (JSON_HEDLEY_UNLIKELY(!j.is_string()))\n    {\n        JSON_THROW(type_error::create(302, concat(\"type must be string, but is \", j.type_name()), &j));\n    }\n    const auto& s = *j.template get_ptr<const typename BasicJsonType::string_t*>();\n#ifdef JSON_HAS_CPP_20\n    p = std_fs::path(std::u8string_view(reinterpret_cast<const char8_t*>(s.data()), s.size()));\n#else\n    p = std_fs::u8path(s); // accepts UTF-8 encoded std::string in C++17, deprecated in C++20\n#endif\n}\n#endif\n\nstruct from_json_fn\n{\n    template<typename BasicJsonType, typename T>\n    auto operator()(const BasicJsonType& j, T&& val) const\n    noexcept(noexcept(from_json(j, std::forward<T>(val))))\n    -> decltype(from_json(j, std::forward<T>(val)))\n    {\n        return from_json(j, std::forward<T>(val));\n    }\n};\n\n}  // namespace detail\n\n#ifndef JSON_HAS_CPP_17\n/// namespace to hold default `from_json` function\n/// to see why this is required:\n/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html\nnamespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces)\n{\n#endif\nJSON_INLINE_VARIABLE constexpr const auto& from_json = // NOLINT(misc-definitions-in-headers)\n    detail::static_const<detail::from_json_fn>::value;\n#ifndef JSON_HAS_CPP_17\n}  // namespace\n#endif\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/conversions/to_json.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/macro_scope.hpp>\n// JSON_HAS_CPP_17\n#ifdef JSON_HAS_CPP_17\n    #include <optional> // optional\n#endif\n\n#include <algorithm> // copy\n#include <iterator> // begin, end\n#include <string> // string\n#include <tuple> // tuple, get\n#include <type_traits> // is_same, is_constructible, is_floating_point, is_enum, underlying_type\n#include <utility> // move, forward, declval, pair\n#include <valarray> // valarray\n#include <vector> // vector\n\n// #include <nlohmann/detail/iterators/iteration_proxy.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // size_t\n#include <iterator> // forward_iterator_tag\n#include <tuple> // tuple_size, get, tuple_element\n#include <utility> // move\n\n#if JSON_HAS_RANGES\n    #include <ranges> // enable_borrowed_range\n#endif\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/string_utils.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // size_t\n#include <string> // string, to_string\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename StringType>\nvoid int_to_string(StringType& target, std::size_t value)\n{\n    // For ADL\n    using std::to_string;\n    target = to_string(value);\n}\n\ntemplate<typename StringType>\nStringType to_string(std::size_t value)\n{\n    StringType result;\n    int_to_string(result, value);\n    return result;\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename IteratorType> class iteration_proxy_value\n{\n  public:\n    using difference_type = std::ptrdiff_t;\n    using value_type = iteration_proxy_value;\n    using pointer = value_type *;\n    using reference = value_type &;\n    using iterator_category = std::forward_iterator_tag;\n    using string_type = typename std::remove_cv< typename std::remove_reference<decltype( std::declval<IteratorType>().key() ) >::type >::type;\n\n  private:\n    /// the iterator\n    IteratorType anchor{};\n    /// an index for arrays (used to create key names)\n    std::size_t array_index = 0;\n    /// last stringified array index\n    mutable std::size_t array_index_last = 0;\n    /// a string representation of the array index\n    mutable string_type array_index_str = \"0\";\n    /// an empty string (to return a reference for primitive values)\n    string_type empty_str{};\n\n  public:\n    explicit iteration_proxy_value() = default;\n    explicit iteration_proxy_value(IteratorType it, std::size_t array_index_ = 0)\n    noexcept(std::is_nothrow_move_constructible<IteratorType>::value\n             && std::is_nothrow_default_constructible<string_type>::value)\n        : anchor(std::move(it))\n        , array_index(array_index_)\n    {}\n\n    iteration_proxy_value(iteration_proxy_value const&) = default;\n    iteration_proxy_value& operator=(iteration_proxy_value const&) = default;\n    // older GCCs are a bit fussy and require explicit noexcept specifiers on defaulted functions\n    iteration_proxy_value(iteration_proxy_value&&)\n    noexcept(std::is_nothrow_move_constructible<IteratorType>::value\n             && std::is_nothrow_move_constructible<string_type>::value) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations)\n    iteration_proxy_value& operator=(iteration_proxy_value&&)\n    noexcept(std::is_nothrow_move_assignable<IteratorType>::value\n             && std::is_nothrow_move_assignable<string_type>::value) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations)\n    ~iteration_proxy_value() = default;\n\n    /// dereference operator (needed for range-based for)\n    const iteration_proxy_value& operator*() const\n    {\n        return *this;\n    }\n\n    /// increment operator (needed for range-based for)\n    iteration_proxy_value& operator++()\n    {\n        ++anchor;\n        ++array_index;\n\n        return *this;\n    }\n\n    iteration_proxy_value operator++(int)& // NOLINT(cert-dcl21-cpp)\n    {\n        auto tmp = iteration_proxy_value(anchor, array_index);\n        ++anchor;\n        ++array_index;\n        return tmp;\n    }\n\n    /// equality operator (needed for InputIterator)\n    bool operator==(const iteration_proxy_value& o) const\n    {\n        return anchor == o.anchor;\n    }\n\n    /// inequality operator (needed for range-based for)\n    bool operator!=(const iteration_proxy_value& o) const\n    {\n        return anchor != o.anchor;\n    }\n\n    /// return key of the iterator\n    const string_type& key() const\n    {\n        JSON_ASSERT(anchor.m_object != nullptr);\n\n        switch (anchor.m_object->type())\n        {\n            // use integer array index as key\n            case value_t::array:\n            {\n                if (array_index != array_index_last)\n                {\n                    int_to_string( array_index_str, array_index );\n                    array_index_last = array_index;\n                }\n                return array_index_str;\n            }\n\n            // use key from the object\n            case value_t::object:\n                return anchor.key();\n\n            // use an empty key for all primitive types\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n                return empty_str;\n        }\n    }\n\n    /// return value of the iterator\n    typename IteratorType::reference value() const\n    {\n        return anchor.value();\n    }\n};\n\n/// proxy class for the items() function\ntemplate<typename IteratorType> class iteration_proxy\n{\n  private:\n    /// the container to iterate\n    typename IteratorType::pointer container = nullptr;\n\n  public:\n    explicit iteration_proxy() = default;\n\n    /// construct iteration proxy from a container\n    explicit iteration_proxy(typename IteratorType::reference cont) noexcept\n        : container(&cont) {}\n\n    iteration_proxy(iteration_proxy const&) = default;\n    iteration_proxy& operator=(iteration_proxy const&) = default;\n    iteration_proxy(iteration_proxy&&) noexcept = default;\n    iteration_proxy& operator=(iteration_proxy&&) noexcept = default;\n    ~iteration_proxy() = default;\n\n    /// return iterator begin (needed for range-based for)\n    iteration_proxy_value<IteratorType> begin() const noexcept\n    {\n        return iteration_proxy_value<IteratorType>(container->begin());\n    }\n\n    /// return iterator end (needed for range-based for)\n    iteration_proxy_value<IteratorType> end() const noexcept\n    {\n        return iteration_proxy_value<IteratorType>(container->end());\n    }\n};\n\n// Structured Bindings Support\n// For further reference see https://blog.tartanllama.xyz/structured-bindings/\n// And see https://github.com/nlohmann/json/pull/1391\ntemplate<std::size_t N, typename IteratorType, enable_if_t<N == 0, int> = 0>\nauto get(const nlohmann::detail::iteration_proxy_value<IteratorType>& i) -> decltype(i.key())\n{\n    return i.key();\n}\n// Structured Bindings Support\n// For further reference see https://blog.tartanllama.xyz/structured-bindings/\n// And see https://github.com/nlohmann/json/pull/1391\ntemplate<std::size_t N, typename IteratorType, enable_if_t<N == 1, int> = 0>\nauto get(const nlohmann::detail::iteration_proxy_value<IteratorType>& i) -> decltype(i.value())\n{\n    return i.value();\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// The Addition to the STD Namespace is required to add\n// Structured Bindings Support to the iteration_proxy_value class\n// For further reference see https://blog.tartanllama.xyz/structured-bindings/\n// And see https://github.com/nlohmann/json/pull/1391\nnamespace std\n{\n\n#if defined(__clang__)\n    // Fix: https://github.com/nlohmann/json/issues/1401\n    #pragma clang diagnostic push\n    #pragma clang diagnostic ignored \"-Wmismatched-tags\"\n#endif\ntemplate<typename IteratorType>\nclass tuple_size<::nlohmann::detail::iteration_proxy_value<IteratorType>> // NOLINT(cert-dcl58-cpp)\n    : public std::integral_constant<std::size_t, 2> {};\n\ntemplate<std::size_t N, typename IteratorType>\nclass tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >> // NOLINT(cert-dcl58-cpp)\n{\n  public:\n    using type = decltype(\n                     get<N>(std::declval <\n                            ::nlohmann::detail::iteration_proxy_value<IteratorType >> ()));\n};\n#if defined(__clang__)\n    #pragma clang diagnostic pop\n#endif\n\n}  // namespace std\n\n#if JSON_HAS_RANGES\n    template <typename IteratorType>\n    inline constexpr bool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy<IteratorType>> = true;\n#endif\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/std_fs.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n//////////////////\n// constructors //\n//////////////////\n\n/*\n * Note all external_constructor<>::construct functions need to call\n * j.m_data.m_value.destroy(j.m_data.m_type) to avoid a memory leak in case j contains an\n * allocated value (e.g., a string). See bug issue\n * https://github.com/nlohmann/json/issues/2865 for more information.\n */\n\ntemplate<value_t> struct external_constructor;\n\ntemplate<>\nstruct external_constructor<value_t::boolean>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::boolean;\n        j.m_data.m_value = b;\n        j.assert_invariant();\n    }\n};\n\ntemplate<>\nstruct external_constructor<value_t::string>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::string;\n        j.m_data.m_value = s;\n        j.assert_invariant();\n    }\n\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::string;\n        j.m_data.m_value = std::move(s);\n        j.assert_invariant();\n    }\n\n    template < typename BasicJsonType, typename CompatibleStringType,\n               enable_if_t < !std::is_same<CompatibleStringType, typename BasicJsonType::string_t>::value,\n                             int > = 0 >\n    static void construct(BasicJsonType& j, const CompatibleStringType& str)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::string;\n        j.m_data.m_value.string = j.template create<typename BasicJsonType::string_t>(str);\n        j.assert_invariant();\n    }\n};\n\ntemplate<>\nstruct external_constructor<value_t::binary>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, const typename BasicJsonType::binary_t& b)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::binary;\n        j.m_data.m_value = typename BasicJsonType::binary_t(b);\n        j.assert_invariant();\n    }\n\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::binary_t&& b)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::binary;\n        j.m_data.m_value = typename BasicJsonType::binary_t(std::move(b));\n        j.assert_invariant();\n    }\n};\n\ntemplate<>\nstruct external_constructor<value_t::number_float>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::number_float;\n        j.m_data.m_value = val;\n        j.assert_invariant();\n    }\n};\n\ntemplate<>\nstruct external_constructor<value_t::number_unsigned>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::number_unsigned;\n        j.m_data.m_value = val;\n        j.assert_invariant();\n    }\n};\n\ntemplate<>\nstruct external_constructor<value_t::number_integer>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::number_integer;\n        j.m_data.m_value = val;\n        j.assert_invariant();\n    }\n};\n\ntemplate<>\nstruct external_constructor<value_t::array>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::array;\n        j.m_data.m_value = arr;\n        j.set_parents();\n        j.assert_invariant();\n    }\n\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::array;\n        j.m_data.m_value = std::move(arr);\n        j.set_parents();\n        j.assert_invariant();\n    }\n\n    template < typename BasicJsonType, typename CompatibleArrayType,\n               enable_if_t < !std::is_same<CompatibleArrayType, typename BasicJsonType::array_t>::value,\n                             int > = 0 >\n    static void construct(BasicJsonType& j, const CompatibleArrayType& arr)\n    {\n        using std::begin;\n        using std::end;\n\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::array;\n        j.m_data.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr));\n        j.set_parents();\n        j.assert_invariant();\n    }\n\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, const std::vector<bool>& arr)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::array;\n        j.m_data.m_value = value_t::array;\n        j.m_data.m_value.array->reserve(arr.size());\n        for (const bool x : arr)\n        {\n            j.m_data.m_value.array->push_back(x);\n            j.set_parent(j.m_data.m_value.array->back());\n        }\n        j.assert_invariant();\n    }\n\n    template<typename BasicJsonType, typename T,\n             enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>\n    static void construct(BasicJsonType& j, const std::valarray<T>& arr)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::array;\n        j.m_data.m_value = value_t::array;\n        j.m_data.m_value.array->resize(arr.size());\n        if (arr.size() > 0)\n        {\n            std::copy(std::begin(arr), std::end(arr), j.m_data.m_value.array->begin());\n        }\n        j.set_parents();\n        j.assert_invariant();\n    }\n};\n\ntemplate<>\nstruct external_constructor<value_t::object>\n{\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::object;\n        j.m_data.m_value = obj;\n        j.set_parents();\n        j.assert_invariant();\n    }\n\n    template<typename BasicJsonType>\n    static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj)\n    {\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::object;\n        j.m_data.m_value = std::move(obj);\n        j.set_parents();\n        j.assert_invariant();\n    }\n\n    template < typename BasicJsonType, typename CompatibleObjectType,\n               enable_if_t < !std::is_same<CompatibleObjectType, typename BasicJsonType::object_t>::value, int > = 0 >\n    static void construct(BasicJsonType& j, const CompatibleObjectType& obj)\n    {\n        using std::begin;\n        using std::end;\n\n        j.m_data.m_value.destroy(j.m_data.m_type);\n        j.m_data.m_type = value_t::object;\n        j.m_data.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj));\n        j.set_parents();\n        j.assert_invariant();\n    }\n};\n\n/////////////\n// to_json //\n/////////////\n\n#ifdef JSON_HAS_CPP_17\ntemplate<typename BasicJsonType, typename T,\n         enable_if_t<std::is_constructible<BasicJsonType, T>::value, int> = 0>\nvoid to_json(BasicJsonType& j, const std::optional<T>& opt)\n{\n    if (opt.has_value())\n    {\n        j = *opt;\n    }\n    else\n    {\n        j = nullptr;\n    }\n}\n#endif\n\ntemplate<typename BasicJsonType, typename T,\n         enable_if_t<std::is_same<T, typename BasicJsonType::boolean_t>::value, int> = 0>\ninline void to_json(BasicJsonType& j, T b) noexcept\n{\n    external_constructor<value_t::boolean>::construct(j, b);\n}\n\ntemplate < typename BasicJsonType, typename BoolRef,\n           enable_if_t <\n               ((std::is_same<std::vector<bool>::reference, BoolRef>::value\n                 && !std::is_same <std::vector<bool>::reference, typename BasicJsonType::boolean_t&>::value)\n                || (std::is_same<std::vector<bool>::const_reference, BoolRef>::value\n                    && !std::is_same <detail::uncvref_t<std::vector<bool>::const_reference>,\n                                      typename BasicJsonType::boolean_t >::value))\n               && std::is_convertible<const BoolRef&, typename BasicJsonType::boolean_t>::value, int > = 0 >\ninline void to_json(BasicJsonType& j, const BoolRef& b) noexcept\n{\n    external_constructor<value_t::boolean>::construct(j, static_cast<typename BasicJsonType::boolean_t>(b));\n}\n\ntemplate<typename BasicJsonType, typename CompatibleString,\n         enable_if_t<std::is_constructible<typename BasicJsonType::string_t, CompatibleString>::value, int> = 0>\ninline void to_json(BasicJsonType& j, const CompatibleString& s)\n{\n    external_constructor<value_t::string>::construct(j, s);\n}\n\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s)\n{\n    external_constructor<value_t::string>::construct(j, std::move(s));\n}\n\ntemplate<typename BasicJsonType, typename FloatType,\n         enable_if_t<std::is_floating_point<FloatType>::value, int> = 0>\ninline void to_json(BasicJsonType& j, FloatType val) noexcept\n{\n    external_constructor<value_t::number_float>::construct(j, static_cast<typename BasicJsonType::number_float_t>(val));\n}\n\ntemplate<typename BasicJsonType, typename CompatibleNumberUnsignedType,\n         enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, CompatibleNumberUnsignedType>::value, int> = 0>\ninline void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept\n{\n    external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val));\n}\n\ntemplate<typename BasicJsonType, typename CompatibleNumberIntegerType,\n         enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, CompatibleNumberIntegerType>::value, int> = 0>\ninline void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept\n{\n    external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val));\n}\n\n#if !JSON_DISABLE_ENUM_SERIALIZATION\ntemplate<typename BasicJsonType, typename EnumType,\n         enable_if_t<std::is_enum<EnumType>::value, int> = 0>\ninline void to_json(BasicJsonType& j, EnumType e) noexcept\n{\n    using underlying_type = typename std::underlying_type<EnumType>::type;\n    static constexpr value_t integral_value_t = std::is_unsigned<underlying_type>::value ? value_t::number_unsigned : value_t::number_integer;\n    external_constructor<integral_value_t>::construct(j, static_cast<underlying_type>(e));\n}\n#endif  // JSON_DISABLE_ENUM_SERIALIZATION\n\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, const std::vector<bool>& e)\n{\n    external_constructor<value_t::array>::construct(j, e);\n}\n\ntemplate < typename BasicJsonType, typename CompatibleArrayType,\n           enable_if_t < is_compatible_array_type<BasicJsonType,\n                         CompatibleArrayType>::value&&\n                         !is_compatible_object_type<BasicJsonType, CompatibleArrayType>::value&&\n                         !is_compatible_string_type<BasicJsonType, CompatibleArrayType>::value&&\n                         !std::is_same<typename BasicJsonType::binary_t, CompatibleArrayType>::value&&\n                         !is_basic_json<CompatibleArrayType>::value,\n                         int > = 0 >\ninline void to_json(BasicJsonType& j, const CompatibleArrayType& arr)\n{\n    external_constructor<value_t::array>::construct(j, arr);\n}\n\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin)\n{\n    external_constructor<value_t::binary>::construct(j, bin);\n}\n\ntemplate<typename BasicJsonType, typename T,\n         enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0>\ninline void to_json(BasicJsonType& j, const std::valarray<T>& arr)\n{\n    external_constructor<value_t::array>::construct(j, std::move(arr));\n}\n\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr)\n{\n    external_constructor<value_t::array>::construct(j, std::move(arr));\n}\n\ntemplate < typename BasicJsonType, typename CompatibleObjectType,\n           enable_if_t < is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value&& !is_basic_json<CompatibleObjectType>::value, int > = 0 >\ninline void to_json(BasicJsonType& j, const CompatibleObjectType& obj)\n{\n    external_constructor<value_t::object>::construct(j, obj);\n}\n\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj)\n{\n    external_constructor<value_t::object>::construct(j, std::move(obj));\n}\n\ntemplate <\n    typename BasicJsonType, typename T, std::size_t N,\n    enable_if_t < !std::is_constructible<typename BasicJsonType::string_t,\n                  const T(&)[N]>::value, // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n                  int > = 0 >\ninline void to_json(BasicJsonType& j, const T(&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n{\n    external_constructor<value_t::array>::construct(j, arr);\n}\n\ntemplate < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible<BasicJsonType, T1>::value&& std::is_constructible<BasicJsonType, T2>::value, int > = 0 >\ninline void to_json(BasicJsonType& j, const std::pair<T1, T2>& p)\n{\n    j = { p.first, p.second };\n}\n\n// for https://github.com/nlohmann/json/pull/1134\ntemplate<typename BasicJsonType, typename T,\n         enable_if_t<std::is_same<T, iteration_proxy_value<typename BasicJsonType::iterator>>::value, int> = 0>\ninline void to_json(BasicJsonType& j, const T& b)\n{\n    j = { {b.key(), b.value()} };\n}\n\ntemplate<typename BasicJsonType, typename Tuple, std::size_t... Idx>\ninline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<Idx...> /*unused*/)\n{\n    j = { std::get<Idx>(t)... };\n}\n\ntemplate<typename BasicJsonType, typename Tuple>\ninline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/)\n{\n    using array_t = typename BasicJsonType::array_t;\n    j = array_t();\n}\n\ntemplate<typename BasicJsonType, typename T, enable_if_t<is_constructible_tuple<BasicJsonType, T>::value, int > = 0>\ninline void to_json(BasicJsonType& j, const T& t)\n{\n    to_json_tuple_impl(j, t, make_index_sequence<std::tuple_size<T>::value> {});\n}\n\n#if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM\ntemplate<typename BasicJsonType>\ninline void to_json(BasicJsonType& j, const std_fs::path& p)\n{\n#ifdef JSON_HAS_CPP_20\n    const std::u8string s = p.u8string();\n    j = std::string(s.begin(), s.end());\n#else\n    j = p.u8string(); // returns std::string in C++17\n#endif\n}\n#endif\n\nstruct to_json_fn\n{\n    template<typename BasicJsonType, typename T>\n    auto operator()(BasicJsonType& j, T&& val) const noexcept(noexcept(to_json(j, std::forward<T>(val))))\n    -> decltype(to_json(j, std::forward<T>(val)), void())\n    {\n        return to_json(j, std::forward<T>(val));\n    }\n};\n}  // namespace detail\n\n#ifndef JSON_HAS_CPP_17\n/// namespace to hold default `to_json` function\n/// to see why this is required:\n/// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html\nnamespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces)\n{\n#endif\nJSON_INLINE_VARIABLE constexpr const auto& to_json = // NOLINT(misc-definitions-in-headers)\n    detail::static_const<detail::to_json_fn>::value;\n#ifndef JSON_HAS_CPP_17\n}  // namespace\n#endif\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/identity_tag.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/// @sa https://json.nlohmann.me/api/adl_serializer/\ntemplate<typename ValueType, typename>\nstruct adl_serializer\n{\n    /// @brief convert a JSON value to any value type\n    /// @sa https://json.nlohmann.me/api/adl_serializer/from_json/\n    template<typename BasicJsonType, typename TargetType = ValueType>\n    static auto from_json(BasicJsonType && j, TargetType& val) noexcept(\n        noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val)))\n    -> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), val), void())\n    {\n        ::nlohmann::from_json(std::forward<BasicJsonType>(j), val);\n    }\n\n    /// @brief convert a JSON value to any value type\n    /// @sa https://json.nlohmann.me/api/adl_serializer/from_json/\n    template<typename BasicJsonType, typename TargetType = ValueType>\n    static auto from_json(BasicJsonType && j) noexcept(\n    noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {})))\n    -> decltype(::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {}))\n    {\n        return ::nlohmann::from_json(std::forward<BasicJsonType>(j), detail::identity_tag<TargetType> {});\n    }\n\n    /// @brief convert any value type to a JSON value\n    /// @sa https://json.nlohmann.me/api/adl_serializer/to_json/\n    template<typename BasicJsonType, typename TargetType = ValueType>\n    static auto to_json(BasicJsonType& j, TargetType && val) noexcept(\n        noexcept(::nlohmann::to_json(j, std::forward<TargetType>(val))))\n    -> decltype(::nlohmann::to_json(j, std::forward<TargetType>(val)), void())\n    {\n        ::nlohmann::to_json(j, std::forward<TargetType>(val));\n    }\n};\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/byte_container_with_subtype.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstdint> // uint8_t, uint64_t\n#include <tuple> // tie\n#include <utility> // move\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/// @brief an internal type for a backed binary type\n/// @sa https://json.nlohmann.me/api/byte_container_with_subtype/\ntemplate<typename BinaryType>\nclass byte_container_with_subtype : public BinaryType\n{\n  public:\n    using container_type = BinaryType;\n    using subtype_type = std::uint64_t;\n\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/\n    byte_container_with_subtype() noexcept(noexcept(container_type()))\n        : container_type()\n    {}\n\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/\n    byte_container_with_subtype(const container_type& b) noexcept(noexcept(container_type(b)))\n        : container_type(b)\n    {}\n\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/\n    byte_container_with_subtype(container_type&& b) noexcept(noexcept(container_type(std::move(b))))\n        : container_type(std::move(b))\n    {}\n\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/\n    byte_container_with_subtype(const container_type& b, subtype_type subtype_) noexcept(noexcept(container_type(b)))\n        : container_type(b)\n        , m_subtype(subtype_)\n        , m_has_subtype(true)\n    {}\n\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/byte_container_with_subtype/\n    byte_container_with_subtype(container_type&& b, subtype_type subtype_) noexcept(noexcept(container_type(std::move(b))))\n        : container_type(std::move(b))\n        , m_subtype(subtype_)\n        , m_has_subtype(true)\n    {}\n\n    bool operator==(const byte_container_with_subtype& rhs) const\n    {\n        return std::tie(static_cast<const BinaryType&>(*this), m_subtype, m_has_subtype) ==\n               std::tie(static_cast<const BinaryType&>(rhs), rhs.m_subtype, rhs.m_has_subtype);\n    }\n\n    bool operator!=(const byte_container_with_subtype& rhs) const\n    {\n        return !(rhs == *this);\n    }\n\n    /// @brief sets the binary subtype\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/set_subtype/\n    void set_subtype(subtype_type subtype_) noexcept\n    {\n        m_subtype = subtype_;\n        m_has_subtype = true;\n    }\n\n    /// @brief return the binary subtype\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/subtype/\n    constexpr subtype_type subtype() const noexcept\n    {\n        return m_has_subtype ? m_subtype : static_cast<subtype_type>(-1);\n    }\n\n    /// @brief return whether the value has a subtype\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/has_subtype/\n    constexpr bool has_subtype() const noexcept\n    {\n        return m_has_subtype;\n    }\n\n    /// @brief clears the binary subtype\n    /// @sa https://json.nlohmann.me/api/byte_container_with_subtype/clear_subtype/\n    void clear_subtype() noexcept\n    {\n        m_subtype = 0;\n        m_has_subtype = false;\n    }\n\n  private:\n    subtype_type m_subtype = 0;\n    bool m_has_subtype = false;\n};\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/conversions/from_json.hpp>\n\n// #include <nlohmann/detail/conversions/to_json.hpp>\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/hash.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstdint> // uint8_t\n#include <cstddef> // size_t\n#include <functional> // hash\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n// boost::hash_combine\ninline std::size_t combine(std::size_t seed, std::size_t h) noexcept\n{\n    seed ^= h + 0x9e3779b9 + (seed << 6U) + (seed >> 2U);\n    return seed;\n}\n\n/*!\n@brief hash a JSON value\n\nThe hash function tries to rely on std::hash where possible. Furthermore, the\ntype of the JSON value is taken into account to have different hash values for\nnull, 0, 0U, and false, etc.\n\n@tparam BasicJsonType basic_json specialization\n@param j JSON value to hash\n@return hash value of j\n*/\ntemplate<typename BasicJsonType>\nstd::size_t hash(const BasicJsonType& j)\n{\n    using string_t = typename BasicJsonType::string_t;\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n\n    const auto type = static_cast<std::size_t>(j.type());\n    switch (j.type())\n    {\n        case BasicJsonType::value_t::null:\n        case BasicJsonType::value_t::discarded:\n        {\n            return combine(type, 0);\n        }\n\n        case BasicJsonType::value_t::object:\n        {\n            auto seed = combine(type, j.size());\n            for (const auto& element : j.items())\n            {\n                const auto h = std::hash<string_t> {}(element.key());\n                seed = combine(seed, h);\n                seed = combine(seed, hash(element.value()));\n            }\n            return seed;\n        }\n\n        case BasicJsonType::value_t::array:\n        {\n            auto seed = combine(type, j.size());\n            for (const auto& element : j)\n            {\n                seed = combine(seed, hash(element));\n            }\n            return seed;\n        }\n\n        case BasicJsonType::value_t::string:\n        {\n            const auto h = std::hash<string_t> {}(j.template get_ref<const string_t&>());\n            return combine(type, h);\n        }\n\n        case BasicJsonType::value_t::boolean:\n        {\n            const auto h = std::hash<bool> {}(j.template get<bool>());\n            return combine(type, h);\n        }\n\n        case BasicJsonType::value_t::number_integer:\n        {\n            const auto h = std::hash<number_integer_t> {}(j.template get<number_integer_t>());\n            return combine(type, h);\n        }\n\n        case BasicJsonType::value_t::number_unsigned:\n        {\n            const auto h = std::hash<number_unsigned_t> {}(j.template get<number_unsigned_t>());\n            return combine(type, h);\n        }\n\n        case BasicJsonType::value_t::number_float:\n        {\n            const auto h = std::hash<number_float_t> {}(j.template get<number_float_t>());\n            return combine(type, h);\n        }\n\n        case BasicJsonType::value_t::binary:\n        {\n            auto seed = combine(type, j.get_binary().size());\n            const auto h = std::hash<bool> {}(j.get_binary().has_subtype());\n            seed = combine(seed, h);\n            seed = combine(seed, static_cast<std::size_t>(j.get_binary().subtype()));\n            for (const auto byte : j.get_binary())\n            {\n                seed = combine(seed, std::hash<std::uint8_t> {}(byte));\n            }\n            return seed;\n        }\n\n        default:                   // LCOV_EXCL_LINE\n            JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n            return 0;              // LCOV_EXCL_LINE\n    }\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/input/binary_reader.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // generate_n\n#include <array> // array\n#include <cmath> // ldexp\n#include <cstddef> // size_t\n#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t\n#include <cstdio> // snprintf\n#include <cstring> // memcpy\n#include <iterator> // back_inserter\n#include <limits> // numeric_limits\n#include <string> // char_traits, string\n#include <utility> // make_pair, move\n#include <vector> // vector\n#ifdef __cpp_lib_byteswap\n    #include <bit>  //byteswap\n#endif\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/input/input_adapters.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <array> // array\n#include <cstddef> // size_t\n#include <cstring> // strlen\n#include <iterator> // begin, end, iterator_traits, random_access_iterator_tag, distance, next\n#include <memory> // shared_ptr, make_shared, addressof\n#include <numeric> // accumulate\n#include <string> // string, char_traits\n#include <type_traits> // enable_if, is_base_of, is_pointer, is_integral, remove_pointer\n#include <utility> // pair, declval\n\n#ifndef JSON_NO_IO\n    #include <cstdio>   // FILE *\n    #include <istream>  // istream\n#endif                  // JSON_NO_IO\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/iterators/iterator_traits.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/// the supported input formats\nenum class input_format_t { json, cbor, msgpack, ubjson, bson, bjdata };\n\n////////////////////\n// input adapters //\n////////////////////\n\n#ifndef JSON_NO_IO\n/*!\nInput adapter for stdio file access. This adapter read only 1 byte and do not use any\n buffer. This adapter is a very low level adapter.\n*/\nclass file_input_adapter\n{\n  public:\n    using char_type = char;\n\n    JSON_HEDLEY_NON_NULL(2)\n    explicit file_input_adapter(std::FILE* f) noexcept\n        : m_file(f)\n    {\n        JSON_ASSERT(m_file != nullptr);\n    }\n\n    // make class move-only\n    file_input_adapter(const file_input_adapter&) = delete;\n    file_input_adapter(file_input_adapter&&) noexcept = default;\n    file_input_adapter& operator=(const file_input_adapter&) = delete;\n    file_input_adapter& operator=(file_input_adapter&&) = delete;\n    ~file_input_adapter() = default;\n\n    std::char_traits<char>::int_type get_character() noexcept\n    {\n        return std::fgetc(m_file);\n    }\n\n    // returns the number of characters successfully read\n    template<class T>\n    std::size_t get_elements(T* dest, std::size_t count = 1)\n    {\n        return fread(dest, 1, sizeof(T) * count, m_file);\n    }\n\n  private:\n    /// the file pointer to read from\n    std::FILE* m_file;\n};\n\n/*!\nInput adapter for a (caching) istream. Ignores a UFT Byte Order Mark at\nbeginning of input. Does not support changing the underlying std::streambuf\nin mid-input. Maintains underlying std::istream and std::streambuf to support\nsubsequent use of standard std::istream operations to process any input\ncharacters following those used in parsing the JSON input.  Clears the\nstd::istream flags; any input errors (e.g., EOF) will be detected by the first\nsubsequent call for input from the std::istream.\n*/\nclass input_stream_adapter\n{\n  public:\n    using char_type = char;\n\n    ~input_stream_adapter()\n    {\n        // clear stream flags; we use underlying streambuf I/O, do not\n        // maintain ifstream flags, except eof\n        if (is != nullptr)\n        {\n            is->clear(is->rdstate() & std::ios::eofbit);\n        }\n    }\n\n    explicit input_stream_adapter(std::istream& i)\n        : is(&i), sb(i.rdbuf())\n    {}\n\n    // delete because of pointer members\n    input_stream_adapter(const input_stream_adapter&) = delete;\n    input_stream_adapter& operator=(input_stream_adapter&) = delete;\n    input_stream_adapter& operator=(input_stream_adapter&&) = delete;\n\n    input_stream_adapter(input_stream_adapter&& rhs) noexcept\n        : is(rhs.is), sb(rhs.sb)\n    {\n        rhs.is = nullptr;\n        rhs.sb = nullptr;\n    }\n\n    // std::istream/std::streambuf use std::char_traits<char>::to_int_type, to\n    // ensure that std::char_traits<char>::eof() and the character 0xFF do not\n    // end up as the same value, e.g. 0xFFFFFFFF.\n    std::char_traits<char>::int_type get_character()\n    {\n        auto res = sb->sbumpc();\n        // set eof manually, as we don't use the istream interface.\n        if (JSON_HEDLEY_UNLIKELY(res == std::char_traits<char>::eof()))\n        {\n            is->clear(is->rdstate() | std::ios::eofbit);\n        }\n        return res;\n    }\n\n    template<class T>\n    std::size_t get_elements(T* dest, std::size_t count = 1)\n    {\n        auto res = static_cast<std::size_t>(sb->sgetn(reinterpret_cast<char*>(dest), static_cast<std::streamsize>(count * sizeof(T))));\n        if (JSON_HEDLEY_UNLIKELY(res < count * sizeof(T)))\n        {\n            is->clear(is->rdstate() | std::ios::eofbit);\n        }\n        return res;\n    }\n\n  private:\n    /// the associated input stream\n    std::istream* is = nullptr;\n    std::streambuf* sb = nullptr;\n};\n#endif  // JSON_NO_IO\n\n// General-purpose iterator-based adapter. It might not be as fast as\n// theoretically possible for some containers, but it is extremely versatile.\ntemplate<typename IteratorType>\nclass iterator_input_adapter\n{\n  public:\n    using char_type = typename std::iterator_traits<IteratorType>::value_type;\n\n    iterator_input_adapter(IteratorType first, IteratorType last)\n        : current(std::move(first)), end(std::move(last))\n    {}\n\n    typename char_traits<char_type>::int_type get_character()\n    {\n        if (JSON_HEDLEY_LIKELY(current != end))\n        {\n            auto result = char_traits<char_type>::to_int_type(*current);\n            std::advance(current, 1);\n            return result;\n        }\n\n        return char_traits<char_type>::eof();\n    }\n\n    // for general iterators, we cannot really do something better than falling back to processing the range one-by-one\n    template<class T>\n    std::size_t get_elements(T* dest, std::size_t count = 1)\n    {\n        auto* ptr = reinterpret_cast<char*>(dest);\n        for (std::size_t read_index = 0; read_index < count * sizeof(T); ++read_index)\n        {\n            if (JSON_HEDLEY_LIKELY(current != end))\n            {\n                ptr[read_index] = static_cast<char>(*current);\n                std::advance(current, 1);\n            }\n            else\n            {\n                return read_index;\n            }\n        }\n        return count * sizeof(T);\n    }\n\n  private:\n    IteratorType current;\n    IteratorType end;\n\n    template<typename BaseInputAdapter, size_t T>\n    friend struct wide_string_input_helper;\n\n    bool empty() const\n    {\n        return current == end;\n    }\n};\n\ntemplate<typename BaseInputAdapter, size_t T>\nstruct wide_string_input_helper;\n\ntemplate<typename BaseInputAdapter>\nstruct wide_string_input_helper<BaseInputAdapter, 4>\n{\n    // UTF-32\n    static void fill_buffer(BaseInputAdapter& input,\n                            std::array<std::char_traits<char>::int_type, 4>& utf8_bytes,\n                            size_t& utf8_bytes_index,\n                            size_t& utf8_bytes_filled)\n    {\n        utf8_bytes_index = 0;\n\n        if (JSON_HEDLEY_UNLIKELY(input.empty()))\n        {\n            utf8_bytes[0] = std::char_traits<char>::eof();\n            utf8_bytes_filled = 1;\n        }\n        else\n        {\n            // get the current character\n            const auto wc = input.get_character();\n\n            // UTF-32 to UTF-8 encoding\n            if (wc < 0x80)\n            {\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);\n                utf8_bytes_filled = 1;\n            }\n            else if (wc <= 0x7FF)\n            {\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xC0u | ((static_cast<unsigned int>(wc) >> 6u) & 0x1Fu));\n                utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));\n                utf8_bytes_filled = 2;\n            }\n            else if (wc <= 0xFFFF)\n            {\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xE0u | ((static_cast<unsigned int>(wc) >> 12u) & 0x0Fu));\n                utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 6u) & 0x3Fu));\n                utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));\n                utf8_bytes_filled = 3;\n            }\n            else if (wc <= 0x10FFFF)\n            {\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | ((static_cast<unsigned int>(wc) >> 18u) & 0x07u));\n                utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 12u) & 0x3Fu));\n                utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 6u) & 0x3Fu));\n                utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));\n                utf8_bytes_filled = 4;\n            }\n            else\n            {\n                // unknown character\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);\n                utf8_bytes_filled = 1;\n            }\n        }\n    }\n};\n\ntemplate<typename BaseInputAdapter>\nstruct wide_string_input_helper<BaseInputAdapter, 2>\n{\n    // UTF-16\n    static void fill_buffer(BaseInputAdapter& input,\n                            std::array<std::char_traits<char>::int_type, 4>& utf8_bytes,\n                            size_t& utf8_bytes_index,\n                            size_t& utf8_bytes_filled)\n    {\n        utf8_bytes_index = 0;\n\n        if (JSON_HEDLEY_UNLIKELY(input.empty()))\n        {\n            utf8_bytes[0] = std::char_traits<char>::eof();\n            utf8_bytes_filled = 1;\n        }\n        else\n        {\n            // get the current character\n            const auto wc = input.get_character();\n\n            // UTF-16 to UTF-8 encoding\n            if (wc < 0x80)\n            {\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);\n                utf8_bytes_filled = 1;\n            }\n            else if (wc <= 0x7FF)\n            {\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xC0u | ((static_cast<unsigned int>(wc) >> 6u)));\n                utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));\n                utf8_bytes_filled = 2;\n            }\n            else if (0xD800 > wc || wc >= 0xE000)\n            {\n                utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xE0u | ((static_cast<unsigned int>(wc) >> 12u)));\n                utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((static_cast<unsigned int>(wc) >> 6u) & 0x3Fu));\n                utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | (static_cast<unsigned int>(wc) & 0x3Fu));\n                utf8_bytes_filled = 3;\n            }\n            else\n            {\n                if (JSON_HEDLEY_UNLIKELY(!input.empty()))\n                {\n                    const auto wc2 = static_cast<unsigned int>(input.get_character());\n                    const auto charcode = 0x10000u + (((static_cast<unsigned int>(wc) & 0x3FFu) << 10u) | (wc2 & 0x3FFu));\n                    utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(0xF0u | (charcode >> 18u));\n                    utf8_bytes[1] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 12u) & 0x3Fu));\n                    utf8_bytes[2] = static_cast<std::char_traits<char>::int_type>(0x80u | ((charcode >> 6u) & 0x3Fu));\n                    utf8_bytes[3] = static_cast<std::char_traits<char>::int_type>(0x80u | (charcode & 0x3Fu));\n                    utf8_bytes_filled = 4;\n                }\n                else\n                {\n                    utf8_bytes[0] = static_cast<std::char_traits<char>::int_type>(wc);\n                    utf8_bytes_filled = 1;\n                }\n            }\n        }\n    }\n};\n\n// Wraps another input adapter to convert wide character types into individual bytes.\ntemplate<typename BaseInputAdapter, typename WideCharType>\nclass wide_string_input_adapter\n{\n  public:\n    using char_type = char;\n\n    wide_string_input_adapter(BaseInputAdapter base)\n        : base_adapter(base) {}\n\n    typename std::char_traits<char>::int_type get_character() noexcept\n    {\n        // check if buffer needs to be filled\n        if (utf8_bytes_index == utf8_bytes_filled)\n        {\n            fill_buffer<sizeof(WideCharType)>();\n\n            JSON_ASSERT(utf8_bytes_filled > 0);\n            JSON_ASSERT(utf8_bytes_index == 0);\n        }\n\n        // use buffer\n        JSON_ASSERT(utf8_bytes_filled > 0);\n        JSON_ASSERT(utf8_bytes_index < utf8_bytes_filled);\n        return utf8_bytes[utf8_bytes_index++];\n    }\n\n    // parsing binary with wchar doesn't make sense, but since the parsing mode can be runtime, we need something here\n    template<class T>\n    std::size_t get_elements(T* /*dest*/, std::size_t /*count*/ = 1)\n    {\n        JSON_THROW(parse_error::create(112, 1, \"wide string type cannot be interpreted as binary data\", nullptr));\n    }\n\n  private:\n    BaseInputAdapter base_adapter;\n\n    template<size_t T>\n    void fill_buffer()\n    {\n        wide_string_input_helper<BaseInputAdapter, T>::fill_buffer(base_adapter, utf8_bytes, utf8_bytes_index, utf8_bytes_filled);\n    }\n\n    /// a buffer for UTF-8 bytes\n    std::array<std::char_traits<char>::int_type, 4> utf8_bytes = {{0, 0, 0, 0}};\n\n    /// index to the utf8_codes array for the next valid byte\n    std::size_t utf8_bytes_index = 0;\n    /// number of valid bytes in the utf8_codes array\n    std::size_t utf8_bytes_filled = 0;\n};\n\ntemplate<typename IteratorType, typename Enable = void>\nstruct iterator_input_adapter_factory\n{\n    using iterator_type = IteratorType;\n    using char_type = typename std::iterator_traits<iterator_type>::value_type;\n    using adapter_type = iterator_input_adapter<iterator_type>;\n\n    static adapter_type create(IteratorType first, IteratorType last)\n    {\n        return adapter_type(std::move(first), std::move(last));\n    }\n};\n\ntemplate<typename T>\nstruct is_iterator_of_multibyte\n{\n    using value_type = typename std::iterator_traits<T>::value_type;\n    enum\n    {\n        value = sizeof(value_type) > 1\n    };\n};\n\ntemplate<typename IteratorType>\nstruct iterator_input_adapter_factory<IteratorType, enable_if_t<is_iterator_of_multibyte<IteratorType>::value>>\n{\n    using iterator_type = IteratorType;\n    using char_type = typename std::iterator_traits<iterator_type>::value_type;\n    using base_adapter_type = iterator_input_adapter<iterator_type>;\n    using adapter_type = wide_string_input_adapter<base_adapter_type, char_type>;\n\n    static adapter_type create(IteratorType first, IteratorType last)\n    {\n        return adapter_type(base_adapter_type(std::move(first), std::move(last)));\n    }\n};\n\n// General purpose iterator-based input\ntemplate<typename IteratorType>\ntypename iterator_input_adapter_factory<IteratorType>::adapter_type input_adapter(IteratorType first, IteratorType last)\n{\n    using factory_type = iterator_input_adapter_factory<IteratorType>;\n    return factory_type::create(first, last);\n}\n\n// Convenience shorthand from container to iterator\n// Enables ADL on begin(container) and end(container)\n// Encloses the using declarations in namespace for not to leak them to outside scope\n\nnamespace container_input_adapter_factory_impl\n{\n\nusing std::begin;\nusing std::end;\n\ntemplate<typename ContainerType, typename Enable = void>\nstruct container_input_adapter_factory {};\n\ntemplate<typename ContainerType>\nstruct container_input_adapter_factory< ContainerType,\n       void_t<decltype(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>()))>>\n       {\n           using adapter_type = decltype(input_adapter(begin(std::declval<ContainerType>()), end(std::declval<ContainerType>())));\n\n           static adapter_type create(const ContainerType& container)\n{\n    return input_adapter(begin(container), end(container));\n}\n       };\n\n}  // namespace container_input_adapter_factory_impl\n\ntemplate<typename ContainerType>\ntypename container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::adapter_type input_adapter(const ContainerType& container)\n{\n    return container_input_adapter_factory_impl::container_input_adapter_factory<ContainerType>::create(container);\n}\n\n// specialization for std::string\nusing string_input_adapter_type = decltype(input_adapter(std::declval<std::string>()));\n\n#ifndef JSON_NO_IO\n// Special cases with fast paths\ninline file_input_adapter input_adapter(std::FILE* file)\n{\n    if (file == nullptr)\n    {\n        JSON_THROW(parse_error::create(101, 0, \"attempting to parse an empty input; check that your input string or stream contains the expected JSON\", nullptr));\n    }\n    return file_input_adapter(file);\n}\n\ninline input_stream_adapter input_adapter(std::istream& stream)\n{\n    return input_stream_adapter(stream);\n}\n\ninline input_stream_adapter input_adapter(std::istream&& stream)\n{\n    return input_stream_adapter(stream);\n}\n#endif  // JSON_NO_IO\n\nusing contiguous_bytes_input_adapter = decltype(input_adapter(std::declval<const char*>(), std::declval<const char*>()));\n\n// Null-delimited strings, and the like.\ntemplate < typename CharT,\n           typename std::enable_if <\n               std::is_pointer<CharT>::value&&\n               !std::is_array<CharT>::value&&\n               std::is_integral<typename std::remove_pointer<CharT>::type>::value&&\n               sizeof(typename std::remove_pointer<CharT>::type) == 1,\n               int >::type = 0 >\ncontiguous_bytes_input_adapter input_adapter(CharT b)\n{\n    if (b == nullptr)\n    {\n        JSON_THROW(parse_error::create(101, 0, \"attempting to parse an empty input; check that your input string or stream contains the expected JSON\", nullptr));\n    }\n    auto length = std::strlen(reinterpret_cast<const char*>(b));\n    const auto* ptr = reinterpret_cast<const char*>(b);\n    return input_adapter(ptr, ptr + length); // cppcheck-suppress[nullPointerArithmeticRedundantCheck]\n}\n\ntemplate<typename T, std::size_t N>\nauto input_adapter(T (&array)[N]) -> decltype(input_adapter(array, array + N)) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n{\n    return input_adapter(array, array + N);\n}\n\n// This class only handles inputs of input_buffer_adapter type.\n// It's required so that expressions like {ptr, len} can be implicitly cast\n// to the correct adapter.\nclass span_input_adapter\n{\n  public:\n    template < typename CharT,\n               typename std::enable_if <\n                   std::is_pointer<CharT>::value&&\n                   std::is_integral<typename std::remove_pointer<CharT>::type>::value&&\n                   sizeof(typename std::remove_pointer<CharT>::type) == 1,\n                   int >::type = 0 >\n    span_input_adapter(CharT b, std::size_t l)\n        : ia(reinterpret_cast<const char*>(b), reinterpret_cast<const char*>(b) + l) {}\n\n    template<class IteratorType,\n             typename std::enable_if<\n                 std::is_same<typename iterator_traits<IteratorType>::iterator_category, std::random_access_iterator_tag>::value,\n                 int>::type = 0>\n    span_input_adapter(IteratorType first, IteratorType last)\n        : ia(input_adapter(first, last)) {}\n\n    contiguous_bytes_input_adapter&& get()\n    {\n        return std::move(ia); // NOLINT(hicpp-move-const-arg,performance-move-const-arg)\n    }\n\n  private:\n    contiguous_bytes_input_adapter ia;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/input/json_sax.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef>\n#include <string> // string\n#include <type_traits> // enable_if_t\n#include <utility> // move\n#include <vector> // vector\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/input/lexer.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <array> // array\n#include <clocale> // localeconv\n#include <cstddef> // size_t\n#include <cstdio> // snprintf\n#include <cstdlib> // strtof, strtod, strtold, strtoll, strtoull\n#include <initializer_list> // initializer_list\n#include <string> // char_traits, string\n#include <utility> // move\n#include <vector> // vector\n\n// #include <nlohmann/detail/input/input_adapters.hpp>\n\n// #include <nlohmann/detail/input/position_t.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n///////////\n// lexer //\n///////////\n\ntemplate<typename BasicJsonType>\nclass lexer_base\n{\n  public:\n    /// token types for the parser\n    enum class token_type\n    {\n        uninitialized,    ///< indicating the scanner is uninitialized\n        literal_true,     ///< the `true` literal\n        literal_false,    ///< the `false` literal\n        literal_null,     ///< the `null` literal\n        value_string,     ///< a string -- use get_string() for actual value\n        value_unsigned,   ///< an unsigned integer -- use get_number_unsigned() for actual value\n        value_integer,    ///< a signed integer -- use get_number_integer() for actual value\n        value_float,      ///< an floating point number -- use get_number_float() for actual value\n        begin_array,      ///< the character for array begin `[`\n        begin_object,     ///< the character for object begin `{`\n        end_array,        ///< the character for array end `]`\n        end_object,       ///< the character for object end `}`\n        name_separator,   ///< the name separator `:`\n        value_separator,  ///< the value separator `,`\n        parse_error,      ///< indicating a parse error\n        end_of_input,     ///< indicating the end of the input buffer\n        literal_or_value  ///< a literal or the begin of a value (only for diagnostics)\n    };\n\n    /// return name of values of type token_type (only used for errors)\n    JSON_HEDLEY_RETURNS_NON_NULL\n    JSON_HEDLEY_CONST\n    static const char* token_type_name(const token_type t) noexcept\n    {\n        switch (t)\n        {\n            case token_type::uninitialized:\n                return \"<uninitialized>\";\n            case token_type::literal_true:\n                return \"true literal\";\n            case token_type::literal_false:\n                return \"false literal\";\n            case token_type::literal_null:\n                return \"null literal\";\n            case token_type::value_string:\n                return \"string literal\";\n            case token_type::value_unsigned:\n            case token_type::value_integer:\n            case token_type::value_float:\n                return \"number literal\";\n            case token_type::begin_array:\n                return \"'['\";\n            case token_type::begin_object:\n                return \"'{'\";\n            case token_type::end_array:\n                return \"']'\";\n            case token_type::end_object:\n                return \"'}'\";\n            case token_type::name_separator:\n                return \"':'\";\n            case token_type::value_separator:\n                return \"','\";\n            case token_type::parse_error:\n                return \"<parse error>\";\n            case token_type::end_of_input:\n                return \"end of input\";\n            case token_type::literal_or_value:\n                return \"'[', '{', or a literal\";\n            // LCOV_EXCL_START\n            default: // catch non-enum values\n                return \"unknown token\";\n                // LCOV_EXCL_STOP\n        }\n    }\n};\n/*!\n@brief lexical analysis\n\nThis class organizes the lexical analysis during JSON deserialization.\n*/\ntemplate<typename BasicJsonType, typename InputAdapterType>\nclass lexer : public lexer_base<BasicJsonType>\n{\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using char_type = typename InputAdapterType::char_type;\n    using char_int_type = typename char_traits<char_type>::int_type;\n\n  public:\n    using token_type = typename lexer_base<BasicJsonType>::token_type;\n\n    explicit lexer(InputAdapterType&& adapter, bool ignore_comments_ = false) noexcept\n        : ia(std::move(adapter))\n        , ignore_comments(ignore_comments_)\n        , decimal_point_char(static_cast<char_int_type>(get_decimal_point()))\n    {}\n\n    // delete because of pointer members\n    lexer(const lexer&) = delete;\n    lexer(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    lexer& operator=(lexer&) = delete;\n    lexer& operator=(lexer&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    ~lexer() = default;\n\n  private:\n    /////////////////////\n    // locales\n    /////////////////////\n\n    /// return the locale-dependent decimal point\n    JSON_HEDLEY_PURE\n    static char get_decimal_point() noexcept\n    {\n        const auto* loc = localeconv();\n        JSON_ASSERT(loc != nullptr);\n        return (loc->decimal_point == nullptr) ? '.' : *(loc->decimal_point);\n    }\n\n    /////////////////////\n    // scan functions\n    /////////////////////\n\n    /*!\n    @brief get codepoint from 4 hex characters following `\\u`\n\n    For input \"\\u c1 c2 c3 c4\" the codepoint is:\n      (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4\n    = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0)\n\n    Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f'\n    must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The\n    conversion is done by subtracting the offset (0x30, 0x37, and 0x57)\n    between the ASCII value of the character and the desired integer value.\n\n    @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or\n            non-hex character)\n    */\n    int get_codepoint()\n    {\n        // this function only makes sense after reading `\\u`\n        JSON_ASSERT(current == 'u');\n        int codepoint = 0;\n\n        const auto factors = { 12u, 8u, 4u, 0u };\n        for (const auto factor : factors)\n        {\n            get();\n\n            if (current >= '0' && current <= '9')\n            {\n                codepoint += static_cast<int>((static_cast<unsigned int>(current) - 0x30u) << factor);\n            }\n            else if (current >= 'A' && current <= 'F')\n            {\n                codepoint += static_cast<int>((static_cast<unsigned int>(current) - 0x37u) << factor);\n            }\n            else if (current >= 'a' && current <= 'f')\n            {\n                codepoint += static_cast<int>((static_cast<unsigned int>(current) - 0x57u) << factor);\n            }\n            else\n            {\n                return -1;\n            }\n        }\n\n        JSON_ASSERT(0x0000 <= codepoint && codepoint <= 0xFFFF);\n        return codepoint;\n    }\n\n    /*!\n    @brief check if the next byte(s) are inside a given range\n\n    Adds the current byte and, for each passed range, reads a new byte and\n    checks if it is inside the range. If a violation was detected, set up an\n    error message and return false. Otherwise, return true.\n\n    @param[in] ranges  list of integers; interpreted as list of pairs of\n                       inclusive lower and upper bound, respectively\n\n    @pre The passed list @a ranges must have 2, 4, or 6 elements; that is,\n         1, 2, or 3 pairs. This precondition is enforced by an assertion.\n\n    @return true if and only if no range violation was detected\n    */\n    bool next_byte_in_range(std::initializer_list<char_int_type> ranges)\n    {\n        JSON_ASSERT(ranges.size() == 2 || ranges.size() == 4 || ranges.size() == 6);\n        add(current);\n\n        for (auto range = ranges.begin(); range != ranges.end(); ++range)\n        {\n            get();\n            if (JSON_HEDLEY_LIKELY(*range <= current && current <= *(++range))) // NOLINT(bugprone-inc-dec-in-conditions)\n            {\n                add(current);\n            }\n            else\n            {\n                error_message = \"invalid string: ill-formed UTF-8 byte\";\n                return false;\n            }\n        }\n\n        return true;\n    }\n\n    /*!\n    @brief scan a string literal\n\n    This function scans a string according to Sect. 7 of RFC 8259. While\n    scanning, bytes are escaped and copied into buffer token_buffer. Then the\n    function returns successfully, token_buffer is *not* null-terminated (as it\n    may contain \\0 bytes), and token_buffer.size() is the number of bytes in the\n    string.\n\n    @return token_type::value_string if string could be successfully scanned,\n            token_type::parse_error otherwise\n\n    @note In case of errors, variable error_message contains a textual\n          description.\n    */\n    token_type scan_string()\n    {\n        // reset token_buffer (ignore opening quote)\n        reset();\n\n        // we entered the function by reading an open quote\n        JSON_ASSERT(current == '\\\"');\n\n        while (true)\n        {\n            // get next character\n            switch (get())\n            {\n                // end of file while parsing string\n                case char_traits<char_type>::eof():\n                {\n                    error_message = \"invalid string: missing closing quote\";\n                    return token_type::parse_error;\n                }\n\n                // closing quote\n                case '\\\"':\n                {\n                    return token_type::value_string;\n                }\n\n                // escapes\n                case '\\\\':\n                {\n                    switch (get())\n                    {\n                        // quotation mark\n                        case '\\\"':\n                            add('\\\"');\n                            break;\n                        // reverse solidus\n                        case '\\\\':\n                            add('\\\\');\n                            break;\n                        // solidus\n                        case '/':\n                            add('/');\n                            break;\n                        // backspace\n                        case 'b':\n                            add('\\b');\n                            break;\n                        // form feed\n                        case 'f':\n                            add('\\f');\n                            break;\n                        // line feed\n                        case 'n':\n                            add('\\n');\n                            break;\n                        // carriage return\n                        case 'r':\n                            add('\\r');\n                            break;\n                        // tab\n                        case 't':\n                            add('\\t');\n                            break;\n\n                        // unicode escapes\n                        case 'u':\n                        {\n                            const int codepoint1 = get_codepoint();\n                            int codepoint = codepoint1; // start with codepoint1\n\n                            if (JSON_HEDLEY_UNLIKELY(codepoint1 == -1))\n                            {\n                                error_message = \"invalid string: '\\\\u' must be followed by 4 hex digits\";\n                                return token_type::parse_error;\n                            }\n\n                            // check if code point is a high surrogate\n                            if (0xD800 <= codepoint1 && codepoint1 <= 0xDBFF)\n                            {\n                                // expect next \\uxxxx entry\n                                if (JSON_HEDLEY_LIKELY(get() == '\\\\' && get() == 'u'))\n                                {\n                                    const int codepoint2 = get_codepoint();\n\n                                    if (JSON_HEDLEY_UNLIKELY(codepoint2 == -1))\n                                    {\n                                        error_message = \"invalid string: '\\\\u' must be followed by 4 hex digits\";\n                                        return token_type::parse_error;\n                                    }\n\n                                    // check if codepoint2 is a low surrogate\n                                    if (JSON_HEDLEY_LIKELY(0xDC00 <= codepoint2 && codepoint2 <= 0xDFFF))\n                                    {\n                                        // overwrite codepoint\n                                        codepoint = static_cast<int>(\n                                                        // high surrogate occupies the most significant 22 bits\n                                                        (static_cast<unsigned int>(codepoint1) << 10u)\n                                                        // low surrogate occupies the least significant 15 bits\n                                                        + static_cast<unsigned int>(codepoint2)\n                                                        // there is still the 0xD800, 0xDC00 and 0x10000 noise\n                                                        // in the result, so we have to subtract with:\n                                                        // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00\n                                                        - 0x35FDC00u);\n                                    }\n                                    else\n                                    {\n                                        error_message = \"invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF\";\n                                        return token_type::parse_error;\n                                    }\n                                }\n                                else\n                                {\n                                    error_message = \"invalid string: surrogate U+D800..U+DBFF must be followed by U+DC00..U+DFFF\";\n                                    return token_type::parse_error;\n                                }\n                            }\n                            else\n                            {\n                                if (JSON_HEDLEY_UNLIKELY(0xDC00 <= codepoint1 && codepoint1 <= 0xDFFF))\n                                {\n                                    error_message = \"invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF\";\n                                    return token_type::parse_error;\n                                }\n                            }\n\n                            // result of the above calculation yields a proper codepoint\n                            JSON_ASSERT(0x00 <= codepoint && codepoint <= 0x10FFFF);\n\n                            // translate codepoint into bytes\n                            if (codepoint < 0x80)\n                            {\n                                // 1-byte characters: 0xxxxxxx (ASCII)\n                                add(static_cast<char_int_type>(codepoint));\n                            }\n                            else if (codepoint <= 0x7FF)\n                            {\n                                // 2-byte characters: 110xxxxx 10xxxxxx\n                                add(static_cast<char_int_type>(0xC0u | (static_cast<unsigned int>(codepoint) >> 6u)));\n                                add(static_cast<char_int_type>(0x80u | (static_cast<unsigned int>(codepoint) & 0x3Fu)));\n                            }\n                            else if (codepoint <= 0xFFFF)\n                            {\n                                // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx\n                                add(static_cast<char_int_type>(0xE0u | (static_cast<unsigned int>(codepoint) >> 12u)));\n                                add(static_cast<char_int_type>(0x80u | ((static_cast<unsigned int>(codepoint) >> 6u) & 0x3Fu)));\n                                add(static_cast<char_int_type>(0x80u | (static_cast<unsigned int>(codepoint) & 0x3Fu)));\n                            }\n                            else\n                            {\n                                // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx\n                                add(static_cast<char_int_type>(0xF0u | (static_cast<unsigned int>(codepoint) >> 18u)));\n                                add(static_cast<char_int_type>(0x80u | ((static_cast<unsigned int>(codepoint) >> 12u) & 0x3Fu)));\n                                add(static_cast<char_int_type>(0x80u | ((static_cast<unsigned int>(codepoint) >> 6u) & 0x3Fu)));\n                                add(static_cast<char_int_type>(0x80u | (static_cast<unsigned int>(codepoint) & 0x3Fu)));\n                            }\n\n                            break;\n                        }\n\n                        // other characters after escape\n                        default:\n                            error_message = \"invalid string: forbidden character after backslash\";\n                            return token_type::parse_error;\n                    }\n\n                    break;\n                }\n\n                // invalid control characters\n                case 0x00:\n                {\n                    error_message = \"invalid string: control character U+0000 (NUL) must be escaped to \\\\u0000\";\n                    return token_type::parse_error;\n                }\n\n                case 0x01:\n                {\n                    error_message = \"invalid string: control character U+0001 (SOH) must be escaped to \\\\u0001\";\n                    return token_type::parse_error;\n                }\n\n                case 0x02:\n                {\n                    error_message = \"invalid string: control character U+0002 (STX) must be escaped to \\\\u0002\";\n                    return token_type::parse_error;\n                }\n\n                case 0x03:\n                {\n                    error_message = \"invalid string: control character U+0003 (ETX) must be escaped to \\\\u0003\";\n                    return token_type::parse_error;\n                }\n\n                case 0x04:\n                {\n                    error_message = \"invalid string: control character U+0004 (EOT) must be escaped to \\\\u0004\";\n                    return token_type::parse_error;\n                }\n\n                case 0x05:\n                {\n                    error_message = \"invalid string: control character U+0005 (ENQ) must be escaped to \\\\u0005\";\n                    return token_type::parse_error;\n                }\n\n                case 0x06:\n                {\n                    error_message = \"invalid string: control character U+0006 (ACK) must be escaped to \\\\u0006\";\n                    return token_type::parse_error;\n                }\n\n                case 0x07:\n                {\n                    error_message = \"invalid string: control character U+0007 (BEL) must be escaped to \\\\u0007\";\n                    return token_type::parse_error;\n                }\n\n                case 0x08:\n                {\n                    error_message = \"invalid string: control character U+0008 (BS) must be escaped to \\\\u0008 or \\\\b\";\n                    return token_type::parse_error;\n                }\n\n                case 0x09:\n                {\n                    error_message = \"invalid string: control character U+0009 (HT) must be escaped to \\\\u0009 or \\\\t\";\n                    return token_type::parse_error;\n                }\n\n                case 0x0A:\n                {\n                    error_message = \"invalid string: control character U+000A (LF) must be escaped to \\\\u000A or \\\\n\";\n                    return token_type::parse_error;\n                }\n\n                case 0x0B:\n                {\n                    error_message = \"invalid string: control character U+000B (VT) must be escaped to \\\\u000B\";\n                    return token_type::parse_error;\n                }\n\n                case 0x0C:\n                {\n                    error_message = \"invalid string: control character U+000C (FF) must be escaped to \\\\u000C or \\\\f\";\n                    return token_type::parse_error;\n                }\n\n                case 0x0D:\n                {\n                    error_message = \"invalid string: control character U+000D (CR) must be escaped to \\\\u000D or \\\\r\";\n                    return token_type::parse_error;\n                }\n\n                case 0x0E:\n                {\n                    error_message = \"invalid string: control character U+000E (SO) must be escaped to \\\\u000E\";\n                    return token_type::parse_error;\n                }\n\n                case 0x0F:\n                {\n                    error_message = \"invalid string: control character U+000F (SI) must be escaped to \\\\u000F\";\n                    return token_type::parse_error;\n                }\n\n                case 0x10:\n                {\n                    error_message = \"invalid string: control character U+0010 (DLE) must be escaped to \\\\u0010\";\n                    return token_type::parse_error;\n                }\n\n                case 0x11:\n                {\n                    error_message = \"invalid string: control character U+0011 (DC1) must be escaped to \\\\u0011\";\n                    return token_type::parse_error;\n                }\n\n                case 0x12:\n                {\n                    error_message = \"invalid string: control character U+0012 (DC2) must be escaped to \\\\u0012\";\n                    return token_type::parse_error;\n                }\n\n                case 0x13:\n                {\n                    error_message = \"invalid string: control character U+0013 (DC3) must be escaped to \\\\u0013\";\n                    return token_type::parse_error;\n                }\n\n                case 0x14:\n                {\n                    error_message = \"invalid string: control character U+0014 (DC4) must be escaped to \\\\u0014\";\n                    return token_type::parse_error;\n                }\n\n                case 0x15:\n                {\n                    error_message = \"invalid string: control character U+0015 (NAK) must be escaped to \\\\u0015\";\n                    return token_type::parse_error;\n                }\n\n                case 0x16:\n                {\n                    error_message = \"invalid string: control character U+0016 (SYN) must be escaped to \\\\u0016\";\n                    return token_type::parse_error;\n                }\n\n                case 0x17:\n                {\n                    error_message = \"invalid string: control character U+0017 (ETB) must be escaped to \\\\u0017\";\n                    return token_type::parse_error;\n                }\n\n                case 0x18:\n                {\n                    error_message = \"invalid string: control character U+0018 (CAN) must be escaped to \\\\u0018\";\n                    return token_type::parse_error;\n                }\n\n                case 0x19:\n                {\n                    error_message = \"invalid string: control character U+0019 (EM) must be escaped to \\\\u0019\";\n                    return token_type::parse_error;\n                }\n\n                case 0x1A:\n                {\n                    error_message = \"invalid string: control character U+001A (SUB) must be escaped to \\\\u001A\";\n                    return token_type::parse_error;\n                }\n\n                case 0x1B:\n                {\n                    error_message = \"invalid string: control character U+001B (ESC) must be escaped to \\\\u001B\";\n                    return token_type::parse_error;\n                }\n\n                case 0x1C:\n                {\n                    error_message = \"invalid string: control character U+001C (FS) must be escaped to \\\\u001C\";\n                    return token_type::parse_error;\n                }\n\n                case 0x1D:\n                {\n                    error_message = \"invalid string: control character U+001D (GS) must be escaped to \\\\u001D\";\n                    return token_type::parse_error;\n                }\n\n                case 0x1E:\n                {\n                    error_message = \"invalid string: control character U+001E (RS) must be escaped to \\\\u001E\";\n                    return token_type::parse_error;\n                }\n\n                case 0x1F:\n                {\n                    error_message = \"invalid string: control character U+001F (US) must be escaped to \\\\u001F\";\n                    return token_type::parse_error;\n                }\n\n                // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace))\n                case 0x20:\n                case 0x21:\n                case 0x23:\n                case 0x24:\n                case 0x25:\n                case 0x26:\n                case 0x27:\n                case 0x28:\n                case 0x29:\n                case 0x2A:\n                case 0x2B:\n                case 0x2C:\n                case 0x2D:\n                case 0x2E:\n                case 0x2F:\n                case 0x30:\n                case 0x31:\n                case 0x32:\n                case 0x33:\n                case 0x34:\n                case 0x35:\n                case 0x36:\n                case 0x37:\n                case 0x38:\n                case 0x39:\n                case 0x3A:\n                case 0x3B:\n                case 0x3C:\n                case 0x3D:\n                case 0x3E:\n                case 0x3F:\n                case 0x40:\n                case 0x41:\n                case 0x42:\n                case 0x43:\n                case 0x44:\n                case 0x45:\n                case 0x46:\n                case 0x47:\n                case 0x48:\n                case 0x49:\n                case 0x4A:\n                case 0x4B:\n                case 0x4C:\n                case 0x4D:\n                case 0x4E:\n                case 0x4F:\n                case 0x50:\n                case 0x51:\n                case 0x52:\n                case 0x53:\n                case 0x54:\n                case 0x55:\n                case 0x56:\n                case 0x57:\n                case 0x58:\n                case 0x59:\n                case 0x5A:\n                case 0x5B:\n                case 0x5D:\n                case 0x5E:\n                case 0x5F:\n                case 0x60:\n                case 0x61:\n                case 0x62:\n                case 0x63:\n                case 0x64:\n                case 0x65:\n                case 0x66:\n                case 0x67:\n                case 0x68:\n                case 0x69:\n                case 0x6A:\n                case 0x6B:\n                case 0x6C:\n                case 0x6D:\n                case 0x6E:\n                case 0x6F:\n                case 0x70:\n                case 0x71:\n                case 0x72:\n                case 0x73:\n                case 0x74:\n                case 0x75:\n                case 0x76:\n                case 0x77:\n                case 0x78:\n                case 0x79:\n                case 0x7A:\n                case 0x7B:\n                case 0x7C:\n                case 0x7D:\n                case 0x7E:\n                case 0x7F:\n                {\n                    add(current);\n                    break;\n                }\n\n                // U+0080..U+07FF: bytes C2..DF 80..BF\n                case 0xC2:\n                case 0xC3:\n                case 0xC4:\n                case 0xC5:\n                case 0xC6:\n                case 0xC7:\n                case 0xC8:\n                case 0xC9:\n                case 0xCA:\n                case 0xCB:\n                case 0xCC:\n                case 0xCD:\n                case 0xCE:\n                case 0xCF:\n                case 0xD0:\n                case 0xD1:\n                case 0xD2:\n                case 0xD3:\n                case 0xD4:\n                case 0xD5:\n                case 0xD6:\n                case 0xD7:\n                case 0xD8:\n                case 0xD9:\n                case 0xDA:\n                case 0xDB:\n                case 0xDC:\n                case 0xDD:\n                case 0xDE:\n                case 0xDF:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!next_byte_in_range({0x80, 0xBF})))\n                    {\n                        return token_type::parse_error;\n                    }\n                    break;\n                }\n\n                // U+0800..U+0FFF: bytes E0 A0..BF 80..BF\n                case 0xE0:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF}))))\n                    {\n                        return token_type::parse_error;\n                    }\n                    break;\n                }\n\n                // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF\n                // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF\n                case 0xE1:\n                case 0xE2:\n                case 0xE3:\n                case 0xE4:\n                case 0xE5:\n                case 0xE6:\n                case 0xE7:\n                case 0xE8:\n                case 0xE9:\n                case 0xEA:\n                case 0xEB:\n                case 0xEC:\n                case 0xEE:\n                case 0xEF:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF}))))\n                    {\n                        return token_type::parse_error;\n                    }\n                    break;\n                }\n\n                // U+D000..U+D7FF: bytes ED 80..9F 80..BF\n                case 0xED:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x9F, 0x80, 0xBF}))))\n                    {\n                        return token_type::parse_error;\n                    }\n                    break;\n                }\n\n                // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF\n                case 0xF0:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))\n                    {\n                        return token_type::parse_error;\n                    }\n                    break;\n                }\n\n                // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF\n                case 0xF1:\n                case 0xF2:\n                case 0xF3:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF}))))\n                    {\n                        return token_type::parse_error;\n                    }\n                    break;\n                }\n\n                // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF\n                case 0xF4:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!(next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF}))))\n                    {\n                        return token_type::parse_error;\n                    }\n                    break;\n                }\n\n                // remaining bytes (80..C1 and F5..FF) are ill-formed\n                default:\n                {\n                    error_message = \"invalid string: ill-formed UTF-8 byte\";\n                    return token_type::parse_error;\n                }\n            }\n        }\n    }\n\n    /*!\n     * @brief scan a comment\n     * @return whether comment could be scanned successfully\n     */\n    bool scan_comment()\n    {\n        switch (get())\n        {\n            // single-line comments skip input until a newline or EOF is read\n            case '/':\n            {\n                while (true)\n                {\n                    switch (get())\n                    {\n                        case '\\n':\n                        case '\\r':\n                        case char_traits<char_type>::eof():\n                        case '\\0':\n                            return true;\n\n                        default:\n                            break;\n                    }\n                }\n            }\n\n            // multi-line comments skip input until */ is read\n            case '*':\n            {\n                while (true)\n                {\n                    switch (get())\n                    {\n                        case char_traits<char_type>::eof():\n                        case '\\0':\n                        {\n                            error_message = \"invalid comment; missing closing '*/'\";\n                            return false;\n                        }\n\n                        case '*':\n                        {\n                            switch (get())\n                            {\n                                case '/':\n                                    return true;\n\n                                default:\n                                {\n                                    unget();\n                                    continue;\n                                }\n                            }\n                        }\n\n                        default:\n                            continue;\n                    }\n                }\n            }\n\n            // unexpected character after reading '/'\n            default:\n            {\n                error_message = \"invalid comment; expecting '/' or '*' after '/'\";\n                return false;\n            }\n        }\n    }\n\n    JSON_HEDLEY_NON_NULL(2)\n    static void strtof(float& f, const char* str, char** endptr) noexcept\n    {\n        f = std::strtof(str, endptr);\n    }\n\n    JSON_HEDLEY_NON_NULL(2)\n    static void strtof(double& f, const char* str, char** endptr) noexcept\n    {\n        f = std::strtod(str, endptr);\n    }\n\n    JSON_HEDLEY_NON_NULL(2)\n    static void strtof(long double& f, const char* str, char** endptr) noexcept\n    {\n        f = std::strtold(str, endptr);\n    }\n\n    /*!\n    @brief scan a number literal\n\n    This function scans a string according to Sect. 6 of RFC 8259.\n\n    The function is realized with a deterministic finite state machine derived\n    from the grammar described in RFC 8259. Starting in state \"init\", the\n    input is read and used to determined the next state. Only state \"done\"\n    accepts the number. State \"error\" is a trap state to model errors. In the\n    table below, \"anything\" means any character but the ones listed before.\n\n    state    | 0        | 1-9      | e E      | +       | -       | .        | anything\n    ---------|----------|----------|----------|---------|---------|----------|-----------\n    init     | zero     | any1     | [error]  | [error] | minus   | [error]  | [error]\n    minus    | zero     | any1     | [error]  | [error] | [error] | [error]  | [error]\n    zero     | done     | done     | exponent | done    | done    | decimal1 | done\n    any1     | any1     | any1     | exponent | done    | done    | decimal1 | done\n    decimal1 | decimal2 | decimal2 | [error]  | [error] | [error] | [error]  | [error]\n    decimal2 | decimal2 | decimal2 | exponent | done    | done    | done     | done\n    exponent | any2     | any2     | [error]  | sign    | sign    | [error]  | [error]\n    sign     | any2     | any2     | [error]  | [error] | [error] | [error]  | [error]\n    any2     | any2     | any2     | done     | done    | done    | done     | done\n\n    The state machine is realized with one label per state (prefixed with\n    \"scan_number_\") and `goto` statements between them. The state machine\n    contains cycles, but any cycle can be left when EOF is read. Therefore,\n    the function is guaranteed to terminate.\n\n    During scanning, the read bytes are stored in token_buffer. This string is\n    then converted to a signed integer, an unsigned integer, or a\n    floating-point number.\n\n    @return token_type::value_unsigned, token_type::value_integer, or\n            token_type::value_float if number could be successfully scanned,\n            token_type::parse_error otherwise\n\n    @note The scanner is independent of the current locale. Internally, the\n          locale's decimal point is used instead of `.` to work with the\n          locale-dependent converters.\n    */\n    token_type scan_number()  // lgtm [cpp/use-of-goto] `goto` is used in this function to implement the number-parsing state machine described above. By design, any finite input will eventually reach the \"done\" state or return token_type::parse_error. In each intermediate state, 1 byte of the input is appended to the token_buffer vector, and only the already initialized variables token_buffer, number_type, and error_message are manipulated.\n    {\n        // reset token_buffer to store the number's bytes\n        reset();\n\n        // the type of the parsed number; initially set to unsigned; will be\n        // changed if minus sign, decimal point or exponent is read\n        token_type number_type = token_type::value_unsigned;\n\n        // state (init): we just found out we need to scan a number\n        switch (current)\n        {\n            case '-':\n            {\n                add(current);\n                goto scan_number_minus;\n            }\n\n            case '0':\n            {\n                add(current);\n                goto scan_number_zero;\n            }\n\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_any1;\n            }\n\n            // all other characters are rejected outside scan_number()\n            default:            // LCOV_EXCL_LINE\n                JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n        }\n\nscan_number_minus:\n        // state: we just parsed a leading minus sign\n        number_type = token_type::value_integer;\n        switch (get())\n        {\n            case '0':\n            {\n                add(current);\n                goto scan_number_zero;\n            }\n\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_any1;\n            }\n\n            default:\n            {\n                error_message = \"invalid number; expected digit after '-'\";\n                return token_type::parse_error;\n            }\n        }\n\nscan_number_zero:\n        // state: we just parse a zero (maybe with a leading minus sign)\n        switch (get())\n        {\n            case '.':\n            {\n                add(decimal_point_char);\n                decimal_point_position = token_buffer.size() - 1;\n                goto scan_number_decimal1;\n            }\n\n            case 'e':\n            case 'E':\n            {\n                add(current);\n                goto scan_number_exponent;\n            }\n\n            default:\n                goto scan_number_done;\n        }\n\nscan_number_any1:\n        // state: we just parsed a number 0-9 (maybe with a leading minus sign)\n        switch (get())\n        {\n            case '0':\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_any1;\n            }\n\n            case '.':\n            {\n                add(decimal_point_char);\n                decimal_point_position = token_buffer.size() - 1;\n                goto scan_number_decimal1;\n            }\n\n            case 'e':\n            case 'E':\n            {\n                add(current);\n                goto scan_number_exponent;\n            }\n\n            default:\n                goto scan_number_done;\n        }\n\nscan_number_decimal1:\n        // state: we just parsed a decimal point\n        number_type = token_type::value_float;\n        switch (get())\n        {\n            case '0':\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_decimal2;\n            }\n\n            default:\n            {\n                error_message = \"invalid number; expected digit after '.'\";\n                return token_type::parse_error;\n            }\n        }\n\nscan_number_decimal2:\n        // we just parsed at least one number after a decimal point\n        switch (get())\n        {\n            case '0':\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_decimal2;\n            }\n\n            case 'e':\n            case 'E':\n            {\n                add(current);\n                goto scan_number_exponent;\n            }\n\n            default:\n                goto scan_number_done;\n        }\n\nscan_number_exponent:\n        // we just parsed an exponent\n        number_type = token_type::value_float;\n        switch (get())\n        {\n            case '+':\n            case '-':\n            {\n                add(current);\n                goto scan_number_sign;\n            }\n\n            case '0':\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_any2;\n            }\n\n            default:\n            {\n                error_message =\n                    \"invalid number; expected '+', '-', or digit after exponent\";\n                return token_type::parse_error;\n            }\n        }\n\nscan_number_sign:\n        // we just parsed an exponent sign\n        switch (get())\n        {\n            case '0':\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_any2;\n            }\n\n            default:\n            {\n                error_message = \"invalid number; expected digit after exponent sign\";\n                return token_type::parse_error;\n            }\n        }\n\nscan_number_any2:\n        // we just parsed a number after the exponent or exponent sign\n        switch (get())\n        {\n            case '0':\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n            {\n                add(current);\n                goto scan_number_any2;\n            }\n\n            default:\n                goto scan_number_done;\n        }\n\nscan_number_done:\n        // unget the character after the number (we only read it to know that\n        // we are done scanning a number)\n        unget();\n\n        char* endptr = nullptr; // NOLINT(misc-const-correctness,cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n        errno = 0;\n\n        // try to parse integers first and fall back to floats\n        if (number_type == token_type::value_unsigned)\n        {\n            const auto x = std::strtoull(token_buffer.data(), &endptr, 10);\n\n            // we checked the number format before\n            JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());\n\n            if (errno != ERANGE)\n            {\n                value_unsigned = static_cast<number_unsigned_t>(x);\n                if (value_unsigned == x)\n                {\n                    return token_type::value_unsigned;\n                }\n            }\n        }\n        else if (number_type == token_type::value_integer)\n        {\n            const auto x = std::strtoll(token_buffer.data(), &endptr, 10);\n\n            // we checked the number format before\n            JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());\n\n            if (errno != ERANGE)\n            {\n                value_integer = static_cast<number_integer_t>(x);\n                if (value_integer == x)\n                {\n                    return token_type::value_integer;\n                }\n            }\n        }\n\n        // this code is reached if we parse a floating-point number or if an\n        // integer conversion above failed\n        strtof(value_float, token_buffer.data(), &endptr);\n\n        // we checked the number format before\n        JSON_ASSERT(endptr == token_buffer.data() + token_buffer.size());\n\n        return token_type::value_float;\n    }\n\n    /*!\n    @param[in] literal_text  the literal text to expect\n    @param[in] length        the length of the passed literal text\n    @param[in] return_type   the token type to return on success\n    */\n    JSON_HEDLEY_NON_NULL(2)\n    token_type scan_literal(const char_type* literal_text, const std::size_t length,\n                            token_type return_type)\n    {\n        JSON_ASSERT(char_traits<char_type>::to_char_type(current) == literal_text[0]);\n        for (std::size_t i = 1; i < length; ++i)\n        {\n            if (JSON_HEDLEY_UNLIKELY(char_traits<char_type>::to_char_type(get()) != literal_text[i]))\n            {\n                error_message = \"invalid literal\";\n                return token_type::parse_error;\n            }\n        }\n        return return_type;\n    }\n\n    /////////////////////\n    // input management\n    /////////////////////\n\n    /// reset token_buffer; current character is beginning of token\n    void reset() noexcept\n    {\n        token_buffer.clear();\n        token_string.clear();\n        decimal_point_position = std::string::npos;\n        token_string.push_back(char_traits<char_type>::to_char_type(current));\n    }\n\n    /*\n    @brief get next character from the input\n\n    This function provides the interface to the used input adapter. It does\n    not throw in case the input reached EOF, but returns a\n    `char_traits<char>::eof()` in that case.  Stores the scanned characters\n    for use in error messages.\n\n    @return character read from the input\n    */\n    char_int_type get()\n    {\n        ++position.chars_read_total;\n        ++position.chars_read_current_line;\n\n        if (next_unget)\n        {\n            // just reset the next_unget variable and work with current\n            next_unget = false;\n        }\n        else\n        {\n            current = ia.get_character();\n        }\n\n        if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))\n        {\n            token_string.push_back(char_traits<char_type>::to_char_type(current));\n        }\n\n        if (current == '\\n')\n        {\n            ++position.lines_read;\n            position.chars_read_current_line = 0;\n        }\n\n        return current;\n    }\n\n    /*!\n    @brief unget current character (read it again on next get)\n\n    We implement unget by setting variable next_unget to true. The input is not\n    changed - we just simulate ungetting by modifying chars_read_total,\n    chars_read_current_line, and token_string. The next call to get() will\n    behave as if the unget character is read again.\n    */\n    void unget()\n    {\n        next_unget = true;\n\n        --position.chars_read_total;\n\n        // in case we \"unget\" a newline, we have to also decrement the lines_read\n        if (position.chars_read_current_line == 0)\n        {\n            if (position.lines_read > 0)\n            {\n                --position.lines_read;\n            }\n        }\n        else\n        {\n            --position.chars_read_current_line;\n        }\n\n        if (JSON_HEDLEY_LIKELY(current != char_traits<char_type>::eof()))\n        {\n            JSON_ASSERT(!token_string.empty());\n            token_string.pop_back();\n        }\n    }\n\n    /// add a character to token_buffer\n    void add(char_int_type c)\n    {\n        token_buffer.push_back(static_cast<typename string_t::value_type>(c));\n    }\n\n  public:\n    /////////////////////\n    // value getters\n    /////////////////////\n\n    /// return integer value\n    constexpr number_integer_t get_number_integer() const noexcept\n    {\n        return value_integer;\n    }\n\n    /// return unsigned integer value\n    constexpr number_unsigned_t get_number_unsigned() const noexcept\n    {\n        return value_unsigned;\n    }\n\n    /// return floating-point value\n    constexpr number_float_t get_number_float() const noexcept\n    {\n        return value_float;\n    }\n\n    /// return current string value (implicitly resets the token; useful only once)\n    string_t& get_string()\n    {\n        // translate decimal points from locale back to '.' (#4084)\n        if (decimal_point_char != '.' && decimal_point_position != std::string::npos)\n        {\n            token_buffer[decimal_point_position] = '.';\n        }\n        return token_buffer;\n    }\n\n    /////////////////////\n    // diagnostics\n    /////////////////////\n\n    /// return position of last read token\n    constexpr position_t get_position() const noexcept\n    {\n        return position;\n    }\n\n    /// return the last read token (for errors only).  Will never contain EOF\n    /// (an arbitrary value that is not a valid char value, often -1), because\n    /// 255 may legitimately occur.  May contain NUL, which should be escaped.\n    std::string get_token_string() const\n    {\n        // escape control characters\n        std::string result;\n        for (const auto c : token_string)\n        {\n            if (static_cast<unsigned char>(c) <= '\\x1F')\n            {\n                // escape control characters\n                std::array<char, 9> cs{{}};\n                static_cast<void>((std::snprintf)(cs.data(), cs.size(), \"<U+%.4X>\", static_cast<unsigned char>(c))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n                result += cs.data();\n            }\n            else\n            {\n                // add character as is\n                result.push_back(static_cast<std::string::value_type>(c));\n            }\n        }\n\n        return result;\n    }\n\n    /// return syntax error message\n    JSON_HEDLEY_RETURNS_NON_NULL\n    constexpr const char* get_error_message() const noexcept\n    {\n        return error_message;\n    }\n\n    /////////////////////\n    // actual scanner\n    /////////////////////\n\n    /*!\n    @brief skip the UTF-8 byte order mark\n    @return true iff there is no BOM or the correct BOM has been skipped\n    */\n    bool skip_bom()\n    {\n        if (get() == 0xEF)\n        {\n            // check if we completely parse the BOM\n            return get() == 0xBB && get() == 0xBF;\n        }\n\n        // the first character is not the beginning of the BOM; unget it to\n        // process is later\n        unget();\n        return true;\n    }\n\n    void skip_whitespace()\n    {\n        do\n        {\n            get();\n        }\n        while (current == ' ' || current == '\\t' || current == '\\n' || current == '\\r');\n    }\n\n    token_type scan()\n    {\n        // initially, skip the BOM\n        if (position.chars_read_total == 0 && !skip_bom())\n        {\n            error_message = \"invalid BOM; must be 0xEF 0xBB 0xBF if given\";\n            return token_type::parse_error;\n        }\n\n        // read next character and ignore whitespace\n        skip_whitespace();\n\n        // ignore comments\n        while (ignore_comments && current == '/')\n        {\n            if (!scan_comment())\n            {\n                return token_type::parse_error;\n            }\n\n            // skip following whitespace\n            skip_whitespace();\n        }\n\n        switch (current)\n        {\n            // structural characters\n            case '[':\n                return token_type::begin_array;\n            case ']':\n                return token_type::end_array;\n            case '{':\n                return token_type::begin_object;\n            case '}':\n                return token_type::end_object;\n            case ':':\n                return token_type::name_separator;\n            case ',':\n                return token_type::value_separator;\n\n            // literals\n            case 't':\n            {\n                std::array<char_type, 4> true_literal = {{static_cast<char_type>('t'), static_cast<char_type>('r'), static_cast<char_type>('u'), static_cast<char_type>('e')}};\n                return scan_literal(true_literal.data(), true_literal.size(), token_type::literal_true);\n            }\n            case 'f':\n            {\n                std::array<char_type, 5> false_literal = {{static_cast<char_type>('f'), static_cast<char_type>('a'), static_cast<char_type>('l'), static_cast<char_type>('s'), static_cast<char_type>('e')}};\n                return scan_literal(false_literal.data(), false_literal.size(), token_type::literal_false);\n            }\n            case 'n':\n            {\n                std::array<char_type, 4> null_literal = {{static_cast<char_type>('n'), static_cast<char_type>('u'), static_cast<char_type>('l'), static_cast<char_type>('l')}};\n                return scan_literal(null_literal.data(), null_literal.size(), token_type::literal_null);\n            }\n\n            // string\n            case '\\\"':\n                return scan_string();\n\n            // number\n            case '-':\n            case '0':\n            case '1':\n            case '2':\n            case '3':\n            case '4':\n            case '5':\n            case '6':\n            case '7':\n            case '8':\n            case '9':\n                return scan_number();\n\n            // end of input (the null byte is needed when parsing from\n            // string literals)\n            case '\\0':\n            case char_traits<char_type>::eof():\n                return token_type::end_of_input;\n\n            // error\n            default:\n                error_message = \"invalid literal\";\n                return token_type::parse_error;\n        }\n    }\n\n  private:\n    /// input adapter\n    InputAdapterType ia;\n\n    /// whether comments should be ignored (true) or signaled as errors (false)\n    const bool ignore_comments = false;\n\n    /// the current character\n    char_int_type current = char_traits<char_type>::eof();\n\n    /// whether the next get() call should just return current\n    bool next_unget = false;\n\n    /// the start position of the current token\n    position_t position {};\n\n    /// raw input token string (for error messages)\n    std::vector<char_type> token_string {};\n\n    /// buffer for variable-length tokens (numbers, strings)\n    string_t token_buffer {};\n\n    /// a description of occurred lexer errors\n    const char* error_message = \"\";\n\n    // number values\n    number_integer_t value_integer = 0;\n    number_unsigned_t value_unsigned = 0;\n    number_float_t value_float = 0;\n\n    /// the decimal point\n    const char_int_type decimal_point_char = '.';\n    /// the position of the decimal point in the input\n    std::size_t decimal_point_position = std::string::npos;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/*!\n@brief SAX interface\n\nThis class describes the SAX interface used by @ref nlohmann::json::sax_parse.\nEach function is called in different situations while the input is parsed. The\nboolean return value informs the parser whether to continue processing the\ninput.\n*/\ntemplate<typename BasicJsonType>\nstruct json_sax\n{\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n\n    /*!\n    @brief a null value was read\n    @return whether parsing should proceed\n    */\n    virtual bool null() = 0;\n\n    /*!\n    @brief a boolean value was read\n    @param[in] val  boolean value\n    @return whether parsing should proceed\n    */\n    virtual bool boolean(bool val) = 0;\n\n    /*!\n    @brief an integer number was read\n    @param[in] val  integer value\n    @return whether parsing should proceed\n    */\n    virtual bool number_integer(number_integer_t val) = 0;\n\n    /*!\n    @brief an unsigned integer number was read\n    @param[in] val  unsigned integer value\n    @return whether parsing should proceed\n    */\n    virtual bool number_unsigned(number_unsigned_t val) = 0;\n\n    /*!\n    @brief a floating-point number was read\n    @param[in] val  floating-point value\n    @param[in] s    raw token value\n    @return whether parsing should proceed\n    */\n    virtual bool number_float(number_float_t val, const string_t& s) = 0;\n\n    /*!\n    @brief a string value was read\n    @param[in] val  string value\n    @return whether parsing should proceed\n    @note It is safe to move the passed string value.\n    */\n    virtual bool string(string_t& val) = 0;\n\n    /*!\n    @brief a binary value was read\n    @param[in] val  binary value\n    @return whether parsing should proceed\n    @note It is safe to move the passed binary value.\n    */\n    virtual bool binary(binary_t& val) = 0;\n\n    /*!\n    @brief the beginning of an object was read\n    @param[in] elements  number of object elements or -1 if unknown\n    @return whether parsing should proceed\n    @note binary formats may report the number of elements\n    */\n    virtual bool start_object(std::size_t elements) = 0;\n\n    /*!\n    @brief an object key was read\n    @param[in] val  object key\n    @return whether parsing should proceed\n    @note It is safe to move the passed string.\n    */\n    virtual bool key(string_t& val) = 0;\n\n    /*!\n    @brief the end of an object was read\n    @return whether parsing should proceed\n    */\n    virtual bool end_object() = 0;\n\n    /*!\n    @brief the beginning of an array was read\n    @param[in] elements  number of array elements or -1 if unknown\n    @return whether parsing should proceed\n    @note binary formats may report the number of elements\n    */\n    virtual bool start_array(std::size_t elements) = 0;\n\n    /*!\n    @brief the end of an array was read\n    @return whether parsing should proceed\n    */\n    virtual bool end_array() = 0;\n\n    /*!\n    @brief a parse error occurred\n    @param[in] position    the position in the input where the error occurs\n    @param[in] last_token  the last read token\n    @param[in] ex          an exception object describing the error\n    @return whether parsing should proceed (must return false)\n    */\n    virtual bool parse_error(std::size_t position,\n                             const std::string& last_token,\n                             const detail::exception& ex) = 0;\n\n    json_sax() = default;\n    json_sax(const json_sax&) = default;\n    json_sax(json_sax&&) noexcept = default;\n    json_sax& operator=(const json_sax&) = default;\n    json_sax& operator=(json_sax&&) noexcept = default;\n    virtual ~json_sax() = default;\n};\n\nnamespace detail\n{\nconstexpr std::size_t unknown_size()\n{\n    return (std::numeric_limits<std::size_t>::max)();\n}\n\n/*!\n@brief SAX implementation to create a JSON value from SAX events\n\nThis class implements the @ref json_sax interface and processes the SAX events\nto create a JSON value which makes it basically a DOM parser. The structure or\nhierarchy of the JSON value is managed by the stack `ref_stack` which contains\na pointer to the respective array or object for each recursion depth.\n\nAfter successful parsing, the value that is passed by reference to the\nconstructor contains the parsed value.\n\n@tparam BasicJsonType  the JSON type\n*/\ntemplate<typename BasicJsonType, typename InputAdapterType>\nclass json_sax_dom_parser\n{\n  public:\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n    using lexer_t = lexer<BasicJsonType, InputAdapterType>;\n\n    /*!\n    @param[in,out] r  reference to a JSON value that is manipulated while\n                       parsing\n    @param[in] allow_exceptions_  whether parse errors yield exceptions\n    */\n    explicit json_sax_dom_parser(BasicJsonType& r, const bool allow_exceptions_ = true, lexer_t* lexer_ = nullptr)\n        : root(r), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_)\n    {}\n\n    // make class move-only\n    json_sax_dom_parser(const json_sax_dom_parser&) = delete;\n    json_sax_dom_parser(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    json_sax_dom_parser& operator=(const json_sax_dom_parser&) = delete;\n    json_sax_dom_parser& operator=(json_sax_dom_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    ~json_sax_dom_parser() = default;\n\n    bool null()\n    {\n        handle_value(nullptr);\n        return true;\n    }\n\n    bool boolean(bool val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool number_integer(number_integer_t val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool number_unsigned(number_unsigned_t val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool number_float(number_float_t val, const string_t& /*unused*/)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool string(string_t& val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool binary(binary_t& val)\n    {\n        handle_value(std::move(val));\n        return true;\n    }\n\n    bool start_object(std::size_t len)\n    {\n        ref_stack.push_back(handle_value(BasicJsonType::value_t::object));\n\n#if JSON_DIAGNOSTIC_POSITIONS\n        // Manually set the start position of the object here.\n        // Ensure this is after the call to handle_value to ensure correct start position.\n        if (m_lexer_ref)\n        {\n            // Lexer has read the first character of the object, so\n            // subtract 1 from the position to get the correct start position.\n            ref_stack.back()->start_position = m_lexer_ref->get_position() - 1;\n        }\n#endif\n\n        if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))\n        {\n            JSON_THROW(out_of_range::create(408, concat(\"excessive object size: \", std::to_string(len)), ref_stack.back()));\n        }\n\n        return true;\n    }\n\n    bool key(string_t& val)\n    {\n        JSON_ASSERT(!ref_stack.empty());\n        JSON_ASSERT(ref_stack.back()->is_object());\n\n        // add null at given key and store the reference for later\n        object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val));\n        return true;\n    }\n\n    bool end_object()\n    {\n        JSON_ASSERT(!ref_stack.empty());\n        JSON_ASSERT(ref_stack.back()->is_object());\n\n#if JSON_DIAGNOSTIC_POSITIONS\n        if (m_lexer_ref)\n        {\n            // Lexer's position is past the closing brace, so set that as the end position.\n            ref_stack.back()->end_position = m_lexer_ref->get_position();\n        }\n#endif\n\n        ref_stack.back()->set_parents();\n        ref_stack.pop_back();\n        return true;\n    }\n\n    bool start_array(std::size_t len)\n    {\n        ref_stack.push_back(handle_value(BasicJsonType::value_t::array));\n\n#if JSON_DIAGNOSTIC_POSITIONS\n        // Manually set the start position of the array here.\n        // Ensure this is after the call to handle_value to ensure correct start position.\n        if (m_lexer_ref)\n        {\n            ref_stack.back()->start_position = m_lexer_ref->get_position() - 1;\n        }\n#endif\n\n        if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))\n        {\n            JSON_THROW(out_of_range::create(408, concat(\"excessive array size: \", std::to_string(len)), ref_stack.back()));\n        }\n\n        return true;\n    }\n\n    bool end_array()\n    {\n        JSON_ASSERT(!ref_stack.empty());\n        JSON_ASSERT(ref_stack.back()->is_array());\n\n#if JSON_DIAGNOSTIC_POSITIONS\n        if (m_lexer_ref)\n        {\n            // Lexer's position is past the closing bracket, so set that as the end position.\n            ref_stack.back()->end_position = m_lexer_ref->get_position();\n        }\n#endif\n\n        ref_stack.back()->set_parents();\n        ref_stack.pop_back();\n        return true;\n    }\n\n    template<class Exception>\n    bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,\n                     const Exception& ex)\n    {\n        errored = true;\n        static_cast<void>(ex);\n        if (allow_exceptions)\n        {\n            JSON_THROW(ex);\n        }\n        return false;\n    }\n\n    constexpr bool is_errored() const\n    {\n        return errored;\n    }\n\n  private:\n\n#if JSON_DIAGNOSTIC_POSITIONS\n    void handle_diagnostic_positions_for_json_value(BasicJsonType& v)\n    {\n        if (m_lexer_ref)\n        {\n            // Lexer has read past the current field value, so set the end position to the current position.\n            // The start position will be set below based on the length of the string representation\n            // of the value.\n            v.end_position = m_lexer_ref->get_position();\n\n            switch (v.type())\n            {\n                case value_t::boolean:\n                {\n                    // 4 and 5 are the string length of \"true\" and \"false\"\n                    v.start_position = v.end_position - (v.m_data.m_value.boolean ? 4 : 5);\n                    break;\n                }\n\n                case value_t::null:\n                {\n                    // 4 is the string length of \"null\"\n                    v.start_position = v.end_position - 4;\n                    break;\n                }\n\n                case value_t::string:\n                {\n                    // include the length of the quotes, which is 2\n                    v.start_position = v.end_position - v.m_data.m_value.string->size() - 2;\n                    break;\n                }\n\n                // As we handle the start and end positions for values created during parsing,\n                // we do not expect the following value type to be called. Regardless, set the positions\n                // in case this is created manually or through a different constructor. Exclude from lcov\n                // since the exact condition of this switch is esoteric.\n                // LCOV_EXCL_START\n                case value_t::discarded:\n                {\n                    v.end_position = std::string::npos;\n                    v.start_position = v.end_position;\n                    break;\n                }\n                // LCOV_EXCL_STOP\n                case value_t::binary:\n                case value_t::number_integer:\n                case value_t::number_unsigned:\n                case value_t::number_float:\n                {\n                    v.start_position = v.end_position - m_lexer_ref->get_string().size();\n                    break;\n                }\n                case value_t::object:\n                case value_t::array:\n                {\n                    // object and array are handled in start_object() and start_array() handlers\n                    // skip setting the values here.\n                    break;\n                }\n                default: // LCOV_EXCL_LINE\n                    // Handle all possible types discretely, default handler should never be reached.\n                    JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE\n            }\n        }\n    }\n#endif\n\n    /*!\n    @invariant If the ref stack is empty, then the passed value will be the new\n               root.\n    @invariant If the ref stack contains a value, then it is an array or an\n               object to which we can add elements\n    */\n    template<typename Value>\n    JSON_HEDLEY_RETURNS_NON_NULL\n    BasicJsonType* handle_value(Value&& v)\n    {\n        if (ref_stack.empty())\n        {\n            root = BasicJsonType(std::forward<Value>(v));\n\n#if JSON_DIAGNOSTIC_POSITIONS\n            handle_diagnostic_positions_for_json_value(root);\n#endif\n\n            return &root;\n        }\n\n        JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object());\n\n        if (ref_stack.back()->is_array())\n        {\n            ref_stack.back()->m_data.m_value.array->emplace_back(std::forward<Value>(v));\n\n#if JSON_DIAGNOSTIC_POSITIONS\n            handle_diagnostic_positions_for_json_value(ref_stack.back()->m_data.m_value.array->back());\n#endif\n\n            return &(ref_stack.back()->m_data.m_value.array->back());\n        }\n\n        JSON_ASSERT(ref_stack.back()->is_object());\n        JSON_ASSERT(object_element);\n        *object_element = BasicJsonType(std::forward<Value>(v));\n\n#if JSON_DIAGNOSTIC_POSITIONS\n        handle_diagnostic_positions_for_json_value(*object_element);\n#endif\n\n        return object_element;\n    }\n\n    /// the parsed JSON value\n    BasicJsonType& root;\n    /// stack to model hierarchy of values\n    std::vector<BasicJsonType*> ref_stack {};\n    /// helper to hold the reference for the next object element\n    BasicJsonType* object_element = nullptr;\n    /// whether a syntax error occurred\n    bool errored = false;\n    /// whether to throw exceptions in case of errors\n    const bool allow_exceptions = true;\n    /// the lexer reference to obtain the current position\n    lexer_t* m_lexer_ref = nullptr;\n};\n\ntemplate<typename BasicJsonType, typename InputAdapterType>\nclass json_sax_dom_callback_parser\n{\n  public:\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n    using parser_callback_t = typename BasicJsonType::parser_callback_t;\n    using parse_event_t = typename BasicJsonType::parse_event_t;\n    using lexer_t = lexer<BasicJsonType, InputAdapterType>;\n\n    json_sax_dom_callback_parser(BasicJsonType& r,\n                                 parser_callback_t cb,\n                                 const bool allow_exceptions_ = true,\n                                 lexer_t* lexer_ = nullptr)\n        : root(r), callback(std::move(cb)), allow_exceptions(allow_exceptions_), m_lexer_ref(lexer_)\n    {\n        keep_stack.push_back(true);\n    }\n\n    // make class move-only\n    json_sax_dom_callback_parser(const json_sax_dom_callback_parser&) = delete;\n    json_sax_dom_callback_parser(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    json_sax_dom_callback_parser& operator=(const json_sax_dom_callback_parser&) = delete;\n    json_sax_dom_callback_parser& operator=(json_sax_dom_callback_parser&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    ~json_sax_dom_callback_parser() = default;\n\n    bool null()\n    {\n        handle_value(nullptr);\n        return true;\n    }\n\n    bool boolean(bool val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool number_integer(number_integer_t val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool number_unsigned(number_unsigned_t val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool number_float(number_float_t val, const string_t& /*unused*/)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool string(string_t& val)\n    {\n        handle_value(val);\n        return true;\n    }\n\n    bool binary(binary_t& val)\n    {\n        handle_value(std::move(val));\n        return true;\n    }\n\n    bool start_object(std::size_t len)\n    {\n        // check callback for object start\n        const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::object_start, discarded);\n        keep_stack.push_back(keep);\n\n        auto val = handle_value(BasicJsonType::value_t::object, true);\n        ref_stack.push_back(val.second);\n\n        if (ref_stack.back())\n        {\n\n#if JSON_DIAGNOSTIC_POSITIONS\n            // Manually set the start position of the object here.\n            // Ensure this is after the call to handle_value to ensure correct start position.\n            if (m_lexer_ref)\n            {\n                // Lexer has read the first character of the object, so\n                // subtract 1 from the position to get the correct start position.\n                ref_stack.back()->start_position = m_lexer_ref->get_position() - 1;\n            }\n#endif\n\n            // check object limit\n            if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))\n            {\n                JSON_THROW(out_of_range::create(408, concat(\"excessive object size: \", std::to_string(len)), ref_stack.back()));\n            }\n        }\n        return true;\n    }\n\n    bool key(string_t& val)\n    {\n        BasicJsonType k = BasicJsonType(val);\n\n        // check callback for key\n        const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::key, k);\n        key_keep_stack.push_back(keep);\n\n        // add discarded value at given key and store the reference for later\n        if (keep && ref_stack.back())\n        {\n            object_element = &(ref_stack.back()->m_data.m_value.object->operator[](val) = discarded);\n        }\n\n        return true;\n    }\n\n    bool end_object()\n    {\n        if (ref_stack.back())\n        {\n            if (!callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::object_end, *ref_stack.back()))\n            {\n                // discard object\n                *ref_stack.back() = discarded;\n\n#if JSON_DIAGNOSTIC_POSITIONS\n                // Set start/end positions for discarded object.\n                handle_diagnostic_positions_for_json_value(*ref_stack.back());\n#endif\n            }\n            else\n            {\n\n#if JSON_DIAGNOSTIC_POSITIONS\n                if (m_lexer_ref)\n                {\n                    // Lexer's position is past the closing brace, so set that as the end position.\n                    ref_stack.back()->end_position = m_lexer_ref->get_position();\n                }\n#endif\n\n                ref_stack.back()->set_parents();\n            }\n        }\n\n        JSON_ASSERT(!ref_stack.empty());\n        JSON_ASSERT(!keep_stack.empty());\n        ref_stack.pop_back();\n        keep_stack.pop_back();\n\n        if (!ref_stack.empty() && ref_stack.back() && ref_stack.back()->is_structured())\n        {\n            // remove discarded value\n            for (auto it = ref_stack.back()->begin(); it != ref_stack.back()->end(); ++it)\n            {\n                if (it->is_discarded())\n                {\n                    ref_stack.back()->erase(it);\n                    break;\n                }\n            }\n        }\n\n        return true;\n    }\n\n    bool start_array(std::size_t len)\n    {\n        const bool keep = callback(static_cast<int>(ref_stack.size()), parse_event_t::array_start, discarded);\n        keep_stack.push_back(keep);\n\n        auto val = handle_value(BasicJsonType::value_t::array, true);\n        ref_stack.push_back(val.second);\n\n        if (ref_stack.back())\n        {\n\n#if JSON_DIAGNOSTIC_POSITIONS\n            // Manually set the start position of the array here.\n            // Ensure this is after the call to handle_value to ensure correct start position.\n            if (m_lexer_ref)\n            {\n                // Lexer has read the first character of the array, so\n                // subtract 1 from the position to get the correct start position.\n                ref_stack.back()->start_position = m_lexer_ref->get_position() - 1;\n            }\n#endif\n\n            // check array limit\n            if (JSON_HEDLEY_UNLIKELY(len != detail::unknown_size() && len > ref_stack.back()->max_size()))\n            {\n                JSON_THROW(out_of_range::create(408, concat(\"excessive array size: \", std::to_string(len)), ref_stack.back()));\n            }\n        }\n\n        return true;\n    }\n\n    bool end_array()\n    {\n        bool keep = true;\n\n        if (ref_stack.back())\n        {\n            keep = callback(static_cast<int>(ref_stack.size()) - 1, parse_event_t::array_end, *ref_stack.back());\n            if (keep)\n            {\n\n#if JSON_DIAGNOSTIC_POSITIONS\n                if (m_lexer_ref)\n                {\n                    // Lexer's position is past the closing bracket, so set that as the end position.\n                    ref_stack.back()->end_position = m_lexer_ref->get_position();\n                }\n#endif\n\n                ref_stack.back()->set_parents();\n            }\n            else\n            {\n                // discard array\n                *ref_stack.back() = discarded;\n\n#if JSON_DIAGNOSTIC_POSITIONS\n                // Set start/end positions for discarded array.\n                handle_diagnostic_positions_for_json_value(*ref_stack.back());\n#endif\n            }\n        }\n\n        JSON_ASSERT(!ref_stack.empty());\n        JSON_ASSERT(!keep_stack.empty());\n        ref_stack.pop_back();\n        keep_stack.pop_back();\n\n        // remove discarded value\n        if (!keep && !ref_stack.empty() && ref_stack.back()->is_array())\n        {\n            ref_stack.back()->m_data.m_value.array->pop_back();\n        }\n\n        return true;\n    }\n\n    template<class Exception>\n    bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/,\n                     const Exception& ex)\n    {\n        errored = true;\n        static_cast<void>(ex);\n        if (allow_exceptions)\n        {\n            JSON_THROW(ex);\n        }\n        return false;\n    }\n\n    constexpr bool is_errored() const\n    {\n        return errored;\n    }\n\n  private:\n\n#if JSON_DIAGNOSTIC_POSITIONS\n    void handle_diagnostic_positions_for_json_value(BasicJsonType& v)\n    {\n        if (m_lexer_ref)\n        {\n            // Lexer has read past the current field value, so set the end position to the current position.\n            // The start position will be set below based on the length of the string representation\n            // of the value.\n            v.end_position = m_lexer_ref->get_position();\n\n            switch (v.type())\n            {\n                case value_t::boolean:\n                {\n                    // 4 and 5 are the string length of \"true\" and \"false\"\n                    v.start_position = v.end_position - (v.m_data.m_value.boolean ? 4 : 5);\n                    break;\n                }\n\n                case value_t::null:\n                {\n                    // 4 is the string length of \"null\"\n                    v.start_position = v.end_position - 4;\n                    break;\n                }\n\n                case value_t::string:\n                {\n                    // include the length of the quotes, which is 2\n                    v.start_position = v.end_position - v.m_data.m_value.string->size() - 2;\n                    break;\n                }\n\n                case value_t::discarded:\n                {\n                    v.end_position = std::string::npos;\n                    v.start_position = v.end_position;\n                    break;\n                }\n\n                case value_t::binary:\n                case value_t::number_integer:\n                case value_t::number_unsigned:\n                case value_t::number_float:\n                {\n                    v.start_position = v.end_position - m_lexer_ref->get_string().size();\n                    break;\n                }\n\n                case value_t::object:\n                case value_t::array:\n                {\n                    // object and array are handled in start_object() and start_array() handlers\n                    // skip setting the values here.\n                    break;\n                }\n                default: // LCOV_EXCL_LINE\n                    // Handle all possible types discretely, default handler should never be reached.\n                    JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert,-warnings-as-errors) LCOV_EXCL_LINE\n            }\n        }\n    }\n#endif\n\n    /*!\n    @param[in] v  value to add to the JSON value we build during parsing\n    @param[in] skip_callback  whether we should skip calling the callback\n               function; this is required after start_array() and\n               start_object() SAX events, because otherwise we would call the\n               callback function with an empty array or object, respectively.\n\n    @invariant If the ref stack is empty, then the passed value will be the new\n               root.\n    @invariant If the ref stack contains a value, then it is an array or an\n               object to which we can add elements\n\n    @return pair of boolean (whether value should be kept) and pointer (to the\n            passed value in the ref_stack hierarchy; nullptr if not kept)\n    */\n    template<typename Value>\n    std::pair<bool, BasicJsonType*> handle_value(Value&& v, const bool skip_callback = false)\n    {\n        JSON_ASSERT(!keep_stack.empty());\n\n        // do not handle this value if we know it would be added to a discarded\n        // container\n        if (!keep_stack.back())\n        {\n            return {false, nullptr};\n        }\n\n        // create value\n        auto value = BasicJsonType(std::forward<Value>(v));\n\n#if JSON_DIAGNOSTIC_POSITIONS\n        handle_diagnostic_positions_for_json_value(value);\n#endif\n\n        // check callback\n        const bool keep = skip_callback || callback(static_cast<int>(ref_stack.size()), parse_event_t::value, value);\n\n        // do not handle this value if we just learnt it shall be discarded\n        if (!keep)\n        {\n            return {false, nullptr};\n        }\n\n        if (ref_stack.empty())\n        {\n            root = std::move(value);\n            return {true, & root};\n        }\n\n        // skip this value if we already decided to skip the parent\n        // (https://github.com/nlohmann/json/issues/971#issuecomment-413678360)\n        if (!ref_stack.back())\n        {\n            return {false, nullptr};\n        }\n\n        // we now only expect arrays and objects\n        JSON_ASSERT(ref_stack.back()->is_array() || ref_stack.back()->is_object());\n\n        // array\n        if (ref_stack.back()->is_array())\n        {\n            ref_stack.back()->m_data.m_value.array->emplace_back(std::move(value));\n            return {true, & (ref_stack.back()->m_data.m_value.array->back())};\n        }\n\n        // object\n        JSON_ASSERT(ref_stack.back()->is_object());\n        // check if we should store an element for the current key\n        JSON_ASSERT(!key_keep_stack.empty());\n        const bool store_element = key_keep_stack.back();\n        key_keep_stack.pop_back();\n\n        if (!store_element)\n        {\n            return {false, nullptr};\n        }\n\n        JSON_ASSERT(object_element);\n        *object_element = std::move(value);\n        return {true, object_element};\n    }\n\n    /// the parsed JSON value\n    BasicJsonType& root;\n    /// stack to model hierarchy of values\n    std::vector<BasicJsonType*> ref_stack {};\n    /// stack to manage which values to keep\n    std::vector<bool> keep_stack {}; // NOLINT(readability-redundant-member-init)\n    /// stack to manage which object keys to keep\n    std::vector<bool> key_keep_stack {}; // NOLINT(readability-redundant-member-init)\n    /// helper to hold the reference for the next object element\n    BasicJsonType* object_element = nullptr;\n    /// whether a syntax error occurred\n    bool errored = false;\n    /// callback function\n    const parser_callback_t callback = nullptr;\n    /// whether to throw exceptions in case of errors\n    const bool allow_exceptions = true;\n    /// a discarded value for the callback\n    BasicJsonType discarded = BasicJsonType::value_t::discarded;\n    /// the lexer reference to obtain the current position\n    lexer_t* m_lexer_ref = nullptr;\n};\n\ntemplate<typename BasicJsonType>\nclass json_sax_acceptor\n{\n  public:\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n\n    bool null()\n    {\n        return true;\n    }\n\n    bool boolean(bool /*unused*/)\n    {\n        return true;\n    }\n\n    bool number_integer(number_integer_t /*unused*/)\n    {\n        return true;\n    }\n\n    bool number_unsigned(number_unsigned_t /*unused*/)\n    {\n        return true;\n    }\n\n    bool number_float(number_float_t /*unused*/, const string_t& /*unused*/)\n    {\n        return true;\n    }\n\n    bool string(string_t& /*unused*/)\n    {\n        return true;\n    }\n\n    bool binary(binary_t& /*unused*/)\n    {\n        return true;\n    }\n\n    bool start_object(std::size_t /*unused*/ = detail::unknown_size())\n    {\n        return true;\n    }\n\n    bool key(string_t& /*unused*/)\n    {\n        return true;\n    }\n\n    bool end_object()\n    {\n        return true;\n    }\n\n    bool start_array(std::size_t /*unused*/ = detail::unknown_size())\n    {\n        return true;\n    }\n\n    bool end_array()\n    {\n        return true;\n    }\n\n    bool parse_error(std::size_t /*unused*/, const std::string& /*unused*/, const detail::exception& /*unused*/)\n    {\n        return false;\n    }\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/input/lexer.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/is_sax.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstdint> // size_t\n#include <utility> // declval\n#include <string> // string\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/meta/detected.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename T>\nusing null_function_t = decltype(std::declval<T&>().null());\n\ntemplate<typename T>\nusing boolean_function_t =\n    decltype(std::declval<T&>().boolean(std::declval<bool>()));\n\ntemplate<typename T, typename Integer>\nusing number_integer_function_t =\n    decltype(std::declval<T&>().number_integer(std::declval<Integer>()));\n\ntemplate<typename T, typename Unsigned>\nusing number_unsigned_function_t =\n    decltype(std::declval<T&>().number_unsigned(std::declval<Unsigned>()));\n\ntemplate<typename T, typename Float, typename String>\nusing number_float_function_t = decltype(std::declval<T&>().number_float(\n                                    std::declval<Float>(), std::declval<const String&>()));\n\ntemplate<typename T, typename String>\nusing string_function_t =\n    decltype(std::declval<T&>().string(std::declval<String&>()));\n\ntemplate<typename T, typename Binary>\nusing binary_function_t =\n    decltype(std::declval<T&>().binary(std::declval<Binary&>()));\n\ntemplate<typename T>\nusing start_object_function_t =\n    decltype(std::declval<T&>().start_object(std::declval<std::size_t>()));\n\ntemplate<typename T, typename String>\nusing key_function_t =\n    decltype(std::declval<T&>().key(std::declval<String&>()));\n\ntemplate<typename T>\nusing end_object_function_t = decltype(std::declval<T&>().end_object());\n\ntemplate<typename T>\nusing start_array_function_t =\n    decltype(std::declval<T&>().start_array(std::declval<std::size_t>()));\n\ntemplate<typename T>\nusing end_array_function_t = decltype(std::declval<T&>().end_array());\n\ntemplate<typename T, typename Exception>\nusing parse_error_function_t = decltype(std::declval<T&>().parse_error(\n        std::declval<std::size_t>(), std::declval<const std::string&>(),\n        std::declval<const Exception&>()));\n\ntemplate<typename SAX, typename BasicJsonType>\nstruct is_sax\n{\n  private:\n    static_assert(is_basic_json<BasicJsonType>::value,\n                  \"BasicJsonType must be of type basic_json<...>\");\n\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n    using exception_t = typename BasicJsonType::exception;\n\n  public:\n    static constexpr bool value =\n        is_detected_exact<bool, null_function_t, SAX>::value &&\n        is_detected_exact<bool, boolean_function_t, SAX>::value &&\n        is_detected_exact<bool, number_integer_function_t, SAX, number_integer_t>::value &&\n        is_detected_exact<bool, number_unsigned_function_t, SAX, number_unsigned_t>::value &&\n        is_detected_exact<bool, number_float_function_t, SAX, number_float_t, string_t>::value &&\n        is_detected_exact<bool, string_function_t, SAX, string_t>::value &&\n        is_detected_exact<bool, binary_function_t, SAX, binary_t>::value &&\n        is_detected_exact<bool, start_object_function_t, SAX>::value &&\n        is_detected_exact<bool, key_function_t, SAX, string_t>::value &&\n        is_detected_exact<bool, end_object_function_t, SAX>::value &&\n        is_detected_exact<bool, start_array_function_t, SAX>::value &&\n        is_detected_exact<bool, end_array_function_t, SAX>::value &&\n        is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value;\n};\n\ntemplate<typename SAX, typename BasicJsonType>\nstruct is_sax_static_asserts\n{\n  private:\n    static_assert(is_basic_json<BasicJsonType>::value,\n                  \"BasicJsonType must be of type basic_json<...>\");\n\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n    using exception_t = typename BasicJsonType::exception;\n\n  public:\n    static_assert(is_detected_exact<bool, null_function_t, SAX>::value,\n                  \"Missing/invalid function: bool null()\");\n    static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,\n                  \"Missing/invalid function: bool boolean(bool)\");\n    static_assert(is_detected_exact<bool, boolean_function_t, SAX>::value,\n                  \"Missing/invalid function: bool boolean(bool)\");\n    static_assert(\n        is_detected_exact<bool, number_integer_function_t, SAX,\n        number_integer_t>::value,\n        \"Missing/invalid function: bool number_integer(number_integer_t)\");\n    static_assert(\n        is_detected_exact<bool, number_unsigned_function_t, SAX,\n        number_unsigned_t>::value,\n        \"Missing/invalid function: bool number_unsigned(number_unsigned_t)\");\n    static_assert(is_detected_exact<bool, number_float_function_t, SAX,\n                  number_float_t, string_t>::value,\n                  \"Missing/invalid function: bool number_float(number_float_t, const string_t&)\");\n    static_assert(\n        is_detected_exact<bool, string_function_t, SAX, string_t>::value,\n        \"Missing/invalid function: bool string(string_t&)\");\n    static_assert(\n        is_detected_exact<bool, binary_function_t, SAX, binary_t>::value,\n        \"Missing/invalid function: bool binary(binary_t&)\");\n    static_assert(is_detected_exact<bool, start_object_function_t, SAX>::value,\n                  \"Missing/invalid function: bool start_object(std::size_t)\");\n    static_assert(is_detected_exact<bool, key_function_t, SAX, string_t>::value,\n                  \"Missing/invalid function: bool key(string_t&)\");\n    static_assert(is_detected_exact<bool, end_object_function_t, SAX>::value,\n                  \"Missing/invalid function: bool end_object()\");\n    static_assert(is_detected_exact<bool, start_array_function_t, SAX>::value,\n                  \"Missing/invalid function: bool start_array(std::size_t)\");\n    static_assert(is_detected_exact<bool, end_array_function_t, SAX>::value,\n                  \"Missing/invalid function: bool end_array()\");\n    static_assert(\n        is_detected_exact<bool, parse_error_function_t, SAX, exception_t>::value,\n        \"Missing/invalid function: bool parse_error(std::size_t, const \"\n        \"std::string&, const exception&)\");\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/// how to treat CBOR tags\nenum class cbor_tag_handler_t\n{\n    error,   ///< throw a parse_error exception in case of a tag\n    ignore,  ///< ignore tags\n    store    ///< store tags as binary type\n};\n\n/*!\n@brief determine system byte order\n\n@return true if and only if system's byte order is little endian\n\n@note from https://stackoverflow.com/a/1001328/266378\n*/\nstatic inline bool little_endianness(int num = 1) noexcept\n{\n    return *reinterpret_cast<char*>(&num) == 1;\n}\n\n///////////////////\n// binary reader //\n///////////////////\n\n/*!\n@brief deserialization of CBOR, MessagePack, and UBJSON values\n*/\ntemplate<typename BasicJsonType, typename InputAdapterType, typename SAX = json_sax_dom_parser<BasicJsonType, InputAdapterType>>\nclass binary_reader\n{\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n    using json_sax_t = SAX;\n    using char_type = typename InputAdapterType::char_type;\n    using char_int_type = typename char_traits<char_type>::int_type;\n\n  public:\n    /*!\n    @brief create a binary reader\n\n    @param[in] adapter  input adapter to read from\n    */\n    explicit binary_reader(InputAdapterType&& adapter, const input_format_t format = input_format_t::json) noexcept : ia(std::move(adapter)), input_format(format)\n    {\n        (void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};\n    }\n\n    // make class move-only\n    binary_reader(const binary_reader&) = delete;\n    binary_reader(binary_reader&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    binary_reader& operator=(const binary_reader&) = delete;\n    binary_reader& operator=(binary_reader&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor)\n    ~binary_reader() = default;\n\n    /*!\n    @param[in] format  the binary format to parse\n    @param[in] sax_    a SAX event processor\n    @param[in] strict  whether to expect the input to be consumed completed\n    @param[in] tag_handler  how to treat CBOR tags\n\n    @return whether parsing was successful\n    */\n    JSON_HEDLEY_NON_NULL(3)\n    bool sax_parse(const input_format_t format,\n                   json_sax_t* sax_,\n                   const bool strict = true,\n                   const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)\n    {\n        sax = sax_;\n        bool result = false;\n\n        switch (format)\n        {\n            case input_format_t::bson:\n                result = parse_bson_internal();\n                break;\n\n            case input_format_t::cbor:\n                result = parse_cbor_internal(true, tag_handler);\n                break;\n\n            case input_format_t::msgpack:\n                result = parse_msgpack_internal();\n                break;\n\n            case input_format_t::ubjson:\n            case input_format_t::bjdata:\n                result = parse_ubjson_internal();\n                break;\n\n            case input_format_t::json: // LCOV_EXCL_LINE\n            default:            // LCOV_EXCL_LINE\n                JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n        }\n\n        // strict mode: next byte must be EOF\n        if (result && strict)\n        {\n            if (input_format == input_format_t::ubjson || input_format == input_format_t::bjdata)\n            {\n                get_ignore_noop();\n            }\n            else\n            {\n                get();\n            }\n\n            if (JSON_HEDLEY_UNLIKELY(current != char_traits<char_type>::eof()))\n            {\n                return sax->parse_error(chars_read, get_token_string(), parse_error::create(110, chars_read,\n                                        exception_message(input_format, concat(\"expected end of input; last byte: 0x\", get_token_string()), \"value\"), nullptr));\n            }\n        }\n\n        return result;\n    }\n\n  private:\n    //////////\n    // BSON //\n    //////////\n\n    /*!\n    @brief Reads in a BSON-object and passes it to the SAX-parser.\n    @return whether a valid BSON-value was passed to the SAX parser\n    */\n    bool parse_bson_internal()\n    {\n        std::int32_t document_size{};\n        get_number<std::int32_t, true>(input_format_t::bson, document_size);\n\n        if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size())))\n        {\n            return false;\n        }\n\n        if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/false)))\n        {\n            return false;\n        }\n\n        return sax->end_object();\n    }\n\n    /*!\n    @brief Parses a C-style string from the BSON input.\n    @param[in,out] result  A reference to the string variable where the read\n                            string is to be stored.\n    @return `true` if the \\x00-byte indicating the end of the string was\n             encountered before the EOF; false` indicates an unexpected EOF.\n    */\n    bool get_bson_cstr(string_t& result)\n    {\n        auto out = std::back_inserter(result);\n        while (true)\n        {\n            get();\n            if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, \"cstring\")))\n            {\n                return false;\n            }\n            if (current == 0x00)\n            {\n                return true;\n            }\n            *out++ = static_cast<typename string_t::value_type>(current);\n        }\n    }\n\n    /*!\n    @brief Parses a zero-terminated string of length @a len from the BSON\n           input.\n    @param[in] len  The length (including the zero-byte at the end) of the\n                    string to be read.\n    @param[in,out] result  A reference to the string variable where the read\n                            string is to be stored.\n    @tparam NumberType The type of the length @a len\n    @pre len >= 1\n    @return `true` if the string was successfully parsed\n    */\n    template<typename NumberType>\n    bool get_bson_string(const NumberType len, string_t& result)\n    {\n        if (JSON_HEDLEY_UNLIKELY(len < 1))\n        {\n            auto last_token = get_token_string();\n            return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                    exception_message(input_format_t::bson, concat(\"string length must be at least 1, is \", std::to_string(len)), \"string\"), nullptr));\n        }\n\n        return get_string(input_format_t::bson, len - static_cast<NumberType>(1), result) && get() != char_traits<char_type>::eof();\n    }\n\n    /*!\n    @brief Parses a byte array input of length @a len from the BSON input.\n    @param[in] len  The length of the byte array to be read.\n    @param[in,out] result  A reference to the binary variable where the read\n                            array is to be stored.\n    @tparam NumberType The type of the length @a len\n    @pre len >= 0\n    @return `true` if the byte array was successfully parsed\n    */\n    template<typename NumberType>\n    bool get_bson_binary(const NumberType len, binary_t& result)\n    {\n        if (JSON_HEDLEY_UNLIKELY(len < 0))\n        {\n            auto last_token = get_token_string();\n            return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                    exception_message(input_format_t::bson, concat(\"byte array length cannot be negative, is \", std::to_string(len)), \"binary\"), nullptr));\n        }\n\n        // All BSON binary values have a subtype\n        std::uint8_t subtype{};\n        get_number<std::uint8_t>(input_format_t::bson, subtype);\n        result.set_subtype(subtype);\n\n        return get_binary(input_format_t::bson, len, result);\n    }\n\n    /*!\n    @brief Read a BSON document element of the given @a element_type.\n    @param[in] element_type The BSON element type, c.f. http://bsonspec.org/spec.html\n    @param[in] element_type_parse_position The position in the input stream,\n               where the `element_type` was read.\n    @warning Not all BSON element types are supported yet. An unsupported\n             @a element_type will give rise to a parse_error.114:\n             Unsupported BSON record type 0x...\n    @return whether a valid BSON-object/array was passed to the SAX parser\n    */\n    bool parse_bson_element_internal(const char_int_type element_type,\n                                     const std::size_t element_type_parse_position)\n    {\n        switch (element_type)\n        {\n            case 0x01: // double\n            {\n                double number{};\n                return get_number<double, true>(input_format_t::bson, number) && sax->number_float(static_cast<number_float_t>(number), \"\");\n            }\n\n            case 0x02: // string\n            {\n                std::int32_t len{};\n                string_t value;\n                return get_number<std::int32_t, true>(input_format_t::bson, len) && get_bson_string(len, value) && sax->string(value);\n            }\n\n            case 0x03: // object\n            {\n                return parse_bson_internal();\n            }\n\n            case 0x04: // array\n            {\n                return parse_bson_array();\n            }\n\n            case 0x05: // binary\n            {\n                std::int32_t len{};\n                binary_t value;\n                return get_number<std::int32_t, true>(input_format_t::bson, len) && get_bson_binary(len, value) && sax->binary(value);\n            }\n\n            case 0x08: // boolean\n            {\n                return sax->boolean(get() != 0);\n            }\n\n            case 0x0A: // null\n            {\n                return sax->null();\n            }\n\n            case 0x10: // int32\n            {\n                std::int32_t value{};\n                return get_number<std::int32_t, true>(input_format_t::bson, value) && sax->number_integer(value);\n            }\n\n            case 0x12: // int64\n            {\n                std::int64_t value{};\n                return get_number<std::int64_t, true>(input_format_t::bson, value) && sax->number_integer(value);\n            }\n\n            case 0x11: // uint64\n            {\n                std::uint64_t value{};\n                return get_number<std::uint64_t, true>(input_format_t::bson, value) && sax->number_unsigned(value);\n            }\n\n            default: // anything else not supported (yet)\n            {\n                std::array<char, 3> cr{{}};\n                static_cast<void>((std::snprintf)(cr.data(), cr.size(), \"%.2hhX\", static_cast<unsigned char>(element_type))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n                const std::string cr_str{cr.data()};\n                return sax->parse_error(element_type_parse_position, cr_str,\n                                        parse_error::create(114, element_type_parse_position, concat(\"Unsupported BSON record type 0x\", cr_str), nullptr));\n            }\n        }\n    }\n\n    /*!\n    @brief Read a BSON element list (as specified in the BSON-spec)\n\n    The same binary layout is used for objects and arrays, hence it must be\n    indicated with the argument @a is_array which one is expected\n    (true --> array, false --> object).\n\n    @param[in] is_array Determines if the element list being read is to be\n                        treated as an object (@a is_array == false), or as an\n                        array (@a is_array == true).\n    @return whether a valid BSON-object/array was passed to the SAX parser\n    */\n    bool parse_bson_element_list(const bool is_array)\n    {\n        string_t key;\n\n        while (auto element_type = get())\n        {\n            if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::bson, \"element list\")))\n            {\n                return false;\n            }\n\n            const std::size_t element_type_parse_position = chars_read;\n            if (JSON_HEDLEY_UNLIKELY(!get_bson_cstr(key)))\n            {\n                return false;\n            }\n\n            if (!is_array && !sax->key(key))\n            {\n                return false;\n            }\n\n            if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_internal(element_type, element_type_parse_position)))\n            {\n                return false;\n            }\n\n            // get_bson_cstr only appends\n            key.clear();\n        }\n\n        return true;\n    }\n\n    /*!\n    @brief Reads an array from the BSON input and passes it to the SAX-parser.\n    @return whether a valid BSON-array was passed to the SAX parser\n    */\n    bool parse_bson_array()\n    {\n        std::int32_t document_size{};\n        get_number<std::int32_t, true>(input_format_t::bson, document_size);\n\n        if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size())))\n        {\n            return false;\n        }\n\n        if (JSON_HEDLEY_UNLIKELY(!parse_bson_element_list(/*is_array*/true)))\n        {\n            return false;\n        }\n\n        return sax->end_array();\n    }\n\n    //////////\n    // CBOR //\n    //////////\n\n    /*!\n    @param[in] get_char  whether a new character should be retrieved from the\n                         input (true) or whether the last read character should\n                         be considered instead (false)\n    @param[in] tag_handler how CBOR tags should be treated\n\n    @return whether a valid CBOR value was passed to the SAX parser\n    */\n    bool parse_cbor_internal(const bool get_char,\n                             const cbor_tag_handler_t tag_handler)\n    {\n        switch (get_char ? get() : current)\n        {\n            // EOF\n            case char_traits<char_type>::eof():\n                return unexpect_eof(input_format_t::cbor, \"value\");\n\n            // Integer 0x00..0x17 (0..23)\n            case 0x00:\n            case 0x01:\n            case 0x02:\n            case 0x03:\n            case 0x04:\n            case 0x05:\n            case 0x06:\n            case 0x07:\n            case 0x08:\n            case 0x09:\n            case 0x0A:\n            case 0x0B:\n            case 0x0C:\n            case 0x0D:\n            case 0x0E:\n            case 0x0F:\n            case 0x10:\n            case 0x11:\n            case 0x12:\n            case 0x13:\n            case 0x14:\n            case 0x15:\n            case 0x16:\n            case 0x17:\n                return sax->number_unsigned(static_cast<number_unsigned_t>(current));\n\n            case 0x18: // Unsigned integer (one-byte uint8_t follows)\n            {\n                std::uint8_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_unsigned(number);\n            }\n\n            case 0x19: // Unsigned integer (two-byte uint16_t follows)\n            {\n                std::uint16_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_unsigned(number);\n            }\n\n            case 0x1A: // Unsigned integer (four-byte uint32_t follows)\n            {\n                std::uint32_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_unsigned(number);\n            }\n\n            case 0x1B: // Unsigned integer (eight-byte uint64_t follows)\n            {\n                std::uint64_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_unsigned(number);\n            }\n\n            // Negative integer -1-0x00..-1-0x17 (-1..-24)\n            case 0x20:\n            case 0x21:\n            case 0x22:\n            case 0x23:\n            case 0x24:\n            case 0x25:\n            case 0x26:\n            case 0x27:\n            case 0x28:\n            case 0x29:\n            case 0x2A:\n            case 0x2B:\n            case 0x2C:\n            case 0x2D:\n            case 0x2E:\n            case 0x2F:\n            case 0x30:\n            case 0x31:\n            case 0x32:\n            case 0x33:\n            case 0x34:\n            case 0x35:\n            case 0x36:\n            case 0x37:\n                return sax->number_integer(static_cast<std::int8_t>(0x20 - 1 - current));\n\n            case 0x38: // Negative integer (one-byte uint8_t follows)\n            {\n                std::uint8_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number);\n            }\n\n            case 0x39: // Negative integer -1-n (two-byte uint16_t follows)\n            {\n                std::uint16_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number);\n            }\n\n            case 0x3A: // Negative integer -1-n (four-byte uint32_t follows)\n            {\n                std::uint32_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number);\n            }\n\n            case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows)\n            {\n                std::uint64_t number{};\n                return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1)\n                        - static_cast<number_integer_t>(number));\n            }\n\n            // Binary data (0x00..0x17 bytes follow)\n            case 0x40:\n            case 0x41:\n            case 0x42:\n            case 0x43:\n            case 0x44:\n            case 0x45:\n            case 0x46:\n            case 0x47:\n            case 0x48:\n            case 0x49:\n            case 0x4A:\n            case 0x4B:\n            case 0x4C:\n            case 0x4D:\n            case 0x4E:\n            case 0x4F:\n            case 0x50:\n            case 0x51:\n            case 0x52:\n            case 0x53:\n            case 0x54:\n            case 0x55:\n            case 0x56:\n            case 0x57:\n            case 0x58: // Binary data (one-byte uint8_t for n follows)\n            case 0x59: // Binary data (two-byte uint16_t for n follow)\n            case 0x5A: // Binary data (four-byte uint32_t for n follow)\n            case 0x5B: // Binary data (eight-byte uint64_t for n follow)\n            case 0x5F: // Binary data (indefinite length)\n            {\n                binary_t b;\n                return get_cbor_binary(b) && sax->binary(b);\n            }\n\n            // UTF-8 string (0x00..0x17 bytes follow)\n            case 0x60:\n            case 0x61:\n            case 0x62:\n            case 0x63:\n            case 0x64:\n            case 0x65:\n            case 0x66:\n            case 0x67:\n            case 0x68:\n            case 0x69:\n            case 0x6A:\n            case 0x6B:\n            case 0x6C:\n            case 0x6D:\n            case 0x6E:\n            case 0x6F:\n            case 0x70:\n            case 0x71:\n            case 0x72:\n            case 0x73:\n            case 0x74:\n            case 0x75:\n            case 0x76:\n            case 0x77:\n            case 0x78: // UTF-8 string (one-byte uint8_t for n follows)\n            case 0x79: // UTF-8 string (two-byte uint16_t for n follow)\n            case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)\n            case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)\n            case 0x7F: // UTF-8 string (indefinite length)\n            {\n                string_t s;\n                return get_cbor_string(s) && sax->string(s);\n            }\n\n            // array (0x00..0x17 data items follow)\n            case 0x80:\n            case 0x81:\n            case 0x82:\n            case 0x83:\n            case 0x84:\n            case 0x85:\n            case 0x86:\n            case 0x87:\n            case 0x88:\n            case 0x89:\n            case 0x8A:\n            case 0x8B:\n            case 0x8C:\n            case 0x8D:\n            case 0x8E:\n            case 0x8F:\n            case 0x90:\n            case 0x91:\n            case 0x92:\n            case 0x93:\n            case 0x94:\n            case 0x95:\n            case 0x96:\n            case 0x97:\n                return get_cbor_array(\n                           conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu), tag_handler);\n\n            case 0x98: // array (one-byte uint8_t for n follows)\n            {\n                std::uint8_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0x99: // array (two-byte uint16_t for n follow)\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_array(static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0x9A: // array (four-byte uint32_t for n follow)\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0x9B: // array (eight-byte uint64_t for n follow)\n            {\n                std::uint64_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_array(conditional_static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0x9F: // array (indefinite length)\n                return get_cbor_array(detail::unknown_size(), tag_handler);\n\n            // map (0x00..0x17 pairs of data items follow)\n            case 0xA0:\n            case 0xA1:\n            case 0xA2:\n            case 0xA3:\n            case 0xA4:\n            case 0xA5:\n            case 0xA6:\n            case 0xA7:\n            case 0xA8:\n            case 0xA9:\n            case 0xAA:\n            case 0xAB:\n            case 0xAC:\n            case 0xAD:\n            case 0xAE:\n            case 0xAF:\n            case 0xB0:\n            case 0xB1:\n            case 0xB2:\n            case 0xB3:\n            case 0xB4:\n            case 0xB5:\n            case 0xB6:\n            case 0xB7:\n                return get_cbor_object(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x1Fu), tag_handler);\n\n            case 0xB8: // map (one-byte uint8_t for n follows)\n            {\n                std::uint8_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0xB9: // map (two-byte uint16_t for n follow)\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_object(static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0xBA: // map (four-byte uint32_t for n follow)\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0xBB: // map (eight-byte uint64_t for n follow)\n            {\n                std::uint64_t len{};\n                return get_number(input_format_t::cbor, len) && get_cbor_object(conditional_static_cast<std::size_t>(len), tag_handler);\n            }\n\n            case 0xBF: // map (indefinite length)\n                return get_cbor_object(detail::unknown_size(), tag_handler);\n\n            case 0xC6: // tagged item\n            case 0xC7:\n            case 0xC8:\n            case 0xC9:\n            case 0xCA:\n            case 0xCB:\n            case 0xCC:\n            case 0xCD:\n            case 0xCE:\n            case 0xCF:\n            case 0xD0:\n            case 0xD1:\n            case 0xD2:\n            case 0xD3:\n            case 0xD4:\n            case 0xD8: // tagged item (1 bytes follow)\n            case 0xD9: // tagged item (2 bytes follow)\n            case 0xDA: // tagged item (4 bytes follow)\n            case 0xDB: // tagged item (8 bytes follow)\n            {\n                switch (tag_handler)\n                {\n                    case cbor_tag_handler_t::error:\n                    {\n                        auto last_token = get_token_string();\n                        return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                                exception_message(input_format_t::cbor, concat(\"invalid byte: 0x\", last_token), \"value\"), nullptr));\n                    }\n\n                    case cbor_tag_handler_t::ignore:\n                    {\n                        // ignore binary subtype\n                        switch (current)\n                        {\n                            case 0xD8:\n                            {\n                                std::uint8_t subtype_to_ignore{};\n                                get_number(input_format_t::cbor, subtype_to_ignore);\n                                break;\n                            }\n                            case 0xD9:\n                            {\n                                std::uint16_t subtype_to_ignore{};\n                                get_number(input_format_t::cbor, subtype_to_ignore);\n                                break;\n                            }\n                            case 0xDA:\n                            {\n                                std::uint32_t subtype_to_ignore{};\n                                get_number(input_format_t::cbor, subtype_to_ignore);\n                                break;\n                            }\n                            case 0xDB:\n                            {\n                                std::uint64_t subtype_to_ignore{};\n                                get_number(input_format_t::cbor, subtype_to_ignore);\n                                break;\n                            }\n                            default:\n                                break;\n                        }\n                        return parse_cbor_internal(true, tag_handler);\n                    }\n\n                    case cbor_tag_handler_t::store:\n                    {\n                        binary_t b;\n                        // use binary subtype and store in binary container\n                        switch (current)\n                        {\n                            case 0xD8:\n                            {\n                                std::uint8_t subtype{};\n                                get_number(input_format_t::cbor, subtype);\n                                b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));\n                                break;\n                            }\n                            case 0xD9:\n                            {\n                                std::uint16_t subtype{};\n                                get_number(input_format_t::cbor, subtype);\n                                b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));\n                                break;\n                            }\n                            case 0xDA:\n                            {\n                                std::uint32_t subtype{};\n                                get_number(input_format_t::cbor, subtype);\n                                b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));\n                                break;\n                            }\n                            case 0xDB:\n                            {\n                                std::uint64_t subtype{};\n                                get_number(input_format_t::cbor, subtype);\n                                b.set_subtype(detail::conditional_static_cast<typename binary_t::subtype_type>(subtype));\n                                break;\n                            }\n                            default:\n                                return parse_cbor_internal(true, tag_handler);\n                        }\n                        get();\n                        return get_cbor_binary(b) && sax->binary(b);\n                    }\n\n                    default:                 // LCOV_EXCL_LINE\n                        JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n                        return false;        // LCOV_EXCL_LINE\n                }\n            }\n\n            case 0xF4: // false\n                return sax->boolean(false);\n\n            case 0xF5: // true\n                return sax->boolean(true);\n\n            case 0xF6: // null\n                return sax->null();\n\n            case 0xF9: // Half-Precision Float (two-byte IEEE 754)\n            {\n                const auto byte1_raw = get();\n                if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, \"number\")))\n                {\n                    return false;\n                }\n                const auto byte2_raw = get();\n                if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, \"number\")))\n                {\n                    return false;\n                }\n\n                const auto byte1 = static_cast<unsigned char>(byte1_raw);\n                const auto byte2 = static_cast<unsigned char>(byte2_raw);\n\n                // code from RFC 7049, Appendix D, Figure 3:\n                // As half-precision floating-point numbers were only added\n                // to IEEE 754 in 2008, today's programming platforms often\n                // still only have limited support for them. It is very\n                // easy to include at least decoding support for them even\n                // without such support. An example of a small decoder for\n                // half-precision floating-point numbers in the C language\n                // is shown in Fig. 3.\n                const auto half = static_cast<unsigned int>((byte1 << 8u) + byte2);\n                const double val = [&half]\n                {\n                    const int exp = (half >> 10u) & 0x1Fu;\n                    const unsigned int mant = half & 0x3FFu;\n                    JSON_ASSERT(0 <= exp&& exp <= 32);\n                    JSON_ASSERT(mant <= 1024);\n                    switch (exp)\n                    {\n                        case 0:\n                            return std::ldexp(mant, -24);\n                        case 31:\n                            return (mant == 0)\n                            ? std::numeric_limits<double>::infinity()\n                            : std::numeric_limits<double>::quiet_NaN();\n                        default:\n                            return std::ldexp(mant + 1024, exp - 25);\n                    }\n                }();\n                return sax->number_float((half & 0x8000u) != 0\n                                         ? static_cast<number_float_t>(-val)\n                                         : static_cast<number_float_t>(val), \"\");\n            }\n\n            case 0xFA: // Single-Precision Float (four-byte IEEE 754)\n            {\n                float number{};\n                return get_number(input_format_t::cbor, number) && sax->number_float(static_cast<number_float_t>(number), \"\");\n            }\n\n            case 0xFB: // Double-Precision Float (eight-byte IEEE 754)\n            {\n                double number{};\n                return get_number(input_format_t::cbor, number) && sax->number_float(static_cast<number_float_t>(number), \"\");\n            }\n\n            default: // anything else (0xFF is handled inside the other types)\n            {\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                        exception_message(input_format_t::cbor, concat(\"invalid byte: 0x\", last_token), \"value\"), nullptr));\n            }\n        }\n    }\n\n    /*!\n    @brief reads a CBOR string\n\n    This function first reads starting bytes to determine the expected\n    string length and then copies this number of bytes into a string.\n    Additionally, CBOR's strings with indefinite lengths are supported.\n\n    @param[out] result  created string\n\n    @return whether string creation completed\n    */\n    bool get_cbor_string(string_t& result)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, \"string\")))\n        {\n            return false;\n        }\n\n        switch (current)\n        {\n            // UTF-8 string (0x00..0x17 bytes follow)\n            case 0x60:\n            case 0x61:\n            case 0x62:\n            case 0x63:\n            case 0x64:\n            case 0x65:\n            case 0x66:\n            case 0x67:\n            case 0x68:\n            case 0x69:\n            case 0x6A:\n            case 0x6B:\n            case 0x6C:\n            case 0x6D:\n            case 0x6E:\n            case 0x6F:\n            case 0x70:\n            case 0x71:\n            case 0x72:\n            case 0x73:\n            case 0x74:\n            case 0x75:\n            case 0x76:\n            case 0x77:\n            {\n                return get_string(input_format_t::cbor, static_cast<unsigned int>(current) & 0x1Fu, result);\n            }\n\n            case 0x78: // UTF-8 string (one-byte uint8_t for n follows)\n            {\n                std::uint8_t len{};\n                return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result);\n            }\n\n            case 0x79: // UTF-8 string (two-byte uint16_t for n follow)\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result);\n            }\n\n            case 0x7A: // UTF-8 string (four-byte uint32_t for n follow)\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result);\n            }\n\n            case 0x7B: // UTF-8 string (eight-byte uint64_t for n follow)\n            {\n                std::uint64_t len{};\n                return get_number(input_format_t::cbor, len) && get_string(input_format_t::cbor, len, result);\n            }\n\n            case 0x7F: // UTF-8 string (indefinite length)\n            {\n                while (get() != 0xFF)\n                {\n                    string_t chunk;\n                    if (!get_cbor_string(chunk))\n                    {\n                        return false;\n                    }\n                    result.append(chunk);\n                }\n                return true;\n            }\n\n            default:\n            {\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,\n                                        exception_message(input_format_t::cbor, concat(\"expected length specification (0x60-0x7B) or indefinite string type (0x7F); last byte: 0x\", last_token), \"string\"), nullptr));\n            }\n        }\n    }\n\n    /*!\n    @brief reads a CBOR byte array\n\n    This function first reads starting bytes to determine the expected\n    byte array length and then copies this number of bytes into the byte array.\n    Additionally, CBOR's byte arrays with indefinite lengths are supported.\n\n    @param[out] result  created byte array\n\n    @return whether byte array creation completed\n    */\n    bool get_cbor_binary(binary_t& result)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::cbor, \"binary\")))\n        {\n            return false;\n        }\n\n        switch (current)\n        {\n            // Binary data (0x00..0x17 bytes follow)\n            case 0x40:\n            case 0x41:\n            case 0x42:\n            case 0x43:\n            case 0x44:\n            case 0x45:\n            case 0x46:\n            case 0x47:\n            case 0x48:\n            case 0x49:\n            case 0x4A:\n            case 0x4B:\n            case 0x4C:\n            case 0x4D:\n            case 0x4E:\n            case 0x4F:\n            case 0x50:\n            case 0x51:\n            case 0x52:\n            case 0x53:\n            case 0x54:\n            case 0x55:\n            case 0x56:\n            case 0x57:\n            {\n                return get_binary(input_format_t::cbor, static_cast<unsigned int>(current) & 0x1Fu, result);\n            }\n\n            case 0x58: // Binary data (one-byte uint8_t for n follows)\n            {\n                std::uint8_t len{};\n                return get_number(input_format_t::cbor, len) &&\n                       get_binary(input_format_t::cbor, len, result);\n            }\n\n            case 0x59: // Binary data (two-byte uint16_t for n follow)\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::cbor, len) &&\n                       get_binary(input_format_t::cbor, len, result);\n            }\n\n            case 0x5A: // Binary data (four-byte uint32_t for n follow)\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::cbor, len) &&\n                       get_binary(input_format_t::cbor, len, result);\n            }\n\n            case 0x5B: // Binary data (eight-byte uint64_t for n follow)\n            {\n                std::uint64_t len{};\n                return get_number(input_format_t::cbor, len) &&\n                       get_binary(input_format_t::cbor, len, result);\n            }\n\n            case 0x5F: // Binary data (indefinite length)\n            {\n                while (get() != 0xFF)\n                {\n                    binary_t chunk;\n                    if (!get_cbor_binary(chunk))\n                    {\n                        return false;\n                    }\n                    result.insert(result.end(), chunk.begin(), chunk.end());\n                }\n                return true;\n            }\n\n            default:\n            {\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,\n                                        exception_message(input_format_t::cbor, concat(\"expected length specification (0x40-0x5B) or indefinite binary array type (0x5F); last byte: 0x\", last_token), \"binary\"), nullptr));\n            }\n        }\n    }\n\n    /*!\n    @param[in] len  the length of the array or detail::unknown_size() for an\n                    array of indefinite size\n    @param[in] tag_handler how CBOR tags should be treated\n    @return whether array creation completed\n    */\n    bool get_cbor_array(const std::size_t len,\n                        const cbor_tag_handler_t tag_handler)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len)))\n        {\n            return false;\n        }\n\n        if (len != detail::unknown_size())\n        {\n            for (std::size_t i = 0; i < len; ++i)\n            {\n                if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler)))\n                {\n                    return false;\n                }\n            }\n        }\n        else\n        {\n            while (get() != 0xFF)\n            {\n                if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(false, tag_handler)))\n                {\n                    return false;\n                }\n            }\n        }\n\n        return sax->end_array();\n    }\n\n    /*!\n    @param[in] len  the length of the object or detail::unknown_size() for an\n                    object of indefinite size\n    @param[in] tag_handler how CBOR tags should be treated\n    @return whether object creation completed\n    */\n    bool get_cbor_object(const std::size_t len,\n                         const cbor_tag_handler_t tag_handler)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len)))\n        {\n            return false;\n        }\n\n        if (len != 0)\n        {\n            string_t key;\n            if (len != detail::unknown_size())\n            {\n                for (std::size_t i = 0; i < len; ++i)\n                {\n                    get();\n                    if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key)))\n                    {\n                        return false;\n                    }\n\n                    if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler)))\n                    {\n                        return false;\n                    }\n                    key.clear();\n                }\n            }\n            else\n            {\n                while (get() != 0xFF)\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!get_cbor_string(key) || !sax->key(key)))\n                    {\n                        return false;\n                    }\n\n                    if (JSON_HEDLEY_UNLIKELY(!parse_cbor_internal(true, tag_handler)))\n                    {\n                        return false;\n                    }\n                    key.clear();\n                }\n            }\n        }\n\n        return sax->end_object();\n    }\n\n    /////////////\n    // MsgPack //\n    /////////////\n\n    /*!\n    @return whether a valid MessagePack value was passed to the SAX parser\n    */\n    bool parse_msgpack_internal()\n    {\n        switch (get())\n        {\n            // EOF\n            case char_traits<char_type>::eof():\n                return unexpect_eof(input_format_t::msgpack, \"value\");\n\n            // positive fixint\n            case 0x00:\n            case 0x01:\n            case 0x02:\n            case 0x03:\n            case 0x04:\n            case 0x05:\n            case 0x06:\n            case 0x07:\n            case 0x08:\n            case 0x09:\n            case 0x0A:\n            case 0x0B:\n            case 0x0C:\n            case 0x0D:\n            case 0x0E:\n            case 0x0F:\n            case 0x10:\n            case 0x11:\n            case 0x12:\n            case 0x13:\n            case 0x14:\n            case 0x15:\n            case 0x16:\n            case 0x17:\n            case 0x18:\n            case 0x19:\n            case 0x1A:\n            case 0x1B:\n            case 0x1C:\n            case 0x1D:\n            case 0x1E:\n            case 0x1F:\n            case 0x20:\n            case 0x21:\n            case 0x22:\n            case 0x23:\n            case 0x24:\n            case 0x25:\n            case 0x26:\n            case 0x27:\n            case 0x28:\n            case 0x29:\n            case 0x2A:\n            case 0x2B:\n            case 0x2C:\n            case 0x2D:\n            case 0x2E:\n            case 0x2F:\n            case 0x30:\n            case 0x31:\n            case 0x32:\n            case 0x33:\n            case 0x34:\n            case 0x35:\n            case 0x36:\n            case 0x37:\n            case 0x38:\n            case 0x39:\n            case 0x3A:\n            case 0x3B:\n            case 0x3C:\n            case 0x3D:\n            case 0x3E:\n            case 0x3F:\n            case 0x40:\n            case 0x41:\n            case 0x42:\n            case 0x43:\n            case 0x44:\n            case 0x45:\n            case 0x46:\n            case 0x47:\n            case 0x48:\n            case 0x49:\n            case 0x4A:\n            case 0x4B:\n            case 0x4C:\n            case 0x4D:\n            case 0x4E:\n            case 0x4F:\n            case 0x50:\n            case 0x51:\n            case 0x52:\n            case 0x53:\n            case 0x54:\n            case 0x55:\n            case 0x56:\n            case 0x57:\n            case 0x58:\n            case 0x59:\n            case 0x5A:\n            case 0x5B:\n            case 0x5C:\n            case 0x5D:\n            case 0x5E:\n            case 0x5F:\n            case 0x60:\n            case 0x61:\n            case 0x62:\n            case 0x63:\n            case 0x64:\n            case 0x65:\n            case 0x66:\n            case 0x67:\n            case 0x68:\n            case 0x69:\n            case 0x6A:\n            case 0x6B:\n            case 0x6C:\n            case 0x6D:\n            case 0x6E:\n            case 0x6F:\n            case 0x70:\n            case 0x71:\n            case 0x72:\n            case 0x73:\n            case 0x74:\n            case 0x75:\n            case 0x76:\n            case 0x77:\n            case 0x78:\n            case 0x79:\n            case 0x7A:\n            case 0x7B:\n            case 0x7C:\n            case 0x7D:\n            case 0x7E:\n            case 0x7F:\n                return sax->number_unsigned(static_cast<number_unsigned_t>(current));\n\n            // fixmap\n            case 0x80:\n            case 0x81:\n            case 0x82:\n            case 0x83:\n            case 0x84:\n            case 0x85:\n            case 0x86:\n            case 0x87:\n            case 0x88:\n            case 0x89:\n            case 0x8A:\n            case 0x8B:\n            case 0x8C:\n            case 0x8D:\n            case 0x8E:\n            case 0x8F:\n                return get_msgpack_object(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x0Fu));\n\n            // fixarray\n            case 0x90:\n            case 0x91:\n            case 0x92:\n            case 0x93:\n            case 0x94:\n            case 0x95:\n            case 0x96:\n            case 0x97:\n            case 0x98:\n            case 0x99:\n            case 0x9A:\n            case 0x9B:\n            case 0x9C:\n            case 0x9D:\n            case 0x9E:\n            case 0x9F:\n                return get_msgpack_array(conditional_static_cast<std::size_t>(static_cast<unsigned int>(current) & 0x0Fu));\n\n            // fixstr\n            case 0xA0:\n            case 0xA1:\n            case 0xA2:\n            case 0xA3:\n            case 0xA4:\n            case 0xA5:\n            case 0xA6:\n            case 0xA7:\n            case 0xA8:\n            case 0xA9:\n            case 0xAA:\n            case 0xAB:\n            case 0xAC:\n            case 0xAD:\n            case 0xAE:\n            case 0xAF:\n            case 0xB0:\n            case 0xB1:\n            case 0xB2:\n            case 0xB3:\n            case 0xB4:\n            case 0xB5:\n            case 0xB6:\n            case 0xB7:\n            case 0xB8:\n            case 0xB9:\n            case 0xBA:\n            case 0xBB:\n            case 0xBC:\n            case 0xBD:\n            case 0xBE:\n            case 0xBF:\n            case 0xD9: // str 8\n            case 0xDA: // str 16\n            case 0xDB: // str 32\n            {\n                string_t s;\n                return get_msgpack_string(s) && sax->string(s);\n            }\n\n            case 0xC0: // nil\n                return sax->null();\n\n            case 0xC2: // false\n                return sax->boolean(false);\n\n            case 0xC3: // true\n                return sax->boolean(true);\n\n            case 0xC4: // bin 8\n            case 0xC5: // bin 16\n            case 0xC6: // bin 32\n            case 0xC7: // ext 8\n            case 0xC8: // ext 16\n            case 0xC9: // ext 32\n            case 0xD4: // fixext 1\n            case 0xD5: // fixext 2\n            case 0xD6: // fixext 4\n            case 0xD7: // fixext 8\n            case 0xD8: // fixext 16\n            {\n                binary_t b;\n                return get_msgpack_binary(b) && sax->binary(b);\n            }\n\n            case 0xCA: // float 32\n            {\n                float number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast<number_float_t>(number), \"\");\n            }\n\n            case 0xCB: // float 64\n            {\n                double number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_float(static_cast<number_float_t>(number), \"\");\n            }\n\n            case 0xCC: // uint 8\n            {\n                std::uint8_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number);\n            }\n\n            case 0xCD: // uint 16\n            {\n                std::uint16_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number);\n            }\n\n            case 0xCE: // uint 32\n            {\n                std::uint32_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number);\n            }\n\n            case 0xCF: // uint 64\n            {\n                std::uint64_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_unsigned(number);\n            }\n\n            case 0xD0: // int 8\n            {\n                std::int8_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_integer(number);\n            }\n\n            case 0xD1: // int 16\n            {\n                std::int16_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_integer(number);\n            }\n\n            case 0xD2: // int 32\n            {\n                std::int32_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_integer(number);\n            }\n\n            case 0xD3: // int 64\n            {\n                std::int64_t number{};\n                return get_number(input_format_t::msgpack, number) && sax->number_integer(number);\n            }\n\n            case 0xDC: // array 16\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::msgpack, len) && get_msgpack_array(static_cast<std::size_t>(len));\n            }\n\n            case 0xDD: // array 32\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::msgpack, len) && get_msgpack_array(conditional_static_cast<std::size_t>(len));\n            }\n\n            case 0xDE: // map 16\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::msgpack, len) && get_msgpack_object(static_cast<std::size_t>(len));\n            }\n\n            case 0xDF: // map 32\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::msgpack, len) && get_msgpack_object(conditional_static_cast<std::size_t>(len));\n            }\n\n            // negative fixint\n            case 0xE0:\n            case 0xE1:\n            case 0xE2:\n            case 0xE3:\n            case 0xE4:\n            case 0xE5:\n            case 0xE6:\n            case 0xE7:\n            case 0xE8:\n            case 0xE9:\n            case 0xEA:\n            case 0xEB:\n            case 0xEC:\n            case 0xED:\n            case 0xEE:\n            case 0xEF:\n            case 0xF0:\n            case 0xF1:\n            case 0xF2:\n            case 0xF3:\n            case 0xF4:\n            case 0xF5:\n            case 0xF6:\n            case 0xF7:\n            case 0xF8:\n            case 0xF9:\n            case 0xFA:\n            case 0xFB:\n            case 0xFC:\n            case 0xFD:\n            case 0xFE:\n            case 0xFF:\n                return sax->number_integer(static_cast<std::int8_t>(current));\n\n            default: // anything else\n            {\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                        exception_message(input_format_t::msgpack, concat(\"invalid byte: 0x\", last_token), \"value\"), nullptr));\n            }\n        }\n    }\n\n    /*!\n    @brief reads a MessagePack string\n\n    This function first reads starting bytes to determine the expected\n    string length and then copies this number of bytes into a string.\n\n    @param[out] result  created string\n\n    @return whether string creation completed\n    */\n    bool get_msgpack_string(string_t& result)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format_t::msgpack, \"string\")))\n        {\n            return false;\n        }\n\n        switch (current)\n        {\n            // fixstr\n            case 0xA0:\n            case 0xA1:\n            case 0xA2:\n            case 0xA3:\n            case 0xA4:\n            case 0xA5:\n            case 0xA6:\n            case 0xA7:\n            case 0xA8:\n            case 0xA9:\n            case 0xAA:\n            case 0xAB:\n            case 0xAC:\n            case 0xAD:\n            case 0xAE:\n            case 0xAF:\n            case 0xB0:\n            case 0xB1:\n            case 0xB2:\n            case 0xB3:\n            case 0xB4:\n            case 0xB5:\n            case 0xB6:\n            case 0xB7:\n            case 0xB8:\n            case 0xB9:\n            case 0xBA:\n            case 0xBB:\n            case 0xBC:\n            case 0xBD:\n            case 0xBE:\n            case 0xBF:\n            {\n                return get_string(input_format_t::msgpack, static_cast<unsigned int>(current) & 0x1Fu, result);\n            }\n\n            case 0xD9: // str 8\n            {\n                std::uint8_t len{};\n                return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result);\n            }\n\n            case 0xDA: // str 16\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result);\n            }\n\n            case 0xDB: // str 32\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::msgpack, len) && get_string(input_format_t::msgpack, len, result);\n            }\n\n            default:\n            {\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,\n                                        exception_message(input_format_t::msgpack, concat(\"expected length specification (0xA0-0xBF, 0xD9-0xDB); last byte: 0x\", last_token), \"string\"), nullptr));\n            }\n        }\n    }\n\n    /*!\n    @brief reads a MessagePack byte array\n\n    This function first reads starting bytes to determine the expected\n    byte array length and then copies this number of bytes into a byte array.\n\n    @param[out] result  created byte array\n\n    @return whether byte array creation completed\n    */\n    bool get_msgpack_binary(binary_t& result)\n    {\n        // helper function to set the subtype\n        auto assign_and_return_true = [&result](std::int8_t subtype)\n        {\n            result.set_subtype(static_cast<std::uint8_t>(subtype));\n            return true;\n        };\n\n        switch (current)\n        {\n            case 0xC4: // bin 8\n            {\n                std::uint8_t len{};\n                return get_number(input_format_t::msgpack, len) &&\n                       get_binary(input_format_t::msgpack, len, result);\n            }\n\n            case 0xC5: // bin 16\n            {\n                std::uint16_t len{};\n                return get_number(input_format_t::msgpack, len) &&\n                       get_binary(input_format_t::msgpack, len, result);\n            }\n\n            case 0xC6: // bin 32\n            {\n                std::uint32_t len{};\n                return get_number(input_format_t::msgpack, len) &&\n                       get_binary(input_format_t::msgpack, len, result);\n            }\n\n            case 0xC7: // ext 8\n            {\n                std::uint8_t len{};\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, len) &&\n                       get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, len, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            case 0xC8: // ext 16\n            {\n                std::uint16_t len{};\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, len) &&\n                       get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, len, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            case 0xC9: // ext 32\n            {\n                std::uint32_t len{};\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, len) &&\n                       get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, len, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            case 0xD4: // fixext 1\n            {\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, 1, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            case 0xD5: // fixext 2\n            {\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, 2, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            case 0xD6: // fixext 4\n            {\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, 4, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            case 0xD7: // fixext 8\n            {\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, 8, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            case 0xD8: // fixext 16\n            {\n                std::int8_t subtype{};\n                return get_number(input_format_t::msgpack, subtype) &&\n                       get_binary(input_format_t::msgpack, 16, result) &&\n                       assign_and_return_true(subtype);\n            }\n\n            default:           // LCOV_EXCL_LINE\n                return false;  // LCOV_EXCL_LINE\n        }\n    }\n\n    /*!\n    @param[in] len  the length of the array\n    @return whether array creation completed\n    */\n    bool get_msgpack_array(const std::size_t len)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!sax->start_array(len)))\n        {\n            return false;\n        }\n\n        for (std::size_t i = 0; i < len; ++i)\n        {\n            if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal()))\n            {\n                return false;\n            }\n        }\n\n        return sax->end_array();\n    }\n\n    /*!\n    @param[in] len  the length of the object\n    @return whether object creation completed\n    */\n    bool get_msgpack_object(const std::size_t len)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!sax->start_object(len)))\n        {\n            return false;\n        }\n\n        string_t key;\n        for (std::size_t i = 0; i < len; ++i)\n        {\n            get();\n            if (JSON_HEDLEY_UNLIKELY(!get_msgpack_string(key) || !sax->key(key)))\n            {\n                return false;\n            }\n\n            if (JSON_HEDLEY_UNLIKELY(!parse_msgpack_internal()))\n            {\n                return false;\n            }\n            key.clear();\n        }\n\n        return sax->end_object();\n    }\n\n    ////////////\n    // UBJSON //\n    ////////////\n\n    /*!\n    @param[in] get_char  whether a new character should be retrieved from the\n                         input (true, default) or whether the last read\n                         character should be considered instead\n\n    @return whether a valid UBJSON value was passed to the SAX parser\n    */\n    bool parse_ubjson_internal(const bool get_char = true)\n    {\n        return get_ubjson_value(get_char ? get_ignore_noop() : current);\n    }\n\n    /*!\n    @brief reads a UBJSON string\n\n    This function is either called after reading the 'S' byte explicitly\n    indicating a string, or in case of an object key where the 'S' byte can be\n    left out.\n\n    @param[out] result   created string\n    @param[in] get_char  whether a new character should be retrieved from the\n                         input (true, default) or whether the last read\n                         character should be considered instead\n\n    @return whether string creation completed\n    */\n    bool get_ubjson_string(string_t& result, const bool get_char = true)\n    {\n        if (get_char)\n        {\n            get();  // TODO(niels): may we ignore N here?\n        }\n\n        if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"value\")))\n        {\n            return false;\n        }\n\n        switch (current)\n        {\n            case 'U':\n            {\n                std::uint8_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            case 'i':\n            {\n                std::int8_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            case 'I':\n            {\n                std::int16_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            case 'l':\n            {\n                std::int32_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            case 'L':\n            {\n                std::int64_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            case 'u':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint16_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            case 'm':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint32_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            case 'M':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint64_t len{};\n                return get_number(input_format, len) && get_string(input_format, len, result);\n            }\n\n            default:\n                break;\n        }\n        auto last_token = get_token_string();\n        std::string message;\n\n        if (input_format != input_format_t::bjdata)\n        {\n            message = \"expected length type specification (U, i, I, l, L); last byte: 0x\" + last_token;\n        }\n        else\n        {\n            message = \"expected length type specification (U, i, u, I, m, l, M, L); last byte: 0x\" + last_token;\n        }\n        return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, \"string\"), nullptr));\n    }\n\n    /*!\n    @param[out] dim  an integer vector storing the ND array dimensions\n    @return whether reading ND array size vector is successful\n    */\n    bool get_ubjson_ndarray_size(std::vector<size_t>& dim)\n    {\n        std::pair<std::size_t, char_int_type> size_and_type;\n        size_t dimlen = 0;\n        bool no_ndarray = true;\n\n        if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type, no_ndarray)))\n        {\n            return false;\n        }\n\n        if (size_and_type.first != npos)\n        {\n            if (size_and_type.second != 0)\n            {\n                if (size_and_type.second != 'N')\n                {\n                    for (std::size_t i = 0; i < size_and_type.first; ++i)\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, size_and_type.second)))\n                        {\n                            return false;\n                        }\n                        dim.push_back(dimlen);\n                    }\n                }\n            }\n            else\n            {\n                for (std::size_t i = 0; i < size_and_type.first; ++i)\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray)))\n                    {\n                        return false;\n                    }\n                    dim.push_back(dimlen);\n                }\n            }\n        }\n        else\n        {\n            while (current != ']')\n            {\n                if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_value(dimlen, no_ndarray, current)))\n                {\n                    return false;\n                }\n                dim.push_back(dimlen);\n                get_ignore_noop();\n            }\n        }\n        return true;\n    }\n\n    /*!\n    @param[out] result  determined size\n    @param[in,out] is_ndarray  for input, `true` means already inside an ndarray vector\n                               or ndarray dimension is not allowed; `false` means ndarray\n                               is allowed; for output, `true` means an ndarray is found;\n                               is_ndarray can only return `true` when its initial value\n                               is `false`\n    @param[in] prefix  type marker if already read, otherwise set to 0\n\n    @return whether size determination completed\n    */\n    bool get_ubjson_size_value(std::size_t& result, bool& is_ndarray, char_int_type prefix = 0)\n    {\n        if (prefix == 0)\n        {\n            prefix = get_ignore_noop();\n        }\n\n        switch (prefix)\n        {\n            case 'U':\n            {\n                std::uint8_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                result = static_cast<std::size_t>(number);\n                return true;\n            }\n\n            case 'i':\n            {\n                std::int8_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                if (number < 0)\n                {\n                    return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read,\n                                            exception_message(input_format, \"count in an optimized container must be positive\", \"size\"), nullptr));\n                }\n                result = static_cast<std::size_t>(number); // NOLINT(bugprone-signed-char-misuse,cert-str34-c): number is not a char\n                return true;\n            }\n\n            case 'I':\n            {\n                std::int16_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                if (number < 0)\n                {\n                    return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read,\n                                            exception_message(input_format, \"count in an optimized container must be positive\", \"size\"), nullptr));\n                }\n                result = static_cast<std::size_t>(number);\n                return true;\n            }\n\n            case 'l':\n            {\n                std::int32_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                if (number < 0)\n                {\n                    return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read,\n                                            exception_message(input_format, \"count in an optimized container must be positive\", \"size\"), nullptr));\n                }\n                result = static_cast<std::size_t>(number);\n                return true;\n            }\n\n            case 'L':\n            {\n                std::int64_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                if (number < 0)\n                {\n                    return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read,\n                                            exception_message(input_format, \"count in an optimized container must be positive\", \"size\"), nullptr));\n                }\n                if (!value_in_range_of<std::size_t>(number))\n                {\n                    return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408,\n                                            exception_message(input_format, \"integer value overflow\", \"size\"), nullptr));\n                }\n                result = static_cast<std::size_t>(number);\n                return true;\n            }\n\n            case 'u':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint16_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                result = static_cast<std::size_t>(number);\n                return true;\n            }\n\n            case 'm':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint32_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                result = conditional_static_cast<std::size_t>(number);\n                return true;\n            }\n\n            case 'M':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint64_t number{};\n                if (JSON_HEDLEY_UNLIKELY(!get_number(input_format, number)))\n                {\n                    return false;\n                }\n                if (!value_in_range_of<std::size_t>(number))\n                {\n                    return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408,\n                                            exception_message(input_format, \"integer value overflow\", \"size\"), nullptr));\n                }\n                result = detail::conditional_static_cast<std::size_t>(number);\n                return true;\n            }\n\n            case '[':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                if (is_ndarray) // ndarray dimensional vector can only contain integers, and can not embed another array\n                {\n                    return sax->parse_error(chars_read, get_token_string(), parse_error::create(113, chars_read, exception_message(input_format, \"ndarray dimensional vector is not allowed\", \"size\"), nullptr));\n                }\n                std::vector<size_t> dim;\n                if (JSON_HEDLEY_UNLIKELY(!get_ubjson_ndarray_size(dim)))\n                {\n                    return false;\n                }\n                if (dim.size() == 1 || (dim.size() == 2 && dim.at(0) == 1)) // return normal array size if 1D row vector\n                {\n                    result = dim.at(dim.size() - 1);\n                    return true;\n                }\n                if (!dim.empty())  // if ndarray, convert to an object in JData annotated array format\n                {\n                    for (auto i : dim) // test if any dimension in an ndarray is 0, if so, return a 1D empty container\n                    {\n                        if ( i == 0 )\n                        {\n                            result = 0;\n                            return true;\n                        }\n                    }\n\n                    string_t key = \"_ArraySize_\";\n                    if (JSON_HEDLEY_UNLIKELY(!sax->start_object(3) || !sax->key(key) || !sax->start_array(dim.size())))\n                    {\n                        return false;\n                    }\n                    result = 1;\n                    for (auto i : dim)\n                    {\n                        result *= i;\n                        if (result == 0 || result == npos) // because dim elements shall not have zeros, result = 0 means overflow happened; it also can't be npos as it is used to initialize size in get_ubjson_size_type()\n                        {\n                            return sax->parse_error(chars_read, get_token_string(), out_of_range::create(408, exception_message(input_format, \"excessive ndarray size caused overflow\", \"size\"), nullptr));\n                        }\n                        if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(static_cast<number_unsigned_t>(i))))\n                        {\n                            return false;\n                        }\n                    }\n                    is_ndarray = true;\n                    return sax->end_array();\n                }\n                result = 0;\n                return true;\n            }\n\n            default:\n                break;\n        }\n        auto last_token = get_token_string();\n        std::string message;\n\n        if (input_format != input_format_t::bjdata)\n        {\n            message = \"expected length type specification (U, i, I, l, L) after '#'; last byte: 0x\" + last_token;\n        }\n        else\n        {\n            message = \"expected length type specification (U, i, u, I, m, l, M, L) after '#'; last byte: 0x\" + last_token;\n        }\n        return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read, exception_message(input_format, message, \"size\"), nullptr));\n    }\n\n    /*!\n    @brief determine the type and size for a container\n\n    In the optimized UBJSON format, a type and a size can be provided to allow\n    for a more compact representation.\n\n    @param[out] result  pair of the size and the type\n    @param[in] inside_ndarray  whether the parser is parsing an ND array dimensional vector\n\n    @return whether pair creation completed\n    */\n    bool get_ubjson_size_type(std::pair<std::size_t, char_int_type>& result, bool inside_ndarray = false)\n    {\n        result.first = npos; // size\n        result.second = 0; // type\n        bool is_ndarray = false;\n\n        get_ignore_noop();\n\n        if (current == '$')\n        {\n            result.second = get();  // must not ignore 'N', because 'N' maybe the type\n            if (input_format == input_format_t::bjdata\n                    && JSON_HEDLEY_UNLIKELY(std::binary_search(bjd_optimized_type_markers.begin(), bjd_optimized_type_markers.end(), result.second)))\n            {\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                        exception_message(input_format, concat(\"marker 0x\", last_token, \" is not a permitted optimized array type\"), \"type\"), nullptr));\n            }\n\n            if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"type\")))\n            {\n                return false;\n            }\n\n            get_ignore_noop();\n            if (JSON_HEDLEY_UNLIKELY(current != '#'))\n            {\n                if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"value\")))\n                {\n                    return false;\n                }\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                        exception_message(input_format, concat(\"expected '#' after type information; last byte: 0x\", last_token), \"size\"), nullptr));\n            }\n\n            const bool is_error = get_ubjson_size_value(result.first, is_ndarray);\n            if (input_format == input_format_t::bjdata && is_ndarray)\n            {\n                if (inside_ndarray)\n                {\n                    return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read,\n                                            exception_message(input_format, \"ndarray can not be recursive\", \"size\"), nullptr));\n                }\n                result.second |= (1 << 8); // use bit 8 to indicate ndarray, all UBJSON and BJData markers should be ASCII letters\n            }\n            return is_error;\n        }\n\n        if (current == '#')\n        {\n            const bool is_error = get_ubjson_size_value(result.first, is_ndarray);\n            if (input_format == input_format_t::bjdata && is_ndarray)\n            {\n                return sax->parse_error(chars_read, get_token_string(), parse_error::create(112, chars_read,\n                                        exception_message(input_format, \"ndarray requires both type and size\", \"size\"), nullptr));\n            }\n            return is_error;\n        }\n\n        return true;\n    }\n\n    /*!\n    @param prefix  the previously read or set type prefix\n    @return whether value creation completed\n    */\n    bool get_ubjson_value(const char_int_type prefix)\n    {\n        switch (prefix)\n        {\n            case char_traits<char_type>::eof():  // EOF\n                return unexpect_eof(input_format, \"value\");\n\n            case 'T':  // true\n                return sax->boolean(true);\n            case 'F':  // false\n                return sax->boolean(false);\n\n            case 'Z':  // null\n                return sax->null();\n\n            case 'B':  // byte\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint8_t number{};\n                return get_number(input_format, number) && sax->number_unsigned(number);\n            }\n\n            case 'U':\n            {\n                std::uint8_t number{};\n                return get_number(input_format, number) && sax->number_unsigned(number);\n            }\n\n            case 'i':\n            {\n                std::int8_t number{};\n                return get_number(input_format, number) && sax->number_integer(number);\n            }\n\n            case 'I':\n            {\n                std::int16_t number{};\n                return get_number(input_format, number) && sax->number_integer(number);\n            }\n\n            case 'l':\n            {\n                std::int32_t number{};\n                return get_number(input_format, number) && sax->number_integer(number);\n            }\n\n            case 'L':\n            {\n                std::int64_t number{};\n                return get_number(input_format, number) && sax->number_integer(number);\n            }\n\n            case 'u':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint16_t number{};\n                return get_number(input_format, number) && sax->number_unsigned(number);\n            }\n\n            case 'm':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint32_t number{};\n                return get_number(input_format, number) && sax->number_unsigned(number);\n            }\n\n            case 'M':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                std::uint64_t number{};\n                return get_number(input_format, number) && sax->number_unsigned(number);\n            }\n\n            case 'h':\n            {\n                if (input_format != input_format_t::bjdata)\n                {\n                    break;\n                }\n                const auto byte1_raw = get();\n                if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"number\")))\n                {\n                    return false;\n                }\n                const auto byte2_raw = get();\n                if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"number\")))\n                {\n                    return false;\n                }\n\n                const auto byte1 = static_cast<unsigned char>(byte1_raw);\n                const auto byte2 = static_cast<unsigned char>(byte2_raw);\n\n                // code from RFC 7049, Appendix D, Figure 3:\n                // As half-precision floating-point numbers were only added\n                // to IEEE 754 in 2008, today's programming platforms often\n                // still only have limited support for them. It is very\n                // easy to include at least decoding support for them even\n                // without such support. An example of a small decoder for\n                // half-precision floating-point numbers in the C language\n                // is shown in Fig. 3.\n                const auto half = static_cast<unsigned int>((byte2 << 8u) + byte1);\n                const double val = [&half]\n                {\n                    const int exp = (half >> 10u) & 0x1Fu;\n                    const unsigned int mant = half & 0x3FFu;\n                    JSON_ASSERT(0 <= exp&& exp <= 32);\n                    JSON_ASSERT(mant <= 1024);\n                    switch (exp)\n                    {\n                        case 0:\n                            return std::ldexp(mant, -24);\n                        case 31:\n                            return (mant == 0)\n                            ? std::numeric_limits<double>::infinity()\n                            : std::numeric_limits<double>::quiet_NaN();\n                        default:\n                            return std::ldexp(mant + 1024, exp - 25);\n                    }\n                }();\n                return sax->number_float((half & 0x8000u) != 0\n                                         ? static_cast<number_float_t>(-val)\n                                         : static_cast<number_float_t>(val), \"\");\n            }\n\n            case 'd':\n            {\n                float number{};\n                return get_number(input_format, number) && sax->number_float(static_cast<number_float_t>(number), \"\");\n            }\n\n            case 'D':\n            {\n                double number{};\n                return get_number(input_format, number) && sax->number_float(static_cast<number_float_t>(number), \"\");\n            }\n\n            case 'H':\n            {\n                return get_ubjson_high_precision_number();\n            }\n\n            case 'C':  // char\n            {\n                get();\n                if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"char\")))\n                {\n                    return false;\n                }\n                if (JSON_HEDLEY_UNLIKELY(current > 127))\n                {\n                    auto last_token = get_token_string();\n                    return sax->parse_error(chars_read, last_token, parse_error::create(113, chars_read,\n                                            exception_message(input_format, concat(\"byte after 'C' must be in range 0x00..0x7F; last byte: 0x\", last_token), \"char\"), nullptr));\n                }\n                string_t s(1, static_cast<typename string_t::value_type>(current));\n                return sax->string(s);\n            }\n\n            case 'S':  // string\n            {\n                string_t s;\n                return get_ubjson_string(s) && sax->string(s);\n            }\n\n            case '[':  // array\n                return get_ubjson_array();\n\n            case '{':  // object\n                return get_ubjson_object();\n\n            default: // anything else\n                break;\n        }\n        auto last_token = get_token_string();\n        return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read, exception_message(input_format, \"invalid byte: 0x\" + last_token, \"value\"), nullptr));\n    }\n\n    /*!\n    @return whether array creation completed\n    */\n    bool get_ubjson_array()\n    {\n        std::pair<std::size_t, char_int_type> size_and_type;\n        if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type)))\n        {\n            return false;\n        }\n\n        // if bit-8 of size_and_type.second is set to 1, encode bjdata ndarray as an object in JData annotated array format (https://github.com/NeuroJSON/jdata):\n        // {\"_ArrayType_\" : \"typeid\", \"_ArraySize_\" : [n1, n2, ...], \"_ArrayData_\" : [v1, v2, ...]}\n\n        if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)\n        {\n            size_and_type.second &= ~(static_cast<char_int_type>(1) << 8);  // use bit 8 to indicate ndarray, here we remove the bit to restore the type marker\n            auto it = std::lower_bound(bjd_types_map.begin(), bjd_types_map.end(), size_and_type.second, [](const bjd_type & p, char_int_type t)\n            {\n                return p.first < t;\n            });\n            string_t key = \"_ArrayType_\";\n            if (JSON_HEDLEY_UNLIKELY(it == bjd_types_map.end() || it->first != size_and_type.second))\n            {\n                auto last_token = get_token_string();\n                return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                        exception_message(input_format, \"invalid byte: 0x\" + last_token, \"type\"), nullptr));\n            }\n\n            string_t type = it->second; // sax->string() takes a reference\n            if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->string(type)))\n            {\n                return false;\n            }\n\n            if (size_and_type.second == 'C' || size_and_type.second == 'B')\n            {\n                size_and_type.second = 'U';\n            }\n\n            key = \"_ArrayData_\";\n            if (JSON_HEDLEY_UNLIKELY(!sax->key(key) || !sax->start_array(size_and_type.first) ))\n            {\n                return false;\n            }\n\n            for (std::size_t i = 0; i < size_and_type.first; ++i)\n            {\n                if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second)))\n                {\n                    return false;\n                }\n            }\n\n            return (sax->end_array() && sax->end_object());\n        }\n\n        // If BJData type marker is 'B' decode as binary\n        if (input_format == input_format_t::bjdata && size_and_type.first != npos && size_and_type.second == 'B')\n        {\n            binary_t result;\n            return get_binary(input_format, size_and_type.first, result) && sax->binary(result);\n        }\n\n        if (size_and_type.first != npos)\n        {\n            if (JSON_HEDLEY_UNLIKELY(!sax->start_array(size_and_type.first)))\n            {\n                return false;\n            }\n\n            if (size_and_type.second != 0)\n            {\n                if (size_and_type.second != 'N')\n                {\n                    for (std::size_t i = 0; i < size_and_type.first; ++i)\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second)))\n                        {\n                            return false;\n                        }\n                    }\n                }\n            }\n            else\n            {\n                for (std::size_t i = 0; i < size_and_type.first; ++i)\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal()))\n                    {\n                        return false;\n                    }\n                }\n            }\n        }\n        else\n        {\n            if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size())))\n            {\n                return false;\n            }\n\n            while (current != ']')\n            {\n                if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal(false)))\n                {\n                    return false;\n                }\n                get_ignore_noop();\n            }\n        }\n\n        return sax->end_array();\n    }\n\n    /*!\n    @return whether object creation completed\n    */\n    bool get_ubjson_object()\n    {\n        std::pair<std::size_t, char_int_type> size_and_type;\n        if (JSON_HEDLEY_UNLIKELY(!get_ubjson_size_type(size_and_type)))\n        {\n            return false;\n        }\n\n        // do not accept ND-array size in objects in BJData\n        if (input_format == input_format_t::bjdata && size_and_type.first != npos && (size_and_type.second & (1 << 8)) != 0)\n        {\n            auto last_token = get_token_string();\n            return sax->parse_error(chars_read, last_token, parse_error::create(112, chars_read,\n                                    exception_message(input_format, \"BJData object does not support ND-array size in optimized format\", \"object\"), nullptr));\n        }\n\n        string_t key;\n        if (size_and_type.first != npos)\n        {\n            if (JSON_HEDLEY_UNLIKELY(!sax->start_object(size_and_type.first)))\n            {\n                return false;\n            }\n\n            if (size_and_type.second != 0)\n            {\n                for (std::size_t i = 0; i < size_and_type.first; ++i)\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key)))\n                    {\n                        return false;\n                    }\n                    if (JSON_HEDLEY_UNLIKELY(!get_ubjson_value(size_and_type.second)))\n                    {\n                        return false;\n                    }\n                    key.clear();\n                }\n            }\n            else\n            {\n                for (std::size_t i = 0; i < size_and_type.first; ++i)\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key) || !sax->key(key)))\n                    {\n                        return false;\n                    }\n                    if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal()))\n                    {\n                        return false;\n                    }\n                    key.clear();\n                }\n            }\n        }\n        else\n        {\n            if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size())))\n            {\n                return false;\n            }\n\n            while (current != '}')\n            {\n                if (JSON_HEDLEY_UNLIKELY(!get_ubjson_string(key, false) || !sax->key(key)))\n                {\n                    return false;\n                }\n                if (JSON_HEDLEY_UNLIKELY(!parse_ubjson_internal()))\n                {\n                    return false;\n                }\n                get_ignore_noop();\n                key.clear();\n            }\n        }\n\n        return sax->end_object();\n    }\n\n    // Note, no reader for UBJSON binary types is implemented because they do\n    // not exist\n\n    bool get_ubjson_high_precision_number()\n    {\n        // get size of following number string\n        std::size_t size{};\n        bool no_ndarray = true;\n        auto res = get_ubjson_size_value(size, no_ndarray);\n        if (JSON_HEDLEY_UNLIKELY(!res))\n        {\n            return res;\n        }\n\n        // get number string\n        std::vector<char> number_vector;\n        for (std::size_t i = 0; i < size; ++i)\n        {\n            get();\n            if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(input_format, \"number\")))\n            {\n                return false;\n            }\n            number_vector.push_back(static_cast<char>(current));\n        }\n\n        // parse number string\n        using ia_type = decltype(detail::input_adapter(number_vector));\n        auto number_lexer = detail::lexer<BasicJsonType, ia_type>(detail::input_adapter(number_vector), false);\n        const auto result_number = number_lexer.scan();\n        const auto number_string = number_lexer.get_token_string();\n        const auto result_remainder = number_lexer.scan();\n\n        using token_type = typename detail::lexer_base<BasicJsonType>::token_type;\n\n        if (JSON_HEDLEY_UNLIKELY(result_remainder != token_type::end_of_input))\n        {\n            return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read,\n                                    exception_message(input_format, concat(\"invalid number text: \", number_lexer.get_token_string()), \"high-precision number\"), nullptr));\n        }\n\n        switch (result_number)\n        {\n            case token_type::value_integer:\n                return sax->number_integer(number_lexer.get_number_integer());\n            case token_type::value_unsigned:\n                return sax->number_unsigned(number_lexer.get_number_unsigned());\n            case token_type::value_float:\n                return sax->number_float(number_lexer.get_number_float(), std::move(number_string));\n            case token_type::uninitialized:\n            case token_type::literal_true:\n            case token_type::literal_false:\n            case token_type::literal_null:\n            case token_type::value_string:\n            case token_type::begin_array:\n            case token_type::begin_object:\n            case token_type::end_array:\n            case token_type::end_object:\n            case token_type::name_separator:\n            case token_type::value_separator:\n            case token_type::parse_error:\n            case token_type::end_of_input:\n            case token_type::literal_or_value:\n            default:\n                return sax->parse_error(chars_read, number_string, parse_error::create(115, chars_read,\n                                        exception_message(input_format, concat(\"invalid number text: \", number_lexer.get_token_string()), \"high-precision number\"), nullptr));\n        }\n    }\n\n    ///////////////////////\n    // Utility functions //\n    ///////////////////////\n\n    /*!\n    @brief get next character from the input\n\n    This function provides the interface to the used input adapter. It does\n    not throw in case the input reached EOF, but returns a -'ve valued\n    `char_traits<char_type>::eof()` in that case.\n\n    @return character read from the input\n    */\n    char_int_type get()\n    {\n        ++chars_read;\n        return current = ia.get_character();\n    }\n\n    /*!\n    @brief get_to read into a primitive type\n\n    This function provides the interface to the used input adapter. It does\n    not throw in case the input reached EOF, but returns false instead\n\n    @return bool, whether the read was successful\n    */\n    template<class T>\n    bool get_to(T& dest, const input_format_t format, const char* context)\n    {\n        auto new_chars_read = ia.get_elements(&dest);\n        chars_read += new_chars_read;\n        if (JSON_HEDLEY_UNLIKELY(new_chars_read < sizeof(T)))\n        {\n            // in case of failure, advance position by 1 to report failing location\n            ++chars_read;\n            sax->parse_error(chars_read, \"<end of file>\", parse_error::create(110, chars_read, exception_message(format, \"unexpected end of input\", context), nullptr));\n            return false;\n        }\n        return true;\n    }\n\n    /*!\n    @return character read from the input after ignoring all 'N' entries\n    */\n    char_int_type get_ignore_noop()\n    {\n        do\n        {\n            get();\n        }\n        while (current == 'N');\n\n        return current;\n    }\n\n    template<class NumberType>\n    static void byte_swap(NumberType& number)\n    {\n        constexpr std::size_t sz = sizeof(number);\n#ifdef __cpp_lib_byteswap\n        if constexpr (sz == 1)\n        {\n            return;\n        }\n        if constexpr(std::is_integral_v<NumberType>)\n        {\n            number = std::byteswap(number);\n            return;\n        }\n#endif\n        auto* ptr = reinterpret_cast<std::uint8_t*>(&number);\n        for (std::size_t i = 0; i < sz / 2; ++i)\n        {\n            std::swap(ptr[i], ptr[sz - i - 1]);\n        }\n    }\n\n    /*\n    @brief read a number from the input\n\n    @tparam NumberType the type of the number\n    @param[in] format   the current format (for diagnostics)\n    @param[out] result  number of type @a NumberType\n\n    @return whether conversion completed\n\n    @note This function needs to respect the system's endianness, because\n          bytes in CBOR, MessagePack, and UBJSON are stored in network order\n          (big endian) and therefore need reordering on little endian systems.\n          On the other hand, BSON and BJData use little endian and should reorder\n          on big endian systems.\n    */\n    template<typename NumberType, bool InputIsLittleEndian = false>\n    bool get_number(const input_format_t format, NumberType& result)\n    {\n        // read in the original format\n\n        if (JSON_HEDLEY_UNLIKELY(!get_to(result, format, \"number\")))\n        {\n            return false;\n        }\n        if (is_little_endian != (InputIsLittleEndian || format == input_format_t::bjdata))\n        {\n            byte_swap(result);\n        }\n        return true;\n    }\n\n    /*!\n    @brief create a string by reading characters from the input\n\n    @tparam NumberType the type of the number\n    @param[in] format the current format (for diagnostics)\n    @param[in] len number of characters to read\n    @param[out] result string created by reading @a len bytes\n\n    @return whether string creation completed\n\n    @note We can not reserve @a len bytes for the result, because @a len\n          may be too large. Usually, @ref unexpect_eof() detects the end of\n          the input before we run out of string memory.\n    */\n    template<typename NumberType>\n    bool get_string(const input_format_t format,\n                    const NumberType len,\n                    string_t& result)\n    {\n        bool success = true;\n        for (NumberType i = 0; i < len; i++)\n        {\n            get();\n            if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, \"string\")))\n            {\n                success = false;\n                break;\n            }\n            result.push_back(static_cast<typename string_t::value_type>(current));\n        }\n        return success;\n    }\n\n    /*!\n    @brief create a byte array by reading bytes from the input\n\n    @tparam NumberType the type of the number\n    @param[in] format the current format (for diagnostics)\n    @param[in] len number of bytes to read\n    @param[out] result byte array created by reading @a len bytes\n\n    @return whether byte array creation completed\n\n    @note We can not reserve @a len bytes for the result, because @a len\n          may be too large. Usually, @ref unexpect_eof() detects the end of\n          the input before we run out of memory.\n    */\n    template<typename NumberType>\n    bool get_binary(const input_format_t format,\n                    const NumberType len,\n                    binary_t& result)\n    {\n        bool success = true;\n        for (NumberType i = 0; i < len; i++)\n        {\n            get();\n            if (JSON_HEDLEY_UNLIKELY(!unexpect_eof(format, \"binary\")))\n            {\n                success = false;\n                break;\n            }\n            result.push_back(static_cast<std::uint8_t>(current));\n        }\n        return success;\n    }\n\n    /*!\n    @param[in] format   the current format (for diagnostics)\n    @param[in] context  further context information (for diagnostics)\n    @return whether the last read character is not EOF\n    */\n    JSON_HEDLEY_NON_NULL(3)\n    bool unexpect_eof(const input_format_t format, const char* context) const\n    {\n        if (JSON_HEDLEY_UNLIKELY(current == char_traits<char_type>::eof()))\n        {\n            return sax->parse_error(chars_read, \"<end of file>\",\n                                    parse_error::create(110, chars_read, exception_message(format, \"unexpected end of input\", context), nullptr));\n        }\n        return true;\n    }\n\n    /*!\n    @return a string representation of the last read byte\n    */\n    std::string get_token_string() const\n    {\n        std::array<char, 3> cr{{}};\n        static_cast<void>((std::snprintf)(cr.data(), cr.size(), \"%.2hhX\", static_cast<unsigned char>(current))); // NOLINT(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n        return std::string{cr.data()};\n    }\n\n    /*!\n    @param[in] format   the current format\n    @param[in] detail   a detailed error message\n    @param[in] context  further context information\n    @return a message string to use in the parse_error exceptions\n    */\n    std::string exception_message(const input_format_t format,\n                                  const std::string& detail,\n                                  const std::string& context) const\n    {\n        std::string error_msg = \"syntax error while parsing \";\n\n        switch (format)\n        {\n            case input_format_t::cbor:\n                error_msg += \"CBOR\";\n                break;\n\n            case input_format_t::msgpack:\n                error_msg += \"MessagePack\";\n                break;\n\n            case input_format_t::ubjson:\n                error_msg += \"UBJSON\";\n                break;\n\n            case input_format_t::bson:\n                error_msg += \"BSON\";\n                break;\n\n            case input_format_t::bjdata:\n                error_msg += \"BJData\";\n                break;\n\n            case input_format_t::json: // LCOV_EXCL_LINE\n            default:            // LCOV_EXCL_LINE\n                JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n        }\n\n        return concat(error_msg, ' ', context, \": \", detail);\n    }\n\n  private:\n    static JSON_INLINE_VARIABLE constexpr std::size_t npos = detail::unknown_size();\n\n    /// input adapter\n    InputAdapterType ia;\n\n    /// the current character\n    char_int_type current = char_traits<char_type>::eof();\n\n    /// the number of characters read\n    std::size_t chars_read = 0;\n\n    /// whether we can assume little endianness\n    const bool is_little_endian = little_endianness();\n\n    /// input format\n    const input_format_t input_format = input_format_t::json;\n\n    /// the SAX parser\n    json_sax_t* sax = nullptr;\n\n    // excluded markers in bjdata optimized type\n#define JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_ \\\n    make_array<char_int_type>('F', 'H', 'N', 'S', 'T', 'Z', '[', '{')\n\n#define JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_ \\\n    make_array<bjd_type>(                      \\\n    bjd_type{'B', \"byte\"},                     \\\n    bjd_type{'C', \"char\"},                     \\\n    bjd_type{'D', \"double\"},                   \\\n    bjd_type{'I', \"int16\"},                    \\\n    bjd_type{'L', \"int64\"},                    \\\n    bjd_type{'M', \"uint64\"},                   \\\n    bjd_type{'U', \"uint8\"},                    \\\n    bjd_type{'d', \"single\"},                   \\\n    bjd_type{'i', \"int8\"},                     \\\n    bjd_type{'l', \"int32\"},                    \\\n    bjd_type{'m', \"uint32\"},                   \\\n    bjd_type{'u', \"uint16\"})\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    // lookup tables\n    // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes)\n    const decltype(JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_) bjd_optimized_type_markers =\n        JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_;\n\n    using bjd_type = std::pair<char_int_type, string_t>;\n    // NOLINTNEXTLINE(cppcoreguidelines-non-private-member-variables-in-classes)\n    const decltype(JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_) bjd_types_map =\n        JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_;\n\n#undef JSON_BINARY_READER_MAKE_BJD_OPTIMIZED_TYPE_MARKERS_\n#undef JSON_BINARY_READER_MAKE_BJD_TYPES_MAP_\n};\n\n#ifndef JSON_HAS_CPP_17\n    template<typename BasicJsonType, typename InputAdapterType, typename SAX>\n    constexpr std::size_t binary_reader<BasicJsonType, InputAdapterType, SAX>::npos;\n#endif\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/input/input_adapters.hpp>\n\n// #include <nlohmann/detail/input/lexer.hpp>\n\n// #include <nlohmann/detail/input/parser.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cmath> // isfinite\n#include <cstdint> // uint8_t\n#include <functional> // function\n#include <string> // string\n#include <utility> // move\n#include <vector> // vector\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/input/input_adapters.hpp>\n\n// #include <nlohmann/detail/input/json_sax.hpp>\n\n// #include <nlohmann/detail/input/lexer.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/is_sax.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n////////////\n// parser //\n////////////\n\nenum class parse_event_t : std::uint8_t\n{\n    /// the parser read `{` and started to process a JSON object\n    object_start,\n    /// the parser read `}` and finished processing a JSON object\n    object_end,\n    /// the parser read `[` and started to process a JSON array\n    array_start,\n    /// the parser read `]` and finished processing a JSON array\n    array_end,\n    /// the parser read a key of a value in an object\n    key,\n    /// the parser finished reading a JSON value\n    value\n};\n\ntemplate<typename BasicJsonType>\nusing parser_callback_t =\n    std::function<bool(int /*depth*/, parse_event_t /*event*/, BasicJsonType& /*parsed*/)>;\n\n/*!\n@brief syntax analysis\n\nThis class implements a recursive descent parser.\n*/\ntemplate<typename BasicJsonType, typename InputAdapterType>\nclass parser\n{\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using string_t = typename BasicJsonType::string_t;\n    using lexer_t = lexer<BasicJsonType, InputAdapterType>;\n    using token_type = typename lexer_t::token_type;\n\n  public:\n    /// a parser reading from an input adapter\n    explicit parser(InputAdapterType&& adapter,\n                    parser_callback_t<BasicJsonType> cb = nullptr,\n                    const bool allow_exceptions_ = true,\n                    const bool skip_comments = false)\n        : callback(std::move(cb))\n        , m_lexer(std::move(adapter), skip_comments)\n        , allow_exceptions(allow_exceptions_)\n    {\n        // read first token\n        get_token();\n    }\n\n    /*!\n    @brief public parser interface\n\n    @param[in] strict      whether to expect the last token to be EOF\n    @param[in,out] result  parsed JSON value\n\n    @throw parse_error.101 in case of an unexpected token\n    @throw parse_error.102 if to_unicode fails or surrogate error\n    @throw parse_error.103 if to_unicode fails\n    */\n    void parse(const bool strict, BasicJsonType& result)\n    {\n        if (callback)\n        {\n            json_sax_dom_callback_parser<BasicJsonType, InputAdapterType> sdp(result, callback, allow_exceptions, &m_lexer);\n            sax_parse_internal(&sdp);\n\n            // in strict mode, input must be completely read\n            if (strict && (get_token() != token_type::end_of_input))\n            {\n                sdp.parse_error(m_lexer.get_position(),\n                                m_lexer.get_token_string(),\n                                parse_error::create(101, m_lexer.get_position(),\n                                                    exception_message(token_type::end_of_input, \"value\"), nullptr));\n            }\n\n            // in case of an error, return discarded value\n            if (sdp.is_errored())\n            {\n                result = value_t::discarded;\n                return;\n            }\n\n            // set top-level value to null if it was discarded by the callback\n            // function\n            if (result.is_discarded())\n            {\n                result = nullptr;\n            }\n        }\n        else\n        {\n            json_sax_dom_parser<BasicJsonType, InputAdapterType> sdp(result, allow_exceptions, &m_lexer);\n            sax_parse_internal(&sdp);\n\n            // in strict mode, input must be completely read\n            if (strict && (get_token() != token_type::end_of_input))\n            {\n                sdp.parse_error(m_lexer.get_position(),\n                                m_lexer.get_token_string(),\n                                parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, \"value\"), nullptr));\n            }\n\n            // in case of an error, return discarded value\n            if (sdp.is_errored())\n            {\n                result = value_t::discarded;\n                return;\n            }\n        }\n\n        result.assert_invariant();\n    }\n\n    /*!\n    @brief public accept interface\n\n    @param[in] strict  whether to expect the last token to be EOF\n    @return whether the input is a proper JSON text\n    */\n    bool accept(const bool strict = true)\n    {\n        json_sax_acceptor<BasicJsonType> sax_acceptor;\n        return sax_parse(&sax_acceptor, strict);\n    }\n\n    template<typename SAX>\n    JSON_HEDLEY_NON_NULL(2)\n    bool sax_parse(SAX* sax, const bool strict = true)\n    {\n        (void)detail::is_sax_static_asserts<SAX, BasicJsonType> {};\n        const bool result = sax_parse_internal(sax);\n\n        // strict mode: next byte must be EOF\n        if (result && strict && (get_token() != token_type::end_of_input))\n        {\n            return sax->parse_error(m_lexer.get_position(),\n                                    m_lexer.get_token_string(),\n                                    parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_of_input, \"value\"), nullptr));\n        }\n\n        return result;\n    }\n\n  private:\n    template<typename SAX>\n    JSON_HEDLEY_NON_NULL(2)\n    bool sax_parse_internal(SAX* sax)\n    {\n        // stack to remember the hierarchy of structured values we are parsing\n        // true = array; false = object\n        std::vector<bool> states;\n        // value to avoid a goto (see comment where set to true)\n        bool skip_to_state_evaluation = false;\n\n        while (true)\n        {\n            if (!skip_to_state_evaluation)\n            {\n                // invariant: get_token() was called before each iteration\n                switch (last_token)\n                {\n                    case token_type::begin_object:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->start_object(detail::unknown_size())))\n                        {\n                            return false;\n                        }\n\n                        // closing } -> we are done\n                        if (get_token() == token_type::end_object)\n                        {\n                            if (JSON_HEDLEY_UNLIKELY(!sax->end_object()))\n                            {\n                                return false;\n                            }\n                            break;\n                        }\n\n                        // parse key\n                        if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string))\n                        {\n                            return sax->parse_error(m_lexer.get_position(),\n                                                    m_lexer.get_token_string(),\n                                                    parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, \"object key\"), nullptr));\n                        }\n                        if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))\n                        {\n                            return false;\n                        }\n\n                        // parse separator (:)\n                        if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))\n                        {\n                            return sax->parse_error(m_lexer.get_position(),\n                                                    m_lexer.get_token_string(),\n                                                    parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, \"object separator\"), nullptr));\n                        }\n\n                        // remember we are now inside an object\n                        states.push_back(false);\n\n                        // parse values\n                        get_token();\n                        continue;\n                    }\n\n                    case token_type::begin_array:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->start_array(detail::unknown_size())))\n                        {\n                            return false;\n                        }\n\n                        // closing ] -> we are done\n                        if (get_token() == token_type::end_array)\n                        {\n                            if (JSON_HEDLEY_UNLIKELY(!sax->end_array()))\n                            {\n                                return false;\n                            }\n                            break;\n                        }\n\n                        // remember we are now inside an array\n                        states.push_back(true);\n\n                        // parse values (no need to call get_token)\n                        continue;\n                    }\n\n                    case token_type::value_float:\n                    {\n                        const auto res = m_lexer.get_number_float();\n\n                        if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res)))\n                        {\n                            return sax->parse_error(m_lexer.get_position(),\n                                                    m_lexer.get_token_string(),\n                                                    out_of_range::create(406, concat(\"number overflow parsing '\", m_lexer.get_token_string(), '\\''), nullptr));\n                        }\n\n                        if (JSON_HEDLEY_UNLIKELY(!sax->number_float(res, m_lexer.get_string())))\n                        {\n                            return false;\n                        }\n\n                        break;\n                    }\n\n                    case token_type::literal_false:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->boolean(false)))\n                        {\n                            return false;\n                        }\n                        break;\n                    }\n\n                    case token_type::literal_null:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->null()))\n                        {\n                            return false;\n                        }\n                        break;\n                    }\n\n                    case token_type::literal_true:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->boolean(true)))\n                        {\n                            return false;\n                        }\n                        break;\n                    }\n\n                    case token_type::value_integer:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->number_integer(m_lexer.get_number_integer())))\n                        {\n                            return false;\n                        }\n                        break;\n                    }\n\n                    case token_type::value_string:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string())))\n                        {\n                            return false;\n                        }\n                        break;\n                    }\n\n                    case token_type::value_unsigned:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!sax->number_unsigned(m_lexer.get_number_unsigned())))\n                        {\n                            return false;\n                        }\n                        break;\n                    }\n\n                    case token_type::parse_error:\n                    {\n                        // using \"uninitialized\" to avoid \"expected\" message\n                        return sax->parse_error(m_lexer.get_position(),\n                                                m_lexer.get_token_string(),\n                                                parse_error::create(101, m_lexer.get_position(), exception_message(token_type::uninitialized, \"value\"), nullptr));\n                    }\n                    case token_type::end_of_input:\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total == 1))\n                        {\n                            return sax->parse_error(m_lexer.get_position(),\n                                                    m_lexer.get_token_string(),\n                                                    parse_error::create(101, m_lexer.get_position(),\n                                                            \"attempting to parse an empty input; check that your input string or stream contains the expected JSON\", nullptr));\n                        }\n\n                        return sax->parse_error(m_lexer.get_position(),\n                                                m_lexer.get_token_string(),\n                                                parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, \"value\"), nullptr));\n                    }\n                    case token_type::uninitialized:\n                    case token_type::end_array:\n                    case token_type::end_object:\n                    case token_type::name_separator:\n                    case token_type::value_separator:\n                    case token_type::literal_or_value:\n                    default: // the last token was unexpected\n                    {\n                        return sax->parse_error(m_lexer.get_position(),\n                                                m_lexer.get_token_string(),\n                                                parse_error::create(101, m_lexer.get_position(), exception_message(token_type::literal_or_value, \"value\"), nullptr));\n                    }\n                }\n            }\n            else\n            {\n                skip_to_state_evaluation = false;\n            }\n\n            // we reached this line after we successfully parsed a value\n            if (states.empty())\n            {\n                // empty stack: we reached the end of the hierarchy: done\n                return true;\n            }\n\n            if (states.back())  // array\n            {\n                // comma -> next value\n                if (get_token() == token_type::value_separator)\n                {\n                    // parse a new value\n                    get_token();\n                    continue;\n                }\n\n                // closing ]\n                if (JSON_HEDLEY_LIKELY(last_token == token_type::end_array))\n                {\n                    if (JSON_HEDLEY_UNLIKELY(!sax->end_array()))\n                    {\n                        return false;\n                    }\n\n                    // We are done with this array. Before we can parse a\n                    // new value, we need to evaluate the new state first.\n                    // By setting skip_to_state_evaluation to false, we\n                    // are effectively jumping to the beginning of this if.\n                    JSON_ASSERT(!states.empty());\n                    states.pop_back();\n                    skip_to_state_evaluation = true;\n                    continue;\n                }\n\n                return sax->parse_error(m_lexer.get_position(),\n                                        m_lexer.get_token_string(),\n                                        parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_array, \"array\"), nullptr));\n            }\n\n            // states.back() is false -> object\n\n            // comma -> next value\n            if (get_token() == token_type::value_separator)\n            {\n                // parse key\n                if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::value_string))\n                {\n                    return sax->parse_error(m_lexer.get_position(),\n                                            m_lexer.get_token_string(),\n                                            parse_error::create(101, m_lexer.get_position(), exception_message(token_type::value_string, \"object key\"), nullptr));\n                }\n\n                if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string())))\n                {\n                    return false;\n                }\n\n                // parse separator (:)\n                if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator))\n                {\n                    return sax->parse_error(m_lexer.get_position(),\n                                            m_lexer.get_token_string(),\n                                            parse_error::create(101, m_lexer.get_position(), exception_message(token_type::name_separator, \"object separator\"), nullptr));\n                }\n\n                // parse values\n                get_token();\n                continue;\n            }\n\n            // closing }\n            if (JSON_HEDLEY_LIKELY(last_token == token_type::end_object))\n            {\n                if (JSON_HEDLEY_UNLIKELY(!sax->end_object()))\n                {\n                    return false;\n                }\n\n                // We are done with this object. Before we can parse a\n                // new value, we need to evaluate the new state first.\n                // By setting skip_to_state_evaluation to false, we\n                // are effectively jumping to the beginning of this if.\n                JSON_ASSERT(!states.empty());\n                states.pop_back();\n                skip_to_state_evaluation = true;\n                continue;\n            }\n\n            return sax->parse_error(m_lexer.get_position(),\n                                    m_lexer.get_token_string(),\n                                    parse_error::create(101, m_lexer.get_position(), exception_message(token_type::end_object, \"object\"), nullptr));\n        }\n    }\n\n    /// get next token from lexer\n    token_type get_token()\n    {\n        return last_token = m_lexer.scan();\n    }\n\n    std::string exception_message(const token_type expected, const std::string& context)\n    {\n        std::string error_msg = \"syntax error \";\n\n        if (!context.empty())\n        {\n            error_msg += concat(\"while parsing \", context, ' ');\n        }\n\n        error_msg += \"- \";\n\n        if (last_token == token_type::parse_error)\n        {\n            error_msg += concat(m_lexer.get_error_message(), \"; last read: '\",\n                                m_lexer.get_token_string(), '\\'');\n        }\n        else\n        {\n            error_msg += concat(\"unexpected \", lexer_t::token_type_name(last_token));\n        }\n\n        if (expected != token_type::uninitialized)\n        {\n            error_msg += concat(\"; expected \", lexer_t::token_type_name(expected));\n        }\n\n        return error_msg;\n    }\n\n  private:\n    /// callback function\n    const parser_callback_t<BasicJsonType> callback = nullptr;\n    /// the type of the last read token\n    token_type last_token = token_type::uninitialized;\n    /// the lexer\n    lexer_t m_lexer;\n    /// whether to throw exceptions in case of errors\n    const bool allow_exceptions = true;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/iterators/internal_iterator.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/iterators/primitive_iterator.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // ptrdiff_t\n#include <limits>  // numeric_limits\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/*\n@brief an iterator for primitive JSON types\n\nThis class models an iterator for primitive JSON types (boolean, number,\nstring). It's only purpose is to allow the iterator/const_iterator classes\nto \"iterate\" over primitive values. Internally, the iterator is modeled by\na `difference_type` variable. Value begin_value (`0`) models the begin,\nend_value (`1`) models past the end.\n*/\nclass primitive_iterator_t\n{\n  private:\n    using difference_type = std::ptrdiff_t;\n    static constexpr difference_type begin_value = 0;\n    static constexpr difference_type end_value = begin_value + 1;\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    /// iterator as signed integer type\n    difference_type m_it = (std::numeric_limits<std::ptrdiff_t>::min)();\n\n  public:\n    constexpr difference_type get_value() const noexcept\n    {\n        return m_it;\n    }\n\n    /// set iterator to a defined beginning\n    void set_begin() noexcept\n    {\n        m_it = begin_value;\n    }\n\n    /// set iterator to a defined past the end\n    void set_end() noexcept\n    {\n        m_it = end_value;\n    }\n\n    /// return whether the iterator can be dereferenced\n    constexpr bool is_begin() const noexcept\n    {\n        return m_it == begin_value;\n    }\n\n    /// return whether the iterator is at end\n    constexpr bool is_end() const noexcept\n    {\n        return m_it == end_value;\n    }\n\n    friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept\n    {\n        return lhs.m_it == rhs.m_it;\n    }\n\n    friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept\n    {\n        return lhs.m_it < rhs.m_it;\n    }\n\n    primitive_iterator_t operator+(difference_type n) noexcept\n    {\n        auto result = *this;\n        result += n;\n        return result;\n    }\n\n    friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept\n    {\n        return lhs.m_it - rhs.m_it;\n    }\n\n    primitive_iterator_t& operator++() noexcept\n    {\n        ++m_it;\n        return *this;\n    }\n\n    primitive_iterator_t operator++(int)& noexcept // NOLINT(cert-dcl21-cpp)\n    {\n        auto result = *this;\n        ++m_it;\n        return result;\n    }\n\n    primitive_iterator_t& operator--() noexcept\n    {\n        --m_it;\n        return *this;\n    }\n\n    primitive_iterator_t operator--(int)& noexcept // NOLINT(cert-dcl21-cpp)\n    {\n        auto result = *this;\n        --m_it;\n        return result;\n    }\n\n    primitive_iterator_t& operator+=(difference_type n) noexcept\n    {\n        m_it += n;\n        return *this;\n    }\n\n    primitive_iterator_t& operator-=(difference_type n) noexcept\n    {\n        m_it -= n;\n        return *this;\n    }\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/*!\n@brief an iterator value\n\n@note This structure could easily be a union, but MSVC currently does not allow\nunions members with complex constructors, see https://github.com/nlohmann/json/pull/105.\n*/\ntemplate<typename BasicJsonType> struct internal_iterator\n{\n    /// iterator for JSON objects\n    typename BasicJsonType::object_t::iterator object_iterator {};\n    /// iterator for JSON arrays\n    typename BasicJsonType::array_t::iterator array_iterator {};\n    /// generic iterator for all other types\n    primitive_iterator_t primitive_iterator {};\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/iterators/iter_impl.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <iterator> // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next\n#include <type_traits> // conditional, is_const, remove_const\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/iterators/internal_iterator.hpp>\n\n// #include <nlohmann/detail/iterators/primitive_iterator.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n// forward declare, to be able to friend it later on\ntemplate<typename IteratorType> class iteration_proxy;\ntemplate<typename IteratorType> class iteration_proxy_value;\n\n/*!\n@brief a template for a bidirectional iterator for the @ref basic_json class\nThis class implements a both iterators (iterator and const_iterator) for the\n@ref basic_json class.\n@note An iterator is called *initialized* when a pointer to a JSON value has\n      been set (e.g., by a constructor or a copy assignment). If the iterator is\n      default-constructed, it is *uninitialized* and most methods are undefined.\n      **The library uses assertions to detect calls on uninitialized iterators.**\n@requirement The class satisfies the following concept requirements:\n-\n[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):\n  The iterator that can be moved can be moved in both directions (i.e.\n  incremented and decremented).\n@since version 1.0.0, simplified in version 2.0.9, change to bidirectional\n       iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593)\n*/\ntemplate<typename BasicJsonType>\nclass iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)\n{\n    /// the iterator with BasicJsonType of different const-ness\n    using other_iter_impl = iter_impl<typename std::conditional<std::is_const<BasicJsonType>::value, typename std::remove_const<BasicJsonType>::type, const BasicJsonType>::type>;\n    /// allow basic_json to access private members\n    friend other_iter_impl;\n    friend BasicJsonType;\n    friend iteration_proxy<iter_impl>;\n    friend iteration_proxy_value<iter_impl>;\n\n    using object_t = typename BasicJsonType::object_t;\n    using array_t = typename BasicJsonType::array_t;\n    // make sure BasicJsonType is basic_json or const basic_json\n    static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value,\n                  \"iter_impl only accepts (const) basic_json\");\n    // superficial check for the LegacyBidirectionalIterator named requirement\n    static_assert(std::is_base_of<std::bidirectional_iterator_tag, std::bidirectional_iterator_tag>::value\n                  &&  std::is_base_of<std::bidirectional_iterator_tag, typename std::iterator_traits<typename array_t::iterator>::iterator_category>::value,\n                  \"basic_json iterator assumes array and object type iterators satisfy the LegacyBidirectionalIterator named requirement.\");\n\n  public:\n    /// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17.\n    /// The C++ Standard has never required user-defined iterators to derive from std::iterator.\n    /// A user-defined iterator should provide publicly accessible typedefs named\n    /// iterator_category, value_type, difference_type, pointer, and reference.\n    /// Note that value_type is required to be non-const, even for constant iterators.\n    using iterator_category = std::bidirectional_iterator_tag;\n\n    /// the type of the values when the iterator is dereferenced\n    using value_type = typename BasicJsonType::value_type;\n    /// a type to represent differences between iterators\n    using difference_type = typename BasicJsonType::difference_type;\n    /// defines a pointer to the type iterated over (value_type)\n    using pointer = typename std::conditional<std::is_const<BasicJsonType>::value,\n          typename BasicJsonType::const_pointer,\n          typename BasicJsonType::pointer>::type;\n    /// defines a reference to the type iterated over (value_type)\n    using reference =\n        typename std::conditional<std::is_const<BasicJsonType>::value,\n        typename BasicJsonType::const_reference,\n        typename BasicJsonType::reference>::type;\n\n    iter_impl() = default;\n    ~iter_impl() = default;\n    iter_impl(iter_impl&&) noexcept = default;\n    iter_impl& operator=(iter_impl&&) noexcept = default;\n\n    /*!\n    @brief constructor for a given JSON instance\n    @param[in] object  pointer to a JSON object for this iterator\n    @pre object != nullptr\n    @post The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    explicit iter_impl(pointer object) noexcept : m_object(object)\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n            {\n                m_it.object_iterator = typename object_t::iterator();\n                break;\n            }\n\n            case value_t::array:\n            {\n                m_it.array_iterator = typename array_t::iterator();\n                break;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                m_it.primitive_iterator = primitive_iterator_t();\n                break;\n            }\n        }\n    }\n\n    /*!\n    @note The conventional copy constructor and copy assignment are implicitly\n          defined. Combined with the following converting constructor and\n          assignment, they support: (1) copy from iterator to iterator, (2)\n          copy from const iterator to const iterator, and (3) conversion from\n          iterator to const iterator. However conversion from const iterator\n          to iterator is not defined.\n    */\n\n    /*!\n    @brief const copy constructor\n    @param[in] other const iterator to copy from\n    @note This copy constructor had to be defined explicitly to circumvent a bug\n          occurring on msvc v19.0 compiler (VS 2015) debug build. For more\n          information refer to: https://github.com/nlohmann/json/issues/1608\n    */\n    iter_impl(const iter_impl<const BasicJsonType>& other) noexcept\n        : m_object(other.m_object), m_it(other.m_it)\n    {}\n\n    /*!\n    @brief converting assignment\n    @param[in] other const iterator to copy from\n    @return const/non-const iterator\n    @note It is not checked whether @a other is initialized.\n    */\n    iter_impl& operator=(const iter_impl<const BasicJsonType>& other) noexcept\n    {\n        if (&other != this)\n        {\n            m_object = other.m_object;\n            m_it = other.m_it;\n        }\n        return *this;\n    }\n\n    /*!\n    @brief converting constructor\n    @param[in] other  non-const iterator to copy from\n    @note It is not checked whether @a other is initialized.\n    */\n    iter_impl(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept\n        : m_object(other.m_object), m_it(other.m_it)\n    {}\n\n    /*!\n    @brief converting assignment\n    @param[in] other  non-const iterator to copy from\n    @return const/non-const iterator\n    @note It is not checked whether @a other is initialized.\n    */\n    iter_impl& operator=(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept // NOLINT(cert-oop54-cpp)\n    {\n        m_object = other.m_object;\n        m_it = other.m_it;\n        return *this;\n    }\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    /*!\n    @brief set the iterator to the first value\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    void set_begin() noexcept\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n            {\n                m_it.object_iterator = m_object->m_data.m_value.object->begin();\n                break;\n            }\n\n            case value_t::array:\n            {\n                m_it.array_iterator = m_object->m_data.m_value.array->begin();\n                break;\n            }\n\n            case value_t::null:\n            {\n                // set to end so begin()==end() is true: null is empty\n                m_it.primitive_iterator.set_end();\n                break;\n            }\n\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                m_it.primitive_iterator.set_begin();\n                break;\n            }\n        }\n    }\n\n    /*!\n    @brief set the iterator past the last value\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    void set_end() noexcept\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n            {\n                m_it.object_iterator = m_object->m_data.m_value.object->end();\n                break;\n            }\n\n            case value_t::array:\n            {\n                m_it.array_iterator = m_object->m_data.m_value.array->end();\n                break;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                m_it.primitive_iterator.set_end();\n                break;\n            }\n        }\n    }\n\n  public:\n    /*!\n    @brief return a reference to the value pointed to by the iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    reference operator*() const\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n            {\n                JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());\n                return m_it.object_iterator->second;\n            }\n\n            case value_t::array:\n            {\n                JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());\n                return *m_it.array_iterator;\n            }\n\n            case value_t::null:\n                JSON_THROW(invalid_iterator::create(214, \"cannot get value\", m_object));\n\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))\n                {\n                    return *m_object;\n                }\n\n                JSON_THROW(invalid_iterator::create(214, \"cannot get value\", m_object));\n            }\n        }\n    }\n\n    /*!\n    @brief dereference the iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    pointer operator->() const\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n            {\n                JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());\n                return &(m_it.object_iterator->second);\n            }\n\n            case value_t::array:\n            {\n                JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());\n                return &*m_it.array_iterator;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))\n                {\n                    return m_object;\n                }\n\n                JSON_THROW(invalid_iterator::create(214, \"cannot get value\", m_object));\n            }\n        }\n    }\n\n    /*!\n    @brief post-increment (it++)\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl operator++(int)& // NOLINT(cert-dcl21-cpp)\n    {\n        auto result = *this;\n        ++(*this);\n        return result;\n    }\n\n    /*!\n    @brief pre-increment (++it)\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl& operator++()\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n            {\n                std::advance(m_it.object_iterator, 1);\n                break;\n            }\n\n            case value_t::array:\n            {\n                std::advance(m_it.array_iterator, 1);\n                break;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                ++m_it.primitive_iterator;\n                break;\n            }\n        }\n\n        return *this;\n    }\n\n    /*!\n    @brief post-decrement (it--)\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl operator--(int)& // NOLINT(cert-dcl21-cpp)\n    {\n        auto result = *this;\n        --(*this);\n        return result;\n    }\n\n    /*!\n    @brief pre-decrement (--it)\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl& operator--()\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n            {\n                std::advance(m_it.object_iterator, -1);\n                break;\n            }\n\n            case value_t::array:\n            {\n                std::advance(m_it.array_iterator, -1);\n                break;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                --m_it.primitive_iterator;\n                break;\n            }\n        }\n\n        return *this;\n    }\n\n    /*!\n    @brief comparison: equal\n    @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized.\n    */\n    template < typename IterImpl, detail::enable_if_t < (std::is_same<IterImpl, iter_impl>::value || std::is_same<IterImpl, other_iter_impl>::value), std::nullptr_t > = nullptr >\n    bool operator==(const IterImpl& other) const\n    {\n        // if objects are not the same, the comparison is undefined\n        if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(212, \"cannot compare iterators of different containers\", m_object));\n        }\n\n        // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493\n        if (m_object == nullptr)\n        {\n            return true;\n        }\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n                return (m_it.object_iterator == other.m_it.object_iterator);\n\n            case value_t::array:\n                return (m_it.array_iterator == other.m_it.array_iterator);\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n                return (m_it.primitive_iterator == other.m_it.primitive_iterator);\n        }\n    }\n\n    /*!\n    @brief comparison: not equal\n    @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized.\n    */\n    template < typename IterImpl, detail::enable_if_t < (std::is_same<IterImpl, iter_impl>::value || std::is_same<IterImpl, other_iter_impl>::value), std::nullptr_t > = nullptr >\n    bool operator!=(const IterImpl& other) const\n    {\n        return !operator==(other);\n    }\n\n    /*!\n    @brief comparison: smaller\n    @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized.\n    */\n    bool operator<(const iter_impl& other) const\n    {\n        // if objects are not the same, the comparison is undefined\n        if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(212, \"cannot compare iterators of different containers\", m_object));\n        }\n\n        // value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493\n        if (m_object == nullptr)\n        {\n            // the iterators are both value-initialized and are to be considered equal, but this function checks for smaller, so we return false\n            return false;\n        }\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n                JSON_THROW(invalid_iterator::create(213, \"cannot compare order of object iterators\", m_object));\n\n            case value_t::array:\n                return (m_it.array_iterator < other.m_it.array_iterator);\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n                return (m_it.primitive_iterator < other.m_it.primitive_iterator);\n        }\n    }\n\n    /*!\n    @brief comparison: less than or equal\n    @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized.\n    */\n    bool operator<=(const iter_impl& other) const\n    {\n        return !other.operator < (*this);\n    }\n\n    /*!\n    @brief comparison: greater than\n    @pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized.\n    */\n    bool operator>(const iter_impl& other) const\n    {\n        return !operator<=(other);\n    }\n\n    /*!\n    @brief comparison: greater than or equal\n    @pre (1) The iterator is initialized; i.e. `m_object != nullptr`, or (2) both iterators are value-initialized.\n    */\n    bool operator>=(const iter_impl& other) const\n    {\n        return !operator<(other);\n    }\n\n    /*!\n    @brief add to iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl& operator+=(difference_type i)\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n                JSON_THROW(invalid_iterator::create(209, \"cannot use offsets with object iterators\", m_object));\n\n            case value_t::array:\n            {\n                std::advance(m_it.array_iterator, i);\n                break;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                m_it.primitive_iterator += i;\n                break;\n            }\n        }\n\n        return *this;\n    }\n\n    /*!\n    @brief subtract from iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl& operator-=(difference_type i)\n    {\n        return operator+=(-i);\n    }\n\n    /*!\n    @brief add to iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl operator+(difference_type i) const\n    {\n        auto result = *this;\n        result += i;\n        return result;\n    }\n\n    /*!\n    @brief addition of distance and iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    friend iter_impl operator+(difference_type i, const iter_impl& it)\n    {\n        auto result = it;\n        result += i;\n        return result;\n    }\n\n    /*!\n    @brief subtract from iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    iter_impl operator-(difference_type i) const\n    {\n        auto result = *this;\n        result -= i;\n        return result;\n    }\n\n    /*!\n    @brief return difference\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    difference_type operator-(const iter_impl& other) const\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n                JSON_THROW(invalid_iterator::create(209, \"cannot use offsets with object iterators\", m_object));\n\n            case value_t::array:\n                return m_it.array_iterator - other.m_it.array_iterator;\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n                return m_it.primitive_iterator - other.m_it.primitive_iterator;\n        }\n    }\n\n    /*!\n    @brief access to successor\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    reference operator[](difference_type n) const\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        switch (m_object->m_data.m_type)\n        {\n            case value_t::object:\n                JSON_THROW(invalid_iterator::create(208, \"cannot use operator[] for object iterators\", m_object));\n\n            case value_t::array:\n                return *std::next(m_it.array_iterator, n);\n\n            case value_t::null:\n                JSON_THROW(invalid_iterator::create(214, \"cannot get value\", m_object));\n\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.get_value() == -n))\n                {\n                    return *m_object;\n                }\n\n                JSON_THROW(invalid_iterator::create(214, \"cannot get value\", m_object));\n            }\n        }\n    }\n\n    /*!\n    @brief return the key of an object iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    const typename object_t::key_type& key() const\n    {\n        JSON_ASSERT(m_object != nullptr);\n\n        if (JSON_HEDLEY_LIKELY(m_object->is_object()))\n        {\n            return m_it.object_iterator->first;\n        }\n\n        JSON_THROW(invalid_iterator::create(207, \"cannot use key() for non-object iterators\", m_object));\n    }\n\n    /*!\n    @brief return the value of an iterator\n    @pre The iterator is initialized; i.e. `m_object != nullptr`.\n    */\n    reference value() const\n    {\n        return operator*();\n    }\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    /// associated JSON instance\n    pointer m_object = nullptr;\n    /// the actual iterator of the associated instance\n    internal_iterator<typename std::remove_const<BasicJsonType>::type> m_it {};\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/iterators/iteration_proxy.hpp>\n\n// #include <nlohmann/detail/iterators/json_reverse_iterator.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <cstddef> // ptrdiff_t\n#include <iterator> // reverse_iterator\n#include <utility> // declval\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n//////////////////////\n// reverse_iterator //\n//////////////////////\n\n/*!\n@brief a template for a reverse iterator class\n\n@tparam Base the base iterator type to reverse. Valid types are @ref\niterator (to create @ref reverse_iterator) and @ref const_iterator (to\ncreate @ref const_reverse_iterator).\n\n@requirement The class satisfies the following concept requirements:\n-\n[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):\n  The iterator that can be moved can be moved in both directions (i.e.\n  incremented and decremented).\n- [OutputIterator](https://en.cppreference.com/w/cpp/named_req/OutputIterator):\n  It is possible to write to the pointed-to element (only if @a Base is\n  @ref iterator).\n\n@since version 1.0.0\n*/\ntemplate<typename Base>\nclass json_reverse_iterator : public std::reverse_iterator<Base>\n{\n  public:\n    using difference_type = std::ptrdiff_t;\n    /// shortcut to the reverse iterator adapter\n    using base_iterator = std::reverse_iterator<Base>;\n    /// the reference type for the pointed-to element\n    using reference = typename Base::reference;\n\n    /// create reverse iterator from iterator\n    explicit json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept\n        : base_iterator(it) {}\n\n    /// create reverse iterator from base class\n    explicit json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {}\n\n    /// post-increment (it++)\n    json_reverse_iterator operator++(int)& // NOLINT(cert-dcl21-cpp)\n    {\n        return static_cast<json_reverse_iterator>(base_iterator::operator++(1));\n    }\n\n    /// pre-increment (++it)\n    json_reverse_iterator& operator++()\n    {\n        return static_cast<json_reverse_iterator&>(base_iterator::operator++());\n    }\n\n    /// post-decrement (it--)\n    json_reverse_iterator operator--(int)& // NOLINT(cert-dcl21-cpp)\n    {\n        return static_cast<json_reverse_iterator>(base_iterator::operator--(1));\n    }\n\n    /// pre-decrement (--it)\n    json_reverse_iterator& operator--()\n    {\n        return static_cast<json_reverse_iterator&>(base_iterator::operator--());\n    }\n\n    /// add to iterator\n    json_reverse_iterator& operator+=(difference_type i)\n    {\n        return static_cast<json_reverse_iterator&>(base_iterator::operator+=(i));\n    }\n\n    /// add to iterator\n    json_reverse_iterator operator+(difference_type i) const\n    {\n        return static_cast<json_reverse_iterator>(base_iterator::operator+(i));\n    }\n\n    /// subtract from iterator\n    json_reverse_iterator operator-(difference_type i) const\n    {\n        return static_cast<json_reverse_iterator>(base_iterator::operator-(i));\n    }\n\n    /// return difference\n    difference_type operator-(const json_reverse_iterator& other) const\n    {\n        return base_iterator(*this) - base_iterator(other);\n    }\n\n    /// access to successor\n    reference operator[](difference_type n) const\n    {\n        return *(this->operator+(n));\n    }\n\n    /// return the key of an object iterator\n    auto key() const -> decltype(std::declval<Base>().key())\n    {\n        auto it = --this->base();\n        return it.key();\n    }\n\n    /// return the value of an iterator\n    reference value() const\n    {\n        auto it = --this->base();\n        return it.operator * ();\n    }\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/iterators/primitive_iterator.hpp>\n\n// #include <nlohmann/detail/json_custom_base_class.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <type_traits> // conditional, is_same\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/*!\n@brief Default base class of the @ref basic_json class.\n\nSo that the correct implementations of the copy / move ctors / assign operators\nof @ref basic_json do not require complex case distinctions\n(no base class / custom base class used as customization point),\n@ref basic_json always has a base class.\nBy default, this class is used because it is empty and thus has no effect\non the behavior of @ref basic_json.\n*/\nstruct json_default_base {};\n\ntemplate<class T>\nusing json_base_class = typename std::conditional <\n                        std::is_same<T, void>::value,\n                        json_default_base,\n                        T\n                        >::type;\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/json_pointer.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // all_of\n#include <cctype> // isdigit\n#include <cerrno> // errno, ERANGE\n#include <cstdlib> // strtoull\n#ifndef JSON_NO_IO\n    #include <iosfwd> // ostream\n#endif  // JSON_NO_IO\n#include <limits> // max\n#include <numeric> // accumulate\n#include <string> // string\n#include <utility> // move\n#include <vector> // vector\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/string_escape.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document\n/// @sa https://json.nlohmann.me/api/json_pointer/\ntemplate<typename RefStringType>\nclass json_pointer\n{\n    // allow basic_json to access private members\n    NLOHMANN_BASIC_JSON_TPL_DECLARATION\n    friend class basic_json;\n\n    template<typename>\n    friend class json_pointer;\n\n    template<typename T>\n    struct string_t_helper\n    {\n        using type = T;\n    };\n\n    NLOHMANN_BASIC_JSON_TPL_DECLARATION\n    struct string_t_helper<NLOHMANN_BASIC_JSON_TPL>\n    {\n        using type = StringType;\n    };\n\n  public:\n    // for backwards compatibility accept BasicJsonType\n    using string_t = typename string_t_helper<RefStringType>::type;\n\n    /// @brief create JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/json_pointer/\n    explicit json_pointer(const string_t& s = \"\")\n        : reference_tokens(split(s))\n    {}\n\n    /// @brief return a string representation of the JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/to_string/\n    string_t to_string() const\n    {\n        return std::accumulate(reference_tokens.begin(), reference_tokens.end(),\n                               string_t{},\n                               [](const string_t& a, const string_t& b)\n        {\n            return detail::concat(a, '/', detail::escape(b));\n        });\n    }\n\n    /// @brief return a string representation of the JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_string/\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, to_string())\n    operator string_t() const\n    {\n        return to_string();\n    }\n\n#ifndef JSON_NO_IO\n    /// @brief write string representation of the JSON pointer to stream\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/\n    friend std::ostream& operator<<(std::ostream& o, const json_pointer& ptr)\n    {\n        o << ptr.to_string();\n        return o;\n    }\n#endif\n\n    /// @brief append another JSON pointer at the end of this JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/\n    json_pointer& operator/=(const json_pointer& ptr)\n    {\n        reference_tokens.insert(reference_tokens.end(),\n                                ptr.reference_tokens.begin(),\n                                ptr.reference_tokens.end());\n        return *this;\n    }\n\n    /// @brief append an unescaped reference token at the end of this JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/\n    json_pointer& operator/=(string_t token)\n    {\n        push_back(std::move(token));\n        return *this;\n    }\n\n    /// @brief append an array index at the end of this JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_slasheq/\n    json_pointer& operator/=(std::size_t array_idx)\n    {\n        return *this /= std::to_string(array_idx);\n    }\n\n    /// @brief create a new JSON pointer by appending the right JSON pointer at the end of the left JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/\n    friend json_pointer operator/(const json_pointer& lhs,\n                                  const json_pointer& rhs)\n    {\n        return json_pointer(lhs) /= rhs;\n    }\n\n    /// @brief create a new JSON pointer by appending the unescaped token at the end of the JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/\n    friend json_pointer operator/(const json_pointer& lhs, string_t token) // NOLINT(performance-unnecessary-value-param)\n    {\n        return json_pointer(lhs) /= std::move(token);\n    }\n\n    /// @brief create a new JSON pointer by appending the array-index-token at the end of the JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_slash/\n    friend json_pointer operator/(const json_pointer& lhs, std::size_t array_idx)\n    {\n        return json_pointer(lhs) /= array_idx;\n    }\n\n    /// @brief returns the parent of this JSON pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/parent_pointer/\n    json_pointer parent_pointer() const\n    {\n        if (empty())\n        {\n            return *this;\n        }\n\n        json_pointer res = *this;\n        res.pop_back();\n        return res;\n    }\n\n    /// @brief remove last reference token\n    /// @sa https://json.nlohmann.me/api/json_pointer/pop_back/\n    void pop_back()\n    {\n        if (JSON_HEDLEY_UNLIKELY(empty()))\n        {\n            JSON_THROW(detail::out_of_range::create(405, \"JSON pointer has no parent\", nullptr));\n        }\n\n        reference_tokens.pop_back();\n    }\n\n    /// @brief return last reference token\n    /// @sa https://json.nlohmann.me/api/json_pointer/back/\n    const string_t& back() const\n    {\n        if (JSON_HEDLEY_UNLIKELY(empty()))\n        {\n            JSON_THROW(detail::out_of_range::create(405, \"JSON pointer has no parent\", nullptr));\n        }\n\n        return reference_tokens.back();\n    }\n\n    /// @brief append an unescaped token at the end of the reference pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/push_back/\n    void push_back(const string_t& token)\n    {\n        reference_tokens.push_back(token);\n    }\n\n    /// @brief append an unescaped token at the end of the reference pointer\n    /// @sa https://json.nlohmann.me/api/json_pointer/push_back/\n    void push_back(string_t&& token)\n    {\n        reference_tokens.push_back(std::move(token));\n    }\n\n    /// @brief return whether pointer points to the root document\n    /// @sa https://json.nlohmann.me/api/json_pointer/empty/\n    bool empty() const noexcept\n    {\n        return reference_tokens.empty();\n    }\n\n  private:\n    /*!\n    @param[in] s  reference token to be converted into an array index\n\n    @return integer representation of @a s\n\n    @throw parse_error.106  if an array index begins with '0'\n    @throw parse_error.109  if an array index begins not with a digit\n    @throw out_of_range.404 if string @a s could not be converted to an integer\n    @throw out_of_range.410 if an array index exceeds size_type\n    */\n    template<typename BasicJsonType>\n    static typename BasicJsonType::size_type array_index(const string_t& s)\n    {\n        using size_type = typename BasicJsonType::size_type;\n\n        // error condition (cf. RFC 6901, Sect. 4)\n        if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && s[0] == '0'))\n        {\n            JSON_THROW(detail::parse_error::create(106, 0, detail::concat(\"array index '\", s, \"' must not begin with '0'\"), nullptr));\n        }\n\n        // error condition (cf. RFC 6901, Sect. 4)\n        if (JSON_HEDLEY_UNLIKELY(s.size() > 1 && !(s[0] >= '1' && s[0] <= '9')))\n        {\n            JSON_THROW(detail::parse_error::create(109, 0, detail::concat(\"array index '\", s, \"' is not a number\"), nullptr));\n        }\n\n        const char* p = s.c_str();\n        char* p_end = nullptr; // NOLINT(misc-const-correctness)\n        errno = 0; // strtoull doesn't reset errno\n        const unsigned long long res = std::strtoull(p, &p_end, 10); // NOLINT(runtime/int)\n        if (p == p_end // invalid input or empty string\n                || errno == ERANGE // out of range\n                || JSON_HEDLEY_UNLIKELY(static_cast<std::size_t>(p_end - p) != s.size())) // incomplete read\n        {\n            JSON_THROW(detail::out_of_range::create(404, detail::concat(\"unresolved reference token '\", s, \"'\"), nullptr));\n        }\n\n        // only triggered on special platforms (like 32bit), see also\n        // https://github.com/nlohmann/json/pull/2203\n        if (res >= static_cast<unsigned long long>((std::numeric_limits<size_type>::max)()))  // NOLINT(runtime/int)\n        {\n            JSON_THROW(detail::out_of_range::create(410, detail::concat(\"array index \", s, \" exceeds size_type\"), nullptr));   // LCOV_EXCL_LINE\n        }\n\n        return static_cast<size_type>(res);\n    }\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    json_pointer top() const\n    {\n        if (JSON_HEDLEY_UNLIKELY(empty()))\n        {\n            JSON_THROW(detail::out_of_range::create(405, \"JSON pointer has no parent\", nullptr));\n        }\n\n        json_pointer result = *this;\n        result.reference_tokens = {reference_tokens[0]};\n        return result;\n    }\n\n  private:\n    /*!\n    @brief create and return a reference to the pointed to value\n\n    @complexity Linear in the number of reference tokens.\n\n    @throw parse_error.109 if array index is not a number\n    @throw type_error.313 if value cannot be unflattened\n    */\n    template<typename BasicJsonType>\n    BasicJsonType& get_and_create(BasicJsonType& j) const\n    {\n        auto* result = &j;\n\n        // in case no reference tokens exist, return a reference to the JSON value\n        // j which will be overwritten by a primitive value\n        for (const auto& reference_token : reference_tokens)\n        {\n            switch (result->type())\n            {\n                case detail::value_t::null:\n                {\n                    if (reference_token == \"0\")\n                    {\n                        // start a new array if reference token is 0\n                        result = &result->operator[](0);\n                    }\n                    else\n                    {\n                        // start a new object otherwise\n                        result = &result->operator[](reference_token);\n                    }\n                    break;\n                }\n\n                case detail::value_t::object:\n                {\n                    // create an entry in the object\n                    result = &result->operator[](reference_token);\n                    break;\n                }\n\n                case detail::value_t::array:\n                {\n                    // create an entry in the array\n                    result = &result->operator[](array_index<BasicJsonType>(reference_token));\n                    break;\n                }\n\n                /*\n                The following code is only reached if there exists a reference\n                token _and_ the current value is primitive. In this case, we have\n                an error situation, because primitive values may only occur as\n                single value; that is, with an empty list of reference tokens.\n                */\n                case detail::value_t::string:\n                case detail::value_t::boolean:\n                case detail::value_t::number_integer:\n                case detail::value_t::number_unsigned:\n                case detail::value_t::number_float:\n                case detail::value_t::binary:\n                case detail::value_t::discarded:\n                default:\n                    JSON_THROW(detail::type_error::create(313, \"invalid value to unflatten\", &j));\n            }\n        }\n\n        return *result;\n    }\n\n    /*!\n    @brief return a reference to the pointed to value\n\n    @note This version does not throw if a value is not present, but tries to\n          create nested values instead. For instance, calling this function\n          with pointer `\"/this/that\"` on a null value is equivalent to calling\n          `operator[](\"this\").operator[](\"that\")` on that value, effectively\n          changing the null value to an object.\n\n    @param[in] ptr  a JSON value\n\n    @return reference to the JSON value pointed to by the JSON pointer\n\n    @complexity Linear in the length of the JSON pointer.\n\n    @throw parse_error.106   if an array index begins with '0'\n    @throw parse_error.109   if an array index was not a number\n    @throw out_of_range.404  if the JSON pointer can not be resolved\n    */\n    template<typename BasicJsonType>\n    BasicJsonType& get_unchecked(BasicJsonType* ptr) const\n    {\n        for (const auto& reference_token : reference_tokens)\n        {\n            // convert null values to arrays or objects before continuing\n            if (ptr->is_null())\n            {\n                // check if reference token is a number\n                const bool nums =\n                    std::all_of(reference_token.begin(), reference_token.end(),\n                                [](const unsigned char x)\n                {\n                    return std::isdigit(x);\n                });\n\n                // change value to array for numbers or \"-\" or to object otherwise\n                *ptr = (nums || reference_token == \"-\")\n                       ? detail::value_t::array\n                       : detail::value_t::object;\n            }\n\n            switch (ptr->type())\n            {\n                case detail::value_t::object:\n                {\n                    // use unchecked object access\n                    ptr = &ptr->operator[](reference_token);\n                    break;\n                }\n\n                case detail::value_t::array:\n                {\n                    if (reference_token == \"-\")\n                    {\n                        // explicitly treat \"-\" as index beyond the end\n                        ptr = &ptr->operator[](ptr->m_data.m_value.array->size());\n                    }\n                    else\n                    {\n                        // convert array index to number; unchecked access\n                        ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));\n                    }\n                    break;\n                }\n\n                case detail::value_t::null:\n                case detail::value_t::string:\n                case detail::value_t::boolean:\n                case detail::value_t::number_integer:\n                case detail::value_t::number_unsigned:\n                case detail::value_t::number_float:\n                case detail::value_t::binary:\n                case detail::value_t::discarded:\n                default:\n                    JSON_THROW(detail::out_of_range::create(404, detail::concat(\"unresolved reference token '\", reference_token, \"'\"), ptr));\n            }\n        }\n\n        return *ptr;\n    }\n\n    /*!\n    @throw parse_error.106   if an array index begins with '0'\n    @throw parse_error.109   if an array index was not a number\n    @throw out_of_range.402  if the array index '-' is used\n    @throw out_of_range.404  if the JSON pointer can not be resolved\n    */\n    template<typename BasicJsonType>\n    BasicJsonType& get_checked(BasicJsonType* ptr) const\n    {\n        for (const auto& reference_token : reference_tokens)\n        {\n            switch (ptr->type())\n            {\n                case detail::value_t::object:\n                {\n                    // note: at performs range check\n                    ptr = &ptr->at(reference_token);\n                    break;\n                }\n\n                case detail::value_t::array:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(reference_token == \"-\"))\n                    {\n                        // \"-\" always fails the range check\n                        JSON_THROW(detail::out_of_range::create(402, detail::concat(\n                                \"array index '-' (\", std::to_string(ptr->m_data.m_value.array->size()),\n                                \") is out of range\"), ptr));\n                    }\n\n                    // note: at performs range check\n                    ptr = &ptr->at(array_index<BasicJsonType>(reference_token));\n                    break;\n                }\n\n                case detail::value_t::null:\n                case detail::value_t::string:\n                case detail::value_t::boolean:\n                case detail::value_t::number_integer:\n                case detail::value_t::number_unsigned:\n                case detail::value_t::number_float:\n                case detail::value_t::binary:\n                case detail::value_t::discarded:\n                default:\n                    JSON_THROW(detail::out_of_range::create(404, detail::concat(\"unresolved reference token '\", reference_token, \"'\"), ptr));\n            }\n        }\n\n        return *ptr;\n    }\n\n    /*!\n    @brief return a const reference to the pointed to value\n\n    @param[in] ptr  a JSON value\n\n    @return const reference to the JSON value pointed to by the JSON\n    pointer\n\n    @throw parse_error.106   if an array index begins with '0'\n    @throw parse_error.109   if an array index was not a number\n    @throw out_of_range.402  if the array index '-' is used\n    @throw out_of_range.404  if the JSON pointer can not be resolved\n    */\n    template<typename BasicJsonType>\n    const BasicJsonType& get_unchecked(const BasicJsonType* ptr) const\n    {\n        for (const auto& reference_token : reference_tokens)\n        {\n            switch (ptr->type())\n            {\n                case detail::value_t::object:\n                {\n                    // use unchecked object access\n                    ptr = &ptr->operator[](reference_token);\n                    break;\n                }\n\n                case detail::value_t::array:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(reference_token == \"-\"))\n                    {\n                        // \"-\" cannot be used for const access\n                        JSON_THROW(detail::out_of_range::create(402, detail::concat(\"array index '-' (\", std::to_string(ptr->m_data.m_value.array->size()), \") is out of range\"), ptr));\n                    }\n\n                    // use unchecked array access\n                    ptr = &ptr->operator[](array_index<BasicJsonType>(reference_token));\n                    break;\n                }\n\n                case detail::value_t::null:\n                case detail::value_t::string:\n                case detail::value_t::boolean:\n                case detail::value_t::number_integer:\n                case detail::value_t::number_unsigned:\n                case detail::value_t::number_float:\n                case detail::value_t::binary:\n                case detail::value_t::discarded:\n                default:\n                    JSON_THROW(detail::out_of_range::create(404, detail::concat(\"unresolved reference token '\", reference_token, \"'\"), ptr));\n            }\n        }\n\n        return *ptr;\n    }\n\n    /*!\n    @throw parse_error.106   if an array index begins with '0'\n    @throw parse_error.109   if an array index was not a number\n    @throw out_of_range.402  if the array index '-' is used\n    @throw out_of_range.404  if the JSON pointer can not be resolved\n    */\n    template<typename BasicJsonType>\n    const BasicJsonType& get_checked(const BasicJsonType* ptr) const\n    {\n        for (const auto& reference_token : reference_tokens)\n        {\n            switch (ptr->type())\n            {\n                case detail::value_t::object:\n                {\n                    // note: at performs range check\n                    ptr = &ptr->at(reference_token);\n                    break;\n                }\n\n                case detail::value_t::array:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(reference_token == \"-\"))\n                    {\n                        // \"-\" always fails the range check\n                        JSON_THROW(detail::out_of_range::create(402, detail::concat(\n                                \"array index '-' (\", std::to_string(ptr->m_data.m_value.array->size()),\n                                \") is out of range\"), ptr));\n                    }\n\n                    // note: at performs range check\n                    ptr = &ptr->at(array_index<BasicJsonType>(reference_token));\n                    break;\n                }\n\n                case detail::value_t::null:\n                case detail::value_t::string:\n                case detail::value_t::boolean:\n                case detail::value_t::number_integer:\n                case detail::value_t::number_unsigned:\n                case detail::value_t::number_float:\n                case detail::value_t::binary:\n                case detail::value_t::discarded:\n                default:\n                    JSON_THROW(detail::out_of_range::create(404, detail::concat(\"unresolved reference token '\", reference_token, \"'\"), ptr));\n            }\n        }\n\n        return *ptr;\n    }\n\n    /*!\n    @throw parse_error.106   if an array index begins with '0'\n    @throw parse_error.109   if an array index was not a number\n    */\n    template<typename BasicJsonType>\n    bool contains(const BasicJsonType* ptr) const\n    {\n        for (const auto& reference_token : reference_tokens)\n        {\n            switch (ptr->type())\n            {\n                case detail::value_t::object:\n                {\n                    if (!ptr->contains(reference_token))\n                    {\n                        // we did not find the key in the object\n                        return false;\n                    }\n\n                    ptr = &ptr->operator[](reference_token);\n                    break;\n                }\n\n                case detail::value_t::array:\n                {\n                    if (JSON_HEDLEY_UNLIKELY(reference_token == \"-\"))\n                    {\n                        // \"-\" always fails the range check\n                        return false;\n                    }\n                    if (JSON_HEDLEY_UNLIKELY(reference_token.size() == 1 && !(\"0\" <= reference_token && reference_token <= \"9\")))\n                    {\n                        // invalid char\n                        return false;\n                    }\n                    if (JSON_HEDLEY_UNLIKELY(reference_token.size() > 1))\n                    {\n                        if (JSON_HEDLEY_UNLIKELY(!('1' <= reference_token[0] && reference_token[0] <= '9')))\n                        {\n                            // first char should be between '1' and '9'\n                            return false;\n                        }\n                        for (std::size_t i = 1; i < reference_token.size(); i++)\n                        {\n                            if (JSON_HEDLEY_UNLIKELY(!('0' <= reference_token[i] && reference_token[i] <= '9')))\n                            {\n                                // other char should be between '0' and '9'\n                                return false;\n                            }\n                        }\n                    }\n\n                    const auto idx = array_index<BasicJsonType>(reference_token);\n                    if (idx >= ptr->size())\n                    {\n                        // index out of range\n                        return false;\n                    }\n\n                    ptr = &ptr->operator[](idx);\n                    break;\n                }\n\n                case detail::value_t::null:\n                case detail::value_t::string:\n                case detail::value_t::boolean:\n                case detail::value_t::number_integer:\n                case detail::value_t::number_unsigned:\n                case detail::value_t::number_float:\n                case detail::value_t::binary:\n                case detail::value_t::discarded:\n                default:\n                {\n                    // we do not expect primitive values if there is still a\n                    // reference token to process\n                    return false;\n                }\n            }\n        }\n\n        // no reference token left means we found a primitive value\n        return true;\n    }\n\n    /*!\n    @brief split the string input to reference tokens\n\n    @note This function is only called by the json_pointer constructor.\n          All exceptions below are documented there.\n\n    @throw parse_error.107  if the pointer is not empty or begins with '/'\n    @throw parse_error.108  if character '~' is not followed by '0' or '1'\n    */\n    static std::vector<string_t> split(const string_t& reference_string)\n    {\n        std::vector<string_t> result;\n\n        // special case: empty reference string -> no reference tokens\n        if (reference_string.empty())\n        {\n            return result;\n        }\n\n        // check if nonempty reference string begins with slash\n        if (JSON_HEDLEY_UNLIKELY(reference_string[0] != '/'))\n        {\n            JSON_THROW(detail::parse_error::create(107, 1, detail::concat(\"JSON pointer must be empty or begin with '/' - was: '\", reference_string, \"'\"), nullptr));\n        }\n\n        // extract the reference tokens:\n        // - slash: position of the last read slash (or end of string)\n        // - start: position after the previous slash\n        for (\n            // search for the first slash after the first character\n            std::size_t slash = reference_string.find_first_of('/', 1),\n            // set the beginning of the first reference token\n            start = 1;\n            // we can stop if start == 0 (if slash == string_t::npos)\n            start != 0;\n            // set the beginning of the next reference token\n            // (will eventually be 0 if slash == string_t::npos)\n            start = (slash == string_t::npos) ? 0 : slash + 1,\n            // find next slash\n            slash = reference_string.find_first_of('/', start))\n        {\n            // use the text between the beginning of the reference token\n            // (start) and the last slash (slash).\n            auto reference_token = reference_string.substr(start, slash - start);\n\n            // check reference tokens are properly escaped\n            for (std::size_t pos = reference_token.find_first_of('~');\n                    pos != string_t::npos;\n                    pos = reference_token.find_first_of('~', pos + 1))\n            {\n                JSON_ASSERT(reference_token[pos] == '~');\n\n                // ~ must be followed by 0 or 1\n                if (JSON_HEDLEY_UNLIKELY(pos == reference_token.size() - 1 ||\n                                         (reference_token[pos + 1] != '0' &&\n                                          reference_token[pos + 1] != '1')))\n                {\n                    JSON_THROW(detail::parse_error::create(108, 0, \"escape character '~' must be followed with '0' or '1'\", nullptr));\n                }\n            }\n\n            // finally, store the reference token\n            detail::unescape(reference_token);\n            result.push_back(reference_token);\n        }\n\n        return result;\n    }\n\n  private:\n    /*!\n    @param[in] reference_string  the reference string to the current value\n    @param[in] value             the value to consider\n    @param[in,out] result        the result object to insert values to\n\n    @note Empty objects or arrays are flattened to `null`.\n    */\n    template<typename BasicJsonType>\n    static void flatten(const string_t& reference_string,\n                        const BasicJsonType& value,\n                        BasicJsonType& result)\n    {\n        switch (value.type())\n        {\n            case detail::value_t::array:\n            {\n                if (value.m_data.m_value.array->empty())\n                {\n                    // flatten empty array as null\n                    result[reference_string] = nullptr;\n                }\n                else\n                {\n                    // iterate array and use index as reference string\n                    for (std::size_t i = 0; i < value.m_data.m_value.array->size(); ++i)\n                    {\n                        flatten(detail::concat<string_t>(reference_string, '/', std::to_string(i)),\n                                value.m_data.m_value.array->operator[](i), result);\n                    }\n                }\n                break;\n            }\n\n            case detail::value_t::object:\n            {\n                if (value.m_data.m_value.object->empty())\n                {\n                    // flatten empty object as null\n                    result[reference_string] = nullptr;\n                }\n                else\n                {\n                    // iterate object and use keys as reference string\n                    for (const auto& element : *value.m_data.m_value.object)\n                    {\n                        flatten(detail::concat<string_t>(reference_string, '/', detail::escape(element.first)), element.second, result);\n                    }\n                }\n                break;\n            }\n\n            case detail::value_t::null:\n            case detail::value_t::string:\n            case detail::value_t::boolean:\n            case detail::value_t::number_integer:\n            case detail::value_t::number_unsigned:\n            case detail::value_t::number_float:\n            case detail::value_t::binary:\n            case detail::value_t::discarded:\n            default:\n            {\n                // add primitive value with its reference string\n                result[reference_string] = value;\n                break;\n            }\n        }\n    }\n\n    /*!\n    @param[in] value  flattened JSON\n\n    @return unflattened JSON\n\n    @throw parse_error.109 if array index is not a number\n    @throw type_error.314  if value is not an object\n    @throw type_error.315  if object values are not primitive\n    @throw type_error.313  if value cannot be unflattened\n    */\n    template<typename BasicJsonType>\n    static BasicJsonType\n    unflatten(const BasicJsonType& value)\n    {\n        if (JSON_HEDLEY_UNLIKELY(!value.is_object()))\n        {\n            JSON_THROW(detail::type_error::create(314, \"only objects can be unflattened\", &value));\n        }\n\n        BasicJsonType result;\n\n        // iterate the JSON object values\n        for (const auto& element : *value.m_data.m_value.object)\n        {\n            if (JSON_HEDLEY_UNLIKELY(!element.second.is_primitive()))\n            {\n                JSON_THROW(detail::type_error::create(315, \"values in object must be primitive\", &element.second));\n            }\n\n            // assign value to reference pointed to by JSON pointer; Note that if\n            // the JSON pointer is \"\" (i.e., points to the whole value), function\n            // get_and_create returns a reference to result itself. An assignment\n            // will then create a primitive value.\n            json_pointer(element.first).get_and_create(result) = element.second;\n        }\n\n        return result;\n    }\n\n    // can't use conversion operator because of ambiguity\n    json_pointer<string_t> convert() const&\n    {\n        json_pointer<string_t> result;\n        result.reference_tokens = reference_tokens;\n        return result;\n    }\n\n    json_pointer<string_t> convert()&&\n    {\n        json_pointer<string_t> result;\n        result.reference_tokens = std::move(reference_tokens);\n        return result;\n    }\n\n  public:\n#if JSON_HAS_THREE_WAY_COMPARISON\n    /// @brief compares two JSON pointers for equality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/\n    template<typename RefStringTypeRhs>\n    bool operator==(const json_pointer<RefStringTypeRhs>& rhs) const noexcept\n    {\n        return reference_tokens == rhs.reference_tokens;\n    }\n\n    /// @brief compares JSON pointer and string for equality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer))\n    bool operator==(const string_t& rhs) const\n    {\n        return *this == json_pointer(rhs);\n    }\n\n    /// @brief 3-way compares two JSON pointers\n    template<typename RefStringTypeRhs>\n    std::strong_ordering operator<=>(const json_pointer<RefStringTypeRhs>& rhs) const noexcept // *NOPAD*\n    {\n        return  reference_tokens <=> rhs.reference_tokens; // *NOPAD*\n    }\n#else\n    /// @brief compares two JSON pointers for equality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/\n    template<typename RefStringTypeLhs, typename RefStringTypeRhs>\n    // NOLINTNEXTLINE(readability-redundant-declaration)\n    friend bool operator==(const json_pointer<RefStringTypeLhs>& lhs,\n                           const json_pointer<RefStringTypeRhs>& rhs) noexcept;\n\n    /// @brief compares JSON pointer and string for equality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/\n    template<typename RefStringTypeLhs, typename StringType>\n    // NOLINTNEXTLINE(readability-redundant-declaration)\n    friend bool operator==(const json_pointer<RefStringTypeLhs>& lhs,\n                           const StringType& rhs);\n\n    /// @brief compares string and JSON pointer for equality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_eq/\n    template<typename RefStringTypeRhs, typename StringType>\n    // NOLINTNEXTLINE(readability-redundant-declaration)\n    friend bool operator==(const StringType& lhs,\n                           const json_pointer<RefStringTypeRhs>& rhs);\n\n    /// @brief compares two JSON pointers for inequality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/\n    template<typename RefStringTypeLhs, typename RefStringTypeRhs>\n    // NOLINTNEXTLINE(readability-redundant-declaration)\n    friend bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,\n                           const json_pointer<RefStringTypeRhs>& rhs) noexcept;\n\n    /// @brief compares JSON pointer and string for inequality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/\n    template<typename RefStringTypeLhs, typename StringType>\n    // NOLINTNEXTLINE(readability-redundant-declaration)\n    friend bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,\n                           const StringType& rhs);\n\n    /// @brief compares string and JSON pointer for inequality\n    /// @sa https://json.nlohmann.me/api/json_pointer/operator_ne/\n    template<typename RefStringTypeRhs, typename StringType>\n    // NOLINTNEXTLINE(readability-redundant-declaration)\n    friend bool operator!=(const StringType& lhs,\n                           const json_pointer<RefStringTypeRhs>& rhs);\n\n    /// @brief compares two JSON pointer for less-than\n    template<typename RefStringTypeLhs, typename RefStringTypeRhs>\n    // NOLINTNEXTLINE(readability-redundant-declaration)\n    friend bool operator<(const json_pointer<RefStringTypeLhs>& lhs,\n                          const json_pointer<RefStringTypeRhs>& rhs) noexcept;\n#endif\n\n  private:\n    /// the reference tokens\n    std::vector<string_t> reference_tokens;\n};\n\n#if !JSON_HAS_THREE_WAY_COMPARISON\n// functions cannot be defined inside class due to ODR violations\ntemplate<typename RefStringTypeLhs, typename RefStringTypeRhs>\ninline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,\n                       const json_pointer<RefStringTypeRhs>& rhs) noexcept\n{\n    return lhs.reference_tokens == rhs.reference_tokens;\n}\n\ntemplate<typename RefStringTypeLhs,\n         typename StringType = typename json_pointer<RefStringTypeLhs>::string_t>\nJSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))\ninline bool operator==(const json_pointer<RefStringTypeLhs>& lhs,\n                       const StringType& rhs)\n{\n    return lhs == json_pointer<RefStringTypeLhs>(rhs);\n}\n\ntemplate<typename RefStringTypeRhs,\n         typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>\nJSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator==(json_pointer, json_pointer))\ninline bool operator==(const StringType& lhs,\n                       const json_pointer<RefStringTypeRhs>& rhs)\n{\n    return json_pointer<RefStringTypeRhs>(lhs) == rhs;\n}\n\ntemplate<typename RefStringTypeLhs, typename RefStringTypeRhs>\ninline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,\n                       const json_pointer<RefStringTypeRhs>& rhs) noexcept\n{\n    return !(lhs == rhs);\n}\n\ntemplate<typename RefStringTypeLhs,\n         typename StringType = typename json_pointer<RefStringTypeLhs>::string_t>\nJSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))\ninline bool operator!=(const json_pointer<RefStringTypeLhs>& lhs,\n                       const StringType& rhs)\n{\n    return !(lhs == rhs);\n}\n\ntemplate<typename RefStringTypeRhs,\n         typename StringType = typename json_pointer<RefStringTypeRhs>::string_t>\nJSON_HEDLEY_DEPRECATED_FOR(3.11.2, operator!=(json_pointer, json_pointer))\ninline bool operator!=(const StringType& lhs,\n                       const json_pointer<RefStringTypeRhs>& rhs)\n{\n    return !(lhs == rhs);\n}\n\ntemplate<typename RefStringTypeLhs, typename RefStringTypeRhs>\ninline bool operator<(const json_pointer<RefStringTypeLhs>& lhs,\n                      const json_pointer<RefStringTypeRhs>& rhs) noexcept\n{\n    return lhs.reference_tokens < rhs.reference_tokens;\n}\n#endif\n\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/json_ref.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <initializer_list>\n#include <utility>\n\n// #include <nlohmann/detail/abi_macros.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\ntemplate<typename BasicJsonType>\nclass json_ref\n{\n  public:\n    using value_type = BasicJsonType;\n\n    json_ref(value_type&& value)\n        : owned_value(std::move(value))\n    {}\n\n    json_ref(const value_type& value)\n        : value_ref(&value)\n    {}\n\n    json_ref(std::initializer_list<json_ref> init)\n        : owned_value(init)\n    {}\n\n    template <\n        class... Args,\n        enable_if_t<std::is_constructible<value_type, Args...>::value, int> = 0 >\n    json_ref(Args && ... args)\n        : owned_value(std::forward<Args>(args)...)\n    {}\n\n    // class should be movable only\n    json_ref(json_ref&&) noexcept = default;\n    json_ref(const json_ref&) = delete;\n    json_ref& operator=(const json_ref&) = delete;\n    json_ref& operator=(json_ref&&) = delete;\n    ~json_ref() = default;\n\n    value_type moved_or_copied() const\n    {\n        if (value_ref == nullptr)\n        {\n            return std::move(owned_value);\n        }\n        return *value_ref;\n    }\n\n    value_type const& operator*() const\n    {\n        return value_ref ? *value_ref : owned_value;\n    }\n\n    value_type const* operator->() const\n    {\n        return &** this;\n    }\n\n  private:\n    mutable value_type owned_value = nullptr;\n    value_type const* value_ref = nullptr;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/string_escape.hpp>\n\n// #include <nlohmann/detail/string_utils.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n// #include <nlohmann/detail/output/binary_writer.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // reverse\n#include <array> // array\n#include <map> // map\n#include <cmath> // isnan, isinf\n#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t\n#include <cstring> // memcpy\n#include <limits> // numeric_limits\n#include <string> // string\n#include <utility> // move\n#include <vector> // vector\n\n// #include <nlohmann/detail/input/binary_reader.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/output/output_adapters.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // copy\n#include <cstddef> // size_t\n#include <iterator> // back_inserter\n#include <memory> // shared_ptr, make_shared\n#include <string> // basic_string\n#include <vector> // vector\n\n#ifndef JSON_NO_IO\n    #include <ios>      // streamsize\n    #include <ostream>  // basic_ostream\n#endif  // JSON_NO_IO\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/// abstract output adapter interface\ntemplate<typename CharType> struct output_adapter_protocol\n{\n    virtual void write_character(CharType c) = 0;\n    virtual void write_characters(const CharType* s, std::size_t length) = 0;\n    virtual ~output_adapter_protocol() = default;\n\n    output_adapter_protocol() = default;\n    output_adapter_protocol(const output_adapter_protocol&) = default;\n    output_adapter_protocol(output_adapter_protocol&&) noexcept = default;\n    output_adapter_protocol& operator=(const output_adapter_protocol&) = default;\n    output_adapter_protocol& operator=(output_adapter_protocol&&) noexcept = default;\n};\n\n/// a type to simplify interfaces\ntemplate<typename CharType>\nusing output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>;\n\n/// output adapter for byte vectors\ntemplate<typename CharType, typename AllocatorType = std::allocator<CharType>>\nclass output_vector_adapter : public output_adapter_protocol<CharType>\n{\n  public:\n    explicit output_vector_adapter(std::vector<CharType, AllocatorType>& vec) noexcept\n        : v(vec)\n    {}\n\n    void write_character(CharType c) override\n    {\n        v.push_back(c);\n    }\n\n    JSON_HEDLEY_NON_NULL(2)\n    void write_characters(const CharType* s, std::size_t length) override\n    {\n        v.insert(v.end(), s, s + length);\n    }\n\n  private:\n    std::vector<CharType, AllocatorType>& v;\n};\n\n#ifndef JSON_NO_IO\n/// output adapter for output streams\ntemplate<typename CharType>\nclass output_stream_adapter : public output_adapter_protocol<CharType>\n{\n  public:\n    explicit output_stream_adapter(std::basic_ostream<CharType>& s) noexcept\n        : stream(s)\n    {}\n\n    void write_character(CharType c) override\n    {\n        stream.put(c);\n    }\n\n    JSON_HEDLEY_NON_NULL(2)\n    void write_characters(const CharType* s, std::size_t length) override\n    {\n        stream.write(s, static_cast<std::streamsize>(length));\n    }\n\n  private:\n    std::basic_ostream<CharType>& stream;\n};\n#endif  // JSON_NO_IO\n\n/// output adapter for basic_string\ntemplate<typename CharType, typename StringType = std::basic_string<CharType>>\nclass output_string_adapter : public output_adapter_protocol<CharType>\n{\n  public:\n    explicit output_string_adapter(StringType& s) noexcept\n        : str(s)\n    {}\n\n    void write_character(CharType c) override\n    {\n        str.push_back(c);\n    }\n\n    JSON_HEDLEY_NON_NULL(2)\n    void write_characters(const CharType* s, std::size_t length) override\n    {\n        str.append(s, length);\n    }\n\n  private:\n    StringType& str;\n};\n\ntemplate<typename CharType, typename StringType = std::basic_string<CharType>>\nclass output_adapter\n{\n  public:\n    template<typename AllocatorType = std::allocator<CharType>>\n    output_adapter(std::vector<CharType, AllocatorType>& vec)\n        : oa(std::make_shared<output_vector_adapter<CharType, AllocatorType>>(vec)) {}\n\n#ifndef JSON_NO_IO\n    output_adapter(std::basic_ostream<CharType>& s)\n        : oa(std::make_shared<output_stream_adapter<CharType>>(s)) {}\n#endif  // JSON_NO_IO\n\n    output_adapter(StringType& s)\n        : oa(std::make_shared<output_string_adapter<CharType, StringType>>(s)) {}\n\n    operator output_adapter_t<CharType>()\n    {\n        return oa;\n    }\n\n  private:\n    output_adapter_t<CharType> oa = nullptr;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/// how to encode BJData\nenum class bjdata_version_t\n{\n    draft2,\n    draft3,\n};\n\n///////////////////\n// binary writer //\n///////////////////\n\n/*!\n@brief serialization to CBOR and MessagePack values\n*/\ntemplate<typename BasicJsonType, typename CharType>\nclass binary_writer\n{\n    using string_t = typename BasicJsonType::string_t;\n    using binary_t = typename BasicJsonType::binary_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n\n  public:\n    /*!\n    @brief create a binary writer\n\n    @param[in] adapter  output adapter to write to\n    */\n    explicit binary_writer(output_adapter_t<CharType> adapter) : oa(std::move(adapter))\n    {\n        JSON_ASSERT(oa);\n    }\n\n    /*!\n    @param[in] j  JSON value to serialize\n    @pre       j.type() == value_t::object\n    */\n    void write_bson(const BasicJsonType& j)\n    {\n        switch (j.type())\n        {\n            case value_t::object:\n            {\n                write_bson_object(*j.m_data.m_value.object);\n                break;\n            }\n\n            case value_t::null:\n            case value_t::array:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                JSON_THROW(type_error::create(317, concat(\"to serialize to BSON, top-level type must be object, but is \", j.type_name()), &j));\n            }\n        }\n    }\n\n    /*!\n    @param[in] j  JSON value to serialize\n    */\n    void write_cbor(const BasicJsonType& j)\n    {\n        switch (j.type())\n        {\n            case value_t::null:\n            {\n                oa->write_character(to_char_type(0xF6));\n                break;\n            }\n\n            case value_t::boolean:\n            {\n                oa->write_character(j.m_data.m_value.boolean\n                                    ? to_char_type(0xF5)\n                                    : to_char_type(0xF4));\n                break;\n            }\n\n            case value_t::number_integer:\n            {\n                if (j.m_data.m_value.number_integer >= 0)\n                {\n                    // CBOR does not differentiate between positive signed\n                    // integers and unsigned integers. Therefore, we used the\n                    // code from the value_t::number_unsigned case here.\n                    if (j.m_data.m_value.number_integer <= 0x17)\n                    {\n                        write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)())\n                    {\n                        oa->write_character(to_char_type(0x18));\n                        write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)())\n                    {\n                        oa->write_character(to_char_type(0x19));\n                        write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)())\n                    {\n                        oa->write_character(to_char_type(0x1A));\n                        write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));\n                    }\n                    else\n                    {\n                        oa->write_character(to_char_type(0x1B));\n                        write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));\n                    }\n                }\n                else\n                {\n                    // The conversions below encode the sign in the first\n                    // byte, and the value is converted to a positive number.\n                    const auto positive_number = -1 - j.m_data.m_value.number_integer;\n                    if (j.m_data.m_value.number_integer >= -24)\n                    {\n                        write_number(static_cast<std::uint8_t>(0x20 + positive_number));\n                    }\n                    else if (positive_number <= (std::numeric_limits<std::uint8_t>::max)())\n                    {\n                        oa->write_character(to_char_type(0x38));\n                        write_number(static_cast<std::uint8_t>(positive_number));\n                    }\n                    else if (positive_number <= (std::numeric_limits<std::uint16_t>::max)())\n                    {\n                        oa->write_character(to_char_type(0x39));\n                        write_number(static_cast<std::uint16_t>(positive_number));\n                    }\n                    else if (positive_number <= (std::numeric_limits<std::uint32_t>::max)())\n                    {\n                        oa->write_character(to_char_type(0x3A));\n                        write_number(static_cast<std::uint32_t>(positive_number));\n                    }\n                    else\n                    {\n                        oa->write_character(to_char_type(0x3B));\n                        write_number(static_cast<std::uint64_t>(positive_number));\n                    }\n                }\n                break;\n            }\n\n            case value_t::number_unsigned:\n            {\n                if (j.m_data.m_value.number_unsigned <= 0x17)\n                {\n                    write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));\n                }\n                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x18));\n                    write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_unsigned));\n                }\n                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x19));\n                    write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_unsigned));\n                }\n                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x1A));\n                    write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_unsigned));\n                }\n                else\n                {\n                    oa->write_character(to_char_type(0x1B));\n                    write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned));\n                }\n                break;\n            }\n\n            case value_t::number_float:\n            {\n                if (std::isnan(j.m_data.m_value.number_float))\n                {\n                    // NaN is 0xf97e00 in CBOR\n                    oa->write_character(to_char_type(0xF9));\n                    oa->write_character(to_char_type(0x7E));\n                    oa->write_character(to_char_type(0x00));\n                }\n                else if (std::isinf(j.m_data.m_value.number_float))\n                {\n                    // Infinity is 0xf97c00, -Infinity is 0xf9fc00\n                    oa->write_character(to_char_type(0xf9));\n                    oa->write_character(j.m_data.m_value.number_float > 0 ? to_char_type(0x7C) : to_char_type(0xFC));\n                    oa->write_character(to_char_type(0x00));\n                }\n                else\n                {\n                    write_compact_float(j.m_data.m_value.number_float, detail::input_format_t::cbor);\n                }\n                break;\n            }\n\n            case value_t::string:\n            {\n                // step 1: write control byte and the string length\n                const auto N = j.m_data.m_value.string->size();\n                if (N <= 0x17)\n                {\n                    write_number(static_cast<std::uint8_t>(0x60 + N));\n                }\n                else if (N <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x78));\n                    write_number(static_cast<std::uint8_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x79));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x7A));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n                // LCOV_EXCL_START\n                else if (N <= (std::numeric_limits<std::uint64_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x7B));\n                    write_number(static_cast<std::uint64_t>(N));\n                }\n                // LCOV_EXCL_STOP\n\n                // step 2: write the string\n                oa->write_characters(\n                    reinterpret_cast<const CharType*>(j.m_data.m_value.string->c_str()),\n                    j.m_data.m_value.string->size());\n                break;\n            }\n\n            case value_t::array:\n            {\n                // step 1: write control byte and the array size\n                const auto N = j.m_data.m_value.array->size();\n                if (N <= 0x17)\n                {\n                    write_number(static_cast<std::uint8_t>(0x80 + N));\n                }\n                else if (N <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x98));\n                    write_number(static_cast<std::uint8_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x99));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x9A));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n                // LCOV_EXCL_START\n                else if (N <= (std::numeric_limits<std::uint64_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x9B));\n                    write_number(static_cast<std::uint64_t>(N));\n                }\n                // LCOV_EXCL_STOP\n\n                // step 2: write each element\n                for (const auto& el : *j.m_data.m_value.array)\n                {\n                    write_cbor(el);\n                }\n                break;\n            }\n\n            case value_t::binary:\n            {\n                if (j.m_data.m_value.binary->has_subtype())\n                {\n                    if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint8_t>::max)())\n                    {\n                        write_number(static_cast<std::uint8_t>(0xd8));\n                        write_number(static_cast<std::uint8_t>(j.m_data.m_value.binary->subtype()));\n                    }\n                    else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint16_t>::max)())\n                    {\n                        write_number(static_cast<std::uint8_t>(0xd9));\n                        write_number(static_cast<std::uint16_t>(j.m_data.m_value.binary->subtype()));\n                    }\n                    else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint32_t>::max)())\n                    {\n                        write_number(static_cast<std::uint8_t>(0xda));\n                        write_number(static_cast<std::uint32_t>(j.m_data.m_value.binary->subtype()));\n                    }\n                    else if (j.m_data.m_value.binary->subtype() <= (std::numeric_limits<std::uint64_t>::max)())\n                    {\n                        write_number(static_cast<std::uint8_t>(0xdb));\n                        write_number(static_cast<std::uint64_t>(j.m_data.m_value.binary->subtype()));\n                    }\n                }\n\n                // step 1: write control byte and the binary array size\n                const auto N = j.m_data.m_value.binary->size();\n                if (N <= 0x17)\n                {\n                    write_number(static_cast<std::uint8_t>(0x40 + N));\n                }\n                else if (N <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x58));\n                    write_number(static_cast<std::uint8_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x59));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x5A));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n                // LCOV_EXCL_START\n                else if (N <= (std::numeric_limits<std::uint64_t>::max)())\n                {\n                    oa->write_character(to_char_type(0x5B));\n                    write_number(static_cast<std::uint64_t>(N));\n                }\n                // LCOV_EXCL_STOP\n\n                // step 2: write each element\n                oa->write_characters(\n                    reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),\n                    N);\n\n                break;\n            }\n\n            case value_t::object:\n            {\n                // step 1: write control byte and the object size\n                const auto N = j.m_data.m_value.object->size();\n                if (N <= 0x17)\n                {\n                    write_number(static_cast<std::uint8_t>(0xA0 + N));\n                }\n                else if (N <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    oa->write_character(to_char_type(0xB8));\n                    write_number(static_cast<std::uint8_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    oa->write_character(to_char_type(0xB9));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    oa->write_character(to_char_type(0xBA));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n                // LCOV_EXCL_START\n                else if (N <= (std::numeric_limits<std::uint64_t>::max)())\n                {\n                    oa->write_character(to_char_type(0xBB));\n                    write_number(static_cast<std::uint64_t>(N));\n                }\n                // LCOV_EXCL_STOP\n\n                // step 2: write each element\n                for (const auto& el : *j.m_data.m_value.object)\n                {\n                    write_cbor(el.first);\n                    write_cbor(el.second);\n                }\n                break;\n            }\n\n            case value_t::discarded:\n            default:\n                break;\n        }\n    }\n\n    /*!\n    @param[in] j  JSON value to serialize\n    */\n    void write_msgpack(const BasicJsonType& j)\n    {\n        switch (j.type())\n        {\n            case value_t::null: // nil\n            {\n                oa->write_character(to_char_type(0xC0));\n                break;\n            }\n\n            case value_t::boolean: // true and false\n            {\n                oa->write_character(j.m_data.m_value.boolean\n                                    ? to_char_type(0xC3)\n                                    : to_char_type(0xC2));\n                break;\n            }\n\n            case value_t::number_integer:\n            {\n                if (j.m_data.m_value.number_integer >= 0)\n                {\n                    // MessagePack does not differentiate between positive\n                    // signed integers and unsigned integers. Therefore, we used\n                    // the code from the value_t::number_unsigned case here.\n                    if (j.m_data.m_value.number_unsigned < 128)\n                    {\n                        // positive fixnum\n                        write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())\n                    {\n                        // uint 8\n                        oa->write_character(to_char_type(0xCC));\n                        write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())\n                    {\n                        // uint 16\n                        oa->write_character(to_char_type(0xCD));\n                        write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())\n                    {\n                        // uint 32\n                        oa->write_character(to_char_type(0xCE));\n                        write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())\n                    {\n                        // uint 64\n                        oa->write_character(to_char_type(0xCF));\n                        write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));\n                    }\n                }\n                else\n                {\n                    if (j.m_data.m_value.number_integer >= -32)\n                    {\n                        // negative fixnum\n                        write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int8_t>::min)() &&\n                             j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())\n                    {\n                        // int 8\n                        oa->write_character(to_char_type(0xD0));\n                        write_number(static_cast<std::int8_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int16_t>::min)() &&\n                             j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())\n                    {\n                        // int 16\n                        oa->write_character(to_char_type(0xD1));\n                        write_number(static_cast<std::int16_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int32_t>::min)() &&\n                             j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())\n                    {\n                        // int 32\n                        oa->write_character(to_char_type(0xD2));\n                        write_number(static_cast<std::int32_t>(j.m_data.m_value.number_integer));\n                    }\n                    else if (j.m_data.m_value.number_integer >= (std::numeric_limits<std::int64_t>::min)() &&\n                             j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())\n                    {\n                        // int 64\n                        oa->write_character(to_char_type(0xD3));\n                        write_number(static_cast<std::int64_t>(j.m_data.m_value.number_integer));\n                    }\n                }\n                break;\n            }\n\n            case value_t::number_unsigned:\n            {\n                if (j.m_data.m_value.number_unsigned < 128)\n                {\n                    // positive fixnum\n                    write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));\n                }\n                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    // uint 8\n                    oa->write_character(to_char_type(0xCC));\n                    write_number(static_cast<std::uint8_t>(j.m_data.m_value.number_integer));\n                }\n                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    // uint 16\n                    oa->write_character(to_char_type(0xCD));\n                    write_number(static_cast<std::uint16_t>(j.m_data.m_value.number_integer));\n                }\n                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    // uint 32\n                    oa->write_character(to_char_type(0xCE));\n                    write_number(static_cast<std::uint32_t>(j.m_data.m_value.number_integer));\n                }\n                else if (j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())\n                {\n                    // uint 64\n                    oa->write_character(to_char_type(0xCF));\n                    write_number(static_cast<std::uint64_t>(j.m_data.m_value.number_integer));\n                }\n                break;\n            }\n\n            case value_t::number_float:\n            {\n                write_compact_float(j.m_data.m_value.number_float, detail::input_format_t::msgpack);\n                break;\n            }\n\n            case value_t::string:\n            {\n                // step 1: write control byte and the string length\n                const auto N = j.m_data.m_value.string->size();\n                if (N <= 31)\n                {\n                    // fixstr\n                    write_number(static_cast<std::uint8_t>(0xA0 | N));\n                }\n                else if (N <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    // str 8\n                    oa->write_character(to_char_type(0xD9));\n                    write_number(static_cast<std::uint8_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    // str 16\n                    oa->write_character(to_char_type(0xDA));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    // str 32\n                    oa->write_character(to_char_type(0xDB));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n\n                // step 2: write the string\n                oa->write_characters(\n                    reinterpret_cast<const CharType*>(j.m_data.m_value.string->c_str()),\n                    j.m_data.m_value.string->size());\n                break;\n            }\n\n            case value_t::array:\n            {\n                // step 1: write control byte and the array size\n                const auto N = j.m_data.m_value.array->size();\n                if (N <= 15)\n                {\n                    // fixarray\n                    write_number(static_cast<std::uint8_t>(0x90 | N));\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    // array 16\n                    oa->write_character(to_char_type(0xDC));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    // array 32\n                    oa->write_character(to_char_type(0xDD));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n\n                // step 2: write each element\n                for (const auto& el : *j.m_data.m_value.array)\n                {\n                    write_msgpack(el);\n                }\n                break;\n            }\n\n            case value_t::binary:\n            {\n                // step 0: determine if the binary type has a set subtype to\n                // determine whether to use the ext or fixext types\n                const bool use_ext = j.m_data.m_value.binary->has_subtype();\n\n                // step 1: write control byte and the byte string length\n                const auto N = j.m_data.m_value.binary->size();\n                if (N <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    std::uint8_t output_type{};\n                    bool fixed = true;\n                    if (use_ext)\n                    {\n                        switch (N)\n                        {\n                            case 1:\n                                output_type = 0xD4; // fixext 1\n                                break;\n                            case 2:\n                                output_type = 0xD5; // fixext 2\n                                break;\n                            case 4:\n                                output_type = 0xD6; // fixext 4\n                                break;\n                            case 8:\n                                output_type = 0xD7; // fixext 8\n                                break;\n                            case 16:\n                                output_type = 0xD8; // fixext 16\n                                break;\n                            default:\n                                output_type = 0xC7; // ext 8\n                                fixed = false;\n                                break;\n                        }\n\n                    }\n                    else\n                    {\n                        output_type = 0xC4; // bin 8\n                        fixed = false;\n                    }\n\n                    oa->write_character(to_char_type(output_type));\n                    if (!fixed)\n                    {\n                        write_number(static_cast<std::uint8_t>(N));\n                    }\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    const std::uint8_t output_type = use_ext\n                                                     ? 0xC8 // ext 16\n                                                     : 0xC5; // bin 16\n\n                    oa->write_character(to_char_type(output_type));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    const std::uint8_t output_type = use_ext\n                                                     ? 0xC9 // ext 32\n                                                     : 0xC6; // bin 32\n\n                    oa->write_character(to_char_type(output_type));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n\n                // step 1.5: if this is an ext type, write the subtype\n                if (use_ext)\n                {\n                    write_number(static_cast<std::int8_t>(j.m_data.m_value.binary->subtype()));\n                }\n\n                // step 2: write the byte string\n                oa->write_characters(\n                    reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),\n                    N);\n\n                break;\n            }\n\n            case value_t::object:\n            {\n                // step 1: write control byte and the object size\n                const auto N = j.m_data.m_value.object->size();\n                if (N <= 15)\n                {\n                    // fixmap\n                    write_number(static_cast<std::uint8_t>(0x80 | (N & 0xF)));\n                }\n                else if (N <= (std::numeric_limits<std::uint16_t>::max)())\n                {\n                    // map 16\n                    oa->write_character(to_char_type(0xDE));\n                    write_number(static_cast<std::uint16_t>(N));\n                }\n                else if (N <= (std::numeric_limits<std::uint32_t>::max)())\n                {\n                    // map 32\n                    oa->write_character(to_char_type(0xDF));\n                    write_number(static_cast<std::uint32_t>(N));\n                }\n\n                // step 2: write each element\n                for (const auto& el : *j.m_data.m_value.object)\n                {\n                    write_msgpack(el.first);\n                    write_msgpack(el.second);\n                }\n                break;\n            }\n\n            case value_t::discarded:\n            default:\n                break;\n        }\n    }\n\n    /*!\n    @param[in] j  JSON value to serialize\n    @param[in] use_count   whether to use '#' prefixes (optimized format)\n    @param[in] use_type    whether to use '$' prefixes (optimized format)\n    @param[in] add_prefix  whether prefixes need to be used for this value\n    @param[in] use_bjdata  whether write in BJData format, default is false\n    @param[in] bjdata_version  which BJData version to use, default is draft2\n    */\n    void write_ubjson(const BasicJsonType& j, const bool use_count,\n                      const bool use_type, const bool add_prefix = true,\n                      const bool use_bjdata = false, const bjdata_version_t bjdata_version = bjdata_version_t::draft2)\n    {\n        const bool bjdata_draft3 = use_bjdata && bjdata_version == bjdata_version_t::draft3;\n\n        switch (j.type())\n        {\n            case value_t::null:\n            {\n                if (add_prefix)\n                {\n                    oa->write_character(to_char_type('Z'));\n                }\n                break;\n            }\n\n            case value_t::boolean:\n            {\n                if (add_prefix)\n                {\n                    oa->write_character(j.m_data.m_value.boolean\n                                        ? to_char_type('T')\n                                        : to_char_type('F'));\n                }\n                break;\n            }\n\n            case value_t::number_integer:\n            {\n                write_number_with_ubjson_prefix(j.m_data.m_value.number_integer, add_prefix, use_bjdata);\n                break;\n            }\n\n            case value_t::number_unsigned:\n            {\n                write_number_with_ubjson_prefix(j.m_data.m_value.number_unsigned, add_prefix, use_bjdata);\n                break;\n            }\n\n            case value_t::number_float:\n            {\n                write_number_with_ubjson_prefix(j.m_data.m_value.number_float, add_prefix, use_bjdata);\n                break;\n            }\n\n            case value_t::string:\n            {\n                if (add_prefix)\n                {\n                    oa->write_character(to_char_type('S'));\n                }\n                write_number_with_ubjson_prefix(j.m_data.m_value.string->size(), true, use_bjdata);\n                oa->write_characters(\n                    reinterpret_cast<const CharType*>(j.m_data.m_value.string->c_str()),\n                    j.m_data.m_value.string->size());\n                break;\n            }\n\n            case value_t::array:\n            {\n                if (add_prefix)\n                {\n                    oa->write_character(to_char_type('['));\n                }\n\n                bool prefix_required = true;\n                if (use_type && !j.m_data.m_value.array->empty())\n                {\n                    JSON_ASSERT(use_count);\n                    const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);\n                    const bool same_prefix = std::all_of(j.begin() + 1, j.end(),\n                                                         [this, first_prefix, use_bjdata](const BasicJsonType & v)\n                    {\n                        return ubjson_prefix(v, use_bjdata) == first_prefix;\n                    });\n\n                    std::vector<CharType> bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type\n\n                    if (same_prefix && !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end()))\n                    {\n                        prefix_required = false;\n                        oa->write_character(to_char_type('$'));\n                        oa->write_character(first_prefix);\n                    }\n                }\n\n                if (use_count)\n                {\n                    oa->write_character(to_char_type('#'));\n                    write_number_with_ubjson_prefix(j.m_data.m_value.array->size(), true, use_bjdata);\n                }\n\n                for (const auto& el : *j.m_data.m_value.array)\n                {\n                    write_ubjson(el, use_count, use_type, prefix_required, use_bjdata, bjdata_version);\n                }\n\n                if (!use_count)\n                {\n                    oa->write_character(to_char_type(']'));\n                }\n\n                break;\n            }\n\n            case value_t::binary:\n            {\n                if (add_prefix)\n                {\n                    oa->write_character(to_char_type('['));\n                }\n\n                if (use_type && (bjdata_draft3 || !j.m_data.m_value.binary->empty()))\n                {\n                    JSON_ASSERT(use_count);\n                    oa->write_character(to_char_type('$'));\n                    oa->write_character(bjdata_draft3 ? 'B' : 'U');\n                }\n\n                if (use_count)\n                {\n                    oa->write_character(to_char_type('#'));\n                    write_number_with_ubjson_prefix(j.m_data.m_value.binary->size(), true, use_bjdata);\n                }\n\n                if (use_type)\n                {\n                    oa->write_characters(\n                        reinterpret_cast<const CharType*>(j.m_data.m_value.binary->data()),\n                        j.m_data.m_value.binary->size());\n                }\n                else\n                {\n                    for (size_t i = 0; i < j.m_data.m_value.binary->size(); ++i)\n                    {\n                        oa->write_character(to_char_type(bjdata_draft3 ? 'B' : 'U'));\n                        oa->write_character(j.m_data.m_value.binary->data()[i]);\n                    }\n                }\n\n                if (!use_count)\n                {\n                    oa->write_character(to_char_type(']'));\n                }\n\n                break;\n            }\n\n            case value_t::object:\n            {\n                if (use_bjdata && j.m_data.m_value.object->size() == 3 && j.m_data.m_value.object->find(\"_ArrayType_\") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find(\"_ArraySize_\") != j.m_data.m_value.object->end() && j.m_data.m_value.object->find(\"_ArrayData_\") != j.m_data.m_value.object->end())\n                {\n                    if (!write_bjdata_ndarray(*j.m_data.m_value.object, use_count, use_type, bjdata_version))  // decode bjdata ndarray in the JData format (https://github.com/NeuroJSON/jdata)\n                    {\n                        break;\n                    }\n                }\n\n                if (add_prefix)\n                {\n                    oa->write_character(to_char_type('{'));\n                }\n\n                bool prefix_required = true;\n                if (use_type && !j.m_data.m_value.object->empty())\n                {\n                    JSON_ASSERT(use_count);\n                    const CharType first_prefix = ubjson_prefix(j.front(), use_bjdata);\n                    const bool same_prefix = std::all_of(j.begin(), j.end(),\n                                                         [this, first_prefix, use_bjdata](const BasicJsonType & v)\n                    {\n                        return ubjson_prefix(v, use_bjdata) == first_prefix;\n                    });\n\n                    std::vector<CharType> bjdx = {'[', '{', 'S', 'H', 'T', 'F', 'N', 'Z'}; // excluded markers in bjdata optimized type\n\n                    if (same_prefix && !(use_bjdata && std::find(bjdx.begin(), bjdx.end(), first_prefix) != bjdx.end()))\n                    {\n                        prefix_required = false;\n                        oa->write_character(to_char_type('$'));\n                        oa->write_character(first_prefix);\n                    }\n                }\n\n                if (use_count)\n                {\n                    oa->write_character(to_char_type('#'));\n                    write_number_with_ubjson_prefix(j.m_data.m_value.object->size(), true, use_bjdata);\n                }\n\n                for (const auto& el : *j.m_data.m_value.object)\n                {\n                    write_number_with_ubjson_prefix(el.first.size(), true, use_bjdata);\n                    oa->write_characters(\n                        reinterpret_cast<const CharType*>(el.first.c_str()),\n                        el.first.size());\n                    write_ubjson(el.second, use_count, use_type, prefix_required, use_bjdata, bjdata_version);\n                }\n\n                if (!use_count)\n                {\n                    oa->write_character(to_char_type('}'));\n                }\n\n                break;\n            }\n\n            case value_t::discarded:\n            default:\n                break;\n        }\n    }\n\n  private:\n    //////////\n    // BSON //\n    //////////\n\n    /*!\n    @return The size of a BSON document entry header, including the id marker\n            and the entry name size (and its null-terminator).\n    */\n    static std::size_t calc_bson_entry_header_size(const string_t& name, const BasicJsonType& j)\n    {\n        const auto it = name.find(static_cast<typename string_t::value_type>(0));\n        if (JSON_HEDLEY_UNLIKELY(it != BasicJsonType::string_t::npos))\n        {\n            JSON_THROW(out_of_range::create(409, concat(\"BSON key cannot contain code point U+0000 (at byte \", std::to_string(it), \")\"), &j));\n            static_cast<void>(j);\n        }\n\n        return /*id*/ 1ul + name.size() + /*zero-terminator*/1u;\n    }\n\n    /*!\n    @brief Writes the given @a element_type and @a name to the output adapter\n    */\n    void write_bson_entry_header(const string_t& name,\n                                 const std::uint8_t element_type)\n    {\n        oa->write_character(to_char_type(element_type)); // boolean\n        oa->write_characters(\n            reinterpret_cast<const CharType*>(name.c_str()),\n            name.size() + 1u);\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and boolean value @a value\n    */\n    void write_bson_boolean(const string_t& name,\n                            const bool value)\n    {\n        write_bson_entry_header(name, 0x08);\n        oa->write_character(value ? to_char_type(0x01) : to_char_type(0x00));\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and double value @a value\n    */\n    void write_bson_double(const string_t& name,\n                           const double value)\n    {\n        write_bson_entry_header(name, 0x01);\n        write_number<double>(value, true);\n    }\n\n    /*!\n    @return The size of the BSON-encoded string in @a value\n    */\n    static std::size_t calc_bson_string_size(const string_t& value)\n    {\n        return sizeof(std::int32_t) + value.size() + 1ul;\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and string value @a value\n    */\n    void write_bson_string(const string_t& name,\n                           const string_t& value)\n    {\n        write_bson_entry_header(name, 0x02);\n\n        write_number<std::int32_t>(static_cast<std::int32_t>(value.size() + 1ul), true);\n        oa->write_characters(\n            reinterpret_cast<const CharType*>(value.c_str()),\n            value.size() + 1);\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and null value\n    */\n    void write_bson_null(const string_t& name)\n    {\n        write_bson_entry_header(name, 0x0A);\n    }\n\n    /*!\n    @return The size of the BSON-encoded integer @a value\n    */\n    static std::size_t calc_bson_integer_size(const std::int64_t value)\n    {\n        return (std::numeric_limits<std::int32_t>::min)() <= value && value <= (std::numeric_limits<std::int32_t>::max)()\n               ? sizeof(std::int32_t)\n               : sizeof(std::int64_t);\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and integer @a value\n    */\n    void write_bson_integer(const string_t& name,\n                            const std::int64_t value)\n    {\n        if ((std::numeric_limits<std::int32_t>::min)() <= value && value <= (std::numeric_limits<std::int32_t>::max)())\n        {\n            write_bson_entry_header(name, 0x10); // int32\n            write_number<std::int32_t>(static_cast<std::int32_t>(value), true);\n        }\n        else\n        {\n            write_bson_entry_header(name, 0x12); // int64\n            write_number<std::int64_t>(static_cast<std::int64_t>(value), true);\n        }\n    }\n\n    /*!\n    @return The size of the BSON-encoded unsigned integer in @a j\n    */\n    static constexpr std::size_t calc_bson_unsigned_size(const std::uint64_t value) noexcept\n    {\n        return (value <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))\n               ? sizeof(std::int32_t)\n               : sizeof(std::int64_t);\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and unsigned @a value\n    */\n    void write_bson_unsigned(const string_t& name,\n                             const BasicJsonType& j)\n    {\n        if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))\n        {\n            write_bson_entry_header(name, 0x10 /* int32 */);\n            write_number<std::int32_t>(static_cast<std::int32_t>(j.m_data.m_value.number_unsigned), true);\n        }\n        else if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))\n        {\n            write_bson_entry_header(name, 0x12 /* int64 */);\n            write_number<std::int64_t>(static_cast<std::int64_t>(j.m_data.m_value.number_unsigned), true);\n        }\n        else\n        {\n            write_bson_entry_header(name, 0x11 /* uint64 */);\n            write_number<std::uint64_t>(static_cast<std::uint64_t>(j.m_data.m_value.number_unsigned), true);\n        }\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and object @a value\n    */\n    void write_bson_object_entry(const string_t& name,\n                                 const typename BasicJsonType::object_t& value)\n    {\n        write_bson_entry_header(name, 0x03); // object\n        write_bson_object(value);\n    }\n\n    /*!\n    @return The size of the BSON-encoded array @a value\n    */\n    static std::size_t calc_bson_array_size(const typename BasicJsonType::array_t& value)\n    {\n        std::size_t array_index = 0ul;\n\n        const std::size_t embedded_document_size = std::accumulate(std::begin(value), std::end(value), static_cast<std::size_t>(0), [&array_index](std::size_t result, const typename BasicJsonType::array_t::value_type & el)\n        {\n            return result + calc_bson_element_size(std::to_string(array_index++), el);\n        });\n\n        return sizeof(std::int32_t) + embedded_document_size + 1ul;\n    }\n\n    /*!\n    @return The size of the BSON-encoded binary array @a value\n    */\n    static std::size_t calc_bson_binary_size(const typename BasicJsonType::binary_t& value)\n    {\n        return sizeof(std::int32_t) + value.size() + 1ul;\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and array @a value\n    */\n    void write_bson_array(const string_t& name,\n                          const typename BasicJsonType::array_t& value)\n    {\n        write_bson_entry_header(name, 0x04); // array\n        write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_array_size(value)), true);\n\n        std::size_t array_index = 0ul;\n\n        for (const auto& el : value)\n        {\n            write_bson_element(std::to_string(array_index++), el);\n        }\n\n        oa->write_character(to_char_type(0x00));\n    }\n\n    /*!\n    @brief Writes a BSON element with key @a name and binary value @a value\n    */\n    void write_bson_binary(const string_t& name,\n                           const binary_t& value)\n    {\n        write_bson_entry_header(name, 0x05);\n\n        write_number<std::int32_t>(static_cast<std::int32_t>(value.size()), true);\n        write_number(value.has_subtype() ? static_cast<std::uint8_t>(value.subtype()) : static_cast<std::uint8_t>(0x00));\n\n        oa->write_characters(reinterpret_cast<const CharType*>(value.data()), value.size());\n    }\n\n    /*!\n    @brief Calculates the size necessary to serialize the JSON value @a j with its @a name\n    @return The calculated size for the BSON document entry for @a j with the given @a name.\n    */\n    static std::size_t calc_bson_element_size(const string_t& name,\n            const BasicJsonType& j)\n    {\n        const auto header_size = calc_bson_entry_header_size(name, j);\n        switch (j.type())\n        {\n            case value_t::object:\n                return header_size + calc_bson_object_size(*j.m_data.m_value.object);\n\n            case value_t::array:\n                return header_size + calc_bson_array_size(*j.m_data.m_value.array);\n\n            case value_t::binary:\n                return header_size + calc_bson_binary_size(*j.m_data.m_value.binary);\n\n            case value_t::boolean:\n                return header_size + 1ul;\n\n            case value_t::number_float:\n                return header_size + 8ul;\n\n            case value_t::number_integer:\n                return header_size + calc_bson_integer_size(j.m_data.m_value.number_integer);\n\n            case value_t::number_unsigned:\n                return header_size + calc_bson_unsigned_size(j.m_data.m_value.number_unsigned);\n\n            case value_t::string:\n                return header_size + calc_bson_string_size(*j.m_data.m_value.string);\n\n            case value_t::null:\n                return header_size + 0ul;\n\n            // LCOV_EXCL_START\n            case value_t::discarded:\n            default:\n                JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)\n                return 0ul;\n                // LCOV_EXCL_STOP\n        }\n    }\n\n    /*!\n    @brief Serializes the JSON value @a j to BSON and associates it with the\n           key @a name.\n    @param name The name to associate with the JSON entity @a j within the\n                current BSON document\n    */\n    void write_bson_element(const string_t& name,\n                            const BasicJsonType& j)\n    {\n        switch (j.type())\n        {\n            case value_t::object:\n                return write_bson_object_entry(name, *j.m_data.m_value.object);\n\n            case value_t::array:\n                return write_bson_array(name, *j.m_data.m_value.array);\n\n            case value_t::binary:\n                return write_bson_binary(name, *j.m_data.m_value.binary);\n\n            case value_t::boolean:\n                return write_bson_boolean(name, j.m_data.m_value.boolean);\n\n            case value_t::number_float:\n                return write_bson_double(name, j.m_data.m_value.number_float);\n\n            case value_t::number_integer:\n                return write_bson_integer(name, j.m_data.m_value.number_integer);\n\n            case value_t::number_unsigned:\n                return write_bson_unsigned(name, j);\n\n            case value_t::string:\n                return write_bson_string(name, *j.m_data.m_value.string);\n\n            case value_t::null:\n                return write_bson_null(name);\n\n            // LCOV_EXCL_START\n            case value_t::discarded:\n            default:\n                JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert)\n                return;\n                // LCOV_EXCL_STOP\n        }\n    }\n\n    /*!\n    @brief Calculates the size of the BSON serialization of the given\n           JSON-object @a j.\n    @param[in] value  JSON value to serialize\n    @pre       value.type() == value_t::object\n    */\n    static std::size_t calc_bson_object_size(const typename BasicJsonType::object_t& value)\n    {\n        const std::size_t document_size = std::accumulate(value.begin(), value.end(), static_cast<std::size_t>(0),\n                                          [](size_t result, const typename BasicJsonType::object_t::value_type & el)\n        {\n            return result += calc_bson_element_size(el.first, el.second);\n        });\n\n        return sizeof(std::int32_t) + document_size + 1ul;\n    }\n\n    /*!\n    @param[in] value  JSON value to serialize\n    @pre       value.type() == value_t::object\n    */\n    void write_bson_object(const typename BasicJsonType::object_t& value)\n    {\n        write_number<std::int32_t>(static_cast<std::int32_t>(calc_bson_object_size(value)), true);\n\n        for (const auto& el : value)\n        {\n            write_bson_element(el.first, el.second);\n        }\n\n        oa->write_character(to_char_type(0x00));\n    }\n\n    //////////\n    // CBOR //\n    //////////\n\n    static constexpr CharType get_cbor_float_prefix(float /*unused*/)\n    {\n        return to_char_type(0xFA);  // Single-Precision Float\n    }\n\n    static constexpr CharType get_cbor_float_prefix(double /*unused*/)\n    {\n        return to_char_type(0xFB);  // Double-Precision Float\n    }\n\n    /////////////\n    // MsgPack //\n    /////////////\n\n    static constexpr CharType get_msgpack_float_prefix(float /*unused*/)\n    {\n        return to_char_type(0xCA);  // float 32\n    }\n\n    static constexpr CharType get_msgpack_float_prefix(double /*unused*/)\n    {\n        return to_char_type(0xCB);  // float 64\n    }\n\n    ////////////\n    // UBJSON //\n    ////////////\n\n    // UBJSON: write number (floating point)\n    template<typename NumberType, typename std::enable_if<\n                 std::is_floating_point<NumberType>::value, int>::type = 0>\n    void write_number_with_ubjson_prefix(const NumberType n,\n                                         const bool add_prefix,\n                                         const bool use_bjdata)\n    {\n        if (add_prefix)\n        {\n            oa->write_character(get_ubjson_float_prefix(n));\n        }\n        write_number(n, use_bjdata);\n    }\n\n    // UBJSON: write number (unsigned integer)\n    template<typename NumberType, typename std::enable_if<\n                 std::is_unsigned<NumberType>::value, int>::type = 0>\n    void write_number_with_ubjson_prefix(const NumberType n,\n                                         const bool add_prefix,\n                                         const bool use_bjdata)\n    {\n        if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int8_t>::max)()))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('i'));  // int8\n            }\n            write_number(static_cast<std::uint8_t>(n), use_bjdata);\n        }\n        else if (n <= (std::numeric_limits<std::uint8_t>::max)())\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('U'));  // uint8\n            }\n            write_number(static_cast<std::uint8_t>(n), use_bjdata);\n        }\n        else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int16_t>::max)()))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('I'));  // int16\n            }\n            write_number(static_cast<std::int16_t>(n), use_bjdata);\n        }\n        else if (use_bjdata && n <= static_cast<uint64_t>((std::numeric_limits<uint16_t>::max)()))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('u'));  // uint16 - bjdata only\n            }\n            write_number(static_cast<std::uint16_t>(n), use_bjdata);\n        }\n        else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('l'));  // int32\n            }\n            write_number(static_cast<std::int32_t>(n), use_bjdata);\n        }\n        else if (use_bjdata && n <= static_cast<uint64_t>((std::numeric_limits<uint32_t>::max)()))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('m'));  // uint32 - bjdata only\n            }\n            write_number(static_cast<std::uint32_t>(n), use_bjdata);\n        }\n        else if (n <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('L'));  // int64\n            }\n            write_number(static_cast<std::int64_t>(n), use_bjdata);\n        }\n        else if (use_bjdata && n <= (std::numeric_limits<uint64_t>::max)())\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('M'));  // uint64 - bjdata only\n            }\n            write_number(static_cast<std::uint64_t>(n), use_bjdata);\n        }\n        else\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('H'));  // high-precision number\n            }\n\n            const auto number = BasicJsonType(n).dump();\n            write_number_with_ubjson_prefix(number.size(), true, use_bjdata);\n            for (std::size_t i = 0; i < number.size(); ++i)\n            {\n                oa->write_character(to_char_type(static_cast<std::uint8_t>(number[i])));\n            }\n        }\n    }\n\n    // UBJSON: write number (signed integer)\n    template < typename NumberType, typename std::enable_if <\n                   std::is_signed<NumberType>::value&&\n                   !std::is_floating_point<NumberType>::value, int >::type = 0 >\n    void write_number_with_ubjson_prefix(const NumberType n,\n                                         const bool add_prefix,\n                                         const bool use_bjdata)\n    {\n        if ((std::numeric_limits<std::int8_t>::min)() <= n && n <= (std::numeric_limits<std::int8_t>::max)())\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('i'));  // int8\n            }\n            write_number(static_cast<std::int8_t>(n), use_bjdata);\n        }\n        else if (static_cast<std::int64_t>((std::numeric_limits<std::uint8_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint8_t>::max)()))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('U'));  // uint8\n            }\n            write_number(static_cast<std::uint8_t>(n), use_bjdata);\n        }\n        else if ((std::numeric_limits<std::int16_t>::min)() <= n && n <= (std::numeric_limits<std::int16_t>::max)())\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('I'));  // int16\n            }\n            write_number(static_cast<std::int16_t>(n), use_bjdata);\n        }\n        else if (use_bjdata && (static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint16_t>::max)())))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('u'));  // uint16 - bjdata only\n            }\n            write_number(static_cast<uint16_t>(n), use_bjdata);\n        }\n        else if ((std::numeric_limits<std::int32_t>::min)() <= n && n <= (std::numeric_limits<std::int32_t>::max)())\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('l'));  // int32\n            }\n            write_number(static_cast<std::int32_t>(n), use_bjdata);\n        }\n        else if (use_bjdata && (static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::min)()) <= n && n <= static_cast<std::int64_t>((std::numeric_limits<std::uint32_t>::max)())))\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('m'));  // uint32 - bjdata only\n            }\n            write_number(static_cast<uint32_t>(n), use_bjdata);\n        }\n        else if ((std::numeric_limits<std::int64_t>::min)() <= n && n <= (std::numeric_limits<std::int64_t>::max)())\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('L'));  // int64\n            }\n            write_number(static_cast<std::int64_t>(n), use_bjdata);\n        }\n        // LCOV_EXCL_START\n        else\n        {\n            if (add_prefix)\n            {\n                oa->write_character(to_char_type('H'));  // high-precision number\n            }\n\n            const auto number = BasicJsonType(n).dump();\n            write_number_with_ubjson_prefix(number.size(), true, use_bjdata);\n            for (std::size_t i = 0; i < number.size(); ++i)\n            {\n                oa->write_character(to_char_type(static_cast<std::uint8_t>(number[i])));\n            }\n        }\n        // LCOV_EXCL_STOP\n    }\n\n    /*!\n    @brief determine the type prefix of container values\n    */\n    CharType ubjson_prefix(const BasicJsonType& j, const bool use_bjdata) const noexcept\n    {\n        switch (j.type())\n        {\n            case value_t::null:\n                return 'Z';\n\n            case value_t::boolean:\n                return j.m_data.m_value.boolean ? 'T' : 'F';\n\n            case value_t::number_integer:\n            {\n                if ((std::numeric_limits<std::int8_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int8_t>::max)())\n                {\n                    return 'i';\n                }\n                if ((std::numeric_limits<std::uint8_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint8_t>::max)())\n                {\n                    return 'U';\n                }\n                if ((std::numeric_limits<std::int16_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int16_t>::max)())\n                {\n                    return 'I';\n                }\n                if (use_bjdata && ((std::numeric_limits<std::uint16_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint16_t>::max)()))\n                {\n                    return 'u';\n                }\n                if ((std::numeric_limits<std::int32_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int32_t>::max)())\n                {\n                    return 'l';\n                }\n                if (use_bjdata && ((std::numeric_limits<std::uint32_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::uint32_t>::max)()))\n                {\n                    return 'm';\n                }\n                if ((std::numeric_limits<std::int64_t>::min)() <= j.m_data.m_value.number_integer && j.m_data.m_value.number_integer <= (std::numeric_limits<std::int64_t>::max)())\n                {\n                    return 'L';\n                }\n                // anything else is treated as high-precision number\n                return 'H'; // LCOV_EXCL_LINE\n            }\n\n            case value_t::number_unsigned:\n            {\n                if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int8_t>::max)()))\n                {\n                    return 'i';\n                }\n                if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint8_t>::max)()))\n                {\n                    return 'U';\n                }\n                if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int16_t>::max)()))\n                {\n                    return 'I';\n                }\n                if (use_bjdata && j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint16_t>::max)()))\n                {\n                    return 'u';\n                }\n                if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int32_t>::max)()))\n                {\n                    return 'l';\n                }\n                if (use_bjdata && j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::uint32_t>::max)()))\n                {\n                    return 'm';\n                }\n                if (j.m_data.m_value.number_unsigned <= static_cast<std::uint64_t>((std::numeric_limits<std::int64_t>::max)()))\n                {\n                    return 'L';\n                }\n                if (use_bjdata && j.m_data.m_value.number_unsigned <= (std::numeric_limits<std::uint64_t>::max)())\n                {\n                    return 'M';\n                }\n                // anything else is treated as high-precision number\n                return 'H'; // LCOV_EXCL_LINE\n            }\n\n            case value_t::number_float:\n                return get_ubjson_float_prefix(j.m_data.m_value.number_float);\n\n            case value_t::string:\n                return 'S';\n\n            case value_t::array: // fallthrough\n            case value_t::binary:\n                return '[';\n\n            case value_t::object:\n                return '{';\n\n            case value_t::discarded:\n            default:  // discarded values\n                return 'N';\n        }\n    }\n\n    static constexpr CharType get_ubjson_float_prefix(float /*unused*/)\n    {\n        return 'd';  // float 32\n    }\n\n    static constexpr CharType get_ubjson_float_prefix(double /*unused*/)\n    {\n        return 'D';  // float 64\n    }\n\n    /*!\n    @return false if the object is successfully converted to a bjdata ndarray, true if the type or size is invalid\n    */\n    bool write_bjdata_ndarray(const typename BasicJsonType::object_t& value, const bool use_count, const bool use_type, const bjdata_version_t bjdata_version)\n    {\n        std::map<string_t, CharType> bjdtype = {{\"uint8\", 'U'},  {\"int8\", 'i'},  {\"uint16\", 'u'}, {\"int16\", 'I'},\n            {\"uint32\", 'm'}, {\"int32\", 'l'}, {\"uint64\", 'M'}, {\"int64\", 'L'}, {\"single\", 'd'}, {\"double\", 'D'},\n            {\"char\", 'C'}, {\"byte\", 'B'}\n        };\n\n        string_t key = \"_ArrayType_\";\n        auto it = bjdtype.find(static_cast<string_t>(value.at(key)));\n        if (it == bjdtype.end())\n        {\n            return true;\n        }\n        CharType dtype = it->second;\n\n        key = \"_ArraySize_\";\n        std::size_t len = (value.at(key).empty() ? 0 : 1);\n        for (const auto& el : value.at(key))\n        {\n            len *= static_cast<std::size_t>(el.m_data.m_value.number_unsigned);\n        }\n\n        key = \"_ArrayData_\";\n        if (value.at(key).size() != len)\n        {\n            return true;\n        }\n\n        oa->write_character('[');\n        oa->write_character('$');\n        oa->write_character(dtype);\n        oa->write_character('#');\n\n        key = \"_ArraySize_\";\n        write_ubjson(value.at(key), use_count, use_type, true,  true, bjdata_version);\n\n        key = \"_ArrayData_\";\n        if (dtype == 'U' || dtype == 'C' || dtype == 'B')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::uint8_t>(el.m_data.m_value.number_unsigned), true);\n            }\n        }\n        else if (dtype == 'i')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::int8_t>(el.m_data.m_value.number_integer), true);\n            }\n        }\n        else if (dtype == 'u')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::uint16_t>(el.m_data.m_value.number_unsigned), true);\n            }\n        }\n        else if (dtype == 'I')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::int16_t>(el.m_data.m_value.number_integer), true);\n            }\n        }\n        else if (dtype == 'm')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::uint32_t>(el.m_data.m_value.number_unsigned), true);\n            }\n        }\n        else if (dtype == 'l')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::int32_t>(el.m_data.m_value.number_integer), true);\n            }\n        }\n        else if (dtype == 'M')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::uint64_t>(el.m_data.m_value.number_unsigned), true);\n            }\n        }\n        else if (dtype == 'L')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<std::int64_t>(el.m_data.m_value.number_integer), true);\n            }\n        }\n        else if (dtype == 'd')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<float>(el.m_data.m_value.number_float), true);\n            }\n        }\n        else if (dtype == 'D')\n        {\n            for (const auto& el : value.at(key))\n            {\n                write_number(static_cast<double>(el.m_data.m_value.number_float), true);\n            }\n        }\n        return false;\n    }\n\n    ///////////////////////\n    // Utility functions //\n    ///////////////////////\n\n    /*\n    @brief write a number to output input\n    @param[in] n number of type @a NumberType\n    @param[in] OutputIsLittleEndian Set to true if output data is\n                                 required to be little endian\n    @tparam NumberType the type of the number\n\n    @note This function needs to respect the system's endianness, because bytes\n          in CBOR, MessagePack, and UBJSON are stored in network order (big\n          endian) and therefore need reordering on little endian systems.\n          On the other hand, BSON and BJData use little endian and should reorder\n          on big endian systems.\n    */\n    template<typename NumberType>\n    void write_number(const NumberType n, const bool OutputIsLittleEndian = false)\n    {\n        // step 1: write number to array of length NumberType\n        std::array<CharType, sizeof(NumberType)> vec{};\n        std::memcpy(vec.data(), &n, sizeof(NumberType));\n\n        // step 2: write array to output (with possible reordering)\n        if (is_little_endian != OutputIsLittleEndian)\n        {\n            // reverse byte order prior to conversion if necessary\n            std::reverse(vec.begin(), vec.end());\n        }\n\n        oa->write_characters(vec.data(), sizeof(NumberType));\n    }\n\n    void write_compact_float(const number_float_t n, detail::input_format_t format)\n    {\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n        if (static_cast<double>(n) >= static_cast<double>(std::numeric_limits<float>::lowest()) &&\n                static_cast<double>(n) <= static_cast<double>((std::numeric_limits<float>::max)()) &&\n                static_cast<double>(static_cast<float>(n)) == static_cast<double>(n))\n        {\n            oa->write_character(format == detail::input_format_t::cbor\n                                ? get_cbor_float_prefix(static_cast<float>(n))\n                                : get_msgpack_float_prefix(static_cast<float>(n)));\n            write_number(static_cast<float>(n));\n        }\n        else\n        {\n            oa->write_character(format == detail::input_format_t::cbor\n                                ? get_cbor_float_prefix(n)\n                                : get_msgpack_float_prefix(n));\n            write_number(n);\n        }\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif\n    }\n\n  public:\n    // The following to_char_type functions are implement the conversion\n    // between uint8_t and CharType. In case CharType is not unsigned,\n    // such a conversion is required to allow values greater than 128.\n    // See <https://github.com/nlohmann/json/issues/1286> for a discussion.\n    template < typename C = CharType,\n               enable_if_t < std::is_signed<C>::value && std::is_signed<char>::value > * = nullptr >\n    static constexpr CharType to_char_type(std::uint8_t x) noexcept\n    {\n        return *reinterpret_cast<char*>(&x);\n    }\n\n    template < typename C = CharType,\n               enable_if_t < std::is_signed<C>::value && std::is_unsigned<char>::value > * = nullptr >\n    static CharType to_char_type(std::uint8_t x) noexcept\n    {\n        static_assert(sizeof(std::uint8_t) == sizeof(CharType), \"size of CharType must be equal to std::uint8_t\");\n        static_assert(std::is_trivial<CharType>::value, \"CharType must be trivial\");\n        CharType result;\n        std::memcpy(&result, &x, sizeof(x));\n        return result;\n    }\n\n    template<typename C = CharType,\n             enable_if_t<std::is_unsigned<C>::value>* = nullptr>\n    static constexpr CharType to_char_type(std::uint8_t x) noexcept\n    {\n        return x;\n    }\n\n    template < typename InputCharType, typename C = CharType,\n               enable_if_t <\n                   std::is_signed<C>::value &&\n                   std::is_signed<char>::value &&\n                   std::is_same<char, typename std::remove_cv<InputCharType>::type>::value\n                   > * = nullptr >\n    static constexpr CharType to_char_type(InputCharType x) noexcept\n    {\n        return x;\n    }\n\n  private:\n    /// whether we can assume little endianness\n    const bool is_little_endian = little_endianness();\n\n    /// the output\n    output_adapter_t<CharType> oa = nullptr;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/output/output_adapters.hpp>\n\n// #include <nlohmann/detail/output/serializer.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2008 - 2009 Björn Hoehrmann <bjoern@hoehrmann.de>\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <algorithm> // reverse, remove, fill, find, none_of\n#include <array> // array\n#include <clocale> // localeconv, lconv\n#include <cmath> // labs, isfinite, isnan, signbit\n#include <cstddef> // size_t, ptrdiff_t\n#include <cstdint> // uint8_t\n#include <cstdio> // snprintf\n#include <limits> // numeric_limits\n#include <string> // string, char_traits\n#include <iomanip> // setfill, setw\n#include <type_traits> // is_same\n#include <utility> // move\n\n// #include <nlohmann/detail/conversions/to_chars.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2009 Florian Loitsch <https://florian.loitsch.com/>\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <array> // array\n#include <cmath>   // signbit, isfinite\n#include <cstdint> // intN_t, uintN_t\n#include <cstring> // memcpy, memmove\n#include <limits> // numeric_limits\n#include <type_traits> // conditional\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n/*!\n@brief implements the Grisu2 algorithm for binary to decimal floating-point\nconversion.\n\nThis implementation is a slightly modified version of the reference\nimplementation which may be obtained from\nhttp://florian.loitsch.com/publications (bench.tar.gz).\n\nThe code is distributed under the MIT license, Copyright (c) 2009 Florian Loitsch.\n\nFor a detailed description of the algorithm see:\n\n[1] Loitsch, \"Printing Floating-Point Numbers Quickly and Accurately with\n    Integers\", Proceedings of the ACM SIGPLAN 2010 Conference on Programming\n    Language Design and Implementation, PLDI 2010\n[2] Burger, Dybvig, \"Printing Floating-Point Numbers Quickly and Accurately\",\n    Proceedings of the ACM SIGPLAN 1996 Conference on Programming Language\n    Design and Implementation, PLDI 1996\n*/\nnamespace dtoa_impl\n{\n\ntemplate<typename Target, typename Source>\nTarget reinterpret_bits(const Source source)\n{\n    static_assert(sizeof(Target) == sizeof(Source), \"size mismatch\");\n\n    Target target;\n    std::memcpy(&target, &source, sizeof(Source));\n    return target;\n}\n\nstruct diyfp // f * 2^e\n{\n    static constexpr int kPrecision = 64; // = q\n\n    std::uint64_t f = 0;\n    int e = 0;\n\n    constexpr diyfp(std::uint64_t f_, int e_) noexcept : f(f_), e(e_) {}\n\n    /*!\n    @brief returns x - y\n    @pre x.e == y.e and x.f >= y.f\n    */\n    static diyfp sub(const diyfp& x, const diyfp& y) noexcept\n    {\n        JSON_ASSERT(x.e == y.e);\n        JSON_ASSERT(x.f >= y.f);\n\n        return {x.f - y.f, x.e};\n    }\n\n    /*!\n    @brief returns x * y\n    @note The result is rounded. (Only the upper q bits are returned.)\n    */\n    static diyfp mul(const diyfp& x, const diyfp& y) noexcept\n    {\n        static_assert(kPrecision == 64, \"internal error\");\n\n        // Computes:\n        //  f = round((x.f * y.f) / 2^q)\n        //  e = x.e + y.e + q\n\n        // Emulate the 64-bit * 64-bit multiplication:\n        //\n        // p = u * v\n        //   = (u_lo + 2^32 u_hi) (v_lo + 2^32 v_hi)\n        //   = (u_lo v_lo         ) + 2^32 ((u_lo v_hi         ) + (u_hi v_lo         )) + 2^64 (u_hi v_hi         )\n        //   = (p0                ) + 2^32 ((p1                ) + (p2                )) + 2^64 (p3                )\n        //   = (p0_lo + 2^32 p0_hi) + 2^32 ((p1_lo + 2^32 p1_hi) + (p2_lo + 2^32 p2_hi)) + 2^64 (p3                )\n        //   = (p0_lo             ) + 2^32 (p0_hi + p1_lo + p2_lo                      ) + 2^64 (p1_hi + p2_hi + p3)\n        //   = (p0_lo             ) + 2^32 (Q                                          ) + 2^64 (H                 )\n        //   = (p0_lo             ) + 2^32 (Q_lo + 2^32 Q_hi                           ) + 2^64 (H                 )\n        //\n        // (Since Q might be larger than 2^32 - 1)\n        //\n        //   = (p0_lo + 2^32 Q_lo) + 2^64 (Q_hi + H)\n        //\n        // (Q_hi + H does not overflow a 64-bit int)\n        //\n        //   = p_lo + 2^64 p_hi\n\n        const std::uint64_t u_lo = x.f & 0xFFFFFFFFu;\n        const std::uint64_t u_hi = x.f >> 32u;\n        const std::uint64_t v_lo = y.f & 0xFFFFFFFFu;\n        const std::uint64_t v_hi = y.f >> 32u;\n\n        const std::uint64_t p0 = u_lo * v_lo;\n        const std::uint64_t p1 = u_lo * v_hi;\n        const std::uint64_t p2 = u_hi * v_lo;\n        const std::uint64_t p3 = u_hi * v_hi;\n\n        const std::uint64_t p0_hi = p0 >> 32u;\n        const std::uint64_t p1_lo = p1 & 0xFFFFFFFFu;\n        const std::uint64_t p1_hi = p1 >> 32u;\n        const std::uint64_t p2_lo = p2 & 0xFFFFFFFFu;\n        const std::uint64_t p2_hi = p2 >> 32u;\n\n        std::uint64_t Q = p0_hi + p1_lo + p2_lo;\n\n        // The full product might now be computed as\n        //\n        // p_hi = p3 + p2_hi + p1_hi + (Q >> 32)\n        // p_lo = p0_lo + (Q << 32)\n        //\n        // But in this particular case here, the full p_lo is not required.\n        // Effectively we only need to add the highest bit in p_lo to p_hi (and\n        // Q_hi + 1 does not overflow).\n\n        Q += std::uint64_t{1} << (64u - 32u - 1u); // round, ties up\n\n        const std::uint64_t h = p3 + p2_hi + p1_hi + (Q >> 32u);\n\n        return {h, x.e + y.e + 64};\n    }\n\n    /*!\n    @brief normalize x such that the significand is >= 2^(q-1)\n    @pre x.f != 0\n    */\n    static diyfp normalize(diyfp x) noexcept\n    {\n        JSON_ASSERT(x.f != 0);\n\n        while ((x.f >> 63u) == 0)\n        {\n            x.f <<= 1u;\n            x.e--;\n        }\n\n        return x;\n    }\n\n    /*!\n    @brief normalize x such that the result has the exponent E\n    @pre e >= x.e and the upper e - x.e bits of x.f must be zero.\n    */\n    static diyfp normalize_to(const diyfp& x, const int target_exponent) noexcept\n    {\n        const int delta = x.e - target_exponent;\n\n        JSON_ASSERT(delta >= 0);\n        JSON_ASSERT(((x.f << delta) >> delta) == x.f);\n\n        return {x.f << delta, target_exponent};\n    }\n};\n\nstruct boundaries\n{\n    diyfp w;\n    diyfp minus;\n    diyfp plus;\n};\n\n/*!\nCompute the (normalized) diyfp representing the input number 'value' and its\nboundaries.\n\n@pre value must be finite and positive\n*/\ntemplate<typename FloatType>\nboundaries compute_boundaries(FloatType value)\n{\n    JSON_ASSERT(std::isfinite(value));\n    JSON_ASSERT(value > 0);\n\n    // Convert the IEEE representation into a diyfp.\n    //\n    // If v is denormal:\n    //      value = 0.F * 2^(1 - bias) = (          F) * 2^(1 - bias - (p-1))\n    // If v is normalized:\n    //      value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1))\n\n    static_assert(std::numeric_limits<FloatType>::is_iec559,\n                  \"internal error: dtoa_short requires an IEEE-754 floating-point implementation\");\n\n    constexpr int      kPrecision = std::numeric_limits<FloatType>::digits; // = p (includes the hidden bit)\n    constexpr int      kBias      = std::numeric_limits<FloatType>::max_exponent - 1 + (kPrecision - 1);\n    constexpr int      kMinExp    = 1 - kBias;\n    constexpr std::uint64_t kHiddenBit = std::uint64_t{1} << (kPrecision - 1); // = 2^(p-1)\n\n    using bits_type = typename std::conditional<kPrecision == 24, std::uint32_t, std::uint64_t >::type;\n\n    const auto bits = static_cast<std::uint64_t>(reinterpret_bits<bits_type>(value));\n    const std::uint64_t E = bits >> (kPrecision - 1);\n    const std::uint64_t F = bits & (kHiddenBit - 1);\n\n    const bool is_denormal = E == 0;\n    const diyfp v = is_denormal\n                    ? diyfp(F, kMinExp)\n                    : diyfp(F + kHiddenBit, static_cast<int>(E) - kBias);\n\n    // Compute the boundaries m- and m+ of the floating-point value\n    // v = f * 2^e.\n    //\n    // Determine v- and v+, the floating-point predecessor and successor if v,\n    // respectively.\n    //\n    //      v- = v - 2^e        if f != 2^(p-1) or e == e_min                (A)\n    //         = v - 2^(e-1)    if f == 2^(p-1) and e > e_min                (B)\n    //\n    //      v+ = v + 2^e\n    //\n    // Let m- = (v- + v) / 2 and m+ = (v + v+) / 2. All real numbers _strictly_\n    // between m- and m+ round to v, regardless of how the input rounding\n    // algorithm breaks ties.\n    //\n    //      ---+-------------+-------------+-------------+-------------+---  (A)\n    //         v-            m-            v             m+            v+\n    //\n    //      -----------------+------+------+-------------+-------------+---  (B)\n    //                       v-     m-     v             m+            v+\n\n    const bool lower_boundary_is_closer = F == 0 && E > 1;\n    const diyfp m_plus = diyfp((2 * v.f) + 1, v.e - 1);\n    const diyfp m_minus = lower_boundary_is_closer\n                          ? diyfp((4 * v.f) - 1, v.e - 2)  // (B)\n                          : diyfp((2 * v.f) - 1, v.e - 1); // (A)\n\n    // Determine the normalized w+ = m+.\n    const diyfp w_plus = diyfp::normalize(m_plus);\n\n    // Determine w- = m- such that e_(w-) = e_(w+).\n    const diyfp w_minus = diyfp::normalize_to(m_minus, w_plus.e);\n\n    return {diyfp::normalize(v), w_minus, w_plus};\n}\n\n// Given normalized diyfp w, Grisu needs to find a (normalized) cached\n// power-of-ten c, such that the exponent of the product c * w = f * 2^e lies\n// within a certain range [alpha, gamma] (Definition 3.2 from [1])\n//\n//      alpha <= e = e_c + e_w + q <= gamma\n//\n// or\n//\n//      f_c * f_w * 2^alpha <= f_c 2^(e_c) * f_w 2^(e_w) * 2^q\n//                          <= f_c * f_w * 2^gamma\n//\n// Since c and w are normalized, i.e. 2^(q-1) <= f < 2^q, this implies\n//\n//      2^(q-1) * 2^(q-1) * 2^alpha <= c * w * 2^q < 2^q * 2^q * 2^gamma\n//\n// or\n//\n//      2^(q - 2 + alpha) <= c * w < 2^(q + gamma)\n//\n// The choice of (alpha,gamma) determines the size of the table and the form of\n// the digit generation procedure. Using (alpha,gamma)=(-60,-32) works out well\n// in practice:\n//\n// The idea is to cut the number c * w = f * 2^e into two parts, which can be\n// processed independently: An integral part p1, and a fractional part p2:\n//\n//      f * 2^e = ( (f div 2^-e) * 2^-e + (f mod 2^-e) ) * 2^e\n//              = (f div 2^-e) + (f mod 2^-e) * 2^e\n//              = p1 + p2 * 2^e\n//\n// The conversion of p1 into decimal form requires a series of divisions and\n// modulos by (a power of) 10. These operations are faster for 32-bit than for\n// 64-bit integers, so p1 should ideally fit into a 32-bit integer. This can be\n// achieved by choosing\n//\n//      -e >= 32   or   e <= -32 := gamma\n//\n// In order to convert the fractional part\n//\n//      p2 * 2^e = p2 / 2^-e = d[-1] / 10^1 + d[-2] / 10^2 + ...\n//\n// into decimal form, the fraction is repeatedly multiplied by 10 and the digits\n// d[-i] are extracted in order:\n//\n//      (10 * p2) div 2^-e = d[-1]\n//      (10 * p2) mod 2^-e = d[-2] / 10^1 + ...\n//\n// The multiplication by 10 must not overflow. It is sufficient to choose\n//\n//      10 * p2 < 16 * p2 = 2^4 * p2 <= 2^64.\n//\n// Since p2 = f mod 2^-e < 2^-e,\n//\n//      -e <= 60   or   e >= -60 := alpha\n\nconstexpr int kAlpha = -60;\nconstexpr int kGamma = -32;\n\nstruct cached_power // c = f * 2^e ~= 10^k\n{\n    std::uint64_t f;\n    int e;\n    int k;\n};\n\n/*!\nFor a normalized diyfp w = f * 2^e, this function returns a (normalized) cached\npower-of-ten c = f_c * 2^e_c, such that the exponent of the product w * c\nsatisfies (Definition 3.2 from [1])\n\n     alpha <= e_c + e + q <= gamma.\n*/\ninline cached_power get_cached_power_for_binary_exponent(int e)\n{\n    // Now\n    //\n    //      alpha <= e_c + e + q <= gamma                                    (1)\n    //      ==> f_c * 2^alpha <= c * 2^e * 2^q\n    //\n    // and since the c's are normalized, 2^(q-1) <= f_c,\n    //\n    //      ==> 2^(q - 1 + alpha) <= c * 2^(e + q)\n    //      ==> 2^(alpha - e - 1) <= c\n    //\n    // If c were an exact power of ten, i.e. c = 10^k, one may determine k as\n    //\n    //      k = ceil( log_10( 2^(alpha - e - 1) ) )\n    //        = ceil( (alpha - e - 1) * log_10(2) )\n    //\n    // From the paper:\n    // \"In theory the result of the procedure could be wrong since c is rounded,\n    //  and the computation itself is approximated [...]. In practice, however,\n    //  this simple function is sufficient.\"\n    //\n    // For IEEE double precision floating-point numbers converted into\n    // normalized diyfp's w = f * 2^e, with q = 64,\n    //\n    //      e >= -1022      (min IEEE exponent)\n    //           -52        (p - 1)\n    //           -52        (p - 1, possibly normalize denormal IEEE numbers)\n    //           -11        (normalize the diyfp)\n    //         = -1137\n    //\n    // and\n    //\n    //      e <= +1023      (max IEEE exponent)\n    //           -52        (p - 1)\n    //           -11        (normalize the diyfp)\n    //         = 960\n    //\n    // This binary exponent range [-1137,960] results in a decimal exponent\n    // range [-307,324]. One does not need to store a cached power for each\n    // k in this range. For each such k it suffices to find a cached power\n    // such that the exponent of the product lies in [alpha,gamma].\n    // This implies that the difference of the decimal exponents of adjacent\n    // table entries must be less than or equal to\n    //\n    //      floor( (gamma - alpha) * log_10(2) ) = 8.\n    //\n    // (A smaller distance gamma-alpha would require a larger table.)\n\n    // NB:\n    // Actually this function returns c, such that -60 <= e_c + e + 64 <= -34.\n\n    constexpr int kCachedPowersMinDecExp = -300;\n    constexpr int kCachedPowersDecStep = 8;\n\n    static constexpr std::array<cached_power, 79> kCachedPowers =\n    {\n        {\n            { 0xAB70FE17C79AC6CA, -1060, -300 },\n            { 0xFF77B1FCBEBCDC4F, -1034, -292 },\n            { 0xBE5691EF416BD60C, -1007, -284 },\n            { 0x8DD01FAD907FFC3C,  -980, -276 },\n            { 0xD3515C2831559A83,  -954, -268 },\n            { 0x9D71AC8FADA6C9B5,  -927, -260 },\n            { 0xEA9C227723EE8BCB,  -901, -252 },\n            { 0xAECC49914078536D,  -874, -244 },\n            { 0x823C12795DB6CE57,  -847, -236 },\n            { 0xC21094364DFB5637,  -821, -228 },\n            { 0x9096EA6F3848984F,  -794, -220 },\n            { 0xD77485CB25823AC7,  -768, -212 },\n            { 0xA086CFCD97BF97F4,  -741, -204 },\n            { 0xEF340A98172AACE5,  -715, -196 },\n            { 0xB23867FB2A35B28E,  -688, -188 },\n            { 0x84C8D4DFD2C63F3B,  -661, -180 },\n            { 0xC5DD44271AD3CDBA,  -635, -172 },\n            { 0x936B9FCEBB25C996,  -608, -164 },\n            { 0xDBAC6C247D62A584,  -582, -156 },\n            { 0xA3AB66580D5FDAF6,  -555, -148 },\n            { 0xF3E2F893DEC3F126,  -529, -140 },\n            { 0xB5B5ADA8AAFF80B8,  -502, -132 },\n            { 0x87625F056C7C4A8B,  -475, -124 },\n            { 0xC9BCFF6034C13053,  -449, -116 },\n            { 0x964E858C91BA2655,  -422, -108 },\n            { 0xDFF9772470297EBD,  -396, -100 },\n            { 0xA6DFBD9FB8E5B88F,  -369,  -92 },\n            { 0xF8A95FCF88747D94,  -343,  -84 },\n            { 0xB94470938FA89BCF,  -316,  -76 },\n            { 0x8A08F0F8BF0F156B,  -289,  -68 },\n            { 0xCDB02555653131B6,  -263,  -60 },\n            { 0x993FE2C6D07B7FAC,  -236,  -52 },\n            { 0xE45C10C42A2B3B06,  -210,  -44 },\n            { 0xAA242499697392D3,  -183,  -36 },\n            { 0xFD87B5F28300CA0E,  -157,  -28 },\n            { 0xBCE5086492111AEB,  -130,  -20 },\n            { 0x8CBCCC096F5088CC,  -103,  -12 },\n            { 0xD1B71758E219652C,   -77,   -4 },\n            { 0x9C40000000000000,   -50,    4 },\n            { 0xE8D4A51000000000,   -24,   12 },\n            { 0xAD78EBC5AC620000,     3,   20 },\n            { 0x813F3978F8940984,    30,   28 },\n            { 0xC097CE7BC90715B3,    56,   36 },\n            { 0x8F7E32CE7BEA5C70,    83,   44 },\n            { 0xD5D238A4ABE98068,   109,   52 },\n            { 0x9F4F2726179A2245,   136,   60 },\n            { 0xED63A231D4C4FB27,   162,   68 },\n            { 0xB0DE65388CC8ADA8,   189,   76 },\n            { 0x83C7088E1AAB65DB,   216,   84 },\n            { 0xC45D1DF942711D9A,   242,   92 },\n            { 0x924D692CA61BE758,   269,  100 },\n            { 0xDA01EE641A708DEA,   295,  108 },\n            { 0xA26DA3999AEF774A,   322,  116 },\n            { 0xF209787BB47D6B85,   348,  124 },\n            { 0xB454E4A179DD1877,   375,  132 },\n            { 0x865B86925B9BC5C2,   402,  140 },\n            { 0xC83553C5C8965D3D,   428,  148 },\n            { 0x952AB45CFA97A0B3,   455,  156 },\n            { 0xDE469FBD99A05FE3,   481,  164 },\n            { 0xA59BC234DB398C25,   508,  172 },\n            { 0xF6C69A72A3989F5C,   534,  180 },\n            { 0xB7DCBF5354E9BECE,   561,  188 },\n            { 0x88FCF317F22241E2,   588,  196 },\n            { 0xCC20CE9BD35C78A5,   614,  204 },\n            { 0x98165AF37B2153DF,   641,  212 },\n            { 0xE2A0B5DC971F303A,   667,  220 },\n            { 0xA8D9D1535CE3B396,   694,  228 },\n            { 0xFB9B7CD9A4A7443C,   720,  236 },\n            { 0xBB764C4CA7A44410,   747,  244 },\n            { 0x8BAB8EEFB6409C1A,   774,  252 },\n            { 0xD01FEF10A657842C,   800,  260 },\n            { 0x9B10A4E5E9913129,   827,  268 },\n            { 0xE7109BFBA19C0C9D,   853,  276 },\n            { 0xAC2820D9623BF429,   880,  284 },\n            { 0x80444B5E7AA7CF85,   907,  292 },\n            { 0xBF21E44003ACDD2D,   933,  300 },\n            { 0x8E679C2F5E44FF8F,   960,  308 },\n            { 0xD433179D9C8CB841,   986,  316 },\n            { 0x9E19DB92B4E31BA9,  1013,  324 },\n        }\n    };\n\n    // This computation gives exactly the same results for k as\n    //      k = ceil((kAlpha - e - 1) * 0.30102999566398114)\n    // for |e| <= 1500, but doesn't require floating-point operations.\n    // NB: log_10(2) ~= 78913 / 2^18\n    JSON_ASSERT(e >= -1500);\n    JSON_ASSERT(e <=  1500);\n    const int f = kAlpha - e - 1;\n    const int k = ((f * 78913) / (1 << 18)) + static_cast<int>(f > 0);\n\n    const int index = (-kCachedPowersMinDecExp + k + (kCachedPowersDecStep - 1)) / kCachedPowersDecStep;\n    JSON_ASSERT(index >= 0);\n    JSON_ASSERT(static_cast<std::size_t>(index) < kCachedPowers.size());\n\n    const cached_power cached = kCachedPowers[static_cast<std::size_t>(index)];\n    JSON_ASSERT(kAlpha <= cached.e + e + 64);\n    JSON_ASSERT(kGamma >= cached.e + e + 64);\n\n    return cached;\n}\n\n/*!\nFor n != 0, returns k, such that pow10 := 10^(k-1) <= n < 10^k.\nFor n == 0, returns 1 and sets pow10 := 1.\n*/\ninline int find_largest_pow10(const std::uint32_t n, std::uint32_t& pow10)\n{\n    // LCOV_EXCL_START\n    if (n >= 1000000000)\n    {\n        pow10 = 1000000000;\n        return 10;\n    }\n    // LCOV_EXCL_STOP\n    if (n >= 100000000)\n    {\n        pow10 = 100000000;\n        return  9;\n    }\n    if (n >= 10000000)\n    {\n        pow10 = 10000000;\n        return  8;\n    }\n    if (n >= 1000000)\n    {\n        pow10 = 1000000;\n        return  7;\n    }\n    if (n >= 100000)\n    {\n        pow10 = 100000;\n        return  6;\n    }\n    if (n >= 10000)\n    {\n        pow10 = 10000;\n        return  5;\n    }\n    if (n >= 1000)\n    {\n        pow10 = 1000;\n        return  4;\n    }\n    if (n >= 100)\n    {\n        pow10 = 100;\n        return  3;\n    }\n    if (n >= 10)\n    {\n        pow10 = 10;\n        return  2;\n    }\n\n    pow10 = 1;\n    return 1;\n}\n\ninline void grisu2_round(char* buf, int len, std::uint64_t dist, std::uint64_t delta,\n                         std::uint64_t rest, std::uint64_t ten_k)\n{\n    JSON_ASSERT(len >= 1);\n    JSON_ASSERT(dist <= delta);\n    JSON_ASSERT(rest <= delta);\n    JSON_ASSERT(ten_k > 0);\n\n    //               <--------------------------- delta ---->\n    //                                  <---- dist --------->\n    // --------------[------------------+-------------------]--------------\n    //               M-                 w                   M+\n    //\n    //                                  ten_k\n    //                                <------>\n    //                                       <---- rest ---->\n    // --------------[------------------+----+--------------]--------------\n    //                                  w    V\n    //                                       = buf * 10^k\n    //\n    // ten_k represents a unit-in-the-last-place in the decimal representation\n    // stored in buf.\n    // Decrement buf by ten_k while this takes buf closer to w.\n\n    // The tests are written in this order to avoid overflow in unsigned\n    // integer arithmetic.\n\n    while (rest < dist\n            && delta - rest >= ten_k\n            && (rest + ten_k < dist || dist - rest > rest + ten_k - dist))\n    {\n        JSON_ASSERT(buf[len - 1] != '0');\n        buf[len - 1]--;\n        rest += ten_k;\n    }\n}\n\n/*!\nGenerates V = buffer * 10^decimal_exponent, such that M- <= V <= M+.\nM- and M+ must be normalized and share the same exponent -60 <= e <= -32.\n*/\ninline void grisu2_digit_gen(char* buffer, int& length, int& decimal_exponent,\n                             diyfp M_minus, diyfp w, diyfp M_plus)\n{\n    static_assert(kAlpha >= -60, \"internal error\");\n    static_assert(kGamma <= -32, \"internal error\");\n\n    // Generates the digits (and the exponent) of a decimal floating-point\n    // number V = buffer * 10^decimal_exponent in the range [M-, M+]. The diyfp's\n    // w, M- and M+ share the same exponent e, which satisfies alpha <= e <= gamma.\n    //\n    //               <--------------------------- delta ---->\n    //                                  <---- dist --------->\n    // --------------[------------------+-------------------]--------------\n    //               M-                 w                   M+\n    //\n    // Grisu2 generates the digits of M+ from left to right and stops as soon as\n    // V is in [M-,M+].\n\n    JSON_ASSERT(M_plus.e >= kAlpha);\n    JSON_ASSERT(M_plus.e <= kGamma);\n\n    std::uint64_t delta = diyfp::sub(M_plus, M_minus).f; // (significand of (M+ - M-), implicit exponent is e)\n    std::uint64_t dist  = diyfp::sub(M_plus, w      ).f; // (significand of (M+ - w ), implicit exponent is e)\n\n    // Split M+ = f * 2^e into two parts p1 and p2 (note: e < 0):\n    //\n    //      M+ = f * 2^e\n    //         = ((f div 2^-e) * 2^-e + (f mod 2^-e)) * 2^e\n    //         = ((p1        ) * 2^-e + (p2        )) * 2^e\n    //         = p1 + p2 * 2^e\n\n    const diyfp one(std::uint64_t{1} << -M_plus.e, M_plus.e);\n\n    auto p1 = static_cast<std::uint32_t>(M_plus.f >> -one.e); // p1 = f div 2^-e (Since -e >= 32, p1 fits into a 32-bit int.)\n    std::uint64_t p2 = M_plus.f & (one.f - 1);                    // p2 = f mod 2^-e\n\n    // 1)\n    //\n    // Generate the digits of the integral part p1 = d[n-1]...d[1]d[0]\n\n    JSON_ASSERT(p1 > 0);\n\n    std::uint32_t pow10{};\n    const int k = find_largest_pow10(p1, pow10);\n\n    //      10^(k-1) <= p1 < 10^k, pow10 = 10^(k-1)\n    //\n    //      p1 = (p1 div 10^(k-1)) * 10^(k-1) + (p1 mod 10^(k-1))\n    //         = (d[k-1]         ) * 10^(k-1) + (p1 mod 10^(k-1))\n    //\n    //      M+ = p1                                             + p2 * 2^e\n    //         = d[k-1] * 10^(k-1) + (p1 mod 10^(k-1))          + p2 * 2^e\n    //         = d[k-1] * 10^(k-1) + ((p1 mod 10^(k-1)) * 2^-e + p2) * 2^e\n    //         = d[k-1] * 10^(k-1) + (                         rest) * 2^e\n    //\n    // Now generate the digits d[n] of p1 from left to right (n = k-1,...,0)\n    //\n    //      p1 = d[k-1]...d[n] * 10^n + d[n-1]...d[0]\n    //\n    // but stop as soon as\n    //\n    //      rest * 2^e = (d[n-1]...d[0] * 2^-e + p2) * 2^e <= delta * 2^e\n\n    int n = k;\n    while (n > 0)\n    {\n        // Invariants:\n        //      M+ = buffer * 10^n + (p1 + p2 * 2^e)    (buffer = 0 for n = k)\n        //      pow10 = 10^(n-1) <= p1 < 10^n\n        //\n        const std::uint32_t d = p1 / pow10;  // d = p1 div 10^(n-1)\n        const std::uint32_t r = p1 % pow10;  // r = p1 mod 10^(n-1)\n        //\n        //      M+ = buffer * 10^n + (d * 10^(n-1) + r) + p2 * 2^e\n        //         = (buffer * 10 + d) * 10^(n-1) + (r + p2 * 2^e)\n        //\n        JSON_ASSERT(d <= 9);\n        buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d\n        //\n        //      M+ = buffer * 10^(n-1) + (r + p2 * 2^e)\n        //\n        p1 = r;\n        n--;\n        //\n        //      M+ = buffer * 10^n + (p1 + p2 * 2^e)\n        //      pow10 = 10^n\n        //\n\n        // Now check if enough digits have been generated.\n        // Compute\n        //\n        //      p1 + p2 * 2^e = (p1 * 2^-e + p2) * 2^e = rest * 2^e\n        //\n        // Note:\n        // Since rest and delta share the same exponent e, it suffices to\n        // compare the significands.\n        const std::uint64_t rest = (std::uint64_t{p1} << -one.e) + p2;\n        if (rest <= delta)\n        {\n            // V = buffer * 10^n, with M- <= V <= M+.\n\n            decimal_exponent += n;\n\n            // We may now just stop. But instead look if the buffer could be\n            // decremented to bring V closer to w.\n            //\n            // pow10 = 10^n is now 1 ulp in the decimal representation V.\n            // The rounding procedure works with diyfp's with an implicit\n            // exponent of e.\n            //\n            //      10^n = (10^n * 2^-e) * 2^e = ulp * 2^e\n            //\n            const std::uint64_t ten_n = std::uint64_t{pow10} << -one.e;\n            grisu2_round(buffer, length, dist, delta, rest, ten_n);\n\n            return;\n        }\n\n        pow10 /= 10;\n        //\n        //      pow10 = 10^(n-1) <= p1 < 10^n\n        // Invariants restored.\n    }\n\n    // 2)\n    //\n    // The digits of the integral part have been generated:\n    //\n    //      M+ = d[k-1]...d[1]d[0] + p2 * 2^e\n    //         = buffer            + p2 * 2^e\n    //\n    // Now generate the digits of the fractional part p2 * 2^e.\n    //\n    // Note:\n    // No decimal point is generated: the exponent is adjusted instead.\n    //\n    // p2 actually represents the fraction\n    //\n    //      p2 * 2^e\n    //          = p2 / 2^-e\n    //          = d[-1] / 10^1 + d[-2] / 10^2 + ...\n    //\n    // Now generate the digits d[-m] of p1 from left to right (m = 1,2,...)\n    //\n    //      p2 * 2^e = d[-1]d[-2]...d[-m] * 10^-m\n    //                      + 10^-m * (d[-m-1] / 10^1 + d[-m-2] / 10^2 + ...)\n    //\n    // using\n    //\n    //      10^m * p2 = ((10^m * p2) div 2^-e) * 2^-e + ((10^m * p2) mod 2^-e)\n    //                = (                   d) * 2^-e + (                   r)\n    //\n    // or\n    //      10^m * p2 * 2^e = d + r * 2^e\n    //\n    // i.e.\n    //\n    //      M+ = buffer + p2 * 2^e\n    //         = buffer + 10^-m * (d + r * 2^e)\n    //         = (buffer * 10^m + d) * 10^-m + 10^-m * r * 2^e\n    //\n    // and stop as soon as 10^-m * r * 2^e <= delta * 2^e\n\n    JSON_ASSERT(p2 > delta);\n\n    int m = 0;\n    for (;;)\n    {\n        // Invariant:\n        //      M+ = buffer * 10^-m + 10^-m * (d[-m-1] / 10 + d[-m-2] / 10^2 + ...) * 2^e\n        //         = buffer * 10^-m + 10^-m * (p2                                 ) * 2^e\n        //         = buffer * 10^-m + 10^-m * (1/10 * (10 * p2)                   ) * 2^e\n        //         = buffer * 10^-m + 10^-m * (1/10 * ((10*p2 div 2^-e) * 2^-e + (10*p2 mod 2^-e)) * 2^e\n        //\n        JSON_ASSERT(p2 <= (std::numeric_limits<std::uint64_t>::max)() / 10);\n        p2 *= 10;\n        const std::uint64_t d = p2 >> -one.e;     // d = (10 * p2) div 2^-e\n        const std::uint64_t r = p2 & (one.f - 1); // r = (10 * p2) mod 2^-e\n        //\n        //      M+ = buffer * 10^-m + 10^-m * (1/10 * (d * 2^-e + r) * 2^e\n        //         = buffer * 10^-m + 10^-m * (1/10 * (d + r * 2^e))\n        //         = (buffer * 10 + d) * 10^(-m-1) + 10^(-m-1) * r * 2^e\n        //\n        JSON_ASSERT(d <= 9);\n        buffer[length++] = static_cast<char>('0' + d); // buffer := buffer * 10 + d\n        //\n        //      M+ = buffer * 10^(-m-1) + 10^(-m-1) * r * 2^e\n        //\n        p2 = r;\n        m++;\n        //\n        //      M+ = buffer * 10^-m + 10^-m * p2 * 2^e\n        // Invariant restored.\n\n        // Check if enough digits have been generated.\n        //\n        //      10^-m * p2 * 2^e <= delta * 2^e\n        //              p2 * 2^e <= 10^m * delta * 2^e\n        //                    p2 <= 10^m * delta\n        delta *= 10;\n        dist  *= 10;\n        if (p2 <= delta)\n        {\n            break;\n        }\n    }\n\n    // V = buffer * 10^-m, with M- <= V <= M+.\n\n    decimal_exponent -= m;\n\n    // 1 ulp in the decimal representation is now 10^-m.\n    // Since delta and dist are now scaled by 10^m, we need to do the\n    // same with ulp in order to keep the units in sync.\n    //\n    //      10^m * 10^-m = 1 = 2^-e * 2^e = ten_m * 2^e\n    //\n    const std::uint64_t ten_m = one.f;\n    grisu2_round(buffer, length, dist, delta, p2, ten_m);\n\n    // By construction this algorithm generates the shortest possible decimal\n    // number (Loitsch, Theorem 6.2) which rounds back to w.\n    // For an input number of precision p, at least\n    //\n    //      N = 1 + ceil(p * log_10(2))\n    //\n    // decimal digits are sufficient to identify all binary floating-point\n    // numbers (Matula, \"In-and-Out conversions\").\n    // This implies that the algorithm does not produce more than N decimal\n    // digits.\n    //\n    //      N = 17 for p = 53 (IEEE double precision)\n    //      N = 9  for p = 24 (IEEE single precision)\n}\n\n/*!\nv = buf * 10^decimal_exponent\nlen is the length of the buffer (number of decimal digits)\nThe buffer must be large enough, i.e. >= max_digits10.\n*/\nJSON_HEDLEY_NON_NULL(1)\ninline void grisu2(char* buf, int& len, int& decimal_exponent,\n                   diyfp m_minus, diyfp v, diyfp m_plus)\n{\n    JSON_ASSERT(m_plus.e == m_minus.e);\n    JSON_ASSERT(m_plus.e == v.e);\n\n    //  --------(-----------------------+-----------------------)--------    (A)\n    //          m-                      v                       m+\n    //\n    //  --------------------(-----------+-----------------------)--------    (B)\n    //                      m-          v                       m+\n    //\n    // First scale v (and m- and m+) such that the exponent is in the range\n    // [alpha, gamma].\n\n    const cached_power cached = get_cached_power_for_binary_exponent(m_plus.e);\n\n    const diyfp c_minus_k(cached.f, cached.e); // = c ~= 10^-k\n\n    // The exponent of the products is = v.e + c_minus_k.e + q and is in the range [alpha,gamma]\n    const diyfp w       = diyfp::mul(v,       c_minus_k);\n    const diyfp w_minus = diyfp::mul(m_minus, c_minus_k);\n    const diyfp w_plus  = diyfp::mul(m_plus,  c_minus_k);\n\n    //  ----(---+---)---------------(---+---)---------------(---+---)----\n    //          w-                      w                       w+\n    //          = c*m-                  = c*v                   = c*m+\n    //\n    // diyfp::mul rounds its result and c_minus_k is approximated too. w, w- and\n    // w+ are now off by a small amount.\n    // In fact:\n    //\n    //      w - v * 10^k < 1 ulp\n    //\n    // To account for this inaccuracy, add resp. subtract 1 ulp.\n    //\n    //  --------+---[---------------(---+---)---------------]---+--------\n    //          w-  M-                  w                   M+  w+\n    //\n    // Now any number in [M-, M+] (bounds included) will round to w when input,\n    // regardless of how the input rounding algorithm breaks ties.\n    //\n    // And digit_gen generates the shortest possible such number in [M-, M+].\n    // Note that this does not mean that Grisu2 always generates the shortest\n    // possible number in the interval (m-, m+).\n    const diyfp M_minus(w_minus.f + 1, w_minus.e);\n    const diyfp M_plus (w_plus.f  - 1, w_plus.e );\n\n    decimal_exponent = -cached.k; // = -(-k) = k\n\n    grisu2_digit_gen(buf, len, decimal_exponent, M_minus, w, M_plus);\n}\n\n/*!\nv = buf * 10^decimal_exponent\nlen is the length of the buffer (number of decimal digits)\nThe buffer must be large enough, i.e. >= max_digits10.\n*/\ntemplate<typename FloatType>\nJSON_HEDLEY_NON_NULL(1)\nvoid grisu2(char* buf, int& len, int& decimal_exponent, FloatType value)\n{\n    static_assert(diyfp::kPrecision >= std::numeric_limits<FloatType>::digits + 3,\n                  \"internal error: not enough precision\");\n\n    JSON_ASSERT(std::isfinite(value));\n    JSON_ASSERT(value > 0);\n\n    // If the neighbors (and boundaries) of 'value' are always computed for double-precision\n    // numbers, all float's can be recovered using strtod (and strtof). However, the resulting\n    // decimal representations are not exactly \"short\".\n    //\n    // The documentation for 'std::to_chars' (https://en.cppreference.com/w/cpp/utility/to_chars)\n    // says \"value is converted to a string as if by std::sprintf in the default (\"C\") locale\"\n    // and since sprintf promotes floats to doubles, I think this is exactly what 'std::to_chars'\n    // does.\n    // On the other hand, the documentation for 'std::to_chars' requires that \"parsing the\n    // representation using the corresponding std::from_chars function recovers value exactly\". That\n    // indicates that single precision floating-point numbers should be recovered using\n    // 'std::strtof'.\n    //\n    // NB: If the neighbors are computed for single-precision numbers, there is a single float\n    //     (7.0385307e-26f) which can't be recovered using strtod. The resulting double precision\n    //     value is off by 1 ulp.\n#if 0 // NOLINT(readability-avoid-unconditional-preprocessor-if)\n    const boundaries w = compute_boundaries(static_cast<double>(value));\n#else\n    const boundaries w = compute_boundaries(value);\n#endif\n\n    grisu2(buf, len, decimal_exponent, w.minus, w.w, w.plus);\n}\n\n/*!\n@brief appends a decimal representation of e to buf\n@return a pointer to the element following the exponent.\n@pre -1000 < e < 1000\n*/\nJSON_HEDLEY_NON_NULL(1)\nJSON_HEDLEY_RETURNS_NON_NULL\ninline char* append_exponent(char* buf, int e)\n{\n    JSON_ASSERT(e > -1000);\n    JSON_ASSERT(e <  1000);\n\n    if (e < 0)\n    {\n        e = -e;\n        *buf++ = '-';\n    }\n    else\n    {\n        *buf++ = '+';\n    }\n\n    auto k = static_cast<std::uint32_t>(e);\n    if (k < 10)\n    {\n        // Always print at least two digits in the exponent.\n        // This is for compatibility with printf(\"%g\").\n        *buf++ = '0';\n        *buf++ = static_cast<char>('0' + k);\n    }\n    else if (k < 100)\n    {\n        *buf++ = static_cast<char>('0' + (k / 10));\n        k %= 10;\n        *buf++ = static_cast<char>('0' + k);\n    }\n    else\n    {\n        *buf++ = static_cast<char>('0' + (k / 100));\n        k %= 100;\n        *buf++ = static_cast<char>('0' + (k / 10));\n        k %= 10;\n        *buf++ = static_cast<char>('0' + k);\n    }\n\n    return buf;\n}\n\n/*!\n@brief prettify v = buf * 10^decimal_exponent\n\nIf v is in the range [10^min_exp, 10^max_exp) it will be printed in fixed-point\nnotation. Otherwise it will be printed in exponential notation.\n\n@pre min_exp < 0\n@pre max_exp > 0\n*/\nJSON_HEDLEY_NON_NULL(1)\nJSON_HEDLEY_RETURNS_NON_NULL\ninline char* format_buffer(char* buf, int len, int decimal_exponent,\n                           int min_exp, int max_exp)\n{\n    JSON_ASSERT(min_exp < 0);\n    JSON_ASSERT(max_exp > 0);\n\n    const int k = len;\n    const int n = len + decimal_exponent;\n\n    // v = buf * 10^(n-k)\n    // k is the length of the buffer (number of decimal digits)\n    // n is the position of the decimal point relative to the start of the buffer.\n\n    if (k <= n && n <= max_exp)\n    {\n        // digits[000]\n        // len <= max_exp + 2\n\n        std::memset(buf + k, '0', static_cast<size_t>(n) - static_cast<size_t>(k));\n        // Make it look like a floating-point number (#362, #378)\n        buf[n + 0] = '.';\n        buf[n + 1] = '0';\n        return buf + (static_cast<size_t>(n) + 2);\n    }\n\n    if (0 < n && n <= max_exp)\n    {\n        // dig.its\n        // len <= max_digits10 + 1\n\n        JSON_ASSERT(k > n);\n\n        std::memmove(buf + (static_cast<size_t>(n) + 1), buf + n, static_cast<size_t>(k) - static_cast<size_t>(n));\n        buf[n] = '.';\n        return buf + (static_cast<size_t>(k) + 1U);\n    }\n\n    if (min_exp < n && n <= 0)\n    {\n        // 0.[000]digits\n        // len <= 2 + (-min_exp - 1) + max_digits10\n\n        std::memmove(buf + (2 + static_cast<size_t>(-n)), buf, static_cast<size_t>(k));\n        buf[0] = '0';\n        buf[1] = '.';\n        std::memset(buf + 2, '0', static_cast<size_t>(-n));\n        return buf + (2U + static_cast<size_t>(-n) + static_cast<size_t>(k));\n    }\n\n    if (k == 1)\n    {\n        // dE+123\n        // len <= 1 + 5\n\n        buf += 1;\n    }\n    else\n    {\n        // d.igitsE+123\n        // len <= max_digits10 + 1 + 5\n\n        std::memmove(buf + 2, buf + 1, static_cast<size_t>(k) - 1);\n        buf[1] = '.';\n        buf += 1 + static_cast<size_t>(k);\n    }\n\n    *buf++ = 'e';\n    return append_exponent(buf, n - 1);\n}\n\n}  // namespace dtoa_impl\n\n/*!\n@brief generates a decimal representation of the floating-point number value in [first, last).\n\nThe format of the resulting decimal representation is similar to printf's %g\nformat. Returns an iterator pointing past-the-end of the decimal representation.\n\n@note The input number must be finite, i.e. NaN's and Inf's are not supported.\n@note The buffer must be large enough.\n@note The result is NOT null-terminated.\n*/\ntemplate<typename FloatType>\nJSON_HEDLEY_NON_NULL(1, 2)\nJSON_HEDLEY_RETURNS_NON_NULL\nchar* to_chars(char* first, const char* last, FloatType value)\n{\n    static_cast<void>(last); // maybe unused - fix warning\n    JSON_ASSERT(std::isfinite(value));\n\n    // Use signbit(value) instead of (value < 0) since signbit works for -0.\n    if (std::signbit(value))\n    {\n        value = -value;\n        *first++ = '-';\n    }\n\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n    if (value == 0) // +-0\n    {\n        *first++ = '0';\n        // Make it look like a floating-point number (#362, #378)\n        *first++ = '.';\n        *first++ = '0';\n        return first;\n    }\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif\n\n    JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10);\n\n    // Compute v = buffer * 10^decimal_exponent.\n    // The decimal digits are stored in the buffer, which needs to be interpreted\n    // as an unsigned decimal integer.\n    // len is the length of the buffer, i.e. the number of decimal digits.\n    int len = 0;\n    int decimal_exponent = 0;\n    dtoa_impl::grisu2(first, len, decimal_exponent, value);\n\n    JSON_ASSERT(len <= std::numeric_limits<FloatType>::max_digits10);\n\n    // Format the buffer like printf(\"%.*g\", prec, value)\n    constexpr int kMinExp = -4;\n    // Use digits10 here to increase compatibility with version 2.\n    constexpr int kMaxExp = std::numeric_limits<FloatType>::digits10;\n\n    JSON_ASSERT(last - first >= kMaxExp + 2);\n    JSON_ASSERT(last - first >= 2 + (-kMinExp - 1) + std::numeric_limits<FloatType>::max_digits10);\n    JSON_ASSERT(last - first >= std::numeric_limits<FloatType>::max_digits10 + 6);\n\n    return dtoa_impl::format_buffer(first, len, decimal_exponent, kMinExp, kMaxExp);\n}\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/exceptions.hpp>\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/cpp_future.hpp>\n\n// #include <nlohmann/detail/output/binary_writer.hpp>\n\n// #include <nlohmann/detail/output/output_adapters.hpp>\n\n// #include <nlohmann/detail/string_concat.hpp>\n\n// #include <nlohmann/detail/value_t.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\nnamespace detail\n{\n\n///////////////////\n// serialization //\n///////////////////\n\n/// how to treat decoding errors\nenum class error_handler_t\n{\n    strict,  ///< throw a type_error exception in case of invalid UTF-8\n    replace, ///< replace invalid UTF-8 sequences with U+FFFD\n    ignore   ///< ignore invalid UTF-8 sequences\n};\n\ntemplate<typename BasicJsonType>\nclass serializer\n{\n    using string_t = typename BasicJsonType::string_t;\n    using number_float_t = typename BasicJsonType::number_float_t;\n    using number_integer_t = typename BasicJsonType::number_integer_t;\n    using number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n    using binary_char_t = typename BasicJsonType::binary_t::value_type;\n    static constexpr std::uint8_t UTF8_ACCEPT = 0;\n    static constexpr std::uint8_t UTF8_REJECT = 1;\n\n  public:\n    /*!\n    @param[in] s  output stream to serialize to\n    @param[in] ichar  indentation character to use\n    @param[in] error_handler_  how to react on decoding errors\n    */\n    serializer(output_adapter_t<char> s, const char ichar,\n               error_handler_t error_handler_ = error_handler_t::strict)\n        : o(std::move(s))\n        , loc(std::localeconv())\n        , thousands_sep(loc->thousands_sep == nullptr ? '\\0' : std::char_traits<char>::to_char_type(* (loc->thousands_sep)))\n        , decimal_point(loc->decimal_point == nullptr ? '\\0' : std::char_traits<char>::to_char_type(* (loc->decimal_point)))\n        , indent_char(ichar)\n        , indent_string(512, indent_char)\n        , error_handler(error_handler_)\n    {}\n\n    // delete because of pointer members\n    serializer(const serializer&) = delete;\n    serializer& operator=(const serializer&) = delete;\n    serializer(serializer&&) = delete;\n    serializer& operator=(serializer&&) = delete;\n    ~serializer() = default;\n\n    /*!\n    @brief internal implementation of the serialization function\n\n    This function is called by the public member function dump and organizes\n    the serialization internally. The indentation level is propagated as\n    additional parameter. In case of arrays and objects, the function is\n    called recursively.\n\n    - strings and object keys are escaped using `escape_string()`\n    - integer numbers are converted implicitly via `operator<<`\n    - floating-point numbers are converted to a string using `\"%g\"` format\n    - binary values are serialized as objects containing the subtype and the\n      byte array\n\n    @param[in] val               value to serialize\n    @param[in] pretty_print      whether the output shall be pretty-printed\n    @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters\n    in the output are escaped with `\\uXXXX` sequences, and the result consists\n    of ASCII characters only.\n    @param[in] indent_step       the indent level\n    @param[in] current_indent    the current indent level (only used internally)\n    */\n    void dump(const BasicJsonType& val,\n              const bool pretty_print,\n              const bool ensure_ascii,\n              const unsigned int indent_step,\n              const unsigned int current_indent = 0)\n    {\n        switch (val.m_data.m_type)\n        {\n            case value_t::object:\n            {\n                if (val.m_data.m_value.object->empty())\n                {\n                    o->write_characters(\"{}\", 2);\n                    return;\n                }\n\n                if (pretty_print)\n                {\n                    o->write_characters(\"{\\n\", 2);\n\n                    // variable to hold indentation for recursive calls\n                    const auto new_indent = current_indent + indent_step;\n                    if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))\n                    {\n                        indent_string.resize(indent_string.size() * 2, ' ');\n                    }\n\n                    // first n-1 elements\n                    auto i = val.m_data.m_value.object->cbegin();\n                    for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)\n                    {\n                        o->write_characters(indent_string.c_str(), new_indent);\n                        o->write_character('\\\"');\n                        dump_escaped(i->first, ensure_ascii);\n                        o->write_characters(\"\\\": \", 3);\n                        dump(i->second, true, ensure_ascii, indent_step, new_indent);\n                        o->write_characters(\",\\n\", 2);\n                    }\n\n                    // last element\n                    JSON_ASSERT(i != val.m_data.m_value.object->cend());\n                    JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());\n                    o->write_characters(indent_string.c_str(), new_indent);\n                    o->write_character('\\\"');\n                    dump_escaped(i->first, ensure_ascii);\n                    o->write_characters(\"\\\": \", 3);\n                    dump(i->second, true, ensure_ascii, indent_step, new_indent);\n\n                    o->write_character('\\n');\n                    o->write_characters(indent_string.c_str(), current_indent);\n                    o->write_character('}');\n                }\n                else\n                {\n                    o->write_character('{');\n\n                    // first n-1 elements\n                    auto i = val.m_data.m_value.object->cbegin();\n                    for (std::size_t cnt = 0; cnt < val.m_data.m_value.object->size() - 1; ++cnt, ++i)\n                    {\n                        o->write_character('\\\"');\n                        dump_escaped(i->first, ensure_ascii);\n                        o->write_characters(\"\\\":\", 2);\n                        dump(i->second, false, ensure_ascii, indent_step, current_indent);\n                        o->write_character(',');\n                    }\n\n                    // last element\n                    JSON_ASSERT(i != val.m_data.m_value.object->cend());\n                    JSON_ASSERT(std::next(i) == val.m_data.m_value.object->cend());\n                    o->write_character('\\\"');\n                    dump_escaped(i->first, ensure_ascii);\n                    o->write_characters(\"\\\":\", 2);\n                    dump(i->second, false, ensure_ascii, indent_step, current_indent);\n\n                    o->write_character('}');\n                }\n\n                return;\n            }\n\n            case value_t::array:\n            {\n                if (val.m_data.m_value.array->empty())\n                {\n                    o->write_characters(\"[]\", 2);\n                    return;\n                }\n\n                if (pretty_print)\n                {\n                    o->write_characters(\"[\\n\", 2);\n\n                    // variable to hold indentation for recursive calls\n                    const auto new_indent = current_indent + indent_step;\n                    if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))\n                    {\n                        indent_string.resize(indent_string.size() * 2, ' ');\n                    }\n\n                    // first n-1 elements\n                    for (auto i = val.m_data.m_value.array->cbegin();\n                            i != val.m_data.m_value.array->cend() - 1; ++i)\n                    {\n                        o->write_characters(indent_string.c_str(), new_indent);\n                        dump(*i, true, ensure_ascii, indent_step, new_indent);\n                        o->write_characters(\",\\n\", 2);\n                    }\n\n                    // last element\n                    JSON_ASSERT(!val.m_data.m_value.array->empty());\n                    o->write_characters(indent_string.c_str(), new_indent);\n                    dump(val.m_data.m_value.array->back(), true, ensure_ascii, indent_step, new_indent);\n\n                    o->write_character('\\n');\n                    o->write_characters(indent_string.c_str(), current_indent);\n                    o->write_character(']');\n                }\n                else\n                {\n                    o->write_character('[');\n\n                    // first n-1 elements\n                    for (auto i = val.m_data.m_value.array->cbegin();\n                            i != val.m_data.m_value.array->cend() - 1; ++i)\n                    {\n                        dump(*i, false, ensure_ascii, indent_step, current_indent);\n                        o->write_character(',');\n                    }\n\n                    // last element\n                    JSON_ASSERT(!val.m_data.m_value.array->empty());\n                    dump(val.m_data.m_value.array->back(), false, ensure_ascii, indent_step, current_indent);\n\n                    o->write_character(']');\n                }\n\n                return;\n            }\n\n            case value_t::string:\n            {\n                o->write_character('\\\"');\n                dump_escaped(*val.m_data.m_value.string, ensure_ascii);\n                o->write_character('\\\"');\n                return;\n            }\n\n            case value_t::binary:\n            {\n                if (pretty_print)\n                {\n                    o->write_characters(\"{\\n\", 2);\n\n                    // variable to hold indentation for recursive calls\n                    const auto new_indent = current_indent + indent_step;\n                    if (JSON_HEDLEY_UNLIKELY(indent_string.size() < new_indent))\n                    {\n                        indent_string.resize(indent_string.size() * 2, ' ');\n                    }\n\n                    o->write_characters(indent_string.c_str(), new_indent);\n\n                    o->write_characters(\"\\\"bytes\\\": [\", 10);\n\n                    if (!val.m_data.m_value.binary->empty())\n                    {\n                        for (auto i = val.m_data.m_value.binary->cbegin();\n                                i != val.m_data.m_value.binary->cend() - 1; ++i)\n                        {\n                            dump_integer(*i);\n                            o->write_characters(\", \", 2);\n                        }\n                        dump_integer(val.m_data.m_value.binary->back());\n                    }\n\n                    o->write_characters(\"],\\n\", 3);\n                    o->write_characters(indent_string.c_str(), new_indent);\n\n                    o->write_characters(\"\\\"subtype\\\": \", 11);\n                    if (val.m_data.m_value.binary->has_subtype())\n                    {\n                        dump_integer(val.m_data.m_value.binary->subtype());\n                    }\n                    else\n                    {\n                        o->write_characters(\"null\", 4);\n                    }\n                    o->write_character('\\n');\n                    o->write_characters(indent_string.c_str(), current_indent);\n                    o->write_character('}');\n                }\n                else\n                {\n                    o->write_characters(\"{\\\"bytes\\\":[\", 10);\n\n                    if (!val.m_data.m_value.binary->empty())\n                    {\n                        for (auto i = val.m_data.m_value.binary->cbegin();\n                                i != val.m_data.m_value.binary->cend() - 1; ++i)\n                        {\n                            dump_integer(*i);\n                            o->write_character(',');\n                        }\n                        dump_integer(val.m_data.m_value.binary->back());\n                    }\n\n                    o->write_characters(\"],\\\"subtype\\\":\", 12);\n                    if (val.m_data.m_value.binary->has_subtype())\n                    {\n                        dump_integer(val.m_data.m_value.binary->subtype());\n                        o->write_character('}');\n                    }\n                    else\n                    {\n                        o->write_characters(\"null}\", 5);\n                    }\n                }\n                return;\n            }\n\n            case value_t::boolean:\n            {\n                if (val.m_data.m_value.boolean)\n                {\n                    o->write_characters(\"true\", 4);\n                }\n                else\n                {\n                    o->write_characters(\"false\", 5);\n                }\n                return;\n            }\n\n            case value_t::number_integer:\n            {\n                dump_integer(val.m_data.m_value.number_integer);\n                return;\n            }\n\n            case value_t::number_unsigned:\n            {\n                dump_integer(val.m_data.m_value.number_unsigned);\n                return;\n            }\n\n            case value_t::number_float:\n            {\n                dump_float(val.m_data.m_value.number_float);\n                return;\n            }\n\n            case value_t::discarded:\n            {\n                o->write_characters(\"<discarded>\", 11);\n                return;\n            }\n\n            case value_t::null:\n            {\n                o->write_characters(\"null\", 4);\n                return;\n            }\n\n            default:            // LCOV_EXCL_LINE\n                JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n        }\n    }\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    /*!\n    @brief dump escaped string\n\n    Escape a string by replacing certain special characters by a sequence of an\n    escape character (backslash) and another character and other control\n    characters by a sequence of \"\\u\" followed by a four-digit hex\n    representation. The escaped string is written to output stream @a o.\n\n    @param[in] s  the string to escape\n    @param[in] ensure_ascii  whether to escape non-ASCII characters with\n                             \\uXXXX sequences\n\n    @complexity Linear in the length of string @a s.\n    */\n    void dump_escaped(const string_t& s, const bool ensure_ascii)\n    {\n        std::uint32_t codepoint{};\n        std::uint8_t state = UTF8_ACCEPT;\n        std::size_t bytes = 0;  // number of bytes written to string_buffer\n\n        // number of bytes written at the point of the last valid byte\n        std::size_t bytes_after_last_accept = 0;\n        std::size_t undumped_chars = 0;\n\n        for (std::size_t i = 0; i < s.size(); ++i)\n        {\n            const auto byte = static_cast<std::uint8_t>(s[i]);\n\n            switch (decode(state, codepoint, byte))\n            {\n                case UTF8_ACCEPT:  // decode found a new code point\n                {\n                    switch (codepoint)\n                    {\n                        case 0x08: // backspace\n                        {\n                            string_buffer[bytes++] = '\\\\';\n                            string_buffer[bytes++] = 'b';\n                            break;\n                        }\n\n                        case 0x09: // horizontal tab\n                        {\n                            string_buffer[bytes++] = '\\\\';\n                            string_buffer[bytes++] = 't';\n                            break;\n                        }\n\n                        case 0x0A: // newline\n                        {\n                            string_buffer[bytes++] = '\\\\';\n                            string_buffer[bytes++] = 'n';\n                            break;\n                        }\n\n                        case 0x0C: // formfeed\n                        {\n                            string_buffer[bytes++] = '\\\\';\n                            string_buffer[bytes++] = 'f';\n                            break;\n                        }\n\n                        case 0x0D: // carriage return\n                        {\n                            string_buffer[bytes++] = '\\\\';\n                            string_buffer[bytes++] = 'r';\n                            break;\n                        }\n\n                        case 0x22: // quotation mark\n                        {\n                            string_buffer[bytes++] = '\\\\';\n                            string_buffer[bytes++] = '\\\"';\n                            break;\n                        }\n\n                        case 0x5C: // reverse solidus\n                        {\n                            string_buffer[bytes++] = '\\\\';\n                            string_buffer[bytes++] = '\\\\';\n                            break;\n                        }\n\n                        default:\n                        {\n                            // escape control characters (0x00..0x1F) or, if\n                            // ensure_ascii parameter is used, non-ASCII characters\n                            if ((codepoint <= 0x1F) || (ensure_ascii && (codepoint >= 0x7F)))\n                            {\n                                if (codepoint <= 0xFFFF)\n                                {\n                                    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n                                    static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 7, \"\\\\u%04x\",\n                                                                      static_cast<std::uint16_t>(codepoint)));\n                                    bytes += 6;\n                                }\n                                else\n                                {\n                                    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n                                    static_cast<void>((std::snprintf)(string_buffer.data() + bytes, 13, \"\\\\u%04x\\\\u%04x\",\n                                                                      static_cast<std::uint16_t>(0xD7C0u + (codepoint >> 10u)),\n                                                                      static_cast<std::uint16_t>(0xDC00u + (codepoint & 0x3FFu))));\n                                    bytes += 12;\n                                }\n                            }\n                            else\n                            {\n                                // copy byte to buffer (all previous bytes\n                                // been copied have in default case above)\n                                string_buffer[bytes++] = s[i];\n                            }\n                            break;\n                        }\n                    }\n\n                    // write buffer and reset index; there must be 13 bytes\n                    // left, as this is the maximal number of bytes to be\n                    // written (\"\\uxxxx\\uxxxx\\0\") for one code point\n                    if (string_buffer.size() - bytes < 13)\n                    {\n                        o->write_characters(string_buffer.data(), bytes);\n                        bytes = 0;\n                    }\n\n                    // remember the byte position of this accept\n                    bytes_after_last_accept = bytes;\n                    undumped_chars = 0;\n                    break;\n                }\n\n                case UTF8_REJECT:  // decode found invalid UTF-8 byte\n                {\n                    switch (error_handler)\n                    {\n                        case error_handler_t::strict:\n                        {\n                            JSON_THROW(type_error::create(316, concat(\"invalid UTF-8 byte at index \", std::to_string(i), \": 0x\", hex_bytes(byte | 0)), nullptr));\n                        }\n\n                        case error_handler_t::ignore:\n                        case error_handler_t::replace:\n                        {\n                            // in case we saw this character the first time, we\n                            // would like to read it again, because the byte\n                            // may be OK for itself, but just not OK for the\n                            // previous sequence\n                            if (undumped_chars > 0)\n                            {\n                                --i;\n                            }\n\n                            // reset length buffer to the last accepted index;\n                            // thus removing/ignoring the invalid characters\n                            bytes = bytes_after_last_accept;\n\n                            if (error_handler == error_handler_t::replace)\n                            {\n                                // add a replacement character\n                                if (ensure_ascii)\n                                {\n                                    string_buffer[bytes++] = '\\\\';\n                                    string_buffer[bytes++] = 'u';\n                                    string_buffer[bytes++] = 'f';\n                                    string_buffer[bytes++] = 'f';\n                                    string_buffer[bytes++] = 'f';\n                                    string_buffer[bytes++] = 'd';\n                                }\n                                else\n                                {\n                                    string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\\xEF');\n                                    string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\\xBF');\n                                    string_buffer[bytes++] = detail::binary_writer<BasicJsonType, char>::to_char_type('\\xBD');\n                                }\n\n                                // write buffer and reset index; there must be 13 bytes\n                                // left, as this is the maximal number of bytes to be\n                                // written (\"\\uxxxx\\uxxxx\\0\") for one code point\n                                if (string_buffer.size() - bytes < 13)\n                                {\n                                    o->write_characters(string_buffer.data(), bytes);\n                                    bytes = 0;\n                                }\n\n                                bytes_after_last_accept = bytes;\n                            }\n\n                            undumped_chars = 0;\n\n                            // continue processing the string\n                            state = UTF8_ACCEPT;\n                            break;\n                        }\n\n                        default:            // LCOV_EXCL_LINE\n                            JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n                    }\n                    break;\n                }\n\n                default:  // decode found yet incomplete multi-byte code point\n                {\n                    if (!ensure_ascii)\n                    {\n                        // code point will not be escaped - copy byte to buffer\n                        string_buffer[bytes++] = s[i];\n                    }\n                    ++undumped_chars;\n                    break;\n                }\n            }\n        }\n\n        // we finished processing the string\n        if (JSON_HEDLEY_LIKELY(state == UTF8_ACCEPT))\n        {\n            // write buffer\n            if (bytes > 0)\n            {\n                o->write_characters(string_buffer.data(), bytes);\n            }\n        }\n        else\n        {\n            // we finish reading, but do not accept: string was incomplete\n            switch (error_handler)\n            {\n                case error_handler_t::strict:\n                {\n                    JSON_THROW(type_error::create(316, concat(\"incomplete UTF-8 string; last byte: 0x\", hex_bytes(static_cast<std::uint8_t>(s.back() | 0))), nullptr));\n                }\n\n                case error_handler_t::ignore:\n                {\n                    // write all accepted bytes\n                    o->write_characters(string_buffer.data(), bytes_after_last_accept);\n                    break;\n                }\n\n                case error_handler_t::replace:\n                {\n                    // write all accepted bytes\n                    o->write_characters(string_buffer.data(), bytes_after_last_accept);\n                    // add a replacement character\n                    if (ensure_ascii)\n                    {\n                        o->write_characters(\"\\\\ufffd\", 6);\n                    }\n                    else\n                    {\n                        o->write_characters(\"\\xEF\\xBF\\xBD\", 3);\n                    }\n                    break;\n                }\n\n                default:            // LCOV_EXCL_LINE\n                    JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n            }\n        }\n    }\n\n  private:\n    /*!\n    @brief count digits\n\n    Count the number of decimal (base 10) digits for an input unsigned integer.\n\n    @param[in] x  unsigned integer number to count its digits\n    @return    number of decimal digits\n    */\n    unsigned int count_digits(number_unsigned_t x) noexcept\n    {\n        unsigned int n_digits = 1;\n        for (;;)\n        {\n            if (x < 10)\n            {\n                return n_digits;\n            }\n            if (x < 100)\n            {\n                return n_digits + 1;\n            }\n            if (x < 1000)\n            {\n                return n_digits + 2;\n            }\n            if (x < 10000)\n            {\n                return n_digits + 3;\n            }\n            x = x / 10000u;\n            n_digits += 4;\n        }\n    }\n\n    /*!\n     * @brief convert a byte to a uppercase hex representation\n     * @param[in] byte byte to represent\n     * @return representation (\"00\"..\"FF\")\n     */\n    static std::string hex_bytes(std::uint8_t byte)\n    {\n        std::string result = \"FF\";\n        constexpr const char* nibble_to_hex = \"0123456789ABCDEF\";\n        result[0] = nibble_to_hex[byte / 16];\n        result[1] = nibble_to_hex[byte % 16];\n        return result;\n    }\n\n    // templates to avoid warnings about useless casts\n    template <typename NumberType, enable_if_t<std::is_signed<NumberType>::value, int> = 0>\n    bool is_negative_number(NumberType x)\n    {\n        return x < 0;\n    }\n\n    template < typename NumberType, enable_if_t <std::is_unsigned<NumberType>::value, int > = 0 >\n    bool is_negative_number(NumberType /*unused*/)\n    {\n        return false;\n    }\n\n    /*!\n    @brief dump an integer\n\n    Dump a given integer to output stream @a o. Works internally with\n    @a number_buffer.\n\n    @param[in] x  integer number (signed or unsigned) to dump\n    @tparam NumberType either @a number_integer_t or @a number_unsigned_t\n    */\n    template < typename NumberType, detail::enable_if_t <\n                   std::is_integral<NumberType>::value ||\n                   std::is_same<NumberType, number_unsigned_t>::value ||\n                   std::is_same<NumberType, number_integer_t>::value ||\n                   std::is_same<NumberType, binary_char_t>::value,\n                   int > = 0 >\n    void dump_integer(NumberType x)\n    {\n        static constexpr std::array<std::array<char, 2>, 100> digits_to_99\n        {\n            {\n                {{'0', '0'}}, {{'0', '1'}}, {{'0', '2'}}, {{'0', '3'}}, {{'0', '4'}}, {{'0', '5'}}, {{'0', '6'}}, {{'0', '7'}}, {{'0', '8'}}, {{'0', '9'}},\n                {{'1', '0'}}, {{'1', '1'}}, {{'1', '2'}}, {{'1', '3'}}, {{'1', '4'}}, {{'1', '5'}}, {{'1', '6'}}, {{'1', '7'}}, {{'1', '8'}}, {{'1', '9'}},\n                {{'2', '0'}}, {{'2', '1'}}, {{'2', '2'}}, {{'2', '3'}}, {{'2', '4'}}, {{'2', '5'}}, {{'2', '6'}}, {{'2', '7'}}, {{'2', '8'}}, {{'2', '9'}},\n                {{'3', '0'}}, {{'3', '1'}}, {{'3', '2'}}, {{'3', '3'}}, {{'3', '4'}}, {{'3', '5'}}, {{'3', '6'}}, {{'3', '7'}}, {{'3', '8'}}, {{'3', '9'}},\n                {{'4', '0'}}, {{'4', '1'}}, {{'4', '2'}}, {{'4', '3'}}, {{'4', '4'}}, {{'4', '5'}}, {{'4', '6'}}, {{'4', '7'}}, {{'4', '8'}}, {{'4', '9'}},\n                {{'5', '0'}}, {{'5', '1'}}, {{'5', '2'}}, {{'5', '3'}}, {{'5', '4'}}, {{'5', '5'}}, {{'5', '6'}}, {{'5', '7'}}, {{'5', '8'}}, {{'5', '9'}},\n                {{'6', '0'}}, {{'6', '1'}}, {{'6', '2'}}, {{'6', '3'}}, {{'6', '4'}}, {{'6', '5'}}, {{'6', '6'}}, {{'6', '7'}}, {{'6', '8'}}, {{'6', '9'}},\n                {{'7', '0'}}, {{'7', '1'}}, {{'7', '2'}}, {{'7', '3'}}, {{'7', '4'}}, {{'7', '5'}}, {{'7', '6'}}, {{'7', '7'}}, {{'7', '8'}}, {{'7', '9'}},\n                {{'8', '0'}}, {{'8', '1'}}, {{'8', '2'}}, {{'8', '3'}}, {{'8', '4'}}, {{'8', '5'}}, {{'8', '6'}}, {{'8', '7'}}, {{'8', '8'}}, {{'8', '9'}},\n                {{'9', '0'}}, {{'9', '1'}}, {{'9', '2'}}, {{'9', '3'}}, {{'9', '4'}}, {{'9', '5'}}, {{'9', '6'}}, {{'9', '7'}}, {{'9', '8'}}, {{'9', '9'}},\n            }\n        };\n\n        // special case for \"0\"\n        if (x == 0)\n        {\n            o->write_character('0');\n            return;\n        }\n\n        // use a pointer to fill the buffer\n        auto buffer_ptr = number_buffer.begin(); // NOLINT(llvm-qualified-auto,readability-qualified-auto,cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n\n        number_unsigned_t abs_value;\n\n        unsigned int n_chars{};\n\n        if (is_negative_number(x))\n        {\n            *buffer_ptr = '-';\n            abs_value = remove_sign(static_cast<number_integer_t>(x));\n\n            // account one more byte for the minus sign\n            n_chars = 1 + count_digits(abs_value);\n        }\n        else\n        {\n            abs_value = static_cast<number_unsigned_t>(x);\n            n_chars = count_digits(abs_value);\n        }\n\n        // spare 1 byte for '\\0'\n        JSON_ASSERT(n_chars < number_buffer.size() - 1);\n\n        // jump to the end to generate the string from backward,\n        // so we later avoid reversing the result\n        buffer_ptr += n_chars;\n\n        // Fast int2ascii implementation inspired by \"Fastware\" talk by Andrei Alexandrescu\n        // See: https://www.youtube.com/watch?v=o4-CwDo2zpg\n        while (abs_value >= 100)\n        {\n            const auto digits_index = static_cast<unsigned>((abs_value % 100));\n            abs_value /= 100;\n            *(--buffer_ptr) = digits_to_99[digits_index][1];\n            *(--buffer_ptr) = digits_to_99[digits_index][0];\n        }\n\n        if (abs_value >= 10)\n        {\n            const auto digits_index = static_cast<unsigned>(abs_value);\n            *(--buffer_ptr) = digits_to_99[digits_index][1];\n            *(--buffer_ptr) = digits_to_99[digits_index][0];\n        }\n        else\n        {\n            *(--buffer_ptr) = static_cast<char>('0' + abs_value);\n        }\n\n        o->write_characters(number_buffer.data(), n_chars);\n    }\n\n    /*!\n    @brief dump a floating-point number\n\n    Dump a given floating-point number to output stream @a o. Works internally\n    with @a number_buffer.\n\n    @param[in] x  floating-point number to dump\n    */\n    void dump_float(number_float_t x)\n    {\n        // NaN / inf\n        if (!std::isfinite(x))\n        {\n            o->write_characters(\"null\", 4);\n            return;\n        }\n\n        // If number_float_t is an IEEE-754 single or double precision number,\n        // use the Grisu2 algorithm to produce short numbers which are\n        // guaranteed to round-trip, using strtof and strtod, resp.\n        //\n        // NB: The test below works if <long double> == <double>.\n        static constexpr bool is_ieee_single_or_double\n            = (std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 24 && std::numeric_limits<number_float_t>::max_exponent == 128) ||\n              (std::numeric_limits<number_float_t>::is_iec559 && std::numeric_limits<number_float_t>::digits == 53 && std::numeric_limits<number_float_t>::max_exponent == 1024);\n\n        dump_float(x, std::integral_constant<bool, is_ieee_single_or_double>());\n    }\n\n    void dump_float(number_float_t x, std::true_type /*is_ieee_single_or_double*/)\n    {\n        auto* begin = number_buffer.data();\n        auto* end = ::nlohmann::detail::to_chars(begin, begin + number_buffer.size(), x);\n\n        o->write_characters(begin, static_cast<size_t>(end - begin));\n    }\n\n    void dump_float(number_float_t x, std::false_type /*is_ieee_single_or_double*/)\n    {\n        // get number of digits for a float -> text -> float round-trip\n        static constexpr auto d = std::numeric_limits<number_float_t>::max_digits10;\n\n        // the actual conversion\n        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg,hicpp-vararg)\n        std::ptrdiff_t len = (std::snprintf)(number_buffer.data(), number_buffer.size(), \"%.*g\", d, x);\n\n        // negative value indicates an error\n        JSON_ASSERT(len > 0);\n        // check if buffer was large enough\n        JSON_ASSERT(static_cast<std::size_t>(len) < number_buffer.size());\n\n        // erase thousands separator\n        if (thousands_sep != '\\0')\n        {\n            // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::remove returns an iterator, see https://github.com/nlohmann/json/issues/3081\n            const auto end = std::remove(number_buffer.begin(), number_buffer.begin() + len, thousands_sep);\n            std::fill(end, number_buffer.end(), '\\0');\n            JSON_ASSERT((end - number_buffer.begin()) <= len);\n            len = (end - number_buffer.begin());\n        }\n\n        // convert decimal point to '.'\n        if (decimal_point != '\\0' && decimal_point != '.')\n        {\n            // NOLINTNEXTLINE(readability-qualified-auto,llvm-qualified-auto): std::find returns an iterator, see https://github.com/nlohmann/json/issues/3081\n            const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point);\n            if (dec_pos != number_buffer.end())\n            {\n                *dec_pos = '.';\n            }\n        }\n\n        o->write_characters(number_buffer.data(), static_cast<std::size_t>(len));\n\n        // determine if we need to append \".0\"\n        const bool value_is_int_like =\n            std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1,\n                         [](char c)\n        {\n            return c == '.' || c == 'e';\n        });\n\n        if (value_is_int_like)\n        {\n            o->write_characters(\".0\", 2);\n        }\n    }\n\n    /*!\n    @brief check whether a string is UTF-8 encoded\n\n    The function checks each byte of a string whether it is UTF-8 encoded. The\n    result of the check is stored in the @a state parameter. The function must\n    be called initially with state 0 (accept). State 1 means the string must\n    be rejected, because the current byte is not allowed. If the string is\n    completely processed, but the state is non-zero, the string ended\n    prematurely; that is, the last byte indicated more bytes should have\n    followed.\n\n    @param[in,out] state  the state of the decoding\n    @param[in,out] codep  codepoint (valid only if resulting state is UTF8_ACCEPT)\n    @param[in] byte       next byte to decode\n    @return               new state\n\n    @note The function has been edited: a std::array is used.\n\n    @copyright Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>\n    @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa/\n    */\n    static std::uint8_t decode(std::uint8_t& state, std::uint32_t& codep, const std::uint8_t byte) noexcept\n    {\n        static const std::array<std::uint8_t, 400> utf8d =\n        {\n            {\n                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00..1F\n                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20..3F\n                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 40..5F\n                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 60..7F\n                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, // 80..9F\n                7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, // A0..BF\n                8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C0..DF\n                0xA, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, // E0..EF\n                0xB, 0x6, 0x6, 0x6, 0x5, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, // F0..FF\n                0x0, 0x1, 0x2, 0x3, 0x5, 0x8, 0x7, 0x1, 0x1, 0x1, 0x4, 0x6, 0x1, 0x1, 0x1, 0x1, // s0..s0\n                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, // s1..s2\n                1, 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // s3..s4\n                1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, // s5..s6\n                1, 3, 1, 1, 1, 1, 1, 3, 1, 3, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // s7..s8\n            }\n        };\n\n        JSON_ASSERT(byte < utf8d.size());\n        const std::uint8_t type = utf8d[byte];\n\n        codep = (state != UTF8_ACCEPT)\n                ? (byte & 0x3fu) | (codep << 6u)\n                : (0xFFu >> type) & (byte);\n\n        const std::size_t index = 256u + (static_cast<size_t>(state) * 16u) + static_cast<size_t>(type);\n        JSON_ASSERT(index < utf8d.size());\n        state = utf8d[index];\n        return state;\n    }\n\n    /*\n     * Overload to make the compiler happy while it is instantiating\n     * dump_integer for number_unsigned_t.\n     * Must never be called.\n     */\n    number_unsigned_t remove_sign(number_unsigned_t x)\n    {\n        JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n        return x; // LCOV_EXCL_LINE\n    }\n\n    /*\n     * Helper function for dump_integer\n     *\n     * This function takes a negative signed integer and returns its absolute\n     * value as unsigned integer. The plus/minus shuffling is necessary as we can\n     * not directly remove the sign of an arbitrary signed integer as the\n     * absolute values of INT_MIN and INT_MAX are usually not the same. See\n     * #1708 for details.\n     */\n    number_unsigned_t remove_sign(number_integer_t x) noexcept\n    {\n        JSON_ASSERT(x < 0 && x < (std::numeric_limits<number_integer_t>::max)()); // NOLINT(misc-redundant-expression)\n        return static_cast<number_unsigned_t>(-(x + 1)) + 1;\n    }\n\n  private:\n    /// the output of the serializer\n    output_adapter_t<char> o = nullptr;\n\n    /// a (hopefully) large enough character buffer\n    std::array<char, 64> number_buffer{{}};\n\n    /// the locale\n    const std::lconv* loc = nullptr;\n    /// the locale's thousand separator character\n    const char thousands_sep = '\\0';\n    /// the locale's decimal point character\n    const char decimal_point = '\\0';\n\n    /// string buffer\n    std::array<char, 512> string_buffer{{}};\n\n    /// the indentation character\n    const char indent_char;\n    /// the indentation string\n    string_t indent_string;\n\n    /// error_handler how to react on decoding errors\n    const error_handler_t error_handler;\n};\n\n}  // namespace detail\nNLOHMANN_JSON_NAMESPACE_END\n\n// #include <nlohmann/detail/value_t.hpp>\n\n// #include <nlohmann/json_fwd.hpp>\n\n// #include <nlohmann/ordered_map.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#include <functional> // equal_to, less\n#include <initializer_list> // initializer_list\n#include <iterator> // input_iterator_tag, iterator_traits\n#include <memory> // allocator\n#include <stdexcept> // for out_of_range\n#include <type_traits> // enable_if, is_convertible\n#include <utility> // pair\n#include <vector> // vector\n\n// #include <nlohmann/detail/macro_scope.hpp>\n\n// #include <nlohmann/detail/meta/type_traits.hpp>\n\n\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/// ordered_map: a minimal map-like container that preserves insertion order\n/// for use within nlohmann::basic_json<ordered_map>\ntemplate <class Key, class T, class IgnoredLess = std::less<Key>,\n          class Allocator = std::allocator<std::pair<const Key, T>>>\n              struct ordered_map : std::vector<std::pair<const Key, T>, Allocator>\n{\n    using key_type = Key;\n    using mapped_type = T;\n    using Container = std::vector<std::pair<const Key, T>, Allocator>;\n    using iterator = typename Container::iterator;\n    using const_iterator = typename Container::const_iterator;\n    using size_type = typename Container::size_type;\n    using value_type = typename Container::value_type;\n#ifdef JSON_HAS_CPP_14\n    using key_compare = std::equal_to<>;\n#else\n    using key_compare = std::equal_to<Key>;\n#endif\n\n    // Explicit constructors instead of `using Container::Container`\n    // otherwise older compilers choke on it (GCC <= 5.5, xcode <= 9.4)\n    ordered_map() noexcept(noexcept(Container())) : Container{} {}\n    explicit ordered_map(const Allocator& alloc) noexcept(noexcept(Container(alloc))) : Container{alloc} {}\n    template <class It>\n    ordered_map(It first, It last, const Allocator& alloc = Allocator())\n        : Container{first, last, alloc} {}\n    ordered_map(std::initializer_list<value_type> init, const Allocator& alloc = Allocator() )\n        : Container{init, alloc} {}\n\n    std::pair<iterator, bool> emplace(const key_type& key, T&& t)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return {it, false};\n            }\n        }\n        Container::emplace_back(key, std::forward<T>(t));\n        return {std::prev(this->end()), true};\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    std::pair<iterator, bool> emplace(KeyType && key, T && t)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return {it, false};\n            }\n        }\n        Container::emplace_back(std::forward<KeyType>(key), std::forward<T>(t));\n        return {std::prev(this->end()), true};\n    }\n\n    T& operator[](const key_type& key)\n    {\n        return emplace(key, T{}).first->second;\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    T & operator[](KeyType && key)\n    {\n        return emplace(std::forward<KeyType>(key), T{}).first->second;\n    }\n\n    const T& operator[](const key_type& key) const\n    {\n        return at(key);\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    const T & operator[](KeyType && key) const\n    {\n        return at(std::forward<KeyType>(key));\n    }\n\n    T& at(const key_type& key)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return it->second;\n            }\n        }\n\n        JSON_THROW(std::out_of_range(\"key not found\"));\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    T & at(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return it->second;\n            }\n        }\n\n        JSON_THROW(std::out_of_range(\"key not found\"));\n    }\n\n    const T& at(const key_type& key) const\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return it->second;\n            }\n        }\n\n        JSON_THROW(std::out_of_range(\"key not found\"));\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    const T & at(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return it->second;\n            }\n        }\n\n        JSON_THROW(std::out_of_range(\"key not found\"));\n    }\n\n    size_type erase(const key_type& key)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                // Since we cannot move const Keys, re-construct them in place\n                for (auto next = it; ++next != this->end(); ++it)\n                {\n                    it->~value_type(); // Destroy but keep allocation\n                    new (&*it) value_type{std::move(*next)};\n                }\n                Container::pop_back();\n                return 1;\n            }\n        }\n        return 0;\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    size_type erase(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                // Since we cannot move const Keys, re-construct them in place\n                for (auto next = it; ++next != this->end(); ++it)\n                {\n                    it->~value_type(); // Destroy but keep allocation\n                    new (&*it) value_type{std::move(*next)};\n                }\n                Container::pop_back();\n                return 1;\n            }\n        }\n        return 0;\n    }\n\n    iterator erase(iterator pos)\n    {\n        return erase(pos, std::next(pos));\n    }\n\n    iterator erase(iterator first, iterator last)\n    {\n        if (first == last)\n        {\n            return first;\n        }\n\n        const auto elements_affected = std::distance(first, last);\n        const auto offset = std::distance(Container::begin(), first);\n\n        // This is the start situation. We need to delete elements_affected\n        // elements (3 in this example: e, f, g), and need to return an\n        // iterator past the last deleted element (h in this example).\n        // Note that offset is the distance from the start of the vector\n        // to first. We will need this later.\n\n        // [ a, b, c, d, e, f, g, h, i, j ]\n        //               ^        ^\n        //             first    last\n\n        // Since we cannot move const Keys, we re-construct them in place.\n        // We start at first and re-construct (viz. copy) the elements from\n        // the back of the vector. Example for first iteration:\n\n        //               ,--------.\n        //               v        |   destroy e and re-construct with h\n        // [ a, b, c, d, e, f, g, h, i, j ]\n        //               ^        ^\n        //               it       it + elements_affected\n\n        for (auto it = first; std::next(it, elements_affected) != Container::end(); ++it)\n        {\n            it->~value_type(); // destroy but keep allocation\n            new (&*it) value_type{std::move(*std::next(it, elements_affected))}; // \"move\" next element to it\n        }\n\n        // [ a, b, c, d, h, i, j, h, i, j ]\n        //               ^        ^\n        //             first    last\n\n        // remove the unneeded elements at the end of the vector\n        Container::resize(this->size() - static_cast<size_type>(elements_affected));\n\n        // [ a, b, c, d, h, i, j ]\n        //               ^        ^\n        //             first    last\n\n        // first is now pointing past the last deleted element, but we cannot\n        // use this iterator, because it may have been invalidated by the\n        // resize call. Instead, we can return begin() + offset.\n        return Container::begin() + offset;\n    }\n\n    size_type count(const key_type& key) const\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return 1;\n            }\n        }\n        return 0;\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    size_type count(KeyType && key) const // NOLINT(cppcoreguidelines-missing-std-forward)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return 1;\n            }\n        }\n        return 0;\n    }\n\n    iterator find(const key_type& key)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return it;\n            }\n        }\n        return Container::end();\n    }\n\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_key_type<key_compare, key_type, KeyType>::value, int> = 0>\n    iterator find(KeyType && key) // NOLINT(cppcoreguidelines-missing-std-forward)\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return it;\n            }\n        }\n        return Container::end();\n    }\n\n    const_iterator find(const key_type& key) const\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, key))\n            {\n                return it;\n            }\n        }\n        return Container::end();\n    }\n\n    std::pair<iterator, bool> insert( value_type&& value )\n    {\n        return emplace(value.first, std::move(value.second));\n    }\n\n    std::pair<iterator, bool> insert( const value_type& value )\n    {\n        for (auto it = this->begin(); it != this->end(); ++it)\n        {\n            if (m_compare(it->first, value.first))\n            {\n                return {it, false};\n            }\n        }\n        Container::push_back(value);\n        return {--this->end(), true};\n    }\n\n    template<typename InputIt>\n    using require_input_iter = typename std::enable_if<std::is_convertible<typename std::iterator_traits<InputIt>::iterator_category,\n        std::input_iterator_tag>::value>::type;\n\n    template<typename InputIt, typename = require_input_iter<InputIt>>\n    void insert(InputIt first, InputIt last)\n    {\n        for (auto it = first; it != last; ++it)\n        {\n            insert(*it);\n        }\n    }\n\nprivate:\n    JSON_NO_UNIQUE_ADDRESS key_compare m_compare = key_compare();\n};\n\nNLOHMANN_JSON_NAMESPACE_END\n\n\n#if defined(JSON_HAS_CPP_17)\n    #if JSON_HAS_STATIC_RTTI\n        #include <any>\n    #endif\n    #include <string_view>\n#endif\n\n/*!\n@brief namespace for Niels Lohmann\n@see https://github.com/nlohmann\n@since version 1.0.0\n*/\nNLOHMANN_JSON_NAMESPACE_BEGIN\n\n/*!\n@brief a class to store JSON values\n\n@internal\n@invariant The member variables @a m_value and @a m_type have the following\nrelationship:\n- If `m_type == value_t::object`, then `m_value.object != nullptr`.\n- If `m_type == value_t::array`, then `m_value.array != nullptr`.\n- If `m_type == value_t::string`, then `m_value.string != nullptr`.\nThe invariants are checked by member function assert_invariant().\n\n@note ObjectType trick from https://stackoverflow.com/a/9860911\n@endinternal\n\n@since version 1.0.0\n\n@nosubgrouping\n*/\nNLOHMANN_BASIC_JSON_TPL_DECLARATION\nclass basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)\n    : public ::nlohmann::detail::json_base_class<CustomBaseClass>\n{\n  private:\n    template<detail::value_t> friend struct detail::external_constructor;\n\n    template<typename>\n    friend class ::nlohmann::json_pointer;\n    // can be restored when json_pointer backwards compatibility is removed\n    // friend ::nlohmann::json_pointer<StringType>;\n\n    template<typename BasicJsonType, typename InputType>\n    friend class ::nlohmann::detail::parser;\n    friend ::nlohmann::detail::serializer<basic_json>;\n    template<typename BasicJsonType>\n    friend class ::nlohmann::detail::iter_impl;\n    template<typename BasicJsonType, typename CharType>\n    friend class ::nlohmann::detail::binary_writer;\n    template<typename BasicJsonType, typename InputType, typename SAX>\n    friend class ::nlohmann::detail::binary_reader;\n    template<typename BasicJsonType, typename InputAdapterType>\n    friend class ::nlohmann::detail::json_sax_dom_parser;\n    template<typename BasicJsonType, typename InputAdapterType>\n    friend class ::nlohmann::detail::json_sax_dom_callback_parser;\n    friend class ::nlohmann::detail::exception;\n\n    /// workaround type for MSVC\n    using basic_json_t = NLOHMANN_BASIC_JSON_TPL;\n    using json_base_class_t = ::nlohmann::detail::json_base_class<CustomBaseClass>;\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    // convenience aliases for types residing in namespace detail;\n    using lexer = ::nlohmann::detail::lexer_base<basic_json>;\n\n    template<typename InputAdapterType>\n    static ::nlohmann::detail::parser<basic_json, InputAdapterType> parser(\n        InputAdapterType adapter,\n        detail::parser_callback_t<basic_json>cb = nullptr,\n        const bool allow_exceptions = true,\n        const bool ignore_comments = false\n                                 )\n    {\n        return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter),\n            std::move(cb), allow_exceptions, ignore_comments);\n    }\n\n  private:\n    using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t;\n    template<typename BasicJsonType>\n    using internal_iterator = ::nlohmann::detail::internal_iterator<BasicJsonType>;\n    template<typename BasicJsonType>\n    using iter_impl = ::nlohmann::detail::iter_impl<BasicJsonType>;\n    template<typename Iterator>\n    using iteration_proxy = ::nlohmann::detail::iteration_proxy<Iterator>;\n    template<typename Base> using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator<Base>;\n\n    template<typename CharType>\n    using output_adapter_t = ::nlohmann::detail::output_adapter_t<CharType>;\n\n    template<typename InputType>\n    using binary_reader = ::nlohmann::detail::binary_reader<basic_json, InputType>;\n    template<typename CharType> using binary_writer = ::nlohmann::detail::binary_writer<basic_json, CharType>;\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    using serializer = ::nlohmann::detail::serializer<basic_json>;\n\n  public:\n    using value_t = detail::value_t;\n    /// JSON Pointer, see @ref nlohmann::json_pointer\n    using json_pointer = ::nlohmann::json_pointer<StringType>;\n    template<typename T, typename SFINAE>\n    using json_serializer = JSONSerializer<T, SFINAE>;\n    /// how to treat decoding errors\n    using error_handler_t = detail::error_handler_t;\n    /// how to treat CBOR tags\n    using cbor_tag_handler_t = detail::cbor_tag_handler_t;\n    /// how to encode BJData\n    using bjdata_version_t = detail::bjdata_version_t;\n    /// helper type for initializer lists of basic_json values\n    using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>;\n\n    using input_format_t = detail::input_format_t;\n    /// SAX interface type, see @ref nlohmann::json_sax\n    using json_sax_t = json_sax<basic_json>;\n\n    ////////////////\n    // exceptions //\n    ////////////////\n\n    /// @name exceptions\n    /// Classes to implement user-defined exceptions.\n    /// @{\n\n    using exception = detail::exception;\n    using parse_error = detail::parse_error;\n    using invalid_iterator = detail::invalid_iterator;\n    using type_error = detail::type_error;\n    using out_of_range = detail::out_of_range;\n    using other_error = detail::other_error;\n\n    /// @}\n\n    /////////////////////\n    // container types //\n    /////////////////////\n\n    /// @name container types\n    /// The canonic container types to use @ref basic_json like any other STL\n    /// container.\n    /// @{\n\n    /// the type of elements in a basic_json container\n    using value_type = basic_json;\n\n    /// the type of an element reference\n    using reference = value_type&;\n    /// the type of an element const reference\n    using const_reference = const value_type&;\n\n    /// a type to represent differences between iterators\n    using difference_type = std::ptrdiff_t;\n    /// a type to represent container sizes\n    using size_type = std::size_t;\n\n    /// the allocator type\n    using allocator_type = AllocatorType<basic_json>;\n\n    /// the type of an element pointer\n    using pointer = typename std::allocator_traits<allocator_type>::pointer;\n    /// the type of an element const pointer\n    using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer;\n\n    /// an iterator for a basic_json container\n    using iterator = iter_impl<basic_json>;\n    /// a const iterator for a basic_json container\n    using const_iterator = iter_impl<const basic_json>;\n    /// a reverse iterator for a basic_json container\n    using reverse_iterator = json_reverse_iterator<typename basic_json::iterator>;\n    /// a const reverse iterator for a basic_json container\n    using const_reverse_iterator = json_reverse_iterator<typename basic_json::const_iterator>;\n\n    /// @}\n\n    /// @brief returns the allocator associated with the container\n    /// @sa https://json.nlohmann.me/api/basic_json/get_allocator/\n    static allocator_type get_allocator()\n    {\n        return allocator_type();\n    }\n\n    /// @brief returns version information on the library\n    /// @sa https://json.nlohmann.me/api/basic_json/meta/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json meta()\n    {\n        basic_json result;\n\n        result[\"copyright\"] = \"(C) 2013-2025 Niels Lohmann\";\n        result[\"name\"] = \"JSON for Modern C++\";\n        result[\"url\"] = \"https://github.com/nlohmann/json\";\n        result[\"version\"][\"string\"] =\n            detail::concat(std::to_string(NLOHMANN_JSON_VERSION_MAJOR), '.',\n                           std::to_string(NLOHMANN_JSON_VERSION_MINOR), '.',\n                           std::to_string(NLOHMANN_JSON_VERSION_PATCH));\n        result[\"version\"][\"major\"] = NLOHMANN_JSON_VERSION_MAJOR;\n        result[\"version\"][\"minor\"] = NLOHMANN_JSON_VERSION_MINOR;\n        result[\"version\"][\"patch\"] = NLOHMANN_JSON_VERSION_PATCH;\n\n#ifdef _WIN32\n        result[\"platform\"] = \"win32\";\n#elif defined __linux__\n        result[\"platform\"] = \"linux\";\n#elif defined __APPLE__\n        result[\"platform\"] = \"apple\";\n#elif defined __unix__\n        result[\"platform\"] = \"unix\";\n#else\n        result[\"platform\"] = \"unknown\";\n#endif\n\n#if defined(__ICC) || defined(__INTEL_COMPILER)\n        result[\"compiler\"] = {{\"family\", \"icc\"}, {\"version\", __INTEL_COMPILER}};\n#elif defined(__clang__)\n        result[\"compiler\"] = {{\"family\", \"clang\"}, {\"version\", __clang_version__}};\n#elif defined(__GNUC__) || defined(__GNUG__)\n        result[\"compiler\"] = {{\"family\", \"gcc\"}, {\"version\", detail::concat(\n                    std::to_string(__GNUC__), '.',\n                    std::to_string(__GNUC_MINOR__), '.',\n                    std::to_string(__GNUC_PATCHLEVEL__))\n            }\n        };\n#elif defined(__HP_cc) || defined(__HP_aCC)\n        result[\"compiler\"] = \"hp\"\n#elif defined(__IBMCPP__)\n        result[\"compiler\"] = {{\"family\", \"ilecpp\"}, {\"version\", __IBMCPP__}};\n#elif defined(_MSC_VER)\n        result[\"compiler\"] = {{\"family\", \"msvc\"}, {\"version\", _MSC_VER}};\n#elif defined(__PGI)\n        result[\"compiler\"] = {{\"family\", \"pgcpp\"}, {\"version\", __PGI}};\n#elif defined(__SUNPRO_CC)\n        result[\"compiler\"] = {{\"family\", \"sunpro\"}, {\"version\", __SUNPRO_CC}};\n#else\n        result[\"compiler\"] = {{\"family\", \"unknown\"}, {\"version\", \"unknown\"}};\n#endif\n\n#if defined(_MSVC_LANG)\n        result[\"compiler\"][\"c++\"] = std::to_string(_MSVC_LANG);\n#elif defined(__cplusplus)\n        result[\"compiler\"][\"c++\"] = std::to_string(__cplusplus);\n#else\n        result[\"compiler\"][\"c++\"] = \"unknown\";\n#endif\n        return result;\n    }\n\n    ///////////////////////////\n    // JSON value data types //\n    ///////////////////////////\n\n    /// @name JSON value data types\n    /// The data types to store a JSON value. These types are derived from\n    /// the template arguments passed to class @ref basic_json.\n    /// @{\n\n    /// @brief default object key comparator type\n    /// The actual object key comparator type (@ref object_comparator_t) may be\n    /// different.\n    /// @sa https://json.nlohmann.me/api/basic_json/default_object_comparator_t/\n#if defined(JSON_HAS_CPP_14)\n    // use of transparent comparator avoids unnecessary repeated construction of temporaries\n    // in functions involving lookup by key with types other than object_t::key_type (aka. StringType)\n    using default_object_comparator_t = std::less<>;\n#else\n    using default_object_comparator_t = std::less<StringType>;\n#endif\n\n    /// @brief a type for an object\n    /// @sa https://json.nlohmann.me/api/basic_json/object_t/\n    using object_t = ObjectType<StringType,\n          basic_json,\n          default_object_comparator_t,\n          AllocatorType<std::pair<const StringType,\n          basic_json>>>;\n\n    /// @brief a type for an array\n    /// @sa https://json.nlohmann.me/api/basic_json/array_t/\n    using array_t = ArrayType<basic_json, AllocatorType<basic_json>>;\n\n    /// @brief a type for a string\n    /// @sa https://json.nlohmann.me/api/basic_json/string_t/\n    using string_t = StringType;\n\n    /// @brief a type for a boolean\n    /// @sa https://json.nlohmann.me/api/basic_json/boolean_t/\n    using boolean_t = BooleanType;\n\n    /// @brief a type for a number (integer)\n    /// @sa https://json.nlohmann.me/api/basic_json/number_integer_t/\n    using number_integer_t = NumberIntegerType;\n\n    /// @brief a type for a number (unsigned)\n    /// @sa https://json.nlohmann.me/api/basic_json/number_unsigned_t/\n    using number_unsigned_t = NumberUnsignedType;\n\n    /// @brief a type for a number (floating-point)\n    /// @sa https://json.nlohmann.me/api/basic_json/number_float_t/\n    using number_float_t = NumberFloatType;\n\n    /// @brief a type for a packed binary type\n    /// @sa https://json.nlohmann.me/api/basic_json/binary_t/\n    using binary_t = nlohmann::byte_container_with_subtype<BinaryType>;\n\n    /// @brief object key comparator type\n    /// @sa https://json.nlohmann.me/api/basic_json/object_comparator_t/\n    using object_comparator_t = detail::actual_object_comparator_t<basic_json>;\n\n    /// @}\n\n  private:\n\n    /// helper for exception-safe object creation\n    template<typename T, typename... Args>\n    JSON_HEDLEY_RETURNS_NON_NULL\n    static T* create(Args&& ... args)\n    {\n        AllocatorType<T> alloc;\n        using AllocatorTraits = std::allocator_traits<AllocatorType<T>>;\n\n        auto deleter = [&](T * obj)\n        {\n            AllocatorTraits::deallocate(alloc, obj, 1);\n        };\n        std::unique_ptr<T, decltype(deleter)> obj(AllocatorTraits::allocate(alloc, 1), deleter);\n        AllocatorTraits::construct(alloc, obj.get(), std::forward<Args>(args)...);\n        JSON_ASSERT(obj != nullptr);\n        return obj.release();\n    }\n\n    ////////////////////////\n    // JSON value storage //\n    ////////////////////////\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    /*!\n    @brief a JSON value\n\n    The actual storage for a JSON value of the @ref basic_json class. This\n    union combines the different storage types for the JSON value types\n    defined in @ref value_t.\n\n    JSON type | value_t type    | used type\n    --------- | --------------- | ------------------------\n    object    | object          | pointer to @ref object_t\n    array     | array           | pointer to @ref array_t\n    string    | string          | pointer to @ref string_t\n    boolean   | boolean         | @ref boolean_t\n    number    | number_integer  | @ref number_integer_t\n    number    | number_unsigned | @ref number_unsigned_t\n    number    | number_float    | @ref number_float_t\n    binary    | binary          | pointer to @ref binary_t\n    null      | null            | *no value is stored*\n\n    @note Variable-length types (objects, arrays, and strings) are stored as\n    pointers. The size of the union should not exceed 64 bits if the default\n    value types are used.\n\n    @since version 1.0.0\n    */\n    union json_value\n    {\n        /// object (stored with pointer to save storage)\n        object_t* object;\n        /// array (stored with pointer to save storage)\n        array_t* array;\n        /// string (stored with pointer to save storage)\n        string_t* string;\n        /// binary (stored with pointer to save storage)\n        binary_t* binary;\n        /// boolean\n        boolean_t boolean;\n        /// number (integer)\n        number_integer_t number_integer;\n        /// number (unsigned integer)\n        number_unsigned_t number_unsigned;\n        /// number (floating-point)\n        number_float_t number_float;\n\n        /// default constructor (for null values)\n        json_value() = default;\n        /// constructor for booleans\n        json_value(boolean_t v) noexcept : boolean(v) {}\n        /// constructor for numbers (integer)\n        json_value(number_integer_t v) noexcept : number_integer(v) {}\n        /// constructor for numbers (unsigned)\n        json_value(number_unsigned_t v) noexcept : number_unsigned(v) {}\n        /// constructor for numbers (floating-point)\n        json_value(number_float_t v) noexcept : number_float(v) {}\n        /// constructor for empty values of a given type\n        json_value(value_t t)\n        {\n            switch (t)\n            {\n                case value_t::object:\n                {\n                    object = create<object_t>();\n                    break;\n                }\n\n                case value_t::array:\n                {\n                    array = create<array_t>();\n                    break;\n                }\n\n                case value_t::string:\n                {\n                    string = create<string_t>(\"\");\n                    break;\n                }\n\n                case value_t::binary:\n                {\n                    binary = create<binary_t>();\n                    break;\n                }\n\n                case value_t::boolean:\n                {\n                    boolean = static_cast<boolean_t>(false);\n                    break;\n                }\n\n                case value_t::number_integer:\n                {\n                    number_integer = static_cast<number_integer_t>(0);\n                    break;\n                }\n\n                case value_t::number_unsigned:\n                {\n                    number_unsigned = static_cast<number_unsigned_t>(0);\n                    break;\n                }\n\n                case value_t::number_float:\n                {\n                    number_float = static_cast<number_float_t>(0.0);\n                    break;\n                }\n\n                case value_t::null:\n                {\n                    object = nullptr;  // silence warning, see #821\n                    break;\n                }\n\n                case value_t::discarded:\n                default:\n                {\n                    object = nullptr;  // silence warning, see #821\n                    if (JSON_HEDLEY_UNLIKELY(t == value_t::null))\n                    {\n                        JSON_THROW(other_error::create(500, \"961c151d2e87f2686a955a9be24d316f1362bf21 3.12.0\", nullptr)); // LCOV_EXCL_LINE\n                    }\n                    break;\n                }\n            }\n        }\n\n        /// constructor for strings\n        json_value(const string_t& value) : string(create<string_t>(value)) {}\n\n        /// constructor for rvalue strings\n        json_value(string_t&& value) : string(create<string_t>(std::move(value))) {}\n\n        /// constructor for objects\n        json_value(const object_t& value) : object(create<object_t>(value)) {}\n\n        /// constructor for rvalue objects\n        json_value(object_t&& value) : object(create<object_t>(std::move(value))) {}\n\n        /// constructor for arrays\n        json_value(const array_t& value) : array(create<array_t>(value)) {}\n\n        /// constructor for rvalue arrays\n        json_value(array_t&& value) : array(create<array_t>(std::move(value))) {}\n\n        /// constructor for binary arrays\n        json_value(const typename binary_t::container_type& value) : binary(create<binary_t>(value)) {}\n\n        /// constructor for rvalue binary arrays\n        json_value(typename binary_t::container_type&& value) : binary(create<binary_t>(std::move(value))) {}\n\n        /// constructor for binary arrays (internal type)\n        json_value(const binary_t& value) : binary(create<binary_t>(value)) {}\n\n        /// constructor for rvalue binary arrays (internal type)\n        json_value(binary_t&& value) : binary(create<binary_t>(std::move(value))) {}\n\n        void destroy(value_t t)\n        {\n            if (\n                (t == value_t::object && object == nullptr) ||\n                (t == value_t::array && array == nullptr) ||\n                (t == value_t::string && string == nullptr) ||\n                (t == value_t::binary && binary == nullptr)\n            )\n            {\n                //not initialized (e.g. due to exception in the ctor)\n                return;\n            }\n            if (t == value_t::array || t == value_t::object)\n            {\n                // flatten the current json_value to a heap-allocated stack\n                std::vector<basic_json> stack;\n\n                // move the top-level items to stack\n                if (t == value_t::array)\n                {\n                    stack.reserve(array->size());\n                    std::move(array->begin(), array->end(), std::back_inserter(stack));\n                }\n                else\n                {\n                    stack.reserve(object->size());\n                    for (auto&& it : *object)\n                    {\n                        stack.push_back(std::move(it.second));\n                    }\n                }\n\n                while (!stack.empty())\n                {\n                    // move the last item to local variable to be processed\n                    basic_json current_item(std::move(stack.back()));\n                    stack.pop_back();\n\n                    // if current_item is array/object, move\n                    // its children to the stack to be processed later\n                    if (current_item.is_array())\n                    {\n                        std::move(current_item.m_data.m_value.array->begin(), current_item.m_data.m_value.array->end(), std::back_inserter(stack));\n\n                        current_item.m_data.m_value.array->clear();\n                    }\n                    else if (current_item.is_object())\n                    {\n                        for (auto&& it : *current_item.m_data.m_value.object)\n                        {\n                            stack.push_back(std::move(it.second));\n                        }\n\n                        current_item.m_data.m_value.object->clear();\n                    }\n\n                    // it's now safe that current_item get destructed\n                    // since it doesn't have any children\n                }\n            }\n\n            switch (t)\n            {\n                case value_t::object:\n                {\n                    AllocatorType<object_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, object);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, object, 1);\n                    break;\n                }\n\n                case value_t::array:\n                {\n                    AllocatorType<array_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, array);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, array, 1);\n                    break;\n                }\n\n                case value_t::string:\n                {\n                    AllocatorType<string_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, string);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, string, 1);\n                    break;\n                }\n\n                case value_t::binary:\n                {\n                    AllocatorType<binary_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, binary);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, binary, 1);\n                    break;\n                }\n\n                case value_t::null:\n                case value_t::boolean:\n                case value_t::number_integer:\n                case value_t::number_unsigned:\n                case value_t::number_float:\n                case value_t::discarded:\n                default:\n                {\n                    break;\n                }\n            }\n        }\n    };\n\n  private:\n    /*!\n    @brief checks the class invariants\n\n    This function asserts the class invariants. It needs to be called at the\n    end of every constructor to make sure that created objects respect the\n    invariant. Furthermore, it has to be called each time the type of a JSON\n    value is changed, because the invariant expresses a relationship between\n    @a m_type and @a m_value.\n\n    Furthermore, the parent relation is checked for arrays and objects: If\n    @a check_parents true and the value is an array or object, then the\n    container's elements must have the current value as parent.\n\n    @param[in] check_parents  whether the parent relation should be checked.\n               The value is true by default and should only be set to false\n               during destruction of objects when the invariant does not\n               need to hold.\n    */\n    void assert_invariant(bool check_parents = true) const noexcept\n    {\n        JSON_ASSERT(m_data.m_type != value_t::object || m_data.m_value.object != nullptr);\n        JSON_ASSERT(m_data.m_type != value_t::array || m_data.m_value.array != nullptr);\n        JSON_ASSERT(m_data.m_type != value_t::string || m_data.m_value.string != nullptr);\n        JSON_ASSERT(m_data.m_type != value_t::binary || m_data.m_value.binary != nullptr);\n\n#if JSON_DIAGNOSTICS\n        JSON_TRY\n        {\n            // cppcheck-suppress assertWithSideEffect\n            JSON_ASSERT(!check_parents || !is_structured() || std::all_of(begin(), end(), [this](const basic_json & j)\n            {\n                return j.m_parent == this;\n            }));\n        }\n        JSON_CATCH(...) {} // LCOV_EXCL_LINE\n#endif\n        static_cast<void>(check_parents);\n    }\n\n    void set_parents()\n    {\n#if JSON_DIAGNOSTICS\n        switch (m_data.m_type)\n        {\n            case value_t::array:\n            {\n                for (auto& element : *m_data.m_value.array)\n                {\n                    element.m_parent = this;\n                }\n                break;\n            }\n\n            case value_t::object:\n            {\n                for (auto& element : *m_data.m_value.object)\n                {\n                    element.second.m_parent = this;\n                }\n                break;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n                break;\n        }\n#endif\n    }\n\n    iterator set_parents(iterator it, typename iterator::difference_type count_set_parents)\n    {\n#if JSON_DIAGNOSTICS\n        for (typename iterator::difference_type i = 0; i < count_set_parents; ++i)\n        {\n            (it + i)->m_parent = this;\n        }\n#else\n        static_cast<void>(count_set_parents);\n#endif\n        return it;\n    }\n\n    reference set_parent(reference j, std::size_t old_capacity = detail::unknown_size())\n    {\n#if JSON_DIAGNOSTICS\n        if (old_capacity != detail::unknown_size())\n        {\n            // see https://github.com/nlohmann/json/issues/2838\n            JSON_ASSERT(type() == value_t::array);\n            if (JSON_HEDLEY_UNLIKELY(m_data.m_value.array->capacity() != old_capacity))\n            {\n                // capacity has changed: update all parents\n                set_parents();\n                return j;\n            }\n        }\n\n        // ordered_json uses a vector internally, so pointers could have\n        // been invalidated; see https://github.com/nlohmann/json/issues/2962\n#ifdef JSON_HEDLEY_MSVC_VERSION\n#pragma warning(push )\n#pragma warning(disable : 4127) // ignore warning to replace if with if constexpr\n#endif\n        if (detail::is_ordered_map<object_t>::value)\n        {\n            set_parents();\n            return j;\n        }\n#ifdef JSON_HEDLEY_MSVC_VERSION\n#pragma warning( pop )\n#endif\n\n        j.m_parent = this;\n#else\n        static_cast<void>(j);\n        static_cast<void>(old_capacity);\n#endif\n        return j;\n    }\n\n  public:\n    //////////////////////////\n    // JSON parser callback //\n    //////////////////////////\n\n    /// @brief parser event types\n    /// @sa https://json.nlohmann.me/api/basic_json/parse_event_t/\n    using parse_event_t = detail::parse_event_t;\n\n    /// @brief per-element parser callback type\n    /// @sa https://json.nlohmann.me/api/basic_json/parser_callback_t/\n    using parser_callback_t = detail::parser_callback_t<basic_json>;\n\n    //////////////////\n    // constructors //\n    //////////////////\n\n    /// @name constructors and destructors\n    /// Constructors of class @ref basic_json, copy/move constructor, copy\n    /// assignment, static functions creating objects, and the destructor.\n    /// @{\n\n    /// @brief create an empty value with a given type\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    basic_json(const value_t v)\n        : m_data(v)\n    {\n        assert_invariant();\n    }\n\n    /// @brief create a null object\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    basic_json(std::nullptr_t = nullptr) noexcept // NOLINT(bugprone-exception-escape)\n        : basic_json(value_t::null)\n    {\n        assert_invariant();\n    }\n\n    /// @brief create a JSON value from compatible types\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    template < typename CompatibleType,\n               typename U = detail::uncvref_t<CompatibleType>,\n               detail::enable_if_t <\n                   !detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >\n    basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)\n            JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),\n                                       std::forward<CompatibleType>(val))))\n    {\n        JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));\n        set_parents();\n        assert_invariant();\n    }\n\n    /// @brief create a JSON value from an existing one\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    template < typename BasicJsonType,\n               detail::enable_if_t <\n                   detail::is_basic_json<BasicJsonType>::value&& !std::is_same<basic_json, BasicJsonType>::value, int > = 0 >\n    basic_json(const BasicJsonType& val)\n#if JSON_DIAGNOSTIC_POSITIONS\n        : start_position(val.start_pos()),\n          end_position(val.end_pos())\n#endif\n    {\n        using other_boolean_t = typename BasicJsonType::boolean_t;\n        using other_number_float_t = typename BasicJsonType::number_float_t;\n        using other_number_integer_t = typename BasicJsonType::number_integer_t;\n        using other_number_unsigned_t = typename BasicJsonType::number_unsigned_t;\n        using other_string_t = typename BasicJsonType::string_t;\n        using other_object_t = typename BasicJsonType::object_t;\n        using other_array_t = typename BasicJsonType::array_t;\n        using other_binary_t = typename BasicJsonType::binary_t;\n\n        switch (val.type())\n        {\n            case value_t::boolean:\n                JSONSerializer<other_boolean_t>::to_json(*this, val.template get<other_boolean_t>());\n                break;\n            case value_t::number_float:\n                JSONSerializer<other_number_float_t>::to_json(*this, val.template get<other_number_float_t>());\n                break;\n            case value_t::number_integer:\n                JSONSerializer<other_number_integer_t>::to_json(*this, val.template get<other_number_integer_t>());\n                break;\n            case value_t::number_unsigned:\n                JSONSerializer<other_number_unsigned_t>::to_json(*this, val.template get<other_number_unsigned_t>());\n                break;\n            case value_t::string:\n                JSONSerializer<other_string_t>::to_json(*this, val.template get_ref<const other_string_t&>());\n                break;\n            case value_t::object:\n                JSONSerializer<other_object_t>::to_json(*this, val.template get_ref<const other_object_t&>());\n                break;\n            case value_t::array:\n                JSONSerializer<other_array_t>::to_json(*this, val.template get_ref<const other_array_t&>());\n                break;\n            case value_t::binary:\n                JSONSerializer<other_binary_t>::to_json(*this, val.template get_ref<const other_binary_t&>());\n                break;\n            case value_t::null:\n                *this = nullptr;\n                break;\n            case value_t::discarded:\n                m_data.m_type = value_t::discarded;\n                break;\n            default:            // LCOV_EXCL_LINE\n                JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n        }\n        JSON_ASSERT(m_data.m_type == val.type());\n\n        set_parents();\n        assert_invariant();\n    }\n\n    /// @brief create a container (array or object) from an initializer list\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    basic_json(initializer_list_t init,\n               bool type_deduction = true,\n               value_t manual_type = value_t::array)\n    {\n        // check if each element is an array with two elements whose first\n        // element is a string\n        bool is_an_object = std::all_of(init.begin(), init.end(),\n                                        [](const detail::json_ref<basic_json>& element_ref)\n        {\n            // The cast is to ensure op[size_type] is called, bearing in mind size_type may not be int;\n            // (many string types can be constructed from 0 via its null-pointer guise, so we get a\n            // broken call to op[key_type], the wrong semantics and a 4804 warning on Windows)\n            return element_ref->is_array() && element_ref->size() == 2 && (*element_ref)[static_cast<size_type>(0)].is_string();\n        });\n\n        // adjust type if type deduction is not wanted\n        if (!type_deduction)\n        {\n            // if array is wanted, do not create an object though possible\n            if (manual_type == value_t::array)\n            {\n                is_an_object = false;\n            }\n\n            // if object is wanted but impossible, throw an exception\n            if (JSON_HEDLEY_UNLIKELY(manual_type == value_t::object && !is_an_object))\n            {\n                JSON_THROW(type_error::create(301, \"cannot create object from initializer list\", nullptr));\n            }\n        }\n\n        if (is_an_object)\n        {\n            // the initializer list is a list of pairs -> create object\n            m_data.m_type = value_t::object;\n            m_data.m_value = value_t::object;\n\n            for (auto& element_ref : init)\n            {\n                auto element = element_ref.moved_or_copied();\n                m_data.m_value.object->emplace(\n                    std::move(*((*element.m_data.m_value.array)[0].m_data.m_value.string)),\n                    std::move((*element.m_data.m_value.array)[1]));\n            }\n        }\n        else\n        {\n            // the initializer list describes an array -> create array\n            m_data.m_type = value_t::array;\n            m_data.m_value.array = create<array_t>(init.begin(), init.end());\n        }\n\n        set_parents();\n        assert_invariant();\n    }\n\n    /// @brief explicitly create a binary array (without subtype)\n    /// @sa https://json.nlohmann.me/api/basic_json/binary/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json binary(const typename binary_t::container_type& init)\n    {\n        auto res = basic_json();\n        res.m_data.m_type = value_t::binary;\n        res.m_data.m_value = init;\n        return res;\n    }\n\n    /// @brief explicitly create a binary array (with subtype)\n    /// @sa https://json.nlohmann.me/api/basic_json/binary/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json binary(const typename binary_t::container_type& init, typename binary_t::subtype_type subtype)\n    {\n        auto res = basic_json();\n        res.m_data.m_type = value_t::binary;\n        res.m_data.m_value = binary_t(init, subtype);\n        return res;\n    }\n\n    /// @brief explicitly create a binary array\n    /// @sa https://json.nlohmann.me/api/basic_json/binary/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json binary(typename binary_t::container_type&& init)\n    {\n        auto res = basic_json();\n        res.m_data.m_type = value_t::binary;\n        res.m_data.m_value = std::move(init);\n        return res;\n    }\n\n    /// @brief explicitly create a binary array (with subtype)\n    /// @sa https://json.nlohmann.me/api/basic_json/binary/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json binary(typename binary_t::container_type&& init, typename binary_t::subtype_type subtype)\n    {\n        auto res = basic_json();\n        res.m_data.m_type = value_t::binary;\n        res.m_data.m_value = binary_t(std::move(init), subtype);\n        return res;\n    }\n\n    /// @brief explicitly create an array from an initializer list\n    /// @sa https://json.nlohmann.me/api/basic_json/array/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json array(initializer_list_t init = {})\n    {\n        return basic_json(init, false, value_t::array);\n    }\n\n    /// @brief explicitly create an object from an initializer list\n    /// @sa https://json.nlohmann.me/api/basic_json/object/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json object(initializer_list_t init = {})\n    {\n        return basic_json(init, false, value_t::object);\n    }\n\n    /// @brief construct an array with count copies of given value\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    basic_json(size_type cnt, const basic_json& val):\n        m_data{cnt, val}\n    {\n        set_parents();\n        assert_invariant();\n    }\n\n    /// @brief construct a JSON container given an iterator range\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    template < class InputIT, typename std::enable_if <\n                   std::is_same<InputIT, typename basic_json_t::iterator>::value ||\n                   std::is_same<InputIT, typename basic_json_t::const_iterator>::value, int >::type = 0 >\n    basic_json(InputIT first, InputIT last) // NOLINT(performance-unnecessary-value-param)\n    {\n        JSON_ASSERT(first.m_object != nullptr);\n        JSON_ASSERT(last.m_object != nullptr);\n\n        // make sure iterator fits the current value\n        if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(201, \"iterators are not compatible\", nullptr));\n        }\n\n        // copy type from first iterator\n        m_data.m_type = first.m_object->m_data.m_type;\n\n        // check if iterator range is complete for primitive values\n        switch (m_data.m_type)\n        {\n            case value_t::boolean:\n            case value_t::number_float:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::string:\n            {\n                if (JSON_HEDLEY_UNLIKELY(!first.m_it.primitive_iterator.is_begin()\n                                         || !last.m_it.primitive_iterator.is_end()))\n                {\n                    JSON_THROW(invalid_iterator::create(204, \"iterators out of range\", first.m_object));\n                }\n                break;\n            }\n\n            case value_t::null:\n            case value_t::object:\n            case value_t::array:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n                break;\n        }\n\n        switch (m_data.m_type)\n        {\n            case value_t::number_integer:\n            {\n                m_data.m_value.number_integer = first.m_object->m_data.m_value.number_integer;\n                break;\n            }\n\n            case value_t::number_unsigned:\n            {\n                m_data.m_value.number_unsigned = first.m_object->m_data.m_value.number_unsigned;\n                break;\n            }\n\n            case value_t::number_float:\n            {\n                m_data.m_value.number_float = first.m_object->m_data.m_value.number_float;\n                break;\n            }\n\n            case value_t::boolean:\n            {\n                m_data.m_value.boolean = first.m_object->m_data.m_value.boolean;\n                break;\n            }\n\n            case value_t::string:\n            {\n                m_data.m_value = *first.m_object->m_data.m_value.string;\n                break;\n            }\n\n            case value_t::object:\n            {\n                m_data.m_value.object = create<object_t>(first.m_it.object_iterator,\n                                        last.m_it.object_iterator);\n                break;\n            }\n\n            case value_t::array:\n            {\n                m_data.m_value.array = create<array_t>(first.m_it.array_iterator,\n                                                       last.m_it.array_iterator);\n                break;\n            }\n\n            case value_t::binary:\n            {\n                m_data.m_value = *first.m_object->m_data.m_value.binary;\n                break;\n            }\n\n            case value_t::null:\n            case value_t::discarded:\n            default:\n                JSON_THROW(invalid_iterator::create(206, detail::concat(\"cannot construct with iterators from \", first.m_object->type_name()), first.m_object));\n        }\n\n        set_parents();\n        assert_invariant();\n    }\n\n    ///////////////////////////////////////\n    // other constructors and destructor //\n    ///////////////////////////////////////\n\n    template<typename JsonRef,\n             detail::enable_if_t<detail::conjunction<detail::is_json_ref<JsonRef>,\n                                 std::is_same<typename JsonRef::value_type, basic_json>>::value, int> = 0 >\n    basic_json(const JsonRef& ref) : basic_json(ref.moved_or_copied()) {}\n\n    /// @brief copy constructor\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    basic_json(const basic_json& other)\n        : json_base_class_t(other)\n#if JSON_DIAGNOSTIC_POSITIONS\n        , start_position(other.start_position)\n        , end_position(other.end_position)\n#endif\n    {\n        m_data.m_type = other.m_data.m_type;\n        // check of passed value is valid\n        other.assert_invariant();\n\n        switch (m_data.m_type)\n        {\n            case value_t::object:\n            {\n                m_data.m_value = *other.m_data.m_value.object;\n                break;\n            }\n\n            case value_t::array:\n            {\n                m_data.m_value = *other.m_data.m_value.array;\n                break;\n            }\n\n            case value_t::string:\n            {\n                m_data.m_value = *other.m_data.m_value.string;\n                break;\n            }\n\n            case value_t::boolean:\n            {\n                m_data.m_value = other.m_data.m_value.boolean;\n                break;\n            }\n\n            case value_t::number_integer:\n            {\n                m_data.m_value = other.m_data.m_value.number_integer;\n                break;\n            }\n\n            case value_t::number_unsigned:\n            {\n                m_data.m_value = other.m_data.m_value.number_unsigned;\n                break;\n            }\n\n            case value_t::number_float:\n            {\n                m_data.m_value = other.m_data.m_value.number_float;\n                break;\n            }\n\n            case value_t::binary:\n            {\n                m_data.m_value = *other.m_data.m_value.binary;\n                break;\n            }\n\n            case value_t::null:\n            case value_t::discarded:\n            default:\n                break;\n        }\n\n        set_parents();\n        assert_invariant();\n    }\n\n    /// @brief move constructor\n    /// @sa https://json.nlohmann.me/api/basic_json/basic_json/\n    basic_json(basic_json&& other) noexcept\n        : json_base_class_t(std::forward<json_base_class_t>(other)),\n          m_data(std::move(other.m_data)) // cppcheck-suppress[accessForwarded] TODO check\n#if JSON_DIAGNOSTIC_POSITIONS\n        , start_position(other.start_position) // cppcheck-suppress[accessForwarded] TODO check\n        , end_position(other.end_position) // cppcheck-suppress[accessForwarded] TODO check\n#endif\n    {\n        // check that passed value is valid\n        other.assert_invariant(false); // cppcheck-suppress[accessForwarded]\n\n        // invalidate payload\n        other.m_data.m_type = value_t::null;\n        other.m_data.m_value = {};\n\n#if JSON_DIAGNOSTIC_POSITIONS\n        other.start_position = std::string::npos;\n        other.end_position = std::string::npos;\n#endif\n\n        set_parents();\n        assert_invariant();\n    }\n\n    /// @brief copy assignment\n    /// @sa https://json.nlohmann.me/api/basic_json/operator=/\n    basic_json& operator=(basic_json other) noexcept (\n        std::is_nothrow_move_constructible<value_t>::value&&\n        std::is_nothrow_move_assignable<value_t>::value&&\n        std::is_nothrow_move_constructible<json_value>::value&&\n        std::is_nothrow_move_assignable<json_value>::value&&\n        std::is_nothrow_move_assignable<json_base_class_t>::value\n    )\n    {\n        // check that passed value is valid\n        other.assert_invariant();\n\n        using std::swap;\n        swap(m_data.m_type, other.m_data.m_type);\n        swap(m_data.m_value, other.m_data.m_value);\n\n#if JSON_DIAGNOSTIC_POSITIONS\n        swap(start_position, other.start_position);\n        swap(end_position, other.end_position);\n#endif\n\n        json_base_class_t::operator=(std::move(other));\n\n        set_parents();\n        assert_invariant();\n        return *this;\n    }\n\n    /// @brief destructor\n    /// @sa https://json.nlohmann.me/api/basic_json/~basic_json/\n    ~basic_json() noexcept\n    {\n        assert_invariant(false);\n    }\n\n    /// @}\n\n  public:\n    ///////////////////////\n    // object inspection //\n    ///////////////////////\n\n    /// @name object inspection\n    /// Functions to inspect the type of a JSON value.\n    /// @{\n\n    /// @brief serialization\n    /// @sa https://json.nlohmann.me/api/basic_json/dump/\n    string_t dump(const int indent = -1,\n                  const char indent_char = ' ',\n                  const bool ensure_ascii = false,\n                  const error_handler_t error_handler = error_handler_t::strict) const\n    {\n        string_t result;\n        serializer s(detail::output_adapter<char, string_t>(result), indent_char, error_handler);\n\n        if (indent >= 0)\n        {\n            s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent));\n        }\n        else\n        {\n            s.dump(*this, false, ensure_ascii, 0);\n        }\n\n        return result;\n    }\n\n    /// @brief return the type of the JSON value (explicit)\n    /// @sa https://json.nlohmann.me/api/basic_json/type/\n    constexpr value_t type() const noexcept\n    {\n        return m_data.m_type;\n    }\n\n    /// @brief return whether type is primitive\n    /// @sa https://json.nlohmann.me/api/basic_json/is_primitive/\n    constexpr bool is_primitive() const noexcept\n    {\n        return is_null() || is_string() || is_boolean() || is_number() || is_binary();\n    }\n\n    /// @brief return whether type is structured\n    /// @sa https://json.nlohmann.me/api/basic_json/is_structured/\n    constexpr bool is_structured() const noexcept\n    {\n        return is_array() || is_object();\n    }\n\n    /// @brief return whether value is null\n    /// @sa https://json.nlohmann.me/api/basic_json/is_null/\n    constexpr bool is_null() const noexcept\n    {\n        return m_data.m_type == value_t::null;\n    }\n\n    /// @brief return whether value is a boolean\n    /// @sa https://json.nlohmann.me/api/basic_json/is_boolean/\n    constexpr bool is_boolean() const noexcept\n    {\n        return m_data.m_type == value_t::boolean;\n    }\n\n    /// @brief return whether value is a number\n    /// @sa https://json.nlohmann.me/api/basic_json/is_number/\n    constexpr bool is_number() const noexcept\n    {\n        return is_number_integer() || is_number_float();\n    }\n\n    /// @brief return whether value is an integer number\n    /// @sa https://json.nlohmann.me/api/basic_json/is_number_integer/\n    constexpr bool is_number_integer() const noexcept\n    {\n        return m_data.m_type == value_t::number_integer || m_data.m_type == value_t::number_unsigned;\n    }\n\n    /// @brief return whether value is an unsigned integer number\n    /// @sa https://json.nlohmann.me/api/basic_json/is_number_unsigned/\n    constexpr bool is_number_unsigned() const noexcept\n    {\n        return m_data.m_type == value_t::number_unsigned;\n    }\n\n    /// @brief return whether value is a floating-point number\n    /// @sa https://json.nlohmann.me/api/basic_json/is_number_float/\n    constexpr bool is_number_float() const noexcept\n    {\n        return m_data.m_type == value_t::number_float;\n    }\n\n    /// @brief return whether value is an object\n    /// @sa https://json.nlohmann.me/api/basic_json/is_object/\n    constexpr bool is_object() const noexcept\n    {\n        return m_data.m_type == value_t::object;\n    }\n\n    /// @brief return whether value is an array\n    /// @sa https://json.nlohmann.me/api/basic_json/is_array/\n    constexpr bool is_array() const noexcept\n    {\n        return m_data.m_type == value_t::array;\n    }\n\n    /// @brief return whether value is a string\n    /// @sa https://json.nlohmann.me/api/basic_json/is_string/\n    constexpr bool is_string() const noexcept\n    {\n        return m_data.m_type == value_t::string;\n    }\n\n    /// @brief return whether value is a binary array\n    /// @sa https://json.nlohmann.me/api/basic_json/is_binary/\n    constexpr bool is_binary() const noexcept\n    {\n        return m_data.m_type == value_t::binary;\n    }\n\n    /// @brief return whether value is discarded\n    /// @sa https://json.nlohmann.me/api/basic_json/is_discarded/\n    constexpr bool is_discarded() const noexcept\n    {\n        return m_data.m_type == value_t::discarded;\n    }\n\n    /// @brief return the type of the JSON value (implicit)\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_value_t/\n    constexpr operator value_t() const noexcept\n    {\n        return m_data.m_type;\n    }\n\n    /// @}\n\n  private:\n    //////////////////\n    // value access //\n    //////////////////\n\n    /// get a boolean (explicit)\n    boolean_t get_impl(boolean_t* /*unused*/) const\n    {\n        if (JSON_HEDLEY_LIKELY(is_boolean()))\n        {\n            return m_data.m_value.boolean;\n        }\n\n        JSON_THROW(type_error::create(302, detail::concat(\"type must be boolean, but is \", type_name()), this));\n    }\n\n    /// get a pointer to the value (object)\n    object_t* get_impl_ptr(object_t* /*unused*/) noexcept\n    {\n        return is_object() ? m_data.m_value.object : nullptr;\n    }\n\n    /// get a pointer to the value (object)\n    constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept\n    {\n        return is_object() ? m_data.m_value.object : nullptr;\n    }\n\n    /// get a pointer to the value (array)\n    array_t* get_impl_ptr(array_t* /*unused*/) noexcept\n    {\n        return is_array() ? m_data.m_value.array : nullptr;\n    }\n\n    /// get a pointer to the value (array)\n    constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept\n    {\n        return is_array() ? m_data.m_value.array : nullptr;\n    }\n\n    /// get a pointer to the value (string)\n    string_t* get_impl_ptr(string_t* /*unused*/) noexcept\n    {\n        return is_string() ? m_data.m_value.string : nullptr;\n    }\n\n    /// get a pointer to the value (string)\n    constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept\n    {\n        return is_string() ? m_data.m_value.string : nullptr;\n    }\n\n    /// get a pointer to the value (boolean)\n    boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept\n    {\n        return is_boolean() ? &m_data.m_value.boolean : nullptr;\n    }\n\n    /// get a pointer to the value (boolean)\n    constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept\n    {\n        return is_boolean() ? &m_data.m_value.boolean : nullptr;\n    }\n\n    /// get a pointer to the value (integer number)\n    number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept\n    {\n        return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr;\n    }\n\n    /// get a pointer to the value (integer number)\n    constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept\n    {\n        return m_data.m_type == value_t::number_integer ? &m_data.m_value.number_integer : nullptr;\n    }\n\n    /// get a pointer to the value (unsigned number)\n    number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept\n    {\n        return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr;\n    }\n\n    /// get a pointer to the value (unsigned number)\n    constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept\n    {\n        return is_number_unsigned() ? &m_data.m_value.number_unsigned : nullptr;\n    }\n\n    /// get a pointer to the value (floating-point number)\n    number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept\n    {\n        return is_number_float() ? &m_data.m_value.number_float : nullptr;\n    }\n\n    /// get a pointer to the value (floating-point number)\n    constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept\n    {\n        return is_number_float() ? &m_data.m_value.number_float : nullptr;\n    }\n\n    /// get a pointer to the value (binary)\n    binary_t* get_impl_ptr(binary_t* /*unused*/) noexcept\n    {\n        return is_binary() ? m_data.m_value.binary : nullptr;\n    }\n\n    /// get a pointer to the value (binary)\n    constexpr const binary_t* get_impl_ptr(const binary_t* /*unused*/) const noexcept\n    {\n        return is_binary() ? m_data.m_value.binary : nullptr;\n    }\n\n    /*!\n    @brief helper function to implement get_ref()\n\n    This function helps to implement get_ref() without code duplication for\n    const and non-const overloads\n\n    @tparam ThisType will be deduced as `basic_json` or `const basic_json`\n\n    @throw type_error.303 if ReferenceType does not match underlying value\n    type of the current JSON\n    */\n    template<typename ReferenceType, typename ThisType>\n    static ReferenceType get_ref_impl(ThisType& obj)\n    {\n        // delegate the call to get_ptr<>()\n        auto* ptr = obj.template get_ptr<typename std::add_pointer<ReferenceType>::type>();\n\n        if (JSON_HEDLEY_LIKELY(ptr != nullptr))\n        {\n            return *ptr;\n        }\n\n        JSON_THROW(type_error::create(303, detail::concat(\"incompatible ReferenceType for get_ref, actual type is \", obj.type_name()), &obj));\n    }\n\n  public:\n    /// @name value access\n    /// Direct access to the stored value of a JSON value.\n    /// @{\n\n    /// @brief get a pointer value (implicit)\n    /// @sa https://json.nlohmann.me/api/basic_json/get_ptr/\n    template<typename PointerType, typename std::enable_if<\n                 std::is_pointer<PointerType>::value, int>::type = 0>\n    auto get_ptr() noexcept -> decltype(std::declval<basic_json_t&>().get_impl_ptr(std::declval<PointerType>()))\n    {\n        // delegate the call to get_impl_ptr<>()\n        return get_impl_ptr(static_cast<PointerType>(nullptr));\n    }\n\n    /// @brief get a pointer value (implicit)\n    /// @sa https://json.nlohmann.me/api/basic_json/get_ptr/\n    template < typename PointerType, typename std::enable_if <\n                   std::is_pointer<PointerType>::value&&\n                   std::is_const<typename std::remove_pointer<PointerType>::type>::value, int >::type = 0 >\n    constexpr auto get_ptr() const noexcept -> decltype(std::declval<const basic_json_t&>().get_impl_ptr(std::declval<PointerType>()))\n    {\n        // delegate the call to get_impl_ptr<>() const\n        return get_impl_ptr(static_cast<PointerType>(nullptr));\n    }\n\n  private:\n    /*!\n    @brief get a value (explicit)\n\n    Explicit type conversion between the JSON value and a compatible value\n    which is [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible)\n    and [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible).\n    The value is converted by calling the @ref json_serializer<ValueType>\n    `from_json()` method.\n\n    The function is equivalent to executing\n    @code {.cpp}\n    ValueType ret;\n    JSONSerializer<ValueType>::from_json(*this, ret);\n    return ret;\n    @endcode\n\n    This overloads is chosen if:\n    - @a ValueType is not @ref basic_json,\n    - @ref json_serializer<ValueType> has a `from_json()` method of the form\n      `void from_json(const basic_json&, ValueType&)`, and\n    - @ref json_serializer<ValueType> does not have a `from_json()` method of\n      the form `ValueType from_json(const basic_json&)`\n\n    @tparam ValueType the returned value type\n\n    @return copy of the JSON value, converted to @a ValueType\n\n    @throw what @ref json_serializer<ValueType> `from_json()` method throws\n\n    @liveexample{The example below shows several conversions from JSON values\n    to other types. There a few things to note: (1) Floating-point numbers can\n    be converted to integers\\, (2) A JSON array can be converted to a standard\n    `std::vector<short>`\\, (3) A JSON object can be converted to C++\n    associative containers such as `std::unordered_map<std::string\\,\n    json>`.,get__ValueType_const}\n\n    @since version 2.1.0\n    */\n    template < typename ValueType,\n               detail::enable_if_t <\n                   detail::is_default_constructible<ValueType>::value&&\n                   detail::has_from_json<basic_json_t, ValueType>::value,\n                   int > = 0 >\n    ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept(\n            JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>())))\n    {\n        auto ret = ValueType();\n        JSONSerializer<ValueType>::from_json(*this, ret);\n        return ret;\n    }\n\n    /*!\n    @brief get a value (explicit); special case\n\n    Explicit type conversion between the JSON value and a compatible value\n    which is **not** [CopyConstructible](https://en.cppreference.com/w/cpp/named_req/CopyConstructible)\n    and **not** [DefaultConstructible](https://en.cppreference.com/w/cpp/named_req/DefaultConstructible).\n    The value is converted by calling the @ref json_serializer<ValueType>\n    `from_json()` method.\n\n    The function is equivalent to executing\n    @code {.cpp}\n    return JSONSerializer<ValueType>::from_json(*this);\n    @endcode\n\n    This overloads is chosen if:\n    - @a ValueType is not @ref basic_json and\n    - @ref json_serializer<ValueType> has a `from_json()` method of the form\n      `ValueType from_json(const basic_json&)`\n\n    @note If @ref json_serializer<ValueType> has both overloads of\n    `from_json()`, this one is chosen.\n\n    @tparam ValueType the returned value type\n\n    @return copy of the JSON value, converted to @a ValueType\n\n    @throw what @ref json_serializer<ValueType> `from_json()` method throws\n\n    @since version 2.1.0\n    */\n    template < typename ValueType,\n               detail::enable_if_t <\n                   detail::has_non_default_from_json<basic_json_t, ValueType>::value,\n                   int > = 0 >\n    ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept(\n            JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>())))\n    {\n        return JSONSerializer<ValueType>::from_json(*this);\n    }\n\n    /*!\n    @brief get special-case overload\n\n    This overloads converts the current @ref basic_json in a different\n    @ref basic_json type\n\n    @tparam BasicJsonType == @ref basic_json\n\n    @return a copy of *this, converted into @a BasicJsonType\n\n    @complexity Depending on the implementation of the called `from_json()`\n                method.\n\n    @since version 3.2.0\n    */\n    template < typename BasicJsonType,\n               detail::enable_if_t <\n                   detail::is_basic_json<BasicJsonType>::value,\n                   int > = 0 >\n    BasicJsonType get_impl(detail::priority_tag<2> /*unused*/) const\n    {\n        return *this;\n    }\n\n    /*!\n    @brief get special-case overload\n\n    This overloads avoids a lot of template boilerplate, it can be seen as the\n    identity method\n\n    @tparam BasicJsonType == @ref basic_json\n\n    @return a copy of *this\n\n    @complexity Constant.\n\n    @since version 2.1.0\n    */\n    template<typename BasicJsonType,\n             detail::enable_if_t<\n                 std::is_same<BasicJsonType, basic_json_t>::value,\n                 int> = 0>\n    basic_json get_impl(detail::priority_tag<3> /*unused*/) const\n    {\n        return *this;\n    }\n\n    /*!\n    @brief get a pointer value (explicit)\n    @copydoc get()\n    */\n    template<typename PointerType,\n             detail::enable_if_t<\n                 std::is_pointer<PointerType>::value,\n                 int> = 0>\n    constexpr auto get_impl(detail::priority_tag<4> /*unused*/) const noexcept\n    -> decltype(std::declval<const basic_json_t&>().template get_ptr<PointerType>())\n    {\n        // delegate the call to get_ptr\n        return get_ptr<PointerType>();\n    }\n\n  public:\n    /*!\n    @brief get a (pointer) value (explicit)\n\n    Performs explicit type conversion between the JSON value and a compatible value if required.\n\n    - If the requested type is a pointer to the internally stored JSON value that pointer is returned.\n    No copies are made.\n\n    - If the requested type is the current @ref basic_json, or a different @ref basic_json convertible\n    from the current @ref basic_json.\n\n    - Otherwise the value is converted by calling the @ref json_serializer<ValueType> `from_json()`\n    method.\n\n    @tparam ValueTypeCV the provided value type\n    @tparam ValueType the returned value type\n\n    @return copy of the JSON value, converted to @tparam ValueType if necessary\n\n    @throw what @ref json_serializer<ValueType> `from_json()` method throws if conversion is required\n\n    @since version 2.1.0\n    */\n    template < typename ValueTypeCV, typename ValueType = detail::uncvref_t<ValueTypeCV>>\n#if defined(JSON_HAS_CPP_14)\n    constexpr\n#endif\n    auto get() const noexcept(\n    noexcept(std::declval<const basic_json_t&>().template get_impl<ValueType>(detail::priority_tag<4> {})))\n    -> decltype(std::declval<const basic_json_t&>().template get_impl<ValueType>(detail::priority_tag<4> {}))\n    {\n        // we cannot static_assert on ValueTypeCV being non-const, because\n        // there is support for get<const basic_json_t>(), which is why we\n        // still need the uncvref\n        static_assert(!std::is_reference<ValueTypeCV>::value,\n                      \"get() cannot be used with reference types, you might want to use get_ref()\");\n        return get_impl<ValueType>(detail::priority_tag<4> {});\n    }\n\n    /*!\n    @brief get a pointer value (explicit)\n\n    Explicit pointer access to the internally stored JSON value. No copies are\n    made.\n\n    @warning The pointer becomes invalid if the underlying JSON object\n    changes.\n\n    @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref\n    object_t, @ref string_t, @ref boolean_t, @ref number_integer_t,\n    @ref number_unsigned_t, or @ref number_float_t.\n\n    @return pointer to the internally stored JSON value if the requested\n    pointer type @a PointerType fits to the JSON value; `nullptr` otherwise\n\n    @complexity Constant.\n\n    @liveexample{The example below shows how pointers to internal values of a\n    JSON value can be requested. Note that no type conversions are made and a\n    `nullptr` is returned if the value and the requested pointer type does not\n    match.,get__PointerType}\n\n    @sa see @ref get_ptr() for explicit pointer-member access\n\n    @since version 1.0.0\n    */\n    template<typename PointerType, typename std::enable_if<\n                 std::is_pointer<PointerType>::value, int>::type = 0>\n    auto get() noexcept -> decltype(std::declval<basic_json_t&>().template get_ptr<PointerType>())\n    {\n        // delegate the call to get_ptr\n        return get_ptr<PointerType>();\n    }\n\n    /// @brief get a value (explicit)\n    /// @sa https://json.nlohmann.me/api/basic_json/get_to/\n    template < typename ValueType,\n               detail::enable_if_t <\n                   !detail::is_basic_json<ValueType>::value&&\n                   detail::has_from_json<basic_json_t, ValueType>::value,\n                   int > = 0 >\n    ValueType & get_to(ValueType& v) const noexcept(noexcept(\n            JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), v)))\n    {\n        JSONSerializer<ValueType>::from_json(*this, v);\n        return v;\n    }\n\n    // specialization to allow calling get_to with a basic_json value\n    // see https://github.com/nlohmann/json/issues/2175\n    template<typename ValueType,\n             detail::enable_if_t <\n                 detail::is_basic_json<ValueType>::value,\n                 int> = 0>\n    ValueType & get_to(ValueType& v) const\n    {\n        v = *this;\n        return v;\n    }\n\n    template <\n        typename T, std::size_t N,\n        typename Array = T (&)[N], // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n        detail::enable_if_t <\n            detail::has_from_json<basic_json_t, Array>::value, int > = 0 >\n    Array get_to(T (&v)[N]) const // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)\n    noexcept(noexcept(JSONSerializer<Array>::from_json(\n                          std::declval<const basic_json_t&>(), v)))\n    {\n        JSONSerializer<Array>::from_json(*this, v);\n        return v;\n    }\n\n    /// @brief get a reference value (implicit)\n    /// @sa https://json.nlohmann.me/api/basic_json/get_ref/\n    template<typename ReferenceType, typename std::enable_if<\n                 std::is_reference<ReferenceType>::value, int>::type = 0>\n    ReferenceType get_ref()\n    {\n        // delegate call to get_ref_impl\n        return get_ref_impl<ReferenceType>(*this);\n    }\n\n    /// @brief get a reference value (implicit)\n    /// @sa https://json.nlohmann.me/api/basic_json/get_ref/\n    template < typename ReferenceType, typename std::enable_if <\n                   std::is_reference<ReferenceType>::value&&\n                   std::is_const<typename std::remove_reference<ReferenceType>::type>::value, int >::type = 0 >\n    ReferenceType get_ref() const\n    {\n        // delegate call to get_ref_impl\n        return get_ref_impl<ReferenceType>(*this);\n    }\n\n    /*!\n    @brief get a value (implicit)\n\n    Implicit type conversion between the JSON value and a compatible value.\n    The call is realized by calling @ref get() const.\n\n    @tparam ValueType non-pointer type compatible to the JSON value, for\n    instance `int` for JSON integer numbers, `bool` for JSON booleans, or\n    `std::vector` types for JSON arrays. The character type of @ref string_t\n    as well as an initializer list of this type is excluded to avoid\n    ambiguities as these types implicitly convert to `std::string`.\n\n    @return copy of the JSON value, converted to type @a ValueType\n\n    @throw type_error.302 in case passed type @a ValueType is incompatible\n    to the JSON value type (e.g., the JSON value is of type boolean, but a\n    string is requested); see example below\n\n    @complexity Linear in the size of the JSON value.\n\n    @liveexample{The example below shows several conversions from JSON values\n    to other types. There a few things to note: (1) Floating-point numbers can\n    be converted to integers\\, (2) A JSON array can be converted to a standard\n    `std::vector<short>`\\, (3) A JSON object can be converted to C++\n    associative containers such as `std::unordered_map<std::string\\,\n    json>`.,operator__ValueType}\n\n    @since version 1.0.0\n    */\n    template < typename ValueType, typename std::enable_if <\n                   detail::conjunction <\n                       detail::negation<std::is_pointer<ValueType>>,\n                       detail::negation<std::is_same<ValueType, std::nullptr_t>>,\n                       detail::negation<std::is_same<ValueType, detail::json_ref<basic_json>>>,\n                                        detail::negation<std::is_same<ValueType, typename string_t::value_type>>,\n                                        detail::negation<detail::is_basic_json<ValueType>>,\n                                        detail::negation<std::is_same<ValueType, std::initializer_list<typename string_t::value_type>>>,\n#if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914))\n                                                detail::negation<std::is_same<ValueType, std::string_view>>,\n#endif\n#if defined(JSON_HAS_CPP_17) && JSON_HAS_STATIC_RTTI\n                                                detail::negation<std::is_same<ValueType, std::any>>,\n#endif\n                                                detail::is_detected_lazy<detail::get_template_function, const basic_json_t&, ValueType>\n                                                >::value, int >::type = 0 >\n                                        JSON_EXPLICIT operator ValueType() const\n    {\n        // delegate the call to get<>() const\n        return get<ValueType>();\n    }\n\n    /// @brief get a binary value\n    /// @sa https://json.nlohmann.me/api/basic_json/get_binary/\n    binary_t& get_binary()\n    {\n        if (!is_binary())\n        {\n            JSON_THROW(type_error::create(302, detail::concat(\"type must be binary, but is \", type_name()), this));\n        }\n\n        return *get_ptr<binary_t*>();\n    }\n\n    /// @brief get a binary value\n    /// @sa https://json.nlohmann.me/api/basic_json/get_binary/\n    const binary_t& get_binary() const\n    {\n        if (!is_binary())\n        {\n            JSON_THROW(type_error::create(302, detail::concat(\"type must be binary, but is \", type_name()), this));\n        }\n\n        return *get_ptr<const binary_t*>();\n    }\n\n    /// @}\n\n    ////////////////////\n    // element access //\n    ////////////////////\n\n    /// @name element access\n    /// Access to the JSON value.\n    /// @{\n\n    /// @brief access specified array element with bounds checking\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    reference at(size_type idx)\n    {\n        // at only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            JSON_TRY\n            {\n                return set_parent(m_data.m_value.array->at(idx));\n            }\n            JSON_CATCH (std::out_of_range&)\n            {\n                // create better exception explanation\n                JSON_THROW(out_of_range::create(401, detail::concat(\"array index \", std::to_string(idx), \" is out of range\"), this));\n            } // cppcheck-suppress[missingReturn]\n        }\n        else\n        {\n            JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \", type_name()), this));\n        }\n    }\n\n    /// @brief access specified array element with bounds checking\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    const_reference at(size_type idx) const\n    {\n        // at only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            JSON_TRY\n            {\n                return m_data.m_value.array->at(idx);\n            }\n            JSON_CATCH (std::out_of_range&)\n            {\n                // create better exception explanation\n                JSON_THROW(out_of_range::create(401, detail::concat(\"array index \", std::to_string(idx), \" is out of range\"), this));\n            } // cppcheck-suppress[missingReturn]\n        }\n        else\n        {\n            JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \", type_name()), this));\n        }\n    }\n\n    /// @brief access specified object element with bounds checking\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    reference at(const typename object_t::key_type& key)\n    {\n        // at only works for objects\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \", type_name()), this));\n        }\n\n        auto it = m_data.m_value.object->find(key);\n        if (it == m_data.m_value.object->end())\n        {\n            JSON_THROW(out_of_range::create(403, detail::concat(\"key '\", key, \"' not found\"), this));\n        }\n        return set_parent(it->second);\n    }\n\n    /// @brief access specified object element with bounds checking\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n    reference at(KeyType && key)\n    {\n        // at only works for objects\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \", type_name()), this));\n        }\n\n        auto it = m_data.m_value.object->find(std::forward<KeyType>(key));\n        if (it == m_data.m_value.object->end())\n        {\n            JSON_THROW(out_of_range::create(403, detail::concat(\"key '\", string_t(std::forward<KeyType>(key)), \"' not found\"), this));\n        }\n        return set_parent(it->second);\n    }\n\n    /// @brief access specified object element with bounds checking\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    const_reference at(const typename object_t::key_type& key) const\n    {\n        // at only works for objects\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \", type_name()), this));\n        }\n\n        auto it = m_data.m_value.object->find(key);\n        if (it == m_data.m_value.object->end())\n        {\n            JSON_THROW(out_of_range::create(403, detail::concat(\"key '\", key, \"' not found\"), this));\n        }\n        return it->second;\n    }\n\n    /// @brief access specified object element with bounds checking\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n    const_reference at(KeyType && key) const\n    {\n        // at only works for objects\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(304, detail::concat(\"cannot use at() with \", type_name()), this));\n        }\n\n        auto it = m_data.m_value.object->find(std::forward<KeyType>(key));\n        if (it == m_data.m_value.object->end())\n        {\n            JSON_THROW(out_of_range::create(403, detail::concat(\"key '\", string_t(std::forward<KeyType>(key)), \"' not found\"), this));\n        }\n        return it->second;\n    }\n\n    /// @brief access specified array element\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    reference operator[](size_type idx)\n    {\n        // implicitly convert null value to an empty array\n        if (is_null())\n        {\n            m_data.m_type = value_t::array;\n            m_data.m_value.array = create<array_t>();\n            assert_invariant();\n        }\n\n        // operator[] only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            // fill up array with null values if given idx is outside range\n            if (idx >= m_data.m_value.array->size())\n            {\n#if JSON_DIAGNOSTICS\n                // remember array size & capacity before resizing\n                const auto old_size = m_data.m_value.array->size();\n                const auto old_capacity = m_data.m_value.array->capacity();\n#endif\n                m_data.m_value.array->resize(idx + 1);\n\n#if JSON_DIAGNOSTICS\n                if (JSON_HEDLEY_UNLIKELY(m_data.m_value.array->capacity() != old_capacity))\n                {\n                    // capacity has changed: update all parents\n                    set_parents();\n                }\n                else\n                {\n                    // set parent for values added above\n                    set_parents(begin() + static_cast<typename iterator::difference_type>(old_size), static_cast<typename iterator::difference_type>(idx + 1 - old_size));\n                }\n#endif\n                assert_invariant();\n            }\n\n            return m_data.m_value.array->operator[](idx);\n        }\n\n        JSON_THROW(type_error::create(305, detail::concat(\"cannot use operator[] with a numeric argument with \", type_name()), this));\n    }\n\n    /// @brief access specified array element\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    const_reference operator[](size_type idx) const\n    {\n        // const operator[] only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            return m_data.m_value.array->operator[](idx);\n        }\n\n        JSON_THROW(type_error::create(305, detail::concat(\"cannot use operator[] with a numeric argument with \", type_name()), this));\n    }\n\n    /// @brief access specified object element\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    reference operator[](typename object_t::key_type key) // NOLINT(performance-unnecessary-value-param)\n    {\n        // implicitly convert null value to an empty object\n        if (is_null())\n        {\n            m_data.m_type = value_t::object;\n            m_data.m_value.object = create<object_t>();\n            assert_invariant();\n        }\n\n        // operator[] only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            auto result = m_data.m_value.object->emplace(std::move(key), nullptr);\n            return set_parent(result.first->second);\n        }\n\n        JSON_THROW(type_error::create(305, detail::concat(\"cannot use operator[] with a string argument with \", type_name()), this));\n    }\n\n    /// @brief access specified object element\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    const_reference operator[](const typename object_t::key_type& key) const\n    {\n        // const operator[] only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            auto it = m_data.m_value.object->find(key);\n            JSON_ASSERT(it != m_data.m_value.object->end());\n            return it->second;\n        }\n\n        JSON_THROW(type_error::create(305, detail::concat(\"cannot use operator[] with a string argument with \", type_name()), this));\n    }\n\n    // these two functions resolve a (const) char * ambiguity affecting Clang and MSVC\n    // (they seemingly cannot be constrained to resolve the ambiguity)\n    template<typename T>\n    reference operator[](T* key)\n    {\n        return operator[](typename object_t::key_type(key));\n    }\n\n    template<typename T>\n    const_reference operator[](T* key) const\n    {\n        return operator[](typename object_t::key_type(key));\n    }\n\n    /// @brief access specified object element\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int > = 0 >\n    reference operator[](KeyType && key)\n    {\n        // implicitly convert null value to an empty object\n        if (is_null())\n        {\n            m_data.m_type = value_t::object;\n            m_data.m_value.object = create<object_t>();\n            assert_invariant();\n        }\n\n        // operator[] only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            auto result = m_data.m_value.object->emplace(std::forward<KeyType>(key), nullptr);\n            return set_parent(result.first->second);\n        }\n\n        JSON_THROW(type_error::create(305, detail::concat(\"cannot use operator[] with a string argument with \", type_name()), this));\n    }\n\n    /// @brief access specified object element\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int > = 0 >\n    const_reference operator[](KeyType && key) const\n    {\n        // const operator[] only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            auto it = m_data.m_value.object->find(std::forward<KeyType>(key));\n            JSON_ASSERT(it != m_data.m_value.object->end());\n            return it->second;\n        }\n\n        JSON_THROW(type_error::create(305, detail::concat(\"cannot use operator[] with a string argument with \", type_name()), this));\n    }\n\n  private:\n    template<typename KeyType>\n    using is_comparable_with_object_key = detail::is_comparable <\n        object_comparator_t, const typename object_t::key_type&, KeyType >;\n\n    template<typename ValueType>\n    using value_return_type = std::conditional <\n        detail::is_c_string_uncvref<ValueType>::value,\n        string_t, typename std::decay<ValueType>::type >;\n\n  public:\n    /// @brief access specified object element with default value\n    /// @sa https://json.nlohmann.me/api/basic_json/value/\n    template < class ValueType, detail::enable_if_t <\n                   !detail::is_transparent<object_comparator_t>::value\n                   && detail::is_getable<basic_json_t, ValueType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const\n    {\n        // value only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            // if key is found, return value and given default value otherwise\n            const auto it = find(key);\n            if (it != end())\n            {\n                return it->template get<ValueType>();\n            }\n\n            return default_value;\n        }\n\n        JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \", type_name()), this));\n    }\n\n    /// @brief access specified object element with default value\n    /// @sa https://json.nlohmann.me/api/basic_json/value/\n    template < class ValueType, class ReturnType = typename value_return_type<ValueType>::type,\n               detail::enable_if_t <\n                   !detail::is_transparent<object_comparator_t>::value\n                   && detail::is_getable<basic_json_t, ReturnType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    ReturnType value(const typename object_t::key_type& key, ValueType && default_value) const\n    {\n        // value only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            // if key is found, return value and given default value otherwise\n            const auto it = find(key);\n            if (it != end())\n            {\n                return it->template get<ReturnType>();\n            }\n\n            return std::forward<ValueType>(default_value);\n        }\n\n        JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \", type_name()), this));\n    }\n\n    /// @brief access specified object element with default value\n    /// @sa https://json.nlohmann.me/api/basic_json/value/\n    template < class ValueType, class KeyType, detail::enable_if_t <\n                   detail::is_transparent<object_comparator_t>::value\n                   && !detail::is_json_pointer<KeyType>::value\n                   && is_comparable_with_object_key<KeyType>::value\n                   && detail::is_getable<basic_json_t, ValueType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    ValueType value(KeyType && key, const ValueType& default_value) const\n    {\n        // value only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            // if key is found, return value and given default value otherwise\n            const auto it = find(std::forward<KeyType>(key));\n            if (it != end())\n            {\n                return it->template get<ValueType>();\n            }\n\n            return default_value;\n        }\n\n        JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \", type_name()), this));\n    }\n\n    /// @brief access specified object element via JSON Pointer with default value\n    /// @sa https://json.nlohmann.me/api/basic_json/value/\n    template < class ValueType, class KeyType, class ReturnType = typename value_return_type<ValueType>::type,\n               detail::enable_if_t <\n                   detail::is_transparent<object_comparator_t>::value\n                   && !detail::is_json_pointer<KeyType>::value\n                   && is_comparable_with_object_key<KeyType>::value\n                   && detail::is_getable<basic_json_t, ReturnType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    ReturnType value(KeyType && key, ValueType && default_value) const\n    {\n        // value only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            // if key is found, return value and given default value otherwise\n            const auto it = find(std::forward<KeyType>(key));\n            if (it != end())\n            {\n                return it->template get<ReturnType>();\n            }\n\n            return std::forward<ValueType>(default_value);\n        }\n\n        JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \", type_name()), this));\n    }\n\n    /// @brief access specified object element via JSON Pointer with default value\n    /// @sa https://json.nlohmann.me/api/basic_json/value/\n    template < class ValueType, detail::enable_if_t <\n                   detail::is_getable<basic_json_t, ValueType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    ValueType value(const json_pointer& ptr, const ValueType& default_value) const\n    {\n        // value only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            // if pointer resolves a value, return it or use default value\n            JSON_TRY\n            {\n                return ptr.get_checked(this).template get<ValueType>();\n            }\n            JSON_INTERNAL_CATCH (out_of_range&)\n            {\n                return default_value;\n            }\n        }\n\n        JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \", type_name()), this));\n    }\n\n    /// @brief access specified object element via JSON Pointer with default value\n    /// @sa https://json.nlohmann.me/api/basic_json/value/\n    template < class ValueType, class ReturnType = typename value_return_type<ValueType>::type,\n               detail::enable_if_t <\n                   detail::is_getable<basic_json_t, ReturnType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    ReturnType value(const json_pointer& ptr, ValueType && default_value) const\n    {\n        // value only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            // if pointer resolves a value, return it or use default value\n            JSON_TRY\n            {\n                return ptr.get_checked(this).template get<ReturnType>();\n            }\n            JSON_INTERNAL_CATCH (out_of_range&)\n            {\n                return std::forward<ValueType>(default_value);\n            }\n        }\n\n        JSON_THROW(type_error::create(306, detail::concat(\"cannot use value() with \", type_name()), this));\n    }\n\n    template < class ValueType, class BasicJsonType, detail::enable_if_t <\n                   detail::is_basic_json<BasicJsonType>::value\n                   && detail::is_getable<basic_json_t, ValueType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n    ValueType value(const ::nlohmann::json_pointer<BasicJsonType>& ptr, const ValueType& default_value) const\n    {\n        return value(ptr.convert(), default_value);\n    }\n\n    template < class ValueType, class BasicJsonType, class ReturnType = typename value_return_type<ValueType>::type,\n               detail::enable_if_t <\n                   detail::is_basic_json<BasicJsonType>::value\n                   && detail::is_getable<basic_json_t, ReturnType>::value\n                   && !std::is_same<value_t, detail::uncvref_t<ValueType>>::value, int > = 0 >\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n    ReturnType value(const ::nlohmann::json_pointer<BasicJsonType>& ptr, ValueType && default_value) const\n    {\n        return value(ptr.convert(), std::forward<ValueType>(default_value));\n    }\n\n    /// @brief access the first element\n    /// @sa https://json.nlohmann.me/api/basic_json/front/\n    reference front()\n    {\n        return *begin();\n    }\n\n    /// @brief access the first element\n    /// @sa https://json.nlohmann.me/api/basic_json/front/\n    const_reference front() const\n    {\n        return *cbegin();\n    }\n\n    /// @brief access the last element\n    /// @sa https://json.nlohmann.me/api/basic_json/back/\n    reference back()\n    {\n        auto tmp = end();\n        --tmp;\n        return *tmp;\n    }\n\n    /// @brief access the last element\n    /// @sa https://json.nlohmann.me/api/basic_json/back/\n    const_reference back() const\n    {\n        auto tmp = cend();\n        --tmp;\n        return *tmp;\n    }\n\n    /// @brief remove element given an iterator\n    /// @sa https://json.nlohmann.me/api/basic_json/erase/\n    template < class IteratorType, detail::enable_if_t <\n                   std::is_same<IteratorType, typename basic_json_t::iterator>::value ||\n                   std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int > = 0 >\n    IteratorType erase(IteratorType pos) // NOLINT(performance-unnecessary-value-param)\n    {\n        // make sure iterator fits the current value\n        if (JSON_HEDLEY_UNLIKELY(this != pos.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(202, \"iterator does not fit current value\", this));\n        }\n\n        IteratorType result = end();\n\n        switch (m_data.m_type)\n        {\n            case value_t::boolean:\n            case value_t::number_float:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::string:\n            case value_t::binary:\n            {\n                if (JSON_HEDLEY_UNLIKELY(!pos.m_it.primitive_iterator.is_begin()))\n                {\n                    JSON_THROW(invalid_iterator::create(205, \"iterator out of range\", this));\n                }\n\n                if (is_string())\n                {\n                    AllocatorType<string_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.string);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.string, 1);\n                    m_data.m_value.string = nullptr;\n                }\n                else if (is_binary())\n                {\n                    AllocatorType<binary_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.binary);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.binary, 1);\n                    m_data.m_value.binary = nullptr;\n                }\n\n                m_data.m_type = value_t::null;\n                assert_invariant();\n                break;\n            }\n\n            case value_t::object:\n            {\n                result.m_it.object_iterator = m_data.m_value.object->erase(pos.m_it.object_iterator);\n                break;\n            }\n\n            case value_t::array:\n            {\n                result.m_it.array_iterator = m_data.m_value.array->erase(pos.m_it.array_iterator);\n                break;\n            }\n\n            case value_t::null:\n            case value_t::discarded:\n            default:\n                JSON_THROW(type_error::create(307, detail::concat(\"cannot use erase() with \", type_name()), this));\n        }\n\n        return result;\n    }\n\n    /// @brief remove elements given an iterator range\n    /// @sa https://json.nlohmann.me/api/basic_json/erase/\n    template < class IteratorType, detail::enable_if_t <\n                   std::is_same<IteratorType, typename basic_json_t::iterator>::value ||\n                   std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int > = 0 >\n    IteratorType erase(IteratorType first, IteratorType last) // NOLINT(performance-unnecessary-value-param)\n    {\n        // make sure iterator fits the current value\n        if (JSON_HEDLEY_UNLIKELY(this != first.m_object || this != last.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(203, \"iterators do not fit current value\", this));\n        }\n\n        IteratorType result = end();\n\n        switch (m_data.m_type)\n        {\n            case value_t::boolean:\n            case value_t::number_float:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::string:\n            case value_t::binary:\n            {\n                if (JSON_HEDLEY_LIKELY(!first.m_it.primitive_iterator.is_begin()\n                                       || !last.m_it.primitive_iterator.is_end()))\n                {\n                    JSON_THROW(invalid_iterator::create(204, \"iterators out of range\", this));\n                }\n\n                if (is_string())\n                {\n                    AllocatorType<string_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.string);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.string, 1);\n                    m_data.m_value.string = nullptr;\n                }\n                else if (is_binary())\n                {\n                    AllocatorType<binary_t> alloc;\n                    std::allocator_traits<decltype(alloc)>::destroy(alloc, m_data.m_value.binary);\n                    std::allocator_traits<decltype(alloc)>::deallocate(alloc, m_data.m_value.binary, 1);\n                    m_data.m_value.binary = nullptr;\n                }\n\n                m_data.m_type = value_t::null;\n                assert_invariant();\n                break;\n            }\n\n            case value_t::object:\n            {\n                result.m_it.object_iterator = m_data.m_value.object->erase(first.m_it.object_iterator,\n                                              last.m_it.object_iterator);\n                break;\n            }\n\n            case value_t::array:\n            {\n                result.m_it.array_iterator = m_data.m_value.array->erase(first.m_it.array_iterator,\n                                             last.m_it.array_iterator);\n                break;\n            }\n\n            case value_t::null:\n            case value_t::discarded:\n            default:\n                JSON_THROW(type_error::create(307, detail::concat(\"cannot use erase() with \", type_name()), this));\n        }\n\n        return result;\n    }\n\n  private:\n    template < typename KeyType, detail::enable_if_t <\n                   detail::has_erase_with_key_type<basic_json_t, KeyType>::value, int > = 0 >\n    size_type erase_internal(KeyType && key)\n    {\n        // this erase only works for objects\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(307, detail::concat(\"cannot use erase() with \", type_name()), this));\n        }\n\n        return m_data.m_value.object->erase(std::forward<KeyType>(key));\n    }\n\n    template < typename KeyType, detail::enable_if_t <\n                   !detail::has_erase_with_key_type<basic_json_t, KeyType>::value, int > = 0 >\n    size_type erase_internal(KeyType && key)\n    {\n        // this erase only works for objects\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(307, detail::concat(\"cannot use erase() with \", type_name()), this));\n        }\n\n        const auto it = m_data.m_value.object->find(std::forward<KeyType>(key));\n        if (it != m_data.m_value.object->end())\n        {\n            m_data.m_value.object->erase(it);\n            return 1;\n        }\n        return 0;\n    }\n\n  public:\n\n    /// @brief remove element from a JSON object given a key\n    /// @sa https://json.nlohmann.me/api/basic_json/erase/\n    size_type erase(const typename object_t::key_type& key)\n    {\n        // the indirection via erase_internal() is added to avoid making this\n        // function a template and thus de-rank it during overload resolution\n        return erase_internal(key);\n    }\n\n    /// @brief remove element from a JSON object given a key\n    /// @sa https://json.nlohmann.me/api/basic_json/erase/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n    size_type erase(KeyType && key)\n    {\n        return erase_internal(std::forward<KeyType>(key));\n    }\n\n    /// @brief remove element from a JSON array given an index\n    /// @sa https://json.nlohmann.me/api/basic_json/erase/\n    void erase(const size_type idx)\n    {\n        // this erase only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            if (JSON_HEDLEY_UNLIKELY(idx >= size()))\n            {\n                JSON_THROW(out_of_range::create(401, detail::concat(\"array index \", std::to_string(idx), \" is out of range\"), this));\n            }\n\n            m_data.m_value.array->erase(m_data.m_value.array->begin() + static_cast<difference_type>(idx));\n        }\n        else\n        {\n            JSON_THROW(type_error::create(307, detail::concat(\"cannot use erase() with \", type_name()), this));\n        }\n    }\n\n    /// @}\n\n    ////////////\n    // lookup //\n    ////////////\n\n    /// @name lookup\n    /// @{\n\n    /// @brief find an element in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/find/\n    iterator find(const typename object_t::key_type& key)\n    {\n        auto result = end();\n\n        if (is_object())\n        {\n            result.m_it.object_iterator = m_data.m_value.object->find(key);\n        }\n\n        return result;\n    }\n\n    /// @brief find an element in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/find/\n    const_iterator find(const typename object_t::key_type& key) const\n    {\n        auto result = cend();\n\n        if (is_object())\n        {\n            result.m_it.object_iterator = m_data.m_value.object->find(key);\n        }\n\n        return result;\n    }\n\n    /// @brief find an element in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/find/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n    iterator find(KeyType && key)\n    {\n        auto result = end();\n\n        if (is_object())\n        {\n            result.m_it.object_iterator = m_data.m_value.object->find(std::forward<KeyType>(key));\n        }\n\n        return result;\n    }\n\n    /// @brief find an element in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/find/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n    const_iterator find(KeyType && key) const\n    {\n        auto result = cend();\n\n        if (is_object())\n        {\n            result.m_it.object_iterator = m_data.m_value.object->find(std::forward<KeyType>(key));\n        }\n\n        return result;\n    }\n\n    /// @brief returns the number of occurrences of a key in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/count/\n    size_type count(const typename object_t::key_type& key) const\n    {\n        // return 0 for all nonobject types\n        return is_object() ? m_data.m_value.object->count(key) : 0;\n    }\n\n    /// @brief returns the number of occurrences of a key in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/count/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n    size_type count(KeyType && key) const\n    {\n        // return 0 for all nonobject types\n        return is_object() ? m_data.m_value.object->count(std::forward<KeyType>(key)) : 0;\n    }\n\n    /// @brief check the existence of an element in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/contains/\n    bool contains(const typename object_t::key_type& key) const\n    {\n        return is_object() && m_data.m_value.object->find(key) != m_data.m_value.object->end();\n    }\n\n    /// @brief check the existence of an element in a JSON object\n    /// @sa https://json.nlohmann.me/api/basic_json/contains/\n    template<class KeyType, detail::enable_if_t<\n                 detail::is_usable_as_basic_json_key_type<basic_json_t, KeyType>::value, int> = 0>\n    bool contains(KeyType && key) const\n    {\n        return is_object() && m_data.m_value.object->find(std::forward<KeyType>(key)) != m_data.m_value.object->end();\n    }\n\n    /// @brief check the existence of an element in a JSON object given a JSON pointer\n    /// @sa https://json.nlohmann.me/api/basic_json/contains/\n    bool contains(const json_pointer& ptr) const\n    {\n        return ptr.contains(this);\n    }\n\n    template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n    bool contains(const typename ::nlohmann::json_pointer<BasicJsonType>& ptr) const\n    {\n        return ptr.contains(this);\n    }\n\n    /// @}\n\n    ///////////////\n    // iterators //\n    ///////////////\n\n    /// @name iterators\n    /// @{\n\n    /// @brief returns an iterator to the first element\n    /// @sa https://json.nlohmann.me/api/basic_json/begin/\n    iterator begin() noexcept\n    {\n        iterator result(this);\n        result.set_begin();\n        return result;\n    }\n\n    /// @brief returns an iterator to the first element\n    /// @sa https://json.nlohmann.me/api/basic_json/begin/\n    const_iterator begin() const noexcept\n    {\n        return cbegin();\n    }\n\n    /// @brief returns a const iterator to the first element\n    /// @sa https://json.nlohmann.me/api/basic_json/cbegin/\n    const_iterator cbegin() const noexcept\n    {\n        const_iterator result(this);\n        result.set_begin();\n        return result;\n    }\n\n    /// @brief returns an iterator to one past the last element\n    /// @sa https://json.nlohmann.me/api/basic_json/end/\n    iterator end() noexcept\n    {\n        iterator result(this);\n        result.set_end();\n        return result;\n    }\n\n    /// @brief returns an iterator to one past the last element\n    /// @sa https://json.nlohmann.me/api/basic_json/end/\n    const_iterator end() const noexcept\n    {\n        return cend();\n    }\n\n    /// @brief returns an iterator to one past the last element\n    /// @sa https://json.nlohmann.me/api/basic_json/cend/\n    const_iterator cend() const noexcept\n    {\n        const_iterator result(this);\n        result.set_end();\n        return result;\n    }\n\n    /// @brief returns an iterator to the reverse-beginning\n    /// @sa https://json.nlohmann.me/api/basic_json/rbegin/\n    reverse_iterator rbegin() noexcept\n    {\n        return reverse_iterator(end());\n    }\n\n    /// @brief returns an iterator to the reverse-beginning\n    /// @sa https://json.nlohmann.me/api/basic_json/rbegin/\n    const_reverse_iterator rbegin() const noexcept\n    {\n        return crbegin();\n    }\n\n    /// @brief returns an iterator to the reverse-end\n    /// @sa https://json.nlohmann.me/api/basic_json/rend/\n    reverse_iterator rend() noexcept\n    {\n        return reverse_iterator(begin());\n    }\n\n    /// @brief returns an iterator to the reverse-end\n    /// @sa https://json.nlohmann.me/api/basic_json/rend/\n    const_reverse_iterator rend() const noexcept\n    {\n        return crend();\n    }\n\n    /// @brief returns a const reverse iterator to the last element\n    /// @sa https://json.nlohmann.me/api/basic_json/crbegin/\n    const_reverse_iterator crbegin() const noexcept\n    {\n        return const_reverse_iterator(cend());\n    }\n\n    /// @brief returns a const reverse iterator to one before the first\n    /// @sa https://json.nlohmann.me/api/basic_json/crend/\n    const_reverse_iterator crend() const noexcept\n    {\n        return const_reverse_iterator(cbegin());\n    }\n\n  public:\n    /// @brief wrapper to access iterator member functions in range-based for\n    /// @sa https://json.nlohmann.me/api/basic_json/items/\n    /// @deprecated This function is deprecated since 3.1.0 and will be removed in\n    ///             version 4.0.0 of the library. Please use @ref items() instead;\n    ///             that is, replace `json::iterator_wrapper(j)` with `j.items()`.\n    JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items())\n    static iteration_proxy<iterator> iterator_wrapper(reference ref) noexcept\n    {\n        return ref.items();\n    }\n\n    /// @brief wrapper to access iterator member functions in range-based for\n    /// @sa https://json.nlohmann.me/api/basic_json/items/\n    /// @deprecated This function is deprecated since 3.1.0 and will be removed in\n    ///         version 4.0.0 of the library. Please use @ref items() instead;\n    ///         that is, replace `json::iterator_wrapper(j)` with `j.items()`.\n    JSON_HEDLEY_DEPRECATED_FOR(3.1.0, items())\n    static iteration_proxy<const_iterator> iterator_wrapper(const_reference ref) noexcept\n    {\n        return ref.items();\n    }\n\n    /// @brief helper to access iterator member functions in range-based for\n    /// @sa https://json.nlohmann.me/api/basic_json/items/\n    iteration_proxy<iterator> items() noexcept\n    {\n        return iteration_proxy<iterator>(*this);\n    }\n\n    /// @brief helper to access iterator member functions in range-based for\n    /// @sa https://json.nlohmann.me/api/basic_json/items/\n    iteration_proxy<const_iterator> items() const noexcept\n    {\n        return iteration_proxy<const_iterator>(*this);\n    }\n\n    /// @}\n\n    //////////////\n    // capacity //\n    //////////////\n\n    /// @name capacity\n    /// @{\n\n    /// @brief checks whether the container is empty.\n    /// @sa https://json.nlohmann.me/api/basic_json/empty/\n    bool empty() const noexcept\n    {\n        switch (m_data.m_type)\n        {\n            case value_t::null:\n            {\n                // null values are empty\n                return true;\n            }\n\n            case value_t::array:\n            {\n                // delegate call to array_t::empty()\n                return m_data.m_value.array->empty();\n            }\n\n            case value_t::object:\n            {\n                // delegate call to object_t::empty()\n                return m_data.m_value.object->empty();\n            }\n\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                // all other types are nonempty\n                return false;\n            }\n        }\n    }\n\n    /// @brief returns the number of elements\n    /// @sa https://json.nlohmann.me/api/basic_json/size/\n    size_type size() const noexcept\n    {\n        switch (m_data.m_type)\n        {\n            case value_t::null:\n            {\n                // null values are empty\n                return 0;\n            }\n\n            case value_t::array:\n            {\n                // delegate call to array_t::size()\n                return m_data.m_value.array->size();\n            }\n\n            case value_t::object:\n            {\n                // delegate call to object_t::size()\n                return m_data.m_value.object->size();\n            }\n\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                // all other types have size 1\n                return 1;\n            }\n        }\n    }\n\n    /// @brief returns the maximum possible number of elements\n    /// @sa https://json.nlohmann.me/api/basic_json/max_size/\n    size_type max_size() const noexcept\n    {\n        switch (m_data.m_type)\n        {\n            case value_t::array:\n            {\n                // delegate call to array_t::max_size()\n                return m_data.m_value.array->max_size();\n            }\n\n            case value_t::object:\n            {\n                // delegate call to object_t::max_size()\n                return m_data.m_value.object->max_size();\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                // all other types have max_size() == size()\n                return size();\n            }\n        }\n    }\n\n    /// @}\n\n    ///////////////\n    // modifiers //\n    ///////////////\n\n    /// @name modifiers\n    /// @{\n\n    /// @brief clears the contents\n    /// @sa https://json.nlohmann.me/api/basic_json/clear/\n    void clear() noexcept\n    {\n        switch (m_data.m_type)\n        {\n            case value_t::number_integer:\n            {\n                m_data.m_value.number_integer = 0;\n                break;\n            }\n\n            case value_t::number_unsigned:\n            {\n                m_data.m_value.number_unsigned = 0;\n                break;\n            }\n\n            case value_t::number_float:\n            {\n                m_data.m_value.number_float = 0.0;\n                break;\n            }\n\n            case value_t::boolean:\n            {\n                m_data.m_value.boolean = false;\n                break;\n            }\n\n            case value_t::string:\n            {\n                m_data.m_value.string->clear();\n                break;\n            }\n\n            case value_t::binary:\n            {\n                m_data.m_value.binary->clear();\n                break;\n            }\n\n            case value_t::array:\n            {\n                m_data.m_value.array->clear();\n                break;\n            }\n\n            case value_t::object:\n            {\n                m_data.m_value.object->clear();\n                break;\n            }\n\n            case value_t::null:\n            case value_t::discarded:\n            default:\n                break;\n        }\n    }\n\n    /// @brief add an object to an array\n    /// @sa https://json.nlohmann.me/api/basic_json/push_back/\n    void push_back(basic_json&& val)\n    {\n        // push_back only works for null objects or arrays\n        if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array())))\n        {\n            JSON_THROW(type_error::create(308, detail::concat(\"cannot use push_back() with \", type_name()), this));\n        }\n\n        // transform null object into an array\n        if (is_null())\n        {\n            m_data.m_type = value_t::array;\n            m_data.m_value = value_t::array;\n            assert_invariant();\n        }\n\n        // add element to array (move semantics)\n        const auto old_capacity = m_data.m_value.array->capacity();\n        m_data.m_value.array->push_back(std::move(val));\n        set_parent(m_data.m_value.array->back(), old_capacity);\n        // if val is moved from, basic_json move constructor marks it null, so we do not call the destructor\n    }\n\n    /// @brief add an object to an array\n    /// @sa https://json.nlohmann.me/api/basic_json/operator+=/\n    reference operator+=(basic_json&& val)\n    {\n        push_back(std::move(val));\n        return *this;\n    }\n\n    /// @brief add an object to an array\n    /// @sa https://json.nlohmann.me/api/basic_json/push_back/\n    void push_back(const basic_json& val)\n    {\n        // push_back only works for null objects or arrays\n        if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array())))\n        {\n            JSON_THROW(type_error::create(308, detail::concat(\"cannot use push_back() with \", type_name()), this));\n        }\n\n        // transform null object into an array\n        if (is_null())\n        {\n            m_data.m_type = value_t::array;\n            m_data.m_value = value_t::array;\n            assert_invariant();\n        }\n\n        // add element to array\n        const auto old_capacity = m_data.m_value.array->capacity();\n        m_data.m_value.array->push_back(val);\n        set_parent(m_data.m_value.array->back(), old_capacity);\n    }\n\n    /// @brief add an object to an array\n    /// @sa https://json.nlohmann.me/api/basic_json/operator+=/\n    reference operator+=(const basic_json& val)\n    {\n        push_back(val);\n        return *this;\n    }\n\n    /// @brief add an object to an object\n    /// @sa https://json.nlohmann.me/api/basic_json/push_back/\n    void push_back(const typename object_t::value_type& val)\n    {\n        // push_back only works for null objects or objects\n        if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object())))\n        {\n            JSON_THROW(type_error::create(308, detail::concat(\"cannot use push_back() with \", type_name()), this));\n        }\n\n        // transform null object into an object\n        if (is_null())\n        {\n            m_data.m_type = value_t::object;\n            m_data.m_value = value_t::object;\n            assert_invariant();\n        }\n\n        // add element to object\n        auto res = m_data.m_value.object->insert(val);\n        set_parent(res.first->second);\n    }\n\n    /// @brief add an object to an object\n    /// @sa https://json.nlohmann.me/api/basic_json/operator+=/\n    reference operator+=(const typename object_t::value_type& val)\n    {\n        push_back(val);\n        return *this;\n    }\n\n    /// @brief add an object to an object\n    /// @sa https://json.nlohmann.me/api/basic_json/push_back/\n    void push_back(initializer_list_t init)\n    {\n        if (is_object() && init.size() == 2 && (*init.begin())->is_string())\n        {\n            basic_json&& key = init.begin()->moved_or_copied();\n            push_back(typename object_t::value_type(\n                          std::move(key.get_ref<string_t&>()), (init.begin() + 1)->moved_or_copied()));\n        }\n        else\n        {\n            push_back(basic_json(init));\n        }\n    }\n\n    /// @brief add an object to an object\n    /// @sa https://json.nlohmann.me/api/basic_json/operator+=/\n    reference operator+=(initializer_list_t init)\n    {\n        push_back(init);\n        return *this;\n    }\n\n    /// @brief add an object to an array\n    /// @sa https://json.nlohmann.me/api/basic_json/emplace_back/\n    template<class... Args>\n    reference emplace_back(Args&& ... args)\n    {\n        // emplace_back only works for null objects or arrays\n        if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_array())))\n        {\n            JSON_THROW(type_error::create(311, detail::concat(\"cannot use emplace_back() with \", type_name()), this));\n        }\n\n        // transform null object into an array\n        if (is_null())\n        {\n            m_data.m_type = value_t::array;\n            m_data.m_value = value_t::array;\n            assert_invariant();\n        }\n\n        // add element to array (perfect forwarding)\n        const auto old_capacity = m_data.m_value.array->capacity();\n        m_data.m_value.array->emplace_back(std::forward<Args>(args)...);\n        return set_parent(m_data.m_value.array->back(), old_capacity);\n    }\n\n    /// @brief add an object to an object if key does not exist\n    /// @sa https://json.nlohmann.me/api/basic_json/emplace/\n    template<class... Args>\n    std::pair<iterator, bool> emplace(Args&& ... args)\n    {\n        // emplace only works for null objects or arrays\n        if (JSON_HEDLEY_UNLIKELY(!(is_null() || is_object())))\n        {\n            JSON_THROW(type_error::create(311, detail::concat(\"cannot use emplace() with \", type_name()), this));\n        }\n\n        // transform null object into an object\n        if (is_null())\n        {\n            m_data.m_type = value_t::object;\n            m_data.m_value = value_t::object;\n            assert_invariant();\n        }\n\n        // add element to array (perfect forwarding)\n        auto res = m_data.m_value.object->emplace(std::forward<Args>(args)...);\n        set_parent(res.first->second);\n\n        // create result iterator and set iterator to the result of emplace\n        auto it = begin();\n        it.m_it.object_iterator = res.first;\n\n        // return pair of iterator and boolean\n        return {it, res.second};\n    }\n\n    /// Helper for insertion of an iterator\n    /// @note: This uses std::distance to support GCC 4.8,\n    ///        see https://github.com/nlohmann/json/pull/1257\n    template<typename... Args>\n    iterator insert_iterator(const_iterator pos, Args&& ... args) // NOLINT(performance-unnecessary-value-param)\n    {\n        iterator result(this);\n        JSON_ASSERT(m_data.m_value.array != nullptr);\n\n        auto insert_pos = std::distance(m_data.m_value.array->begin(), pos.m_it.array_iterator);\n        m_data.m_value.array->insert(pos.m_it.array_iterator, std::forward<Args>(args)...);\n        result.m_it.array_iterator = m_data.m_value.array->begin() + insert_pos;\n\n        // This could have been written as:\n        // result.m_it.array_iterator = m_data.m_value.array->insert(pos.m_it.array_iterator, cnt, val);\n        // but the return value of insert is missing in GCC 4.8, so it is written this way instead.\n\n        set_parents();\n        return result;\n    }\n\n    /// @brief inserts element into array\n    /// @sa https://json.nlohmann.me/api/basic_json/insert/\n    iterator insert(const_iterator pos, const basic_json& val) // NOLINT(performance-unnecessary-value-param)\n    {\n        // insert only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            // check if iterator pos fits to this JSON value\n            if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))\n            {\n                JSON_THROW(invalid_iterator::create(202, \"iterator does not fit current value\", this));\n            }\n\n            // insert to array and return iterator\n            return insert_iterator(pos, val);\n        }\n\n        JSON_THROW(type_error::create(309, detail::concat(\"cannot use insert() with \", type_name()), this));\n    }\n\n    /// @brief inserts element into array\n    /// @sa https://json.nlohmann.me/api/basic_json/insert/\n    iterator insert(const_iterator pos, basic_json&& val) // NOLINT(performance-unnecessary-value-param)\n    {\n        return insert(pos, val);\n    }\n\n    /// @brief inserts copies of element into array\n    /// @sa https://json.nlohmann.me/api/basic_json/insert/\n    iterator insert(const_iterator pos, size_type cnt, const basic_json& val) // NOLINT(performance-unnecessary-value-param)\n    {\n        // insert only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            // check if iterator pos fits to this JSON value\n            if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))\n            {\n                JSON_THROW(invalid_iterator::create(202, \"iterator does not fit current value\", this));\n            }\n\n            // insert to array and return iterator\n            return insert_iterator(pos, cnt, val);\n        }\n\n        JSON_THROW(type_error::create(309, detail::concat(\"cannot use insert() with \", type_name()), this));\n    }\n\n    /// @brief inserts range of elements into array\n    /// @sa https://json.nlohmann.me/api/basic_json/insert/\n    iterator insert(const_iterator pos, const_iterator first, const_iterator last) // NOLINT(performance-unnecessary-value-param)\n    {\n        // insert only works for arrays\n        if (JSON_HEDLEY_UNLIKELY(!is_array()))\n        {\n            JSON_THROW(type_error::create(309, detail::concat(\"cannot use insert() with \", type_name()), this));\n        }\n\n        // check if iterator pos fits to this JSON value\n        if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))\n        {\n            JSON_THROW(invalid_iterator::create(202, \"iterator does not fit current value\", this));\n        }\n\n        // check if range iterators belong to the same JSON object\n        if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(210, \"iterators do not fit\", this));\n        }\n\n        if (JSON_HEDLEY_UNLIKELY(first.m_object == this))\n        {\n            JSON_THROW(invalid_iterator::create(211, \"passed iterators may not belong to container\", this));\n        }\n\n        // insert to array and return iterator\n        return insert_iterator(pos, first.m_it.array_iterator, last.m_it.array_iterator);\n    }\n\n    /// @brief inserts elements from initializer list into array\n    /// @sa https://json.nlohmann.me/api/basic_json/insert/\n    iterator insert(const_iterator pos, initializer_list_t ilist) // NOLINT(performance-unnecessary-value-param)\n    {\n        // insert only works for arrays\n        if (JSON_HEDLEY_UNLIKELY(!is_array()))\n        {\n            JSON_THROW(type_error::create(309, detail::concat(\"cannot use insert() with \", type_name()), this));\n        }\n\n        // check if iterator pos fits to this JSON value\n        if (JSON_HEDLEY_UNLIKELY(pos.m_object != this))\n        {\n            JSON_THROW(invalid_iterator::create(202, \"iterator does not fit current value\", this));\n        }\n\n        // insert to array and return iterator\n        return insert_iterator(pos, ilist.begin(), ilist.end());\n    }\n\n    /// @brief inserts range of elements into object\n    /// @sa https://json.nlohmann.me/api/basic_json/insert/\n    void insert(const_iterator first, const_iterator last) // NOLINT(performance-unnecessary-value-param)\n    {\n        // insert only works for objects\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(309, detail::concat(\"cannot use insert() with \", type_name()), this));\n        }\n\n        // check if range iterators belong to the same JSON object\n        if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(210, \"iterators do not fit\", this));\n        }\n\n        // passed iterators must belong to objects\n        if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object()))\n        {\n            JSON_THROW(invalid_iterator::create(202, \"iterators first and last must point to objects\", this));\n        }\n\n        m_data.m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator);\n        set_parents();\n    }\n\n    /// @brief updates a JSON object from another object, overwriting existing keys\n    /// @sa https://json.nlohmann.me/api/basic_json/update/\n    void update(const_reference j, bool merge_objects = false)\n    {\n        update(j.begin(), j.end(), merge_objects);\n    }\n\n    /// @brief updates a JSON object from another object, overwriting existing keys\n    /// @sa https://json.nlohmann.me/api/basic_json/update/\n    void update(const_iterator first, const_iterator last, bool merge_objects = false) // NOLINT(performance-unnecessary-value-param)\n    {\n        // implicitly convert null value to an empty object\n        if (is_null())\n        {\n            m_data.m_type = value_t::object;\n            m_data.m_value.object = create<object_t>();\n            assert_invariant();\n        }\n\n        if (JSON_HEDLEY_UNLIKELY(!is_object()))\n        {\n            JSON_THROW(type_error::create(312, detail::concat(\"cannot use update() with \", type_name()), this));\n        }\n\n        // check if range iterators belong to the same JSON object\n        if (JSON_HEDLEY_UNLIKELY(first.m_object != last.m_object))\n        {\n            JSON_THROW(invalid_iterator::create(210, \"iterators do not fit\", this));\n        }\n\n        // passed iterators must belong to objects\n        if (JSON_HEDLEY_UNLIKELY(!first.m_object->is_object()))\n        {\n            JSON_THROW(type_error::create(312, detail::concat(\"cannot use update() with \", first.m_object->type_name()), first.m_object));\n        }\n\n        for (auto it = first; it != last; ++it)\n        {\n            if (merge_objects && it.value().is_object())\n            {\n                auto it2 = m_data.m_value.object->find(it.key());\n                if (it2 != m_data.m_value.object->end())\n                {\n                    it2->second.update(it.value(), true);\n                    continue;\n                }\n            }\n            m_data.m_value.object->operator[](it.key()) = it.value();\n#if JSON_DIAGNOSTICS\n            m_data.m_value.object->operator[](it.key()).m_parent = this;\n#endif\n        }\n    }\n\n    /// @brief exchanges the values\n    /// @sa https://json.nlohmann.me/api/basic_json/swap/\n    void swap(reference other) noexcept (\n        std::is_nothrow_move_constructible<value_t>::value&&\n        std::is_nothrow_move_assignable<value_t>::value&&\n        std::is_nothrow_move_constructible<json_value>::value&& // NOLINT(cppcoreguidelines-noexcept-swap,performance-noexcept-swap)\n        std::is_nothrow_move_assignable<json_value>::value\n    )\n    {\n        std::swap(m_data.m_type, other.m_data.m_type);\n        std::swap(m_data.m_value, other.m_data.m_value);\n\n        set_parents();\n        other.set_parents();\n        assert_invariant();\n    }\n\n    /// @brief exchanges the values\n    /// @sa https://json.nlohmann.me/api/basic_json/swap/\n    friend void swap(reference left, reference right) noexcept (\n        std::is_nothrow_move_constructible<value_t>::value&&\n        std::is_nothrow_move_assignable<value_t>::value&&\n        std::is_nothrow_move_constructible<json_value>::value&& // NOLINT(cppcoreguidelines-noexcept-swap,performance-noexcept-swap)\n        std::is_nothrow_move_assignable<json_value>::value\n    )\n    {\n        left.swap(right);\n    }\n\n    /// @brief exchanges the values\n    /// @sa https://json.nlohmann.me/api/basic_json/swap/\n    void swap(array_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)\n    {\n        // swap only works for arrays\n        if (JSON_HEDLEY_LIKELY(is_array()))\n        {\n            using std::swap;\n            swap(*(m_data.m_value.array), other);\n        }\n        else\n        {\n            JSON_THROW(type_error::create(310, detail::concat(\"cannot use swap(array_t&) with \", type_name()), this));\n        }\n    }\n\n    /// @brief exchanges the values\n    /// @sa https://json.nlohmann.me/api/basic_json/swap/\n    void swap(object_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)\n    {\n        // swap only works for objects\n        if (JSON_HEDLEY_LIKELY(is_object()))\n        {\n            using std::swap;\n            swap(*(m_data.m_value.object), other);\n        }\n        else\n        {\n            JSON_THROW(type_error::create(310, detail::concat(\"cannot use swap(object_t&) with \", type_name()), this));\n        }\n    }\n\n    /// @brief exchanges the values\n    /// @sa https://json.nlohmann.me/api/basic_json/swap/\n    void swap(string_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)\n    {\n        // swap only works for strings\n        if (JSON_HEDLEY_LIKELY(is_string()))\n        {\n            using std::swap;\n            swap(*(m_data.m_value.string), other);\n        }\n        else\n        {\n            JSON_THROW(type_error::create(310, detail::concat(\"cannot use swap(string_t&) with \", type_name()), this));\n        }\n    }\n\n    /// @brief exchanges the values\n    /// @sa https://json.nlohmann.me/api/basic_json/swap/\n    void swap(binary_t& other) // NOLINT(bugprone-exception-escape,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)\n    {\n        // swap only works for strings\n        if (JSON_HEDLEY_LIKELY(is_binary()))\n        {\n            using std::swap;\n            swap(*(m_data.m_value.binary), other);\n        }\n        else\n        {\n            JSON_THROW(type_error::create(310, detail::concat(\"cannot use swap(binary_t&) with \", type_name()), this));\n        }\n    }\n\n    /// @brief exchanges the values\n    /// @sa https://json.nlohmann.me/api/basic_json/swap/\n    void swap(typename binary_t::container_type& other) // NOLINT(bugprone-exception-escape)\n    {\n        // swap only works for strings\n        if (JSON_HEDLEY_LIKELY(is_binary()))\n        {\n            using std::swap;\n            swap(*(m_data.m_value.binary), other);\n        }\n        else\n        {\n            JSON_THROW(type_error::create(310, detail::concat(\"cannot use swap(binary_t::container_type&) with \", type_name()), this));\n        }\n    }\n\n    /// @}\n\n    //////////////////////////////////////////\n    // lexicographical comparison operators //\n    //////////////////////////////////////////\n\n    /// @name lexicographical comparison operators\n    /// @{\n\n    // note parentheses around operands are necessary; see\n    // https://github.com/nlohmann/json/issues/1530\n#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result)                       \\\n    const auto lhs_type = lhs.type();                                                                    \\\n    const auto rhs_type = rhs.type();                                                                    \\\n    \\\n    if (lhs_type == rhs_type) /* NOLINT(readability/braces) */                                           \\\n    {                                                                                                    \\\n        switch (lhs_type)                                                                                \\\n        {                                                                                                \\\n            case value_t::array:                                                                         \\\n                return (*lhs.m_data.m_value.array) op (*rhs.m_data.m_value.array);                                     \\\n                \\\n            case value_t::object:                                                                        \\\n                return (*lhs.m_data.m_value.object) op (*rhs.m_data.m_value.object);                                   \\\n                \\\n            case value_t::null:                                                                          \\\n                return (null_result);                                                                    \\\n                \\\n            case value_t::string:                                                                        \\\n                return (*lhs.m_data.m_value.string) op (*rhs.m_data.m_value.string);                                   \\\n                \\\n            case value_t::boolean:                                                                       \\\n                return (lhs.m_data.m_value.boolean) op (rhs.m_data.m_value.boolean);                                   \\\n                \\\n            case value_t::number_integer:                                                                \\\n                return (lhs.m_data.m_value.number_integer) op (rhs.m_data.m_value.number_integer);                     \\\n                \\\n            case value_t::number_unsigned:                                                               \\\n                return (lhs.m_data.m_value.number_unsigned) op (rhs.m_data.m_value.number_unsigned);                   \\\n                \\\n            case value_t::number_float:                                                                  \\\n                return (lhs.m_data.m_value.number_float) op (rhs.m_data.m_value.number_float);                         \\\n                \\\n            case value_t::binary:                                                                        \\\n                return (*lhs.m_data.m_value.binary) op (*rhs.m_data.m_value.binary);                                   \\\n                \\\n            case value_t::discarded:                                                                     \\\n            default:                                                                                     \\\n                return (unordered_result);                                                               \\\n        }                                                                                                \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float)                   \\\n    {                                                                                                    \\\n        return static_cast<number_float_t>(lhs.m_data.m_value.number_integer) op rhs.m_data.m_value.number_float;      \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer)                   \\\n    {                                                                                                    \\\n        return lhs.m_data.m_value.number_float op static_cast<number_float_t>(rhs.m_data.m_value.number_integer);      \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float)                  \\\n    {                                                                                                    \\\n        return static_cast<number_float_t>(lhs.m_data.m_value.number_unsigned) op rhs.m_data.m_value.number_float;     \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned)                  \\\n    {                                                                                                    \\\n        return lhs.m_data.m_value.number_float op static_cast<number_float_t>(rhs.m_data.m_value.number_unsigned);     \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer)                \\\n    {                                                                                                    \\\n        return static_cast<number_integer_t>(lhs.m_data.m_value.number_unsigned) op rhs.m_data.m_value.number_integer; \\\n    }                                                                                                    \\\n    else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned)                \\\n    {                                                                                                    \\\n        return lhs.m_data.m_value.number_integer op static_cast<number_integer_t>(rhs.m_data.m_value.number_unsigned); \\\n    }                                                                                                    \\\n    else if(compares_unordered(lhs, rhs))\\\n    {\\\n        return (unordered_result);\\\n    }\\\n    \\\n    return (default_result);\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    // returns true if:\n    // - any operand is NaN and the other operand is of number type\n    // - any operand is discarded\n    // in legacy mode, discarded values are considered ordered if\n    // an operation is computed as an odd number of inverses of others\n    static bool compares_unordered(const_reference lhs, const_reference rhs, bool inverse = false) noexcept\n    {\n        if ((lhs.is_number_float() && std::isnan(lhs.m_data.m_value.number_float) && rhs.is_number())\n                || (rhs.is_number_float() && std::isnan(rhs.m_data.m_value.number_float) && lhs.is_number()))\n        {\n            return true;\n        }\n#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON\n        return (lhs.is_discarded() || rhs.is_discarded()) && !inverse;\n#else\n        static_cast<void>(inverse);\n        return lhs.is_discarded() || rhs.is_discarded();\n#endif\n    }\n\n  private:\n    bool compares_unordered(const_reference rhs, bool inverse = false) const noexcept\n    {\n        return compares_unordered(*this, rhs, inverse);\n    }\n\n  public:\n#if JSON_HAS_THREE_WAY_COMPARISON\n    /// @brief comparison: equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/\n    bool operator==(const_reference rhs) const noexcept\n    {\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n        const_reference lhs = *this;\n        JSON_IMPLEMENT_OPERATOR( ==, true, false, false)\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif\n    }\n\n    /// @brief comparison: equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/\n    template<typename ScalarType>\n    requires std::is_scalar_v<ScalarType>\n    bool operator==(ScalarType rhs) const noexcept\n    {\n        return *this == basic_json(rhs);\n    }\n\n    /// @brief comparison: not equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/\n    bool operator!=(const_reference rhs) const noexcept\n    {\n        if (compares_unordered(rhs, true))\n        {\n            return false;\n        }\n        return !operator==(rhs);\n    }\n\n    /// @brief comparison: 3-way\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/\n    std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD*\n    {\n        const_reference lhs = *this;\n        // default_result is used if we cannot compare values. In that case,\n        // we compare types.\n        JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD*\n                                std::partial_ordering::equivalent,\n                                std::partial_ordering::unordered,\n                                lhs_type <=> rhs_type) // *NOPAD*\n    }\n\n    /// @brief comparison: 3-way\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/\n    template<typename ScalarType>\n    requires std::is_scalar_v<ScalarType>\n    std::partial_ordering operator<=>(ScalarType rhs) const noexcept // *NOPAD*\n    {\n        return *this <=> basic_json(rhs); // *NOPAD*\n    }\n\n#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON\n    // all operators that are computed as an odd number of inverses of others\n    // need to be overloaded to emulate the legacy comparison behavior\n\n    /// @brief comparison: less than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_le/\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON)\n    bool operator<=(const_reference rhs) const noexcept\n    {\n        if (compares_unordered(rhs, true))\n        {\n            return false;\n        }\n        return !(rhs < *this);\n    }\n\n    /// @brief comparison: less than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_le/\n    template<typename ScalarType>\n    requires std::is_scalar_v<ScalarType>\n    bool operator<=(ScalarType rhs) const noexcept\n    {\n        return *this <= basic_json(rhs);\n    }\n\n    /// @brief comparison: greater than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON)\n    bool operator>=(const_reference rhs) const noexcept\n    {\n        if (compares_unordered(rhs, true))\n        {\n            return false;\n        }\n        return !(*this < rhs);\n    }\n\n    /// @brief comparison: greater than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/\n    template<typename ScalarType>\n    requires std::is_scalar_v<ScalarType>\n    bool operator>=(ScalarType rhs) const noexcept\n    {\n        return *this >= basic_json(rhs);\n    }\n#endif\n#else\n    /// @brief comparison: equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/\n    friend bool operator==(const_reference lhs, const_reference rhs) noexcept\n    {\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wfloat-equal\"\n#endif\n        JSON_IMPLEMENT_OPERATOR( ==, true, false, false)\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif\n    }\n\n    /// @brief comparison: equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator==(const_reference lhs, ScalarType rhs) noexcept\n    {\n        return lhs == basic_json(rhs);\n    }\n\n    /// @brief comparison: equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator==(ScalarType lhs, const_reference rhs) noexcept\n    {\n        return basic_json(lhs) == rhs;\n    }\n\n    /// @brief comparison: not equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/\n    friend bool operator!=(const_reference lhs, const_reference rhs) noexcept\n    {\n        if (compares_unordered(lhs, rhs, true))\n        {\n            return false;\n        }\n        return !(lhs == rhs);\n    }\n\n    /// @brief comparison: not equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator!=(const_reference lhs, ScalarType rhs) noexcept\n    {\n        return lhs != basic_json(rhs);\n    }\n\n    /// @brief comparison: not equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator!=(ScalarType lhs, const_reference rhs) noexcept\n    {\n        return basic_json(lhs) != rhs;\n    }\n\n    /// @brief comparison: less than\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/\n    friend bool operator<(const_reference lhs, const_reference rhs) noexcept\n    {\n        // default_result is used if we cannot compare values. In that case,\n        // we compare types. Note we have to call the operator explicitly,\n        // because MSVC has problems otherwise.\n        JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type))\n    }\n\n    /// @brief comparison: less than\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator<(const_reference lhs, ScalarType rhs) noexcept\n    {\n        return lhs < basic_json(rhs);\n    }\n\n    /// @brief comparison: less than\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator<(ScalarType lhs, const_reference rhs) noexcept\n    {\n        return basic_json(lhs) < rhs;\n    }\n\n    /// @brief comparison: less than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_le/\n    friend bool operator<=(const_reference lhs, const_reference rhs) noexcept\n    {\n        if (compares_unordered(lhs, rhs, true))\n        {\n            return false;\n        }\n        return !(rhs < lhs);\n    }\n\n    /// @brief comparison: less than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_le/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator<=(const_reference lhs, ScalarType rhs) noexcept\n    {\n        return lhs <= basic_json(rhs);\n    }\n\n    /// @brief comparison: less than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_le/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator<=(ScalarType lhs, const_reference rhs) noexcept\n    {\n        return basic_json(lhs) <= rhs;\n    }\n\n    /// @brief comparison: greater than\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/\n    friend bool operator>(const_reference lhs, const_reference rhs) noexcept\n    {\n        // double inverse\n        if (compares_unordered(lhs, rhs))\n        {\n            return false;\n        }\n        return !(lhs <= rhs);\n    }\n\n    /// @brief comparison: greater than\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator>(const_reference lhs, ScalarType rhs) noexcept\n    {\n        return lhs > basic_json(rhs);\n    }\n\n    /// @brief comparison: greater than\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator>(ScalarType lhs, const_reference rhs) noexcept\n    {\n        return basic_json(lhs) > rhs;\n    }\n\n    /// @brief comparison: greater than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/\n    friend bool operator>=(const_reference lhs, const_reference rhs) noexcept\n    {\n        if (compares_unordered(lhs, rhs, true))\n        {\n            return false;\n        }\n        return !(lhs < rhs);\n    }\n\n    /// @brief comparison: greater than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator>=(const_reference lhs, ScalarType rhs) noexcept\n    {\n        return lhs >= basic_json(rhs);\n    }\n\n    /// @brief comparison: greater than or equal\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/\n    template<typename ScalarType, typename std::enable_if<\n                 std::is_scalar<ScalarType>::value, int>::type = 0>\n    friend bool operator>=(ScalarType lhs, const_reference rhs) noexcept\n    {\n        return basic_json(lhs) >= rhs;\n    }\n#endif\n\n#undef JSON_IMPLEMENT_OPERATOR\n\n    /// @}\n\n    ///////////////////\n    // serialization //\n    ///////////////////\n\n    /// @name serialization\n    /// @{\n#ifndef JSON_NO_IO\n    /// @brief serialize to stream\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/\n    friend std::ostream& operator<<(std::ostream& o, const basic_json& j)\n    {\n        // read width member and use it as indentation parameter if nonzero\n        const bool pretty_print = o.width() > 0;\n        const auto indentation = pretty_print ? o.width() : 0;\n\n        // reset width to 0 for subsequent calls to this stream\n        o.width(0);\n\n        // do the actual serialization\n        serializer s(detail::output_adapter<char>(o), o.fill());\n        s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation));\n        return o;\n    }\n\n    /// @brief serialize to stream\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_ltlt/\n    /// @deprecated This function is deprecated since 3.0.0 and will be removed in\n    ///             version 4.0.0 of the library. Please use\n    ///             operator<<(std::ostream&, const basic_json&) instead; that is,\n    ///             replace calls like `j >> o;` with `o << j;`.\n    JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator<<(std::ostream&, const basic_json&))\n    friend std::ostream& operator>>(const basic_json& j, std::ostream& o)\n    {\n        return o << j;\n    }\n#endif  // JSON_NO_IO\n    /// @}\n\n    /////////////////////\n    // deserialization //\n    /////////////////////\n\n    /// @name deserialization\n    /// @{\n\n    /// @brief deserialize from a compatible input\n    /// @sa https://json.nlohmann.me/api/basic_json/parse/\n    template<typename InputType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json parse(InputType&& i,\n                            parser_callback_t cb = nullptr,\n                            const bool allow_exceptions = true,\n                            const bool ignore_comments = false)\n    {\n        basic_json result;\n        parser(detail::input_adapter(std::forward<InputType>(i)), std::move(cb), allow_exceptions, ignore_comments).parse(true, result); // cppcheck-suppress[accessMoved,accessForwarded]\n        return result;\n    }\n\n    /// @brief deserialize from a pair of character iterators\n    /// @sa https://json.nlohmann.me/api/basic_json/parse/\n    template<typename IteratorType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json parse(IteratorType first,\n                            IteratorType last,\n                            parser_callback_t cb = nullptr,\n                            const bool allow_exceptions = true,\n                            const bool ignore_comments = false)\n    {\n        basic_json result;\n        parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments).parse(true, result); // cppcheck-suppress[accessMoved]\n        return result;\n    }\n\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, parse(ptr, ptr + len))\n    static basic_json parse(detail::span_input_adapter&& i,\n                            parser_callback_t cb = nullptr,\n                            const bool allow_exceptions = true,\n                            const bool ignore_comments = false)\n    {\n        basic_json result;\n        parser(i.get(), std::move(cb), allow_exceptions, ignore_comments).parse(true, result); // cppcheck-suppress[accessMoved]\n        return result;\n    }\n\n    /// @brief check if the input is valid JSON\n    /// @sa https://json.nlohmann.me/api/basic_json/accept/\n    template<typename InputType>\n    static bool accept(InputType&& i,\n                       const bool ignore_comments = false)\n    {\n        return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments).accept(true);\n    }\n\n    /// @brief check if the input is valid JSON\n    /// @sa https://json.nlohmann.me/api/basic_json/accept/\n    template<typename IteratorType>\n    static bool accept(IteratorType first, IteratorType last,\n                       const bool ignore_comments = false)\n    {\n        return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments).accept(true);\n    }\n\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, accept(ptr, ptr + len))\n    static bool accept(detail::span_input_adapter&& i,\n                       const bool ignore_comments = false)\n    {\n        return parser(i.get(), nullptr, false, ignore_comments).accept(true);\n    }\n\n    /// @brief generate SAX events\n    /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/\n    template <typename InputType, typename SAX>\n    JSON_HEDLEY_NON_NULL(2)\n    static bool sax_parse(InputType&& i, SAX* sax,\n                          input_format_t format = input_format_t::json,\n                          const bool strict = true,\n                          const bool ignore_comments = false)\n    {\n        auto ia = detail::input_adapter(std::forward<InputType>(i));\n        return format == input_format_t::json\n               ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict)\n               : detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);\n    }\n\n    /// @brief generate SAX events\n    /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/\n    template<class IteratorType, class SAX>\n    JSON_HEDLEY_NON_NULL(3)\n    static bool sax_parse(IteratorType first, IteratorType last, SAX* sax,\n                          input_format_t format = input_format_t::json,\n                          const bool strict = true,\n                          const bool ignore_comments = false)\n    {\n        auto ia = detail::input_adapter(std::move(first), std::move(last));\n        return format == input_format_t::json\n               ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict)\n               : detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);\n    }\n\n    /// @brief generate SAX events\n    /// @sa https://json.nlohmann.me/api/basic_json/sax_parse/\n    /// @deprecated This function is deprecated since 3.8.0 and will be removed in\n    ///             version 4.0.0 of the library. Please use\n    ///             sax_parse(ptr, ptr + len) instead.\n    template <typename SAX>\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, sax_parse(ptr, ptr + len, ...))\n    JSON_HEDLEY_NON_NULL(2)\n    static bool sax_parse(detail::span_input_adapter&& i, SAX* sax,\n                          input_format_t format = input_format_t::json,\n                          const bool strict = true,\n                          const bool ignore_comments = false)\n    {\n        auto ia = i.get();\n        return format == input_format_t::json\n               // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n               ? parser(std::move(ia), nullptr, true, ignore_comments).sax_parse(sax, strict)\n               // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n               : detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);\n    }\n#ifndef JSON_NO_IO\n    /// @brief deserialize from stream\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/\n    /// @deprecated This stream operator is deprecated since 3.0.0 and will be removed in\n    ///             version 4.0.0 of the library. Please use\n    ///             operator>>(std::istream&, basic_json&) instead; that is,\n    ///             replace calls like `j << i;` with `i >> j;`.\n    JSON_HEDLEY_DEPRECATED_FOR(3.0.0, operator>>(std::istream&, basic_json&))\n    friend std::istream& operator<<(basic_json& j, std::istream& i)\n    {\n        return operator>>(i, j);\n    }\n\n    /// @brief deserialize from stream\n    /// @sa https://json.nlohmann.me/api/basic_json/operator_gtgt/\n    friend std::istream& operator>>(std::istream& i, basic_json& j)\n    {\n        parser(detail::input_adapter(i)).parse(false, j);\n        return i;\n    }\n#endif  // JSON_NO_IO\n    /// @}\n\n    ///////////////////////////\n    // convenience functions //\n    ///////////////////////////\n\n    /// @brief return the type as string\n    /// @sa https://json.nlohmann.me/api/basic_json/type_name/\n    JSON_HEDLEY_RETURNS_NON_NULL\n    const char* type_name() const noexcept\n    {\n        switch (m_data.m_type)\n        {\n            case value_t::null:\n                return \"null\";\n            case value_t::object:\n                return \"object\";\n            case value_t::array:\n                return \"array\";\n            case value_t::string:\n                return \"string\";\n            case value_t::boolean:\n                return \"boolean\";\n            case value_t::binary:\n                return \"binary\";\n            case value_t::discarded:\n                return \"discarded\";\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            default:\n                return \"number\";\n        }\n    }\n\n  JSON_PRIVATE_UNLESS_TESTED:\n    //////////////////////\n    // member variables //\n    //////////////////////\n\n    struct data\n    {\n        /// the type of the current element\n        value_t m_type = value_t::null;\n\n        /// the value of the current element\n        json_value m_value = {};\n\n        data(const value_t v)\n            : m_type(v), m_value(v)\n        {\n        }\n\n        data(size_type cnt, const basic_json& val)\n            : m_type(value_t::array)\n        {\n            m_value.array = create<array_t>(cnt, val);\n        }\n\n        data() noexcept = default;\n        data(data&&) noexcept = default;\n        data(const data&) noexcept = delete;\n        data& operator=(data&&) noexcept = delete;\n        data& operator=(const data&) noexcept = delete;\n\n        ~data() noexcept\n        {\n            m_value.destroy(m_type);\n        }\n    };\n\n    data m_data = {};\n\n#if JSON_DIAGNOSTICS\n    /// a pointer to a parent value (for debugging purposes)\n    basic_json* m_parent = nullptr;\n#endif\n\n#if JSON_DIAGNOSTIC_POSITIONS\n    /// the start position of the value\n    std::size_t start_position = std::string::npos;\n    /// the end position of the value\n    std::size_t end_position = std::string::npos;\n  public:\n    constexpr std::size_t start_pos() const noexcept\n    {\n        return start_position;\n    }\n\n    constexpr std::size_t end_pos() const noexcept\n    {\n        return end_position;\n    }\n#endif\n\n    //////////////////////////////////////////\n    // binary serialization/deserialization //\n    //////////////////////////////////////////\n\n    /// @name binary serialization/deserialization support\n    /// @{\n\n  public:\n    /// @brief create a CBOR serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/\n    static std::vector<std::uint8_t> to_cbor(const basic_json& j)\n    {\n        std::vector<std::uint8_t> result;\n        to_cbor(j, result);\n        return result;\n    }\n\n    /// @brief create a CBOR serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/\n    static void to_cbor(const basic_json& j, detail::output_adapter<std::uint8_t> o)\n    {\n        binary_writer<std::uint8_t>(o).write_cbor(j);\n    }\n\n    /// @brief create a CBOR serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_cbor/\n    static void to_cbor(const basic_json& j, detail::output_adapter<char> o)\n    {\n        binary_writer<char>(o).write_cbor(j);\n    }\n\n    /// @brief create a MessagePack serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/\n    static std::vector<std::uint8_t> to_msgpack(const basic_json& j)\n    {\n        std::vector<std::uint8_t> result;\n        to_msgpack(j, result);\n        return result;\n    }\n\n    /// @brief create a MessagePack serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/\n    static void to_msgpack(const basic_json& j, detail::output_adapter<std::uint8_t> o)\n    {\n        binary_writer<std::uint8_t>(o).write_msgpack(j);\n    }\n\n    /// @brief create a MessagePack serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_msgpack/\n    static void to_msgpack(const basic_json& j, detail::output_adapter<char> o)\n    {\n        binary_writer<char>(o).write_msgpack(j);\n    }\n\n    /// @brief create a UBJSON serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/\n    static std::vector<std::uint8_t> to_ubjson(const basic_json& j,\n            const bool use_size = false,\n            const bool use_type = false)\n    {\n        std::vector<std::uint8_t> result;\n        to_ubjson(j, result, use_size, use_type);\n        return result;\n    }\n\n    /// @brief create a UBJSON serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/\n    static void to_ubjson(const basic_json& j, detail::output_adapter<std::uint8_t> o,\n                          const bool use_size = false, const bool use_type = false)\n    {\n        binary_writer<std::uint8_t>(o).write_ubjson(j, use_size, use_type);\n    }\n\n    /// @brief create a UBJSON serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_ubjson/\n    static void to_ubjson(const basic_json& j, detail::output_adapter<char> o,\n                          const bool use_size = false, const bool use_type = false)\n    {\n        binary_writer<char>(o).write_ubjson(j, use_size, use_type);\n    }\n\n    /// @brief create a BJData serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/\n    static std::vector<std::uint8_t> to_bjdata(const basic_json& j,\n            const bool use_size = false,\n            const bool use_type = false,\n            const bjdata_version_t version = bjdata_version_t::draft2)\n    {\n        std::vector<std::uint8_t> result;\n        to_bjdata(j, result, use_size, use_type, version);\n        return result;\n    }\n\n    /// @brief create a BJData serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/\n    static void to_bjdata(const basic_json& j, detail::output_adapter<std::uint8_t> o,\n                          const bool use_size = false, const bool use_type = false,\n                          const bjdata_version_t version = bjdata_version_t::draft2)\n    {\n        binary_writer<std::uint8_t>(o).write_ubjson(j, use_size, use_type, true, true, version);\n    }\n\n    /// @brief create a BJData serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_bjdata/\n    static void to_bjdata(const basic_json& j, detail::output_adapter<char> o,\n                          const bool use_size = false, const bool use_type = false,\n                          const bjdata_version_t version = bjdata_version_t::draft2)\n    {\n        binary_writer<char>(o).write_ubjson(j, use_size, use_type, true, true, version);\n    }\n\n    /// @brief create a BSON serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_bson/\n    static std::vector<std::uint8_t> to_bson(const basic_json& j)\n    {\n        std::vector<std::uint8_t> result;\n        to_bson(j, result);\n        return result;\n    }\n\n    /// @brief create a BSON serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_bson/\n    static void to_bson(const basic_json& j, detail::output_adapter<std::uint8_t> o)\n    {\n        binary_writer<std::uint8_t>(o).write_bson(j);\n    }\n\n    /// @brief create a BSON serialization of a given JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/to_bson/\n    static void to_bson(const basic_json& j, detail::output_adapter<char> o)\n    {\n        binary_writer<char>(o).write_bson(j);\n    }\n\n    /// @brief create a JSON value from an input in CBOR format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/\n    template<typename InputType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_cbor(InputType&& i,\n                                const bool strict = true,\n                                const bool allow_exceptions = true,\n                                const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)\n    {\n        basic_json result;\n        auto ia = detail::input_adapter(std::forward<InputType>(i));\n        detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved]\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in CBOR format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_cbor/\n    template<typename IteratorType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_cbor(IteratorType first, IteratorType last,\n                                const bool strict = true,\n                                const bool allow_exceptions = true,\n                                const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)\n    {\n        basic_json result;\n        auto ia = detail::input_adapter(std::move(first), std::move(last));\n        detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved]\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    template<typename T>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len))\n    static basic_json from_cbor(const T* ptr, std::size_t len,\n                                const bool strict = true,\n                                const bool allow_exceptions = true,\n                                const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)\n    {\n        return from_cbor(ptr, ptr + len, strict, allow_exceptions, tag_handler);\n    }\n\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_cbor(ptr, ptr + len))\n    static basic_json from_cbor(detail::span_input_adapter&& i,\n                                const bool strict = true,\n                                const bool allow_exceptions = true,\n                                const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)\n    {\n        basic_json result;\n        auto ia = i.get();\n        detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);\n        // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved]\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in MessagePack format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/\n    template<typename InputType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_msgpack(InputType&& i,\n                                   const bool strict = true,\n                                   const bool allow_exceptions = true)\n    {\n        basic_json result;\n        auto ia = detail::input_adapter(std::forward<InputType>(i));\n        detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved]\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in MessagePack format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/\n    template<typename IteratorType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_msgpack(IteratorType first, IteratorType last,\n                                   const bool strict = true,\n                                   const bool allow_exceptions = true)\n    {\n        basic_json result;\n        auto ia = detail::input_adapter(std::move(first), std::move(last));\n        detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved]\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    template<typename T>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len))\n    static basic_json from_msgpack(const T* ptr, std::size_t len,\n                                   const bool strict = true,\n                                   const bool allow_exceptions = true)\n    {\n        return from_msgpack(ptr, ptr + len, strict, allow_exceptions);\n    }\n\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_msgpack(ptr, ptr + len))\n    static basic_json from_msgpack(detail::span_input_adapter&& i,\n                                   const bool strict = true,\n                                   const bool allow_exceptions = true)\n    {\n        basic_json result;\n        auto ia = i.get();\n        detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);\n        // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved]\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in UBJSON format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/\n    template<typename InputType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_ubjson(InputType&& i,\n                                  const bool strict = true,\n                                  const bool allow_exceptions = true)\n    {\n        basic_json result;\n        auto ia = detail::input_adapter(std::forward<InputType>(i));\n        detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved]\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in UBJSON format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/\n    template<typename IteratorType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_ubjson(IteratorType first, IteratorType last,\n                                  const bool strict = true,\n                                  const bool allow_exceptions = true)\n    {\n        basic_json result;\n        auto ia = detail::input_adapter(std::move(first), std::move(last));\n        detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved]\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    template<typename T>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len))\n    static basic_json from_ubjson(const T* ptr, std::size_t len,\n                                  const bool strict = true,\n                                  const bool allow_exceptions = true)\n    {\n        return from_ubjson(ptr, ptr + len, strict, allow_exceptions);\n    }\n\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_ubjson(ptr, ptr + len))\n    static basic_json from_ubjson(detail::span_input_adapter&& i,\n                                  const bool strict = true,\n                                  const bool allow_exceptions = true)\n    {\n        basic_json result;\n        auto ia = i.get();\n        detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);\n        // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved]\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in BJData format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/\n    template<typename InputType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_bjdata(InputType&& i,\n                                  const bool strict = true,\n                                  const bool allow_exceptions = true)\n    {\n        basic_json result;\n        auto ia = detail::input_adapter(std::forward<InputType>(i));\n        detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved]\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in BJData format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/\n    template<typename IteratorType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_bjdata(IteratorType first, IteratorType last,\n                                  const bool strict = true,\n                                  const bool allow_exceptions = true)\n    {\n        basic_json result;\n        auto ia = detail::input_adapter(std::move(first), std::move(last));\n        detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved]\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in BSON format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_bson/\n    template<typename InputType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_bson(InputType&& i,\n                                const bool strict = true,\n                                const bool allow_exceptions = true)\n    {\n        basic_json result;\n        auto ia = detail::input_adapter(std::forward<InputType>(i));\n        detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved]\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    /// @brief create a JSON value from an input in BSON format\n    /// @sa https://json.nlohmann.me/api/basic_json/from_bson/\n    template<typename IteratorType>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json from_bson(IteratorType first, IteratorType last,\n                                const bool strict = true,\n                                const bool allow_exceptions = true)\n    {\n        basic_json result;\n        auto ia = detail::input_adapter(std::move(first), std::move(last));\n        detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved]\n        return res ? result : basic_json(value_t::discarded);\n    }\n\n    template<typename T>\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len))\n    static basic_json from_bson(const T* ptr, std::size_t len,\n                                const bool strict = true,\n                                const bool allow_exceptions = true)\n    {\n        return from_bson(ptr, ptr + len, strict, allow_exceptions);\n    }\n\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    JSON_HEDLEY_DEPRECATED_FOR(3.8.0, from_bson(ptr, ptr + len))\n    static basic_json from_bson(detail::span_input_adapter&& i,\n                                const bool strict = true,\n                                const bool allow_exceptions = true)\n    {\n        basic_json result;\n        auto ia = i.get();\n        detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);\n        // NOLINTNEXTLINE(hicpp-move-const-arg,performance-move-const-arg)\n        const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved]\n        return res ? result : basic_json(value_t::discarded);\n    }\n    /// @}\n\n    //////////////////////////\n    // JSON Pointer support //\n    //////////////////////////\n\n    /// @name JSON Pointer functions\n    /// @{\n\n    /// @brief access specified element via JSON Pointer\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    reference operator[](const json_pointer& ptr)\n    {\n        return ptr.get_unchecked(this);\n    }\n\n    template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n    reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr)\n    {\n        return ptr.get_unchecked(this);\n    }\n\n    /// @brief access specified element via JSON Pointer\n    /// @sa https://json.nlohmann.me/api/basic_json/operator%5B%5D/\n    const_reference operator[](const json_pointer& ptr) const\n    {\n        return ptr.get_unchecked(this);\n    }\n\n    template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n    const_reference operator[](const ::nlohmann::json_pointer<BasicJsonType>& ptr) const\n    {\n        return ptr.get_unchecked(this);\n    }\n\n    /// @brief access specified element via JSON Pointer\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    reference at(const json_pointer& ptr)\n    {\n        return ptr.get_checked(this);\n    }\n\n    template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n    reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr)\n    {\n        return ptr.get_checked(this);\n    }\n\n    /// @brief access specified element via JSON Pointer\n    /// @sa https://json.nlohmann.me/api/basic_json/at/\n    const_reference at(const json_pointer& ptr) const\n    {\n        return ptr.get_checked(this);\n    }\n\n    template<typename BasicJsonType, detail::enable_if_t<detail::is_basic_json<BasicJsonType>::value, int> = 0>\n    JSON_HEDLEY_DEPRECATED_FOR(3.11.0, basic_json::json_pointer or nlohmann::json_pointer<basic_json::string_t>) // NOLINT(readability/alt_tokens)\n    const_reference at(const ::nlohmann::json_pointer<BasicJsonType>& ptr) const\n    {\n        return ptr.get_checked(this);\n    }\n\n    /// @brief return flattened JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/flatten/\n    basic_json flatten() const\n    {\n        basic_json result(value_t::object);\n        json_pointer::flatten(\"\", *this, result);\n        return result;\n    }\n\n    /// @brief unflatten a previously flattened JSON value\n    /// @sa https://json.nlohmann.me/api/basic_json/unflatten/\n    basic_json unflatten() const\n    {\n        return json_pointer::unflatten(*this);\n    }\n\n    /// @}\n\n    //////////////////////////\n    // JSON Patch functions //\n    //////////////////////////\n\n    /// @name JSON Patch functions\n    /// @{\n\n    /// @brief applies a JSON patch in-place without copying the object\n    /// @sa https://json.nlohmann.me/api/basic_json/patch/\n    void patch_inplace(const basic_json& json_patch)\n    {\n        basic_json& result = *this;\n        // the valid JSON Patch operations\n        enum class patch_operations {add, remove, replace, move, copy, test, invalid};\n\n        const auto get_op = [](const string_t& op)\n        {\n            if (op == \"add\")\n            {\n                return patch_operations::add;\n            }\n            if (op == \"remove\")\n            {\n                return patch_operations::remove;\n            }\n            if (op == \"replace\")\n            {\n                return patch_operations::replace;\n            }\n            if (op == \"move\")\n            {\n                return patch_operations::move;\n            }\n            if (op == \"copy\")\n            {\n                return patch_operations::copy;\n            }\n            if (op == \"test\")\n            {\n                return patch_operations::test;\n            }\n\n            return patch_operations::invalid;\n        };\n\n        // wrapper for \"add\" operation; add value at ptr\n        const auto operation_add = [&result](json_pointer & ptr, const basic_json & val)\n        {\n            // adding to the root of the target document means replacing it\n            if (ptr.empty())\n            {\n                result = val;\n                return;\n            }\n\n            // make sure the top element of the pointer exists\n            json_pointer const top_pointer = ptr.top();\n            if (top_pointer != ptr)\n            {\n                result.at(top_pointer);\n            }\n\n            // get reference to parent of JSON pointer ptr\n            const auto last_path = ptr.back();\n            ptr.pop_back();\n            // parent must exist when performing patch add per RFC6902 specs\n            basic_json& parent = result.at(ptr);\n\n            switch (parent.m_data.m_type)\n            {\n                case value_t::null:\n                case value_t::object:\n                {\n                    // use operator[] to add value\n                    parent[last_path] = val;\n                    break;\n                }\n\n                case value_t::array:\n                {\n                    if (last_path == \"-\")\n                    {\n                        // special case: append to back\n                        parent.push_back(val);\n                    }\n                    else\n                    {\n                        const auto idx = json_pointer::template array_index<basic_json_t>(last_path);\n                        if (JSON_HEDLEY_UNLIKELY(idx > parent.size()))\n                        {\n                            // avoid undefined behavior\n                            JSON_THROW(out_of_range::create(401, detail::concat(\"array index \", std::to_string(idx), \" is out of range\"), &parent));\n                        }\n\n                        // default case: insert add offset\n                        parent.insert(parent.begin() + static_cast<difference_type>(idx), val);\n                    }\n                    break;\n                }\n\n                // if there exists a parent it cannot be primitive\n                case value_t::string: // LCOV_EXCL_LINE\n                case value_t::boolean: // LCOV_EXCL_LINE\n                case value_t::number_integer: // LCOV_EXCL_LINE\n                case value_t::number_unsigned: // LCOV_EXCL_LINE\n                case value_t::number_float: // LCOV_EXCL_LINE\n                case value_t::binary: // LCOV_EXCL_LINE\n                case value_t::discarded: // LCOV_EXCL_LINE\n                default:            // LCOV_EXCL_LINE\n                    JSON_ASSERT(false); // NOLINT(cert-dcl03-c,hicpp-static-assert,misc-static-assert) LCOV_EXCL_LINE\n            }\n        };\n\n        // wrapper for \"remove\" operation; remove value at ptr\n        const auto operation_remove = [this, & result](json_pointer & ptr)\n        {\n            // get reference to parent of JSON pointer ptr\n            const auto last_path = ptr.back();\n            ptr.pop_back();\n            basic_json& parent = result.at(ptr);\n\n            // remove child\n            if (parent.is_object())\n            {\n                // perform range check\n                auto it = parent.find(last_path);\n                if (JSON_HEDLEY_LIKELY(it != parent.end()))\n                {\n                    parent.erase(it);\n                }\n                else\n                {\n                    JSON_THROW(out_of_range::create(403, detail::concat(\"key '\", last_path, \"' not found\"), this));\n                }\n            }\n            else if (parent.is_array())\n            {\n                // note erase performs range check\n                parent.erase(json_pointer::template array_index<basic_json_t>(last_path));\n            }\n        };\n\n        // type check: top level value must be an array\n        if (JSON_HEDLEY_UNLIKELY(!json_patch.is_array()))\n        {\n            JSON_THROW(parse_error::create(104, 0, \"JSON patch must be an array of objects\", &json_patch));\n        }\n\n        // iterate and apply the operations\n        for (const auto& val : json_patch)\n        {\n            // wrapper to get a value for an operation\n            const auto get_value = [&val](const string_t& op,\n                                          const string_t& member,\n                                          bool string_type) -> basic_json &\n            {\n                // find value\n                auto it = val.m_data.m_value.object->find(member);\n\n                // context-sensitive error message\n                const auto error_msg = (op == \"op\") ? \"operation\" : detail::concat(\"operation '\", op, '\\''); // NOLINT(bugprone-unused-local-non-trivial-variable)\n\n                // check if desired value is present\n                if (JSON_HEDLEY_UNLIKELY(it == val.m_data.m_value.object->end()))\n                {\n                    // NOLINTNEXTLINE(performance-inefficient-string-concatenation)\n                    JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, \" must have member '\", member, \"'\"), &val));\n                }\n\n                // check if result is of type string\n                if (JSON_HEDLEY_UNLIKELY(string_type && !it->second.is_string()))\n                {\n                    // NOLINTNEXTLINE(performance-inefficient-string-concatenation)\n                    JSON_THROW(parse_error::create(105, 0, detail::concat(error_msg, \" must have string member '\", member, \"'\"), &val));\n                }\n\n                // no error: return value\n                return it->second;\n            };\n\n            // type check: every element of the array must be an object\n            if (JSON_HEDLEY_UNLIKELY(!val.is_object()))\n            {\n                JSON_THROW(parse_error::create(104, 0, \"JSON patch must be an array of objects\", &val));\n            }\n\n            // collect mandatory members\n            const auto op = get_value(\"op\", \"op\", true).template get<string_t>();\n            const auto path = get_value(op, \"path\", true).template get<string_t>();\n            json_pointer ptr(path);\n\n            switch (get_op(op))\n            {\n                case patch_operations::add:\n                {\n                    operation_add(ptr, get_value(\"add\", \"value\", false));\n                    break;\n                }\n\n                case patch_operations::remove:\n                {\n                    operation_remove(ptr);\n                    break;\n                }\n\n                case patch_operations::replace:\n                {\n                    // the \"path\" location must exist - use at()\n                    result.at(ptr) = get_value(\"replace\", \"value\", false);\n                    break;\n                }\n\n                case patch_operations::move:\n                {\n                    const auto from_path = get_value(\"move\", \"from\", true).template get<string_t>();\n                    json_pointer from_ptr(from_path);\n\n                    // the \"from\" location must exist - use at()\n                    basic_json const v = result.at(from_ptr);\n\n                    // The move operation is functionally identical to a\n                    // \"remove\" operation on the \"from\" location, followed\n                    // immediately by an \"add\" operation at the target\n                    // location with the value that was just removed.\n                    operation_remove(from_ptr);\n                    operation_add(ptr, v);\n                    break;\n                }\n\n                case patch_operations::copy:\n                {\n                    const auto from_path = get_value(\"copy\", \"from\", true).template get<string_t>();\n                    const json_pointer from_ptr(from_path);\n\n                    // the \"from\" location must exist - use at()\n                    basic_json const v = result.at(from_ptr);\n\n                    // The copy is functionally identical to an \"add\"\n                    // operation at the target location using the value\n                    // specified in the \"from\" member.\n                    operation_add(ptr, v);\n                    break;\n                }\n\n                case patch_operations::test:\n                {\n                    bool success = false;\n                    JSON_TRY\n                    {\n                        // check if \"value\" matches the one at \"path\"\n                        // the \"path\" location must exist - use at()\n                        success = (result.at(ptr) == get_value(\"test\", \"value\", false));\n                    }\n                    JSON_INTERNAL_CATCH (out_of_range&)\n                    {\n                        // ignore out of range errors: success remains false\n                    }\n\n                    // throw an exception if test fails\n                    if (JSON_HEDLEY_UNLIKELY(!success))\n                    {\n                        JSON_THROW(other_error::create(501, detail::concat(\"unsuccessful: \", val.dump()), &val));\n                    }\n\n                    break;\n                }\n\n                case patch_operations::invalid:\n                default:\n                {\n                    // op must be \"add\", \"remove\", \"replace\", \"move\", \"copy\", or\n                    // \"test\"\n                    JSON_THROW(parse_error::create(105, 0, detail::concat(\"operation value '\", op, \"' is invalid\"), &val));\n                }\n            }\n        }\n    }\n\n    /// @brief applies a JSON patch to a copy of the current object\n    /// @sa https://json.nlohmann.me/api/basic_json/patch/\n    basic_json patch(const basic_json& json_patch) const\n    {\n        basic_json result = *this;\n        result.patch_inplace(json_patch);\n        return result;\n    }\n\n    /// @brief creates a diff as a JSON patch\n    /// @sa https://json.nlohmann.me/api/basic_json/diff/\n    JSON_HEDLEY_WARN_UNUSED_RESULT\n    static basic_json diff(const basic_json& source, const basic_json& target,\n                           const string_t& path = \"\")\n    {\n        // the patch\n        basic_json result(value_t::array);\n\n        // if the values are the same, return empty patch\n        if (source == target)\n        {\n            return result;\n        }\n\n        if (source.type() != target.type())\n        {\n            // different types: replace value\n            result.push_back(\n            {\n                {\"op\", \"replace\"}, {\"path\", path}, {\"value\", target}\n            });\n            return result;\n        }\n\n        switch (source.type())\n        {\n            case value_t::array:\n            {\n                // first pass: traverse common elements\n                std::size_t i = 0;\n                while (i < source.size() && i < target.size())\n                {\n                    // recursive call to compare array values at index i\n                    auto temp_diff = diff(source[i], target[i], detail::concat<string_t>(path, '/', detail::to_string<string_t>(i)));\n                    result.insert(result.end(), temp_diff.begin(), temp_diff.end());\n                    ++i;\n                }\n\n                // We now reached the end of at least one array\n                // in a second pass, traverse the remaining elements\n\n                // remove my remaining elements\n                const auto end_index = static_cast<difference_type>(result.size());\n                while (i < source.size())\n                {\n                    // add operations in reverse order to avoid invalid\n                    // indices\n                    result.insert(result.begin() + end_index, object(\n                    {\n                        {\"op\", \"remove\"},\n                        {\"path\", detail::concat<string_t>(path, '/', detail::to_string<string_t>(i))}\n                    }));\n                    ++i;\n                }\n\n                // add other remaining elements\n                while (i < target.size())\n                {\n                    result.push_back(\n                    {\n                        {\"op\", \"add\"},\n                        {\"path\", detail::concat<string_t>(path, \"/-\")},\n                        {\"value\", target[i]}\n                    });\n                    ++i;\n                }\n\n                break;\n            }\n\n            case value_t::object:\n            {\n                // first pass: traverse this object's elements\n                for (auto it = source.cbegin(); it != source.cend(); ++it)\n                {\n                    // escape the key name to be used in a JSON patch\n                    const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));\n\n                    if (target.find(it.key()) != target.end())\n                    {\n                        // recursive call to compare object values at key it\n                        auto temp_diff = diff(it.value(), target[it.key()], path_key);\n                        result.insert(result.end(), temp_diff.begin(), temp_diff.end());\n                    }\n                    else\n                    {\n                        // found a key that is not in o -> remove it\n                        result.push_back(object(\n                        {\n                            {\"op\", \"remove\"}, {\"path\", path_key}\n                        }));\n                    }\n                }\n\n                // second pass: traverse other object's elements\n                for (auto it = target.cbegin(); it != target.cend(); ++it)\n                {\n                    if (source.find(it.key()) == source.end())\n                    {\n                        // found a key that is not in this -> add it\n                        const auto path_key = detail::concat<string_t>(path, '/', detail::escape(it.key()));\n                        result.push_back(\n                        {\n                            {\"op\", \"add\"}, {\"path\", path_key},\n                            {\"value\", it.value()}\n                        });\n                    }\n                }\n\n                break;\n            }\n\n            case value_t::null:\n            case value_t::string:\n            case value_t::boolean:\n            case value_t::number_integer:\n            case value_t::number_unsigned:\n            case value_t::number_float:\n            case value_t::binary:\n            case value_t::discarded:\n            default:\n            {\n                // both primitive type: replace value\n                result.push_back(\n                {\n                    {\"op\", \"replace\"}, {\"path\", path}, {\"value\", target}\n                });\n                break;\n            }\n        }\n\n        return result;\n    }\n    /// @}\n\n    ////////////////////////////////\n    // JSON Merge Patch functions //\n    ////////////////////////////////\n\n    /// @name JSON Merge Patch functions\n    /// @{\n\n    /// @brief applies a JSON Merge Patch\n    /// @sa https://json.nlohmann.me/api/basic_json/merge_patch/\n    void merge_patch(const basic_json& apply_patch)\n    {\n        if (apply_patch.is_object())\n        {\n            if (!is_object())\n            {\n                *this = object();\n            }\n            for (auto it = apply_patch.begin(); it != apply_patch.end(); ++it)\n            {\n                if (it.value().is_null())\n                {\n                    erase(it.key());\n                }\n                else\n                {\n                    operator[](it.key()).merge_patch(it.value());\n                }\n            }\n        }\n        else\n        {\n            *this = apply_patch;\n        }\n    }\n\n    /// @}\n};\n\n/// @brief user-defined to_string function for JSON values\n/// @sa https://json.nlohmann.me/api/basic_json/to_string/\nNLOHMANN_BASIC_JSON_TPL_DECLARATION\nstd::string to_string(const NLOHMANN_BASIC_JSON_TPL& j)\n{\n    return j.dump();\n}\n\ninline namespace literals\n{\ninline namespace json_literals\n{\n\n/// @brief user-defined string literal for JSON values\n/// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json/\nJSON_HEDLEY_NON_NULL(1)\n#if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0)\n    inline nlohmann::json operator \"\"_json(const char* s, std::size_t n)\n#else\n    inline nlohmann::json operator \"\" _json(const char* s, std::size_t n)\n#endif\n{\n    return nlohmann::json::parse(s, s + n);\n}\n\n/// @brief user-defined string literal for JSON pointer\n/// @sa https://json.nlohmann.me/api/basic_json/operator_literal_json_pointer/\nJSON_HEDLEY_NON_NULL(1)\n#if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0)\n    inline nlohmann::json::json_pointer operator \"\"_json_pointer(const char* s, std::size_t n)\n#else\n    inline nlohmann::json::json_pointer operator \"\" _json_pointer(const char* s, std::size_t n)\n#endif\n{\n    return nlohmann::json::json_pointer(std::string(s, n));\n}\n\n}  // namespace json_literals\n}  // namespace literals\nNLOHMANN_JSON_NAMESPACE_END\n\n///////////////////////\n// nonmember support //\n///////////////////////\n\nnamespace std // NOLINT(cert-dcl58-cpp)\n{\n\n/// @brief hash value for JSON objects\n/// @sa https://json.nlohmann.me/api/basic_json/std_hash/\nNLOHMANN_BASIC_JSON_TPL_DECLARATION\nstruct hash<nlohmann::NLOHMANN_BASIC_JSON_TPL> // NOLINT(cert-dcl58-cpp)\n{\n    std::size_t operator()(const nlohmann::NLOHMANN_BASIC_JSON_TPL& j) const\n    {\n        return nlohmann::detail::hash(j);\n    }\n};\n\n// specialization for std::less<value_t>\ntemplate<>\nstruct less< ::nlohmann::detail::value_t> // do not remove the space after '<', see https://github.com/nlohmann/json/pull/679\n{\n    /*!\n    @brief compare two value_t enum values\n    @since version 3.0.0\n    */\n    bool operator()(::nlohmann::detail::value_t lhs,\n                    ::nlohmann::detail::value_t rhs) const noexcept\n    {\n#if JSON_HAS_THREE_WAY_COMPARISON\n        return std::is_lt(lhs <=> rhs); // *NOPAD*\n#else\n        return ::nlohmann::detail::operator<(lhs, rhs);\n#endif\n    }\n};\n\n// C++20 prohibit function specialization in the std namespace.\n#ifndef JSON_HAS_CPP_20\n\n/// @brief exchanges the values of two JSON objects\n/// @sa https://json.nlohmann.me/api/basic_json/std_swap/\nNLOHMANN_BASIC_JSON_TPL_DECLARATION\ninline void swap(nlohmann::NLOHMANN_BASIC_JSON_TPL& j1, nlohmann::NLOHMANN_BASIC_JSON_TPL& j2) noexcept(  // NOLINT(readability-inconsistent-declaration-parameter-name, cert-dcl58-cpp)\n    is_nothrow_move_constructible<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value&&                          // NOLINT(misc-redundant-expression,cppcoreguidelines-noexcept-swap,performance-noexcept-swap)\n    is_nothrow_move_assignable<nlohmann::NLOHMANN_BASIC_JSON_TPL>::value)\n{\n    j1.swap(j2);\n}\n\n#endif\n\n}  // namespace std\n\n#if JSON_USE_GLOBAL_UDLS\n    #if !defined(JSON_HEDLEY_GCC_VERSION) || JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0)\n        using nlohmann::literals::json_literals::operator \"\"_json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers)\n        using nlohmann::literals::json_literals::operator \"\"_json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers)\n    #else\n        using nlohmann::literals::json_literals::operator \"\" _json; // NOLINT(misc-unused-using-decls,google-global-names-in-headers)\n        using nlohmann::literals::json_literals::operator \"\" _json_pointer; //NOLINT(misc-unused-using-decls,google-global-names-in-headers)\n    #endif\n#endif\n\n// #include <nlohmann/detail/macro_unscope.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n// restore clang diagnostic settings\n#if defined(__clang__)\n    #pragma clang diagnostic pop\n#endif\n\n// clean up\n#undef JSON_ASSERT\n#undef JSON_INTERNAL_CATCH\n#undef JSON_THROW\n#undef JSON_PRIVATE_UNLESS_TESTED\n#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION\n#undef NLOHMANN_BASIC_JSON_TPL\n#undef JSON_EXPLICIT\n#undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL\n#undef JSON_INLINE_VARIABLE\n#undef JSON_NO_UNIQUE_ADDRESS\n#undef JSON_DISABLE_ENUM_SERIALIZATION\n#undef JSON_USE_GLOBAL_UDLS\n\n#ifndef JSON_TEST_KEEP_MACROS\n    #undef JSON_CATCH\n    #undef JSON_TRY\n    #undef JSON_HAS_CPP_11\n    #undef JSON_HAS_CPP_14\n    #undef JSON_HAS_CPP_17\n    #undef JSON_HAS_CPP_20\n    #undef JSON_HAS_CPP_23\n    #undef JSON_HAS_FILESYSTEM\n    #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM\n    #undef JSON_HAS_THREE_WAY_COMPARISON\n    #undef JSON_HAS_RANGES\n    #undef JSON_HAS_STATIC_RTTI\n    #undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON\n#endif\n\n// #include <nlohmann/thirdparty/hedley/hedley_undef.hpp>\n//     __ _____ _____ _____\n//  __|  |   __|     |   | |  JSON for Modern C++\n// |  |  |__   |  |  | | | |  version 3.12.0\n// |_____|_____|_____|_|___|  https://github.com/nlohmann/json\n//\n// SPDX-FileCopyrightText: 2013 - 2025 Niels Lohmann <https://nlohmann.me>\n// SPDX-License-Identifier: MIT\n\n\n\n#undef JSON_HEDLEY_ALWAYS_INLINE\n#undef JSON_HEDLEY_ARM_VERSION\n#undef JSON_HEDLEY_ARM_VERSION_CHECK\n#undef JSON_HEDLEY_ARRAY_PARAM\n#undef JSON_HEDLEY_ASSUME\n#undef JSON_HEDLEY_BEGIN_C_DECLS\n#undef JSON_HEDLEY_CLANG_HAS_ATTRIBUTE\n#undef JSON_HEDLEY_CLANG_HAS_BUILTIN\n#undef JSON_HEDLEY_CLANG_HAS_CPP_ATTRIBUTE\n#undef JSON_HEDLEY_CLANG_HAS_DECLSPEC_DECLSPEC_ATTRIBUTE\n#undef JSON_HEDLEY_CLANG_HAS_EXTENSION\n#undef JSON_HEDLEY_CLANG_HAS_FEATURE\n#undef JSON_HEDLEY_CLANG_HAS_WARNING\n#undef JSON_HEDLEY_COMPCERT_VERSION\n#undef JSON_HEDLEY_COMPCERT_VERSION_CHECK\n#undef JSON_HEDLEY_CONCAT\n#undef JSON_HEDLEY_CONCAT3\n#undef JSON_HEDLEY_CONCAT3_EX\n#undef JSON_HEDLEY_CONCAT_EX\n#undef JSON_HEDLEY_CONST\n#undef JSON_HEDLEY_CONSTEXPR\n#undef JSON_HEDLEY_CONST_CAST\n#undef JSON_HEDLEY_CPP_CAST\n#undef JSON_HEDLEY_CRAY_VERSION\n#undef JSON_HEDLEY_CRAY_VERSION_CHECK\n#undef JSON_HEDLEY_C_DECL\n#undef JSON_HEDLEY_DEPRECATED\n#undef JSON_HEDLEY_DEPRECATED_FOR\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS\n#undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION\n#undef JSON_HEDLEY_DIAGNOSTIC_POP\n#undef JSON_HEDLEY_DIAGNOSTIC_PUSH\n#undef JSON_HEDLEY_DMC_VERSION\n#undef JSON_HEDLEY_DMC_VERSION_CHECK\n#undef JSON_HEDLEY_EMPTY_BASES\n#undef JSON_HEDLEY_EMSCRIPTEN_VERSION\n#undef JSON_HEDLEY_EMSCRIPTEN_VERSION_CHECK\n#undef JSON_HEDLEY_END_C_DECLS\n#undef JSON_HEDLEY_FLAGS\n#undef JSON_HEDLEY_FLAGS_CAST\n#undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE\n#undef JSON_HEDLEY_GCC_HAS_BUILTIN\n#undef JSON_HEDLEY_GCC_HAS_CPP_ATTRIBUTE\n#undef JSON_HEDLEY_GCC_HAS_DECLSPEC_ATTRIBUTE\n#undef JSON_HEDLEY_GCC_HAS_EXTENSION\n#undef JSON_HEDLEY_GCC_HAS_FEATURE\n#undef JSON_HEDLEY_GCC_HAS_WARNING\n#undef JSON_HEDLEY_GCC_NOT_CLANG_VERSION_CHECK\n#undef JSON_HEDLEY_GCC_VERSION\n#undef JSON_HEDLEY_GCC_VERSION_CHECK\n#undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE\n#undef JSON_HEDLEY_GNUC_HAS_BUILTIN\n#undef JSON_HEDLEY_GNUC_HAS_CPP_ATTRIBUTE\n#undef JSON_HEDLEY_GNUC_HAS_DECLSPEC_ATTRIBUTE\n#undef JSON_HEDLEY_GNUC_HAS_EXTENSION\n#undef JSON_HEDLEY_GNUC_HAS_FEATURE\n#undef JSON_HEDLEY_GNUC_HAS_WARNING\n#undef JSON_HEDLEY_GNUC_VERSION\n#undef JSON_HEDLEY_GNUC_VERSION_CHECK\n#undef JSON_HEDLEY_HAS_ATTRIBUTE\n#undef JSON_HEDLEY_HAS_BUILTIN\n#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE\n#undef JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS\n#undef JSON_HEDLEY_HAS_DECLSPEC_ATTRIBUTE\n#undef JSON_HEDLEY_HAS_EXTENSION\n#undef JSON_HEDLEY_HAS_FEATURE\n#undef JSON_HEDLEY_HAS_WARNING\n#undef JSON_HEDLEY_IAR_VERSION\n#undef JSON_HEDLEY_IAR_VERSION_CHECK\n#undef JSON_HEDLEY_IBM_VERSION\n#undef JSON_HEDLEY_IBM_VERSION_CHECK\n#undef JSON_HEDLEY_IMPORT\n#undef JSON_HEDLEY_INLINE\n#undef JSON_HEDLEY_INTEL_CL_VERSION\n#undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK\n#undef JSON_HEDLEY_INTEL_VERSION\n#undef JSON_HEDLEY_INTEL_VERSION_CHECK\n#undef JSON_HEDLEY_IS_CONSTANT\n#undef JSON_HEDLEY_IS_CONSTEXPR_\n#undef JSON_HEDLEY_LIKELY\n#undef JSON_HEDLEY_MALLOC\n#undef JSON_HEDLEY_MCST_LCC_VERSION\n#undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK\n#undef JSON_HEDLEY_MESSAGE\n#undef JSON_HEDLEY_MSVC_VERSION\n#undef JSON_HEDLEY_MSVC_VERSION_CHECK\n#undef JSON_HEDLEY_NEVER_INLINE\n#undef JSON_HEDLEY_NON_NULL\n#undef JSON_HEDLEY_NO_ESCAPE\n#undef JSON_HEDLEY_NO_RETURN\n#undef JSON_HEDLEY_NO_THROW\n#undef JSON_HEDLEY_NULL\n#undef JSON_HEDLEY_PELLES_VERSION\n#undef JSON_HEDLEY_PELLES_VERSION_CHECK\n#undef JSON_HEDLEY_PGI_VERSION\n#undef JSON_HEDLEY_PGI_VERSION_CHECK\n#undef JSON_HEDLEY_PREDICT\n#undef JSON_HEDLEY_PRINTF_FORMAT\n#undef JSON_HEDLEY_PRIVATE\n#undef JSON_HEDLEY_PUBLIC\n#undef JSON_HEDLEY_PURE\n#undef JSON_HEDLEY_REINTERPRET_CAST\n#undef JSON_HEDLEY_REQUIRE\n#undef JSON_HEDLEY_REQUIRE_CONSTEXPR\n#undef JSON_HEDLEY_REQUIRE_MSG\n#undef JSON_HEDLEY_RESTRICT\n#undef JSON_HEDLEY_RETURNS_NON_NULL\n#undef JSON_HEDLEY_SENTINEL\n#undef JSON_HEDLEY_STATIC_ASSERT\n#undef JSON_HEDLEY_STATIC_CAST\n#undef JSON_HEDLEY_STRINGIFY\n#undef JSON_HEDLEY_STRINGIFY_EX\n#undef JSON_HEDLEY_SUNPRO_VERSION\n#undef JSON_HEDLEY_SUNPRO_VERSION_CHECK\n#undef JSON_HEDLEY_TINYC_VERSION\n#undef JSON_HEDLEY_TINYC_VERSION_CHECK\n#undef JSON_HEDLEY_TI_ARMCL_VERSION\n#undef JSON_HEDLEY_TI_ARMCL_VERSION_CHECK\n#undef JSON_HEDLEY_TI_CL2000_VERSION\n#undef JSON_HEDLEY_TI_CL2000_VERSION_CHECK\n#undef JSON_HEDLEY_TI_CL430_VERSION\n#undef JSON_HEDLEY_TI_CL430_VERSION_CHECK\n#undef JSON_HEDLEY_TI_CL6X_VERSION\n#undef JSON_HEDLEY_TI_CL6X_VERSION_CHECK\n#undef JSON_HEDLEY_TI_CL7X_VERSION\n#undef JSON_HEDLEY_TI_CL7X_VERSION_CHECK\n#undef JSON_HEDLEY_TI_CLPRU_VERSION\n#undef JSON_HEDLEY_TI_CLPRU_VERSION_CHECK\n#undef JSON_HEDLEY_TI_VERSION\n#undef JSON_HEDLEY_TI_VERSION_CHECK\n#undef JSON_HEDLEY_UNAVAILABLE\n#undef JSON_HEDLEY_UNLIKELY\n#undef JSON_HEDLEY_UNPREDICTABLE\n#undef JSON_HEDLEY_UNREACHABLE\n#undef JSON_HEDLEY_UNREACHABLE_RETURN\n#undef JSON_HEDLEY_VERSION\n#undef JSON_HEDLEY_VERSION_DECODE_MAJOR\n#undef JSON_HEDLEY_VERSION_DECODE_MINOR\n#undef JSON_HEDLEY_VERSION_DECODE_REVISION\n#undef JSON_HEDLEY_VERSION_ENCODE\n#undef JSON_HEDLEY_WARNING\n#undef JSON_HEDLEY_WARN_UNUSED_RESULT\n#undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG\n#undef JSON_HEDLEY_FALL_THROUGH\n\n\n\n#endif  // INCLUDE_NLOHMANN_JSON_HPP_\n"
  },
  {
    "path": "dependencies/nlohmann_json/version.txt",
    "content": "JSON for Modern C++\nversion 3.12.0\nhttps://github.com/nlohmann/json\n"
  },
  {
    "path": "dependencies/tl_expected/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2024 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n\nadd_library(tl_expected INTERFACE)\n\n# Relative sources are allowed only since cmake 3.13.\ntarget_sources(tl_expected INTERFACE\n\t${CMAKE_CURRENT_SOURCE_DIR}/expected/include/tl/expected.hpp\n)\n\ntarget_include_directories(tl_expected\n\tSYSTEM INTERFACE\n\t\t\"${CMAKE_SOURCE_DIR}/dependencies/tl_expected/expected/include\"\n)\n\n\n"
  },
  {
    "path": "dependencies/tl_expected/expected/.appveyor.yml",
    "content": "os:\n- Visual Studio 2015\n- Visual Studio 2017\n- Visual Studio 2019\n- Visual Studio 2022\n\nbuild_script:\n  - cmake -Bbuild -S.\n  - cmake --build build\n  - cmake --build build --target RUN_TESTS\n"
  },
  {
    "path": "dependencies/tl_expected/expected/.clang-format",
    "content": "BasedOnStyle: LLVM\n"
  },
  {
    "path": "dependencies/tl_expected/expected/.github/workflows/cmake.yml",
    "content": "name: CMake\n\non:\n  push:\n    branches: [ \"master\" ]\n  pull_request:\n    branches: [ \"master\" ]\n\njobs:\n  build:\n    runs-on: ubuntu-20.04\n    \n    strategy:\n      matrix:\n        std: [11, 14]\n        cxx: [g++-4.8, g++-4.9, g++-5, g++-6, g++-7, g++-8, g++-9, g++-10, clang++-3.5, clang++-3.6, clang++-3.7, clang++-3.8, clang++-3.9, clang++-4.0, clang++-5.0, clang++-6.0, clang++-7, clang++-8, clang++-9, clang++-10, clang++-11]\n        \n        exclude:\n          - cxx: g++-4.8\n            std: 14\n          - cxx: g++4.9\n            std: 14\n            \n        include:\n          - cxx: g++-4.8\n            install: sudo apt install g++-4.8\n          - cxx: g++-4.9\n            install: sudo apt install g++-4.9\n          - cxx: g++-5\n            install: sudo apt install g++-5\n          - cxx: g++-6\n            install: sudo apt install g++-6\n          - cxx: g++-7\n            install: sudo apt install g++-7\n          - cxx: g++-8\n            std: 11\n            install: sudo apt install g++-8\n          - cxx: g++-8\n            std: 14\n            install: sudo apt install g++-8\n          - cxx: g++-8\n            std: 17\n            install: sudo apt install g++-8\n          - cxx: g++-9\n            std: 14\n          - cxx: g++-9\n            std: 17\n          - cxx: g++-10\n            std: 14\n          - cxx: g++-10\n            std: 17\n          - cxx: g++-11\n            std: 14\n            install: sudo apt install g++-11\n          - cxx: g++-11\n            std: 17\n            install: sudo apt install g++-11\n          - cxx: g++-11\n            std: 20\n            install: sudo apt install g++-11\n            \n            \n          - cxx: clang++-3.5\n            install: sudo apt install clang-3.5\n          - cxx: clang++-3.6\n            install: sudo apt install clang-3.6\n          - cxx: clang++-3.7\n            install: sudo apt install clang-3.7\n          - cxx: clang++-3.8\n            install: sudo apt install clang-3.8\n          - cxx: clang++-3.9\n            install: sudo apt install clang-3.9\n          - cxx: clang++-4.0\n            install: sudo apt install clang-4.0\n          - cxx: clang++-5.0\n            install: sudo apt install clang-5.0\n          - cxx: clang++-6.0\n            install: sudo apt install clang-6.0\n          - cxx: clang++-7\n            install: sudo apt install clang-7\n          - cxx: clang++-8\n            install: sudo apt install clang-8\n          - cxx: clang++-9\n            install: sudo apt install clang-9\n          - cxx: clang++-10\n            install: sudo apt install clang-10\n          - cxx: clang++-11\n            install: sudo apt install clang-11\n            \n\n          - cxx: clang++-6.0\n            std: 17\n            install: sudo apt install clang-6.0\n          - cxx: clang++-7\n            std: 17\n            install: sudo apt install clang-7\n          - cxx: clang++-8\n            std: 17\n            install: sudo apt install clang-8\n          - cxx: clang++-9\n            std: 17\n            install: sudo apt install clang-9\n          - cxx: clang++-10\n            std: 17\n            install: sudo apt install clang-10\n          - cxx: clang++-11\n            std: 17\n            install: sudo apt install clang-11\n\n    steps:\n    - uses: actions/checkout@v3\n    \n    - name: Setup Toolchain\n      run: |\n        sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ xenial main'\n        sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ xenial universe'  \n        sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic main'\n        sudo apt-add-repository 'deb http://azure.archive.ubuntu.com/ubuntu/ bionic universe'\n        ${{matrix.install}}\n\n    - name: Configure CMake\n      env:\n        CXX: ${{matrix.cxx}}\n      run: cmake -B ${{github.workspace}}/build -DCMAKE_CXX_STANDARD=${{matrix.std}}\n\n    - name: Build\n      run: cmake --build ${{github.workspace}}/build \n\n    - name: Test\n      working-directory: ${{github.workspace}}/build\n      run: cmake --build ${{github.workspace}}/build --target test\n"
  },
  {
    "path": "dependencies/tl_expected/expected/.gitignore",
    "content": "\\#*\n.\\#*\n/build/\n"
  },
  {
    "path": "dependencies/tl_expected/expected/.travis.yml",
    "content": "language: cpp\n\ndist: xenial\n\nmatrix:\n  include:\n    - compiler: gcc\n      addons:\n        apt:\n          sources:\n             - ubuntu-toolchain-r-test\n          packages:\n             - g++-5\n      env: COMPILER=g++-5 CXXSTD=11  \n    - compiler: gcc\n      addons:\n        apt:\n          sources:\n             - ubuntu-toolchain-r-test\n          packages:\n             - g++-6\n      env: COMPILER=g++-6 CXXSTD=11\n    - compiler: gcc\n      addons:\n        apt:\n          sources:\n             - ubuntu-toolchain-r-test\n          packages:\n             - g++-7\n      env: COMPILER=g++-7 CXXSTD=11\n    - compiler: gcc\n      addons:\n        apt:\n          sources:\n             - ubuntu-toolchain-r-test\n          packages:\n             - g++-8\n      env: COMPILER=g++-8 CXXSTD=11\n    - compiler: gcc\n      addons:\n        apt:\n          sources:\n             - ubuntu-toolchain-r-test\n          packages:\n             - g++-4.9\n      env: COMPILER=g++-4.9 CXXSTD=11\n    - compiler: gcc\n      addons:\n        apt:\n          sources:\n             - ubuntu-toolchain-r-test\n          packages:\n             - g++-4.8\n      env: COMPILER=g++-4.8 CXXSTD=11\n    - compiler: clang\n      addons:\n        apt:\n          sources:\n             - llvm-toolchain-precise-3.5\n             - ubuntu-toolchain-r-test\n          packages:\n             - clang++-3.5\n             - libc++-dev\n      env: COMPILER=clang++-3.5 CXXSTD=11\n    - compiler: clang\n      addons:\n        apt:\n          sources:\n             - llvm-toolchain-precise-3.6\n             - ubuntu-toolchain-r-test\n          packages:\n             - clang++-3.6\n             - libc++-dev\n      env: COMPILER=clang++-3.6 CXXSTD=11\n    - compiler: clang\n      addons:\n        apt:\n          sources:\n             - llvm-toolchain-precise-3.7\n             - ubuntu-toolchain-r-test\n          packages:\n             - clang++-3.7\n             - libc++-dev\n      env: COMPILER=clang++-3.7 CXXSTD=11\n    - compiler: clang\n      addons:\n        apt:\n          sources:\n            - sourceline: \"deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-3.8 main\"\n              key_url: \"http://apt.llvm.org/llvm-snapshot.gpg.key\"\n            - ubuntu-toolchain-r-test\n          packages:\n             - clang++-3.8\n             - libc++-dev\n      env: COMPILER=clang++-3.8 CXXSTD=11\n    - compiler: clang\n      addons:\n        apt:\n          sources:\n            - sourceline: \"deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-3.9 main\"\n              key_url: \"http://apt.llvm.org/llvm-snapshot.gpg.key\"\n            - ubuntu-toolchain-r-test\n          packages:\n            - clang++-3.9\n            - libc++-dev\n      env: COMPILER=clang++-3.9 CXXSTD=11\n    - compiler: clang\n      addons:\n        apt:\n          sources:\n             - llvm-toolchain-xenial-4.0\n             - ubuntu-toolchain-r-test\n          packages:\n             - clang++-4.0\n             - libc++-dev\n      env: COMPILER=clang++-4.0 CXXSTD=11\n    - compiler: clang\n      addons:\n        apt:\n          sources:\n             - llvm-toolchain-xenial-5.0\n             - ubuntu-toolchain-r-test\n          packages:\n             - clang++-5.0\n             - libc++-dev\n      env: COMPILER=clang++-5.0 CXXSTD=11             \n    - compiler: clang\n      addons:\n        apt:\n          sources:\n             - llvm-toolchain-xenial-6.0\n             - ubuntu-toolchain-r-test\n          packages:\n             - clang++-6.0\n             - libc++-dev\n      env: COMPILER=clang++-6.0 CXXSTD=11             \n\n    - compiler: gcc\n      addons:\n        apt:\n          sources:\n             - ubuntu-toolchain-r-test\n          packages:\n             - g++-5\n      env: COMPILER=g++-5 CXXSTD=14\n    - compiler: gcc\n      addons:\n        apt:\n          sources:\n             - ubuntu-toolchain-r-test\n          packages:\n             - g++-6\n      env: COMPILER=g++-6 CXXSTD=14\n    - compiler: gcc\n      addons:\n        apt:\n          sources:\n             - ubuntu-toolchain-r-test\n          packages:\n             - g++-7\n      env: COMPILER=g++-7 CXXSTD=14\n    - compiler: gcc\n      addons:\n        apt:\n          sources:\n             - ubuntu-toolchain-r-test\n          packages:\n             - g++-8\n      env: COMPILER=g++-8 CXXSTD=14      \n    - compiler: clang\n      addons:\n        apt:\n          sources:\n             - llvm-toolchain-precise-3.5\n             - ubuntu-toolchain-r-test\n          packages:\n             - clang++-3.5\n             - libc++-dev\n      env: COMPILER=clang++-3.5 CXXSTD=14\n    - compiler: clang\n      addons:\n        apt:\n          sources:\n             - llvm-toolchain-precise-3.6\n             - ubuntu-toolchain-r-test\n          packages:\n             - clang++-3.6\n             - libc++-dev\n      env: COMPILER=clang++-3.6 CXXSTD=14\n    - compiler: clang\n      addons:\n        apt:\n          sources:\n             - llvm-toolchain-precise-3.7\n             - ubuntu-toolchain-r-test\n          packages:\n             - clang++-3.7\n             - libc++-dev\n      env: COMPILER=clang++-3.7 CXXSTD=14\n    - compiler: clang\n      addons:\n        apt:\n          sources:\n            - sourceline: \"deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-3.8 main\"\n              key_url: \"http://apt.llvm.org/llvm-snapshot.gpg.key\"\n            - ubuntu-toolchain-r-test\n          packages:\n             - clang++-3.8\n             - libc++-dev\n      env: COMPILER=clang++-3.8 CXXSTD=14\n    - compiler: clang\n      addons:\n        apt:\n          sources:\n            - sourceline: \"deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-3.9 main\"\n              key_url: \"http://apt.llvm.org/llvm-snapshot.gpg.key\"\n            - ubuntu-toolchain-r-test\n          packages:\n            - clang++-3.9\n            - libc++-dev\n      env: COMPILER=clang++-3.9 CXXSTD=14\n    - compiler: clang\n      addons:\n        apt:\n          sources:\n             - llvm-toolchain-xenial-4.0\n             - ubuntu-toolchain-r-test\n          packages:\n             - clang++-4.0\n             - libc++-dev\n      env: COMPILER=clang++-4.0 CXXSTD=14\n    - compiler: clang\n      addons:\n        apt:\n          sources:\n             - llvm-toolchain-xenial-5.0\n             - ubuntu-toolchain-r-test\n          packages:\n             - clang++-5.0\n             - libc++-dev\n      env: COMPILER=clang++-5.0 CXXSTD=14      \n    - compiler: clang\n      addons:\n        apt:\n          sources:\n             - llvm-toolchain-xenial-6.0\n             - ubuntu-toolchain-r-test\n          packages:\n             - clang++-6.0\n             - libc++-dev\n      env: COMPILER=clang++-6.0 CXXSTD=14      \n      \nbefore_install:\n  - sudo apt update\n  - sudo apt install -y apt-transport-https ca-certificates gnupg software-properties-common \n  - curl -L https://apt.kitware.com/keys/kitware-archive-latest.asc | sudo apt-key add -\n  - sudo apt-add-repository 'deb https://apt.kitware.com/ubuntu/ xenial main' \n  - sudo apt update\n\ninstall:\n  - if [ \"$CXX\" = \"clang++\" ]; then export CXX=\"$COMPILER -stdlib=libc++\"; fi\n  - if [ \"$CXX\" = \"g++\" ]; then export CXX=\"$COMPILER\"; fi\n  - sudo apt install -y cmake\n\nscript:\n  - /usr/bin/cmake -B build -S . \"-DCMAKE_CXX_STANDARD=$CXXSTD\"\n  - /usr/bin/cmake --build build\n  - /usr/bin/cmake --build build --target test\n"
  },
  {
    "path": "dependencies/tl_expected/expected/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.14)\nproject(tl-expected\n  HOMEPAGE_URL https://tl.tartanllama.xyz\n  DESCRIPTION \"C++11/14/17 std::expected with functional-style extensions\"\n  VERSION 1.0.0\n  LANGUAGES CXX)\n\ninclude(CMakePackageConfigHelpers)\ninclude(CMakeDependentOption)\ninclude(GNUInstallDirs)\ninclude(FetchContent)\ninclude(CTest)\n\nif (NOT DEFINED CMAKE_CXX_STANDARD)\n  set(CMAKE_CXX_STANDARD 14)\nendif()\n\noption(EXPECTED_BUILD_PACKAGE \"Build package files as well\" ON)\n\ncmake_dependent_option(EXPECTED_BUILD_TESTS\n  \"Enable tl::expected tests\" ON\n  \"BUILD_TESTING\" OFF)\n\ncmake_dependent_option(EXPECTED_BUILD_PACKAGE_DEB\n  \"Create a DEB\" ON\n  \"EXPECTED_BUILD_PACKAGE\" OFF)\n\nadd_library(expected INTERFACE)\ntarget_include_directories(expected\n  INTERFACE\n    $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>\n    $<INSTALL_INTERFACE:include>)\n\nif (NOT CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)\n  add_library(tl::expected ALIAS expected)\nendif()\n\n# Installation help\nconfigure_package_config_file(\n  \"${PROJECT_SOURCE_DIR}/cmake/${PROJECT_NAME}-config.cmake.in\"\n  \"${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config.cmake\"\n  INSTALL_DESTINATION \"share/cmake/${PROJECT_NAME}\")\n\nwrite_basic_package_version_file(\n  \"${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake\"\n  COMPATIBILITY SameMajorVersion\n  ARCH_INDEPENDENT)\n\ninstall(TARGETS expected\n  EXPORT ${PROJECT_NAME}-targets\n  INCLUDES DESTINATION \"${CMAKE_INSTALL_DATADIR}\")\n\ninstall(EXPORT ${PROJECT_NAME}-targets\n  DESTINATION \"${CMAKE_INSTALL_DATADIR}/cmake/${PROJECT_NAME}\"\n  NAMESPACE tl::\n  FILE \"${PROJECT_NAME}-targets.cmake\")\n\ninstall(FILES\n  \"${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake\"\n  \"${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config.cmake\"\n  DESTINATION \"${CMAKE_INSTALL_DATADIR}/cmake/${PROJECT_NAME}\")\n\ninstall(DIRECTORY \"include/\" TYPE INCLUDE)\n\nif(EXPECTED_BUILD_TESTS)\n  set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)\n  set(CATCH_INSTALL_HELPERS OFF)\n  set(CATCH_BUILD_TESTING OFF)\n  set(CATCH_INSTALL_DOCS OFF)\n  FetchContent_Declare(Catch2 URL\n    https://github.com/catchorg/Catch2/archive/v2.13.10.zip) \n  FetchContent_MakeAvailable(Catch2)\n\n  file(GLOB test-sources CONFIGURE_DEPENDS tests/*.cpp)\n  list(FILTER test-sources EXCLUDE REGEX \"tests/test.cpp\")\n  add_executable(${PROJECT_NAME}-tests \"${test-sources}\")\n  target_compile_options(${PROJECT_NAME}-tests PRIVATE\n    $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra>)\n\n  target_link_libraries(${PROJECT_NAME}-tests\n    PRIVATE\n      Catch2::Catch2\n      expected)\n  add_test(NAME tl::expected::tests COMMAND ${PROJECT_NAME}-tests)\nendif()\n\nif (NOT EXPECTED_BUILD_PACKAGE)\n  return()\nendif()\n\nlist(APPEND source-generators TBZ2 TGZ TXZ ZIP)\n\nif (CMAKE_HOST_WIN32)\n  list(APPEND binary-generators \"WIX\")\nendif()\n\nif (EXPECTED_BUILD_PACKAGE_DEB)\n  list(APPEND binary-generators \"DEB\")\nendif()\n\nif (EXPECTED_BUILD_RPM)\n  list(APPEND binary-generators \"RPM\")\nendif()\n\n\nset(CPACK_SOURCE_GENERATOR ${source-generators})\nset(CPACK_GENERATOR ${binary-generators})\n\nset(CPACK_PACKAGE_FILE_NAME \"${PROJECT_NAME}-${PROJECT_VERSION}\")\nset(CPACK_SOURCE_PACKAGE_FILE_NAME \"${CPACK_PACKAGE_FILE_NAME}\")\n\nset(CPACK_DEBIAN_PACKAGE_MAINTAINER \"Sy Brand\")\n\nlist(APPEND CPACK_SOURCE_IGNORE_FILES /.git/ /build/ .gitignore .DS_Store)\n\ninclude(CPack)\n\n"
  },
  {
    "path": "dependencies/tl_expected/expected/COPYING",
    "content": "Creative Commons Legal Code\n\nCC0 1.0 Universal\n\n    CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE\n    LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN\n    ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS\n    INFORMATION ON AN \"AS-IS\" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES\n    REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS\n    PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM\n    THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED\n    HEREUNDER.\n\nStatement of Purpose\n\nThe laws of most jurisdictions throughout the world automatically confer\nexclusive Copyright and Related Rights (defined below) upon the creator\nand subsequent owner(s) (each and all, an \"owner\") of an original work of\nauthorship and/or a database (each, a \"Work\").\n\nCertain owners wish to permanently relinquish those rights to a Work for\nthe purpose of contributing to a commons of creative, cultural and\nscientific works (\"Commons\") that the public can reliably and without fear\nof later claims of infringement build upon, modify, incorporate in other\nworks, reuse and redistribute as freely as possible in any form whatsoever\nand for any purposes, including without limitation commercial purposes.\nThese owners may contribute to the Commons to promote the ideal of a free\nculture and the further production of creative, cultural and scientific\nworks, or to gain reputation or greater distribution for their Work in\npart through the use and efforts of others.\n\nFor these and/or other purposes and motivations, and without any\nexpectation of additional consideration or compensation, the person\nassociating CC0 with a Work (the \"Affirmer\"), to the extent that he or she\nis an owner of Copyright and Related Rights in the Work, voluntarily\nelects to apply CC0 to the Work and publicly distribute the Work under its\nterms, with knowledge of his or her Copyright and Related Rights in the\nWork and the meaning and intended legal effect of CC0 on those rights.\n\n1. Copyright and Related Rights. A Work made available under CC0 may be\nprotected by copyright and related or neighboring rights (\"Copyright and\nRelated Rights\"). Copyright and Related Rights include, but are not\nlimited to, the following:\n\n  i. the right to reproduce, adapt, distribute, perform, display,\n     communicate, and translate a Work;\n ii. moral rights retained by the original author(s) and/or performer(s);\niii. publicity and privacy rights pertaining to a person's image or\n     likeness depicted in a Work;\n iv. rights protecting against unfair competition in regards to a Work,\n     subject to the limitations in paragraph 4(a), below;\n  v. rights protecting the extraction, dissemination, use and reuse of data\n     in a Work;\n vi. database rights (such as those arising under Directive 96/9/EC of the\n     European Parliament and of the Council of 11 March 1996 on the legal\n     protection of databases, and under any national implementation\n     thereof, including any amended or successor version of such\n     directive); and\nvii. other similar, equivalent or corresponding rights throughout the\n     world based on applicable law or treaty, and any national\n     implementations thereof.\n\n2. Waiver. To the greatest extent permitted by, but not in contravention\nof, applicable law, Affirmer hereby overtly, fully, permanently,\nirrevocably and unconditionally waives, abandons, and surrenders all of\nAffirmer's Copyright and Related Rights and associated claims and causes\nof action, whether now known or unknown (including existing as well as\nfuture claims and causes of action), in the Work (i) in all territories\nworldwide, (ii) for the maximum duration provided by applicable law or\ntreaty (including future time extensions), (iii) in any current or future\nmedium and for any number of copies, and (iv) for any purpose whatsoever,\nincluding without limitation commercial, advertising or promotional\npurposes (the \"Waiver\"). Affirmer makes the Waiver for the benefit of each\nmember of the public at large and to the detriment of Affirmer's heirs and\nsuccessors, fully intending that such Waiver shall not be subject to\nrevocation, rescission, cancellation, termination, or any other legal or\nequitable action to disrupt the quiet enjoyment of the Work by the public\nas contemplated by Affirmer's express Statement of Purpose.\n\n3. Public License Fallback. Should any part of the Waiver for any reason\nbe judged legally invalid or ineffective under applicable law, then the\nWaiver shall be preserved to the maximum extent permitted taking into\naccount Affirmer's express Statement of Purpose. In addition, to the\nextent the Waiver is so judged Affirmer hereby grants to each affected\nperson a royalty-free, non transferable, non sublicensable, non exclusive,\nirrevocable and unconditional license to exercise Affirmer's Copyright and\nRelated Rights in the Work (i) in all territories worldwide, (ii) for the\nmaximum duration provided by applicable law or treaty (including future\ntime extensions), (iii) in any current or future medium and for any number\nof copies, and (iv) for any purpose whatsoever, including without\nlimitation commercial, advertising or promotional purposes (the\n\"License\"). The License shall be deemed effective as of the date CC0 was\napplied by Affirmer to the Work. Should any part of the License for any\nreason be judged legally invalid or ineffective under applicable law, such\npartial invalidity or ineffectiveness shall not invalidate the remainder\nof the License, and in such case Affirmer hereby affirms that he or she\nwill not (i) exercise any of his or her remaining Copyright and Related\nRights in the Work or (ii) assert any associated claims and causes of\naction with respect to the Work, in either case contrary to Affirmer's\nexpress Statement of Purpose.\n\n4. Limitations and Disclaimers.\n\n a. No trademark or patent rights held by Affirmer are waived, abandoned,\n    surrendered, licensed or otherwise affected by this document.\n b. Affirmer offers the Work as-is and makes no representations or\n    warranties of any kind concerning the Work, express, implied,\n    statutory or otherwise, including without limitation warranties of\n    title, merchantability, fitness for a particular purpose, non\n    infringement, or the absence of latent or other defects, accuracy, or\n    the present or absence of errors, whether or not discoverable, all to\n    the greatest extent permissible under applicable law.\n c. Affirmer disclaims responsibility for clearing rights of other persons\n    that may apply to the Work or any use thereof, including without\n    limitation any person's Copyright and Related Rights in the Work.\n    Further, Affirmer disclaims responsibility for obtaining any necessary\n    consents, permissions or other rights required for any use of the\n    Work.\n d. Affirmer understands and acknowledges that Creative Commons is not a\n    party to this document and has no duty or obligation with respect to\n    this CC0 or use of the Work.\n"
  },
  {
    "path": "dependencies/tl_expected/expected/README.md",
    "content": "# expected\nSingle header implementation of `std::expected` with functional-style extensions.\n\n[![Documentation SmartStatus](https://readthedocs.org/projects/tl-docs/badge/?version=latest)](https://tl.tartanllama.xyz/en/latest/?badge=latest)\nClang + GCC: [![Linux Build SmartStatus](https://github.com/TartanLlama/expected/actions/workflows/cmake.yml/badge.svg)](https://github.com/TartanLlama/expected/actions/workflows/cmake.yml)\nMSVC: [![Windows Build SmartStatus](https://ci.appveyor.com/api/projects/status/k5x00xa11y3s5wsg?svg=true)](https://ci.appveyor.com/project/TartanLlama/expected)\n\nAvailable on [Vcpkg](https://github.com/microsoft/vcpkg/tree/master/ports/tl-expected) and [Conan](https://github.com/yipdw/conan-tl-expected).\n\n[`std::expected`](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0323r3.pdf) is proposed as the preferred way to represent object which will either have an expected value, or an unexpected value giving information about why something failed. Unfortunately, chaining together many computations which may fail can be verbose, as error-checking code will be mixed in with the actual programming logic. This implementation provides a number of utilities to make coding with `expected` cleaner.\n\nFor example, instead of writing this code:\n\n```cpp\nstd::expected<image,fail_reason> get_cute_cat (const image& img) {\n    auto cropped = crop_to_cat(img);\n    if (!cropped) {\n      return cropped;\n    }\n\n    auto with_tie = add_bow_tie(*cropped);\n    if (!with_tie) {\n      return with_tie;\n    }\n\n    auto with_sparkles = make_eyes_sparkle(*with_tie);\n    if (!with_sparkles) {\n       return with_sparkles;\n    }\n\n    return add_rainbow(make_smaller(*with_sparkles));\n}\n```\n\nYou can do this:\n\n```cpp\ntl::expected<image,fail_reason> get_cute_cat (const image& img) {\n    return crop_to_cat(img)\n           .and_then(add_bow_tie)\n           .and_then(make_eyes_sparkle)\n           .map(make_smaller)\n           .map(add_rainbow);\n}\n```\n\nThe interface is the same as `std::expected` as proposed in [p0323r3](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0323r3.pdf), but the following member functions are also defined. Explicit types are for clarity.\n\n- `map`: carries out some operation on the stored object if there is one.\n  * `tl::expected<std::size_t,std::error_code> s = exp_string.map(&std::string::size);`\n- `map_error`: carries out some operation on the unexpected object if there is one.\n  * `my_error_code translate_error (std::error_code);`\n  * `tl::expected<int,my_error_code> s = exp_int.map_error(translate_error);`\n- `and_then`: like `map`, but for operations which return a `tl::expected`.\n  * `tl::expected<ast, fail_reason> parse (const std::string& s);`\n  * `tl::expected<ast, fail_reason> exp_ast = exp_string.and_then(parse);`\n- `or_else`: calls some function if there is no value stored.\n  * `exp.or_else([] { throw std::runtime_error{\"oh no\"}; });`\n\np0323r3 specifies calling `.error()` on an expected value, or using the `*` or `->` operators on an unexpected value, to be undefined behaviour. In this implementation it causes an assertion failure. The implementation of assertions can be overridden by defining the macro `TL_ASSERT(boolean_condition)` before #including <tl/expected.hpp>; by default, `assert(boolean_condition)` from the `<cassert>` header is used. Note that correct code would not rely on these assertions.\n\n### Compiler support\n\nTested on:\n\n- Linux\n  * clang++ 3.5, 3.6, 3.7, 3.8, 3.9, 4, 5, 6, 7, 8, 9, 10, 11\n  * g++ 4.8, 4.9, 5.5, 6.4, 7.5, 8, 9, 10 \n- Windows\n  * MSVC 2015, 2017, 2019, 2022\n\n----------\n\n[![CC0](http://i.creativecommons.org/p/zero/1.0/88x31.png)](\"http://creativecommons.org/publicdomain/zero/1.0/\")\n\nTo the extent possible under law, [Sy Brand](https://twitter.com/TartanLlama) has waived all copyright and related or neighboring rights to the `expected` library. This work is published from: United Kingdom.\n"
  },
  {
    "path": "dependencies/tl_expected/expected/cmake/tl-expected-config.cmake.in",
    "content": "@PACKAGE_INIT@\n\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/tl-expected-targets.cmake\")"
  },
  {
    "path": "dependencies/tl_expected/expected/include/tl/expected.hpp",
    "content": "///\n// expected - An implementation of std::expected with extensions\n// Written in 2017 by Sy Brand (tartanllama@gmail.com, @TartanLlama)\n//\n// Documentation available at http://tl.tartanllama.xyz/\n//\n// To the extent possible under law, the author(s) have dedicated all\n// copyright and related and neighboring rights to this software to the\n// public domain worldwide. This software is distributed without any warranty.\n//\n// You should have received a copy of the CC0 Public Domain Dedication\n// along with this software. If not, see\n// <http://creativecommons.org/publicdomain/zero/1.0/>.\n///\n\n#ifndef TL_EXPECTED_HPP\n#define TL_EXPECTED_HPP\n\n#define TL_EXPECTED_VERSION_MAJOR 1\n#define TL_EXPECTED_VERSION_MINOR 1\n#define TL_EXPECTED_VERSION_PATCH 0\n\n#include <exception>\n#include <functional>\n#include <type_traits>\n#include <utility>\n\n#if defined(__EXCEPTIONS) || defined(_CPPUNWIND)\n#define TL_EXPECTED_EXCEPTIONS_ENABLED\n#endif\n\n#if (defined(_MSC_VER) && _MSC_VER == 1900)\n#define TL_EXPECTED_MSVC2015\n#define TL_EXPECTED_MSVC2015_CONSTEXPR\n#else\n#define TL_EXPECTED_MSVC2015_CONSTEXPR constexpr\n#endif\n\n#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 &&              \\\n     !defined(__clang__))\n#define TL_EXPECTED_GCC49\n#endif\n\n#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 &&              \\\n     !defined(__clang__))\n#define TL_EXPECTED_GCC54\n#endif\n\n#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 &&              \\\n     !defined(__clang__))\n#define TL_EXPECTED_GCC55\n#endif\n\n#if !defined(TL_ASSERT)\n//can't have assert in constexpr in C++11 and GCC 4.9 has a compiler bug\n#if (__cplusplus > 201103L) && !defined(TL_EXPECTED_GCC49)\n#include <cassert>\n#define TL_ASSERT(x) assert(x)\n#else \n#define TL_ASSERT(x)\n#endif\n#endif\n\n#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 &&              \\\n     !defined(__clang__))\n// GCC < 5 doesn't support overloading on const&& for member functions\n\n#define TL_EXPECTED_NO_CONSTRR\n// GCC < 5 doesn't support some standard C++11 type traits\n#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T)                         \\\n  std::has_trivial_copy_constructor<T>\n#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T)                            \\\n  std::has_trivial_copy_assign<T>\n\n// This one will be different for GCC 5.7 if it's ever supported\n#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T)                               \\\n  std::is_trivially_destructible<T>\n\n// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks\n// std::vector for non-copyable types\n#elif (defined(__GNUC__) && __GNUC__ < 8 && !defined(__clang__))\n#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX\n#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX\nnamespace tl {\nnamespace detail {\ntemplate <class T>\nstruct is_trivially_copy_constructible\n    : std::is_trivially_copy_constructible<T> {};\n#ifdef _GLIBCXX_VECTOR\ntemplate <class T, class A>\nstruct is_trivially_copy_constructible<std::vector<T, A>> : std::false_type {};\n#endif\n} // namespace detail\n} // namespace tl\n#endif\n\n#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T)                         \\\n  tl::detail::is_trivially_copy_constructible<T>\n#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T)                            \\\n  std::is_trivially_copy_assignable<T>\n#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T)                               \\\n  std::is_trivially_destructible<T>\n#else\n#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T)                         \\\n  std::is_trivially_copy_constructible<T>\n#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T)                            \\\n  std::is_trivially_copy_assignable<T>\n#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T)                               \\\n  std::is_trivially_destructible<T>\n#endif\n\n#if __cplusplus > 201103L\n#define TL_EXPECTED_CXX14\n#endif\n\n#ifdef TL_EXPECTED_GCC49\n#define TL_EXPECTED_GCC49_CONSTEXPR\n#else\n#define TL_EXPECTED_GCC49_CONSTEXPR constexpr\n#endif\n\n#if (__cplusplus == 201103L || defined(TL_EXPECTED_MSVC2015) ||                \\\n     defined(TL_EXPECTED_GCC49))\n#define TL_EXPECTED_11_CONSTEXPR\n#else\n#define TL_EXPECTED_11_CONSTEXPR constexpr\n#endif\n\nnamespace tl {\ntemplate <class T, class E> class expected;\n\n#ifndef TL_MONOSTATE_INPLACE_MUTEX\n#define TL_MONOSTATE_INPLACE_MUTEX\nclass monostate {};\n\nstruct in_place_t {\n  explicit in_place_t() = default;\n};\nstatic constexpr in_place_t in_place{};\n#endif\n\ntemplate <class E> class unexpected {\npublic:\n  static_assert(!std::is_same<E, void>::value, \"E must not be void\");\n\n  unexpected() = delete;\n  constexpr explicit unexpected(const E &e) : m_val(e) {}\n\n  constexpr explicit unexpected(E &&e) : m_val(std::move(e)) {}\n\n  template <class... Args, typename std::enable_if<std::is_constructible<\n                               E, Args &&...>::value>::type * = nullptr>\n  constexpr explicit unexpected(Args &&...args)\n      : m_val(std::forward<Args>(args)...) {}\n  template <\n      class U, class... Args,\n      typename std::enable_if<std::is_constructible<\n          E, std::initializer_list<U> &, Args &&...>::value>::type * = nullptr>\n  constexpr explicit unexpected(std::initializer_list<U> l, Args &&...args)\n      : m_val(l, std::forward<Args>(args)...) {}\n\n  constexpr const E &value() const & { return m_val; }\n  TL_EXPECTED_11_CONSTEXPR E &value() & { return m_val; }\n  TL_EXPECTED_11_CONSTEXPR E &&value() && { return std::move(m_val); }\n  constexpr const E &&value() const && { return std::move(m_val); }\n\nprivate:\n  E m_val;\n};\n\n#ifdef __cpp_deduction_guides\ntemplate <class E> unexpected(E) -> unexpected<E>;\n#endif\n\ntemplate <class E>\nconstexpr bool operator==(const unexpected<E> &lhs, const unexpected<E> &rhs) {\n  return lhs.value() == rhs.value();\n}\ntemplate <class E>\nconstexpr bool operator!=(const unexpected<E> &lhs, const unexpected<E> &rhs) {\n  return lhs.value() != rhs.value();\n}\ntemplate <class E>\nconstexpr bool operator<(const unexpected<E> &lhs, const unexpected<E> &rhs) {\n  return lhs.value() < rhs.value();\n}\ntemplate <class E>\nconstexpr bool operator<=(const unexpected<E> &lhs, const unexpected<E> &rhs) {\n  return lhs.value() <= rhs.value();\n}\ntemplate <class E>\nconstexpr bool operator>(const unexpected<E> &lhs, const unexpected<E> &rhs) {\n  return lhs.value() > rhs.value();\n}\ntemplate <class E>\nconstexpr bool operator>=(const unexpected<E> &lhs, const unexpected<E> &rhs) {\n  return lhs.value() >= rhs.value();\n}\n\ntemplate <class E>\nunexpected<typename std::decay<E>::type> make_unexpected(E &&e) {\n  return unexpected<typename std::decay<E>::type>(std::forward<E>(e));\n}\n\nstruct unexpect_t {\n  unexpect_t() = default;\n};\nstatic constexpr unexpect_t unexpect{};\n\nnamespace detail {\ntemplate <typename E>\n[[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E &&e) {\n#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED\n  throw std::forward<E>(e);\n#else\n  (void)e;\n#ifdef _MSC_VER\n  __assume(0);\n#else\n  __builtin_unreachable();\n#endif\n#endif\n}\n\n#ifndef TL_TRAITS_MUTEX\n#define TL_TRAITS_MUTEX\n// C++14-style aliases for brevity\ntemplate <class T> using remove_const_t = typename std::remove_const<T>::type;\ntemplate <class T>\nusing remove_reference_t = typename std::remove_reference<T>::type;\ntemplate <class T> using decay_t = typename std::decay<T>::type;\ntemplate <bool E, class T = void>\nusing enable_if_t = typename std::enable_if<E, T>::type;\ntemplate <bool B, class T, class F>\nusing conditional_t = typename std::conditional<B, T, F>::type;\n\n// std::conjunction from C++17\ntemplate <class...> struct conjunction : std::true_type {};\ntemplate <class B> struct conjunction<B> : B {};\ntemplate <class B, class... Bs>\nstruct conjunction<B, Bs...>\n    : std::conditional<bool(B::value), conjunction<Bs...>, B>::type {};\n\n#if defined(_LIBCPP_VERSION) && __cplusplus == 201103L\n#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND\n#endif\n\n// In C++11 mode, there's an issue in libc++'s std::mem_fn\n// which results in a hard-error when using it in a noexcept expression\n// in some cases. This is a check to workaround the common failing case.\n#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND\ntemplate <class T>\nstruct is_pointer_to_non_const_member_func : std::false_type {};\ntemplate <class T, class Ret, class... Args>\nstruct is_pointer_to_non_const_member_func<Ret (T::*)(Args...)>\n    : std::true_type {};\ntemplate <class T, class Ret, class... Args>\nstruct is_pointer_to_non_const_member_func<Ret (T::*)(Args...) &>\n    : std::true_type {};\ntemplate <class T, class Ret, class... Args>\nstruct is_pointer_to_non_const_member_func<Ret (T::*)(Args...) &&>\n    : std::true_type {};\ntemplate <class T, class Ret, class... Args>\nstruct is_pointer_to_non_const_member_func<Ret (T::*)(Args...) volatile>\n    : std::true_type {};\ntemplate <class T, class Ret, class... Args>\nstruct is_pointer_to_non_const_member_func<Ret (T::*)(Args...) volatile &>\n    : std::true_type {};\ntemplate <class T, class Ret, class... Args>\nstruct is_pointer_to_non_const_member_func<Ret (T::*)(Args...) volatile &&>\n    : std::true_type {};\n\ntemplate <class T> struct is_const_or_const_ref : std::false_type {};\ntemplate <class T> struct is_const_or_const_ref<T const &> : std::true_type {};\ntemplate <class T> struct is_const_or_const_ref<T const> : std::true_type {};\n#endif\n\n// std::invoke from C++17\n// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround\ntemplate <\n    typename Fn, typename... Args,\n#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND\n    typename = enable_if_t<!(is_pointer_to_non_const_member_func<Fn>::value &&\n                             is_const_or_const_ref<Args...>::value)>,\n#endif\n    typename = enable_if_t<std::is_member_pointer<decay_t<Fn>>::value>, int = 0>\nconstexpr auto invoke(Fn &&f, Args &&...args) noexcept(\n    noexcept(std::mem_fn(f)(std::forward<Args>(args)...)))\n    -> decltype(std::mem_fn(f)(std::forward<Args>(args)...)) {\n  return std::mem_fn(f)(std::forward<Args>(args)...);\n}\n\ntemplate <typename Fn, typename... Args,\n          typename = enable_if_t<!std::is_member_pointer<decay_t<Fn>>::value>>\nconstexpr auto invoke(Fn &&f, Args &&...args) noexcept(\n    noexcept(std::forward<Fn>(f)(std::forward<Args>(args)...)))\n    -> decltype(std::forward<Fn>(f)(std::forward<Args>(args)...)) {\n  return std::forward<Fn>(f)(std::forward<Args>(args)...);\n}\n\n// std::invoke_result from C++17\ntemplate <class F, class, class... Us> struct invoke_result_impl;\n\ntemplate <class F, class... Us>\nstruct invoke_result_impl<\n    F,\n    decltype(detail::invoke(std::declval<F>(), std::declval<Us>()...), void()),\n    Us...> {\n  using type =\n      decltype(detail::invoke(std::declval<F>(), std::declval<Us>()...));\n};\n\ntemplate <class F, class... Us>\nusing invoke_result = invoke_result_impl<F, void, Us...>;\n\ntemplate <class F, class... Us>\nusing invoke_result_t = typename invoke_result<F, Us...>::type;\n\n#if defined(_MSC_VER) && _MSC_VER <= 1900\n// TODO make a version which works with MSVC 2015\ntemplate <class T, class U = T> struct is_swappable : std::true_type {};\n\ntemplate <class T, class U = T> struct is_nothrow_swappable : std::true_type {};\n#else\n// https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept\nnamespace swap_adl_tests {\n// if swap ADL finds this then it would call std::swap otherwise (same\n// signature)\nstruct tag {};\n\ntemplate <class T> tag swap(T &, T &);\ntemplate <class T, std::size_t N> tag swap(T (&a)[N], T (&b)[N]);\n\n// helper functions to test if an unqualified swap is possible, and if it\n// becomes std::swap\ntemplate <class, class> std::false_type can_swap(...) noexcept(false);\ntemplate <class T, class U,\n          class = decltype(swap(std::declval<T &>(), std::declval<U &>()))>\nstd::true_type can_swap(int) noexcept(noexcept(swap(std::declval<T &>(),\n                                                    std::declval<U &>())));\n\ntemplate <class, class> std::false_type uses_std(...);\ntemplate <class T, class U>\nstd::is_same<decltype(swap(std::declval<T &>(), std::declval<U &>())), tag>\nuses_std(int);\n\ntemplate <class T>\nstruct is_std_swap_noexcept\n    : std::integral_constant<bool,\n                             std::is_nothrow_move_constructible<T>::value &&\n                                 std::is_nothrow_move_assignable<T>::value> {};\n\ntemplate <class T, std::size_t N>\nstruct is_std_swap_noexcept<T[N]> : is_std_swap_noexcept<T> {};\n\ntemplate <class T, class U>\nstruct is_adl_swap_noexcept\n    : std::integral_constant<bool, noexcept(can_swap<T, U>(0))> {};\n} // namespace swap_adl_tests\n\ntemplate <class T, class U = T>\nstruct is_swappable\n    : std::integral_constant<\n          bool,\n          decltype(detail::swap_adl_tests::can_swap<T, U>(0))::value &&\n              (!decltype(detail::swap_adl_tests::uses_std<T, U>(0))::value ||\n               (std::is_move_assignable<T>::value &&\n                std::is_move_constructible<T>::value))> {};\n\ntemplate <class T, std::size_t N>\nstruct is_swappable<T[N], T[N]>\n    : std::integral_constant<\n          bool,\n          decltype(detail::swap_adl_tests::can_swap<T[N], T[N]>(0))::value &&\n              (!decltype(detail::swap_adl_tests::uses_std<T[N], T[N]>(\n                   0))::value ||\n               is_swappable<T, T>::value)> {};\n\ntemplate <class T, class U = T>\nstruct is_nothrow_swappable\n    : std::integral_constant<\n          bool,\n          is_swappable<T, U>::value &&\n              ((decltype(detail::swap_adl_tests::uses_std<T, U>(0))::value &&\n                detail::swap_adl_tests::is_std_swap_noexcept<T>::value) ||\n               (!decltype(detail::swap_adl_tests::uses_std<T, U>(0))::value &&\n                detail::swap_adl_tests::is_adl_swap_noexcept<T, U>::value))> {};\n#endif\n#endif\n\n// Trait for checking if a type is a tl::expected\ntemplate <class T> struct is_expected_impl : std::false_type {};\ntemplate <class T, class E>\nstruct is_expected_impl<expected<T, E>> : std::true_type {};\ntemplate <class T> using is_expected = is_expected_impl<decay_t<T>>;\n\ntemplate <class T, class E, class U>\nusing expected_enable_forward_value = detail::enable_if_t<\n    std::is_constructible<T, U &&>::value &&\n    !std::is_same<detail::decay_t<U>, in_place_t>::value &&\n    !std::is_same<expected<T, E>, detail::decay_t<U>>::value &&\n    !std::is_same<unexpected<E>, detail::decay_t<U>>::value>;\n\ntemplate <class T, class E, class U, class G, class UR, class GR>\nusing expected_enable_from_other = detail::enable_if_t<\n    std::is_constructible<T, UR>::value &&\n    std::is_constructible<E, GR>::value &&\n    !std::is_constructible<T, expected<U, G> &>::value &&\n    !std::is_constructible<T, expected<U, G> &&>::value &&\n    !std::is_constructible<T, const expected<U, G> &>::value &&\n    !std::is_constructible<T, const expected<U, G> &&>::value &&\n    !std::is_convertible<expected<U, G> &, T>::value &&\n    !std::is_convertible<expected<U, G> &&, T>::value &&\n    !std::is_convertible<const expected<U, G> &, T>::value &&\n    !std::is_convertible<const expected<U, G> &&, T>::value>;\n\ntemplate <class T, class U>\nusing is_void_or = conditional_t<std::is_void<T>::value, std::true_type, U>;\n\ntemplate <class T>\nusing is_copy_constructible_or_void =\n    is_void_or<T, std::is_copy_constructible<T>>;\n\ntemplate <class T>\nusing is_move_constructible_or_void =\n    is_void_or<T, std::is_move_constructible<T>>;\n\ntemplate <class T>\nusing is_copy_assignable_or_void = is_void_or<T, std::is_copy_assignable<T>>;\n\ntemplate <class T>\nusing is_move_assignable_or_void = is_void_or<T, std::is_move_assignable<T>>;\n\n} // namespace detail\n\nnamespace detail {\nstruct no_init_t {};\nstatic constexpr no_init_t no_init{};\n\n// Implements the storage of the values, and ensures that the destructor is\n// trivial if it can be.\n//\n// This specialization is for where neither `T` or `E` is trivially\n// destructible, so the destructors must be called on destruction of the\n// `expected`\ntemplate <class T, class E, bool = std::is_trivially_destructible<T>::value,\n          bool = std::is_trivially_destructible<E>::value>\nstruct expected_storage_base {\n  constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {}\n  constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {}\n\n  template <class... Args,\n            detail::enable_if_t<std::is_constructible<T, Args &&...>::value> * =\n                nullptr>\n  constexpr expected_storage_base(in_place_t, Args &&...args)\n      : m_val(std::forward<Args>(args)...), m_has_val(true) {}\n\n  template <class U, class... Args,\n            detail::enable_if_t<std::is_constructible<\n                T, std::initializer_list<U> &, Args &&...>::value> * = nullptr>\n  constexpr expected_storage_base(in_place_t, std::initializer_list<U> il,\n                                  Args &&...args)\n      : m_val(il, std::forward<Args>(args)...), m_has_val(true) {}\n  template <class... Args,\n            detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =\n                nullptr>\n  constexpr explicit expected_storage_base(unexpect_t, Args &&...args)\n      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}\n\n  template <class U, class... Args,\n            detail::enable_if_t<std::is_constructible<\n                E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>\n  constexpr explicit expected_storage_base(unexpect_t,\n                                           std::initializer_list<U> il,\n                                           Args &&...args)\n      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}\n\n  ~expected_storage_base() {\n    if (m_has_val) {\n      m_val.~T();\n    } else {\n      m_unexpect.~unexpected<E>();\n    }\n  }\n  union {\n    T m_val;\n    unexpected<E> m_unexpect;\n    char m_no_init;\n  };\n  bool m_has_val;\n};\n\n// This specialization is for when both `T` and `E` are trivially-destructible,\n// so the destructor of the `expected` can be trivial.\ntemplate <class T, class E> struct expected_storage_base<T, E, true, true> {\n  constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {}\n  constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {}\n\n  template <class... Args,\n            detail::enable_if_t<std::is_constructible<T, Args &&...>::value> * =\n                nullptr>\n  constexpr expected_storage_base(in_place_t, Args &&...args)\n      : m_val(std::forward<Args>(args)...), m_has_val(true) {}\n\n  template <class U, class... Args,\n            detail::enable_if_t<std::is_constructible<\n                T, std::initializer_list<U> &, Args &&...>::value> * = nullptr>\n  constexpr expected_storage_base(in_place_t, std::initializer_list<U> il,\n                                  Args &&...args)\n      : m_val(il, std::forward<Args>(args)...), m_has_val(true) {}\n  template <class... Args,\n            detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =\n                nullptr>\n  constexpr explicit expected_storage_base(unexpect_t, Args &&...args)\n      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}\n\n  template <class U, class... Args,\n            detail::enable_if_t<std::is_constructible<\n                E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>\n  constexpr explicit expected_storage_base(unexpect_t,\n                                           std::initializer_list<U> il,\n                                           Args &&...args)\n      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}\n\n  ~expected_storage_base() = default;\n  union {\n    T m_val;\n    unexpected<E> m_unexpect;\n    char m_no_init;\n  };\n  bool m_has_val;\n};\n\n// T is trivial, E is not.\ntemplate <class T, class E> struct expected_storage_base<T, E, true, false> {\n  constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {}\n  TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t)\n      : m_no_init(), m_has_val(false) {}\n\n  template <class... Args,\n            detail::enable_if_t<std::is_constructible<T, Args &&...>::value> * =\n                nullptr>\n  constexpr expected_storage_base(in_place_t, Args &&...args)\n      : m_val(std::forward<Args>(args)...), m_has_val(true) {}\n\n  template <class U, class... Args,\n            detail::enable_if_t<std::is_constructible<\n                T, std::initializer_list<U> &, Args &&...>::value> * = nullptr>\n  constexpr expected_storage_base(in_place_t, std::initializer_list<U> il,\n                                  Args &&...args)\n      : m_val(il, std::forward<Args>(args)...), m_has_val(true) {}\n  template <class... Args,\n            detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =\n                nullptr>\n  constexpr explicit expected_storage_base(unexpect_t, Args &&...args)\n      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}\n\n  template <class U, class... Args,\n            detail::enable_if_t<std::is_constructible<\n                E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>\n  constexpr explicit expected_storage_base(unexpect_t,\n                                           std::initializer_list<U> il,\n                                           Args &&...args)\n      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}\n\n  ~expected_storage_base() {\n    if (!m_has_val) {\n      m_unexpect.~unexpected<E>();\n    }\n  }\n\n  union {\n    T m_val;\n    unexpected<E> m_unexpect;\n    char m_no_init;\n  };\n  bool m_has_val;\n};\n\n// E is trivial, T is not.\ntemplate <class T, class E> struct expected_storage_base<T, E, false, true> {\n  constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {}\n  constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {}\n\n  template <class... Args,\n            detail::enable_if_t<std::is_constructible<T, Args &&...>::value> * =\n                nullptr>\n  constexpr expected_storage_base(in_place_t, Args &&...args)\n      : m_val(std::forward<Args>(args)...), m_has_val(true) {}\n\n  template <class U, class... Args,\n            detail::enable_if_t<std::is_constructible<\n                T, std::initializer_list<U> &, Args &&...>::value> * = nullptr>\n  constexpr expected_storage_base(in_place_t, std::initializer_list<U> il,\n                                  Args &&...args)\n      : m_val(il, std::forward<Args>(args)...), m_has_val(true) {}\n  template <class... Args,\n            detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =\n                nullptr>\n  constexpr explicit expected_storage_base(unexpect_t, Args &&...args)\n      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}\n\n  template <class U, class... Args,\n            detail::enable_if_t<std::is_constructible<\n                E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>\n  constexpr explicit expected_storage_base(unexpect_t,\n                                           std::initializer_list<U> il,\n                                           Args &&...args)\n      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}\n\n  ~expected_storage_base() {\n    if (m_has_val) {\n      m_val.~T();\n    }\n  }\n  union {\n    T m_val;\n    unexpected<E> m_unexpect;\n    char m_no_init;\n  };\n  bool m_has_val;\n};\n\n// `T` is `void`, `E` is trivially-destructible\ntemplate <class E> struct expected_storage_base<void, E, false, true> {\n  #if __GNUC__ <= 5\n  //no constexpr for GCC 4/5 bug\n  #else\n  TL_EXPECTED_MSVC2015_CONSTEXPR\n  #endif \n  expected_storage_base() : m_has_val(true) {}\n     \n  constexpr expected_storage_base(no_init_t) : m_val(), m_has_val(false) {}\n\n  constexpr expected_storage_base(in_place_t) : m_has_val(true) {}\n\n  template <class... Args,\n            detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =\n                nullptr>\n  constexpr explicit expected_storage_base(unexpect_t, Args &&...args)\n      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}\n\n  template <class U, class... Args,\n            detail::enable_if_t<std::is_constructible<\n                E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>\n  constexpr explicit expected_storage_base(unexpect_t,\n                                           std::initializer_list<U> il,\n                                           Args &&...args)\n      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}\n\n  ~expected_storage_base() = default;\n  struct dummy {};\n  union {\n    unexpected<E> m_unexpect;\n    dummy m_val;\n  };\n  bool m_has_val;\n};\n\n// `T` is `void`, `E` is not trivially-destructible\ntemplate <class E> struct expected_storage_base<void, E, false, false> {\n  constexpr expected_storage_base() : m_dummy(), m_has_val(true) {}\n  constexpr expected_storage_base(no_init_t) : m_dummy(), m_has_val(false) {}\n\n  constexpr expected_storage_base(in_place_t) : m_dummy(), m_has_val(true) {}\n\n  template <class... Args,\n            detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =\n                nullptr>\n  constexpr explicit expected_storage_base(unexpect_t, Args &&...args)\n      : m_unexpect(std::forward<Args>(args)...), m_has_val(false) {}\n\n  template <class U, class... Args,\n            detail::enable_if_t<std::is_constructible<\n                E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>\n  constexpr explicit expected_storage_base(unexpect_t,\n                                           std::initializer_list<U> il,\n                                           Args &&...args)\n      : m_unexpect(il, std::forward<Args>(args)...), m_has_val(false) {}\n\n  ~expected_storage_base() {\n    if (!m_has_val) {\n      m_unexpect.~unexpected<E>();\n    }\n  }\n\n  union {\n    unexpected<E> m_unexpect;\n    char m_dummy;\n  };\n  bool m_has_val;\n};\n\n// This base class provides some handy member functions which can be used in\n// further derived classes\ntemplate <class T, class E>\nstruct expected_operations_base : expected_storage_base<T, E> {\n  using expected_storage_base<T, E>::expected_storage_base;\n\n  template <class... Args> void construct(Args &&...args) noexcept {\n    new (std::addressof(this->m_val)) T(std::forward<Args>(args)...);\n    this->m_has_val = true;\n  }\n\n  template <class Rhs> void construct_with(Rhs &&rhs) noexcept {\n    new (std::addressof(this->m_val)) T(std::forward<Rhs>(rhs).get());\n    this->m_has_val = true;\n  }\n\n  template <class... Args> void construct_error(Args &&...args) noexcept {\n    new (std::addressof(this->m_unexpect))\n        unexpected<E>(std::forward<Args>(args)...);\n    this->m_has_val = false;\n  }\n\n#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED\n\n  // These assign overloads ensure that the most efficient assignment\n  // implementation is used while maintaining the strong exception guarantee.\n  // The problematic case is where rhs has a value, but *this does not.\n  //\n  // This overload handles the case where we can just copy-construct `T`\n  // directly into place without throwing.\n  template <class U = T,\n            detail::enable_if_t<std::is_nothrow_copy_constructible<U>::value>\n                * = nullptr>\n  void assign(const expected_operations_base &rhs) noexcept {\n    if (!this->m_has_val && rhs.m_has_val) {\n      geterr().~unexpected<E>();\n      construct(rhs.get());\n    } else {\n      assign_common(rhs);\n    }\n  }\n\n  // This overload handles the case where we can attempt to create a copy of\n  // `T`, then no-throw move it into place if the copy was successful.\n  template <class U = T,\n            detail::enable_if_t<!std::is_nothrow_copy_constructible<U>::value &&\n                                std::is_nothrow_move_constructible<U>::value>\n                * = nullptr>\n  void assign(const expected_operations_base &rhs) noexcept {\n    if (!this->m_has_val && rhs.m_has_val) {\n      T tmp = rhs.get();\n      geterr().~unexpected<E>();\n      construct(std::move(tmp));\n    } else {\n      assign_common(rhs);\n    }\n  }\n\n  // This overload is the worst-case, where we have to move-construct the\n  // unexpected value into temporary storage, then try to copy the T into place.\n  // If the construction succeeds, then everything is fine, but if it throws,\n  // then we move the old unexpected value back into place before rethrowing the\n  // exception.\n  template <class U = T,\n            detail::enable_if_t<!std::is_nothrow_copy_constructible<U>::value &&\n                                !std::is_nothrow_move_constructible<U>::value>\n                * = nullptr>\n  void assign(const expected_operations_base &rhs) {\n    if (!this->m_has_val && rhs.m_has_val) {\n      auto tmp = std::move(geterr());\n      geterr().~unexpected<E>();\n\n#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED\n      try {\n        construct(rhs.get());\n      } catch (...) {\n        geterr() = std::move(tmp);\n        throw;\n      }\n#else\n      construct(rhs.get());\n#endif\n    } else {\n      assign_common(rhs);\n    }\n  }\n\n  // These overloads do the same as above, but for rvalues\n  template <class U = T,\n            detail::enable_if_t<std::is_nothrow_move_constructible<U>::value>\n                * = nullptr>\n  void assign(expected_operations_base &&rhs) noexcept {\n    if (!this->m_has_val && rhs.m_has_val) {\n      geterr().~unexpected<E>();\n      construct(std::move(rhs).get());\n    } else {\n      assign_common(std::move(rhs));\n    }\n  }\n\n  template <class U = T,\n            detail::enable_if_t<!std::is_nothrow_move_constructible<U>::value>\n                * = nullptr>\n  void assign(expected_operations_base &&rhs) {\n    if (!this->m_has_val && rhs.m_has_val) {\n      auto tmp = std::move(geterr());\n      geterr().~unexpected<E>();\n#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED\n      try {\n        construct(std::move(rhs).get());\n      } catch (...) {\n        geterr() = std::move(tmp);\n        throw;\n      }\n#else\n      construct(std::move(rhs).get());\n#endif\n    } else {\n      assign_common(std::move(rhs));\n    }\n  }\n\n#else\n\n  // If exceptions are disabled then we can just copy-construct\n  void assign(const expected_operations_base &rhs) noexcept {\n    if (!this->m_has_val && rhs.m_has_val) {\n      geterr().~unexpected<E>();\n      construct(rhs.get());\n    } else {\n      assign_common(rhs);\n    }\n  }\n\n  void assign(expected_operations_base &&rhs) noexcept {\n    if (!this->m_has_val && rhs.m_has_val) {\n      geterr().~unexpected<E>();\n      construct(std::move(rhs).get());\n    } else {\n      assign_common(std::move(rhs));\n    }\n  }\n\n#endif\n\n  // The common part of move/copy assigning\n  template <class Rhs> void assign_common(Rhs &&rhs) {\n    if (this->m_has_val) {\n      if (rhs.m_has_val) {\n        get() = std::forward<Rhs>(rhs).get();\n      } else {\n        destroy_val();\n        construct_error(std::forward<Rhs>(rhs).geterr());\n      }\n    } else {\n      if (!rhs.m_has_val) {\n        geterr() = std::forward<Rhs>(rhs).geterr();\n      }\n    }\n  }\n\n  bool has_value() const { return this->m_has_val; }\n\n  TL_EXPECTED_11_CONSTEXPR T &get() & { return this->m_val; }\n  constexpr const T &get() const & { return this->m_val; }\n  TL_EXPECTED_11_CONSTEXPR T &&get() && { return std::move(this->m_val); }\n#ifndef TL_EXPECTED_NO_CONSTRR\n  constexpr const T &&get() const && { return std::move(this->m_val); }\n#endif\n\n  TL_EXPECTED_11_CONSTEXPR unexpected<E> &geterr() & {\n    return this->m_unexpect;\n  }\n  constexpr const unexpected<E> &geterr() const & { return this->m_unexpect; }\n  TL_EXPECTED_11_CONSTEXPR unexpected<E> &&geterr() && {\n    return std::move(this->m_unexpect);\n  }\n#ifndef TL_EXPECTED_NO_CONSTRR\n  constexpr const unexpected<E> &&geterr() const && {\n    return std::move(this->m_unexpect);\n  }\n#endif\n\n  TL_EXPECTED_11_CONSTEXPR void destroy_val() { get().~T(); }\n};\n\n// This base class provides some handy member functions which can be used in\n// further derived classes\ntemplate <class E>\nstruct expected_operations_base<void, E> : expected_storage_base<void, E> {\n  using expected_storage_base<void, E>::expected_storage_base;\n\n  template <class... Args> void construct() noexcept { this->m_has_val = true; }\n\n  // This function doesn't use its argument, but needs it so that code in\n  // levels above this can work independently of whether T is void\n  template <class Rhs> void construct_with(Rhs &&) noexcept {\n    this->m_has_val = true;\n  }\n\n  template <class... Args> void construct_error(Args &&...args) noexcept {\n    new (std::addressof(this->m_unexpect))\n        unexpected<E>(std::forward<Args>(args)...);\n    this->m_has_val = false;\n  }\n\n  template <class Rhs> void assign(Rhs &&rhs) noexcept {\n    if (!this->m_has_val) {\n      if (rhs.m_has_val) {\n        geterr().~unexpected<E>();\n        construct();\n      } else {\n        geterr() = std::forward<Rhs>(rhs).geterr();\n      }\n    } else {\n      if (!rhs.m_has_val) {\n        construct_error(std::forward<Rhs>(rhs).geterr());\n      }\n    }\n  }\n\n  bool has_value() const { return this->m_has_val; }\n\n  TL_EXPECTED_11_CONSTEXPR unexpected<E> &geterr() & {\n    return this->m_unexpect;\n  }\n  constexpr const unexpected<E> &geterr() const & { return this->m_unexpect; }\n  TL_EXPECTED_11_CONSTEXPR unexpected<E> &&geterr() && {\n    return std::move(this->m_unexpect);\n  }\n#ifndef TL_EXPECTED_NO_CONSTRR\n  constexpr const unexpected<E> &&geterr() const && {\n    return std::move(this->m_unexpect);\n  }\n#endif\n\n  TL_EXPECTED_11_CONSTEXPR void destroy_val() {\n    // no-op\n  }\n};\n\n// This class manages conditionally having a trivial copy constructor\n// This specialization is for when T and E are trivially copy constructible\ntemplate <class T, class E,\n          bool = is_void_or<T, TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T)>::\n              value &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value>\nstruct expected_copy_base : expected_operations_base<T, E> {\n  using expected_operations_base<T, E>::expected_operations_base;\n};\n\n// This specialization is for when T or E are not trivially copy constructible\ntemplate <class T, class E>\nstruct expected_copy_base<T, E, false> : expected_operations_base<T, E> {\n  using expected_operations_base<T, E>::expected_operations_base;\n\n  expected_copy_base() = default;\n  expected_copy_base(const expected_copy_base &rhs)\n      : expected_operations_base<T, E>(no_init) {\n    if (rhs.has_value()) {\n      this->construct_with(rhs);\n    } else {\n      this->construct_error(rhs.geterr());\n    }\n  }\n\n  expected_copy_base(expected_copy_base &&rhs) = default;\n  expected_copy_base &operator=(const expected_copy_base &rhs) = default;\n  expected_copy_base &operator=(expected_copy_base &&rhs) = default;\n};\n\n// This class manages conditionally having a trivial move constructor\n// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it\n// doesn't implement an analogue to std::is_trivially_move_constructible. We\n// have to make do with a non-trivial move constructor even if T is trivially\n// move constructible\n#ifndef TL_EXPECTED_GCC49\ntemplate <class T, class E,\n          bool = is_void_or<T, std::is_trivially_move_constructible<T>>::value\n              &&std::is_trivially_move_constructible<E>::value>\nstruct expected_move_base : expected_copy_base<T, E> {\n  using expected_copy_base<T, E>::expected_copy_base;\n};\n#else\ntemplate <class T, class E, bool = false> struct expected_move_base;\n#endif\ntemplate <class T, class E>\nstruct expected_move_base<T, E, false> : expected_copy_base<T, E> {\n  using expected_copy_base<T, E>::expected_copy_base;\n\n  expected_move_base() = default;\n  expected_move_base(const expected_move_base &rhs) = default;\n\n  expected_move_base(expected_move_base &&rhs) noexcept(\n      std::is_nothrow_move_constructible<T>::value)\n      : expected_copy_base<T, E>(no_init) {\n    if (rhs.has_value()) {\n      this->construct_with(std::move(rhs));\n    } else {\n      this->construct_error(std::move(rhs.geterr()));\n    }\n  }\n  expected_move_base &operator=(const expected_move_base &rhs) = default;\n  expected_move_base &operator=(expected_move_base &&rhs) = default;\n};\n\n// This class manages conditionally having a trivial copy assignment operator\ntemplate <class T, class E,\n          bool = is_void_or<\n              T, conjunction<TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T),\n                             TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T),\n                             TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T)>>::value\n              &&TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value\n                  &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value\n                      &&TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value>\nstruct expected_copy_assign_base : expected_move_base<T, E> {\n  using expected_move_base<T, E>::expected_move_base;\n};\n\ntemplate <class T, class E>\nstruct expected_copy_assign_base<T, E, false> : expected_move_base<T, E> {\n  using expected_move_base<T, E>::expected_move_base;\n\n  expected_copy_assign_base() = default;\n  expected_copy_assign_base(const expected_copy_assign_base &rhs) = default;\n\n  expected_copy_assign_base(expected_copy_assign_base &&rhs) = default;\n  expected_copy_assign_base &operator=(const expected_copy_assign_base &rhs) {\n    this->assign(rhs);\n    return *this;\n  }\n  expected_copy_assign_base &\n  operator=(expected_copy_assign_base &&rhs) = default;\n};\n\n// This class manages conditionally having a trivial move assignment operator\n// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it\n// doesn't implement an analogue to std::is_trivially_move_assignable. We have\n// to make do with a non-trivial move assignment operator even if T is trivially\n// move assignable\n#ifndef TL_EXPECTED_GCC49\ntemplate <class T, class E,\n          bool =\n              is_void_or<T, conjunction<std::is_trivially_destructible<T>,\n                                        std::is_trivially_move_constructible<T>,\n                                        std::is_trivially_move_assignable<T>>>::\n                  value &&std::is_trivially_destructible<E>::value\n                      &&std::is_trivially_move_constructible<E>::value\n                          &&std::is_trivially_move_assignable<E>::value>\nstruct expected_move_assign_base : expected_copy_assign_base<T, E> {\n  using expected_copy_assign_base<T, E>::expected_copy_assign_base;\n};\n#else\ntemplate <class T, class E, bool = false> struct expected_move_assign_base;\n#endif\n\ntemplate <class T, class E>\nstruct expected_move_assign_base<T, E, false>\n    : expected_copy_assign_base<T, E> {\n  using expected_copy_assign_base<T, E>::expected_copy_assign_base;\n\n  expected_move_assign_base() = default;\n  expected_move_assign_base(const expected_move_assign_base &rhs) = default;\n\n  expected_move_assign_base(expected_move_assign_base &&rhs) = default;\n\n  expected_move_assign_base &\n  operator=(const expected_move_assign_base &rhs) = default;\n\n  expected_move_assign_base &\n  operator=(expected_move_assign_base &&rhs) noexcept(\n      std::is_nothrow_move_constructible<T>::value\n          &&std::is_nothrow_move_assignable<T>::value) {\n    this->assign(std::move(rhs));\n    return *this;\n  }\n};\n\n// expected_delete_ctor_base will conditionally delete copy and move\n// constructors depending on whether T is copy/move constructible\ntemplate <class T, class E,\n          bool EnableCopy = (is_copy_constructible_or_void<T>::value &&\n                             std::is_copy_constructible<E>::value),\n          bool EnableMove = (is_move_constructible_or_void<T>::value &&\n                             std::is_move_constructible<E>::value)>\nstruct expected_delete_ctor_base {\n  expected_delete_ctor_base() = default;\n  expected_delete_ctor_base(const expected_delete_ctor_base &) = default;\n  expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default;\n  expected_delete_ctor_base &\n  operator=(const expected_delete_ctor_base &) = default;\n  expected_delete_ctor_base &\n  operator=(expected_delete_ctor_base &&) noexcept = default;\n};\n\ntemplate <class T, class E>\nstruct expected_delete_ctor_base<T, E, true, false> {\n  expected_delete_ctor_base() = default;\n  expected_delete_ctor_base(const expected_delete_ctor_base &) = default;\n  expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete;\n  expected_delete_ctor_base &\n  operator=(const expected_delete_ctor_base &) = default;\n  expected_delete_ctor_base &\n  operator=(expected_delete_ctor_base &&) noexcept = default;\n};\n\ntemplate <class T, class E>\nstruct expected_delete_ctor_base<T, E, false, true> {\n  expected_delete_ctor_base() = default;\n  expected_delete_ctor_base(const expected_delete_ctor_base &) = delete;\n  expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default;\n  expected_delete_ctor_base &\n  operator=(const expected_delete_ctor_base &) = default;\n  expected_delete_ctor_base &\n  operator=(expected_delete_ctor_base &&) noexcept = default;\n};\n\ntemplate <class T, class E>\nstruct expected_delete_ctor_base<T, E, false, false> {\n  expected_delete_ctor_base() = default;\n  expected_delete_ctor_base(const expected_delete_ctor_base &) = delete;\n  expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete;\n  expected_delete_ctor_base &\n  operator=(const expected_delete_ctor_base &) = default;\n  expected_delete_ctor_base &\n  operator=(expected_delete_ctor_base &&) noexcept = default;\n};\n\n// expected_delete_assign_base will conditionally delete copy and move\n// constructors depending on whether T and E are copy/move constructible +\n// assignable\ntemplate <class T, class E,\n          bool EnableCopy = (is_copy_constructible_or_void<T>::value &&\n                             std::is_copy_constructible<E>::value &&\n                             is_copy_assignable_or_void<T>::value &&\n                             std::is_copy_assignable<E>::value),\n          bool EnableMove = (is_move_constructible_or_void<T>::value &&\n                             std::is_move_constructible<E>::value &&\n                             is_move_assignable_or_void<T>::value &&\n                             std::is_move_assignable<E>::value)>\nstruct expected_delete_assign_base {\n  expected_delete_assign_base() = default;\n  expected_delete_assign_base(const expected_delete_assign_base &) = default;\n  expected_delete_assign_base(expected_delete_assign_base &&) noexcept =\n      default;\n  expected_delete_assign_base &\n  operator=(const expected_delete_assign_base &) = default;\n  expected_delete_assign_base &\n  operator=(expected_delete_assign_base &&) noexcept = default;\n};\n\ntemplate <class T, class E>\nstruct expected_delete_assign_base<T, E, true, false> {\n  expected_delete_assign_base() = default;\n  expected_delete_assign_base(const expected_delete_assign_base &) = default;\n  expected_delete_assign_base(expected_delete_assign_base &&) noexcept =\n      default;\n  expected_delete_assign_base &\n  operator=(const expected_delete_assign_base &) = default;\n  expected_delete_assign_base &\n  operator=(expected_delete_assign_base &&) noexcept = delete;\n};\n\ntemplate <class T, class E>\nstruct expected_delete_assign_base<T, E, false, true> {\n  expected_delete_assign_base() = default;\n  expected_delete_assign_base(const expected_delete_assign_base &) = default;\n  expected_delete_assign_base(expected_delete_assign_base &&) noexcept =\n      default;\n  expected_delete_assign_base &\n  operator=(const expected_delete_assign_base &) = delete;\n  expected_delete_assign_base &\n  operator=(expected_delete_assign_base &&) noexcept = default;\n};\n\ntemplate <class T, class E>\nstruct expected_delete_assign_base<T, E, false, false> {\n  expected_delete_assign_base() = default;\n  expected_delete_assign_base(const expected_delete_assign_base &) = default;\n  expected_delete_assign_base(expected_delete_assign_base &&) noexcept =\n      default;\n  expected_delete_assign_base &\n  operator=(const expected_delete_assign_base &) = delete;\n  expected_delete_assign_base &\n  operator=(expected_delete_assign_base &&) noexcept = delete;\n};\n\n// This is needed to be able to construct the expected_default_ctor_base which\n// follows, while still conditionally deleting the default constructor.\nstruct default_constructor_tag {\n  explicit constexpr default_constructor_tag() = default;\n};\n\n// expected_default_ctor_base will ensure that expected has a deleted default\n// consturctor if T is not default constructible.\n// This specialization is for when T is default constructible\ntemplate <class T, class E,\n          bool Enable =\n              std::is_default_constructible<T>::value || std::is_void<T>::value>\nstruct expected_default_ctor_base {\n  constexpr expected_default_ctor_base() noexcept = default;\n  constexpr expected_default_ctor_base(\n      expected_default_ctor_base const &) noexcept = default;\n  constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept =\n      default;\n  expected_default_ctor_base &\n  operator=(expected_default_ctor_base const &) noexcept = default;\n  expected_default_ctor_base &\n  operator=(expected_default_ctor_base &&) noexcept = default;\n\n  constexpr explicit expected_default_ctor_base(default_constructor_tag) {}\n};\n\n// This specialization is for when T is not default constructible\ntemplate <class T, class E> struct expected_default_ctor_base<T, E, false> {\n  constexpr expected_default_ctor_base() noexcept = delete;\n  constexpr expected_default_ctor_base(\n      expected_default_ctor_base const &) noexcept = default;\n  constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept =\n      default;\n  expected_default_ctor_base &\n  operator=(expected_default_ctor_base const &) noexcept = default;\n  expected_default_ctor_base &\n  operator=(expected_default_ctor_base &&) noexcept = default;\n\n  constexpr explicit expected_default_ctor_base(default_constructor_tag) {}\n};\n} // namespace detail\n\ntemplate <class E> class bad_expected_access : public std::exception {\npublic:\n  explicit bad_expected_access(E e) : m_val(std::move(e)) {}\n\n  virtual const char *what() const noexcept override {\n    return \"Bad expected access\";\n  }\n\n  const E &error() const & { return m_val; }\n  E &error() & { return m_val; }\n  const E &&error() const && { return std::move(m_val); }\n  E &&error() && { return std::move(m_val); }\n\nprivate:\n  E m_val;\n};\n\n/// An `expected<T, E>` object is an object that contains the storage for\n/// another object and manages the lifetime of this contained object `T`.\n/// Alternatively it could contain the storage for another unexpected object\n/// `E`. The contained object may not be initialized after the expected object\n/// has been initialized, and may not be destroyed before the expected object\n/// has been destroyed. The initialization state of the contained object is\n/// tracked by the expected object.\ntemplate <class T, class E>\nclass expected : private detail::expected_move_assign_base<T, E>,\n                 private detail::expected_delete_ctor_base<T, E>,\n                 private detail::expected_delete_assign_base<T, E>,\n                 private detail::expected_default_ctor_base<T, E> {\n  static_assert(!std::is_reference<T>::value, \"T must not be a reference\");\n  static_assert(!std::is_same<T, std::remove_cv<in_place_t>::type>::value,\n                \"T must not be in_place_t\");\n  static_assert(!std::is_same<T, std::remove_cv<unexpect_t>::type>::value,\n                \"T must not be unexpect_t\");\n  static_assert(\n      !std::is_same<T, typename std::remove_cv<unexpected<E>>::type>::value,\n      \"T must not be unexpected<E>\");\n  static_assert(!std::is_reference<E>::value, \"E must not be a reference\");\n\n  T *valptr() { return std::addressof(this->m_val); }\n  const T *valptr() const { return std::addressof(this->m_val); }\n  unexpected<E> *errptr() { return std::addressof(this->m_unexpect); }\n  const unexpected<E> *errptr() const {\n    return std::addressof(this->m_unexpect);\n  }\n\n  template <class U = T,\n            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>\n  TL_EXPECTED_11_CONSTEXPR U &val() {\n    return this->m_val;\n  }\n  TL_EXPECTED_11_CONSTEXPR unexpected<E> &err() { return this->m_unexpect; }\n\n  template <class U = T,\n            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>\n  constexpr const U &val() const {\n    return this->m_val;\n  }\n  constexpr const unexpected<E> &err() const { return this->m_unexpect; }\n\n  using impl_base = detail::expected_move_assign_base<T, E>;\n  using ctor_base = detail::expected_default_ctor_base<T, E>;\n\npublic:\n  typedef T value_type;\n  typedef E error_type;\n  typedef unexpected<E> unexpected_type;\n\n#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) &&               \\\n    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)\n  template <class F> TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & {\n    return and_then_impl(*this, std::forward<F>(f));\n  }\n  template <class F> TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && {\n    return and_then_impl(std::move(*this), std::forward<F>(f));\n  }\n  template <class F> constexpr auto and_then(F &&f) const & {\n    return and_then_impl(*this, std::forward<F>(f));\n  }\n\n#ifndef TL_EXPECTED_NO_CONSTRR\n  template <class F> constexpr auto and_then(F &&f) const && {\n    return and_then_impl(std::move(*this), std::forward<F>(f));\n  }\n#endif\n\n#else\n  template <class F>\n  TL_EXPECTED_11_CONSTEXPR auto\n  and_then(F &&f) & -> decltype(and_then_impl(std::declval<expected &>(),\n                                              std::forward<F>(f))) {\n    return and_then_impl(*this, std::forward<F>(f));\n  }\n  template <class F>\n  TL_EXPECTED_11_CONSTEXPR auto\n  and_then(F &&f) && -> decltype(and_then_impl(std::declval<expected &&>(),\n                                               std::forward<F>(f))) {\n    return and_then_impl(std::move(*this), std::forward<F>(f));\n  }\n  template <class F>\n  constexpr auto and_then(F &&f) const & -> decltype(and_then_impl(\n      std::declval<expected const &>(), std::forward<F>(f))) {\n    return and_then_impl(*this, std::forward<F>(f));\n  }\n\n#ifndef TL_EXPECTED_NO_CONSTRR\n  template <class F>\n  constexpr auto and_then(F &&f) const && -> decltype(and_then_impl(\n      std::declval<expected const &&>(), std::forward<F>(f))) {\n    return and_then_impl(std::move(*this), std::forward<F>(f));\n  }\n#endif\n#endif\n\n#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) &&               \\\n    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)\n  template <class F> TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & {\n    return expected_map_impl(*this, std::forward<F>(f));\n  }\n  template <class F> TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && {\n    return expected_map_impl(std::move(*this), std::forward<F>(f));\n  }\n  template <class F> constexpr auto map(F &&f) const & {\n    return expected_map_impl(*this, std::forward<F>(f));\n  }\n  template <class F> constexpr auto map(F &&f) const && {\n    return expected_map_impl(std::move(*this), std::forward<F>(f));\n  }\n#else\n  template <class F>\n  TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(\n      std::declval<expected &>(), std::declval<F &&>()))\n  map(F &&f) & {\n    return expected_map_impl(*this, std::forward<F>(f));\n  }\n  template <class F>\n  TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval<expected>(),\n                                                      std::declval<F &&>()))\n  map(F &&f) && {\n    return expected_map_impl(std::move(*this), std::forward<F>(f));\n  }\n  template <class F>\n  constexpr decltype(expected_map_impl(std::declval<const expected &>(),\n                                       std::declval<F &&>()))\n  map(F &&f) const & {\n    return expected_map_impl(*this, std::forward<F>(f));\n  }\n\n#ifndef TL_EXPECTED_NO_CONSTRR\n  template <class F>\n  constexpr decltype(expected_map_impl(std::declval<const expected &&>(),\n                                       std::declval<F &&>()))\n  map(F &&f) const && {\n    return expected_map_impl(std::move(*this), std::forward<F>(f));\n  }\n#endif\n#endif\n\n#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) &&               \\\n    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)\n  template <class F> TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & {\n    return expected_map_impl(*this, std::forward<F>(f));\n  }\n  template <class F> TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && {\n    return expected_map_impl(std::move(*this), std::forward<F>(f));\n  }\n  template <class F> constexpr auto transform(F &&f) const & {\n    return expected_map_impl(*this, std::forward<F>(f));\n  }\n  template <class F> constexpr auto transform(F &&f) const && {\n    return expected_map_impl(std::move(*this), std::forward<F>(f));\n  }\n#else\n  template <class F>\n  TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(\n      std::declval<expected &>(), std::declval<F &&>()))\n  transform(F &&f) & {\n    return expected_map_impl(*this, std::forward<F>(f));\n  }\n  template <class F>\n  TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval<expected>(),\n                                                      std::declval<F &&>()))\n  transform(F &&f) && {\n    return expected_map_impl(std::move(*this), std::forward<F>(f));\n  }\n  template <class F>\n  constexpr decltype(expected_map_impl(std::declval<const expected &>(),\n                                       std::declval<F &&>()))\n  transform(F &&f) const & {\n    return expected_map_impl(*this, std::forward<F>(f));\n  }\n\n#ifndef TL_EXPECTED_NO_CONSTRR\n  template <class F>\n  constexpr decltype(expected_map_impl(std::declval<const expected &&>(),\n                                       std::declval<F &&>()))\n  transform(F &&f) const && {\n    return expected_map_impl(std::move(*this), std::forward<F>(f));\n  }\n#endif\n#endif\n\n#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) &&               \\\n    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)\n  template <class F> TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & {\n    return map_error_impl(*this, std::forward<F>(f));\n  }\n  template <class F> TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && {\n    return map_error_impl(std::move(*this), std::forward<F>(f));\n  }\n  template <class F> constexpr auto map_error(F &&f) const & {\n    return map_error_impl(*this, std::forward<F>(f));\n  }\n  template <class F> constexpr auto map_error(F &&f) const && {\n    return map_error_impl(std::move(*this), std::forward<F>(f));\n  }\n#else\n  template <class F>\n  TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &>(),\n                                                   std::declval<F &&>()))\n  map_error(F &&f) & {\n    return map_error_impl(*this, std::forward<F>(f));\n  }\n  template <class F>\n  TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &&>(),\n                                                   std::declval<F &&>()))\n  map_error(F &&f) && {\n    return map_error_impl(std::move(*this), std::forward<F>(f));\n  }\n  template <class F>\n  constexpr decltype(map_error_impl(std::declval<const expected &>(),\n                                    std::declval<F &&>()))\n  map_error(F &&f) const & {\n    return map_error_impl(*this, std::forward<F>(f));\n  }\n\n#ifndef TL_EXPECTED_NO_CONSTRR\n  template <class F>\n  constexpr decltype(map_error_impl(std::declval<const expected &&>(),\n                                    std::declval<F &&>()))\n  map_error(F &&f) const && {\n    return map_error_impl(std::move(*this), std::forward<F>(f));\n  }\n#endif\n#endif\n#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) &&               \\\n    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)\n  template <class F> TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) & {\n    return map_error_impl(*this, std::forward<F>(f));\n  }\n  template <class F> TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) && {\n    return map_error_impl(std::move(*this), std::forward<F>(f));\n  }\n  template <class F> constexpr auto transform_error(F &&f) const & {\n    return map_error_impl(*this, std::forward<F>(f));\n  }\n  template <class F> constexpr auto transform_error(F &&f) const && {\n    return map_error_impl(std::move(*this), std::forward<F>(f));\n  }\n#else\n  template <class F>\n  TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &>(),\n                                                   std::declval<F &&>()))\n  transform_error(F &&f) & {\n    return map_error_impl(*this, std::forward<F>(f));\n  }\n  template <class F>\n  TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval<expected &&>(),\n                                                   std::declval<F &&>()))\n  transform_error(F &&f) && {\n    return map_error_impl(std::move(*this), std::forward<F>(f));\n  }\n  template <class F>\n  constexpr decltype(map_error_impl(std::declval<const expected &>(),\n                                    std::declval<F &&>()))\n  transform_error(F &&f) const & {\n    return map_error_impl(*this, std::forward<F>(f));\n  }\n\n#ifndef TL_EXPECTED_NO_CONSTRR\n  template <class F>\n  constexpr decltype(map_error_impl(std::declval<const expected &&>(),\n                                    std::declval<F &&>()))\n  transform_error(F &&f) const && {\n    return map_error_impl(std::move(*this), std::forward<F>(f));\n  }\n#endif\n#endif\n  template <class F> expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) & {\n    return or_else_impl(*this, std::forward<F>(f));\n  }\n\n  template <class F> expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) && {\n    return or_else_impl(std::move(*this), std::forward<F>(f));\n  }\n\n  template <class F> expected constexpr or_else(F &&f) const & {\n    return or_else_impl(*this, std::forward<F>(f));\n  }\n\n#ifndef TL_EXPECTED_NO_CONSTRR\n  template <class F> expected constexpr or_else(F &&f) const && {\n    return or_else_impl(std::move(*this), std::forward<F>(f));\n  }\n#endif\n  constexpr expected() = default;\n  constexpr expected(const expected &rhs) = default;\n  constexpr expected(expected &&rhs) = default;\n  expected &operator=(const expected &rhs) = default;\n  expected &operator=(expected &&rhs) = default;\n\n  template <class... Args,\n            detail::enable_if_t<std::is_constructible<T, Args &&...>::value> * =\n                nullptr>\n  constexpr expected(in_place_t, Args &&...args)\n      : impl_base(in_place, std::forward<Args>(args)...),\n        ctor_base(detail::default_constructor_tag{}) {}\n\n  template <class U, class... Args,\n            detail::enable_if_t<std::is_constructible<\n                T, std::initializer_list<U> &, Args &&...>::value> * = nullptr>\n  constexpr expected(in_place_t, std::initializer_list<U> il, Args &&...args)\n      : impl_base(in_place, il, std::forward<Args>(args)...),\n        ctor_base(detail::default_constructor_tag{}) {}\n\n  template <class G = E,\n            detail::enable_if_t<std::is_constructible<E, const G &>::value> * =\n                nullptr,\n            detail::enable_if_t<!std::is_convertible<const G &, E>::value> * =\n                nullptr>\n  explicit constexpr expected(const unexpected<G> &e)\n      : impl_base(unexpect, e.value()),\n        ctor_base(detail::default_constructor_tag{}) {}\n\n  template <\n      class G = E,\n      detail::enable_if_t<std::is_constructible<E, const G &>::value> * =\n          nullptr,\n      detail::enable_if_t<std::is_convertible<const G &, E>::value> * = nullptr>\n  constexpr expected(unexpected<G> const &e)\n      : impl_base(unexpect, e.value()),\n        ctor_base(detail::default_constructor_tag{}) {}\n\n  template <\n      class G = E,\n      detail::enable_if_t<std::is_constructible<E, G &&>::value> * = nullptr,\n      detail::enable_if_t<!std::is_convertible<G &&, E>::value> * = nullptr>\n  explicit constexpr expected(unexpected<G> &&e) noexcept(\n      std::is_nothrow_constructible<E, G &&>::value)\n      : impl_base(unexpect, std::move(e.value())),\n        ctor_base(detail::default_constructor_tag{}) {}\n\n  template <\n      class G = E,\n      detail::enable_if_t<std::is_constructible<E, G &&>::value> * = nullptr,\n      detail::enable_if_t<std::is_convertible<G &&, E>::value> * = nullptr>\n  constexpr expected(unexpected<G> &&e) noexcept(\n      std::is_nothrow_constructible<E, G &&>::value)\n      : impl_base(unexpect, std::move(e.value())),\n        ctor_base(detail::default_constructor_tag{}) {}\n\n  template <class... Args,\n            detail::enable_if_t<std::is_constructible<E, Args &&...>::value> * =\n                nullptr>\n  constexpr explicit expected(unexpect_t, Args &&...args)\n      : impl_base(unexpect, std::forward<Args>(args)...),\n        ctor_base(detail::default_constructor_tag{}) {}\n\n  template <class U, class... Args,\n            detail::enable_if_t<std::is_constructible<\n                E, std::initializer_list<U> &, Args &&...>::value> * = nullptr>\n  constexpr explicit expected(unexpect_t, std::initializer_list<U> il,\n                              Args &&...args)\n      : impl_base(unexpect, il, std::forward<Args>(args)...),\n        ctor_base(detail::default_constructor_tag{}) {}\n\n  template <class U, class G,\n            detail::enable_if_t<!(std::is_convertible<U const &, T>::value &&\n                                  std::is_convertible<G const &, E>::value)> * =\n                nullptr,\n            detail::expected_enable_from_other<T, E, U, G, const U &, const G &>\n                * = nullptr>\n  explicit TL_EXPECTED_11_CONSTEXPR expected(const expected<U, G> &rhs)\n      : ctor_base(detail::default_constructor_tag{}) {\n    if (rhs.has_value()) {\n      this->construct(*rhs);\n    } else {\n      this->construct_error(rhs.error());\n    }\n  }\n\n  template <class U, class G,\n            detail::enable_if_t<(std::is_convertible<U const &, T>::value &&\n                                 std::is_convertible<G const &, E>::value)> * =\n                nullptr,\n            detail::expected_enable_from_other<T, E, U, G, const U &, const G &>\n                * = nullptr>\n  TL_EXPECTED_11_CONSTEXPR expected(const expected<U, G> &rhs)\n      : ctor_base(detail::default_constructor_tag{}) {\n    if (rhs.has_value()) {\n      this->construct(*rhs);\n    } else {\n      this->construct_error(rhs.error());\n    }\n  }\n\n  template <\n      class U, class G,\n      detail::enable_if_t<!(std::is_convertible<U &&, T>::value &&\n                            std::is_convertible<G &&, E>::value)> * = nullptr,\n      detail::expected_enable_from_other<T, E, U, G, U &&, G &&> * = nullptr>\n  explicit TL_EXPECTED_11_CONSTEXPR expected(expected<U, G> &&rhs)\n      : ctor_base(detail::default_constructor_tag{}) {\n    if (rhs.has_value()) {\n      this->construct(std::move(*rhs));\n    } else {\n      this->construct_error(std::move(rhs.error()));\n    }\n  }\n\n  template <\n      class U, class G,\n      detail::enable_if_t<(std::is_convertible<U &&, T>::value &&\n                           std::is_convertible<G &&, E>::value)> * = nullptr,\n      detail::expected_enable_from_other<T, E, U, G, U &&, G &&> * = nullptr>\n  TL_EXPECTED_11_CONSTEXPR expected(expected<U, G> &&rhs)\n      : ctor_base(detail::default_constructor_tag{}) {\n    if (rhs.has_value()) {\n      this->construct(std::move(*rhs));\n    } else {\n      this->construct_error(std::move(rhs.error()));\n    }\n  }\n\n  template <\n      class U = T,\n      detail::enable_if_t<!std::is_convertible<U &&, T>::value> * = nullptr,\n      detail::expected_enable_forward_value<T, E, U> * = nullptr>\n  explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v)\n      : expected(in_place, std::forward<U>(v)) {}\n\n  template <\n      class U = T,\n      detail::enable_if_t<std::is_convertible<U &&, T>::value> * = nullptr,\n      detail::expected_enable_forward_value<T, E, U> * = nullptr>\n  TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v)\n      : expected(in_place, std::forward<U>(v)) {}\n\n  template <\n      class U = T, class G = T,\n      detail::enable_if_t<std::is_nothrow_constructible<T, U &&>::value> * =\n          nullptr,\n      detail::enable_if_t<!std::is_void<G>::value> * = nullptr,\n      detail::enable_if_t<\n          (!std::is_same<expected<T, E>, detail::decay_t<U>>::value &&\n           !detail::conjunction<std::is_scalar<T>,\n                                std::is_same<T, detail::decay_t<U>>>::value &&\n           std::is_constructible<T, U>::value &&\n           std::is_assignable<G &, U>::value &&\n           std::is_nothrow_move_constructible<E>::value)> * = nullptr>\n  expected &operator=(U &&v) {\n    if (has_value()) {\n      val() = std::forward<U>(v);\n    } else {\n      err().~unexpected<E>();\n      ::new (valptr()) T(std::forward<U>(v));\n      this->m_has_val = true;\n    }\n\n    return *this;\n  }\n\n  template <\n      class U = T, class G = T,\n      detail::enable_if_t<!std::is_nothrow_constructible<T, U &&>::value> * =\n          nullptr,\n      detail::enable_if_t<!std::is_void<U>::value> * = nullptr,\n      detail::enable_if_t<\n          (!std::is_same<expected<T, E>, detail::decay_t<U>>::value &&\n           !detail::conjunction<std::is_scalar<T>,\n                                std::is_same<T, detail::decay_t<U>>>::value &&\n           std::is_constructible<T, U>::value &&\n           std::is_assignable<G &, U>::value &&\n           std::is_nothrow_move_constructible<E>::value)> * = nullptr>\n  expected &operator=(U &&v) {\n    if (has_value()) {\n      val() = std::forward<U>(v);\n    } else {\n      auto tmp = std::move(err());\n      err().~unexpected<E>();\n\n#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED\n      try {\n        ::new (valptr()) T(std::forward<U>(v));\n        this->m_has_val = true;\n      } catch (...) {\n        err() = std::move(tmp);\n        throw;\n      }\n#else\n      ::new (valptr()) T(std::forward<U>(v));\n      this->m_has_val = true;\n#endif\n    }\n\n    return *this;\n  }\n\n  template <class G = E,\n            detail::enable_if_t<std::is_nothrow_copy_constructible<G>::value &&\n                                std::is_assignable<G &, G>::value> * = nullptr>\n  expected &operator=(const unexpected<G> &rhs) {\n    if (!has_value()) {\n      err() = rhs;\n    } else {\n      this->destroy_val();\n      ::new (errptr()) unexpected<E>(rhs);\n      this->m_has_val = false;\n    }\n\n    return *this;\n  }\n\n  template <class G = E,\n            detail::enable_if_t<std::is_nothrow_move_constructible<G>::value &&\n                                std::is_move_assignable<G>::value> * = nullptr>\n  expected &operator=(unexpected<G> &&rhs) noexcept {\n    if (!has_value()) {\n      err() = std::move(rhs);\n    } else {\n      this->destroy_val();\n      ::new (errptr()) unexpected<E>(std::move(rhs));\n      this->m_has_val = false;\n    }\n\n    return *this;\n  }\n\n  template <class... Args, detail::enable_if_t<std::is_nothrow_constructible<\n                               T, Args &&...>::value> * = nullptr>\n  void emplace(Args &&...args) {\n    if (has_value()) {\n      val().~T();\n    } else {\n      err().~unexpected<E>();\n      this->m_has_val = true;\n    }\n    ::new (valptr()) T(std::forward<Args>(args)...);\n  }\n\n  template <class... Args, detail::enable_if_t<!std::is_nothrow_constructible<\n                               T, Args &&...>::value> * = nullptr>\n  void emplace(Args &&...args) {\n    if (has_value()) {\n      val().~T();\n      ::new (valptr()) T(std::forward<Args>(args)...);\n    } else {\n      auto tmp = std::move(err());\n      err().~unexpected<E>();\n\n#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED\n      try {\n        ::new (valptr()) T(std::forward<Args>(args)...);\n        this->m_has_val = true;\n      } catch (...) {\n        err() = std::move(tmp);\n        throw;\n      }\n#else\n      ::new (valptr()) T(std::forward<Args>(args)...);\n      this->m_has_val = true;\n#endif\n    }\n  }\n\n  template <class U, class... Args,\n            detail::enable_if_t<std::is_nothrow_constructible<\n                T, std::initializer_list<U> &, Args &&...>::value> * = nullptr>\n  void emplace(std::initializer_list<U> il, Args &&...args) {\n    if (has_value()) {\n      T t(il, std::forward<Args>(args)...);\n      val() = std::move(t);\n    } else {\n      err().~unexpected<E>();\n      ::new (valptr()) T(il, std::forward<Args>(args)...);\n      this->m_has_val = true;\n    }\n  }\n\n  template <class U, class... Args,\n            detail::enable_if_t<!std::is_nothrow_constructible<\n                T, std::initializer_list<U> &, Args &&...>::value> * = nullptr>\n  void emplace(std::initializer_list<U> il, Args &&...args) {\n    if (has_value()) {\n      T t(il, std::forward<Args>(args)...);\n      val() = std::move(t);\n    } else {\n      auto tmp = std::move(err());\n      err().~unexpected<E>();\n\n#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED\n      try {\n        ::new (valptr()) T(il, std::forward<Args>(args)...);\n        this->m_has_val = true;\n      } catch (...) {\n        err() = std::move(tmp);\n        throw;\n      }\n#else\n      ::new (valptr()) T(il, std::forward<Args>(args)...);\n      this->m_has_val = true;\n#endif\n    }\n  }\n\nprivate:\n  using t_is_void = std::true_type;\n  using t_is_not_void = std::false_type;\n  using t_is_nothrow_move_constructible = std::true_type;\n  using move_constructing_t_can_throw = std::false_type;\n  using e_is_nothrow_move_constructible = std::true_type;\n  using move_constructing_e_can_throw = std::false_type;\n\n  void swap_where_both_have_value(expected & /*rhs*/, t_is_void) noexcept {\n    // swapping void is a no-op\n  }\n\n  void swap_where_both_have_value(expected &rhs, t_is_not_void) {\n    using std::swap;\n    swap(val(), rhs.val());\n  }\n\n  void swap_where_only_one_has_value(expected &rhs, t_is_void) noexcept(\n      std::is_nothrow_move_constructible<E>::value) {\n    ::new (errptr()) unexpected_type(std::move(rhs.err()));\n    rhs.err().~unexpected_type();\n    std::swap(this->m_has_val, rhs.m_has_val);\n  }\n\n  void swap_where_only_one_has_value(expected &rhs, t_is_not_void) {\n    swap_where_only_one_has_value_and_t_is_not_void(\n        rhs, typename std::is_nothrow_move_constructible<T>::type{},\n        typename std::is_nothrow_move_constructible<E>::type{});\n  }\n\n  void swap_where_only_one_has_value_and_t_is_not_void(\n      expected &rhs, t_is_nothrow_move_constructible,\n      e_is_nothrow_move_constructible) noexcept {\n    auto temp = std::move(val());\n    val().~T();\n    ::new (errptr()) unexpected_type(std::move(rhs.err()));\n    rhs.err().~unexpected_type();\n    ::new (rhs.valptr()) T(std::move(temp));\n    std::swap(this->m_has_val, rhs.m_has_val);\n  }\n\n  void swap_where_only_one_has_value_and_t_is_not_void(\n      expected &rhs, t_is_nothrow_move_constructible,\n      move_constructing_e_can_throw) {\n    auto temp = std::move(val());\n    val().~T();\n#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED\n    try {\n      ::new (errptr()) unexpected_type(std::move(rhs.err()));\n      rhs.err().~unexpected_type();\n      ::new (rhs.valptr()) T(std::move(temp));\n      std::swap(this->m_has_val, rhs.m_has_val);\n    } catch (...) {\n      val() = std::move(temp);\n      throw;\n    }\n#else\n    ::new (errptr()) unexpected_type(std::move(rhs.err()));\n    rhs.err().~unexpected_type();\n    ::new (rhs.valptr()) T(std::move(temp));\n    std::swap(this->m_has_val, rhs.m_has_val);\n#endif\n  }\n\n  void swap_where_only_one_has_value_and_t_is_not_void(\n      expected &rhs, move_constructing_t_can_throw,\n      e_is_nothrow_move_constructible) {\n    auto temp = std::move(rhs.err());\n    rhs.err().~unexpected_type();\n#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED\n    try {\n      ::new (rhs.valptr()) T(std::move(val()));\n      val().~T();\n      ::new (errptr()) unexpected_type(std::move(temp));\n      std::swap(this->m_has_val, rhs.m_has_val);\n    } catch (...) {\n      rhs.err() = std::move(temp);\n      throw;\n    }\n#else\n    ::new (rhs.valptr()) T(std::move(val()));\n    val().~T();\n    ::new (errptr()) unexpected_type(std::move(temp));\n    std::swap(this->m_has_val, rhs.m_has_val);\n#endif\n  }\n\npublic:\n  template <class OT = T, class OE = E>\n  detail::enable_if_t<detail::is_swappable<OT>::value &&\n                      detail::is_swappable<OE>::value &&\n                      (std::is_nothrow_move_constructible<OT>::value ||\n                       std::is_nothrow_move_constructible<OE>::value)>\n  swap(expected &rhs) noexcept(\n      std::is_nothrow_move_constructible<T>::value\n          &&detail::is_nothrow_swappable<T>::value\n              &&std::is_nothrow_move_constructible<E>::value\n                  &&detail::is_nothrow_swappable<E>::value) {\n    if (has_value() && rhs.has_value()) {\n      swap_where_both_have_value(rhs, typename std::is_void<T>::type{});\n    } else if (!has_value() && rhs.has_value()) {\n      rhs.swap(*this);\n    } else if (has_value()) {\n      swap_where_only_one_has_value(rhs, typename std::is_void<T>::type{});\n    } else {\n      using std::swap;\n      swap(err(), rhs.err());\n    }\n  }\n\n  constexpr const T *operator->() const {\n    TL_ASSERT(has_value());\n    return valptr();\n  }\n  TL_EXPECTED_11_CONSTEXPR T *operator->() {\n    TL_ASSERT(has_value());\n    return valptr();\n  }\n\n  template <class U = T,\n            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>\n  constexpr const U &operator*() const & {\n    TL_ASSERT(has_value());\n    return val();\n  }\n  template <class U = T,\n            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>\n  TL_EXPECTED_11_CONSTEXPR U &operator*() & {\n    TL_ASSERT(has_value());\n    return val();\n  }\n  template <class U = T,\n            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>\n  constexpr const U &&operator*() const && {\n    TL_ASSERT(has_value());\n    return std::move(val());\n  }\n  template <class U = T,\n            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>\n  TL_EXPECTED_11_CONSTEXPR U &&operator*() && {\n    TL_ASSERT(has_value());\n    return std::move(val());\n  }\n\n  constexpr bool has_value() const noexcept { return this->m_has_val; }\n  constexpr explicit operator bool() const noexcept { return this->m_has_val; }\n\n  template <class U = T,\n            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>\n  TL_EXPECTED_11_CONSTEXPR const U &value() const & {\n    if (!has_value())\n      detail::throw_exception(bad_expected_access<E>(err().value()));\n    return val();\n  }\n  template <class U = T,\n            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>\n  TL_EXPECTED_11_CONSTEXPR U &value() & {\n    if (!has_value())\n      detail::throw_exception(bad_expected_access<E>(err().value()));\n    return val();\n  }\n  template <class U = T,\n            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>\n  TL_EXPECTED_11_CONSTEXPR const U &&value() const && {\n    if (!has_value())\n      detail::throw_exception(bad_expected_access<E>(std::move(err()).value()));\n    return std::move(val());\n  }\n  template <class U = T,\n            detail::enable_if_t<!std::is_void<U>::value> * = nullptr>\n  TL_EXPECTED_11_CONSTEXPR U &&value() && {\n    if (!has_value())\n      detail::throw_exception(bad_expected_access<E>(std::move(err()).value()));\n    return std::move(val());\n  }\n\n  constexpr const E &error() const & {\n    TL_ASSERT(!has_value());\n    return err().value();\n  }\n  TL_EXPECTED_11_CONSTEXPR E &error() & {\n    TL_ASSERT(!has_value());\n    return err().value();\n  }\n  constexpr const E &&error() const && {\n    TL_ASSERT(!has_value());\n    return std::move(err().value());\n  }\n  TL_EXPECTED_11_CONSTEXPR E &&error() && {\n    TL_ASSERT(!has_value());\n    return std::move(err().value());\n  }\n\n  template <class U> constexpr T value_or(U &&v) const & {\n    static_assert(std::is_copy_constructible<T>::value &&\n                      std::is_convertible<U &&, T>::value,\n                  \"T must be copy-constructible and convertible to from U&&\");\n    return bool(*this) ? **this : static_cast<T>(std::forward<U>(v));\n  }\n  template <class U> TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && {\n    static_assert(std::is_move_constructible<T>::value &&\n                      std::is_convertible<U &&, T>::value,\n                  \"T must be move-constructible and convertible to from U&&\");\n    return bool(*this) ? std::move(**this) : static_cast<T>(std::forward<U>(v));\n  }\n};\n\nnamespace detail {\ntemplate <class Exp> using exp_t = typename detail::decay_t<Exp>::value_type;\ntemplate <class Exp> using err_t = typename detail::decay_t<Exp>::error_type;\ntemplate <class Exp, class Ret> using ret_t = expected<Ret, err_t<Exp>>;\n\n#ifdef TL_EXPECTED_CXX14\ntemplate <class Exp, class F,\n          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              *std::declval<Exp>()))>\nconstexpr auto and_then_impl(Exp &&exp, F &&f) {\n  static_assert(detail::is_expected<Ret>::value, \"F must return an expected\");\n\n  return exp.has_value()\n             ? detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp))\n             : Ret(unexpect, std::forward<Exp>(exp).error());\n}\n\ntemplate <class Exp, class F,\n          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>()))>\nconstexpr auto and_then_impl(Exp &&exp, F &&f) {\n  static_assert(detail::is_expected<Ret>::value, \"F must return an expected\");\n\n  return exp.has_value() ? detail::invoke(std::forward<F>(f))\n                         : Ret(unexpect, std::forward<Exp>(exp).error());\n}\n#else\ntemplate <class> struct TC;\ntemplate <class Exp, class F,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              *std::declval<Exp>())),\n          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr>\nauto and_then_impl(Exp &&exp, F &&f) -> Ret {\n  static_assert(detail::is_expected<Ret>::value, \"F must return an expected\");\n\n  return exp.has_value()\n             ? detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp))\n             : Ret(unexpect, std::forward<Exp>(exp).error());\n}\n\ntemplate <class Exp, class F,\n          class Ret = decltype(detail::invoke(std::declval<F>())),\n          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr>\nconstexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret {\n  static_assert(detail::is_expected<Ret>::value, \"F must return an expected\");\n\n  return exp.has_value() ? detail::invoke(std::forward<F>(f))\n                         : Ret(unexpect, std::forward<Exp>(exp).error());\n}\n#endif\n\n#ifdef TL_EXPECTED_CXX14\ntemplate <class Exp, class F,\n          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              *std::declval<Exp>())),\n          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>\nconstexpr auto expected_map_impl(Exp &&exp, F &&f) {\n  using result = ret_t<Exp, detail::decay_t<Ret>>;\n  return exp.has_value() ? result(detail::invoke(std::forward<F>(f),\n                                                 *std::forward<Exp>(exp)))\n                         : result(unexpect, std::forward<Exp>(exp).error());\n}\n\ntemplate <class Exp, class F,\n          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              *std::declval<Exp>())),\n          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>\nauto expected_map_impl(Exp &&exp, F &&f) {\n  using result = expected<void, err_t<Exp>>;\n  if (exp.has_value()) {\n    detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp));\n    return result();\n  }\n\n  return result(unexpect, std::forward<Exp>(exp).error());\n}\n\ntemplate <class Exp, class F,\n          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>())),\n          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>\nconstexpr auto expected_map_impl(Exp &&exp, F &&f) {\n  using result = ret_t<Exp, detail::decay_t<Ret>>;\n  return exp.has_value() ? result(detail::invoke(std::forward<F>(f)))\n                         : result(unexpect, std::forward<Exp>(exp).error());\n}\n\ntemplate <class Exp, class F,\n          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>())),\n          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>\nauto expected_map_impl(Exp &&exp, F &&f) {\n  using result = expected<void, err_t<Exp>>;\n  if (exp.has_value()) {\n    detail::invoke(std::forward<F>(f));\n    return result();\n  }\n\n  return result(unexpect, std::forward<Exp>(exp).error());\n}\n#else\ntemplate <class Exp, class F,\n          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              *std::declval<Exp>())),\n          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>\n\nconstexpr auto expected_map_impl(Exp &&exp, F &&f)\n    -> ret_t<Exp, detail::decay_t<Ret>> {\n  using result = ret_t<Exp, detail::decay_t<Ret>>;\n\n  return exp.has_value() ? result(detail::invoke(std::forward<F>(f),\n                                                 *std::forward<Exp>(exp)))\n                         : result(unexpect, std::forward<Exp>(exp).error());\n}\n\ntemplate <class Exp, class F,\n          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              *std::declval<Exp>())),\n          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>\n\nauto expected_map_impl(Exp &&exp, F &&f) -> expected<void, err_t<Exp>> {\n  if (exp.has_value()) {\n    detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp));\n    return {};\n  }\n\n  return unexpected<err_t<Exp>>(std::forward<Exp>(exp).error());\n}\n\ntemplate <class Exp, class F,\n          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>())),\n          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>\n\nconstexpr auto expected_map_impl(Exp &&exp, F &&f)\n    -> ret_t<Exp, detail::decay_t<Ret>> {\n  using result = ret_t<Exp, detail::decay_t<Ret>>;\n\n  return exp.has_value() ? result(detail::invoke(std::forward<F>(f)))\n                         : result(unexpect, std::forward<Exp>(exp).error());\n}\n\ntemplate <class Exp, class F,\n          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>())),\n          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>\n\nauto expected_map_impl(Exp &&exp, F &&f) -> expected<void, err_t<Exp>> {\n  if (exp.has_value()) {\n    detail::invoke(std::forward<F>(f));\n    return {};\n  }\n\n  return unexpected<err_t<Exp>>(std::forward<Exp>(exp).error());\n}\n#endif\n\n#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) &&               \\\n    !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55)\ntemplate <class Exp, class F,\n          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              std::declval<Exp>().error())),\n          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>\nconstexpr auto map_error_impl(Exp &&exp, F &&f) {\n  using result = expected<exp_t<Exp>, detail::decay_t<Ret>>;\n  return exp.has_value()\n             ? result(*std::forward<Exp>(exp))\n             : result(unexpect, detail::invoke(std::forward<F>(f),\n                                               std::forward<Exp>(exp).error()));\n}\ntemplate <class Exp, class F,\n          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              std::declval<Exp>().error())),\n          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>\nauto map_error_impl(Exp &&exp, F &&f) {\n  using result = expected<exp_t<Exp>, monostate>;\n  if (exp.has_value()) {\n    return result(*std::forward<Exp>(exp));\n  }\n\n  detail::invoke(std::forward<F>(f), std::forward<Exp>(exp).error());\n  return result(unexpect, monostate{});\n}\ntemplate <class Exp, class F,\n          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              std::declval<Exp>().error())),\n          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>\nconstexpr auto map_error_impl(Exp &&exp, F &&f) {\n  using result = expected<exp_t<Exp>, detail::decay_t<Ret>>;\n  return exp.has_value()\n             ? result()\n             : result(unexpect, detail::invoke(std::forward<F>(f),\n                                               std::forward<Exp>(exp).error()));\n}\ntemplate <class Exp, class F,\n          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              std::declval<Exp>().error())),\n          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>\nauto map_error_impl(Exp &&exp, F &&f) {\n  using result = expected<exp_t<Exp>, monostate>;\n  if (exp.has_value()) {\n    return result();\n  }\n\n  detail::invoke(std::forward<F>(f), std::forward<Exp>(exp).error());\n  return result(unexpect, monostate{});\n}\n#else\ntemplate <class Exp, class F,\n          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              std::declval<Exp>().error())),\n          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>\nconstexpr auto map_error_impl(Exp &&exp, F &&f)\n    -> expected<exp_t<Exp>, detail::decay_t<Ret>> {\n  using result = expected<exp_t<Exp>, detail::decay_t<Ret>>;\n\n  return exp.has_value()\n             ? result(*std::forward<Exp>(exp))\n             : result(unexpect, detail::invoke(std::forward<F>(f),\n                                               std::forward<Exp>(exp).error()));\n}\n\ntemplate <class Exp, class F,\n          detail::enable_if_t<!std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              std::declval<Exp>().error())),\n          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>\nauto map_error_impl(Exp &&exp, F &&f) -> expected<exp_t<Exp>, monostate> {\n  using result = expected<exp_t<Exp>, monostate>;\n  if (exp.has_value()) {\n    return result(*std::forward<Exp>(exp));\n  }\n\n  detail::invoke(std::forward<F>(f), std::forward<Exp>(exp).error());\n  return result(unexpect, monostate{});\n}\n\ntemplate <class Exp, class F,\n          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              std::declval<Exp>().error())),\n          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>\nconstexpr auto map_error_impl(Exp &&exp, F &&f)\n    -> expected<exp_t<Exp>, detail::decay_t<Ret>> {\n  using result = expected<exp_t<Exp>, detail::decay_t<Ret>>;\n\n  return exp.has_value()\n             ? result()\n             : result(unexpect, detail::invoke(std::forward<F>(f),\n                                               std::forward<Exp>(exp).error()));\n}\n\ntemplate <class Exp, class F,\n          detail::enable_if_t<std::is_void<exp_t<Exp>>::value> * = nullptr,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              std::declval<Exp>().error())),\n          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>\nauto map_error_impl(Exp &&exp, F &&f) -> expected<exp_t<Exp>, monostate> {\n  using result = expected<exp_t<Exp>, monostate>;\n  if (exp.has_value()) {\n    return result();\n  }\n\n  detail::invoke(std::forward<F>(f), std::forward<Exp>(exp).error());\n  return result(unexpect, monostate{});\n}\n#endif\n\n#ifdef TL_EXPECTED_CXX14\ntemplate <class Exp, class F,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              std::declval<Exp>().error())),\n          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>\nconstexpr auto or_else_impl(Exp &&exp, F &&f) {\n  static_assert(detail::is_expected<Ret>::value, \"F must return an expected\");\n  return exp.has_value() ? std::forward<Exp>(exp)\n                         : detail::invoke(std::forward<F>(f),\n                                          std::forward<Exp>(exp).error());\n}\n\ntemplate <class Exp, class F,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              std::declval<Exp>().error())),\n          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>\ndetail::decay_t<Exp> or_else_impl(Exp &&exp, F &&f) {\n  return exp.has_value() ? std::forward<Exp>(exp)\n                         : (detail::invoke(std::forward<F>(f),\n                                           std::forward<Exp>(exp).error()),\n                            std::forward<Exp>(exp));\n}\n#else\ntemplate <class Exp, class F,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              std::declval<Exp>().error())),\n          detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>\nauto or_else_impl(Exp &&exp, F &&f) -> Ret {\n  static_assert(detail::is_expected<Ret>::value, \"F must return an expected\");\n  return exp.has_value() ? std::forward<Exp>(exp)\n                         : detail::invoke(std::forward<F>(f),\n                                          std::forward<Exp>(exp).error());\n}\n\ntemplate <class Exp, class F,\n          class Ret = decltype(detail::invoke(std::declval<F>(),\n                                              std::declval<Exp>().error())),\n          detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>\ndetail::decay_t<Exp> or_else_impl(Exp &&exp, F &&f) {\n  return exp.has_value() ? std::forward<Exp>(exp)\n                         : (detail::invoke(std::forward<F>(f),\n                                           std::forward<Exp>(exp).error()),\n                            std::forward<Exp>(exp));\n}\n#endif\n} // namespace detail\n\ntemplate <class T, class E, class U, class F>\nconstexpr bool operator==(const expected<T, E> &lhs,\n                          const expected<U, F> &rhs) {\n  return (lhs.has_value() != rhs.has_value())\n             ? false\n             : (!lhs.has_value() ? lhs.error() == rhs.error() : *lhs == *rhs);\n}\ntemplate <class T, class E, class U, class F>\nconstexpr bool operator!=(const expected<T, E> &lhs,\n                          const expected<U, F> &rhs) {\n  return (lhs.has_value() != rhs.has_value())\n             ? true\n             : (!lhs.has_value() ? lhs.error() != rhs.error() : *lhs != *rhs);\n}\ntemplate <class E, class F>\nconstexpr bool operator==(const expected<void, E> &lhs,\n                          const expected<void, F> &rhs) {\n  return (lhs.has_value() != rhs.has_value())\n             ? false\n             : (!lhs.has_value() ? lhs.error() == rhs.error() : true);\n}\ntemplate <class E, class F>\nconstexpr bool operator!=(const expected<void, E> &lhs,\n                          const expected<void, F> &rhs) {\n  return (lhs.has_value() != rhs.has_value())\n             ? true\n             : (!lhs.has_value() ? lhs.error() == rhs.error() : false);\n}\n\ntemplate <class T, class E, class U>\nconstexpr bool operator==(const expected<T, E> &x, const U &v) {\n  return x.has_value() ? *x == v : false;\n}\ntemplate <class T, class E, class U>\nconstexpr bool operator==(const U &v, const expected<T, E> &x) {\n  return x.has_value() ? *x == v : false;\n}\ntemplate <class T, class E, class U>\nconstexpr bool operator!=(const expected<T, E> &x, const U &v) {\n  return x.has_value() ? *x != v : true;\n}\ntemplate <class T, class E, class U>\nconstexpr bool operator!=(const U &v, const expected<T, E> &x) {\n  return x.has_value() ? *x != v : true;\n}\n\ntemplate <class T, class E>\nconstexpr bool operator==(const expected<T, E> &x, const unexpected<E> &e) {\n  return x.has_value() ? false : x.error() == e.value();\n}\ntemplate <class T, class E>\nconstexpr bool operator==(const unexpected<E> &e, const expected<T, E> &x) {\n  return x.has_value() ? false : x.error() == e.value();\n}\ntemplate <class T, class E>\nconstexpr bool operator!=(const expected<T, E> &x, const unexpected<E> &e) {\n  return x.has_value() ? true : x.error() != e.value();\n}\ntemplate <class T, class E>\nconstexpr bool operator!=(const unexpected<E> &e, const expected<T, E> &x) {\n  return x.has_value() ? true : x.error() != e.value();\n}\n\ntemplate <class T, class E,\n          detail::enable_if_t<(std::is_void<T>::value ||\n                               std::is_move_constructible<T>::value) &&\n                              detail::is_swappable<T>::value &&\n                              std::is_move_constructible<E>::value &&\n                              detail::is_swappable<E>::value> * = nullptr>\nvoid swap(expected<T, E> &lhs,\n          expected<T, E> &rhs) noexcept(noexcept(lhs.swap(rhs))) {\n  lhs.swap(rhs);\n}\n} // namespace tl\n\n#endif\n"
  },
  {
    "path": "dependencies/tl_expected/expected/tests/assertions.cpp",
    "content": "#include <catch2/catch.hpp>\n#include <stdexcept>\n\n#define TL_ASSERT(cond) if (!(cond)) { throw std::runtime_error(std::string(\"assertion failure\")); }\n\n#include <tl/expected.hpp>\n\nTEST_CASE(\"Assertions\", \"[assertions]\") {\n  tl::expected<int,int> o1 = 42;\n  REQUIRE_THROWS_WITH(o1.error(), \"assertion failure\");\n\n  tl::expected<int,int> o2 {tl::unexpect, 0};\n  REQUIRE_THROWS_WITH(*o2, \"assertion failure\");\n\n  struct foo { int bar; };\n  tl::expected<struct foo,int> o3 {tl::unexpect, 0};\n  REQUIRE_THROWS_WITH(o3->bar, \"assertion failure\");\n}\n"
  },
  {
    "path": "dependencies/tl_expected/expected/tests/assignment.cpp",
    "content": "#include <catch2/catch.hpp>\n#include <tl/expected.hpp>\n\nTEST_CASE(\"Simple assignment\", \"[assignment.simple]\") {\n  tl::expected<int, int> e1 = 42;\n  tl::expected<int, int> e2 = 17;\n  tl::expected<int, int> e3 = 21;\n  tl::expected<int, int> e4 = tl::make_unexpected(42);\n  tl::expected<int, int> e5 = tl::make_unexpected(17);\n  tl::expected<int, int> e6 = tl::make_unexpected(21);\n\n  e1 = e2;\n  REQUIRE(e1);\n  REQUIRE(*e1 == 17);\n  REQUIRE(e2);\n  REQUIRE(*e2 == 17);\n\n  e1 = std::move(e2);\n  REQUIRE(e1);\n  REQUIRE(*e1 == 17);\n  REQUIRE(e2);\n  REQUIRE(*e2 == 17);\n\n  e1 = 42;\n  REQUIRE(e1);\n  REQUIRE(*e1 == 42);\n\n  auto unex = tl::make_unexpected(12);\n  e1 = unex;\n  REQUIRE(!e1);\n  REQUIRE(e1.error() == 12);\n\n  e1 = tl::make_unexpected(42);\n  REQUIRE(!e1);\n  REQUIRE(e1.error() == 42);\n\n  e1 = e3;\n  REQUIRE(e1);\n  REQUIRE(*e1 == 21);\n\n  e4 = e5;\n  REQUIRE(!e4);\n  REQUIRE(e4.error() == 17);\n\n  e4 = std::move(e6);\n  REQUIRE(!e4);\n  REQUIRE(e4.error() == 21);\n\n  e4 = e1;\n  REQUIRE(e4);\n  REQUIRE(*e4 == 21);\n}\n\nTEST_CASE(\"Assignment deletion\", \"[assignment.deletion]\") {\n  struct has_all {\n    has_all() = default;\n    has_all(const has_all &) = default;\n    has_all(has_all &&) noexcept = default;\n    has_all &operator=(const has_all &) = default;\n  };\n\n  tl::expected<has_all, has_all> e1 = {};\n  tl::expected<has_all, has_all> e2 = {};\n  e1 = e2;\n\n  struct except_move {\n    except_move() = default;\n    except_move(const except_move &) = default;\n    except_move(except_move &&) noexcept(false){};\n    except_move &operator=(const except_move &) = default;\n  };\n  tl::expected<except_move, except_move> e3 = {};\n  tl::expected<except_move, except_move> e4 = {};\n  // e3 = e4; should not compile\n}\n"
  },
  {
    "path": "dependencies/tl_expected/expected/tests/bases.cpp",
    "content": "#include <catch2/catch.hpp>\n#include <tl/expected.hpp>\n\n#include <string>\n\n// Old versions of GCC don't have the correct trait names. Could fix them up if needs be.\n#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 &&              \\\n     !defined(__clang__))\n// nothing for now\n#else\nTEST_CASE(\"Triviality\", \"[bases.triviality]\") {\n    REQUIRE(std::is_trivially_copy_constructible<tl::expected<int,int>>::value);\n    REQUIRE(std::is_trivially_copy_assignable<tl::expected<int,int>>::value);\n    REQUIRE(std::is_trivially_move_constructible<tl::expected<int,int>>::value);\n    REQUIRE(std::is_trivially_move_assignable<tl::expected<int,int>>::value);\n    REQUIRE(std::is_trivially_destructible<tl::expected<int,int>>::value);\n\n    REQUIRE(std::is_trivially_copy_constructible<tl::expected<void,int>>::value);\n    REQUIRE(std::is_trivially_move_constructible<tl::expected<void,int>>::value);\n    REQUIRE(std::is_trivially_destructible<tl::expected<void,int>>::value);\n\n\n    {\n        struct T {\n            T(const T&) = default;\n            T(T&&) = default;\n            T& operator=(const T&) = default;\n            T& operator=(T&&) = default;\n            ~T() = default;\n        };\n        REQUIRE(std::is_trivially_copy_constructible<tl::expected<T,int>>::value);\n        REQUIRE(std::is_trivially_copy_assignable<tl::expected<T,int>>::value);\n        REQUIRE(std::is_trivially_move_constructible<tl::expected<T,int>>::value);\n        REQUIRE(std::is_trivially_move_assignable<tl::expected<T,int>>::value);\n        REQUIRE(std::is_trivially_destructible<tl::expected<T,int>>::value);\n    }\n\n    {\n        struct T {\n            T(const T&){}\n            T(T&&) {}\n            T& operator=(const T&) { return *this; }\n            T& operator=(T&&) { return *this; }\n            ~T(){}\n        };\n        REQUIRE(!std::is_trivially_copy_constructible<tl::expected<T,int>>::value);\n        REQUIRE(!std::is_trivially_copy_assignable<tl::expected<T,int>>::value);\n        REQUIRE(!std::is_trivially_move_constructible<tl::expected<T,int>>::value);\n        REQUIRE(!std::is_trivially_move_assignable<tl::expected<T,int>>::value);\n        REQUIRE(!std::is_trivially_destructible<tl::expected<T,int>>::value);\n    }\n\n}\n\nTEST_CASE(\"Deletion\", \"[bases.deletion]\") {\n    REQUIRE(std::is_copy_constructible<tl::expected<int,int>>::value);\n    REQUIRE(std::is_copy_assignable<tl::expected<int,int>>::value);\n    REQUIRE(std::is_move_constructible<tl::expected<int,int>>::value);\n    REQUIRE(std::is_move_assignable<tl::expected<int,int>>::value);\n    REQUIRE(std::is_destructible<tl::expected<int,int>>::value);\n\n    {\n        struct T {\n            T()=default;\n        };\n        REQUIRE(std::is_default_constructible<tl::expected<T,int>>::value);\n    }\n\n    {\n        struct T {\n            T(int){}\n        };\n        REQUIRE(!std::is_default_constructible<tl::expected<T,int>>::value);\n    }\n\n    {\n        struct T {\n            T(const T&) = default;\n            T(T&&) = default;\n            T& operator=(const T&) = default;\n            T& operator=(T&&) = default;\n            ~T() = default;\n        };\n        REQUIRE(std::is_copy_constructible<tl::expected<T,int>>::value);\n        REQUIRE(std::is_copy_assignable<tl::expected<T,int>>::value);\n        REQUIRE(std::is_move_constructible<tl::expected<T,int>>::value);\n        REQUIRE(std::is_move_assignable<tl::expected<T,int>>::value);\n        REQUIRE(std::is_destructible<tl::expected<T,int>>::value);\n    }\n\n    {\n        struct T {\n            T(const T&)=delete;\n            T(T&&)=delete;\n            T& operator=(const T&)=delete;\n            T& operator=(T&&)=delete;\n        };\n        REQUIRE(!std::is_copy_constructible<tl::expected<T,int>>::value);\n        REQUIRE(!std::is_copy_assignable<tl::expected<T,int>>::value);\n        REQUIRE(!std::is_move_constructible<tl::expected<T,int>>::value);\n        REQUIRE(!std::is_move_assignable<tl::expected<T,int>>::value);\n    }\n\n    {\n        struct T {\n            T(const T&)=delete;\n            T(T&&)=default;\n            T& operator=(const T&)=delete;\n            T& operator=(T&&)=default;\n        };\n        REQUIRE(!std::is_copy_constructible<tl::expected<T,int>>::value);\n        REQUIRE(!std::is_copy_assignable<tl::expected<T,int>>::value);\n        REQUIRE(std::is_move_constructible<tl::expected<T,int>>::value);\n        REQUIRE(std::is_move_assignable<tl::expected<T,int>>::value);\n    }\n\n    {\n        struct T {\n            T(const T&)=default;\n            T(T&&)=delete;\n            T& operator=(const T&)=default;\n            T& operator=(T&&)=delete;\n        };\n        REQUIRE(std::is_copy_constructible<tl::expected<T,int>>::value);\n        REQUIRE(std::is_copy_assignable<tl::expected<T,int>>::value);\n    }\n\n\t{\n\t\ttl::expected<int, int> e;\n\t\tREQUIRE(std::is_default_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_assignable<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_assignable<decltype(e)>::value);\n\t\tREQUIRE(TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value);\n\t\tREQUIRE(TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value);\n#\tif !defined(TL_EXPECTED_GCC49)\n\t\tREQUIRE(std::is_trivially_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_trivially_move_assignable<decltype(e)>::value);\n#\tendif\n\t}\n\n\t{\n\t\ttl::expected<int, std::string> e;\n\t\tREQUIRE(std::is_default_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_assignable<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_assignable<decltype(e)>::value);\n\t\tREQUIRE(!TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value);\n\t\tREQUIRE(!TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value);\n#\tif !defined(TL_EXPECTED_GCC49)\n\t\tREQUIRE(!std::is_trivially_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(!std::is_trivially_move_assignable<decltype(e)>::value);\n#\tendif\n\t}\n\n\t{\n\t\ttl::expected<std::string, int> e;\n\t\tREQUIRE(std::is_default_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_assignable<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_assignable<decltype(e)>::value);\n\t\tREQUIRE(!TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value);\n\t\tREQUIRE(!TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value);\n#\tif !defined(TL_EXPECTED_GCC49)\n\t\tREQUIRE(!std::is_trivially_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(!std::is_trivially_move_assignable<decltype(e)>::value);\n#\tendif\n\t}\n\n\t{\n\t\ttl::expected<std::string, std::string> e;\n\t\tREQUIRE(std::is_default_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_assignable<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_assignable<decltype(e)>::value);\n\t\tREQUIRE(!TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value);\n\t\tREQUIRE(!TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value);\n#\tif !defined(TL_EXPECTED_GCC49)\n\t\tREQUIRE(!std::is_trivially_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(!std::is_trivially_move_assignable<decltype(e)>::value);\n#\tendif\n\t}\n\n}\n\n\n#endif\n"
  },
  {
    "path": "dependencies/tl_expected/expected/tests/constexpr.cpp",
    "content": "#include <catch2/catch.hpp>\n#include <tl/expected.hpp>\n\nTEST_CASE(\"Constexpr\", \"[constexpr]\") {\n    //TODO\n}\n"
  },
  {
    "path": "dependencies/tl_expected/expected/tests/constructors.cpp",
    "content": "#include <catch2/catch.hpp>\n#include <tl/expected.hpp>\n\n#include <type_traits>\n#include <vector>\n#include <string>\n\nstruct takes_init_and_variadic {\n    std::vector<int> v;\n    std::tuple<int, int> t;\n    template <class... Args>\n    takes_init_and_variadic(std::initializer_list<int> l, Args &&... args)\n        : v(l), t(std::forward<Args>(args)...) {}\n};\n\nTEST_CASE(\"Constructors\", \"[constructors]\") {\n    {\n        tl::expected<int,int> e;\n        REQUIRE(e);\n        REQUIRE(e == 0);\n    }\n\n    {\n        tl::expected<int,int> e = tl::make_unexpected(0);\n        REQUIRE(!e);\n        REQUIRE(e.error() == 0);\n    }\n\n    {\n        tl::expected<int,int> e (tl::unexpect, 0);\n        REQUIRE(!e);\n        REQUIRE(e.error() == 0);\n    }\n\n    {\n        tl::expected<int,int> e (tl::in_place, 42);\n        REQUIRE(e);\n        REQUIRE(e == 42);\n    }\n\n    {\n        tl::expected<std::vector<int>,int> e (tl::in_place, {0,1});\n        REQUIRE(e);\n        REQUIRE((*e)[0] == 0);\n        REQUIRE((*e)[1] == 1);\n    }\n\n    {\n        tl::expected<std::tuple<int,int>,int> e (tl::in_place, 0, 1);\n        REQUIRE(e);\n        REQUIRE(std::get<0>(*e) == 0);\n        REQUIRE(std::get<1>(*e) == 1);\n    }\n\n    {\n        tl::expected<takes_init_and_variadic,int> e (tl::in_place, {0,1}, 2, 3);\n        REQUIRE(e);\n        REQUIRE(e->v[0] == 0);\n        REQUIRE(e->v[1] == 1);\n        REQUIRE(std::get<0>(e->t) == 2);\n        REQUIRE(std::get<1>(e->t) == 3);\n    }\n\n\t{\n\t\ttl::expected<int, int> e;\n\t\tREQUIRE(std::is_default_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_assignable<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_assignable<decltype(e)>::value);\n\t\tREQUIRE(TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value);\n\t\tREQUIRE(TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value);\n#\tif !defined(TL_EXPECTED_GCC49)\n\t\tREQUIRE(std::is_trivially_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_trivially_move_assignable<decltype(e)>::value);\n#\tendif\n\t}\n\n\t{\n\t\ttl::expected<int, std::string> e;\n\t\tREQUIRE(std::is_default_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_assignable<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_assignable<decltype(e)>::value);\n\t\tREQUIRE(!TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value);\n\t\tREQUIRE(!TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value);\n#\tif !defined(TL_EXPECTED_GCC49)\n\t\tREQUIRE(!std::is_trivially_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(!std::is_trivially_move_assignable<decltype(e)>::value);\n#\tendif\n\t}\n\n\t{\n\t\ttl::expected<std::string, int> e;\n\t\tREQUIRE(std::is_default_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_assignable<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_assignable<decltype(e)>::value);\n\t\tREQUIRE(!TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value);\n\t\tREQUIRE(!TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value);\n#\tif !defined(TL_EXPECTED_GCC49)\n\t\tREQUIRE(!std::is_trivially_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(!std::is_trivially_move_assignable<decltype(e)>::value);\n#\tendif\n\t}\n\n\t{\n\t\ttl::expected<std::string, std::string> e;\n\t\tREQUIRE(std::is_default_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(std::is_copy_assignable<decltype(e)>::value);\n\t\tREQUIRE(std::is_move_assignable<decltype(e)>::value);\n\t\tREQUIRE(!TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value);\n\t\tREQUIRE(!TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value);\n#\tif !defined(TL_EXPECTED_GCC49)\n\t\tREQUIRE(!std::is_trivially_move_constructible<decltype(e)>::value);\n\t\tREQUIRE(!std::is_trivially_move_assignable<decltype(e)>::value);\n#\tendif\n\t}\n\n    {\n        tl::expected<void,int> e;\n        REQUIRE(e);\n    }\n\n    {\n        tl::expected<void,int> e (tl::unexpect, 42);\n        REQUIRE(!e);\n        REQUIRE(e.error() == 42);\n    }\n}\n"
  },
  {
    "path": "dependencies/tl_expected/expected/tests/emplace.cpp",
    "content": "#include <catch2/catch.hpp>\n#include <tl/expected.hpp>\n#include <memory>\n#include <vector>\n#include <tuple>\n\nnamespace {\nstruct takes_init_and_variadic {\n  std::vector<int> v;\n  std::tuple<int, int> t;\n  template <class... Args>\n  takes_init_and_variadic(std::initializer_list<int> l, Args &&... args)\n      : v(l), t(std::forward<Args>(args)...) {}\n};\n}\n\nTEST_CASE(\"Emplace\", \"[emplace]\") {\n    {\n        tl::expected<std::unique_ptr<int>,int> e;\n        e.emplace(new int{42});\n        REQUIRE(e);\n        REQUIRE(**e == 42);\n    }\n\n    {\n        tl::expected<std::vector<int>,int> e;\n        e.emplace({0,1});\n        REQUIRE(e);\n        REQUIRE((*e)[0] == 0);\n        REQUIRE((*e)[1] == 1);\n    }\n\n    {\n        tl::expected<std::tuple<int,int>,int> e;\n        e.emplace(2,3);\n        REQUIRE(e);\n        REQUIRE(std::get<0>(*e) == 2);\n        REQUIRE(std::get<1>(*e) == 3);\n    }\n\n    {\n        tl::expected<takes_init_and_variadic,int> e = tl::make_unexpected(0);\n        e.emplace({0,1}, 2, 3);\n        REQUIRE(e);\n        REQUIRE(e->v[0] == 0);\n        REQUIRE(e->v[1] == 1);\n        REQUIRE(std::get<0>(e->t) == 2);\n        REQUIRE(std::get<1>(e->t) == 3);\n    }\n}\n"
  },
  {
    "path": "dependencies/tl_expected/expected/tests/extensions.cpp",
    "content": "#include <catch2/catch.hpp>\n#include <tl/expected.hpp>\n\n#define TOKENPASTE(x, y) x##y\n#define TOKENPASTE2(x, y) TOKENPASTE(x, y)\n#undef STATIC_REQUIRE\n#define STATIC_REQUIRE(e)                                                      \\\n  constexpr bool TOKENPASTE2(rqure, __LINE__) = e;                             \\\n  (void)TOKENPASTE2(rqure, __LINE__);                                          \\\n  REQUIRE(e);\n\nTEST_CASE(\"Map extensions\", \"[extensions.map]\") {\n  auto mul2 = [](int a) { return a * 2; };\n  auto ret_void = [](int a) { (void)a; };\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = e.map(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = e.map(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = std::move(e).map(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = std::move(e).map(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.map(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.map(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).map(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).map(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = e.map(ret_void);\n    REQUIRE(ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = e.map(ret_void);\n    REQUIRE(ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = std::move(e).map(ret_void);\n    REQUIRE(ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = std::move(e).map(ret_void);\n    REQUIRE(ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.map(ret_void);\n    REQUIRE(!ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.map(ret_void);\n    REQUIRE(!ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).map(ret_void);\n    REQUIRE(!ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).map(ret_void);\n    REQUIRE(!ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n\n  // mapping functions which return references\n  {\n    tl::expected<int, int> e(42);\n    auto ret = e.map([](int& i) -> int& { return i; });\n    REQUIRE(ret);\n    REQUIRE(ret == 42);\n  }\n}\n\nTEST_CASE(\"Map error extensions\", \"[extensions.map_error]\") {\n  auto mul2 = [](int a) { return a * 2; };\n  auto ret_void = [](int a) { (void)a; };\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = e.map_error(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = e.map_error(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = std::move(e).map_error(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = std::move(e).map_error(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.map_error(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 42);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.map_error(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 42);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).map_error(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 42);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).map_error(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 42);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = e.map_error(ret_void);\n    REQUIRE(ret);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = e.map_error(ret_void);\n    REQUIRE(ret);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = std::move(e).map_error(ret_void);\n    REQUIRE(ret);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = std::move(e).map_error(ret_void);\n    REQUIRE(ret);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.map_error(ret_void);\n    REQUIRE(!ret);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.map_error(ret_void);\n    REQUIRE(!ret);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).map_error(ret_void);\n    REQUIRE(!ret);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).map_error(ret_void);\n    REQUIRE(!ret);\n  }\n\n}\n\nTEST_CASE(\"And then extensions\", \"[extensions.and_then]\") {\n  auto succeed = [](int a) { (void)a; return tl::expected<int, int>(21 * 2); };\n  auto fail = [](int a) { (void)a; return tl::expected<int, int>(tl::unexpect, 17); };\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = e.and_then(succeed);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = e.and_then(succeed);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = std::move(e).and_then(succeed);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = std::move(e).and_then(succeed);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = e.and_then(fail);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 17);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = e.and_then(fail);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 17);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = std::move(e).and_then(fail);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 17);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = std::move(e).and_then(fail);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 17);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.and_then(succeed);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.and_then(succeed);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).and_then(succeed);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).and_then(succeed);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.and_then(fail);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.and_then(fail);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).and_then(fail);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).and_then(fail);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n}\n\nTEST_CASE(\"or_else\", \"[extensions.or_else]\") {\n  using eptr = std::unique_ptr<int>;\n  auto succeed = [](int a) { (void)a; return tl::expected<int, int>(21 * 2); };\n  auto succeedptr = [](eptr e) { (void)e; return tl::expected<int,eptr>(21*2);};\n  auto fail =    [](int a) { (void)a; return tl::expected<int,int>(tl::unexpect, 17);};\n  auto failptr = [](eptr e) { *e = 17;return tl::expected<int,eptr>(tl::unexpect, std::move(e));};\n  auto failvoid = [](int) {};\n  auto failvoidptr = [](const eptr&) { /* don't consume */};\n  auto consumeptr = [](eptr) {};\n  auto make_u_int = [](int n) { return std::unique_ptr<int>(new int(n));};\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = e.or_else(succeed);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = e.or_else(succeed);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = std::move(e).or_else(succeed);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    tl::expected<int, eptr> e = 21;\n    auto ret = std::move(e).or_else(succeedptr);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = std::move(e).or_else(succeed);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = e.or_else(fail);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = e.or_else(fail);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = std::move(e).or_else(fail);\n    REQUIRE(ret);\n    REQUIRE(ret == 21);\n  }\n\n\n  {\n    tl::expected<int, eptr> e = 21;\n    auto ret = std::move(e).or_else(failptr);\n    REQUIRE(ret);\n    REQUIRE(ret == 21);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = std::move(e).or_else(fail);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.or_else(succeed);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.or_else(succeed);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).or_else(succeed);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    tl::expected<int, eptr> e(tl::unexpect, make_u_int(21));\n    auto ret = std::move(e).or_else(succeedptr);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).or_else(succeed);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.or_else(fail);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 17);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.or_else(failvoid);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.or_else(fail);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 17);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.or_else(failvoid);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).or_else(fail);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 17);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).or_else(failvoid);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    tl::expected<int, eptr> e(tl::unexpect, make_u_int(21));\n    auto ret = std::move(e).or_else(failvoidptr);\n    REQUIRE(!ret);\n    REQUIRE(*ret.error() == 21);\n  }\n\n  {\n    tl::expected<int, eptr> e(tl::unexpect, make_u_int(21));\n    auto ret = std::move(e).or_else(consumeptr);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == nullptr);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).or_else(fail);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 17);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).or_else(failvoid);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n}\n\nTEST_CASE(\"Transform extensions\", \"[extensions.tronsfarm]\") {\n  auto mul2 = [](int a) { return a * 2; };\n  auto ret_void = [](int a) { (void)a; };\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = e.transform(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = e.transform(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = std::move(e).transform(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = std::move(e).transform(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 42);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.transform(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.transform(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).transform(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).transform(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 21);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = e.transform(ret_void);\n    REQUIRE(ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = e.transform(ret_void);\n    REQUIRE(ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = std::move(e).transform(ret_void);\n    REQUIRE(ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = std::move(e).transform(ret_void);\n    REQUIRE(ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.transform(ret_void);\n    REQUIRE(!ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.transform(ret_void);\n    REQUIRE(!ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).transform(ret_void);\n    REQUIRE(!ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).transform(ret_void);\n    REQUIRE(!ret);\n    STATIC_REQUIRE(\n        (std::is_same<decltype(ret), tl::expected<void, int>>::value));\n  }\n\n\n  // mapping functions which return references\n  {\n    tl::expected<int, int> e(42);\n    auto ret = e.transform([](int& i) -> int& { return i; });\n    REQUIRE(ret);\n    REQUIRE(ret == 42);\n  }\n}\n\nTEST_CASE(\"Transform error extensions\", \"[extensions.transform_error]\") {\n  auto mul2 = [](int a) { return a * 2; };\n  auto ret_void = [](int a) { (void)a; };\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = e.transform_error(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = e.transform_error(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = std::move(e).transform_error(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = std::move(e).transform_error(mul2);\n    REQUIRE(ret);\n    REQUIRE(*ret == 21);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.transform_error(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 42);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.transform_error(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 42);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).transform_error(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 42);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).transform_error(mul2);\n    REQUIRE(!ret);\n    REQUIRE(ret.error() == 42);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = e.transform_error(ret_void);\n    REQUIRE(ret);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = e.transform_error(ret_void);\n    REQUIRE(ret);\n  }\n\n  {\n    tl::expected<int, int> e = 21;\n    auto ret = std::move(e).transform_error(ret_void);\n    REQUIRE(ret);\n  }\n\n  {\n    const tl::expected<int, int> e = 21;\n    auto ret = std::move(e).transform_error(ret_void);\n    REQUIRE(ret);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.transform_error(ret_void);\n    REQUIRE(!ret);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = e.transform_error(ret_void);\n    REQUIRE(!ret);\n  }\n\n  {\n    tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).transform_error(ret_void);\n    REQUIRE(!ret);\n  }\n\n  {\n    const tl::expected<int, int> e(tl::unexpect, 21);\n    auto ret = std::move(e).transform_error(ret_void);\n    REQUIRE(!ret);\n  }\n\n}\n\nstruct S {\n    int x;\n};\n\nstruct F {\n    int x;\n};\n\nTEST_CASE(\"14\", \"[issue.14]\") {\n    auto res = tl::expected<S,F>{tl::unexpect, F{}};\n\n    res.map_error([](F f) {\n        (void)f;\n    });\n}\n\nTEST_CASE(\"32\", \"[issue.32]\") {\n    int i = 0;\n    tl::expected<void, int> a;\n    a.map([&i]{i = 42;});\n    REQUIRE(i == 42);\n\n    auto x = a.map([]{return 42;});\n    REQUIRE(*x == 42);\n}\n"
  },
  {
    "path": "dependencies/tl_expected/expected/tests/issues.cpp",
    "content": "#include <catch2/catch.hpp>\n#include <tl/expected.hpp>\n\n#include <string>\n#include <memory>\n\nusing std::string;\n\ntl::expected<int, string> getInt3(int val) { return val; }\n\ntl::expected<int, string> getInt2(int val) { return val; }\n\ntl::expected<int, string> getInt1() { return getInt2(5).and_then(getInt3); }\n\nTEST_CASE(\"Issue 1\", \"[issues.1]\") { getInt1(); }\n\ntl::expected<int, int> operation1() { return 42; }\n\ntl::expected<std::string, int> operation2(int const val) { (void)val; return \"Bananas\"; }\n\nTEST_CASE(\"Issue 17\", \"[issues.17]\") {\n  auto const intermediate_result = operation1();\n\n  intermediate_result.and_then(operation2);\n}\n\nstruct a {};\nstruct b : a {};\n\nauto doit() -> tl::expected<std::unique_ptr<b>, int> {\n    return tl::make_unexpected(0);\n}\n\nTEST_CASE(\"Issue 23\", \"[issues.23]\") {\n    tl::expected<std::unique_ptr<a>, int> msg = doit();\n    REQUIRE(!msg.has_value());    \n}\n\nTEST_CASE(\"Issue 26\", \"[issues.26]\") {\n  tl::expected<a, int> exp = tl::expected<b, int>(tl::unexpect, 0);\n  REQUIRE(!exp.has_value());\n}\n\nstruct foo {\n  foo() = default;\n  foo(foo &) = delete;\n  foo(foo &&){};\n};\n\nTEST_CASE(\"Issue 29\", \"[issues.29]\") {\n  std::vector<foo> v;\n  v.emplace_back();\n  tl::expected<std::vector<foo>, int> ov = std::move(v);\n  REQUIRE(ov->size() == 1);\n}\n\ntl::expected<int, std::string> error() {\n  return tl::make_unexpected(std::string(\"error1 \"));\n}\nstd::string maperror(std::string s) { return s + \"maperror \"; }\n\nTEST_CASE(\"Issue 30\", \"[issues.30]\") {\n  error().map_error(maperror);\n}\n\nstruct i31{\n  int i;\n};\nTEST_CASE(\"Issue 31\", \"[issues.31]\") {\n    const tl::expected<i31, int> a = i31{42};\n    (void)a->i;\n\n    tl::expected< void, std::string > result;\n    tl::expected< void, std::string > result2 = result;\n    result2 = result;\n}\n\nTEST_CASE(\"Issue 33\", \"[issues.33]\") {\n    tl::expected<void, int> res {tl::unexpect, 0};\n    REQUIRE(!res);    \n    res = res.map_error([](int i) { (void)i; return 42; });\n    REQUIRE(res.error() == 42);\n}\n\n\ntl::expected<void, std::string> voidWork() { return {}; }\ntl::expected<int, std::string> work2() { return 42; }\nvoid errorhandling(std::string){}\n\nTEST_CASE(\"Issue 34\", \"[issues.34]\") {\n  tl::expected <int, std::string> result = voidWork ()\n      .and_then (work2);\n  result.map_error ([&] (std::string result) {errorhandling (result);});\n}\n\nstruct non_copyable {\n\tnon_copyable(non_copyable&&) = default;\n\tnon_copyable(non_copyable const&) = delete;\n\tnon_copyable() = default;\n};\n\nTEST_CASE(\"Issue 42\", \"[issues.42]\") {\n\ttl::expected<non_copyable,int>{}.map([](non_copyable) {});\n}\n\nTEST_CASE(\"Issue 43\", \"[issues.43]\") {\n\tauto result = tl::expected<void, std::string>{};\n\tresult = tl::make_unexpected(std::string{ \"foo\" });\n}\n\n#if !(__GNUC__ <= 5)\n#include <memory>\n\nusing MaybeDataPtr = tl::expected<int, std::unique_ptr<int>>;\n\nMaybeDataPtr test(int i) noexcept\n{\n  return std::move(i);\n}\n\nMaybeDataPtr test2(int i) noexcept\n{\n  return std::move(i);\n}\n\nTEST_CASE(\"Issue 49\", \"[issues.49]\") {\n  auto m = test(10)\n    .and_then(test2);\n}\n#endif\n\ntl::expected<int, std::unique_ptr<std::string>> func()\n{\n  return 1;\n}\n\nTEST_CASE(\"Issue 61\", \"[issues.61]\") {\n  REQUIRE(func().value() == 1);\n}\n\nstruct move_tracker {\n        int moved = 0;\n\n        move_tracker() = default;\n\n        move_tracker(move_tracker const &other) noexcept {};\n        move_tracker(move_tracker &&orig) noexcept\n            : moved(orig.moved + 1) {}\n\n        move_tracker &\n        operator=(move_tracker const &other) noexcept {};\n        \n        move_tracker &operator=(move_tracker &&orig) noexcept {\n          moved = orig.moved + 1;\n          return *this;\n        }\n};\n\nTEST_CASE(\"Issue 122\", \"[issues.122]\") { \n     tl::expected<move_tracker, int> res;\n     res.emplace();\n     REQUIRE(res.value().moved == 0);\n}\n\n#ifdef __cpp_deduction_guides\nTEST_CASE(\"Issue 89\", \"[issues.89]\") { \n    auto s = tl::unexpected(\"Some string\");\n    REQUIRE(s.value() == std::string(\"Some string\"));\n}\n#endif\n\nstruct S {\n    int i = 0;\n    int j = 0;\n    S(int i) : i(i) {}\n    S(int i, int j) : i(i), j(j) {}\n};\n\nTEST_CASE(\"Issue 107\", \"[issues.107]\") {\n    tl::expected<int, S> ex1(tl::unexpect, 2); \n    tl::expected<int, S> ex2(tl::unexpect, 2, 2);\n\n    REQUIRE(ex1.error().i == 2);\n    REQUIRE(ex1.error().j == 0);\n    REQUIRE(ex2.error().i == 2);\n    REQUIRE(ex2.error().j == 2);\n}\n\nTEST_CASE(\"Issue 129\", \"[issues.129]\") {\n  tl::expected<std::unique_ptr<int>, int> x1 {std::unique_ptr<int>(new int(4))};\n  tl::expected<std::unique_ptr<int>, int> y1 {std::unique_ptr<int>(new int(2))};\n  x1 = std::move(y1);\n\n  REQUIRE(**x1 == 2);\n}\n"
  },
  {
    "path": "dependencies/tl_expected/expected/tests/main.cpp",
    "content": "#define CATCH_CONFIG_MAIN\n#include <catch2/catch.hpp>\n"
  },
  {
    "path": "dependencies/tl_expected/expected/tests/noexcept.cpp",
    "content": "#include <catch2/catch.hpp>\n#include <tl/expected.hpp>\n\nTEST_CASE(\"Noexcept\", \"[noexcept]\") {\n    //TODO\n}\n"
  },
  {
    "path": "dependencies/tl_expected/expected/tests/observers.cpp",
    "content": "#include <catch2/catch.hpp>\n#include <tl/expected.hpp>\n\nstruct move_detector {\n  move_detector() = default;\n  move_detector(move_detector &&rhs) { rhs.been_moved = true; }\n  bool been_moved = false;\n};\n\nTEST_CASE(\"Observers\", \"[observers]\") {\n    tl::expected<int,int> o1 = 42;\n    tl::expected<int,int> o2 {tl::unexpect, 0};\n    const tl::expected<int,int> o3 = 42;\n\n  REQUIRE(*o1 == 42);\n  REQUIRE(*o1 == o1.value());\n  REQUIRE(o2.value_or(42) == 42);\n  REQUIRE(o2.error() == 0);\n  REQUIRE(o3.value() == 42);\n  auto success = std::is_same<decltype(o1.value()), int &>::value;\n  REQUIRE(success);\n  success = std::is_same<decltype(o3.value()), const int &>::value;\n  REQUIRE(success);\n  success = std::is_same<decltype(std::move(o1).value()), int &&>::value;\n  REQUIRE(success);\n\n  #ifndef TL_EXPECTED_NO_CONSTRR\n  success = std::is_same<decltype(std::move(o3).value()), const int &&>::value;\n  REQUIRE(success);\n  #endif\n\n  tl::expected<move_detector,int> o4{tl::in_place};\n  move_detector o5 = std::move(o4).value();\n  REQUIRE(o4->been_moved);\n  REQUIRE(!o5.been_moved);\n}\n"
  },
  {
    "path": "dependencies/tl_expected/expected/tests/relops.cpp",
    "content": "#include <catch2/catch.hpp>\n#include <tl/expected.hpp>\n\nTEST_CASE(\"Relational operators\", \"[relops]\") {\n  tl::expected<int, int> o1 = 42;\n  tl::expected<int, int> o2{tl::unexpect, 0};\n  const tl::expected<int, int> o3 = 42;\n\n  REQUIRE(o1 == o1);\n  REQUIRE(o1 != o2);\n  REQUIRE(o1 == o3);\n  REQUIRE(o3 == o3);\n\n  tl::expected<void, int> o6;\n\n  REQUIRE(o6 == o6);\n}\n"
  },
  {
    "path": "dependencies/tl_expected/expected/tests/swap.cpp",
    "content": "#include <catch2/catch.hpp>\n#include <tl/expected.hpp>\n\nstruct no_throw {\n  no_throw(std::string i) : i(i) {}\n  std::string i;\n};\nstruct canthrow_move {\n  canthrow_move(std::string i) : i(i) {}\n  canthrow_move(canthrow_move const &) = default;\n  canthrow_move(canthrow_move &&other) noexcept(false) : i(other.i) {}\n  canthrow_move &operator=(canthrow_move &&) = default;\n  std::string i;\n};\n\nbool should_throw = false;\n\n#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED\nstruct willthrow_move {\n  willthrow_move(std::string i) : i(i) {}\n  willthrow_move(willthrow_move const &) = default;\n  willthrow_move(willthrow_move &&other) : i(other.i) {\n    if (should_throw)\n      throw 0;\n  }\n  willthrow_move &operator=(willthrow_move &&) = default;\n  std::string i;\n};\n#endif // TL_EXPECTED_EXCEPTIONS_ENABLED\n\nstatic_assert(tl::detail::is_swappable<no_throw>::value, \"\");\n\ntemplate <class T1, class T2> void swap_test() {\n  std::string s1 = \"abcdefghijklmnopqrstuvwxyz\";\n  std::string s2 = \"zyxwvutsrqponmlkjihgfedcba\";\n\n  tl::expected<T1, T2> a{s1};\n  tl::expected<T1, T2> b{s2};\n  swap(a, b);\n  REQUIRE(a->i == s2);\n  REQUIRE(b->i == s1);\n\n  a = s1;\n  b = tl::unexpected<T2>(s2);\n  swap(a, b);\n  REQUIRE(a.error().i == s2);\n  REQUIRE(b->i == s1);\n\n  a = tl::unexpected<T2>(s1);\n  b = s2;\n  swap(a, b);\n  REQUIRE(a->i == s2);\n  REQUIRE(b.error().i == s1);\n\n  a = tl::unexpected<T2>(s1);\n  b = tl::unexpected<T2>(s2);\n  swap(a, b);\n  REQUIRE(a.error().i == s2);\n  REQUIRE(b.error().i == s1);\n\n  a = s1;\n  b = s2;\n  a.swap(b);\n  REQUIRE(a->i == s2);\n  REQUIRE(b->i == s1);\n\n  a = s1;\n  b = tl::unexpected<T2>(s2);\n  a.swap(b);\n  REQUIRE(a.error().i == s2);\n  REQUIRE(b->i == s1);\n\n  a = tl::unexpected<T2>(s1);\n  b = s2;\n  a.swap(b);\n  REQUIRE(a->i == s2);\n  REQUIRE(b.error().i == s1);\n\n  a = tl::unexpected<T2>(s1);\n  b = tl::unexpected<T2>(s2);\n  a.swap(b);\n  REQUIRE(a.error().i == s2);\n  REQUIRE(b.error().i == s1);\n}\n\n#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED\nTEST_CASE(\"swap\") {\n\n  swap_test<no_throw, no_throw>();\n  swap_test<no_throw, canthrow_move>();\n  swap_test<canthrow_move, no_throw>();\n\n  std::string s1 = \"abcdefghijklmnopqrstuvwxyz\";\n  std::string s2 = \"zyxwvutsrqponmlkjihgfedcbaxxx\";\n  tl::expected<no_throw, willthrow_move> a{s1};\n  tl::expected<no_throw, willthrow_move> b{tl::unexpect, s2};\n  should_throw = 1;\n\n  #ifdef _MSC_VER\n  //this seems to break catch on GCC and Clang\n  REQUIRE_THROWS(swap(a, b));\n  #endif\n\n  REQUIRE(a->i == s1);\n  REQUIRE(b.error().i == s2);\n}\n#endif // TL_EXPECTED_EXCEPTIONS_ENABLED\n"
  },
  {
    "path": "dependencies/tl_expected/expected/tests/test.cpp",
    "content": "struct no_throw {\n  no_throw(std::string i) : i(i) {}\n  std::string i;\n};\nstruct canthrow_move {\n  canthrow_move(std::string i) : i(i) {}\n  canthrow_move(canthrow_move const &) = default;\n  canthrow_move(canthrow_move &&other) noexcept(false) : i(other.i) {}\n  canthrow_move &operator=(canthrow_move &&) = default;\n  std::string i;\n};\n\nbool should_throw = false;\nstruct willthrow_move {\n  willthrow_move(std::string i) : i(i) {}\n  willthrow_move(willthrow_move const &) = default;\n  willthrow_move(willthrow_move &&other) : i(other.i) {\n    if (should_throw)\n      throw 0;\n  }\n  willthrow_move &operator=(willthrow_move &&) = default;\n  std::string i;\n};\n\nint main() {\n  std::string s1 = \"abcdefghijklmnopqrstuvwxyz\";\n  std::string s2 = \"zyxwvutsrqponmlkjihgfedcbaxxx\";\n  tl::expected<no_throw, willthrow_move> a{s1};\n  tl::expected<no_throw, willthrow_move> b{tl::unexpect, s2};\n  should_throw = 1;\n  swap(a, b);\n}"
  },
  {
    "path": "dependencies/tl_expected/version.txt",
    "content": "Expected 1.1.0\nhttps://github.com/TartanLlama/expected\n"
  },
  {
    "path": "dependencies/whereami/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2022 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nadd_library(whereami STATIC)\n\ntarget_sources(whereami PRIVATE\n\twhereami/src/whereami.cpp\n\twhereami/src/whereami.h\n)\n\ntarget_include_directories(whereami\n\tSYSTEM PUBLIC\n\t\t\"${CMAKE_CURRENT_SOURCE_DIR}/whereami/src\"\n)\n\n"
  },
  {
    "path": "dependencies/whereami/version.txt",
    "content": "Where Am I?\ngit master 2022-01-15\nhttps://github.com/gpakosz/whereami\n\nwhereami.c renamed to whereami.cpp\n"
  },
  {
    "path": "dependencies/whereami/whereami/LICENSE.MIT",
    "content": "Copyright Gregory Pakosz\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject 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, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "dependencies/whereami/whereami/LICENSE.WTFPLv2",
    "content": "--------------------------------------------------------------------------------\n        DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n                    Version 2, December 2004\n\n Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>\n\n Everyone is permitted to copy and distribute verbatim or modified\n copies of this license document, and changing it is allowed as long\n as the name is changed.\n\n            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. You just DO WHAT THE FUCK YOU WANT TO.\n  1. Bla bla bla\n  2. Montesqieu et camembert, vive la France, zut alors!\n\n--------------------------------------------------------------------------------\n\nWTFPLv2 is very permissive, see http://www.wtfpl.net/faq/\n\nHowever, if this WTFPLV2 is REALLY a blocker and is the reason you can't use\nthis project, contact me and I'll dual license it.\n\n--------------------------------------------------------------------------------\n"
  },
  {
    "path": "dependencies/whereami/whereami/README.md",
    "content": "# Where Am I?\n\nA drop-in two files library to locate the current executable and the current\nmodule on the file system.\n\nSupported platforms:\n\n- Windows\n- Linux\n- Mac\n- iOS\n- Android\n- QNX Neutrino\n- FreeBSD\n- NetBSD\n- DragonFly BSD\n- SunOS\n- OpenBSD\n\nJust drop `whereami.h` and `whereami.c` into your build and get started. (see\nalso [customizing compilation])\n\n[customizing compilation]: #customizing-compilation\n\n--------------------------------------------------------------------------------\n\n## Usage\n\n- `wai_getExecutablePath()` returns the path of the enclosing executable\n- `wai_getModulePath()` returns the path of the enclosing module\n\nExample usage:\n\n- first call `int length = wai_getExecutablePath(NULL, 0, NULL);` to retrieve\n the length of the path\n- allocate the destination buffer with `path = (char*)malloc(length + 1);`\n- call `wai_getExecutablePath(path, length, &dirname_length)` again to retrieve\n the path\n- add a terminal `NUL` character with `path[length] = '\\0';`\n\nHere is the output of the example:\n\n    $ make -j -C _gnu-make\n    $ cp ./bin/mac-x86_64/library.dylib /tmp/\n    $ ./bin/mac-x86_64/executable --load-library=/tmp/library.dylib\n\n    executable path: /Users/gregory/Projects/whereami/bin/mac-x86_64/executable\n      dirname: /Users/gregory/Projects/whereami/bin/mac-x86_64\n      basename: executable\n    module path: /Users/gregory/Projects/whereami/bin/mac-x86_64/executable\n      dirname: /Users/gregory/Projects/whereami/bin/mac-x86_64\n      basename: executable\n\n    library loaded\n    executable path: /Users/gregory/Projects/whereami/bin/mac-x86_64/executable\n      dirname: /Users/gregory/Projects/whereami/bin/mac-x86_64\n      basename: executable\n    module path: /private/tmp/library.dylib\n      dirname: /private/tmp\n      basename: library.dylib\n    library unloaded\n\n--------------------------------------------------------------------------------\n\n## Customizing compilation\n\nYou can customize the library's behavior by defining the following macros:\n\n- `WAI_FUNCSPEC`\n- `WAI_PREFIX`\n- `WAI_MALLOC`\n- `WAI_REALLOC`\n- `WAI_FREE`\n\n## Compiling for Windows\n\nThere is a Visual Studio 2015 solution in the `_win-vs14/` folder.\n\n## Compiling for Linux or Mac\n\nThere is a GNU Make 3.81 `MakeFile` in the `_gnu-make/` folder:\n\n    $ make -j -C _gnu-make/\n\n## Compiling for Mac\n\nSee above if you want to compile from command line. Otherwise there is an Xcode\nproject located in the `_mac-xcode/` folder.\n\n## Compiling for iOS\n\nThere is an Xcode project located in the `_ios-xcode/` folder.\n\nIf you prefer compiling from command line and deploying to a jailbroken device\nthrough SSH, use:\n\n    $ make -j -C _gnu-make/ binsubdir=ios CC=\"$(xcrun --sdk iphoneos --find clang) -isysroot $(xcrun --sdk iphoneos --show-sdk-path) -arch armv7 -arch armv7s -arch arm64\" postbuild=\"codesign -s 'iPhone Developer'\"\n\n## Compiling for Android\n\nYou will have to install the Android NDK, and point the `$NDK_ROOT` environment\nvariable to the NDK path: e.g. `export NDK_ROOT=/opt/android-ndk` (without a\ntrailing `/` character).\n\nNext, the easy way is to make a standalone Android toolchain with the following\ncommand:\n\n    $ $NDK_ROOT/build/tools/make_standalone_toolchain.py --arch=arm64 --api 21 --install-dir=/tmp/android-toolchain\n\nNow you can compile the example by running:\n\n    $ make -j -C _gnu-make/ platform=android architecture=arm64 CC=/tmp/android-toolchain/bin/aarch64-linux-android-gcc CXX=/tmp/android-toolchain/bin/aarch64-linux-android-g++\n\nLoading page aligned library straight from APKs is supported. To test, use the\nfollowing:\n\n    $ zip -Z store app bin/android/library.so\n    $ zipalign -v -f -p 4 ./app.zip ./app.apk\n\nThen copy `bin/android/executable` and `app.apk` to your Android device and\nthere launch:\n\n    $ ./executable --load-library=$PWD/app.apk!/bin/android/library.so\n"
  },
  {
    "path": "dependencies/whereami/whereami/src/whereami.cpp",
    "content": "// (‑●‑●)> dual licensed under the WTFPL v2 and MIT licenses\n//   without any warranty.\n//   by Gregory Pakosz (@gpakosz)\n// https://github.com/gpakosz/whereami\n\n// in case you want to #include \"whereami.c\" in a larger compilation unit\n#if !defined(WHEREAMI_H)\n#include <whereami.h>\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#if !defined(WAI_MALLOC) || !defined(WAI_FREE) || !defined(WAI_REALLOC)\n#include <stdlib.h>\n#endif\n\n#if !defined(WAI_MALLOC)\n#define WAI_MALLOC(size) malloc(size)\n#endif\n\n#if !defined(WAI_FREE)\n#define WAI_FREE(p) free(p)\n#endif\n\n#if !defined(WAI_REALLOC)\n#define WAI_REALLOC(p, size) realloc(p, size)\n#endif\n\n#ifndef WAI_NOINLINE\n#if defined(_MSC_VER)\n#define WAI_NOINLINE __declspec(noinline)\n#elif defined(__GNUC__)\n#define WAI_NOINLINE __attribute__((noinline))\n#else\n#error unsupported compiler\n#endif\n#endif\n\n#if defined(_MSC_VER)\n#define WAI_RETURN_ADDRESS() _ReturnAddress()\n#elif defined(__GNUC__)\n#define WAI_RETURN_ADDRESS() __builtin_extract_return_addr(__builtin_return_address(0))\n#else\n#error unsupported compiler\n#endif\n\n#if defined(_WIN32)\n\n#ifndef WIN32_LEAN_AND_MEAN\n#define WIN32_LEAN_AND_MEAN\n#endif\n#if defined(_MSC_VER)\n#pragma warning(push, 3)\n#endif\n#include <windows.h>\n#include <intrin.h>\n#if defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n#include <stdbool.h>\n\nstatic int WAI_PREFIX(getModulePath_)(HMODULE module, char* out, int capacity, int* dirname_length)\n{\n  wchar_t buffer1[MAX_PATH];\n  wchar_t buffer2[MAX_PATH];\n  wchar_t* path = NULL;\n  int length = -1;\n  bool ok;\n\n  for (ok = false; !ok; ok = true)\n  {\n    DWORD size;\n    int length_, length__;\n\n    size = GetModuleFileNameW(module, buffer1, sizeof(buffer1) / sizeof(buffer1[0]));\n\n    if (size == 0)\n      break;\n    else if (size == (DWORD)(sizeof(buffer1) / sizeof(buffer1[0])))\n    {\n      DWORD size_ = size;\n      do\n      {\n        wchar_t* path_;\n\n        path_ = (wchar_t*)WAI_REALLOC(path, sizeof(wchar_t) * size_ * 2);\n        if (!path_)\n          break;\n        size_ *= 2;\n        path = path_;\n        size = GetModuleFileNameW(module, path, size_);\n      }\n      while (size == size_);\n\n      if (size == size_)\n        break;\n    }\n    else\n      path = buffer1;\n\n    if (!_wfullpath(buffer2, path, MAX_PATH))\n      break;\n    length_ = (int)wcslen(buffer2);\n    length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_ , out, capacity, NULL, NULL);\n\n    if (length__ == 0)\n      length__ = WideCharToMultiByte(CP_UTF8, 0, buffer2, length_, NULL, 0, NULL, NULL);\n    if (length__ == 0)\n      break;\n\n    if (length__ <= capacity && dirname_length)\n    {\n      int i;\n\n      for (i = length__ - 1; i >= 0; --i)\n      {\n        if (out[i] == '\\\\')\n        {\n          *dirname_length = i;\n          break;\n        }\n      }\n    }\n\n    length = length__;\n  }\n\n  if (path != buffer1)\n    WAI_FREE(path);\n\n  return ok ? length : -1;\n}\n\nWAI_NOINLINE WAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)\n{\n  return WAI_PREFIX(getModulePath_)(NULL, out, capacity, dirname_length);\n}\n\nWAI_NOINLINE WAI_FUNCSPEC\nint WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)\n{\n  HMODULE module;\n  int length = -1;\n\n#if defined(_MSC_VER)\n#pragma warning(push)\n#pragma warning(disable: 4054)\n#endif\n  if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)WAI_RETURN_ADDRESS(), &module))\n#if defined(_MSC_VER)\n#pragma warning(pop)\n#endif\n  {\n    length = WAI_PREFIX(getModulePath_)(module, out, capacity, dirname_length);\n  }\n\n  return length;\n}\n\n#elif defined(__linux__) || defined(__CYGWIN__) || defined(__sun) || defined(WAI_USE_PROC_SELF_EXE)\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#if defined(__linux__)\n#include <linux/limits.h>\n#else\n#include <limits.h>\n#endif\n#ifndef __STDC_FORMAT_MACROS\n#define __STDC_FORMAT_MACROS\n#endif\n#include <inttypes.h>\n#include <stdbool.h>\n\n#if !defined(WAI_PROC_SELF_EXE)\n#if defined(__sun)\n#define WAI_PROC_SELF_EXE \"/proc/self/path/a.out\"\n#else\n#define WAI_PROC_SELF_EXE \"/proc/self/exe\"\n#endif\n#endif\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer[PATH_MAX];\n  char* resolved = NULL;\n  int length = -1;\n  bool ok;\n\n  for (ok = false; !ok; ok = true)\n  {\n    resolved = realpath(WAI_PROC_SELF_EXE, buffer);\n    if (!resolved)\n      break;\n\n    length = (int)strlen(resolved);\n    if (length <= capacity)\n    {\n      memcpy(out, resolved, length);\n\n      if (dirname_length)\n      {\n        int i;\n\n        for (i = length - 1; i >= 0; --i)\n        {\n          if (out[i] == '/')\n          {\n            *dirname_length = i;\n            break;\n          }\n        }\n      }\n    }\n  }\n\n  return ok ? length : -1;\n}\n\n#if !defined(WAI_PROC_SELF_MAPS_RETRY)\n#define WAI_PROC_SELF_MAPS_RETRY 5\n#endif\n\n#if !defined(WAI_PROC_SELF_MAPS)\n#if defined(__sun)\n#define WAI_PROC_SELF_MAPS \"/proc/self/map\"\n#else\n#define WAI_PROC_SELF_MAPS \"/proc/self/maps\"\n#endif\n#endif\n\n#if defined(__ANDROID__) || defined(ANDROID)\n#include <fcntl.h>\n#include <sys/mman.h>\n#include <unistd.h>\n#endif\n#include <stdbool.h>\n\nWAI_NOINLINE WAI_FUNCSPEC\nint WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)\n{\n  int length = -1;\n  FILE* maps = NULL;\n\n  for (int r = 0; r < WAI_PROC_SELF_MAPS_RETRY; ++r)\n  {\n    maps = fopen(WAI_PROC_SELF_MAPS, \"r\");\n    if (!maps)\n      break;\n\n    for (;;)\n    {\n      char buffer[PATH_MAX < 1024 ? 1024 : PATH_MAX];\n      uint64_t low, high;\n      char perms[5];\n      uint64_t offset;\n      uint32_t major, minor;\n      char path[PATH_MAX];\n      uint32_t inode;\n\n      if (!fgets(buffer, sizeof(buffer), maps))\n        break;\n\n      if (sscanf(buffer, \"%\" PRIx64 \"-%\" PRIx64 \" %s %\" PRIx64 \" %x:%x %u %s\\n\", &low, &high, perms, &offset, &major, &minor, &inode, path) == 8)\n      {\n        uint64_t addr = (uintptr_t)WAI_RETURN_ADDRESS();\n        if (low <= addr && addr <= high)\n        {\n          char* resolved;\n\n          resolved = realpath(path, buffer);\n          if (!resolved)\n            break;\n\n          length = (int)strlen(resolved);\n#if defined(__ANDROID__) || defined(ANDROID)\n          if (length > 4\n              &&buffer[length - 1] == 'k'\n              &&buffer[length - 2] == 'p'\n              &&buffer[length - 3] == 'a'\n              &&buffer[length - 4] == '.')\n          {\n            int fd = open(path, O_RDONLY);\n            if (fd == -1)\n            {\n              length = -1; // retry\n              break;\n            }\n\n            char* begin = (char*)mmap(0, offset, PROT_READ, MAP_SHARED, fd, 0);\n            if (begin == MAP_FAILED)\n            {\n              close(fd);\n              length = -1; // retry\n              break;\n            }\n\n            char* p = begin + offset - 30; // minimum size of local file header\n            while (p >= begin) // scan backwards\n            {\n              if (*((uint32_t*)p) == 0x04034b50UL) // local file header signature found\n              {\n                uint16_t length_ = *((uint16_t*)(p + 26));\n\n                if (length + 2 + length_ < (int)sizeof(buffer))\n                {\n                  memcpy(&buffer[length], \"!/\", 2);\n                  memcpy(&buffer[length + 2], p + 30, length_);\n                  length += 2 + length_;\n                }\n\n                break;\n              }\n\n              --p;\n            }\n\n            munmap(begin, offset);\n            close(fd);\n          }\n#endif\n          if (length <= capacity)\n          {\n            memcpy(out, resolved, length);\n\n            if (dirname_length)\n            {\n              int i;\n\n              for (i = length - 1; i >= 0; --i)\n              {\n                if (out[i] == '/')\n                {\n                  *dirname_length = i;\n                  break;\n                }\n              }\n            }\n          }\n\n          break;\n        }\n      }\n    }\n\n    fclose(maps);\n    maps = NULL;\n\n    if (length != -1)\n      break;\n  }\n\n  return length;\n}\n\n#elif defined(__APPLE__)\n\n#define _DARWIN_BETTER_REALPATH\n#include <mach-o/dyld.h>\n#include <limits.h>\n#include <stdlib.h>\n#include <string.h>\n#include <dlfcn.h>\n#include <stdbool.h>\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer1[PATH_MAX];\n  char buffer2[PATH_MAX];\n  char* path = buffer1;\n  char* resolved = NULL;\n  int length = -1;\n  bool ok;\n\n  for (ok = false; !ok; ok = true)\n  {\n    uint32_t size = (uint32_t)sizeof(buffer1);\n    if (_NSGetExecutablePath(path, &size) == -1)\n    {\n      path = (char*)WAI_MALLOC(size);\n      if (!_NSGetExecutablePath(path, &size))\n        break;\n    }\n\n    resolved = realpath(path, buffer2);\n    if (!resolved)\n      break;\n\n    length = (int)strlen(resolved);\n    if (length <= capacity)\n    {\n      memcpy(out, resolved, length);\n\n      if (dirname_length)\n      {\n        int i;\n\n        for (i = length - 1; i >= 0; --i)\n        {\n          if (out[i] == '/')\n          {\n            *dirname_length = i;\n            break;\n          }\n        }\n      }\n    }\n  }\n\n  if (path != buffer1)\n    WAI_FREE(path);\n\n  return ok ? length : -1;\n}\n\nWAI_NOINLINE WAI_FUNCSPEC\nint WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer[PATH_MAX];\n  char* resolved = NULL;\n  int length = -1;\n\n  for(;;)\n  {\n    Dl_info info;\n\n    if (dladdr(WAI_RETURN_ADDRESS(), &info))\n    {\n      resolved = realpath(info.dli_fname, buffer);\n      if (!resolved)\n        break;\n\n      length = (int)strlen(resolved);\n      if (length <= capacity)\n      {\n        memcpy(out, resolved, length);\n\n        if (dirname_length)\n        {\n          int i;\n\n          for (i = length - 1; i >= 0; --i)\n          {\n            if (out[i] == '/')\n            {\n              *dirname_length = i;\n              break;\n            }\n          }\n        }\n      }\n    }\n\n    break;\n  }\n\n  return length;\n}\n\n#elif defined(__QNXNTO__)\n\n#include <limits.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <dlfcn.h>\n#include <stdbool.h>\n\n#if !defined(WAI_PROC_SELF_EXE)\n#define WAI_PROC_SELF_EXE \"/proc/self/exefile\"\n#endif\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer1[PATH_MAX];\n  char buffer2[PATH_MAX];\n  char* resolved = NULL;\n  FILE* self_exe = NULL;\n  int length = -1;\n  bool ok;\n\n  for (ok = false; !ok; ok = true)\n  {\n    self_exe = fopen(WAI_PROC_SELF_EXE, \"r\");\n    if (!self_exe)\n      break;\n\n    if (!fgets(buffer1, sizeof(buffer1), self_exe))\n      break;\n\n    resolved = realpath(buffer1, buffer2);\n    if (!resolved)\n      break;\n\n    length = (int)strlen(resolved);\n    if (length <= capacity)\n    {\n      memcpy(out, resolved, length);\n\n      if (dirname_length)\n      {\n        int i;\n\n        for (i = length - 1; i >= 0; --i)\n        {\n          if (out[i] == '/')\n          {\n            *dirname_length = i;\n            break;\n          }\n        }\n      }\n    }\n  }\n\n  fclose(self_exe);\n\n  return ok ? length : -1;\n}\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer[PATH_MAX];\n  char* resolved = NULL;\n  int length = -1;\n\n  for(;;)\n  {\n    Dl_info info;\n\n    if (dladdr(WAI_RETURN_ADDRESS(), &info))\n    {\n      resolved = realpath(info.dli_fname, buffer);\n      if (!resolved)\n        break;\n\n      length = (int)strlen(resolved);\n      if (length <= capacity)\n      {\n        memcpy(out, resolved, length);\n\n        if (dirname_length)\n        {\n          int i;\n\n          for (i = length - 1; i >= 0; --i)\n          {\n            if (out[i] == '/')\n            {\n              *dirname_length = i;\n              break;\n            }\n          }\n        }\n      }\n    }\n\n    break;\n  }\n\n  return length;\n}\n\n#elif defined(__DragonFly__) || defined(__FreeBSD__) || \\\n      defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__)\n\n#include <limits.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/types.h>\n#include <sys/sysctl.h>\n#include <dlfcn.h>\n#include <stdbool.h>\n\n#if defined(__OpenBSD__)\n\n#include <unistd.h>\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer1[4096];\n  char buffer2[PATH_MAX];\n  char buffer3[PATH_MAX];\n  char** argv = (char**)buffer1;\n  char* resolved = NULL;\n  int length = -1;\n  bool ok;\n\n  for (ok = false; !ok; ok = true)\n  {\n    int mib[4] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV };\n    size_t size;\n\n    if (sysctl(mib, 4, NULL, &size, NULL, 0) != 0)\n        break;\n\n    if (size > sizeof(buffer1))\n    {\n      argv = (char**)WAI_MALLOC(size);\n      if (!argv)\n        break;\n    }\n\n    if (sysctl(mib, 4, argv, &size, NULL, 0) != 0)\n        break;\n\n    if (strchr(argv[0], '/'))\n    {\n      resolved = realpath(argv[0], buffer2);\n      if (!resolved)\n        break;\n    }\n    else\n    {\n      const char* PATH = getenv(\"PATH\");\n      if (!PATH)\n        break;\n\n      size_t argv0_length = strlen(argv[0]);\n\n      const char* begin = PATH;\n      while (1)\n      {\n        const char* separator = strchr(begin, ':');\n        const char* end = separator ? separator : begin + strlen(begin);\n\n        if (end - begin > 0)\n        {\n          if (*(end -1) == '/')\n            --end;\n\n          if (((end - begin) + 1 + argv0_length + 1) <= sizeof(buffer2))\n          {\n            memcpy(buffer2, begin, end - begin);\n            buffer2[end - begin] = '/';\n            memcpy(buffer2 + (end - begin) + 1, argv[0], argv0_length + 1);\n\n            resolved = realpath(buffer2, buffer3);\n            if (resolved)\n              break;\n          }\n        }\n\n        if (!separator)\n          break;\n\n        begin = ++separator;\n      }\n\n      if (!resolved)\n        break;\n    }\n\n    length = (int)strlen(resolved);\n    if (length <= capacity)\n    {\n      memcpy(out, resolved, length);\n\n      if (dirname_length)\n      {\n        int i;\n\n        for (i = length - 1; i >= 0; --i)\n        {\n          if (out[i] == '/')\n          {\n            *dirname_length = i;\n            break;\n          }\n        }\n      }\n    }\n  }\n\n  if (argv != (char**)buffer1)\n    WAI_FREE(argv);\n\n  return ok ? length : -1;\n}\n\n#else\n\nWAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer1[PATH_MAX];\n  char buffer2[PATH_MAX];\n  char* path = buffer1;\n  char* resolved = NULL;\n  int length = -1;\n  bool ok;\n\n  for (ok = false; !ok; ok = true)\n  {\n#if defined(__NetBSD__)\n    int mib[4] = { CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME };\n#else\n    int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };\n#endif\n    size_t size = sizeof(buffer1);\n\n    if (sysctl(mib, 4, path, &size, NULL, 0) != 0)\n        break;\n\n    resolved = realpath(path, buffer2);\n    if (!resolved)\n      break;\n\n    length = (int)strlen(resolved);\n    if (length <= capacity)\n    {\n      memcpy(out, resolved, length);\n\n      if (dirname_length)\n      {\n        int i;\n\n        for (i = length - 1; i >= 0; --i)\n        {\n          if (out[i] == '/')\n          {\n            *dirname_length = i;\n            break;\n          }\n        }\n      }\n    }\n  }\n\n  return ok ? length : -1;\n}\n\n#endif\n\nWAI_NOINLINE WAI_FUNCSPEC\nint WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length)\n{\n  char buffer[PATH_MAX];\n  char* resolved = NULL;\n  int length = -1;\n\n  for(;;)\n  {\n    Dl_info info;\n\n    if (dladdr(WAI_RETURN_ADDRESS(), &info))\n    {\n      resolved = realpath(info.dli_fname, buffer);\n      if (!resolved)\n        break;\n\n      length = (int)strlen(resolved);\n      if (length <= capacity)\n      {\n        memcpy(out, resolved, length);\n\n        if (dirname_length)\n        {\n          int i;\n\n          for (i = length - 1; i >= 0; --i)\n          {\n            if (out[i] == '/')\n            {\n              *dirname_length = i;\n              break;\n            }\n          }\n        }\n      }\n    }\n\n    break;\n  }\n\n  return length;\n}\n\n#else\n\n#error unsupported platform\n\n#endif\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "dependencies/whereami/whereami/src/whereami.h",
    "content": "// (‑●‑●)> dual licensed under the WTFPL v2 and MIT licenses\n//   without any warranty.\n//   by Gregory Pakosz (@gpakosz)\n// https://github.com/gpakosz/whereami\n\n#ifndef WHEREAMI_H\n#define WHEREAMI_H\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#ifndef WAI_FUNCSPEC\n  #define WAI_FUNCSPEC\n#endif\n#ifndef WAI_PREFIX\n#define WAI_PREFIX(function) wai_##function\n#endif\n\n/**\n * Returns the path to the current executable.\n *\n * Usage:\n *  - first call `int length = wai_getExecutablePath(NULL, 0, NULL);` to\n *    retrieve the length of the path\n *  - allocate the destination buffer with `path = (char*)malloc(length + 1);`\n *  - call `wai_getExecutablePath(path, length, NULL)` again to retrieve the\n *    path\n *  - add a terminal NUL character with `path[length] = '\\0';`\n *\n * @param out destination buffer, optional\n * @param capacity destination buffer capacity\n * @param dirname_length optional recipient for the length of the dirname part\n *   of the path.\n *\n * @return the length of the executable path on success (without a terminal NUL\n * character), otherwise `-1`\n */\nWAI_FUNCSPEC\nint WAI_PREFIX(getExecutablePath)(char* out, int capacity, int* dirname_length);\n\n/**\n * Returns the path to the current module\n *\n * Usage:\n *  - first call `int length = wai_getModulePath(NULL, 0, NULL);` to retrieve\n *    the length  of the path\n *  - allocate the destination buffer with `path = (char*)malloc(length + 1);`\n *  - call `wai_getModulePath(path, length, NULL)` again to retrieve the path\n *  - add a terminal NUL character with `path[length] = '\\0';`\n *\n * @param out destination buffer, optional\n * @param capacity destination buffer capacity\n * @param dirname_length optional recipient for the length of the dirname part\n *   of the path.\n *\n * @return the length of the module path on success (without a terminal NUL\n * character), otherwise `-1`\n */\nWAI_FUNCSPEC\nint WAI_PREFIX(getModulePath)(char* out, int capacity, int* dirname_length);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // #ifndef WHEREAMI_H\n"
  },
  {
    "path": "docs/CNAME",
    "content": "gsmartcontrol.shaduri.dev"
  },
  {
    "path": "docs/_config.yml",
    "content": "remote_theme: clearpathrobotics/jekyll-rtd-theme\n\ntitle: GSmartControl\ndescription: Hard disk drive and SSD health inspection tool\n\n# Sitemap\nurl: \"https://gsmartcontrol.shaduri.dev\" # the base hostname & protocol for your site\nplugins:\n  - jekyll-sitemap\n\nexclude:\n  - CNAME\n"
  },
  {
    "path": "docs/_includes/extra/styles.scss",
    "content": "\n/* Hide sidebar footer with GitHub links */\ndiv.addons-wrap {\n\tdisplay: none !important;\n}\n\n/* Hide page breadcrumbs */\ndiv.navigation-top {\n\tdisplay: none !important;\n}\n\n/* Hide a line after page breadcrumbs */\ndiv.content > hr {\n\tdisplay: none !important;\n}\n\n/* Customize navbar links */\na.caption {\n\ttext-transform: none !important;\n\tpadding-left: 12px !important;\n\tpadding: .5em !important;\n\tcolor: #fff !important;\n\tfont-weight: normal !important;\n}\n\n"
  },
  {
    "path": "docs/downloads.md",
    "content": "---\ntitle: \"Downloads\"\npermalink: /downloads\n---\n\n# Downloads\n\n**Note to article writers:** When posting GSmartControl download links, please\nlink to this page instead of the individual files below. This way the users will always get\nthe latest version.\n\n\n## Binary and Distribution-specific Packages\n\n### Linux\n\nMost Linux distributions already include GSmartControl in their repositories.\n[Repology](https://repology.org/project/gsmartcontrol/versions) maintains a list\nof GSmartControl packages available in each distribution. \n\nIf the package contained in your favourite distribution or repository is not up-to-date yet, our own\n[OBS Project Directory](http://download.opensuse.org/repositories/home:/alex_sh:/gsmartcontrol:/stable_latest/)\ncontains the latest GSmartControl packages for a number of Linux distributions. Please\n[select the distribution](https://software.opensuse.org//download.html?project=home%3Aalex_sh%3Agsmartcontrol%3Astable_latest&package=gsmartcontrol)\nto install these packages.\n\n### Windows\n\n#### Windows 7 or Later\n\n- 64-bit installer **(use this if unsure)**: [gsmartcontrol-2.0.2-win64.exe](https://github.com/ashaduri/gsmartcontrol/releases/download/v2.0.2/gsmartcontrol-2.0.2-win64.exe).\n\n- 64-bit zip (portable): [gsmartcontrol-2.0.2.win64.zip](https://github.com/ashaduri/gsmartcontrol/releases/download/v2.0.2/gsmartcontrol-2.0.2-win64.zip).\n\n- 32-bit installer: [gsmartcontrol-2.0.2-win32.exe](https://github.com/ashaduri/gsmartcontrol/releases/download/v2.0.2/gsmartcontrol-2.0.2-win32.exe).\n\n- 32-bit zip (portable): [gsmartcontrol-2.0.2-win32.zip](https://github.com/ashaduri/gsmartcontrol/releases/download/v2.0.2/gsmartcontrol-2.0.2-win32.zip).\n\n#### Outdated: Windows XP, Vista, 2000, 2003\n\nThe last version of GSmartControl that supports Windows XP, Vista, 2000, and 2003\nis **0.9.0**:\n- Outdated 32-bit installer: [gsmartcontrol-0.9.0.exe](https://github.com/ashaduri/gsmartcontrol/releases/download/v0.9.0/gsmartcontrol-0.9.0.exe).\n- Outdated 32-bit zip (portable): [gsmartcontrol-0.9.0-win32.zip](https://github.com/ashaduri/gsmartcontrol/releases/download/v0.9.0/gsmartcontrol-0.9.0-win32.zip).\n\n\n### FreeBSD\n\nGSmartControl is [available](http://www.freshports.org/sysutils/gsmartcontrol) in the ports system.\nUse `cd /usr/ports/sysutils/gsmartcontrol/ && make install clean` to install the port.\nUse `pkg_add -r gsmartcontrol` to add the package.\n\n\n### macOS / Darwin\n\n- The [Homebrew](https://brew.sh/) project maintains a\n[package for GSmartControl](https://formulae.brew.sh/formula/gsmartcontrol).\nSee the [Usage](usage.md) page for information on how to run it.\n- The [MacPorts](https://www.macports.org/) project also maintains a\n[package for GSmartControl](https://github.com/macports/macports-ports/blob/master/sysutils/gsmartcontrol/Portfile). \n\n\n### Live CD / DVD / USB / ...\n\n- [GParted Live](http://gparted.org/livecd.php) is an excellent bootable distribution\nwhich includes GSmartControl. \n\n\n## Source Code\n\nThe latest source package:\n[gsmartcontrol-2.0.2.tar.gz](https://github.com/ashaduri/gsmartcontrol/archive/refs/tags/v2.0.2.tar.gz) \\\nSHA1 sum: 3976f591d17c2007587b9bda27c33d0362721db3\n\nIf you're interested in development, you can check the\n[GitHub Project](https://github.com/ashaduri/gsmartcontrol) page.\n\n\n## Older Versions\nCheck the [GitHub Releases](https://github.com/ashaduri/gsmartcontrol/releases) page for older releases.\n"
  },
  {
    "path": "docs/github.md",
    "content": "---\ntitle: \"GitHub Page\"\npermalink: /github\n---\n\n# GSmartControl at GitHub\n\nPlease visit GSmartControl's [GitHub project](https://github.com/ashaduri/gsmartcontrol) page\nfor source code, issue tracker, and more.\n\n**Note:** If you want to report an issue or suggest a feature, please visit the\n[Support](support.md) page first.\n"
  },
  {
    "path": "docs/index.md",
    "content": "---\ntitle: \"Home\"\npermalink: /\nnav_exclude: false\nnav_order: 1\n---\n\n# GSmartControl\n\n***Hard disk drive and SSD health inspection tool***\n\n[comment]: <> (Same contents as README.md, but without the badges)\n\n---\n\n[GSmartControl](https://gsmartcontrol.shaduri.dev)\nis a graphical user interface for smartctl (from [smartmontools](https://www.smartmontools.org/)\npackage), which is a tool for\nquerying and controlling [SMART](https://en.wikipedia.org/wiki/Self-Monitoring,_Analysis_and_Reporting_Technology)\n(Self-Monitoring, Analysis, and Reporting\nTechnology) data on modern hard disk and solid-state drives. It allows you to\ninspect the drive's SMART data to determine its health, as well as run various\ntests on it.\n\n\n## Downloads\n\nThe [Downloads](https://gsmartcontrol.shaduri.dev/downloads) page contains\nall the available packages of GSmartControl.\n\n\n## Features\n- automatically reports and highlights any anomalies;\n- allows enabling/disabling SMART;\n- supports configuration of global and per-drive options for smartctl;\n- performs SMART self-tests;\n- displays drive identity information, capabilities, attributes, device statistics, etc.;\n- can read in smartctl output from a saved file, interpreting it as a read-only virtual device;\n- works on most smartctl-supported operating systems;\n- has extensive help information.\n\n\n### Supported Hardware\n\nGSmartControl supports SATA, PATA, and NVMe drives, as well as drives\nbehind some USB bridges and RAID controllers.\nPlease see the\n[Supported Hardware](https://gsmartcontrol.shaduri.dev/supported-hardware) page\nfor more information.\n\n\n### Supported Platforms\n\nGSmartControl supports all major desktop operating systems, including\nLinux, Windows, macOS, FreeBSD, and other BSD-style operating systems.\nPlease see the\n[Software Requirements](https://gsmartcontrol.shaduri.dev/software-requirements) page\nfor more information.\n\n\n## Copyright and Licensing\n\nGSmartControl is Copyright (C) 2008 - 2025 Alexander Shaduri [ashaduri@gmail.com](mailto:ashaduri@gmail.com) and contributors.\n\nGSmartControl is licensed under the terms of\n[GNU General Public License Version 3](https://www.gnu.org/licenses/gpl-3.0.en.html).\n\nThis program is free software: you can redistribute it and/or modify it under\nthe terms of version 3 of the GNU General Public License as published by the\nFree Software Foundation.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY\nWARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR\nA PARTICULAR PURPOSE. See the GNU General Public Licenses for more details.\n\nThis product includes icons from Oxygen Icons copyright [KDE](https://kde.org)\nand licensed under the [GNU LGPL version 3](https://www.gnu.org/licenses/lgpl-3.0.en.html) or later.\n"
  },
  {
    "path": "docs/screenshots.md",
    "content": "---\ntitle: \"Screenshots\"\npermalink: /screenshots\n---\n\n# Screenshots\n\n**Note:** Some of these screenshots may have been taken with older versions\nof GSmartControl.\n\n## Main window - All drives pass the health self-check\n\n![3ware RAID](screenshots/main_ok.png)\n\n\n## Main window - One of the drives is failing\n\n![3ware RAID](screenshots/main_failing.png)\n\n\n## Drive information window - General information\n\n![3ware RAID](screenshots/info_identity.png)\n\n\n## Drive information window - A failing drive, attribute list\n\n![3ware RAID](screenshots/info_failing.png)\n\n\n## Drive information window - Statistics\n\n![3ware RAID](screenshots/info_stats.png)\n\n\n## A test in progress\n\n![3ware RAID](screenshots/info_testing.png)\n\n\n## Twelve 1TB drives behind a single 3ware RAID controller\n\n![3ware RAID](screenshots/3ware-raid.png)\n\n"
  },
  {
    "path": "docs/smart.md",
    "content": "---\ntitle: \"What Is Smart?\"\npermalink: /what-is-smart\n---\n\n# What is SMART?\n\nSMART is a technology which provides hard disk drives with methods to\npredict certain kinds of failures with certain chance of success.\n\n## A Detailed Answer\n\nSelf-Monitoring, Analysis, and Reporting Technology, or SMART, is a\nmonitoring system for hard drives to detect and report various indicators\nof reliability, in the hope of anticipating failures. SMART is implemented\ninside the drives, providing several ways of monitoring the drive health.\nIt may present information about general health, various drive attributes\n(for example, number of unreadable sectors), error logs, and so on.\nIt may also provide ways to instruct the drive to run various self-tests,\nwhich may report valuable information. It may even automatically scan\nthe disk surface in when the drive is idle, repairing the defects while\nreallocating the data to more safe areas.\n\nWhile having SMART sounds perfect, there are some nuances to\nconsider. One of the common pitfalls is that it may create a false sense\nof security. That is, a perfectly good SMART data is NOT an indication\nthat the drive won't fail the next minute. The reverse is also true - some\ndrives may function perfectly even with not-so-good-looking SMART\ndata. However, as studies indicate, given a large population of drives,\nsome SMART attributes may reliably predict drive failures within up to\ntwo months.\n\nAnother common mistake is to assume that the attribute values are\nthe real physical values, as experienced by the drive. As manufacturers\ndo not necessarily agree on precise attribute definitions and\nmeasurement units, the exact meaning of the attributes may vary\ngreatly across different drive models.\n\nAt present SMART is implemented individually by manufacturers.\nWhile some aspects are standardized for compatibility, others are not.\nIn fact, most manufacturers refer the users to their own health\nmonitoring utilities and advice against taking SMART data seriously.\nNevertheless, SMART may prove an effective measure against data loss.\n\nYet another issue is that quite often the drives have bugs which\nprevent correct SMART usage. This is usually due to buggy firmware,\nor the manufacturer ignoring the standards. Luckily, smartmontools\nusually detects these bugs and works around them.\n"
  },
  {
    "path": "docs/smartctl_man.html",
    "content": "<html>\n\t<head>\n\t\t<!-- The program contains a link to this file, which redirects to the actual page -->\n\t\t<meta http-equiv=\"refresh\" content=\"0; url=https://www.smartmontools.org/browser/trunk/smartmontools/smartctl.8.in\" />\n\t</head>\n</html>\n"
  },
  {
    "path": "docs/software_requirements.md",
    "content": "---\ntitle: \"Software Requirements\"\npermalink: /software-requirements\n---\n\n# Software Requirements\n\n**Note:** If using the official Windows packages, no additional software is required.\n\n## Supported Operating Systems\n* Linux\n* Windows (Vista SP2 or later)\n  * **Note:** The Windows port uses `pd0`, (`pd1`, ...) devices\n  for physical drives 0, (1, ...).\n* FreeBSD\n* NetBSD\n* OpenBSD\n* DragonFlyBSD\n* macOS\n* Solaris\n* QNX (code written but no testing has been performed yet).\n\n## Build Requirements\n**Note:** These are required only if you're building GSmartControl from source code.\n* [GTK+ 3](https://www.gtk.org) library, version 3.4 or higher.\n* [Gtkmm](https://www.gtkmm.org) library, version 3.4 or higher.\n* C++20 Compiler (GCC 11 or later, Clang/libc++ 17 or later, Apple Clang 15 or later)\n* [CMake](https://cmake.org), version 3.14 or higher.\n\n### Building from Source\n1. Install the dependencies using your package manager.\n2. Clone or extract GSmartControl source code. We assume the directory is named `gsmartcontrol`.\n3. Build GSmartControl using the following commands:\n```bash\ncd gsmartcontrol\nmkdir build\ncd build\ncmake .. -DCMAKE_BUILD_TYPE=Release\nmake\n```\n\n## Runtime Requirements\n**Note:** The Windows packages already include all the required software. \n* [Smartmontools](https://www.smartmontools.org/). Windows users have an option to\ninstall a separate version of smartmontools on their systems, and GSmartControl will automatically use it.\n* xterm (optional, needed to run `update-smart-drivedb` on Linux / Unix systems).\n"
  },
  {
    "path": "docs/support.md",
    "content": "---\ntitle: \"Support\"\npermalink: /support\n---\n\n# Support\n\n\n## Reporting Bugs\n\nPlease report issues at GSmartControl's\n[Issue Tracker](https://github.com/ashaduri/gsmartcontrol/issues) on GitHub.\n\n\n## Before Filing an Issue\n\nPlease see the [Troubleshooting](troubleshooting.md) page before reporting any issues.\n\nIf it is a SMART or drive-related problem, please try to test it with smartctl first.\nChances are, the problem you're experiencing is not tied to GSmartControl,\nbut is a drive firmware or smartctl problem. For example, to see complete\ninformation about your `/dev/sda` drive, type the following in a terminal\nemulator (as `root`, using `sudo` or `su`):\n```\nsmartctl -x /dev/sda\n```\n**Note:** If using Windows, the device name should be `/dev/pd1` for the\nsecond physical drive, etc. Run `cmd` as administrator first.\n\nIf you still think it's a GSmartControl issue, please collect the following\ninformation about your system:\n\n- Which operating system you use (for example, openSUSE Leap 15.5).\n- Which version of GTK and Gtkmm you have installed. Finding this out is very\ndistribution-specific. For example, on openSUSE it would be `rpm -q gtk3 gtkmm3`.\nSome distributions have `gtkmm30` instead. You may also search them in your \ndistribution's graphical package manager, if there is one.\n- Execution log from the program, if possible. To obtain it, run the program\nwith `-v` option, e.g. (type the following in a terminal emulator or Run dialog):\n    ```\n    gsmartcontrol-root -v\n    ```\n- Perform the steps needed to reproduce the bug, then go to\n\"Options -> View Execution Log\", and click \"Save All\".\n**Note:** On Windows, `-v` switch is on by default.\n- Detailed description of steps you performed when the bug occurred.\n\n\n## Contact\nYou may contact me (Alexander Shaduri) directly at [ashaduri@gmail.com](mailto:ashaduri@gmail.com).\n\n"
  },
  {
    "path": "docs/supported_hardware.md",
    "content": "---\ntitle: \"Supported Hardware\"\npermalink: /supported-hardware\n---\n\n# Supported Hardware\n\nGSmartControl supports a wide range of hard disk and solid-state drives:\n- ATA (both SATA and PATA) drives\n- NVMe drives\n- ATA and NVMe drives behind some USB bridges. See\n[USB Devices and Smartmontools](https://www.smartmontools.org/wiki/USB)\nfor more information.\n- ATA drives behind some RAID controllers:\n  - Adaptec (Linux, selected models only)\n  - Areca (Linux, Windows)\n  - HP CCISS (Linux)\n  - HP hpsa / hpahcisr (Linux)\n  - Intel Matrix Storage (CSMI) (Linux, Windows, FreeBSD)\n  - LSI 3ware (Linux, Windows)\n  - LSI MegaRAID (Windows)\n\n**Note:** Smartmontools supports even\n[more RAID Controllers](https://www.smartmontools.org/wiki/Supported_RAID-Controllers).\nThe drives behind such controllers can be manually entered in GSmartControl using\n<tt>Add Device...</tt> functionality or `--add-device` command-line option.\n\nSCSI / SAS drives are **not** supported by GSmartControl, but the application\nmay still display some information about them.\n"
  },
  {
    "path": "docs/troubleshooting.md",
    "content": "---\ntitle: \"Troubleshooting\"\npermalink: /troubleshooting\n---\n\n# Troubleshooting\n\nPlease see the [Support](support.md) page for information on how to report issues. \n\n\n## Known Limitations\n\n- Only ATA drives (both PATA and SATA), NVMe drives, various USB to ATA\nbridges, and drives behind some RAID controllers are supported for now.\nThe main reasons for this are:\n  - We can't support drives which don't work with smartmontools.\n  This affects drives which don't support SMART or don't export SMART data\n  correctly (e.g. some USB enclosures, RAIDs, etc.).\n  - SCSI drives are rarely found in desktop systems and the servers rarely\n  have X11 / Gtkmm running, so this is a low priority task.\n- Immediate Offline Tests are not supported due to being mostly obsolete.\n\n\n## Custom Smartctl Options\nGSmartControl tries its best to guard the user from having to specify smartctl options.\nHowever, this is not always possible due to drive firmware bugs, unimplemented\nfeatures, and so on.\n\nGSmartControl provides the ability to specify custom options to smartctl. The\n[smartctl manual page](https://www.smartmontools.org/browser/trunk/smartmontools/smartctl.8.in)\ncontains detailed information on these options. Additional information is available\nat [smartmontools.org](https://smartmontools.org).\n\n\n## Permission Problems\nYou need to have root / Administrator privileges to perform anything useful with GSmartControl.\nThis is needed because most operating systems prohibit direct access to\nhardware to users with non-administrative privileges.\n\nIn Windows, UAC is automatically invoked when you run it. In Linux / Unix operating\nsystems, running `gsmartcontrol-root` (or using the desktop icon) will\nautomatically launch GSmartControl using the system's preferred su\nmechanism - `PolKit`, `kdesu`, `gnomesu`, etc.\n\nPlease **do not** set the `setuid` flag on smartctl binary. It is considered\na security risk.\n\n\n## SMART Does Not Stay Enabled\nSpecifications say that once you set a SMART-related property, it will \nbe preserved across reboots. For example, when you enable SMART and \nAutomatic Offline Data Collection, both will stay enabled until you disable them.\n\nHowever, BIOS / UEFI, your operating system, your other operating systems\n(if present), and various startup programs may affect that. For example,\nUEFI may enable SMART each time you start your computer, so if you \ndisabled SMART previously, it will be re-enabled on reboot.\n\nThe easiest way to work around this is to set the desired settings on\nsystem startup. You may use `smartctl` or `smartd` to do that. For example,\nto enable both SMART and Automatic Offline Data Collection on `/dev/sda`,\none would write the following to the system startup script (e.g. `boot.local`,\n`rc.local` or similar on Linux):\n```\nsmartctl -s on -o on /dev/sda\n```\nFor more information, see `smartctl` and `smartd` [documentation](https://smartmontools.org).\n"
  },
  {
    "path": "docs/usage.md",
    "content": "---\ntitle: \"Usage\"\npermalink: /usage\n---\n\n# Usage\n\n## Launching GSmartControl on Desktop\n\n### Linux and Unix\nOn Linux and Unix systems, please use the desktop menu entry. Alternatively, you can\nrun `gsmartcontrol-root`, which invokes GSmartControl using your desktop's `su` mechanism.\n\n### Windows\nSimply install GSmartControl and run it from the Start menu.\n\n### macOS\nAfter being installed using Homebrew, GSmartControl can be run by\ntyping `gsmartcontrol` in Terminal. If all you get is `Command not found`, please run\na `brew doctor` command first.\n\n\n## Command-Line Options\n\n**Note:** The Windows version may not output any text to a command-line window,\nso `--help` and similar options will be of no help.\n\nThe most important options are:\n\n`-?`, `--help` - Show help options.\n\n`-V`, `--version` - Display version information.\n\n`--no-scan` - Don't scan devices on startup.\n\n`--add-virtual <file>` - Load smartctl data from file, creating a virtual drive. You\ncan specify this option multiple times.\n\n`--add-device <device>::<type>[::<extra_args>]` - Add a device to device list.\nThis option is useful with `--no-scan` to list certain drives only. You can specify\nthis option multiple times.\nExample:  \n`--add-device /dev/sda --add-device /dev/twa0::3ware,2 --add-device\n'/dev/sdb::::-T permissive'`.\n\n`--forget-devices` - Forget all previously manually added devices.\n\n`-v`, `--verbose` - Enable verbose logging; same as `--verbosity-level 5`.\n\n`-q`, `--quiet` - Disable logging; same as `--verbosity-level 0`.\n\n`-b`, `--verbosity-level` - Set verbosity level \\[0-5].\n\n\n### Advanced Options\n\n`-l`, `--no-locale` - Don't use system locale.\n\n\n## Smartctl Options\n\nGSmartControl provides the ability to specify custom options to smartctl. The\n[smartctl manual page](https://www.smartmontools.org/browser/trunk/smartmontools/smartctl.8.in)\ncontains detailed information on these options. Additional information is available\nat [smartmontools.org](https://smartmontools.org).\n\n\n## Windows Installer Options\n\nGSmartControl's Windows installer is based on NSIS and supports all its options.\nPlease see the [NSIS documentation](https://nsis.sourceforge.io/Docs/Chapter3.html#installerusage)\nfor the list of supported options.\n\nFor example, to run installer in silent mode, use `/S` option:\n```\ngsmartcontrol-<version>-win64.exe /S\n```\n"
  },
  {
    "path": "packaging/CMakeLists.txt",
    "content": "\n# Variables for nsis .in files\nset(WINDOWS_SUFFIX \"win32\")\nif (CMAKE_SIZEOF_VOID_P EQUAL 8)\n\tset(WINDOWS_SUFFIX \"win64\")\nendif()\nconfigure_file(\"nsis/distribution.in.txt\" \"nsis/distribution.txt\" ESCAPE_QUOTES @ONLY NEWLINE_STYLE DOS)\n# configure_file(\"nsis/gsmartcontrol.in.nsi\" \"nsis/gsmartcontrol.nsi\" ESCAPE_QUOTES @ONLY NEWLINE_STYLE DOS)\n\n# NSIS helpers\nif (WIN32)\n\tinstall(FILES \"${CMAKE_CURRENT_BINARY_DIR}/nsis/distribution.txt\" TYPE DOC)\n\n\t# nsis file, only for cross-compilation\n#\tif (NOT ${CMAKE_HOST_SYSTEM_NAME} STREQUAL \"Windows\")\n#\t\tinstall(FILES \"${CMAKE_CURRENT_BINARY_DIR}/nsis/gsmartcontrol.nsi\" DESTINATION .)\n#\tendif()\nendif()\n\n\n# OBS Debian\n#configure_file(\"obs_debian/changelog.in\" \"obs_debian/changelog\" ESCAPE_QUOTES @ONLY)\n\n# OBS RPM\n#configure_file(obs_rpm/gsmartcontrol.in.spec obs_rpm/gsmartcontrol.spec)\n\n"
  },
  {
    "path": "packaging/cpack_options.cmake",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n# CPack options for this project\n\n# Generators supported:\n# Linux: TBZ2 (binary)\n# Windows: NSIS ZIP.\n\n# Per-generator settings\n#set(CPACK_PROJECT_CONFIG_FILE \"${CMAKE_SOURCE_DIR}/data/cmake/cpack_project_config.cmake\")\n\nif (UNIX)\n\t# Make the \"package\" target build .tar.bz2\n\tset(CPACK_GENERATOR \"TBZ2\")\nendif()\n\nif (WIN32)\n\t# Make the \"package\" target build all supported packages (nsis and zip)\n\tset(CPACK_GENERATOR \"\")\nendif()\n\nset(APP_PACKAGE_NAME_DISPLAY \"GSmartControl\")\n#if (WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 8)\n#\tset(APP_PACKAGE_NAME_DISPLAY \"${APP_PACKAGE_NAME_DISPLAY} (64-Bit)\")\n#endif()\n\nset(CPACK_PACKAGE_NAME \"${PROJECT_NAME}\")\nif (WIN32)\n\t# Default value is \"${CPACK_PACKAGE_NAME} ${CPACK_PACKAGE_VERSION}\".\n\tset(CPACK_PACKAGE_INSTALL_DIRECTORY \"${CPACK_PACKAGE_NAME}\")  # appended to e.g. CPACK_NSIS_INSTALL_ROOT\n\tset(CPACK_PACKAGE_INSTALL_REGISTRY_KEY \"${CPACK_PACKAGE_INSTALL_DIRECTORY}\")\nendif()\n\nset(CPACK_PACKAGE_VENDOR \"Alexander Shaduri\")\n\n#set(CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_PROJECT_VERSION_MAJOR})\n#set(CPACK_PACKAGE_VERSION_MINOR ${CMAKE_PROJECT_VERSION_MINOR})\n#set(CPACK_PACKAGE_VERSION_PATCH ${CMAKE_PROJECT_VERSION_PATCH})\n\n# Used by wix only?\nset(APP_PACKAGE_DESCRIPTION_FILE \"${CMAKE_BINARY_DIR}/packaging/nsis/distribution.txt\")\n#if (EXISTS \"${APP_PACKAGE_DESCRIPTION_FILE}\")\n\tset(CPACK_PACKAGE_DESCRIPTION_FILE \"${APP_PACKAGE_DESCRIPTION_FILE}\")\n#endif()\n\nset(CPACK_PACKAGE_DESCRIPTION_SUMMARY \"${CMAKE_PROJECT_DESCRIPTION}\")\n\n# Set to win32 or win64 by default, or CMAKE_SYSTEM_NAME for others.\n#set(CPACK_SYSTEM_NAME \"${APP_TARGET_SYSTEM_NAME}\")\n\n#if (WIN32)\n#\tset(CPACK_PACKAGE_FILE_NAME \"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${CPACK_SYSTEM_NAME}\")\n#endif()\n\nset(CPACK_RESOURCE_FILE_LICENSE \"${CMAKE_SOURCE_DIR}/LICENSE.txt\")\nset(CPACK_STRIP_FILES TRUE)\nset(CPACK_SOURCE_STRIP_FILES FALSE)\n\n# Icon in Add/Remove Programs\nset(CPACK_PACKAGE_ICON \"${CMAKE_SOURCE_DIR}\\\\\\\\data\\\\\\\\gsmartcontrol.ico\")\n\n# Start menu shortcut\nset(CPACK_PACKAGE_EXECUTABLES \"gsmartcontrol;${APP_PACKAGE_NAME_DISPLAY}\")\n\n# Force monolithic installers (ignore per-component stuff, exclude optional components).\nset(CPACK_MONOLITHIC_INSTALL true)\n\nSET(CPACK_SOURCE_IGNORE_FILES\n#\t\"/0.*\"  # build dirs (broken if full path contains 0)\n\t\"/\\\\\\\\.idea/\"\n\t\"/doxygen_doc/\"\n\t\"/cmake-build-.*\"  # build dirs\n\t\"/\\\\\\\\.git/\"  # git\n\t\".*~$\"  # editor temporary files\n\t\"/CMakeLists\\\\\\\\.txt\\\\\\\\.user$\"\n\t\"/TODO$\"\n)\n\n\n\n# --- NSIS options\n\nset(CPACK_NSIS_DISPLAY_NAME \"${APP_PACKAGE_NAME_DISPLAY}\")  # used in add/remove programs as a display text\nset(CPACK_NSIS_PACKAGE_NAME \"${APP_PACKAGE_NAME_DISPLAY}\")  # used in installer window title, start menu folder\nif(CMAKE_SIZEOF_VOID_P EQUAL 8)\n\tset(CPACK_NSIS_INSTALL_ROOT \"$PROGRAMFILES64\")\nelse()\n\tset(CPACK_NSIS_INSTALL_ROOT \"$PROGRAMFILES\")\nendif()\n\nset(CPACK_NSIS_MUI_ICON \"${CMAKE_SOURCE_DIR}\\\\\\\\packaging\\\\\\\\nsis\\\\\\\\nsi_install.ico\")\nset(CPACK_NSIS_MUI_UNIICON \"${CMAKE_SOURCE_DIR}\\\\\\\\packaging\\\\\\\\nsis\\\\\\\\nsi_uninstall.ico\")\n\nset(CPACK_NSIS_HELP_LINK \"${PROJECT_HOMEPAGE_URL}\")\nset(CPACK_NSIS_URL_INFO_ABOUT \"${PROJECT_HOMEPAGE_URL}\")\nset(CPACK_NSIS_CONTACT \"${PROJECT_HOMEPAGE_URL}\")\nset(CPACK_NSIS_COMPRESSOR \"/SOLID lzma\")\nset(CPACK_NSIS_INSTALLED_ICON_NAME \"gsmartcontrol.exe\")\nset(CPACK_NSIS_EXECUTABLES_DIRECTORY \".\")  # Relative to installation directory, start menu uses this.\nset(CPACK_NSIS_MENU_LINKS \"gsmartcontrol\" \"${APP_PACKAGE_NAME_DISPLAY}\")\nset(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL \"ON\")  # Ask to uninstall before installing.\n\n# set(CPACK_NSIS_MUI_FINISHPAGE_RUN \"${APP_BRANDING_INTERNAL_APP_NAME}.exe\")  # may conflict with UAC, better not use it.\n\n# update-smart-drivedb may leave drivedb.h.*\nset(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS \"${CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS}\n\tDelete \\\\\\\"$INSTDIR\\\\\\\\drivedb.h.*\\\\\\\"\n\tDelete \\\\\\\"$INSTDIR\\\\\\\\*stderr.txt\\\\\\\"\n\tDelete \\\\\\\"$INSTDIR\\\\\\\\*stderr.old.txt\\\\\\\"\n\tDelete \\\\\\\"$INSTDIR\\\\\\\\*stdout.txt\\\\\\\"\n\tDelete \\\\\\\"$INSTDIR\\\\\\\\*stdout.old.txt\\\\\\\"\n\")\n\n\n\n# ---------------------------------------------- Windows Dependencies\n\n# Install GTK+ and other dependencies in Windows.\n# Requires installed smartctl-nc.exe, smartctl.exe, update-smart-drivedb.ps1 in bin subdirectory of sysroot.\n# The following packages when cross-compiling from opensuse:\n# mingw64-cross-gcc-c++ mingw64-gtkmm3-devel adwaita-icon-theme\nif (WIN32)\n\tmessage(STATUS \"CMAKE_FIND_ROOT_PATH: ${CMAKE_FIND_ROOT_PATH}\")\n\n\tset(APP_WINDOWS_SYSROOT \"\" CACHE STRING \"Location of system root for Windows binaries, for packing\")\n\tif (NOT APP_WINDOWS_SYSROOT)\n#\t\tif (${CMAKE_HOST_SYSTEM_NAME} STREQUAL \"Windows\")\n#\t\t\tif (CMAKE_SIZEOF_VOID_P EQUAL 8)\n#\t\t\t\tset(APP_WINDOWS_SYSROOT \"/mingw64\")\n#\t\t\telse()\n#\t\t\t\tset(APP_WINDOWS_SYSROOT \"/mingw32\")\n#\t\t\tendif()\n#\t\t\tfile(TO_CMAKE_PATH \"${APP_WINDOWS_SYSROOT}\" APP_WINDOWS_SYSROOT)\n#\t\telse()  # Cross-compiling\n\t\t\tset(APP_WINDOWS_SYSROOT \"${CMAKE_FIND_ROOT_PATH}\")\n#\t\tendif()\n\tendif()\n\tif (NOT APP_WINDOWS_SYSROOT)\n\t\tmessage(FATAL_ERROR \"APP_WINDOWS_SYSROOT or CMAKE_FIND_ROOT_PATH must be set to environment root directory when compiling for Windows.\")\n\tendif()\n\tmessage(STATUS \"APP_WINDOWS_SYSROOT: ${APP_WINDOWS_SYSROOT}\")\n\n\tset(APP_WINDOWS_GTK_ICONS_ROOT \"\" CACHE STRING \"Location of root folder for icons, for packing Windows packages\")\n\tif (NOT APP_WINDOWS_GTK_ICONS_ROOT)\n\t\tif (${CMAKE_HOST_SYSTEM_NAME} STREQUAL \"Windows\")\n\t\t\tset(APP_WINDOWS_GTK_ICONS_ROOT \"${APP_WINDOWS_SYSROOT}/share/icons\")\n\t\telse()  # Cross-compiling\n\t\t\tset(APP_WINDOWS_GTK_ICONS_ROOT \"/usr/share/icons\")\n\t\tendif()\n\tendif()\n\tmessage(STATUS \"APP_WINDOWS_GTK_ICONS_ROOT: ${APP_WINDOWS_GTK_ICONS_ROOT}\")\n\n\t# This is for non-CI builds (CI uses extracted files).\n\t# FIXME The logic may be wrong here.\n\tset(APP_WINDOWS_SMARTCTL_ROOT \"\" CACHE STRING \"Location of root folder for smartctl, for packing Windows packages\")\n\tif (NOT APP_WINDOWS_SMARTCTL_ROOT)\n\t\tif (${CMAKE_HOST_SYSTEM_NAME} STREQUAL \"Windows\")\n\t\t\t# PROGRAMFILES matches the bitness of the installer.\n\t\t\tif (DEFINED ENV{PROGRAMFILES})\n\t\t\t\tset(APP_WINDOWS_SMARTCTL_ROOT \"$ENV{PROGRAMFILES}/smartmontools\")\n\t\t\telseif (CMAKE_SIZEOF_VOID_P EQUAL 8)\n\t\t\t\tset(APP_WINDOWS_SMARTCTL_ROOT \"C:\\\\Program Files\\\\smartmontools\")\n\t\t\telse()  # 32-bit\n\t\t\t\tset(APP_WINDOWS_SMARTCTL_ROOT \"C:\\\\Program Files (x86)\\\\smartmontools\")\n\t\t\tendif ()\n\t\t\tfile(TO_CMAKE_PATH \"${APP_WINDOWS_SMARTCTL_ROOT}\" APP_WINDOWS_SMARTCTL_ROOT)\n\t\telse()  # Cross-compiling\n\t\t\tif (CMAKE_SIZEOF_VOID_P EQUAL 8)\n\t\t\t\tset(APP_WINDOWS_SMARTCTL_ROOT \"$ENV{HOME}/.wine/drive_c/Program Files/smartmontools\")\n\t\t\telse()\n\t\t\t\tset(APP_WINDOWS_SMARTCTL_ROOT \"$ENV{HOME}/.wine/drive_c/Program Files (x86)/smartmontools\")\n\t\t\tendif()\n\t\tendif()\n\tendif()\n\tmessage(STATUS \"APP_WINDOWS_SMARTCTL_ROOT: ${APP_WINDOWS_SMARTCTL_ROOT}\")\n\n\n\tset(WINDOWS_SUFFIX \"win32\")\n\tset(SMARTCTL_EXTRACED_BIN_DIR \"bin\")\n\tif (CMAKE_SIZEOF_VOID_P EQUAL 8)\n\t\tset(WINDOWS_SUFFIX \"win64\")\n\t\tset(SMARTCTL_EXTRACED_BIN_DIR \"bin64\")\n\tendif()\n\n\t# TODO unix2dos doc/*.txt\n\n\t# Smartmontools\n\tmessage(STATUS \"Checking if \\\"${CMAKE_BINARY_DIR}/smartmontools\\\" exists.\")\n\tif (IS_DIRECTORY \"${CMAKE_BINARY_DIR}/smartmontools\")  # CI\n\t\tmessage(STATUS \"Copying smartmontools files from \\\"${CMAKE_BINARY_DIR}/smartmontools\\\".\")\n\t\t# GitHub extract location. The files are extracted without relative paths in archive (7z e).\n\t\tinstall(FILES\n\t\t\t\"${CMAKE_BINARY_DIR}/smartmontools/drivedb.h\"\n\t\t\t\"${CMAKE_BINARY_DIR}/smartmontools/update-smart-drivedb.ps1\"\n\t\t\t\"${CMAKE_BINARY_DIR}/smartmontools/smartctl-nc.exe\"\n\t\t\t\"${CMAKE_BINARY_DIR}/smartmontools/smartctl.exe\"\n\t\t\tDESTINATION .\n\t\t)\n\telse()\n\t\t# System-installed smartmontools\n\t\tmessage(STATUS \"Assuming system-installed smartmontools in \\\"${APP_WINDOWS_SMARTCTL_ROOT}\\\".\")\n\t\tinstall(FILES\n\t\t\t\"${APP_WINDOWS_SMARTCTL_ROOT}/bin/drivedb.h\"\n\t\t\t\"${APP_WINDOWS_SMARTCTL_ROOT}/bin/smartctl-nc.exe\"\n\t\t\t\"${APP_WINDOWS_SMARTCTL_ROOT}/bin/smartctl.exe\"\n\t\t\t\"${APP_WINDOWS_SMARTCTL_ROOT}/bin/update-smart-drivedb.ps1\"\n\t\t\tDESTINATION .\n\t\t)\n\tendif()\n\n\t# GCC Runtime\n\tfile(GLOB MATCHED_FILES LIST_DIRECTORIES false \"${APP_WINDOWS_SYSROOT}/bin/libgcc_s_*.dll\")\n\tinstall(FILES ${MATCHED_FILES} DESTINATION .)\n\tfile(GLOB MATCHED_FILES LIST_DIRECTORIES false \"${APP_WINDOWS_SYSROOT}/bin/libstdc++-*.dll\")\n\tinstall(FILES ${MATCHED_FILES} DESTINATION .)\n\n\n\t#\tAll of GTK+\n\tset(GTK_FILES\n#\t\tgdk-pixbuf-query-loaders.exe\n\t\tgspawn-win32-helper-console.exe\n\t\tgspawn-win32-helper.exe\n\t\tgspawn-win64-helper-console.exe\n\t\tgspawn-win64-helper.exe\n\t\tgtk-query-settings.exe\n#\t\tgtk-query-immodules-3.0.exe\n#\t\tgtk-update-icon-cache-3.0.exe\n\n\t\tedit.dll\n\t\tlibarchive-*.dll\n\t\tlibasprintf-*.dll\n\t\tlibatk-*.dll\n\t\tlibatkmm-*.dll\n\t\tlibatomic-*.dll\n\t\tlibb2-*.dll\n\t\tlibbrotlicommon.dll\n\t\tlibbrotlidec.dll\n\t\tlibbrotlienc.dll\n\t\tlibbz2-*.dll\n\t\tlibcairo-*.dll\n\t\tlibcairo-gobject-*.dll\n\t\tlibcairo-script-interpreter-*.dll\n\t\tlibcairomm-*.dll\n\t\tlibcares-*.dll\n\t\tlibcharset-*.dll\n\t\tlibcrypto-*.dll\n\t\tlibcurl-*.dll\n\t\tlibdatrie-*.dll\n\t\tlibdeflate*.dll\n\t\tlibepoxy-*.dll\n\t\tlibexpat-*.dll\n\t\tlibffi-*.dll\n\t\tlibfontconfig-*.dll\n\t\tlibformw*.dll\n\t\tlibfreetype-*.dll\n\t\tlibfribidi-*.dll\n\t\tlibgailutil-*.dll\n\t\tlibgdk-*.dll\n\t\tlibgdkmm-*.dll\n\t\tlibgdk_pixbuf-*.dll\n\t\tlibgettextlib-*.dll\n\t\tlibgettextpo-*.dll\n\t\tlibgettextsrc-*.dll\n\t\tlibgif-*.dll\n\t\tlibgio-*.dll\n\t\tlibgiomm-*.dll\n\t\tlibgirepository-*.dll\n\t\tlibglib-*.dll\n\t\tlibglibmm-*.dll\n\t\tlibglibmm_generate_extra_defs-*.dll\n\t\tlibgmodule-*.dll\n\t\tlibgmp-*.dll\n\t\tlibgmpxx-*.dll\n\t\tlibgobject-*.dll\n\t\tlibgomp-*.dll\n\t\tlibgraphite*.dll\n\t\tlibgthread-*.dll\n\t\tlibgtk-*.dll\n\t\tlibgtkmm-*.dll\n\t\tlibharfbuzz-*.dll\n\t\tlibhistory*.dll\n\t\tlibhogweed-*.dll\n\t\tlibiconv-*.dll\n\t\tlibidn*.dll\n\t\tlibintl-*.dll\n\t\tlibisl-*.dll\n\t\tlibjansson-*.dll\n\t\tlibjemalloc.dll\n\t\tlibjbig-*.dll\n\t\tlibjpeg-*.dll\n\t\tlibjson-glib-*.dll\n\t\tlibjsoncpp-*.dll\n\t\tlibLerc*.dll\n\t\tliblz4.dll\n\t\tliblzma-*.dll\n\t\tliblzo2-*.dll\n\t\tlibmenuw*.dll\n\t\tlibmpc-*.dll\n\t\tlibmetalink-*.dll\n\t\tlibmpdec++-*.dll\n\t\tlibmpdec-*.dll\n\t\tlibmpfr-*.dll\n\t\tlibncurses*.dll\n\t\tlibnettle-*.dll\n\t\tlibnghttp2-*.dll\n\t\tlibp11-kit-*.dll\n\t\tlibpanelw*.dll\n\t\tlibpango-*.dll\n\t\tlibpangocairo-*.dll\n\t\tlibpangoft2-*.dll\n\t\tlibpangomm-*.dll\n\t\tlibpangowin32-*.dll\n\t\tlibpcre2-*.dll\n\t\tlibpixman-*.dll\n\t\tlibpng*-*.dll\n\t\tlibpsl-*.dll\n#\t\tlibpython*.dll\n\t\tlibquadmath-*.dll\n\t\tlibreadline*.dll\n\t\tlibrhash.dll\n\t\tlibrsvg-*.dll\n\t\tlibsharpyuv-*.dll\n\t\tlibsigc-*.dll\n\t\tlibsqlite3-*.dll\n\t\tlibssh2-*.dll\n#\t\tlibssl-*.dll\n\t\tlibssp-*.dll\n\t\tlibsystre-*.dll\n\t\tlibtasn*.dll\n\t\tlibtermcap-*.dll\n\t\tlibthai-*.dll\n\t\tlibtiff-*.dll\n\t\tlibtiffxx-*.dll\n\t\tlibtre-*.dll\n\t\tlibturbojpeg.dll\n\t\tlibunistring-*.dll\n\t\tlibuv-*.dll\n\t\tlibwebp-*.dll\n#\t\tlibwebpdecoder-*.dll\n#\t\tlibwebpdemux-*.dll\n#\t\tlibwebpmux-*.dll\n\t\tlibwinpthread-*.dll\n\t\tlibxml2-*.dll\n\t\tlibzstd.dll\n#\t\ttcl86.dll\n#\t\ttk86.dll\n\t\tzlib*.dll\n\t)\n\tforeach(pattern ${GTK_FILES})\n\t\tfile(GLOB MATCHED_FILES\n\t\t\tLIST_DIRECTORIES false\n\t\t\t\"${APP_WINDOWS_SYSROOT}/bin/${pattern}\"\n\t\t)\n\t\tmessage(STATUS \"Matched files in ${APP_WINDOWS_SYSROOT}/bin for ${pattern}: ${MATCHED_FILES}\")\n\t\tif (NOT \"${MATCHED_FILES}\" STREQUAL \"\")\n\t\t\tinstall(FILES ${MATCHED_FILES} DESTINATION .)\n\t\tendif()\n\tendforeach()\n\n\t# gdk_pixbuf loaders\n\tinstall(DIRECTORY \"${APP_WINDOWS_SYSROOT}/lib/gdk-pixbuf-2.0/2.10.0/loaders\"\n\t\tDESTINATION \"lib/gdk-pixbuf-2.0/2.10.0/\"\n\t\tFILES_MATCHING PATTERN \"*.dll\")\n\tinstall(FILES \"${APP_WINDOWS_SYSROOT}/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache\"\n\t\tDESTINATION \"lib/gdk-pixbuf-2.0/2.10.0/\")\n\n\n\t# Msys2 in github has problems with this (cannot install: file exists!)\n#\tinstall(FILES \"${APP_WINDOWS_SYSROOT}/etc/fonts/fonts.conf\" DESTINATION \"etc/fonts/\")\n\n\t# <prefix>/etc/gtk-3.0/ should contain settings.ini with a win32 theme.\n\t# Use custom settings.ini to enable Windows theme\n#\tinstall(FILES \"${APP_WINDOWS_SYSROOT}/etc/gtk-3.0/settings.ini\" DESTINATION \"etc/gtk-3.0/\")\n\tinstall(FILES \"${CMAKE_SOURCE_DIR}/packaging/gtk/etc/gtk-3.0/settings.ini\" DESTINATION \"etc/gtk-3.0/\")\n\t#\tinstall(FILES \"${APP_WINDOWS_SYSROOT}/etc/gtk-3.0/im-multipress.conf\" DESTINATION \"etc/gtk-3.0/\")\n\n\t# Not present in msys2\n#\tinstall(DIRECTORY \"${APP_WINDOWS_SYSROOT}/share/themes\" DESTINATION \"share/\")\n\n\t# Without the \"schemas, the Native File Chooser crashes the program.\n\tinstall(DIRECTORY \"${APP_WINDOWS_SYSROOT}/share/glib-2.0/schemas\" DESTINATION \"share/glib-2.0/\")\n\n\n\tset(APP_WINDOWS_INSTALL_GTK_ICONS true)\n\n\tif (APP_WINDOWS_INSTALL_GTK_ICONS)\n\t\t# needed for window titlebar (if using client-side decorations),\n\t\t# tree sorting indicators, GUI icons.\n# windows-close\n# window-maximize\n# window-minimize\n# window-restore\n# pan-down\n# pan-up\n# dialog-information\n# dialog-error\n# dialog-warning\n\n\t\tinstall(DIRECTORY \"${APP_WINDOWS_GTK_ICONS_ROOT}/hicolor\"\n\t\t\tDESTINATION \"share/icons/\")\n\n\t\tinstall(DIRECTORY \"${APP_WINDOWS_GTK_ICONS_ROOT}/Adwaita\"\n\t\t\tDESTINATION \"share/icons/\")\n\tendif()  # icons\nendif()\n\n\n\n# All CPack variables must be set before this\ninclude(CPack)\n\n\nif (WIN32)\n\n\t# TODO Support NSIS from Linux\n\t#\tcd nsis-dist && @NSIS_EXEC@ gsmartcontrol.nsi\n\n\tadd_custom_target(package_nsis\n\t\t\tCOMMAND \"${CMAKE_CPACK_COMMAND}\" -G NSIS -C Release\n\t\t\tCOMMENT \"Creating NSIS installer...\"\n\t\t\tWORKING_DIRECTORY \"${CMAKE_BINARY_DIR}\")\n\tset_target_properties(package_nsis PROPERTIES\n\t\t\tEXCLUDE_FROM_ALL true\n\t\t\tEXCLUDE_FROM_DEFAULT_BUILD true\n\t\t\tPROJECT_LABEL \"PACKAGE_NSIS_INSTALLER\")  # VS likes uppercase names for better visibility of special targets\n\n\tadd_custom_target(package_zip\n\t\t\tCOMMAND \"${CMAKE_CPACK_COMMAND}\" -G ZIP -C Release\n\t\t\tCOMMENT \"Creating ZIP package...\"\n\t\t\tWORKING_DIRECTORY \"${CMAKE_BINARY_DIR}\")\n\tset_target_properties(package_zip PROPERTIES\n\t\t\tEXCLUDE_FROM_ALL true\n\t\t\tEXCLUDE_FROM_DEFAULT_BUILD true\n\t\t\tPROJECT_LABEL \"PACKAGE_ZIP\")  # VS likes uppercase names for better visibility of special targets\nendif()\n\n#if (UNIX)\n#\tadd_custom_target(package_tbz2\n#\t\t\tCOMMAND \"${CMAKE_CPACK_COMMAND}\" -G TBZ2 -C Release\n#\t\t\tCOMMENT \"Creating .tar.bz2 package...\"\n#\t\t\tWORKING_DIRECTORY \"${CMAKE_BINARY_DIR}\")\n#\tset_target_properties(package_tbz2 PROPERTIES\n#\t\t\tEXCLUDE_FROM_ALL true\n#\t\t\tEXCLUDE_FROM_DEFAULT_BUILD true)\n#\n#\tadd_custom_target(package_rpm\n#\t\t\tCOMMAND \"${CMAKE_CPACK_COMMAND}\" -G RPM -C Release\n#\t\t\tCOMMENT \"Creating RPM package...\"\n#\t\t\tWORKING_DIRECTORY \"${CMAKE_BINARY_DIR}\")\n#\tset_target_properties(package_rpm PROPERTIES\n#\t\t\tEXCLUDE_FROM_ALL true\n#\t\t\tEXCLUDE_FROM_DEFAULT_BUILD true)\n#\n#\tadd_custom_target(package_deb\n#\t\t\tCOMMAND \"${CMAKE_CPACK_COMMAND}\" -G DEB -C Release\n#\t\t\tCOMMENT \"Creating DEB package...\"\n#\t\t\tWORKING_DIRECTORY \"${CMAKE_BINARY_DIR}\")\n#\tset_target_properties(package_deb PROPERTIES\n#\t\t\tEXCLUDE_FROM_ALL true\n#\t\t\tEXCLUDE_FROM_DEFAULT_BUILD true)\n#\n#\tadd_custom_target(package_all\n#\t\t\tDEPENDS package_tbz2 package_rpm\n#\t\t\tCOMMENT \"Creating packages...\")\n#\tset_target_properties(package_all PROPERTIES\n#\t\t\tEXCLUDE_FROM_ALL true\n#\t\t\tEXCLUDE_FROM_DEFAULT_BUILD true)\n#endif()\n\n\n"
  },
  {
    "path": "packaging/gtk/etc/gtk-3.0/settings.ini",
    "content": "[Settings]\n# Uncomment this to enable \"native\" look on Windows.\n# This does not work if \"Classic\" theme is enabled in Windows, or\n# if there is fractional system scaling (e.g. 250%).\n#gtk-theme-name=win32\n"
  },
  {
    "path": "packaging/nsis/distribution.in.txt",
    "content": "\nGSmartControl - Hard disk drive and SSD health inspection tool\nVersion @CMAKE_PROJECT_VERSION@\n\nThe latest version of this distribution, as well as the source code of\nGSmartControl, can be found at\nhttps://gsmartcontrol.shaduri.dev\nand\nhttps://github.com/ashaduri/gsmartcontrol/releases\n\n\nThis distribution includes:\n\n* GSmartControl binary (gsmartcontrol.exe) and associated resources.\nThe binary was compiled against Gtkmm (http://www.gtkmm.org/), GTK+\n(http://gtk.org/) and related libraries.\n\n* Smartctl binaries (from smartmontools 7.4, http://smartmontools.sf.net/)\n- smartctl-nc.exe (compiled without console output), smartctl.exe (compiled\nwith console output), update-smart-drivedb.ps1 (drive database updater).\n\n\nThe combined distribution is licensed under GNU GPL version 3.\nSee LICENSE.txt for details.\n"
  },
  {
    "path": "packaging/nsis/gsmartcontrol.in.nsi.old",
    "content": "; License: BSD Zero Clause License file\n; Copyright:\n;   (C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n\n; NSIS2 Script\n; Compatible with NSIS Unicode 2.45.\n\n!define PRODUCT_VERSION \"@CMAKE_PROJECT_VERSION@\"\n!define PRODUCT_NAME \"GSmartControl\"\n!define PRODUCT_NAME_SMALL \"gsmartcontrol\"\n!define PRODUCT_PUBLISHER \"Alexander Shaduri\"\n!define PRODUCT_WEB_SITE \"https://gsmartcontrol.shaduri.dev\"\n\n;!define PRODUCT_DIR_REGKEY \"Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\AppMainExe.exe\"\n!define PRODUCT_UNINST_KEY \"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${PRODUCT_NAME}\"\n!define PRODUCT_UNINST_ROOT_KEY \"HKLM\"\n\n\n!include \"FileFunc.nsh\"  ; GetOptions\n\n\n\n; --------------- General Settings\n\n\n; This is needed for proper start menu item manipulation (for all users) in vista\nRequestExecutionLevel admin\n\n; This compressor gives us the best results\nSetCompressor /SOLID lzma\n\n; Do a CRC check before installing\nCRCCheck On\n\n; This is used in titles\nName \"${PRODUCT_NAME}\"  ;  ${PRODUCT_VERSION}\n\n; Output File Name\nOutFile \"${PRODUCT_NAME_SMALL}-${PRODUCT_VERSION}-@WINDOWS_SUFFIX@.exe\"\n\n\n; The Default Installation Directory:\n; Try to install to the same directory as runtime.\nInstallDir \"$PROGRAMFILES64\\${PRODUCT_NAME}\"\n; If already installed, try here\nInstallDirRegKey HKLM \"SOFTWARE\\${PRODUCT_NAME}\" \"InstallationDirectory\"\n\n\nShowInstDetails show\nShowUnInstDetails show\n\n\n\n\n\n; --------------------- MUI INTERFACE\n\n; MUI 2.0 compatible install\n!include \"MUI2.nsh\"\n!include \"InstallOptions.nsh\"  ; section description macros\n\n; Backgound Colors. uncomment to enable fullscreen.\n; BGGradient 0000FF 000000 FFFFFF\n\n; MUI Settings\n!define MUI_ABORTWARNING\n!define MUI_ICON \"nsi_install.ico\"\n!define MUI_UNICON \"nsi_uninstall.ico\"\n\n\n; Things that need to be extracted on first (keep these lines before any File command!).\n; Only useful for BZIP2 compression.\n;!insertmacro MUI_RESERVEFILE_LANGDLL\n;!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS\n\n\n; Language Selection Dialog Settings\n;!define MUI_LANGDLL_REGISTRY_ROOT \"${PRODUCT_UNINST_ROOT_KEY}\"\n;!define MUI_LANGDLL_REGISTRY_KEY \"${PRODUCT_UNINST_KEY}\"\n;!define MUI_LANGDLL_REGISTRY_VALUENAME \"NSIS:Language\"\n\n!define LICENSE_FILE \"doc\\distribution.txt\"\n\n; Pages to show during installation\n!insertmacro MUI_PAGE_WELCOME\n!insertmacro MUI_PAGE_LICENSE \"${LICENSE_FILE}\"\n!insertmacro MUI_PAGE_DIRECTORY\n!insertmacro MUI_PAGE_INSTFILES\n\n;!define MUI_FINISHPAGE_RUN \"$INSTDIR\\gsmartcontrol.exe\"\n;!define MUI_FINISHPAGE_SHOWREADME \"$INSTDIR\\Example.file\"\n;!define MUI_FINISHPAGE_RUN_NOTCHECKED\n!define MUI_FINISHPAGE_NOAUTOCLOSE\n!define MUI_FINISHPAGE_NOREBOOTSUPPORT\n!insertmacro MUI_PAGE_FINISH\n\n\n\n; Uninstaller page\n!insertmacro MUI_UNPAGE_CONFIRM\n!insertmacro MUI_UNPAGE_INSTFILES\n\n\n\n; Language files\n!insertmacro MUI_LANGUAGE \"English\"\n\n\n; --------------- END MUI\n\nLangString TEXT_IO_TITLE ${LANG_ENGLISH} \"GSmartControl\"\n\n\nvar install_option_removeold  ; uninstall the old version first (if present): yes (default), no.\n\n\n\n; ----------------- INSTALLATION TYPES\n\n; InstType \"Recommended\"\n; InstType \"Full\"\n; InstType \"Minimal\"\n\n\n\n\nSection \"!GSmartControl\" SecMain\nSectionIn 1 RO\n\tSetShellVarContext all  ; use all user variables as opposed to current user\n\tSetOutPath \"$INSTDIR\"\n\n\tSetOverwrite Off ; don't overwrite the config file\n\t; File \"gsmartcontrol2.conf\"\n\n\tSetOverwrite On\n\tFile *.exe\n\tFile *.dll\n\t; File gsmartcontrol.exe.manifest\n\tFile gsmartcontrol.ico\n\tFile icon_*.png\n\tFile drivedb.h\n\n\t; Include the \"doc\" directory completely.\n\tFile /r doc\n\n\t; Include the \"ui\" directory completely.\n\tFile /r ui\n\n\t; GTK and stuff\n\tFile /r etc\n\tFile /r share\n\n\t; Add Shortcuts (this inherits the exe's run permissions)\n\tCreateShortCut \"$SMPROGRAMS\\GSmartControl.lnk\" \"$INSTDIR\\gsmartcontrol.exe\" \"\" \\\n\t\t\"$INSTDIR\\gsmartcontrol.ico\" \"\" SW_SHOWNORMAL \"\" \"GSmartControl - Hard disk drive and SSD health inspection tool\"\n\nSectionEnd\n\n\n\n; Executed on installation start\nFunction .onInit\n\tSetShellVarContext all  ; use all user variables as opposed to current user\n\n\t${GetOptions} \"$CMDLINE\" \"/removeold=\" $install_option_removeold\n\n\tCall PreventMultipleInstances\n\n\tCall DetectPrevInstallation\nFunctionEnd\n\n\n\n; ------------------ POST INSTALL\n\n\nSection -post\n\tSetShellVarContext all  ; use all user variables as opposed to current user\n\n\tWriteRegStr HKLM \"SOFTWARE\\${PRODUCT_NAME}\" \"InstallationDirectory\" \"$INSTDIR\"\n\tWriteRegStr HKLM \"SOFTWARE\\${PRODUCT_NAME}\" \"Vendor\" \"${PRODUCT_PUBLISHER}\"\n\n\tWriteRegStr HKLM \"${PRODUCT_UNINST_KEY}\" \"DisplayName\" \"${PRODUCT_NAME}\"\n\tWriteRegStr HKLM \"${PRODUCT_UNINST_KEY}\" \"UninstallString\" \"$INSTDIR\\gsmartcontrol_uninst.exe\"\n\tWriteRegStr HKLM \"${PRODUCT_UNINST_KEY}\" \"InstallLocation\" \"$INSTDIR\"\n\tWriteRegStr HKLM \"${PRODUCT_UNINST_KEY}\" \"Publisher\" \"${PRODUCT_PUBLISHER}\"\n\tWriteRegStr HKLM \"${PRODUCT_UNINST_KEY}\" \"DisplayIcon\" \"$INSTDIR\\gsmartcontrol.ico\"\n\tWriteRegStr HKLM \"${PRODUCT_UNINST_KEY}\" \"URLInfoAbout\" \"${PRODUCT_WEB_SITE}\"\n\tWriteRegStr HKLM \"${PRODUCT_UNINST_KEY}\" \"DisplayVersion\" \"${PRODUCT_VERSION}\"\n\tWriteRegDWORD HKLM \"${PRODUCT_UNINST_KEY}\" \"NoModify\" 1\n\tWriteRegDWORD HKLM \"${PRODUCT_UNINST_KEY}\" \"NoRepair\" 1\n\n\t; We don't need this, MUI takes care for us\n\t; WriteRegStr HKCU \"Software\\${PRODUCT_NAME}\" \"Installer Language\" $sUAGE\n\n\t; write out uninstaller\n\tWriteUninstaller \"$INSTDIR\\gsmartcontrol_uninst.exe\"\n\n\t; uninstall shortcut\n\t;CreateDirectory \"$SMPROGRAMS\\GSmartControl\"\n\t;CreateShortCut \"$SMPROGRAMS\\GSmartControl\\Uninstall GSmartControl.lnk\" \"$INSTDIR\\gsmartcontrol_uninst.exe\" \"\" \"\"\n\nSectionEnd ; post\n\n\n\n\n\n; ---------------- UNINSTALL\n\n\n\nFunction un.onUninstSuccess\n\tHideWindow\n\tMessageBox MB_ICONINFORMATION|MB_OK \"$(^Name) was successfully removed from your computer.\" /SD IDOK\nFunctionEnd\n\n\n\n\nSection Uninstall\n\tSetShellVarContext all  ; use all user variables as opposed to current user\n\tSetAutoClose false\n\n\t; add delete commands to delete whatever files/registry keys/etc you installed here.\n\tDelete \"$INSTDIR\\gsmartcontrol_uninst.exe\"\n\n\tDeleteRegKey HKLM \"SOFTWARE\\${PRODUCT_NAME}\"\n\tDeleteRegKey HKLM \"${PRODUCT_UNINST_KEY}\"\n\n\tDelete \"$INSTDIR\\*.exe\"\n\tDelete \"$INSTDIR\\*.dll\"\n\t; Delete \"$INSTDIR\\gsmartcontrol.exe.manifest\"\n\tDelete \"$INSTDIR\\gsmartcontrol.ico\"\n\tDelete \"$INSTDIR\\icon_*.png\"\n\n\t; update-smart-drivedb may leave these\n\tDelete \"$INSTDIR\\drivedb.h\"\n\tDelete \"$INSTDIR\\drivedb.h.*\"\n\n\tRMDir /r \"$INSTDIR\\doc\"\n\n\tRMDir /r \"$INSTDIR\\ui\"\n\n\t; GTK and stuff\n\tRMDir /r \"$INSTDIR\\etc\"\n\tRMDir /r \"$INSTDIR\\share\"\n\n\t; clean up generated stuff\n\tDelete \"$INSTDIR\\*stdout.txt\"\n\tDelete \"$INSTDIR\\*stderr.txt\"\n\n\tRMDir \"$INSTDIR\"  ; only if empty\n\n\tDelete \"$SMPROGRAMS\\GSmartControl.lnk\"\n\t;Delete \"$SMPROGRAMS\\GSmartControl\\Uninstall GSmartControl.lnk\"\n\nSectionEnd ; end of uninstall section\n\n\n\n; --------------- Helpers\n\n; Detect previous installation\nFunction DetectPrevInstallation\n\t; if /removeold=no option is given, don't check anything.\n\tStrCmp $install_option_removeold \"no\" old_detect_done\n\n\tSetShellVarContext all  ; use all user variables as opposed to current user\n\tpush $R0\n\n\t; detect previous installation\n\tReadRegStr $R0 HKLM \"${PRODUCT_UNINST_KEY}\" \"UninstallString\"\n\tStrCmp $R0 \"\" old_detect_done\n\n\tMessageBox MB_OKCANCEL|MB_ICONEXCLAMATION \\\n\t\t\"${PRODUCT_NAME} is already installed. $\\n$\\nClick `OK` to remove the \\\n\t\tprevious version or `Cancel` to continue anyway.\" \\\n\t\t/SD IDOK IDOK old_uninst\n\t\t; Abort\n\t\tgoto old_detect_done\n\n\t; Run the old uninstaller\n\told_uninst:\n\t\tClearErrors\n\t\tIfSilent old_silent_uninst old_nosilent_uninst\n\n\t\told_nosilent_uninst:\n\t\t\tExecWait '$R0'\n\t\t\tgoto old_uninst_continue\n\n\t\told_silent_uninst:\n\t\t\tExecWait '$R0 /S _?=$INSTDIR'\n\n\t\told_uninst_continue:\n\n\t\tIfErrors old_no_remove_uninstaller\n\n\t\t; You can either use Delete /REBOOTOK in the uninstaller or add some code\n\t\t; here to remove to remove the uninstaller. Use a registry key to check\n\t\t; whether the user has chosen to uninstall. If you are using an uninstaller\n\t\t; components page, make sure all sections are uninstalled.\n\t\told_no_remove_uninstaller:\n\n\told_detect_done: ; old installation not found, all ok\n\n\tpop $R0\nFunctionEnd\n\n\n\n; Prevent running multiple instances of the installer\nFunction PreventMultipleInstances\n\tPush $R0\n\tSystem::Call 'kernel32::CreateMutexA(i 0, i 0, t ${PRODUCT_NAME}) ?e'\n\tPop $R0\n\tStrCmp $R0 0 +3\n\t\tMessageBox MB_OK|MB_ICONEXCLAMATION \"The installer is already running.\" /SD IDOK\n\t\tAbort\n\tPop $R0\nFunctionEnd\n\n\n\n; eof\n"
  },
  {
    "path": "packaging/obs_debian/debian.changelog",
    "content": "gsmartcontrol (git+nmu1) unstable; urgency=low\n\n  * Permanently Initial Release.\n\n -- Alexander Shaduri <ashaduriREMOVETHIS@gmail.com>  Sat, 10 Dec 2008 21:46:00 +0400\n"
  },
  {
    "path": "packaging/obs_debian/debian.compat",
    "content": "9\n"
  },
  {
    "path": "packaging/obs_debian/debian.control",
    "content": "Source: gsmartcontrol\nSection: utils\nPriority: extra\nHomepage: https://gsmartcontrol.shaduri.dev\nMaintainer: Alexander Shaduri <ashaduri@gmail.com>\nBuild-Depends: debhelper (>= 5), cmake, libgtkmm-3.0-dev (>= 3.4.0)\nStandards-Version: 3.7.3\n\nPackage: gsmartcontrol\nArchitecture: any\nDepends: ${shlibs:Depends}, smartmontools (>= 5.43), xterm, menu\nDescription: Hard disk drive and SSD health inspection tool\n GSmartControl is a graphical user interface for smartctl (from smartmontools\n package), which is a tool for querying and controlling SMART\n (Self-Monitoring, Analysis, and Reporting Technology) data on modern hard\n disk and solid-state drives. It allows you to inspect the drive's SMART data\n to determine its  health, as well as run various tests on it.\n"
  },
  {
    "path": "packaging/obs_debian/debian.copyright",
    "content": "This package was debianized by Alexander Shaduri <ashaduri@gmail.com> on\nSat, 15 Nov 2008 00:12:04 +0400.\n\nIt was downloaded from <https://gsmartcontrol.shaduri.dev>\n\nCopyright:\n\n    Copyright (C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n\nLicense:\n\nGSmartControl is Copyright (C) 2008 - 2021 Alexander Shaduri ashaduri@gmail.com and contributors.\n\nGSmartControl is licensed under the terms of GNU General Public License Version 3.\n\nThis program is free software: you can redistribute it and/or modify it under the terms of version 3 of the GNU General Public License as published by the Free Software Foundation.\n\nThis program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public Licenses for more details.\n\nThis product includes icons from Oxygen Icons copyright KDE (https://kde.org)\nand licensed under the GNU LGPL version 3 or later.\n\nYou should have received copies of these licenses along with this program.\nIf not, see <http://www.gnu.org/licenses/>.\n\nOn Debian systems, the complete text of the GNU General\nPublic License can be found in `/usr/share/common-licenses/GPL-3'.\n\nThe Debian packaging is (C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com> and\nis licensed under the GPLv3, see above.\n\n"
  },
  {
    "path": "packaging/obs_debian/debian.postinst",
    "content": "#!/bin/sh\n\nset -e\n\nif test -x /usr/bin/update-menus; then update-menus; fi\n\n# dh_installdeb will replace this with shell code automatically\n# generated by other debhelper scripts.\n\n#DEBHELPER#\n\nexit 0\n\n\n"
  },
  {
    "path": "packaging/obs_debian/debian.postrm",
    "content": "#!/bin/sh\n\nset -e\n\nif test -x /usr/bin/update-menus; then update-menus; fi\n\n# dh_installdeb will replace this with shell code automatically\n# generated by other debhelper scripts.\n\n#DEBHELPER#\n\nexit 0\n\n\n"
  },
  {
    "path": "packaging/obs_debian/debian.rules",
    "content": "#!/usr/bin/make -f\nexport DH_VERBOSE = 1\nexport DEB_BUILD_MAINT_OPTIONS = hardening=+all\nexport DEB_CFLAGS_MAINT_APPEND  = -Wall\nexport DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed\n\n%:\n\tdh $@\n\noverride_dh_auto_configure:\n\tdh_auto_configure -- -DAPP_COMPILER_ENABLE_WARNINGS=ON -DAPP_BUILD_EXAMPLES=ON -DAPP_BUILD_TESTS=ON\n"
  },
  {
    "path": "packaging/obs_debian/gsmartcontrol-Debian_Testing.dsc",
    "content": "Format: 1.0\nSource: gsmartcontrol\nBinary: gsmartcontrol\nArchitecture: any\nVersion: git+nmu1\nMaintainer: Alexander Shaduri <ashaduri@gmail.com>\nHomepage: https://gsmartcontrol.shaduri.dev\nStandards-Version: 3.7.3\nBuild-Depends: cmake (>= 3.12.0), debhelper (>= 5), libgtkmm-3.0-dev (>= 3.4.0), gettext, build-essential, brz, libjpeg62-turbo-dev\n"
  },
  {
    "path": "packaging/obs_debian/gsmartcontrol-Debian_Unstable.dsc",
    "content": "Format: 1.0\nSource: gsmartcontrol\nBinary: gsmartcontrol\nArchitecture: any\nVersion: git+nmu1\nMaintainer: Alexander Shaduri <ashaduri@gmail.com>\nHomepage: https://gsmartcontrol.shaduri.dev\nStandards-Version: 3.7.3\nBuild-Depends: cmake (>= 3.12.0), debhelper (>= 5), libgtkmm-3.0-dev (>= 3.4.0), gettext, build-essential, libjpeg62-turbo-dev\n"
  },
  {
    "path": "packaging/obs_debian/gsmartcontrol.dsc",
    "content": "Format: 1.0\nSource: gsmartcontrol\nBinary: gsmartcontrol\nArchitecture: any\nVersion: git+nmu1\nMaintainer: Alexander Shaduri <ashaduri@gmail.com>\nHomepage: https://gsmartcontrol.shaduri.dev\nStandards-Version: 3.7.3\nBuild-Depends: cmake (>= 3.12.0), debhelper (>= 5), libgtkmm-3.0-dev (>= 3.4.0), gettext, build-essential\n"
  },
  {
    "path": "packaging/obs_rpm/gsmartcontrol-rpmlintrc",
    "content": "\n# Hide warning about gsmartcontrol.desktop, it's a false positive.\naddFilter(\"desktopfile-without-binary\")\n"
  },
  {
    "path": "packaging/obs_rpm/gsmartcontrol.changes",
    "content": "* Mon Sep 15 2008 Alexander Shaduri <ashaduri 'at' gmail.com>\n- Permanently initial changelog entry.\n"
  },
  {
    "path": "packaging/obs_rpm/gsmartcontrol.spec",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n# This spec file is for openSUSE Build Service.\n# Supported distributions: openSUSE, Fedora, CentOS, RHEL.\n\nName:\t\tgsmartcontrol\nVersion: \tgit\nRelease:\t0\nLicense:\tGPL-3.0-only\nUrl:\t\thttps://gsmartcontrol.shaduri.dev\nVendor:\t\tAlexander Shaduri <ashaduri@gmail.com>\nSource0:\t\thttps://github.com/ashaduri/%{name}/releases/download/v%{version}/%{name}-%{version}.tar.bz2\nSource1:     %{name}-rpmlintrc\nBuildRoot:\t%{_tmppath}/%{name}-%{version}-build\nSummary:\tHard Disk Drive and SSD Health Inspection Tool\nGroup:\t\tHardware/Other\n\n# For distributions that are not listed here we don't specify any dependencies to avoid errors.\n\n# SUSE / OpenSUSE. SLES also defines the correct suse_version.\n%if 0%{?suse_version}\nRequires: smartmontools >= 5.43 xterm\nRequires: polkit bash\n# libX11-devel is required to resove pkgconfig(x11) requirement in SLE15.3\nBuildRequires: cmake >= 3.12.0  gtkmm3-devel >= 3.4.0 libX11-devel pkgconfig\nBuildRequires: update-desktop-files fdupes hicolor-icon-theme polkit\nRecommends: xdg-utils\nRequires: smartmontools >= 5.43 xterm\nRequires: polkit bash\n\n%if 0%{?suse_version} < 1600 || 0%{?sle_version} < 160000\n# 15.x come with gcc7 as the system compiler\nBuildRequires: gcc13-c++ libstdc++6-devel-gcc13\n%else\n# Tumbleweed, ALP, ...\nBuildRequires: gcc-c++ >= 13 libstdc++-devel >= 13\n%endif\n%endif\n\n\n# Fedora, CentOS, RHEL\n%if 0%{?fedora_version} || 0%{?centos_version} || 0%{?rhel_version}\nRequires: smartmontools >= 5.43 polkit bash xterm\n%if 0%{?centos_version} || 0%{?rhel_version}\n# Use cmake from EPEL\nBuildRequires: cmake3 >= 3.12.0\n%else\nBuildRequires: cmake >= 3.12.0\n%endif\nBuildRequires: gcc-c++ >= 13 gtkmm30-devel >= 3.4.0\nBuildRequires: desktop-file-utils hicolor-icon-theme make\n%endif\n\n\n%description\nGSmartControl is a graphical user interface for smartctl, which is a tool for\nquerying and controlling SMART (Self-Monitoring, Analysis, and Reporting\nTechnology) data in hard disk and solid-state drives. It allows you to inspect\nthe drive's SMART data to determine its health, as well as run various tests\non it.\n\n\n%prep\n%setup -q\n\n%build\n\n%if 0%{?suse_version} && ( 0%{?suse_version} < 1600 || 0%{?sle_version} < 160000 )\n%cmake -DAPP_COMPILER_ENABLE_WARNINGS=ON -DCMAKE_CXX_COMPILER=g++-13\n%else\n%cmake -DAPP_COMPILER_ENABLE_WARNINGS=ON\n%endif\n\n%cmake_build\n\n%install\n%cmake_install\n\n%if 0%{?suse_version}\n%suse_update_desktop_file %{name}\n# There are some png file duplicates, hardlink them.\n%fdupes -s %{buildroot}%{_prefix}\n%endif\n\n%post\n%if 0%{?suse_version}\n%desktop_database_post\n%icon_theme_cache_post\n%endif\n\n%postun\n%if 0%{?suse_version}\n%desktop_database_postun\n%icon_theme_cache_postun\n%endif\n\n\n%if 0%{?fedora_version} || 0%{?centos_version} || 0%{?rhel_version}\n%check\ndesktop-file-validate %{buildroot}%{_datadir}/applications/%{name}.desktop\n%endif\n\n\n%files\n%defattr(-,root,root)\n\n%license LICENSE.txt\n\n%attr(0755,root,root) %{_bindir}/%{name}-root\n%attr(0755,root,root) %{_sbindir}/%{name}\n\n%if 0%{?suse_version} || 0%{?fedora_version} || 0%{?centos_version} || 0%{?rhel_version}\n\n%if 0%{?suse_version}\n%doc %{_defaultdocdir}/%{name}\n%endif\n%if 0%{?fedora_version} || 0%{?centos_version} || 0%{?rhel_version}\n%{_pkgdocdir}\n%endif\n\n%else\n%doc %{_datadir}/doc/gsmartcontrol\n%endif\n\n%{_mandir}/man1/*\n\n%{_datadir}/%{name}\n%{_datadir}/applications/*.desktop\n%{_datadir}/icons/hicolor/*/apps/%{name}.png\n%dir %{_datadir}/metainfo\n%{_datadir}/metainfo/%{name}.appdata.xml\n%{_datadir}/polkit-1/actions/org.%{name}.policy\n\n%changelog\n"
  },
  {
    "path": "po/LINGUAS",
    "content": "# keep this file sorted alphabetically, one language code per line\ncs\nka\n"
  },
  {
    "path": "po/Makefile.in.in",
    "content": "# Makefile for PO directory in any package using GNU gettext.\n# Copyright (C) 1995-1997, 2000-2007, 2009-2010 by Ulrich Drepper <drepper@gnu.ai.mit.edu>\n#\n# This file can be copied and used freely without restrictions.  It can\n# be used in projects which are not available under the GNU General Public\n# License but which still want to provide support for the GNU gettext\n# functionality.\n# Please note that the actual code of GNU gettext is covered by the GNU\n# General Public License and is *not* in the public domain.\n#\n# Origin: gettext-0.19\nGETTEXT_MACRO_VERSION = 0.19\n\nPACKAGE = @PACKAGE@\nVERSION = @CMAKE_PROJECT_VERSION@\nPACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@\n\nSED = @SED@\nSHELL = /bin/sh\n@SET_MAKE@\n\nsrcdir = @srcdir@\ntop_srcdir = @top_srcdir@\nVPATH = @srcdir@\n\nprefix = @prefix@\nexec_prefix = @exec_prefix@\ndatarootdir = @datarootdir@\ndatadir = @datadir@\nlocaledir = @localedir@\ngettextsrcdir = $(datadir)/gettext/po\n\nINSTALL = @INSTALL@\nINSTALL_DATA = @INSTALL_DATA@\n\n# We use $(mkdir_p).\n# In automake <= 1.9.x, $(mkdir_p) is defined either as \"mkdir -p --\" or as\n# \"$(mkinstalldirs)\" or as \"$(install_sh) -d\". For these automake versions,\n# @install_sh@ does not start with $(SHELL), so we add it.\n# In automake >= 1.10, @mkdir_p@ is derived from ${MKDIR_P}, which is defined\n# either as \"/path/to/mkdir -p\" or \".../install-sh -c -d\". For these automake\n# versions, $(mkinstalldirs) and $(install_sh) are unused.\nmkinstalldirs = $(SHELL) @install_sh@ -d\ninstall_sh = $(SHELL) @install_sh@\nMKDIR_P = @MKDIR_P@\nmkdir_p = @mkdir_p@\n\nGMSGFMT_ = @GMSGFMT@\nGMSGFMT_no = @GMSGFMT@\nGMSGFMT_yes = @GMSGFMT_015@\nGMSGFMT = $(GMSGFMT_$(USE_MSGCTXT))\nMSGFMT_ = @MSGFMT@\nMSGFMT_no = @MSGFMT@\nMSGFMT_yes = @MSGFMT_015@\nMSGFMT = $(MSGFMT_$(USE_MSGCTXT))\nXGETTEXT_ = @XGETTEXT@\nXGETTEXT_no = @XGETTEXT@\nXGETTEXT_yes = @XGETTEXT_015@\nXGETTEXT = $(XGETTEXT_$(USE_MSGCTXT))\nMSGMERGE = msgmerge\nMSGMERGE_UPDATE = @MSGMERGE@ --update\nMSGINIT = msginit\nMSGCONV = msgconv\nMSGFILTER = msgfilter\n\nPOFILES = @POFILES@\nGMOFILES = @GMOFILES@\nUPDATEPOFILES = @UPDATEPOFILES@\nDUMMYPOFILES = @DUMMYPOFILES@\nDISTFILES.common = Makefile.in.in remove-potcdate.sin \\\n$(DISTFILES.common.extra1) $(DISTFILES.common.extra2) $(DISTFILES.common.extra3)\nDISTFILES = $(DISTFILES.common) Makevars POTFILES.in \\\n$(POFILES) $(GMOFILES) \\\n$(DISTFILES.extra1) $(DISTFILES.extra2) $(DISTFILES.extra3)\n\nPOTFILES = \\\n\nCATALOGS = @CATALOGS@\n\nPOFILESDEPS_ = $(srcdir)/$(DOMAIN).pot\nPOFILESDEPS_yes = $(POFILESDEPS_)\nPOFILESDEPS_no =\nPOFILESDEPS = $(POFILESDEPS_$(PO_DEPENDS_ON_POT))\n\nDISTFILESDEPS_ = update-po\nDISTFILESDEPS_yes = $(DISTFILESDEPS_)\nDISTFILESDEPS_no =\nDISTFILESDEPS = $(DISTFILESDEPS_$(DIST_DEPENDS_ON_UPDATE_PO))\n\n# Makevars gets inserted here. (Don't remove this line!)\n\n.SUFFIXES:\n.SUFFIXES: .po .gmo .mo .sed .sin .nop .po-create .po-update\n\n.po.mo:\n\t@echo \"$(MSGFMT) -c -o $@ $<\"; \\\n\t$(MSGFMT) -c -o t-$@ $< && mv t-$@ $@\n\n.po.gmo:\n\t@lang=`echo $* | sed -e 's,.*/,,'`; \\\n\ttest \"$(srcdir)\" = . && cdcmd=\"\" || cdcmd=\"cd $(srcdir) && \"; \\\n\techo \"$${cdcmd}rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics --verbose -o $${lang}.gmo $${lang}.po\"; \\\n\tcd $(srcdir) && rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics --verbose -o t-$${lang}.gmo $${lang}.po && mv t-$${lang}.gmo $${lang}.gmo\n\n.sin.sed:\n\tsed -e '/^#/d' $< > t-$@\n\tmv t-$@ $@\n\n\nall: all-@USE_NLS@\n\nall-yes: stamp-po\nall-no:\n\n# Ensure that the gettext macros and this Makefile.in.in are in sync.\nCHECK_MACRO_VERSION = \\\n\ttest \"$(GETTEXT_MACRO_VERSION)\" = \"@GETTEXT_MACRO_VERSION@\" \\\n\t  || { echo \"*** error: gettext infrastructure mismatch: using a Makefile.in.in from gettext version $(GETTEXT_MACRO_VERSION) but the autoconf macros are from gettext version @GETTEXT_MACRO_VERSION@\" 1>&2; \\\n\t       exit 1; \\\n\t     }\n\n# $(srcdir)/$(DOMAIN).pot is only created when needed. When xgettext finds no\n# internationalized messages, no $(srcdir)/$(DOMAIN).pot is created (because\n# we don't want to bother translators with empty POT files). We assume that\n# LINGUAS is empty in this case, i.e. $(POFILES) and $(GMOFILES) are empty.\n# In this case, stamp-po is a nop (i.e. a phony target).\n\n# stamp-po is a timestamp denoting the last time at which the CATALOGS have\n# been loosely updated. Its purpose is that when a developer or translator\n# checks out the package via CVS, and the $(DOMAIN).pot file is not in CVS,\n# \"make\" will update the $(DOMAIN).pot and the $(CATALOGS), but subsequent\n# invocations of \"make\" will do nothing. This timestamp would not be necessary\n# if updating the $(CATALOGS) would always touch them; however, the rule for\n# $(POFILES) has been designed to not touch files that don't need to be\n# changed.\nstamp-po: $(srcdir)/$(DOMAIN).pot\n\t@$(CHECK_MACRO_VERSION)\n\ttest ! -f $(srcdir)/$(DOMAIN).pot || \\\n\t  test -z \"$(GMOFILES)\" || $(MAKE) $(GMOFILES)\n\t@test ! -f $(srcdir)/$(DOMAIN).pot || { \\\n\t  echo \"touch stamp-po\" && \\\n\t  echo timestamp > stamp-poT && \\\n\t  mv stamp-poT stamp-po; \\\n\t}\n\n# Note: Target 'all' must not depend on target '$(DOMAIN).pot-update',\n# otherwise packages like GCC can not be built if only parts of the source\n# have been downloaded.\n\n# This target rebuilds $(DOMAIN).pot; it is an expensive operation.\n# Note that $(DOMAIN).pot is not touched if it doesn't need to be changed.\n# The determination of whether the package xyz is a GNU one is based on the\n# heuristic whether some file in the top level directory mentions \"GNU xyz\".\n# If GNU 'find' is available, we avoid grepping through monster files.\n$(DOMAIN).pot-update: $(POTFILES) $(srcdir)/POTFILES.in remove-potcdate.sed\n\tpackage_gnu=\"$(PACKAGE_GNU)\"; \\\n\ttest -n \"$$package_gnu\" || { \\\n\t  if { if (LC_ALL=C find --version) 2>/dev/null | grep GNU >/dev/null; then \\\n\t\t LC_ALL=C find -L $(top_srcdir) -maxdepth 1 -type f \\\n\t\t\t       -size -10000000c -exec grep 'GNU @PACKAGE@' \\\n\t\t\t       /dev/null '{}' ';' 2>/dev/null; \\\n\t       else \\\n\t\t LC_ALL=C grep 'GNU @PACKAGE@' $(top_srcdir)/* 2>/dev/null; \\\n\t       fi; \\\n\t     } | grep -v 'libtool:' >/dev/null; then \\\n\t     package_gnu=yes; \\\n\t   else \\\n\t     package_gnu=no; \\\n\t   fi; \\\n\t}; \\\n\tif test \"$$package_gnu\" = \"yes\"; then \\\n\t  package_prefix='GNU '; \\\n\telse \\\n\t  package_prefix=''; \\\n\tfi; \\\n\tif test -n '$(MSGID_BUGS_ADDRESS)' || test '$(PACKAGE_BUGREPORT)' = '@'PACKAGE_BUGREPORT'@'; then \\\n\t  msgid_bugs_address='$(MSGID_BUGS_ADDRESS)'; \\\n\telse \\\n\t  msgid_bugs_address='$(PACKAGE_BUGREPORT)'; \\\n\tfi; \\\n\tcase `$(XGETTEXT) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \\\n\t  '' | 0.[0-9] | 0.[0-9].* | 0.1[0-5] | 0.1[0-5].* | 0.16 | 0.16.[0-1]*) \\\n\t    $(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \\\n\t      --add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS) @XGETTEXT_EXTRA_OPTIONS@ \\\n\t      --files-from=$(srcdir)/POTFILES.in \\\n\t      --copyright-holder='$(COPYRIGHT_HOLDER)' \\\n\t      --msgid-bugs-address=\"$$msgid_bugs_address\" \\\n\t    ;; \\\n\t  *) \\\n\t    $(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \\\n\t      --add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS) @XGETTEXT_EXTRA_OPTIONS@ \\\n\t      --files-from=$(srcdir)/POTFILES.in \\\n\t      --copyright-holder='$(COPYRIGHT_HOLDER)' \\\n\t      --package-name=\"$${package_prefix}@PACKAGE@\" \\\n\t      --package-version='@CMAKE_PROJECT_VERSION@' \\\n\t      --msgid-bugs-address=\"$$msgid_bugs_address\" \\\n\t    ;; \\\n\tesac\n\ttest ! -f $(DOMAIN).po || { \\\n\t  if test -f $(srcdir)/$(DOMAIN).pot; then \\\n\t    sed -f remove-potcdate.sed < $(srcdir)/$(DOMAIN).pot > $(DOMAIN).1po && \\\n\t    sed -f remove-potcdate.sed < $(DOMAIN).po > $(DOMAIN).2po && \\\n\t    if cmp $(DOMAIN).1po $(DOMAIN).2po >/dev/null 2>&1; then \\\n\t      rm -f $(DOMAIN).1po $(DOMAIN).2po $(DOMAIN).po; \\\n\t    else \\\n\t      rm -f $(DOMAIN).1po $(DOMAIN).2po $(srcdir)/$(DOMAIN).pot && \\\n\t      mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \\\n\t    fi; \\\n\t  else \\\n\t    mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \\\n\t  fi; \\\n\t}\n\n# This rule has no dependencies: we don't need to update $(DOMAIN).pot at\n# every \"make\" invocation, only create it when it is missing.\n# Only \"make $(DOMAIN).pot-update\" or \"make dist\" will force an update.\n$(srcdir)/$(DOMAIN).pot:\n\t$(MAKE) $(DOMAIN).pot-update\n\n# This target rebuilds a PO file if $(DOMAIN).pot has changed.\n# Note that a PO file is not touched if it doesn't need to be changed.\n$(POFILES): $(POFILESDEPS)\n\t@lang=`echo $@ | sed -e 's,.*/,,' -e 's/\\.po$$//'`; \\\n\tif test -f \"$(srcdir)/$${lang}.po\"; then \\\n\t  test -f $(srcdir)/$(DOMAIN).pot || $(MAKE) $(srcdir)/$(DOMAIN).pot; \\\n\t  test \"$(srcdir)\" = . && cdcmd=\"\" || cdcmd=\"cd $(srcdir) && \"; \\\n\t  echo \"$${cdcmd}$(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} $${lang}.po $(DOMAIN).pot\"; \\\n\t  cd $(srcdir) \\\n\t    && { case `$(MSGMERGE_UPDATE) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \\\n\t           '' | 0.[0-9] | 0.[0-9].* | 0.1[0-7] | 0.1[0-7].*) \\\n\t             $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) $${lang}.po $(DOMAIN).pot;; \\\n\t           *) \\\n\t             $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} $${lang}.po $(DOMAIN).pot;; \\\n\t         esac; \\\n\t       }; \\\n\telse \\\n\t  $(MAKE) $${lang}.po-create; \\\n\tfi\n\n\ninstall: install-exec install-data\ninstall-exec:\ninstall-data: install-data-@USE_NLS@\n\tif test \"$(PACKAGE)\" = \"gettext-tools\"; then \\\n\t  $(mkdir_p) $(DESTDIR)$(gettextsrcdir); \\\n\t  for file in $(DISTFILES.common) Makevars.template; do \\\n\t    $(INSTALL_DATA) $(srcdir)/$$file \\\n\t\t\t    $(DESTDIR)$(gettextsrcdir)/$$file; \\\n\t  done; \\\n\t  for file in Makevars; do \\\n\t    rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \\\n\t  done; \\\n\telse \\\n\t  : ; \\\n\tfi\ninstall-data-no: all\ninstall-data-yes: all\n\t@catalogs='$(CATALOGS)'; \\\n\tfor cat in $$catalogs; do \\\n\t  cat=`basename $$cat`; \\\n\t  lang=`echo $$cat | sed -e 's/\\.gmo$$//'`; \\\n\t  dir=$(localedir)/$$lang/LC_MESSAGES; \\\n\t  $(mkdir_p) $(DESTDIR)$$dir; \\\n\t  if test -r $$cat; then realcat=$$cat; else realcat=$(srcdir)/$$cat; fi; \\\n\t  $(INSTALL_DATA) $$realcat $(DESTDIR)$$dir/$(DOMAIN).mo; \\\n\t  echo \"installing $$realcat as $(DESTDIR)$$dir/$(DOMAIN).mo\"; \\\n\t  for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \\\n\t    if test -n \"$$lc\"; then \\\n\t      if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \\\n\t        link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \\\n\t        mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \\\n\t        mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \\\n\t        (cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \\\n\t         for file in *; do \\\n\t           if test -f $$file; then \\\n\t             ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \\\n\t           fi; \\\n\t         done); \\\n\t        rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \\\n\t      else \\\n\t        if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \\\n\t          :; \\\n\t        else \\\n\t          rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \\\n\t          mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \\\n\t        fi; \\\n\t      fi; \\\n\t      rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \\\n\t      ln -s ../LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \\\n\t      ln $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \\\n\t      cp -p $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \\\n\t      echo \"installing $$realcat link as $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo\"; \\\n\t    fi; \\\n\t  done; \\\n\tdone\n\ninstall-strip: install\n\ninstalldirs: installdirs-exec installdirs-data\ninstalldirs-exec:\ninstalldirs-data: installdirs-data-@USE_NLS@\n\tif test \"$(PACKAGE)\" = \"gettext-tools\"; then \\\n\t  $(mkdir_p) $(DESTDIR)$(gettextsrcdir); \\\n\telse \\\n\t  : ; \\\n\tfi\ninstalldirs-data-no:\ninstalldirs-data-yes:\n\t@catalogs='$(CATALOGS)'; \\\n\tfor cat in $$catalogs; do \\\n\t  cat=`basename $$cat`; \\\n\t  lang=`echo $$cat | sed -e 's/\\.gmo$$//'`; \\\n\t  dir=$(localedir)/$$lang/LC_MESSAGES; \\\n\t  $(mkdir_p) $(DESTDIR)$$dir; \\\n\t  for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \\\n\t    if test -n \"$$lc\"; then \\\n\t      if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \\\n\t        link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \\\n\t        mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \\\n\t        mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \\\n\t        (cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \\\n\t         for file in *; do \\\n\t           if test -f $$file; then \\\n\t             ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \\\n\t           fi; \\\n\t         done); \\\n\t        rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \\\n\t      else \\\n\t        if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \\\n\t          :; \\\n\t        else \\\n\t          rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \\\n\t          mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \\\n\t        fi; \\\n\t      fi; \\\n\t    fi; \\\n\t  done; \\\n\tdone\n\n# Define this as empty until I found a useful application.\ninstallcheck:\n\nuninstall: uninstall-exec uninstall-data\nuninstall-exec:\nuninstall-data: uninstall-data-@USE_NLS@\n\tif test \"$(PACKAGE)\" = \"gettext-tools\"; then \\\n\t  for file in $(DISTFILES.common) Makevars.template; do \\\n\t    rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \\\n\t  done; \\\n\telse \\\n\t  : ; \\\n\tfi\nuninstall-data-no:\nuninstall-data-yes:\n\tcatalogs='$(CATALOGS)'; \\\n\tfor cat in $$catalogs; do \\\n\t  cat=`basename $$cat`; \\\n\t  lang=`echo $$cat | sed -e 's/\\.gmo$$//'`; \\\n\t  for lc in LC_MESSAGES $(EXTRA_LOCALE_CATEGORIES); do \\\n\t    rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \\\n\t  done; \\\n\tdone\n\ncheck: all\n\ninfo dvi ps pdf html tags TAGS ctags CTAGS ID:\n\nmostlyclean:\n\trm -f remove-potcdate.sed\n\trm -f stamp-poT\n\trm -f core core.* $(DOMAIN).po $(DOMAIN).1po $(DOMAIN).2po *.new.po\n\trm -fr *.o\n\nclean: mostlyclean\n\ndistclean: clean\n\trm -f Makefile Makefile.in POTFILES *.mo\n\nmaintainer-clean: distclean\n\t@echo \"This command is intended for maintainers to use;\"\n\t@echo \"it deletes files that may require special tools to rebuild.\"\n\trm -f stamp-po $(GMOFILES)\n\ndistdir = $(top_builddir)/$(PACKAGE)-$(VERSION)/$(subdir)\ndist distdir:\n\ttest -z \"$(DISTFILESDEPS)\" || $(MAKE) $(DISTFILESDEPS)\n\t@$(MAKE) dist2\n# This is a separate target because 'update-po' must be executed before.\ndist2: stamp-po $(DISTFILES)\n\tdists=\"$(DISTFILES)\"; \\\n\tif test \"$(PACKAGE)\" = \"gettext-tools\"; then \\\n\t  dists=\"$$dists Makevars.template\"; \\\n\tfi; \\\n\tif test -f $(srcdir)/$(DOMAIN).pot; then \\\n\t  dists=\"$$dists $(DOMAIN).pot stamp-po\"; \\\n\tfi; \\\n\tif test -f $(srcdir)/ChangeLog; then \\\n\t  dists=\"$$dists ChangeLog\"; \\\n\tfi; \\\n\tfor i in 0 1 2 3 4 5 6 7 8 9; do \\\n\t  if test -f $(srcdir)/ChangeLog.$$i; then \\\n\t    dists=\"$$dists ChangeLog.$$i\"; \\\n\t  fi; \\\n\tdone; \\\n\tif test -f $(srcdir)/LINGUAS; then dists=\"$$dists LINGUAS\"; fi; \\\n\tfor file in $$dists; do \\\n\t  if test -f $$file; then \\\n\t    cp -p $$file $(distdir) || exit 1; \\\n\t  else \\\n\t    cp -p $(srcdir)/$$file $(distdir) || exit 1; \\\n\t  fi; \\\n\tdone\n\nupdate-po: Makefile\n\t$(MAKE) $(DOMAIN).pot-update\n\ttest -z \"$(UPDATEPOFILES)\" || $(MAKE) $(UPDATEPOFILES)\n\t$(MAKE) update-gmo\n\n# General rule for creating PO files.\n\n.nop.po-create:\n\t@lang=`echo $@ | sed -e 's/\\.po-create$$//'`; \\\n\techo \"File $$lang.po does not exist. If you are a translator, you can create it through 'msginit'.\" 1>&2; \\\n\texit 1\n\n# General rule for updating PO files.\n\n.nop.po-update:\n\t@lang=`echo $@ | sed -e 's/\\.po-update$$//'`; \\\n\tif test \"$(PACKAGE)\" = \"gettext-tools\"; then PATH=`pwd`/../src:$$PATH; fi; \\\n\ttmpdir=`pwd`; \\\n\techo \"$$lang:\"; \\\n\ttest \"$(srcdir)\" = . && cdcmd=\"\" || cdcmd=\"cd $(srcdir) && \"; \\\n\techo \"$${cdcmd}$(MSGMERGE) $(MSGMERGE_OPTIONS) --lang=$$lang $$lang.po $(DOMAIN).pot -o $$lang.new.po\"; \\\n\tcd $(srcdir); \\\n\tif { case `$(MSGMERGE) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \\\n\t       '' | 0.[0-9] | 0.[0-9].* | 0.1[0-7] | 0.1[0-7].*) \\\n\t         $(MSGMERGE) $(MSGMERGE_OPTIONS) -o $$tmpdir/$$lang.new.po $$lang.po $(DOMAIN).pot;; \\\n\t       *) \\\n\t         $(MSGMERGE) $(MSGMERGE_OPTIONS) --lang=$$lang -o $$tmpdir/$$lang.new.po $$lang.po $(DOMAIN).pot;; \\\n\t     esac; \\\n\t   }; then \\\n\t  if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \\\n\t    rm -f $$tmpdir/$$lang.new.po; \\\n\t  else \\\n\t    if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \\\n\t      :; \\\n\t    else \\\n\t      echo \"msgmerge for $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po\" 1>&2; \\\n\t      exit 1; \\\n\t    fi; \\\n\t  fi; \\\n\telse \\\n\t  echo \"msgmerge for $$lang.po failed!\" 1>&2; \\\n\t  rm -f $$tmpdir/$$lang.new.po; \\\n\tfi\n\n$(DUMMYPOFILES):\n\nupdate-gmo: Makefile $(GMOFILES)\n\t@:\n\n# Recreate Makefile by invoking config.status. Explicitly invoke the shell,\n# because execution permission bits may not work on the current file system.\n# Use @SHELL@, which is the shell determined by autoconf for the use by its\n# scripts, not $(SHELL) which is hardwired to /bin/sh and may be deficient.\nMakefile: Makefile.in.in Makevars $(top_builddir)/config.status @POMAKEFILEDEPS@\n\tcd $(top_builddir) \\\n\t  && @SHELL@ ./config.status $(subdir)/$@.in po-directories\n\nforce:\n\n# Tell versions [3.59,3.63) of GNU make not to export all variables.\n# Otherwise a system limit (for SysV at least) may be exceeded.\n.NOEXPORT:\n"
  },
  {
    "path": "po/Makevars",
    "content": "# Makefile variables for PO directory in any package using GNU gettext.\n\n# Usually the message domain is the same as the package name.\nDOMAIN = gsmartcontrol\n\n# These two variables depend on the location of this directory.\nsubdir = po\ntop_builddir = ..\n\n# These options get passed to xgettext.\nXGETTEXT_OPTIONS = --keyword=_ --keyword=N_\n\n# This is the copyright holder that gets inserted into the header of the\n# $(DOMAIN).pot file.  Set this to the copyright holder of the surrounding\n# package.  (Note that the msgstr strings, extracted from the package's\n# sources, belong to the copyright holder of the package.)  Translators are\n# expected to transfer the copyright for their translations to this person\n# or entity, or to disclaim their copyright.  The empty string stands for\n# the public domain; in this case the translators are expected to disclaim\n# their copyright.\nCOPYRIGHT_HOLDER = Alexander Shaduri\n\n# This tells whether or not to prepend \"GNU \" prefix to the package\n# name that gets inserted into the header of the $(DOMAIN).pot file.\n# Possible values are \"yes\", \"no\", or empty.  If it is empty, try to\n# detect it automatically by scanning the files in $(top_srcdir) for\n# \"GNU packagename\" string.\nPACKAGE_GNU = no\n\n# This is the email address or URL to which the translators shall report\n# bugs in the untranslated strings:\n# - Strings which are not entire sentences, see the maintainer guidelines\n#   in the GNU gettext documentation, section 'Preparing Strings'.\n# - Strings which use unclear terms or require additional context to be\n#   understood.\n# - Strings which make invalid assumptions about notation of date, time or\n#   money.\n# - Pluralisation problems.\n# - Incorrect English spelling.\n# - Incorrect formatting.\n# It can be your email address, or a mailing list address where translators\n# can write to without being subscribed, or the URL of a web page through\n# which the translators can contact you.\nMSGID_BUGS_ADDRESS = https://gsmartcontrol.shaduri.dev/support\n\n# This is the list of locale categories, beyond LC_MESSAGES, for which the\n# message catalogs shall be used.  It is usually empty.\nEXTRA_LOCALE_CATEGORIES =\n\n# This tells whether the $(DOMAIN).pot file contains messages with an 'msgctxt'\n# context.  Possible values are \"yes\" and \"no\".  Set this to yes if the\n# package uses functions taking also a message context, like pgettext(), or\n# if in $(XGETTEXT_OPTIONS) you define keywords with a context argument.\nUSE_MSGCTXT = no\n\n# These options get passed to msgmerge.\n# Useful options are in particular:\n#   --previous            to keep previous msgids of translated messages,\n#   --quiet               to reduce the verbosity.\nMSGMERGE_OPTIONS =\n\n# This tells whether or not to regenerate a PO file when $(DOMAIN).pot\n# has changed.  Possible values are \"yes\" and \"no\".  Set this to no if\n# the POT file is checked in the repository and the version control\n# program ignores timestamps.\nPO_DEPENDS_ON_POT = yes\n\n# This tells whether or not to forcibly update $(DOMAIN).pot and\n# regenerate PO files on \"make dist\".  Possible values are \"yes\" and\n# \"no\".  Set this to no if the POT file and PO files are maintained\n# externally.\nDIST_DEPENDS_ON_UPDATE_PO = yes\n"
  },
  {
    "path": "po/POTFILES.in",
    "content": "src/hz/format_unit.h\n\nsrc/gui/ui/gsc_about_dialog.glade\nsrc/gui/ui/gsc_add_device_window.glade\nsrc/gui/ui/gsc_executor_log_window.glade\nsrc/gui/ui/gsc_info_window.glade\nsrc/gui/ui/gsc_main_window.glade\nsrc/gui/ui/gsc_preferences_window.glade\nsrc/gui/ui/gsc_text_window.glade\n\nsrc/gsc_add_device_window.cpp\nsrc/gsc_main_window.cpp\nsrc/gsc_executor_error_dialog.cpp\nsrc/gsc_executor_log_window.cpp\nsrc/gsc_info_window.cpp\nsrc/gsc_init.cpp\nsrc/gsc_main_window.cpp\nsrc/gsc_main_window_iconview.h\nsrc/gsc_preferences_window.cpp\nsrc/gsc_text_window.h\n\nsrc/applib/app_builder_widget.h\nsrc/applib/cli_executor_3ware.h\nsrc/applib/cli_executor_areca.h\nsrc/applib/command_executor_gui.cpp\nsrc/applib/selftest.cpp\nsrc/applib/smartctl_executor.cpp\nsrc/applib/smartctl_executor.h\nsrc/applib/smartctl_parser.cpp\nsrc/applib/storage_detector.cpp\nsrc/applib/storage_detector_helpers.h\nsrc/applib/storage_detector_linux.cpp\nsrc/applib/storage_detector_other.cpp\nsrc/applib/storage_detector_win32.cpp\nsrc/applib/storage_device.cpp\nsrc/applib/storage_property.cpp\nsrc/applib/storage_property_colors.h\nsrc/applib/storage_property_descr.h\n"
  },
  {
    "path": "po/cs.po",
    "content": "# Czech translations for gsmartcontrol package.\n# Copyright (C) 2018 - 21 CZ-Translator\n# This file is distributed under the same license as the gsmartcontrol package.\n# CZ-Translator (super-vik1@protonmail.com), 2021.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: gsmartcontrol 2.0.1\\n\"\n\"Report-Msgid-Bugs-To: https://gsmartcontrol.shaduri.dev\"\n\"Support\\n\"\n\"POT-Creation-Date: 2021-08-09 01:51+0400\\n\"\n\"PO-Revision-Date: 2021-08-09 15:22+0400\\n\"\n\"Last-Translator:   CZ-Translator <super-vik1@protonmail.com>\\n\"\n\"Language-Team: Czech\\n\"\n\"Language: cs\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\n#: src/gui/ui/gsc_main_window.glade:70\nmsgid \"Drive information:\"\nmsgstr \"Informace o disku:\"\n\n#: src/gui/ui/gsc_main_window.glade:85\nmsgid \"Basic health check:\"\nmsgstr \"Základní kontrola stavu:\"\n\n#: src/gui/ui/gsc_main_window.glade:100\nmsgid \"Model family:\"\nmsgstr \"Řada modelu:\"\n\n#: src/gui/ui/gsc_main_window.glade:195\nmsgid \"enable smart (action-replaced text)\"\nmsgstr \"Zapnout SMART\"\n\n#: src/gui/ui/gsc_add_device_window.glade:29\nmsgid \"Add Device - GSmartControl\"\nmsgstr \"Přidat zařízení - GSmartControl\"\n\n#: src/gui/ui/gsc_add_device_window.glade:51\nmsgid \"The &lt;a href=\\\"%1\\\"&gt;smartctl man page&lt;/a&gt; contains information on what you can enter here.\"\nmsgstr \"&lt;a href=\\\"%1\\\"&gt;smartctl man page&lt;/a&gt; obsahuje informace o tom, co zde můžete zadat.\"\n\n#: src/gui/ui/gsc_add_device_window.glade:89\nmsgid \"Browse...\"\nmsgstr \"Procházet...\"\n\n#: src/gui/ui/gsc_add_device_window.glade:112\nmsgid \"Device type:\"\nmsgstr \"Typ zařízení:\"\n\n#: src/gui/ui/gsc_add_device_window.glade:124\nmsgid \"Additional smartctl parameters\"\nmsgstr \"Dodatečné paramentry smartctl\"\n\n#: src/gui/ui/gsc_add_device_window.glade:126\nmsgid \"Smartctl parameters:\"\nmsgstr \"Paramentry smartctl:\"\n\n#: src/gui/ui/gsc_add_device_window.glade:190\nmsgid \"Note: To make this change permanent, you'll have to add the gsmartcontrol --add-device command line option to its shortcut.\"\nmsgstr \"Poznámka: Chcete-li tuto změnu provést trvale, musíte do terminálu přidat příkaz: gsmartcontrol --add-device.\"\n\n#: src/gui/ui/gsc_info_window.glade:52\nmsgid \"Device Information - GSmartControl\"\nmsgstr \"Informace o zařízení - GSmartControl\"\n\n#: src/gui/ui/gsc_info_window.glade:175\nmsgid \"_General\"\nmsgstr \"_Obecné\"\n\n#: src/gui/ui/gsc_info_window.glade:242\nmsgid \"Attributes\"\nmsgstr \"Atributy\"\n\n#: src/gui/ui/gsc_info_window.glade:240\nmsgid \"SMART Attributes\"\nmsgstr \"Atributy SMARTu\"\n\n#: src/gui/ui/gsc_info_window.glade:309\nmsgid \"Drive Statistics\"\nmsgstr \"Statistiky disku\"\n\n#: src/gui/ui/gsc_info_window.glade:311\nmsgid \"Statistics\"\nmsgstr \"Statistiky\"\n\n#: src/gui/ui/gsc_info_window.glade:338\nmsgid \"Self-tests are built-in tests within the drive designed to recognize drive fault conditions. All self-tests are safe to user data. The tests can be performed during normal system operation, but will take longer to complete if the drive is not idle.\"\nmsgstr \"Selt-Testy jsou vestavěné testy jednotky určené k rozpoznání chybových stavů jednotky. Všechny Self-Testy jsou bezpečné, co se týče uživatelských dat. Testy lze provádět za běžného provozu systému, ale jejich dokončení bude trvat déle, pokud jednotka není v nečinnosti.\"\n\n#: src/gui/ui/gsc_info_window.glade:477\nmsgid \"Test type: <\"\nmsgstr \"Typ testu: <\"\n\n#: src/gui/ui/gsc_info_window.glade:492\nmsgid \"Choose a test type\"\nmsgstr \"Vyberte typ testu\"\n\n#: src/gui/ui/gsc_info_window.glade:396\nmsgid \"Test progress\"\nmsgstr \"Stav testu\"\n\n#: src/gui/ui/gsc_info_window.glade:412\nmsgid \"Abort the test\"\nmsgstr \"Přeskočit test\"\n\n#: src/gui/ui/gsc_info_window.glade:432\nmsgid \"Test description\"\nmsgstr \"Popis testu\"\n\n#: src/gui/ui/gsc_info_window.glade:453\nmsgid \"Perform selected test\"\nmsgstr \"Spustit\"\n\n#: src/gui/ui/gsc_info_window.glade:636\nmsgid \"Self-Test Log\"\nmsgstr \"Protokol Self-Testu\"\n\n#: src/gui/ui/gsc_info_window.glade:741\nmsgid \"Complete error log information\"\nmsgstr \"Kompletní informace z protokolu o chybách\"\n\n#: src/gui/ui/gsc_info_window.glade:784\nmsgid \"SMART error log contains most recent non-trivial errors the hard drive has encountered\"\nmsgstr \"Protokol chyb SMART neobsahuje triviální chyby, se kterými se pevný disk setkal.\"\n\n#: src/gui/ui/gsc_info_window.glade:786\nmsgid \"Error Log\"\nmsgstr \"Protokol chyb\"\n\n#: src/gui/ui/gsc_info_window.glade:848\nmsgid \"Current temperature and history\"\nmsgstr \"Aktuální teplota a historie\"\n\n#: src/gui/ui/gsc_info_window.glade:850\nmsgid \"Temperature Log\"\nmsgstr \"Teplota\"\n\n#: src/gui/ui/gsc_info_window.glade:893\nmsgid \"Capabilities\"\nmsgstr \"Schopnosti\"\n\n#: src/gui/ui/gsc_info_window.glade:850\nmsgid \"Error Recovery\"\nmsgstr \"Obnova Chyb\"\n\n#: src/gui/ui/gsc_info_window.glade:958\nmsgid \"Selective Self-Test Log\"\nmsgstr \"Protokol Self-Testu\"\n\n#: src/gui/ui/gsc_info_window.glade:991\nmsgid \"Physical\"\nmsgstr \"Fyzický\"\n\n#: src/gui/ui/gsc_info_window.glade:1024\nmsgid \"Directory\"\nmsgstr \"Umíštění\"\n\n#: src/gui/ui/gsc_info_window.glade:1048\nmsgid \"Advanced parameters and logs\"\nmsgstr \"Pokročilé paramentry a protokoly\"\n\n#: src/gui/ui/gsc_info_window.glade:1050\nmsgid \"Advanced\"\nmsgstr \"Pokročilé\"\n"
  },
  {
    "path": "po/ka.po",
    "content": "# Georgian translations for gsmartcontrol package.\n# Copyright (C) 2018 - 21 Alexander Shaduri\n# This file is distributed under the same license as the gsmartcontrol package.\n# Alexander Shaduri <ashaduri@gmail.com>, 2018.\n#\nmsgid \"\"\nmsgstr \"\"\n\"Project-Id-Version: gsmartcontrol 2.0.1\\n\"\n\"Report-Msgid-Bugs-To: https://gsmartcontrol.shaduri.dev\"\n\"Support\\n\"\n\"POT-Creation-Date: 2018-02-05 01:51+0400\\n\"\n\"PO-Revision-Date: 2018-02-05 01:22+0400\\n\"\n\"Last-Translator: Alexander Shaduri <ashaduri@gmail.com>\\n\"\n\"Language-Team: Georgian\\n\"\n\"Language: ka\\n\"\n\"MIME-Version: 1.0\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\"Content-Transfer-Encoding: 8bit\\n\"\n\n#: src/ui/gsc_main_window.glade:70\nmsgid \"Drive information:\"\nmsgstr \"ინფორმაცია დრაივის შესახებ:\"\n\n#: src/ui/gsc_main_window.glade:85\nmsgid \"Basic health check:\"\nmsgstr \"ზოგადი ჯანმრთელობა:\"\n\n#: src/ui/gsc_main_window.glade:100\nmsgid \"Model family:\"\nmsgstr \"მოდელების ოჯახი:\"\n"
  },
  {
    "path": "src/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n\nadd_subdirectory(build_config)\n\nadd_subdirectory(applib)\nadd_subdirectory(gui)\nadd_subdirectory(hz)\nadd_subdirectory(libdebug)\nadd_subdirectory(rconfig)\nadd_subdirectory(test_all)\n\n"
  },
  {
    "path": "src/applib/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nadd_library(applib STATIC)\n\ntarget_sources(applib PRIVATE\n\tasync_command_executor.cpp\n\tasync_command_executor.h\n\tapp_builder_widget.h\n\tapp_gtkmm_tools.cpp\n\tapp_gtkmm_tools.h\n\tapp_regex.h\n\tcommand_executor.h\n\tcommand_executor.cpp\n\tcommand_executor_3ware.h\n\tcommand_executor_areca.h\n\tcommand_executor_gui.cpp\n\tcommand_executor_gui.h\n\tcommand_executor_factory.cpp\n\tcommand_executor_factory.h\n\tgsc_settings.h\n\tgui_utils.cpp\n\tgui_utils.h\n\tselftest.cpp\n\tselftest.h\n\tsmartctl_parser.cpp\n\tsmartctl_parser.h\n\tsmartctl_json_ata_parser.cpp\n\tsmartctl_json_ata_parser.h\n\tsmartctl_json_basic_parser.cpp\n\tsmartctl_json_basic_parser.h\n\tsmartctl_json_nvme_parser.cpp\n\tsmartctl_json_nvme_parser.h\n\tsmartctl_json_parser_helpers.h\n\tsmartctl_executor.cpp\n\tsmartctl_executor_gui.h\n\tsmartctl_executor.h\n\tsmartctl_parser_types.h\n\tsmartctl_text_ata_parser.cpp\n\tsmartctl_text_ata_parser.h\n\tsmartctl_text_basic_parser.cpp\n\tsmartctl_text_basic_parser.h\n\tsmartctl_text_parser_helper.cpp\n\tsmartctl_text_parser_helper.h\n\tsmartctl_version_parser.cpp\n\tsmartctl_version_parser.h\n\tstorage_detector.cpp\n\tstorage_detector.h\n\tstorage_detector_helpers.h\n\tstorage_detector_linux.cpp\n\tstorage_detector_linux.h\n\tstorage_detector_other.cpp\n\tstorage_detector_other.h\n\tstorage_detector_win32.cpp\n\tstorage_detector_win32.h\n\tstorage_device.cpp\n\tstorage_device.h\n\tstorage_property.cpp\n\tstorage_property.h\n\tstorage_property_descr.cpp\n\tstorage_property_descr.h\n\tstorage_property_descr_ata_attribute.cpp\n\tstorage_property_descr_ata_attribute.h\n\tstorage_property_descr_ata_statistic.cpp\n\tstorage_property_descr_ata_statistic.h\n\tstorage_property_descr_helpers.h\n\tstorage_property_descr_nvme_attribute.cpp\n\tstorage_property_descr_nvme_attribute.h\n\tstorage_property_repository.cpp\n\tstorage_property_repository.h\n\tstorage_settings.h\n\twarning_colors.cpp\n\twarning_colors.h\n\twarning_level.h\n\twindow_instance_manager.h\n)\n\ntarget_link_libraries(applib\n\tPUBLIC\n\t\tlibdebug\n\t\thz\n\t\trconfig\n\t\tapp_gtkmm_interface\n\t\tapp_gettext_interface\n\t\tfmt\n\t\tbuild_config\n)\n\ntarget_include_directories(applib\n\tPUBLIC\n\t\t\"${CMAKE_SOURCE_DIR}/src\"\n)\n\n\nadd_subdirectory(examples)\nadd_subdirectory(tests)\n\n"
  },
  {
    "path": "src/applib/app_builder_widget.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef APP_BUILDER_WIDGET_H\n#define APP_BUILDER_WIDGET_H\n\n#include <glibmm.h>\n#include <glibmm/i18n.h>\n#include <gtkmm.h>\n\n#include <string>\n#include <memory>\n#include <utility>\n\n#include \"hz/debug.h\"\n#include \"hz/data_file.h\"\n\n#include \"window_instance_manager.h\"\n#include \"gui_utils.h\"  // gui_show_error_dialog\n\n\n\n/// Connect member function (callback) to signal \\ref signal_name on widget\n/// \\ref ui_element, where \\ref ui_element is the widget's gtkbuilder name.\n/// This allows easy attaching of gtkbuilder widget signals to member functions.\n#define APP_BUILDER_CONNECT(ui_element, signal_name, callback) \\\n\tif (true) { \\\n\t\tif (!(ui_element)) \\\n\t\t\t[[maybe_unused]] bool found = this->lookup_widget(#ui_element, ui_element); \\\n\t\tif (ui_element) { \\\n\t\t\t(ui_element)->signal_ ## signal_name ().connect(sigc::mem_fun(*this, &std::remove_reference_t<decltype(*this)>::callback)); \\\n\t\t} \\\n\t} else (void)0\n\n\n\n/// Connect member function (callback) with a name of \\c on_<widget_name>_<signal_name>\n/// to signal \\c signal_name on widget \\c ui_element, where \\c ui_element is the\n/// widget's gtkbuilder name.\n#define APP_BUILDER_AUTO_CONNECT(ui_element, signal_name) \\\n\tAPP_BUILDER_CONNECT(ui_element, signal_name, on_ ## ui_element ## _ ## signal_name)\n\n\n\n\n/// Inherit this when using GtkBuilder-enabled windows (or any other GtkBuilder-enabled objects).\n/// \\c Child is the child class that inherits all the functionality of having instance lifetime\n/// management and other benefits.\n/// If \\c MultiInstance is false, create() will return the same instance each time.\ntemplate<class Child, bool MultiInstance, class WidgetType = Gtk::Window>\nclass AppBuilderWidget : public WidgetType, public WindowInstanceManager<Child, MultiInstance> {\n\tpublic:\n\n\t\tfriend class Gtk::Builder;  // allow construction via GtkBuilder\n\t\t// friend class WindowInstanceManager<Child, MultiInstance>;  // allow construction through instance class\n\n\n\t\t/// Disallow\n\t\tAppBuilderWidget(const AppBuilderWidget& other) = delete;\n\n\t\t/// Disallow\n\t\tAppBuilderWidget(AppBuilderWidget&& other) = delete;\n\n\t\t/// Disallow\n\t\tAppBuilderWidget& operator=(const AppBuilderWidget& other) = delete;\n\n\t\t/// Disallow\n\t\tAppBuilderWidget& operator=(AppBuilderWidget&& other) = delete;\n\n\t\t/// Default\n\t\t~AppBuilderWidget() = default;\n\n\n\n\t\t/// Create an instance of this class, returning an existing instance if not MultiInstance.\n\t\t/// A glade file in \"ui\" data domain is loaded with Child::ui_name filename base and is available as\n\t\t/// `get_ui()` in child object.\n\t\t/// \\return nullptr if widget could not be loaded.\n\t\tstatic std::shared_ptr<Child> create();\n\n\n\t\t/// Get UI resource\n\t\t[[nodiscard]] Glib::RefPtr<Gtk::Builder> get_ui();\n\n\n\t\t/// Find a widget in UI and return it.\n\t\t/// \\return nullptr if widget was not found.\n\t\t[[nodiscard]] Gtk::Widget* lookup_widget(const Glib::ustring& name);\n\n\n\t\t/// Find a widget in UI and return it.\n\t\t/// \\return nullptr if widget was not found.\n\t\ttemplate<typename WidgetPtr>\n\t\t[[nodiscard]] WidgetPtr lookup_widget(const Glib::ustring& name);\n\n\n\t\t/// Find a widget in UI and return it in \\ref w.\n\t\t/// \\return false if widget was not found.\n\t\ttemplate<typename Widget>\n\t\t[[nodiscard]] bool lookup_widget(const Glib::ustring& name, Widget*& w);\n\n\n\tprotected:\n\n\n\t\t/// Protected constructor, use `create()` instead.\n\t\t/// GtkBuilder needs this constructor in a child.\n\t\t/// BaseObjectType is a C type, defined in specific Gtk:: widget class.\n\t\tAppBuilderWidget(typename WidgetType::BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui);\n\n\n\tprivate:\n\n\t\tGlib::RefPtr<Gtk::Builder> ui_;  ///< UI resource\n\n};\n\n\n\n\n\n// ------------------------------------------- Implementation\n\n\ntemplate<class Child, bool MultiInstance, class WidgetType>\nstd::shared_ptr<Child> AppBuilderWidget<Child, MultiInstance, WidgetType>::create()\n{\n\tif constexpr(!MultiInstance) {  // for single-instance objects\n\t\tif (auto inst = WindowInstanceManager<Child, MultiInstance>::instance()) {\n\t\t\treturn inst;\n\t\t}\n\t}\n\n\tstd::string error_msg;\n\n\tauto ui_path = hz::data_file_find(\"ui\", std::string(Child::ui_name) + \".glade\");\n\ttry {\n\t\tauto ui = Gtk::Builder::create_from_file(hz::fs_path_to_string(ui_path));  // may throw\n\n\t\tChild* raw_obj = nullptr;\n\t\tui->get_widget_derived({Child::ui_name.data(), Child::ui_name.size()}, raw_obj);  // Calls Child's constructor\n\t\tif (!raw_obj) {\n\t\t\tdebug_out_fatal(\"app\", \"Fatal error: Cannot get root widget from UI-resource-created hierarchy.\\n\");\n\t\t\tgui_show_error_dialog(_(\"Fatal error: Cannot get root widget from UI-resource-created hierarchy.\"));\n\t\t\treturn nullptr;\n\t\t}\n\n\t\t// Store the instance so it does not get destroyed\n\t\treturn WindowInstanceManager<Child, MultiInstance>::store_instance(raw_obj);\n\t}\n\tcatch (Glib::Exception& ex) {\n\t\terror_msg = ex.what();\n\t}\n\n\tif (!error_msg.empty()) {\n\t\tdebug_out_fatal(\"app\", \"Fatal error: Cannot create UI-resource widgets: \" << error_msg << \"\\n\");\n\t\tgui_show_error_dialog(Glib::ustring::compose(_(\"Fatal error: Cannot create UI-resource widgets: %1\"), error_msg));\n\t}\n\treturn nullptr;\n}\n\n\n\ntemplate<class Child, bool MultiInstance, class WidgetType>\nGlib::RefPtr<Gtk::Builder> AppBuilderWidget<Child, MultiInstance, WidgetType>::get_ui()\n{\n\treturn ui_;\n}\n\n\n\ntemplate<class Child, bool MultiInstance, class WidgetType>\ntemplate<typename WidgetPtr>\nWidgetPtr AppBuilderWidget<Child, MultiInstance, WidgetType>::lookup_widget(const Glib::ustring& name)\n{\n\tWidgetPtr w = nullptr;\n\t[[maybe_unused]] bool success = lookup_widget(name, w);\n\treturn w;\n}\n\n\n\ntemplate<class Child, bool MultiInstance, class WidgetType>\nGtk::Widget* AppBuilderWidget<Child, MultiInstance, WidgetType>::lookup_widget(const Glib::ustring& name)\n{\n\treturn lookup_widget<Gtk::Widget*>(name);\n}\n\n\n\ntemplate<class Child, bool MultiInstance, class WidgetType>\ntemplate<typename Widget>\nbool AppBuilderWidget<Child, MultiInstance, WidgetType>::lookup_widget(const Glib::ustring& name, Widget*& w)\n{\n\tui_->get_widget(name, w);\n\treturn w != nullptr;\n}\n\n\n\ntemplate<class Child, bool MultiInstance, class WidgetType>\nAppBuilderWidget<Child, MultiInstance, WidgetType>::AppBuilderWidget(typename WidgetType::BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui)\n\t\t: WidgetType(gtkcobj), ui_(std::move(ui))\n{\n\t// An example of Child's constructor:\n\n\t// Manually connect signals:\n\t// this->signal_delete_event().connect(sigc::mem_fun(*this, &MainWindow::on_main_window_delete));\n\n\t// Automatically connect signals of GtkBuilder-created objects to member functions:\n\t// Gtk::ToolButton* rescan_devices_toolbutton = 0;\n\t// APP_BUILDER_AUTO_CONNECT(rescan_devices_toolbutton, clicked);\n\n\t// show();\n}\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/app_gtkmm_tools.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include <glibmm.h>\n#include <gtkmm.h>\n#include <glib.h>\n#include <cstring>  // std::strlen\n#include <vector>\n#include <string>\n\n#include \"app_gtkmm_tools.h\"\n#include \"build_config.h\"\n\n\n\nGtk::Widget* app_gtkmm_get_column_header(Gtk::TreeViewColumn& column)\n{\n\tGtk::Widget* w = column.get_widget();\n\tGtk::Widget* p1 = nullptr;\n\tGtk::Widget* p2 = nullptr;\n\tGtk::Widget* p3 = nullptr;\n\n\t// move up to GtkAlignment, then GtkHBox, then GtkButton.\n\tif (w && (p1 = w->get_parent()) && (p2 = p1->get_parent()) && (p3 = p2->get_parent()))\n\t\treturn p3;\n\n\treturn nullptr;\n}\n\n\n\nGtk::Label& app_gtkmm_labelize_column(Gtk::TreeViewColumn& column)\n{\n\tGtk::Label* label = Gtk::manage(new Gtk::Label(column.get_title()));\n\tlabel->show();\n\tcolumn.set_widget(*label);\n\treturn *label;\n}\n\n\n\nvoid app_gtkmm_set_widget_tooltip(Gtk::Widget& widget,\n\t\tconst Glib::ustring& tooltip_text, bool use_markup)\n{\n\tif (use_markup) {\n\t\twidget.set_tooltip_markup(tooltip_text);\n\t} else {\n\t\twidget.set_tooltip_text(tooltip_text);\n\t}\n}\n\n\n\nnamespace {\n\n\t/// This has been copied from _g_utf8_make_valid() (glib-2.20.4).\n\t/// _g_utf8_make_valid() is GLib's private function for auto-correcting\n\t/// the potentially invalid utf-8 data.\n\tinline gchar* app_make_valid_utf_c (const gchar* name)\n\t{\n\t\tGString* str = nullptr;\n\t\tconst gchar* remainder = nullptr;\n\t\tconst gchar* invalid = nullptr;\n\t\tgint remaining_bytes = 0, valid_bytes = 0;\n\n\t\tg_return_val_if_fail (name != nullptr, nullptr);\n\n\t\tstr = nullptr;\n\t\tremainder = name;\n\t\tremaining_bytes = gint(std::strlen(name));\n\n\t\twhile (remaining_bytes != 0) {\n\t\t\tif (g_utf8_validate (remainder, remaining_bytes, &invalid) == TRUE)\n\t\t\t\tbreak;\n\n\t\t\tvalid_bytes = gint(invalid - remainder);\n\n\t\t\tif (str == nullptr)\n\t\t\t\tstr = g_string_sized_new (gsize(remaining_bytes));\n\n\t\t\tg_string_append_len (str, remainder, valid_bytes);\n\t\t\t/* append U+FFFD REPLACEMENT CHARACTER */\n\t\t\tg_string_append (str, \"\\357\\277\\275\");\n\n\t\t\tremaining_bytes -= valid_bytes + 1;\n\t\t\tremainder = invalid + 1;\n\t\t}\n\n\t\tif (str == nullptr)\n\t\t\treturn g_strdup (name);\n\n\t\tg_string_append (str, remainder);\n\n\t\tg_assert (g_utf8_validate (str->str, -1, nullptr));\n\n\t\treturn g_string_free (str, FALSE);\n\t}\n\n}\n\n\n\nGlib::ustring app_ustring_from_gchar(gchar* str)\n{\n\tif (!str) {\n\t\treturn {};\n\t}\n\tGlib::ustring ustr(str);\n\tg_free(str);\n\treturn ustr;\n}\n\n\n\nstd::string app_string_from_gchar(gchar* str)\n{\n\tif (!str) {\n\t\treturn {};\n\t}\n\tstd::string sstr(str);\n\tg_free(str);\n\treturn sstr;\n}\n\n\n\nGlib::ustring app_make_valid_utf8(const Glib::ustring& str)\n{\n\treturn app_ustring_from_gchar(app_make_valid_utf_c(str.c_str()));\n}\n\n\n\nGlib::ustring app_make_valid_utf8_from_command_output(const std::string& str)\n{\n\tif (BuildEnv::is_kernel_family_windows()) {\n\t\ttry {\n\t\t\treturn Glib::locale_to_utf8(str);  // detects invalid utf-8 sequences\n\t\t} catch (Glib::ConvertError& e) {\n\t\t\t// nothing, try to fix as it is\n\t\t}\n\t}\n\treturn app_ustring_from_gchar(app_make_valid_utf_c(str.c_str()));\n}\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/app_gtkmm_tools.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef APP_GTKMM_TOOLS_H\n#define APP_GTKMM_TOOLS_H\n\n#include <string>\n#include <gtkmm.h>\n\n\n\n/// \\def APP_GTKMM_CHECK_VERSION(major, minor, micro)\n/// Similar to GTK_CHECK_VERSION, but for Gtkmm, which lacks this before gtkmm4.\n/// This is useful as Gtk and Gtkmm versions may differ.\n#ifndef APP_GTKMM_CHECK_VERSION\n\t#define APP_GTKMM_CHECK_VERSION(major, minor, micro) \\\n\t\t(GTKMM_MAJOR_VERSION > (major) \\\n\t\t\t|| (GTKMM_MAJOR_VERSION == (major) && (GTKMM_MINOR_VERSION > (minor) \\\n\t\t\t\t|| (GTKMM_MINOR_VERSION == (minor) && GTKMM_MICRO_VERSION >= (micro)) \\\n\t\t\t\t) \\\n\t\t\t) \\\n\t\t)\n#endif\n\n\n\n\n/// Get column header widget of a tree view column.\n/// Note: This works only if the column has custom widget set.\n/// \\return nullptr on failure.\nGtk::Widget* app_gtkmm_get_column_header(Gtk::TreeViewColumn& column);\n\n\n/// Read column header text and create a label with that text. Set the label as\n/// column's custom widget and return it.\nGtk::Label& app_gtkmm_labelize_column(Gtk::TreeViewColumn& column);\n\n\n/// A wrapper around set_tooltip_*(), calling appropriate method depending on `use_markup`.\nvoid app_gtkmm_set_widget_tooltip(Gtk::Widget& widget,\n\t\tconst Glib::ustring& tooltip_text, bool use_markup = false);\n\n\n/// Convenience function for creating a TreeViewColumn from model column.\n/// \\return tree column index\ntemplate<typename DataType>\nint app_gtkmm_create_tree_view_column(const Gtk::TreeModelColumn<DataType>& model_column,\n\t\tGtk::TreeView& treeview, const Glib::ustring& header_title, const Glib::ustring& header_tooltip_text,\n\t\tbool sortable = false, bool use_cell_markup = false, bool header_tooltip_is_markup = false);\n\n\n\n/// Get Glib::ustring from gchar*, freeing gchar*.\nGlib::ustring app_ustring_from_gchar(gchar* str);\n\n\n/// Get std::string from gchar*, freeing gchar*.\nstd::string app_string_from_gchar(gchar* str);\n\n\n/// Convert a possibly invalid utf-8 string to valid utf-8.\n/// \\param str string to test and fix.\nGlib::ustring app_make_valid_utf8(const Glib::ustring& str);\n\n\n/// Make command output a valid utf-8 string. This function takes command output\n/// (in locale encoding under Windows, utf-8 encoding under other OSes), and converts\n/// it to valid utf-8.\n/// The reason for this is that in Win32 we can't execute commands under C locale,\n/// but we do execute them under C in other systems.\nGlib::ustring app_make_valid_utf8_from_command_output(const std::string& str);\n\n\n\n\n// ------------------------------------------- Implementation\n\n\n\n\ntemplate<typename DataType>\nint app_gtkmm_create_tree_view_column(const Gtk::TreeModelColumn<DataType>& model_column,\n\t\tGtk::TreeView& treeview, const Glib::ustring& header_title, const Glib::ustring& header_tooltip_text,\n\t\tbool sortable, bool use_cell_markup, bool header_tooltip_is_markup)\n{\n\tint num_tree_cols = treeview.append_column(header_title, model_column);\n\tGtk::TreeViewColumn* tcol = treeview.get_column(num_tree_cols - 1);\n\tif (tcol) {\n\t\tif (sortable) {\n\t\t\ttcol->set_sort_column(model_column);\n\t\t}\n\n\t\tapp_gtkmm_labelize_column(*tcol);\n\t\ttcol->set_reorderable(true);\n\t\ttcol->set_resizable(true);\n\n\t\tGtk::Widget* header = app_gtkmm_get_column_header(*tcol);\n\t\tif (header) {\n\t\t\tapp_gtkmm_set_widget_tooltip(*header, header_tooltip_text, header_tooltip_is_markup);\n\t\t}\n\t}\n\n\tif (use_cell_markup) {\n\t\tif (auto* cr_type = dynamic_cast<Gtk::CellRendererText*>(treeview.get_column_cell_renderer(num_tree_cols - 1))) {\n\t\t\ttreeview.get_column(num_tree_cols - 1)->clear_attributes(*cr_type);  // clear \"text\" attribute. \"markup\" won't work without this.\n\t\t\ttreeview.get_column(num_tree_cols - 1)->add_attribute(cr_type->property_markup(), model_column);  // render col_type as markup.\n\t\t}\n\t}\n\n\treturn num_tree_cols - 1;\n}\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/app_regex.h",
    "content": "/******************************************************************************\n License: GNU General Public License v3.0 only\n Copyright:\n \t(C) 2024 Alexander Shaduri <ashaduri@gmail.com>\n ******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef APP_REGEX_H\n#define APP_REGEX_H\n\n#include <cstddef>\n#include <map>\n#include <string>\n#include <string_view>\n#include <vector>\n#include <regex>\n\n#include \"hz/debug.h\"\n#include \"hz/string_algo.h\"\n\n\n/**\n * \\file\n * Convenience wrappers for std::regex\n */\n\n\n\n/// Take a string of characters where each character represents a modifier\n/// and return the appropriate flags.\n/// - i - case insensitive match.\n/// - m - multiline (EcmaScript syntax only).\ninline std::regex::flag_type app_regex_get_options(std::string_view modifiers)\n{\n\tstd::regex::flag_type options = std::regex::ECMAScript;  // default, non-greedy.\n\tfor (const char c : modifiers) {\n\t\tswitch (c) {\n\t\t\tcase 'i': options |= std::regex_constants::icase; break;  // case insensitive match.\n\t\t\tcase 'm': options |= std::regex_constants::multiline; break;  // ^ and $ match each line as well. EcmaScript only.\n\t\t\tdefault: debug_out_error(\"app\", DBG_FUNC_MSG << \"Unknown modifier \\'\" << c << \"\\'\\n\"); break;\n\t\t}\n\t}\n\treturn options;\n}\n\n\n\n/// Create a regular expression.\n/// Pattern is in form of \"/pattern/modifiers\".\n/// Note: Slashes should be escaped with backslashes within the pattern.\n/// If the string doesn't start with a slash, it is treated as an ordinary pattern\n/// without modifiers.\ninline std::regex app_regex_re(const std::string& perl_pattern)\n{\n\tif (perl_pattern.size() >= 2 && perl_pattern[0] == '/') {\n\n\t\t// find the separator\n\t\tconst std::string::size_type endpos = perl_pattern.rfind('/');\n\t\tDBG_ASSERT(endpos != std::string::npos);  // shouldn't happen\n\n\t\t// no need to unescape slashes in pattern\n\t\treturn std::regex(perl_pattern.substr(1, endpos - 1),\n\t\t\t\tapp_regex_get_options(perl_pattern.substr(endpos + 1)));\n\t}\n\n\treturn std::regex(perl_pattern, app_regex_get_options({}));\n}\n\n\n\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_partial_match(const std::regex& re, const std::string& str)\n{\n\treturn std::regex_search(str, re);\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_partial_match(const std::string& perl_pattern, const std::string& str)\n{\n\treturn app_regex_partial_match(app_regex_re(perl_pattern), str);\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_partial_match(const char* perl_pattern, const std::string& str)\n{\n\treturn app_regex_partial_match(app_regex_re(perl_pattern), str);\n}\n\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_partial_match(const std::regex& re, const std::string& str, std::smatch& matches)\n{\n\treturn std::regex_search(str, matches, re);\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_partial_match(const std::string& perl_pattern, const std::string& str, std::smatch& matches)\n{\n\treturn app_regex_partial_match(app_regex_re(perl_pattern), str, matches);\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_partial_match(const char* perl_pattern, const std::string& str, std::smatch& matches)\n{\n\treturn app_regex_partial_match(app_regex_re(perl_pattern), str, matches);\n}\n\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_partial_match(const std::regex& re, const std::string& str, std::string* first_submatch)\n{\n\tstd::smatch matches;\n\tif (!app_regex_partial_match(re, str, matches) || matches.size() < 2) {\n\t\treturn false;\n\t}\n\tif (first_submatch) {\n\t\t*first_submatch = matches[1].str();\n\t}\n\treturn true;\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_partial_match(const std::string& perl_pattern, const std::string& str, std::string* first_submatch)\n{\n\treturn app_regex_partial_match(app_regex_re(perl_pattern), str, first_submatch);\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_partial_match(const char* perl_pattern, const std::string& str, std::string* first_submatch)\n{\n\treturn app_regex_partial_match(app_regex_re(perl_pattern), str, first_submatch);\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_partial_match(const std::regex& re, const std::string& str, std::vector<std::string*> matches_vector)\n{\n\tstd::smatch matches;\n\tif (!app_regex_partial_match(re, str, matches) || (matches.size() + 1) < matches_vector.size()) {\n\t\treturn false;\n\t}\n\n\tfor (std::size_t i = 0; i < matches_vector.size(); ++i) {\n\t\tif (matches_vector[i]) {\n\t\t\t*(matches_vector[i]) = matches.str(i + 1);\n\t\t}\n\t}\n\n\treturn true;\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_partial_match(const std::string& perl_pattern, const std::string& str, std::vector<std::string*> matches_vector)\n{\n\treturn app_regex_partial_match(app_regex_re(perl_pattern), str, matches_vector);\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_partial_match(const char* perl_pattern, const std::string& str, std::vector<std::string*> matches_vector)\n{\n\treturn app_regex_partial_match(app_regex_re(perl_pattern), str, matches_vector);\n}\n\n\n\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_full_match(const std::regex& re, const std::string& str)\n{\n\treturn std::regex_match(str, re);\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_full_match(const std::string& perl_pattern, const std::string& str)\n{\n\treturn app_regex_full_match(app_regex_re(perl_pattern), str);\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_full_match(const char* perl_pattern, const std::string& str)\n{\n\treturn app_regex_full_match(app_regex_re(perl_pattern), str);\n}\n\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_full_match(const std::regex& re, const std::string& str, std::smatch& matches)\n{\n\treturn std::regex_match(str, matches, re);\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_full_match(const std::string& perl_pattern, const std::string& str, std::smatch& matches)\n{\n\treturn app_regex_full_match(app_regex_re(perl_pattern), str, matches);\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_full_match(const char* perl_pattern, const std::string& str, std::smatch& matches)\n{\n\treturn app_regex_full_match(app_regex_re(perl_pattern), str, matches);\n}\n\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_full_match(const std::regex& re, const std::string& str, std::string* first_submatch)\n{\n\tstd::smatch matches;\n\tif (!app_regex_full_match(re, str, matches) || matches.size() < 2) {\n\t\treturn false;\n\t}\n\tif (first_submatch) {\n\t\t*first_submatch = matches[1].str();\n\t}\n\treturn true;\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_full_match(const std::string& perl_pattern, const std::string& str, std::string* first_submatch)\n{\n\treturn app_regex_full_match(app_regex_re(perl_pattern), str, first_submatch);\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_full_match(const char* perl_pattern, const std::string& str, std::string* first_submatch)\n{\n\treturn app_regex_full_match(app_regex_re(perl_pattern), str, first_submatch);\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_full_match(const std::regex& re, const std::string& str, std::vector<std::string*> matches_vector)\n{\n\tstd::smatch matches;\n\tif (!app_regex_full_match(re, str, matches) || (matches.size() + 1) < matches_vector.size()) {\n\t\treturn false;\n\t}\n\n\tfor (std::size_t i = 0; i < matches_vector.size(); ++i) {\n\t\tif (matches_vector[i]) {\n\t\t\t*(matches_vector[i]) = matches.str(i + 1);\n\t\t}\n\t}\n\n\treturn true;\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_full_match(const std::string& perl_pattern, const std::string& str, std::vector<std::string*> matches_vector)\n{\n\treturn app_regex_full_match(app_regex_re(perl_pattern), str, matches_vector);\n}\n\n\n/// Partially match a string against a regular expression.\n/// \\return true if a match was found.\ninline bool app_regex_full_match(const char* perl_pattern, const std::string& str, std::vector<std::string*> matches_vector)\n{\n\treturn app_regex_full_match(app_regex_re(perl_pattern), str, matches_vector);\n}\n\n\n\n\n\n\n/// Replace every occurrence of pattern with replacement string in \\c subject.\n/// \\return number of replacements made.\ninline void app_regex_replace(const std::regex& re, const std::string& replacement, std::string& subject)\n{\n\tsubject = std::regex_replace(subject, re, replacement);\n}\n\n\n/// Replace every occurrence of pattern with replacement string in \\c subject.\n/// The pattern is in \"/pattern/modifiers\" format.\n/// \\return number of replacements made.\ninline void app_regex_replace(const std::string& perl_pattern, const std::string& replacement, std::string& subject)\n{\n\tapp_regex_replace(app_regex_re(perl_pattern), replacement, subject);\n}\n\n\n/// Replace every occurrence of pattern with replacement string in \\c subject.\n/// The pattern is in \"/pattern/modifiers\" format.\n/// \\return number of replacements made.\ninline void app_regex_replace(const char* perl_pattern, const std::string& replacement, std::string& subject)\n{\n\tapp_regex_replace(app_regex_re(perl_pattern), replacement, subject);\n}\n\n\n\n\n/// Escape a string to be used inside a regular expression. The result\n/// won't contain any special expression characters.\ninline std::string app_regex_escape(const std::string& str)\n{\n\t// Based on\n\t// https://stackoverflow.com/questions/39228912/stdregex-escape-special-characters-for-use-in-regex\n\tstatic const std::map<std::string, std::string> replacements {\n\t\t\t{\"\\\\\", \"\\\\\\\\\"},\n\t\t\t{\"^\", \"\\\\^\"},\n\t\t\t{\"$\", \"\\\\$\"},\n//\t\t\t{\"-\", \"\\\\-\"},\n\t\t\t{\"|\", \"\\\\|\"},\n\t\t\t{\".\", \"\\\\.\"},\n\t\t\t{\"?\", \"\\\\?\"},\n\t\t\t{\"*\", \"\\\\*\"},\n\t\t\t{\"+\", \"\\\\+\"},\n\t\t\t{\"(\", \"\\\\(\"},\n\t\t\t{\")\", \"\\\\)\"},\n\t\t\t{\"[\", \"\\\\[\"},\n\t\t\t{\"]\", \"\\\\]\"},\n\t\t\t{\"{\", \"\\\\{\"},\n\t\t\t{\"}\", \"\\\\}\"},\n\t};\n\treturn hz::string_replace_array_copy(str, replacements);\n}\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/async_command_executor.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include <glibmm.h>\n\n#include <string>\n#include <sys/types.h>\n#include <cerrno>  // errno (not std::errno, it may be a macro)\n#include <array>\n\n#ifdef _WIN32\n// \t#include <io.h>  // close()\n#else\n\t#include <sys/wait.h>  // waitpid()'s W* macros\n// \t#include <unistd.h>  // close()\n#endif\n\n#include \"hz/process_signal.h\"  // hz::process_signal_send, win32's W*\n#include \"hz/debug.h\"\n#include \"hz/fs.h\"\n\n#include \"async_command_executor.h\"\n#include \"build_config.h\"\n\n\nusing hz::Error;\nusing hz::ErrorLevel;\n\n\n\n\n// this is needed because these callbacks are called by glib.\nextern \"C\" {\n\n\n\t// callbacks\n\n\t/// Child process watcher callback\n\tinline void cmdex_child_watch_handler(GPid arg_pid, int waitpid_status, gpointer data)\n\t{\n\t\tAsyncCommandExecutor::on_child_watch_handler(arg_pid, waitpid_status, data);\n\t}\n\n\n\t/// Child process stdout handler callback\n\tinline gboolean cmdex_on_channel_io_stdout(GIOChannel* source, GIOCondition cond, gpointer data)\n\t{\n\t\treturn AsyncCommandExecutor::on_channel_io(source, cond, static_cast<AsyncCommandExecutor*>(data), AsyncCommandExecutor::Channel::StandardOutput);\n\t}\n\n\n\t/// Child process stderr handler callback\n\tinline gboolean cmdex_on_channel_io_stderr(GIOChannel* source, GIOCondition cond, gpointer data)\n\t{\n\t\treturn AsyncCommandExecutor::on_channel_io(source, cond, static_cast<AsyncCommandExecutor*>(data), AsyncCommandExecutor::Channel::StandardError);\n\t}\n\n\n\t/// Child process termination timeout handler\n\tinline gboolean cmdex_on_term_timeout(gpointer data)\n\t{\n\t\tDBG_FUNCTION_ENTER_MSG;\n\t\tauto* self = static_cast<AsyncCommandExecutor*>(data);\n\t\tself->try_stop(hz::Signal::Terminate);\n\t\treturn FALSE;  // one-time call\n\t}\n\n\n\t/// Child process kill timeout handler\n\tinline gboolean cmdex_on_kill_timeout(gpointer data)\n\t{\n\t\tDBG_FUNCTION_ENTER_MSG;\n\t\tauto* self = static_cast<AsyncCommandExecutor*>(data);\n\t\tself->try_stop(hz::Signal::Kill);\n\t\treturn FALSE;  // one-time call\n\t}\n\n\n\n}  // extern \"C\"\n\n\n\n\nAsyncCommandExecutor::AsyncCommandExecutor(AsyncCommandExecutor::exited_callback_func_t exited_cb)\n\t\t: timer_(g_timer_new()),\n\t\texited_callback_(std::move(exited_cb))\n{ }\n\n\n\nAsyncCommandExecutor::~AsyncCommandExecutor()\n{\n\t// This will help if object is destroyed after the command has exited, but before\n\t// stopped_cleanup() has been called.\n\tstopped_cleanup();\n\n\tg_timer_destroy(timer_);\n\n\t// no need to destroy the channels - stopped_cleanup() calls\n\t// cleanup_members(), which deletes them.\n}\n\n\n\nvoid AsyncCommandExecutor::set_command(std::string command_exec, std::vector<std::string> command_args)\n{\n\tcommand_exec_ = std::move(command_exec);\n\tcommand_args_ = std::move(command_args);\n}\n\n\n\nbool AsyncCommandExecutor::execute()\n{\n\tDBG_FUNCTION_ENTER_MSG;\n\tif (this->running_ || this->stopped_cleanup_needed()) {\n\t\treturn false;\n\t}\n\n\tcleanup_members();\n\tclear_errors();\n\tstr_stdout_.clear();\n\tstr_stderr_.clear();\n\n\n\t// Set the locale for a child to Classic - otherwise it may mangle the output.\n\t// TODO: Disable this for JSON format.\n\tbool change_lang = true;\n\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\t// LANG is posix-only, so it has no effect on win32.\n\t\t// Unfortunately, I was unable to find a way to execute a child with a different\n\t\t// locale in win32. Locale seems to be non-inheritable, so setting it here won't help.\n\t\tchange_lang = false;\n\t}\n\n\tstd::unique_ptr<gchar*, decltype(&g_strfreev)> child_env(g_get_environ(), &g_strfreev);\n\tif (change_lang) {\n\t\tchild_env.reset(g_environ_setenv(child_env.release(), \"LC_ALL\", \"C\", TRUE));\n\t}\n\tconst std::vector<std::string> envp = Glib::ArrayHandler<std::string>::array_to_vector(child_env.release(),\n\t\t\tGlib::OWNERSHIP_DEEP);\n\n\t// Set the current directory to application directory so CWD does not interfere with finding binaries.\n\tauto current_path = hz::fs::current_path();\n\tbool path_changed = false;\n\tif (auto app_dir = hz::fs_get_application_dir(); !app_dir.empty()) {\n\t\tstd::error_code ec;\n\t\thz::fs::current_path(app_dir, ec);\n\t\tpath_changed = !ec;\n\t}\n\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Executing \\\"\" << command_exec_ << \"\\\".\\n\");\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Arguments:\\n\");\n\tfor (const auto& arg : command_args_) {\n\t\tdebug_out_info(\"app\", \"  \" << arg << \"\\n\");\n\t}\n\n\tstd::vector<std::string> argvp = {command_exec_};\n\targvp.insert(argvp.end(), command_args_.begin(), command_args_.end());\n\n\t// Execute the command\n\ttry {\n\t\tGlib::spawn_async_with_pipes(Glib::get_current_dir(), argvp, envp,\n\t\t\t\tGlib::SpawnFlags::SPAWN_SEARCH_PATH | Glib::SpawnFlags::SPAWN_DO_NOT_REAP_CHILD,\n\t\t\t\tGlib::SlotSpawnChildSetup(),\n\t\t\t\t&this->pid_, nullptr, &fd_stdout_, &fd_stderr_);\n\t}\n\tcatch(Glib::SpawnError& e) {\n\t\t// no data is returned to &-parameters on error.\n\t\tpush_error(Error<void>(\"gspawn\", ErrorLevel::Error, e.what()));\n\t\t// Restore CWD\n\t\tif (path_changed) {\n\t\t\tstd::error_code dummy_ec;\n\t\t\thz::fs::current_path(current_path, dummy_ec);\n\t\t}\n\t\treturn false;\n\t}\n\n\t// Restore CWD\n\tif (path_changed) {\n\t\tstd::error_code dummy_ec;\n\t\thz::fs::current_path(current_path, dummy_ec);\n\t}\n\n\tg_timer_start(timer_);  // start the timer\n\n\n\t#ifdef _WIN32\n\t\tchannel_stdout_ = g_io_channel_win32_new_fd(fd_stdout_);\n\t\tchannel_stderr_ = g_io_channel_win32_new_fd(fd_stderr_);\n\t#else\n\t\tchannel_stdout_ = g_io_channel_unix_new(fd_stdout_);\n\t\tchannel_stderr_ = g_io_channel_unix_new(fd_stderr_);\n\t#endif\n\n\t// The internal encoding is always UTF8. To read command output correctly, use\n\t// \"\" for binary data, or set io encoding to current locale.\n\t// If using locales, call g_locale_to_utf8() or g_convert() afterwards.\n\n\t// blocking writes if the pipe is full helps for small-pipe systems (see man 7 pipe).\n\tconst int channel_flags = ~G_IO_FLAG_NONBLOCK;\n\n\t// Note about GError's here:\n\t// What do we do? The command is already running, so let's ignore these\n\t// errors - it's better to get a slightly mangled buffer than to abort the\n\t// command in the mid-run.\n\tif (channel_stdout_) {\n\t\t// Since we invoke shutdown() manually before unref(), this would cause\n\t\t// a double-shutdown.\n\t\t// g_io_channel_set_close_on_unref(channel_stdout_, true);  // close() on fd\n\t\tg_io_channel_set_encoding(channel_stdout_, nullptr, nullptr);  // binary IO\n\t\tg_io_channel_set_flags(channel_stdout_, GIOFlags(g_io_channel_get_flags(channel_stdout_) & channel_flags), nullptr);\n\t\tg_io_channel_set_buffer_size(channel_stdout_, channel_stdout_buffer_size_);\n\t}\n\tif (channel_stderr_) {\n\t\t// g_io_channel_set_close_on_unref(channel_stderr_, true);  // close() on fd\n\t\tg_io_channel_set_encoding(channel_stderr_, nullptr, nullptr);  // binary IO\n\t\tg_io_channel_set_flags(channel_stderr_, GIOFlags(g_io_channel_get_flags(channel_stderr_) & channel_flags), nullptr);\n\t\tg_io_channel_set_buffer_size(channel_stderr_, channel_stderr_buffer_size_);\n\t}\n\n\n\tauto cond = GIOCondition(G_IO_IN | G_IO_PRI | G_IO_HUP | G_IO_ERR | G_IO_NVAL);\n\t// Channel reader callback must be called before other stuff so that the loss is minimal.\n\tconst int io_priority = G_PRIORITY_HIGH;\n\n\tthis->event_source_id_stdout_ = g_io_add_watch_full(channel_stdout_, io_priority, cond,\n\t\t\t&cmdex_on_channel_io_stdout, this, nullptr);\n// \tg_io_channel_unref(channel_stdout_);  // g_io_add_watch_full() holds its own reference\n\n\tthis->event_source_id_stderr_ = g_io_add_watch_full(channel_stderr_, io_priority, cond,\n\t\t\t&cmdex_on_channel_io_stderr, this, nullptr);\n// \tg_io_channel_unref(channel_stderr_);  // g_io_add_watch_full() holds its own reference\n\n\n\t// If using SPAWN_DO_NOT_REAP_CHILD, this is needed to avoid zombies.\n\t// Note: Do NOT use glibmm slot, it doesn't work here.\n\t// (the child stops being a zombie as soon as wait*() exits and this handler is called).\n\tg_child_watch_add(this->pid_, &cmdex_child_watch_handler, this);\n\n\n\tthis->running_ = true;  // the process is running now.\n\n\tDBG_FUNCTION_EXIT_MSG;\n\treturn true;\n}\n\n\n\nbool AsyncCommandExecutor::try_stop(hz::Signal sig)\n{\n\tDBG_FUNCTION_ENTER_MSG;\n\tif (!this->running_ || this->pid_ == 0)\n\t\treturn false;\n\n\t// other variants: SIGHUP(1) (terminal closed), SIGINT(2) (Ctrl-C),\n\t// SIGKILL(9) (kill).\n\t// Note that SIGKILL cannot be trapped by any process.\n\n\tif (process_signal_send(this->pid_, sig) == 0) {  // success\n\t\tthis->kill_signal_sent_ = static_cast<int>(sig);  // just the number to compare later.\n\t\treturn true;  // the rest is done by a handler\n\t}\n\n\t// Possible: EPERM (no permissions), ESRCH (no such process, or zombie)\n\tpush_error(Error<int>(\"errno\", ErrorLevel::Error, errno));\n\n\tDBG_FUNCTION_EXIT_MSG;\n\treturn false;\n}\n\n\n\nbool AsyncCommandExecutor::try_kill()\n{\n\tDBG_TRACE_POINT_AUTO;\n\treturn try_stop(hz::Signal::Kill);\n}\n\n\n\nvoid AsyncCommandExecutor::set_stop_timeouts(std::chrono::milliseconds term_timeout_msec, std::chrono::milliseconds kill_timeout_msec)\n{\n\tDBG_FUNCTION_ENTER_MSG;\n\tDBG_ASSERT(term_timeout_msec.count() == 0 || kill_timeout_msec.count() == 0 || (kill_timeout_msec > term_timeout_msec));\n\n\tif (!this->running_)  // process not running\n\t\treturn;\n\n\tunset_stop_timeouts();\n\n\tif (term_timeout_msec.count() != 0)\n\t\tevent_source_id_term = g_timeout_add(guint(term_timeout_msec.count()), &cmdex_on_term_timeout, this);\n\n\tif (kill_timeout_msec.count() != 0)\n\t\tevent_source_id_kill = g_timeout_add(guint(kill_timeout_msec.count()), &cmdex_on_kill_timeout, this);\n\n\tDBG_FUNCTION_EXIT_MSG;\n}\n\n\n\nvoid AsyncCommandExecutor::unset_stop_timeouts()\n{\n\tDBG_FUNCTION_ENTER_MSG;\n\tif (event_source_id_term != 0) {\n\t\tGSource* source_term = g_main_context_find_source_by_id(nullptr, event_source_id_term);\n\t\tif (source_term)\n\t\t\tg_source_destroy(source_term);\n\t\tevent_source_id_term = 0;\n\t}\n\n\tif (event_source_id_kill != 0) {\n\t\tGSource* source_kill = g_main_context_find_source_by_id(nullptr, event_source_id_kill);\n\t\tif (source_kill)\n\t\t\tg_source_destroy(source_kill);\n\t\tevent_source_id_kill = 0;\n\t}\n\tDBG_FUNCTION_EXIT_MSG;\n}\n\n\n\nvoid AsyncCommandExecutor::stopped_cleanup()\n{\n\tDBG_FUNCTION_ENTER_MSG;\n\tif (this->running_ || !this->stopped_cleanup_needed())  // huh?\n\t\treturn;\n\n\t// remove stop timeout callbacks\n\tunset_stop_timeouts();\n\n\t// various statuses (see waitpid (2)):\n\tif (WIFEXITED(waitpid_status_)) {  // exited normally\n\t\tconst int exit_status = WEXITSTATUS(waitpid_status_);\n\n\t\tif (exit_status != 0) {\n\t\t\t// translate the exit_code into a message\n\t\t\tconst std::string msg = (translator_func_ ? translator_func_(exit_status)\n\t\t\t\t\t: \"[no translator function, exit code: \" + std::to_string(exit_status));\n\t\t\tpush_error(Error<int>(\"exit\", ErrorLevel::Warn, exit_status, msg));\n\t\t}\n\n\t} else {\n\t\tif (WIFSIGNALED(waitpid_status_)) {  // exited by signal\n\t\t\tconst int sig_num = WTERMSIG(waitpid_status_);\n\n\t\t\t// If it's not our signal, treat as error.\n\t\t\t// Note: they will never match under win32\n\t\t\tif (sig_num != this->kill_signal_sent_) {\n\t\t\t\tpush_error(Error<int>(\"signal\", ErrorLevel::Error, sig_num));\n\t\t\t} else {  // it's our signal, treat as warning\n\t\t\t\tpush_error(Error<int>(\"signal\", ErrorLevel::Warn, sig_num));\n\t\t\t}\n\t\t}\n\t}\n\n\tg_spawn_close_pid(this->pid_);  // needed to avoid zombies\n\n\tcleanup_members();\n\n\tthis->running_ = false;\n\tDBG_FUNCTION_EXIT_MSG;\n}\n\n\n\nvoid AsyncCommandExecutor::on_child_watch_handler([[maybe_unused]] GPid arg_pid, int waitpid_status, gpointer data)\n{\n// \tDBG_FUNCTION_ENTER_MSG;\n\tauto* self = static_cast<AsyncCommandExecutor*>(data);\n\n\tg_timer_stop(self->timer_);  // stop the timer\n\n\tself->waitpid_status_ = waitpid_status;\n\tself->child_watch_handler_called_ = true;\n\tself->running_ = false;  // process is not running anymore\n\n\t// These are needed because Windows doesn't read the remaining data otherwise.\n\tg_io_channel_flush(self->channel_stdout_, nullptr);\n\ton_channel_io(self->channel_stdout_, GIOCondition(0), self, Channel::StandardOutput);\n\n\tg_io_channel_flush(self->channel_stderr_, nullptr);\n\ton_channel_io(self->channel_stderr_, GIOCondition(0), self, Channel::StandardError);\n\n\tif (self->channel_stdout_) {\n\t\tg_io_channel_shutdown(self->channel_stdout_, FALSE, nullptr);\n\t\tg_io_channel_unref(self->channel_stdout_);\n\t\tself->channel_stdout_ = nullptr;\n\t}\n\n\tif (self->channel_stderr_) {\n\t\tg_io_channel_shutdown(self->channel_stderr_, FALSE, nullptr);\n\t\tg_io_channel_unref(self->channel_stderr_);\n\t\tself->channel_stderr_ = nullptr;\n\t}\n\n\t// Remove fd IO callbacks. They may actually be removed already (note sure about this).\n\t// This will force calling the iochannel callback (they may not be called\n\t// otherwise at all if there was no output).\n\tif (self->event_source_id_stdout_ != 0) {\n\t\tGSource* source_stdout = g_main_context_find_source_by_id(nullptr, self->event_source_id_stdout_);\n\t\tif (source_stdout)\n\t\t\tg_source_destroy(source_stdout);\n\t}\n\n\tif (self->event_source_id_stderr_ != 0) {\n\t\tGSource* source_stderr = g_main_context_find_source_by_id(nullptr, self->event_source_id_stderr_);\n\t\tif (source_stderr)\n\t\t\tg_source_destroy(source_stderr);\n\t}\n\n\t// Close std pipes.\n\t// The channel closes them now.\n// \tclose(self->fd_stdout_);\n// \tclose(self->fd_stderr_);\n\n\tif (self->exited_callback_)\n\t\tself->exited_callback_();\n\n// \tDBG_FUNCTION_EXIT_MSG;\n}\n\n\n\ngboolean AsyncCommandExecutor::on_channel_io(GIOChannel* channel,\n\t\tGIOCondition cond, AsyncCommandExecutor* self, Channel channel_type)\n{\n// \tDBG_FUNCTION_ENTER_MSG;\n// \tdebug_out_dump(\"app\", \"AsyncCommandExecutor::on_channel_io(\"\n// \t\t\t<< (type == Channel::standard_output ? \"STDOUT\" : \"STDERR\") << \") \" << int(cond) << \"\\n\");\n\n\tbool continue_events = true;\n\tif (bool(cond & G_IO_ERR) || bool(cond & G_IO_HUP) || bool(cond & G_IO_NVAL)) {\n\t\tcontinue_events = false;  // there'll be no more data\n\t}\n\n\tDBG_ASSERT_RETURN(channel_type == Channel::StandardOutput || channel_type == Channel::StandardError, false);\n\n// \tconst gsize count = 4 * 1024;\n\t// read the bytes one by one. without this, a buffered iochannel hangs while waiting for data.\n\t// we don't use unbuffered iochannels - they may lose data on program exit.\n\tconstexpr gsize count = 1;\n\tstd::array<gchar, count> buf = {0};\n\n\tstd::string* output_str = nullptr;\n\tif (channel_type == Channel::StandardOutput) {\n\t\toutput_str = &self->str_stdout_;\n\t} else if (channel_type == Channel::StandardError) {\n\t\toutput_str = &self->str_stderr_;\n\t}\n\tDBG_ASSERT_RETURN(output_str, false);\n\n\n\t// while there's anything to read, read it\n\tdo {\n\t\tGError* channel_error = nullptr;\n\t\tgsize bytes_read = 0;\n\t\tconst GIOStatus status = g_io_channel_read_chars(channel, buf.data(), count, &bytes_read, &channel_error);\n\t\tif (bytes_read != 0)\n\t\t\toutput_str->append(buf.data(), bytes_read);\n\n\t\tif (channel_error) {\n\t\t\tself->push_error(Error<void>(\"giochannel\", ErrorLevel::Error, channel_error->message));\n\t\t\tg_error_free(channel_error);\n\t\t\tbreak;  // stop on next invocation (is this correct?)\n\t\t}\n\n\t\t// IO_STATUS_NORMAL and IO_STATUS_AGAIN (resource unavailable) are continuable.\n\t\tif (status == G_IO_STATUS_ERROR || status == G_IO_STATUS_EOF) {\n\t\t\tcontinue_events = false;\n\t\t\tbreak;\n\t\t}\n\t} while (bool(g_io_channel_get_buffer_condition(channel) & G_IO_IN));\n\n// \tDBG_FUNCTION_EXIT_MSG;\n\n\t// false if the source should be removed, true otherwise.\n\treturn gboolean(continue_events);\n}\n\n\n\nbool AsyncCommandExecutor::stopped_cleanup_needed() const\n{\n\treturn (child_watch_handler_called_);\n}\n\n\n\nbool AsyncCommandExecutor::is_running() const\n{\n\treturn running_;\n}\n\n\n\nvoid AsyncCommandExecutor::set_buffer_sizes(gsize stdout_buffer_size, gsize stderr_buffer_size)\n{\n\tif (stdout_buffer_size > 0) {\n\t\tchannel_stdout_buffer_size_ = stdout_buffer_size;  // 100K by default\n\t}\n\tif (stderr_buffer_size > 0) {\n\t\tchannel_stderr_buffer_size_ = stderr_buffer_size;  // 10K by default\n\t}\n}\n\n\n\nstd::string AsyncCommandExecutor::get_stdout_str(bool clear_existing)\n{\n\t// debug_out_dump(\"app\", str_stdout_);\n\tif (clear_existing) {\n\t\tstd::string ret = str_stdout_;\n\t\tstr_stdout_.clear();\n\t\treturn ret;\n\t}\n\treturn str_stdout_;\n}\n\n\n\nstd::string AsyncCommandExecutor::get_stderr_str(bool clear_existing)\n{\n\tif (clear_existing) {\n\t\tstd::string ret = str_stderr_;\n\t\tstr_stderr_.clear();\n\t\treturn ret;\n\t}\n\treturn str_stderr_;\n}\n\n\n\ndouble AsyncCommandExecutor::get_execution_time_sec()\n{\n\tgulong microsec = 0;\n\treturn g_timer_elapsed(timer_, &microsec);\n}\n\n\n\nvoid AsyncCommandExecutor::set_exit_status_translator(AsyncCommandExecutor::exit_status_translator_func_t func)\n{\n\ttranslator_func_ = std::move(func);\n}\n\n\n\nvoid AsyncCommandExecutor::set_exited_callback(AsyncCommandExecutor::exited_callback_func_t func)\n{\n\texited_callback_ = std::move(func);\n}\n\n\n\nvoid AsyncCommandExecutor::cleanup_members()\n{\n\tkill_signal_sent_ = 0;\n\tchild_watch_handler_called_ = false;\n\tpid_ = 0;\n\twaitpid_status_ = 0;\n\tevent_source_id_stdout_ = 0;\n\tevent_source_id_stderr_ = 0;\n\tfd_stdout_ = 0;\n\tfd_stderr_ = 0;\n}\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/async_command_executor.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n\n#ifndef ASYNC_COMMAND_EXECUTOR_H\n#define ASYNC_COMMAND_EXECUTOR_H\n\n#include <glib.h>\n#include <string>\n#include <functional>\n#include <chrono>\n\n#include \"hz/process_signal.h\"  // hz::SIGNAL_*\n#include \"hz/error_holder.h\"\n\n\n\n/// Command executor.\n/// There are two ways to detect when the command exits:\n/// 1. Add a callback to signal_exited.\n/// 2. Manually poll stopped_cleanup_needed().\n/// In both cases, stopped_cleanup() must be called afterwards.\nclass AsyncCommandExecutor : public hz::ErrorHolder {\n\tpublic:\n\n\t\t/// A function that translates the exit error code into a readable string\n\t\tusing exit_status_translator_func_t = std::function<std::string(int)>;\n\n\t\t/// A function that is called whenever a process exits.\n\t\tusing exited_callback_func_t = std::function<void()>;\n\n\n\t\t/// Constructor\n\t\texplicit AsyncCommandExecutor(exited_callback_func_t exited_cb = nullptr);\n\n\t\t/// Deleted\n\t\tAsyncCommandExecutor(const AsyncCommandExecutor& other) = delete;\n\n\t\t/// Deleted\n\t\tAsyncCommandExecutor(AsyncCommandExecutor&& other) = delete;\n\n\t\t/// Deleted\n\t\tAsyncCommandExecutor& operator=(const AsyncCommandExecutor& other) = delete;\n\n\t\t/// Deleted\n\t\tAsyncCommandExecutor& operator=(AsyncCommandExecutor&& other) = delete;\n\n\n\t\t/// Destructor. Don't destroy this object unless the child has exited. It will leak stuff\n\t\t/// and possibly crash, etc. .\n\t\t~AsyncCommandExecutor() override;\n\n\n\t\t/// Set the command to execute. Call before execute().\n\t\t/// The spawning function escapes the command and arguments, so you don't have to.\n\t\tvoid set_command(std::string command_exec, std::vector<std::string> command_args);\n\n\n\t\t/// Launch the command.\n\t\tbool execute();\n\n\n\t\t/// Send SIGTERM(15) (terminate) to the child process.\n\t\t/// Use only after execute(). Using it after the command has exited has no effect.\n\t\tbool try_stop(hz::Signal sig = hz::Signal::Terminate);\n\n\n\t\t/// Send SIGKILL(9) (kill) to the child process. Same as\n\t\t/// try_stop(hz::SIGNAL_SIGKILL).\n\t\t/// Note that SIGKILL cannot be overridden in child process.\n\t\tbool try_kill();\n\n\n\t\t/// Set a timeout (since call to this function) to terminate the child process,\n\t\t/// kill it or both (use 0 to ignore the parameter).\n\t\t/// The timeouts will be unset automatically when the command exits.\n\t\t/// This has an effect only if the command is running (after execute()).\n\t\tvoid set_stop_timeouts(std::chrono::milliseconds term_timeout_msec,\n\t\t\t\tstd::chrono::milliseconds kill_timeout_msec);\n\n\t\t/// Unset the terminate / kill timeouts. This will stop the timeout counters.\n\t\t/// This has an effect only if the command is running (after execute()).\n\t\tvoid unset_stop_timeouts();\n\n\t\t/// If stopped_cleanup_needed() returned true, call this. The command\n\t\t/// should be exited by this time. Must be called before the next execute().\n\t\tvoid stopped_cleanup();\n\n\n\t\t/// Returns true if command has stopped.\n\t\t/// Call repeatedly in a waiting function, after execute().\n\t\t/// When it returns true, call stopped_cleanup().\n\t\t[[nodiscard]] bool stopped_cleanup_needed() const;\n\n\n\t\t/// Check if the process is running. Note that if this returns false, it doesn't mean that\n\t\t/// the io channels have been closed or that the data may be read safely. Poll\n\t\t/// stopped_cleanup_needed() instead.\n\t\t[[nodiscard]] bool is_running() const;\n\n\n\n\t\t/// Call this before execution. There is a race-like condition - when the command\n\t\t/// outputs something, the io channel reads it from fd to its buffer and the event\n\t\t/// source callback is called. If the command dies, the io channel callback reads\n\t\t/// the remaining data to the channel buffer.\n\t\t/// Since the event source callbacks (which read from the buffer and empty it)\n\t\t/// happen rather sporadically (from the glib loop), the buffer may not get read and\n\t\t/// emptied at all (before the command exits). This is why it's necessary to have\n\t\t/// a buffer size which potentially can hold _all_ the command output.\n\t\t/// A way to fight this is to increase event source priority (which may not help).\n\t\t/// Another way is to delay the command exit so that the event source callback\n\t\t/// catches on and reads the buffer.\n\t\t// Use 0 to ignore the parameter. Call this before execute().\n\t\tvoid set_buffer_sizes(gsize stdout_buffer_size = 0, gsize stderr_buffer_size = 0);\n\n\n\n\t\t/// If stdout_make_str_as_available_ is false, call this after stopped_cleanup(),\n\t\t/// before next execute(). If it's true, you may call this before the command has\n\t\t/// stopped, but it will decrease performance significantly.\n\t\t[[nodiscard]] std::string get_stdout_str(bool clear_existing = false);\n\n\n\t\t/// See notes for \\ref get_stdout_str().\n\t\t[[nodiscard]] std::string get_stderr_str(bool clear_existing = false);\n\n\n\t\t/// Return execution time, in seconds. Call this after execute().\n\t\t[[maybe_unused]] double get_execution_time_sec();\n\n\n\t\t/// Set exit status translator callback, disconnecting the old one.\n\t\t/// Call only before execute().\n\t\tvoid set_exit_status_translator(exit_status_translator_func_t func);\n\n\n\t\t/// Set exit notifier callback, disconnecting the old one.\n\t\t/// You can poll stopped_cleanup_needed() instead of using this function.\n\t\tvoid set_exited_callback(exited_callback_func_t func);\n\n\n\n\t\t// these are sort of private\n\n\t\t/// Channel type, for passing to callbacks\n\t\tenum class Channel {\n\t\t\tStandardOutput,\n\t\t\tStandardError\n\t\t};\n\n\n\t\t// Callbacks (Note: These are called by the real callbacks)\n\n\t\t/// Child watch handler\n\t\tstatic void on_child_watch_handler(GPid arg_pid, int waitpid_status, gpointer data);\n\n\t\t/// Channel I/O handler\n\t\tstatic gboolean on_channel_io(GIOChannel* channel, GIOCondition cond, AsyncCommandExecutor* self, Channel channel_type);\n\n\n\tprivate:\n\n\n\t\t/// Clean up the member variables and shut down the channels if needed.\n\t\tvoid cleanup_members();\n\n\n\n\t\t// default command and its args. std::strings, not ustrings.\n\t\tstd::string command_exec_;  /// Binary name to execute. NOT affected by cleanup_members().\n\t\tstd::vector<std::string> command_args_;  /// Arguments that always go with the binary. NOT affected by cleanup_members().\n\n\n\t\tbool running_ = false;  ///< If true, the child process is running now. NOT affected by cleanup_members().\n\t\tint kill_signal_sent_ = 0;  ///< If non-zero, the process has been sent this signal to terminate\n\t\tbool child_watch_handler_called_ = false;  ///< true after child_watch_handler callback, before stopped_cleanup().\n\n\t\tGPid pid_ = 0;  ///< Process ID. int in Unix, pointer in win32\n\t\tint waitpid_status_ = 0;  ///< After the command is stopped, before cleanup, this will be available (waitpid() status).\n\n\n\t\tGTimer* timer_ = nullptr;  ///< Keeps track of elapsed time since command execution. Value is not used by this class, but may be handy.\n\n\t\tguint event_source_id_term = 0;  ///< Timeout event source ID for SIGTERM.\n\t\tguint event_source_id_kill = 0;  ///< Timeout event source ID for SIGKILL.\n\n\n\t\tint fd_stdout_ = 0;  ///< stdout file descriptor\n\t\tint fd_stderr_ = 0;  ///< stderr file descriptor\n\n\t\tGIOChannel* channel_stdout_ = nullptr;  ///< stdout channel\n\t\tGIOChannel* channel_stderr_ = nullptr;  ///< stderr channel\n\n\t\tgsize channel_stdout_buffer_size_ = 100UL * 1024UL;  ///< stdout channel buffer size. NOT affected by cleanup_members(). 100K.\n\t\tgsize channel_stderr_buffer_size_ = 10UL * 1024UL;  ///< stderr channel buffer size. NOT affected by cleanup_members(). 10K.\n\n\t\tguint event_source_id_stdout_ = 0;  ///< IO watcher event source ID for stdout\n\t\tguint event_source_id_stderr_ = 0;  ///< IO watcher event source ID for stderr\n\n\t\tstd::string str_stdout_;  ///< stdout data read during execution. NOT affected by cleanup_members().\n\t\tstd::string str_stderr_;  ///< stderr data read during execution. NOT affected by cleanup_members().\n\n\n\t\t// signals\n\n\t\t// convert command exit status to message string\n\t\texit_status_translator_func_t translator_func_{ };  ///< Exit status translator function. NOT affected by cleanup_members().\n\n\t\t// \"command exited\" signal callback.\n\t\texited_callback_func_t exited_callback_{ };  ///< Exit notifier function. NOT affected by cleanup_members().\n\n};\n\n\n\n\n\n\n#endif\n"
  },
  {
    "path": "src/applib/command_executor.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include <glibmm.h>\n#include <glibmm/i18n.h>\n#include <glib.h>  // g_usleep()\n\n#include \"command_executor.h\"\n#include \"build_config.h\"\n#include \"hz/string_algo.h\"\n\n\n\ncmdex_signal_execute_finish_t& cmdex_sync_signal_execute_finish()\n{\n\t/// \"Execution finished\" signal\n\tstatic sigc::signal<void, const CommandExecutorResult&> s_cmdex_sync_signal_execute_finish;\n\treturn s_cmdex_sync_signal_execute_finish;\n}\n\n\n\nCommandExecutor::CommandExecutor(std::string command_name, std::vector<std::string> command_args)\n\t\t: CommandExecutor()\n{\n\tthis->set_command(std::move(command_name), std::move(command_args));\n}\n\n\n\nCommandExecutor::CommandExecutor()\n\t\t// Translators: {command} will be replaced by command name.\n\t\t: running_msg_(_(\"Running {command}...\"))\n{\n\tset_error_header(std::string(_(\"An error occurred while executing command:\")) + \"\\n\\n\");\n}\n\n\n\nvoid CommandExecutor::set_command(std::string command_name, std::vector<std::string> command_args)\n{\n\tcmdex_.set_command(command_name, command_args);\n\t// keep a copy locally to avoid locking on get() every time\n\tcommand_name_ = std::move(command_name);\n\tcommand_args_ = std::move(command_args);\n}\n\n\n\nstd::string CommandExecutor::get_command_name() const\n{\n\treturn command_name_;\n}\n\n\n\nstd::vector<std::string> CommandExecutor::get_command_args() const\n{\n\treturn command_args_;\n}\n\n\n\nbool CommandExecutor::execute()\n{\n\tset_error_msg(\"\");  // clear old error if present\n\n\tconst bool slot_connected = !(signal_execute_tick().slots().begin() == signal_execute_tick().slots().end());\n\n\tif (slot_connected && !signal_execute_tick().emit(TickStatus::Starting))\n\t\treturn false;\n\n\tif (!cmdex_.execute()) {  // try to execute\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"cmdex_.execute() failed.\\n\");\n\t\timport_error();  // get error from cmdex and display warnings if needed\n\n\t\t// emit this for execution loggers\n\t\tcmdex_sync_signal_execute_finish().emit(CommandExecutorResult(get_command_name(),\n\t\t\t\tget_command_args(), get_stdout_str(), get_stderr_str(), get_error_msg()));\n\n\t\tif (slot_connected)\n\t\t\tsignal_execute_tick().emit(TickStatus::Failed);\n\t\treturn false;\n\t}\n\n\tbool stop_requested = false;  // stop requested from tick function\n\tbool signals_sent = false;  // stop signals sent\n\n\twhile(!cmdex_.stopped_cleanup_needed()) {\n\n\t\tif (!stop_requested) {  // running and no stop requested yet\n\t\t\t// call the tick function with \"running\" periodically.\n\t\t\t// if it returns false, try to stop.\n\t\t\tif (slot_connected && !signal_execute_tick().emit(TickStatus::Running)) {\n\t\t\t\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"execute_tick slot returned false, trying to stop the program.\\n\");\n\t\t\t\tstop_requested = true;\n\t\t\t}\n\t\t}\n\n\n\t\tif (stop_requested && !signals_sent) {  // stop request received\n\t\t\t// send the stop request to the command\n\t\t\tif (!cmdex_.try_stop()) {  // try sigterm. this returns false if it can't be done (no permissions, zombie)\n\t\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"cmdex_.try_stop() returned false.\\n\");\n\t\t\t}\n\n\t\t\t// set sigkill timeout to 3 sec (in case sigterm fails); won't do anything if already exited.\n\t\t\tcmdex_.set_stop_timeouts(std::chrono::milliseconds(0), forced_kill_timeout_msec_);\n\t\t\t// import_error();  // don't need errors here - they will be available later anyway.\n\n\t\t\tsignals_sent = true;\n\t\t}\n\n\n\t\t// alert the tick function\n\t\tif (stop_requested && slot_connected) {\n\t\t\tsignal_execute_tick().emit(TickStatus::Stopping);  // ignore returned value here\n\t\t}\n\n\n\t\t// Without this, no event sources will be processed and the program will\n\t\t// hang waiting for the child to exit (the watch handler won't be called).\n\t\t// Note: If you have an idle callback, g_main_context_pending() will\n\t\t// always return true (until the idle callback returns false and unregisters itself).\n\t\twhile(g_main_context_pending(nullptr) != FALSE) {\n\t\t\tg_main_context_iteration(nullptr, FALSE);\n\t\t}\n\n\t\tconst gulong sleep_us = 50UL * 1000UL;  // 50 msec. avoids 100% CPU usage.\n\t\tg_usleep(sleep_us);\n\t}\n\n\t// command exited, do a cleanup.\n\tcmdex_.stopped_cleanup();\n\timport_error();  // get error from cmdex and display warnings if needed\n\n\t// emit this for execution loggers\n\tcmdex_sync_signal_execute_finish().emit(CommandExecutorResult(get_command_name(),\n\t\t\tget_command_args(), get_stdout_str(), get_stderr_str(), get_error_msg()));\n\n\tif (slot_connected)\n\t\tsignal_execute_tick().emit(TickStatus::Stopped);  // last call\n\n\treturn true;\n}\n\n\n\nvoid CommandExecutor::set_forced_kill_timeout(std::chrono::milliseconds timeout_msec)\n{\n\tforced_kill_timeout_msec_ = timeout_msec;\n}\n\n\n\nbool CommandExecutor::try_stop(hz::Signal sig)\n{\n\treturn cmdex_.try_stop(sig);\n}\n\n\n\nbool CommandExecutor::try_kill()\n{\n\treturn cmdex_.try_kill();\n}\n\n\n\nvoid CommandExecutor::set_stop_timeouts(std::chrono::milliseconds term_timeout_msec, std::chrono::milliseconds kill_timeout_msec)\n{\n\tcmdex_.set_stop_timeouts(term_timeout_msec, kill_timeout_msec);\n}\n\n\n\nvoid CommandExecutor::unset_stop_timeouts()\n{\n\tcmdex_.unset_stop_timeouts();\n}\n\n\n\nbool CommandExecutor::is_running() const\n{\n\treturn cmdex_.is_running();\n}\n\n\n\nvoid CommandExecutor::set_buffer_sizes(gsize stdout_buffer_size, gsize stderr_buffer_size)\n{\n\tcmdex_.set_buffer_sizes(stdout_buffer_size, stderr_buffer_size);\n}\n\n\n\nstd::string CommandExecutor::get_stdout_str(bool clear_existing)\n{\n\treturn cmdex_.get_stdout_str(clear_existing);\n}\n\n\n\nstd::string CommandExecutor::get_stderr_str(bool clear_existing)\n{\n\treturn cmdex_.get_stderr_str(clear_existing);\n}\n\n\n\nvoid CommandExecutor::set_exit_status_translator(AsyncCommandExecutor::exit_status_translator_func_t func)\n{\n\tcmdex_.set_exit_status_translator(std::move(func));\n}\n\n\n\nstd::string CommandExecutor::get_error_msg(bool with_header) const\n{\n\tif (with_header)\n\t\treturn error_header_ + error_msg_;\n\treturn error_msg_;\n}\n\n\n\nvoid CommandExecutor::set_running_msg(const std::string& msg)\n{\n\trunning_msg_ = msg;\n}\n\n\n\nvoid CommandExecutor::set_error_header(const std::string& msg)\n{\n\terror_header_ = msg;\n}\n\n\n\nstd::string CommandExecutor::get_error_header()\n{\n\treturn error_header_;\n}\n\n\n\nstd::string CommandExecutor::shell_quote(const std::string& str)\n{\n\tif (!BuildEnv::is_kernel_family_windows()) {\n\t\treturn Glib::shell_quote(str);\n\t}\n\t// This may be somewhat insecure, but g_spawn_command_line_async()\n\t// does not work with single quotes on Windows.\n\treturn \"\\\"\" + hz::string_replace_copy(str, \"\\\"\", \"\\\\\\\"\") + \"\\\"\";\n}\n\n\n\nsigc::signal<bool, CommandExecutor::TickStatus>& CommandExecutor::signal_execute_tick()\n{\n\treturn signal_execute_tick_;\n}\n\n\n\nvoid CommandExecutor::import_error()\n{\n\tAsyncCommandExecutor::error_list_t errors = cmdex_.get_errors();  // these are not clones\n\thz::ErrorBase* e = nullptr;\n\tif (!errors.empty())\n\t\te = errors.back()->clone();\n\tcmdex_.clear_errors();  // and clear them\n\n\tif (e) {  // if error is present, alert the user\n\t\ton_error_warn(e);\n\t}\n}\n\n\n\nvoid CommandExecutor::on_error_warn(hz::ErrorBase* e)\n{\n\tif (e) {\n\t\tset_error_msg(e->get_message());  // this message will be displayed\n\t}\n}\n\n\n\nvoid CommandExecutor::set_error_msg(const std::string& error_msg)\n{\n\terror_msg_ = error_msg;\n}\n\n\n\nstd::string CommandExecutor::get_running_msg() const\n{\n\treturn running_msg_;\n}\n\n\n\nAsyncCommandExecutor& CommandExecutor::get_async_executor()\n{\n\treturn cmdex_;\n}\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/command_executor.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef COMMAND_EXECUTOR_H\n#define COMMAND_EXECUTOR_H\n\n#include <sigc++/sigc++.h>\n#include <string>\n#include <chrono>\n#include <utility>\n\n#include \"hz/error_holder.h\"\n#include \"hz/process_signal.h\"  // hz::SIGNAL_*\n\n#include \"async_command_executor.h\"\n\n\n\n/// Information about a finished command.\nstruct CommandExecutorResult {\n\tCommandExecutorResult(std::string arg_command, std::vector<std::string> arg_parameters,\n\t\t\tstd::string arg_std_output, std::string arg_std_error, std::string arg_error_message)\n\t\t\t: command(std::move(arg_command)),\n\t\t\tparameters(std::move(arg_parameters)),\n\t\t\tstd_output(std::move(arg_std_output)),\n\t\t\tstd_error(std::move(arg_std_error)),\n\t\t\terror_message(std::move(arg_error_message))\n\t{ }\n\n\tconst std::string command;  ///< Executed command\n\tconst std::vector<std::string> parameters;  ///< Command parameters\n\tconst std::string std_output;  ///< Stdout data\n\tconst std::string std_error;  ///< Stderr data\n\tconst std::string error_message;  ///< Execution error message\n};\n\n\n\n/// cmdex_sync_signal_execute_finish() return signal.\nusing cmdex_signal_execute_finish_t = sigc::signal<void, const CommandExecutorResult&>;\n\n\n/// This signal is emitted every time execute() finishes.\ncmdex_signal_execute_finish_t& cmdex_sync_signal_execute_finish();\n\n\n\n\n\n/// Synchronous AsyncCommandExecutor (command executor) with ticking support.\nclass CommandExecutor : public sigc::trackable {\n\tpublic:\n\n\t\t/// Constructor\n\t\tCommandExecutor();\n\n\t\t/// Constructor\n\t\tCommandExecutor(std::string command_name, std::vector<std::string> command_args);\n\n\n\t\t/// Deleted\n\t\tCommandExecutor(const CommandExecutor& other) = delete;\n\n\t\t/// Deleted\n\t\tCommandExecutor(CommandExecutor&& other) = delete;\n\n\t\t/// Deleted\n\t\tCommandExecutor& operator=(CommandExecutor& other) = delete;\n\n\t\t/// Deleted\n\t\tCommandExecutor& operator=(CommandExecutor&& other) = delete;\n\n\n\t\t/// Virtual destructor\n\t\tvirtual ~CommandExecutor() = default;\n\n\n\t\t/// Set command to execute and its parameters\n\t\tvoid set_command(std::string command_name, std::vector<std::string> command_args);\n\n\n\t\t/// Get command to execute\n\t\t[[nodiscard]] std::string get_command_name() const;\n\n\n\t\t/// Get command arguments\n\t\t[[nodiscard]] std::vector<std::string> get_command_args() const;\n\n\n\t\t/// Execute the command. The function will return only after the command exits.\n\t\t/// Calls signal_execute_tick signal repeatedly while doing stuff.\n\t\t/// Note: If the command _was_ executed, but there was an error,\n\t\t/// this will return true. Check get_error_msg() for emptiness.\n\t\t/// \\c return false if failed to execute, true otherwise.\n\t\tvirtual bool execute();\n\n\n\t\t/// Set timeout (in ms) to send SIGKILL after sending SIGTERM.\n\t\t/// Used if manual stop was requested through ticker.\n\t\tvoid set_forced_kill_timeout(std::chrono::milliseconds timeout_msec);\n\n\n\t\t/// Try to stop the process. Call this from ticker slot while executing.\n\t\tbool try_stop(hz::Signal sig = hz::Signal::Terminate);\n\n\n\t\t/// Same as try_stop(hz::SIGNAL_SIGKILL).\n\t\tbool try_kill();\n\n\n\t\t/// Set a timeout (since call to this function) to terminate, kill or both (use 0 to ignore the parameter).\n\t\t/// the timeouts will be unset automatically when the command exits.\n\t\t/// Call from ticker slot while executing.\n\t\tvoid set_stop_timeouts(std::chrono::milliseconds term_timeout_msec, std::chrono::milliseconds kill_timeout_msec);\n\n\n\t\t/// Unset terminate / kill timeouts. This will stop the timeout counters.\n\t\t/// Call from ticker slot while executing.\n\t\tvoid unset_stop_timeouts();\n\n\n\t\t/// Check if the child process is running. See AsyncCommandExecutor::is_running().\n\t\t/// Call from ticker slot while executing.\n\t\tbool is_running() const;\n\n\n\t\t/// See AsyncCommandExecutor::set_buffer_sizes() for details. Call this before execute().\n\t\tvoid set_buffer_sizes(gsize stdout_buffer_size = 0, gsize stderr_buffer_size = 0);\n\n\t\t/// See AsyncCommandExecutor::get_stdout_str() for details.\n\t\t[[nodiscard]] std::string get_stdout_str(bool clear_existing = false);\n\n\t\t/// See AsyncCommandExecutor::get_stderr_str() for details.\n\t\t[[nodiscard]] std::string get_stderr_str(bool clear_existing = false);\n\n\t\t/// See AsyncCommandExecutor::set_exit_status_translator() for details.\n\t\tvoid set_exit_status_translator(AsyncCommandExecutor::exit_status_translator_func_t func);\n\n\n\t\t/// Get command execution error message. If \\c with_header\n\t\t/// is true, a header set using set_error_header() will be displayed first.\n\t\t[[nodiscard]] std::string get_error_msg(bool with_header = false) const;\n\n\n\t\t/// Set a message to display when running. \"{command}\" in \\c msg will be replaced by the command.\n\t\tvoid set_running_msg(const std::string& msg);\n\n\n\t\t/// Set error header string. See get_error_msg()\n\t\tvoid set_error_header(const std::string& msg);\n\n\n\t\t/// Get error header string. See get_error_msg()\n\t\t[[nodiscard]] std::string get_error_header();\n\n\n\t\t/// Quote a string for shell execution. This is similar to\n\t\t/// g_shell_quote(), but it uses double quotes in Windows so that\n\t\t/// the command can be actually executed by g_spawn_command_line_async().\n\t\t[[nodiscard]] static std::string shell_quote(const std::string& str);\n\n\n\t\t// ----------------- Signals\n\n\n\t\t/// Status flags for signal_execute_tick slots, along with possible return values.\n\t\tenum class TickStatus {\n\t\t\tStarting,  ///< Return status will indicate whether to proceed with the execution\n\t\t\tFailed,  ///< The execution failed\n\t\t\tRunning,  ///< Return status will indicate whether to abort the execution\n\t\t\tStopping,  ///< The child has been sent a signal\n\t\t\tStopped  ///< The child exited\n\t\t};\n\n\n\t\tsigc::signal<bool, TickStatus>& signal_execute_tick();\n\n\n\n\tprotected:\n\n\t\t/// Import the last error from cmdex_ and clear all errors there.\n\t\tvirtual void import_error();\n\n\n\t\t/// The warnings are already printed via debug_* in cmdex.\n\t\t/// Override if needed.\n\t\tvirtual void on_error_warn(hz::ErrorBase* e);\n\n\n\t\t/// Set error message\n\t\tvoid set_error_msg(const std::string& error_msg);\n\n\n\t\t/// Get \"running\" message\n\t\t[[nodiscard]] std::string get_running_msg() const;\n\n\n\t\t/// Get command executor object\n\t\t[[nodiscard]] AsyncCommandExecutor& get_async_executor();\n\n\n\tprivate:\n\n\t\tAsyncCommandExecutor cmdex_;  ///< Command executor\n\n\t\tstd::string command_name_;  ///< Command name\n\t\tstd::vector<std::string> command_args_;  ///< Command arguments\n\n\t\tstd::string running_msg_;  ///< \"Running\" message (to show in the dialogs, etc.)\n\n\t\tstd::chrono::milliseconds forced_kill_timeout_msec_ = std::chrono::seconds(3);  // 3 sec by default. Kill timeout in ms.\n\n\t\tstd::string error_msg_;  ///< Execution error message\n\t\tstd::string error_header_;  ///< The error message may have this prepended to it.\n\n\n\t\t/// This signal is emitted whenever something happens with the execution\n\t\t/// (the status is changed), and periodically while the process is running.\n\t\tsigc::signal<bool, TickStatus> signal_execute_tick_;\n\n\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/command_executor_3ware.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef COMMAND_EXECUTOR_3WARE_H\n#define COMMAND_EXECUTOR_3WARE_H\n\n#include <ranges>\n\n#include <glibmm.h>\n\n#include \"async_command_executor.h\"\n#include \"command_executor.h\"\n\n\n\n\n/// Executor for tw_cli (3ware utility)\ntemplate<class ExecutorPolicy>\nclass TwCliExecutorGeneric : public ExecutorPolicy {\n\tpublic:\n\n\t\t/// Constructor\n\t\tTwCliExecutorGeneric();\n\n\n\tprotected:\n\n\n\t\t/// Exit status translate handler\n\t\tstatic std::string translate_exit_status(int status);\n\n\n\t\t/// Import the last error from command executor and clear all errors there\n\t\tvoid import_error() override;\n\n\n\t\t/// This is called when an error occurs in command executor.\n\t\t/// Note: The warnings are already printed via debug_* in cmdex.\n\t\tvoid on_error_warn(hz::ErrorBase* e) override;\n\n};\n\n\n\n/// tw_cli executor without GUI support\nusing TwCliExecutor = TwCliExecutorGeneric<CommandExecutor>;\n\n\n/// tw_cli executor with GUI support\nusing TwCliExecutorGui = TwCliExecutorGeneric<CommandExecutorGui>;\n\n\n\n\n// ------------------------------------------- Implementation\n\n\n\ntemplate<class ExecutorPolicy>\nTwCliExecutorGeneric<ExecutorPolicy>::TwCliExecutorGeneric()\n{\n\tExecutorPolicy::get_async_executor().set_exit_status_translator(&TwCliExecutorGeneric::translate_exit_status);\n\tthis->set_error_header(std::string(_(\"An error occurred while executing tw_cli:\")) + \"\\n\\n\");\n}\n\n\n\ntemplate<class ExecutorPolicy>\nstd::string TwCliExecutorGeneric<ExecutorPolicy>::translate_exit_status([[maybe_unused]] int status)\n{\n\treturn {};\n}\n\n\n\ntemplate<class ExecutorPolicy>\nvoid TwCliExecutorGeneric<ExecutorPolicy>::import_error()\n{\n\tAsyncCommandExecutor& cmdex = this->get_async_executor();\n\tAsyncCommandExecutor::error_list_t errors = cmdex.get_errors();  // these are not clones\n\n\thz::ErrorBase* e = nullptr;\n\t// find the last relevant error.\n\tfor (const auto& error : std::ranges::reverse_view(errors)) {\n\t\t// ignore iochannel errors, they may mask the real errors\n\t\tif (error->get_type() != \"giochannel\" && error->get_type() != \"custom\") {\n\t\t\te = error->clone();\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tcmdex.clear_errors();  // and clear them\n\n\tif (e) {  // if error is present, alert the user\n\t\ton_error_warn(e);\n\t}\n}\n\n\n\ntemplate<class ExecutorPolicy>\nvoid TwCliExecutorGeneric<ExecutorPolicy>::on_error_warn(hz::ErrorBase* e)\n{\n\tif (!e)\n\t\treturn;\n\n\t// import the error only if it's relevant.\n\tconst std::string error_type = e->get_type();\n\n\t// ignore giochannel errors - higher level errors will be triggered, and they more user-friendly.\n\tif (error_type == \"giochannel\" || error_type == \"custom\") {\n\t\treturn;\n\t}\n\n\tthis->set_error_msg(e->get_message());\n}\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/command_executor_areca.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef COMMAND_EXECUTOR_ARECA_H\n#define COMMAND_EXECUTOR_ARECA_H\n\n#include <ranges>\n\n#include <glibmm.h>\n#include <glibmm/i18n.h>\n\n#include \"async_command_executor.h\"\n#include \"command_executor.h\"\n\n\n\n\n/// Executor for cli (Areca utility)\ntemplate<class ExecutorPolicy>\nclass ArecaCliExecutorGeneric : public ExecutorPolicy {\n\tpublic:\n\n\t\t/// Constructor\n\t\tArecaCliExecutorGeneric();\n\n\n\tprotected:\n\n\n\t\t/// Exit status translate handler\n\t\tstatic std::string translate_exit_status([[maybe_unused]] [[maybe_unused]] int status);\n\n\n\t\t/// Import the last error from command executor and clear all errors there\n\t\tvoid import_error() override;\n\n\n\t\t/// This is called when an error occurs in command executor.\n\t\t/// Note: The warnings are already printed via debug_* in cmdex.\n\t\tvoid on_error_warn(hz::ErrorBase* e) override;\n\n};\n\n\n\n/// tw_cli executor without GUI support\nusing ArecaCliExecutor = ArecaCliExecutorGeneric<CommandExecutor>;\n\n\n/// tw_cli executor with GUI support\nusing ArecaCliExecutorGui = ArecaCliExecutorGeneric<CommandExecutorGui>;\n\n\n\n\n// ------------------------------------------- Implementation\n\n\n\ntemplate<class ExecutorPolicy>\nArecaCliExecutorGeneric<ExecutorPolicy>::ArecaCliExecutorGeneric()\n{\n\tExecutorPolicy::get_async_executor().set_exit_status_translator(&ArecaCliExecutorGeneric::translate_exit_status);\n\tthis->set_error_header(std::string(_(\"An error occurred while executing Areca cli:\")) + \"\\n\\n\");\n}\n\n\n\ntemplate<class ExecutorPolicy>\nstd::string ArecaCliExecutorGeneric<ExecutorPolicy>::translate_exit_status([[maybe_unused]] int status)\n{\n\treturn {};\n}\n\n\n\ntemplate<class ExecutorPolicy>\nvoid ArecaCliExecutorGeneric<ExecutorPolicy>::import_error()\n{\n\tAsyncCommandExecutor& cmdex = this->get_async_executor();\n\tAsyncCommandExecutor::error_list_t errors = cmdex.get_errors();  // these are not clones\n\n\thz::ErrorBase* e = nullptr;\n\t// find the last relevant error.\n\tfor (const auto& error : std::ranges::reverse_view(errors)) {\n\t\t// ignore iochannel errors, they may mask the real errors\n\t\tif (error->get_type() != \"giochannel\" && error->get_type() != \"custom\") {\n\t\t\te = error->clone();\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tcmdex.clear_errors();  // and clear them\n\n\tif (e) {  // if error is present, alert the user\n\t\ton_error_warn(e);\n\t}\n}\n\n\n\ntemplate<class ExecutorPolicy>\nvoid ArecaCliExecutorGeneric<ExecutorPolicy>::on_error_warn(hz::ErrorBase* e)\n{\n\tif (!e)\n\t\treturn;\n\n\t// import the error only if it's relevant.\n\tconst std::string error_type = e->get_type();\n\n\t// ignore giochannel errors - higher level errors will be triggered, and they more user-friendly.\n\tif (error_type == \"giochannel\" || error_type == \"custom\") {\n\t\treturn;\n\t}\n\n\tthis->set_error_msg(e->get_message());\n}\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/command_executor_factory.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include \"hz/debug.h\"\n#include \"command_executor_factory.h\"\n#include \"smartctl_executor_gui.h\"\n#include \"command_executor_areca.h\"\n#include \"command_executor_3ware.h\"\n\n\n\nCommandExecutorFactory::CommandExecutorFactory(bool use_gui, Gtk::Window* parent)\n\t\t: use_gui_(use_gui), parent_(parent)\n{ }\n\n\n\nstd::shared_ptr<CommandExecutor> CommandExecutorFactory::create_executor(CommandExecutorFactory::ExecutorType type)\n{\n\tswitch (type) {\n\t\tcase ExecutorType::Smartctl:\n\t\t{\n\t\t\tif (use_gui_) {\n\t\t\t\tauto ex = std::make_shared<SmartctlExecutorGui>();\n\t\t\t\tex->create_running_dialog(parent_);  // dialog parent\n\t\t\t\treturn ex;\n\t\t\t}\n\t\t\treturn std::make_shared<SmartctlExecutor>();\n\t\t}\n\t\tcase ExecutorType::TwCli:\n\t\t{\n\t\t\tif (use_gui_) {\n\t\t\t\tauto ex = std::make_shared<TwCliExecutorGui>();\n\t\t\t\tex->create_running_dialog(parent_);  // dialog parent\n\t\t\t\treturn ex;\n\t\t\t}\n\t\t\treturn std::make_shared<TwCliExecutor>();\n\t\t}\n\t\tcase ExecutorType::ArecaCli:\n\t\t{\n\t\t\tif (use_gui_) {\n\t\t\t\tauto ex = std::make_shared<ArecaCliExecutorGui>();\n\t\t\t\tex->create_running_dialog(parent_);  // dialog parent\n\t\t\t\treturn ex;\n\t\t\t}\n\t\t\treturn std::make_shared<ArecaCliExecutor>();\n\t\t}\n\t}\n\n\tDBG_ASSERT(0);\n\treturn std::make_shared<CommandExecutor>();\n}\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/command_executor_factory.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef COMMAND_EXECUTOR_FACTORY_H\n#define COMMAND_EXECUTOR_FACTORY_H\n\n#include <memory>\n\n#include \"command_executor.h\"\n\n\n// Forward declaration\nnamespace Gtk {\n\tclass Window;\n}\n\n\n\n/// This class allows you to create new executors for different commands,\n/// without carrying the GUI/CLI stuff manually.\nclass CommandExecutorFactory {\n\tpublic:\n\n\t\t/// Executor type for create_executor()\n\t\tenum class ExecutorType {\n\t\t\tSmartctl,\n\t\t\tTwCli,\n\t\t\tArecaCli\n\t\t};\n\n\n\t\t/// Constructor. If \\c use_gui is true, specify \\c parent for the GUI dialogs.\n\t\texplicit CommandExecutorFactory(bool use_gui, Gtk::Window* parent = nullptr);\n\n\n\t\t/// Create a new executor instance according to \\c type and the constructor parameters.\n\t\tstd::shared_ptr<CommandExecutor> create_executor(ExecutorType type);\n\n\n\tprivate:\n\n\t\tbool use_gui_ = false;  ///< Whether to construct GUI executors or not.\n\t\tGtk::Window* parent_ = nullptr;  ///< Parent window for dialogs\n\n};\n\n\n\n/// A reference-counting pointer to CommandExecutorFactory\nusing CommandExecutorFactoryPtr = std::shared_ptr<CommandExecutorFactory>;\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/command_executor_gui.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include <glibmm.h>\n#include <glibmm/i18n.h>\n#include <gtkmm.h>  // Gtk::Main\n#include <gdkmm.h>\n\n#include <memory>\n\n#include \"hz/string_algo.h\"\n#include \"hz/fs_ns.h\"\n#include \"command_executor_gui.h\"\n#include \"hz/fs.h\"\n\n\n\nbool CommandExecutorGui::execute()\n{\n\tthis->create_running_dialog();  // create, but don't show.\n\tthis->set_running_dialog_abort_mode(false);  // reset and set the message\n\treturn CommandExecutor::execute();\n}\n\n\n#define CMDEX_DIALOG_MESSAGE_TYPE Gtk::MESSAGE_OTHER\n#define CMDEX_DIALOG_HINT_TYPE Gdk::WINDOW_TYPE_HINT_DIALOG\n\n\n\nGtk::MessageDialog* CommandExecutorGui::create_running_dialog(Gtk::Window* parent, const Glib::ustring& msg)\n{\n\tif (running_dialog_)\n\t\treturn running_dialog_.get();\n\n\tif (!msg.empty())\n\t\tset_running_msg(msg);\n\n\t// Construct the dialog so we can manipulate it before execution\n\tif (parent) {\n\t\trunning_dialog_ = std::make_unique<Gtk::MessageDialog>(*parent, \"\", false,\n\t\t\t\tCMDEX_DIALOG_MESSAGE_TYPE, Gtk::BUTTONS_CANCEL);\n\t} else {\n\t\trunning_dialog_ = std::make_unique<Gtk::MessageDialog>(\"\", false,\n\t\t\t\tCMDEX_DIALOG_MESSAGE_TYPE, Gtk::BUTTONS_CANCEL);\n\t}\n\n\trunning_dialog_->signal_response().connect(sigc::mem_fun(*this,\n\t\t\t&CommandExecutorGui::on_running_dialog_response));\n\n\trunning_dialog_->set_decorated(false);\n\trunning_dialog_->set_deletable(false);\n\trunning_dialog_->set_skip_pager_hint(true);\n\trunning_dialog_->set_skip_taskbar_hint(true);\n\trunning_dialog_->set_type_hint(Gdk::WindowTypeHint(CMDEX_DIALOG_HINT_TYPE));\n\trunning_dialog_->set_position(Gtk::WIN_POS_CENTER_ON_PARENT);\n\t// avoid running multiple programs in parallel (the dialogs can be overlap...).\n\t// this won't harm the tests - they don't involve long-running commands.\n\trunning_dialog_->set_modal(true);\n\n\treturn running_dialog_.get();\n}\n\n\n\nvoid CommandExecutorGui::show_hide_dialog(bool show)\n{\n\tif (running_dialog_) {\n\t\tif (show) {\n\t\t\trunning_dialog_timer_.start();\n\t\t\t// running_dialog_->show();\n\n\t\t} else {\n\t\t\trunning_dialog_->hide();\n\t\t\trunning_dialog_timer_.stop();\n\t\t\trunning_dialog_shown_ = false;\n\t\t}\n\t}\n}\n\n\n\n\nvoid CommandExecutorGui::update_dialog_show_timer()\n{\n\tdouble timeout = 2.;  // 2 sec for normal dialogs\n\tif (running_dialog_abort_mode_)\n\t\ttimeout = 0.4;  // 0.4 sec for aborting... dialogs\n\n\tif (!running_dialog_shown_ && running_dialog_timer_.elapsed() > timeout) {\n\n\t\t// without first making it sensitive, the \"whole label selected\" problem may occur.\n\t\trunning_dialog_->set_response_sensitive(Gtk::RESPONSE_CANCEL, true);\n\t\trunning_dialog_->show();\n\n\t\t// enable / disable the button. do this after show(), or else the label gets selected or the cursor gets visible.\n\t\trunning_dialog_->set_response_sensitive(Gtk::RESPONSE_CANCEL, !running_dialog_abort_mode_);\n\n\t\trunning_dialog_shown_ = true;\n\t}\n}\n\n\n\n\nvoid CommandExecutorGui::set_running_dialog_abort_mode(bool aborting)\n{\n\tif (!running_dialog_)\n\t\treturn;\n\n\tif (aborting && !running_dialog_abort_mode_) {\n\t\t// hide it until another timeout passes. this way, we:\n\t\t// avoid quick show/hide flickering;\n\t\t// avoid a strange problem when sensitive but clear dialog appears;\n\t\t// make it show at center of parent.\n\n\t\tshow_hide_dialog(false);\n\n\t\trunning_dialog_->set_message(std::string(\"\\n     \") + _(\"Aborting...\") + \"     \");\n\t\t// the sensitive button switching is done after show(), to avoid some visual\n\t\t// defects - cursor in label, selected label.\n\n\t\tshow_hide_dialog(true);  // this resets the timer\n\n\t\trunning_dialog_abort_mode_ = true;\n\n\n\t} else if (!aborting) {\n\t\tconst std::string msg = hz::string_replace_copy(get_running_msg(), \"{command}\",\n\t\t\t\thz::fs_path_to_string(hz::fs_path_from_string(this->get_command_name()).filename()));\n\t\trunning_dialog_->set_message(\"\\n     \" + msg + \"     \");\n\t\t// running_dialog_->set_response_sensitive(Gtk::RESPONSE_CANCEL, true);\n\n\t\trunning_dialog_abort_mode_ = false;\n\t}\n}\n\n\n\nbool CommandExecutorGui::execute_tick_func(TickStatus status)\n{\n\tif (status == TickStatus::Starting) {\n\t\tif (execution_running_)\n\t\t\treturn false;  // already running, abort the new one (?)\n\n\t\t// If quit() was called during one of the manual iterations, and execute()\n\t\t// is called in a loop, we need to prevent any real execution past that point.\n\t\tif (Gtk::Main::iteration(false) && Gtk::Main::level() > 0) {\n\t\t\treturn false;  // try to abort execution\n\t\t}\n\n\t\texecution_running_ = true;\n\t\tshould_abort_ = false;\n\n\t\t// show a dialog with \"running...\" and an Abort button\n\t\tthis->show_hide_dialog(true);\n\n\t\treturn true;  // proceed with execution\n\t}\n\n\n\tif (status == TickStatus::Failed) {\n\n\t\t// close the dialog\n\t\tthis->show_hide_dialog(false);\n\n\t\t// show a dialog with an error.\n\t\t// Handled by sync_errors_warn(), nothing else is needed here.\n\n\t\texecution_running_ = false;\n\t\treturn true;  // return value is ignored here\n\t}\n\n\n\tif (status == TickStatus::Running) {\n\n\t\twhile (Gtk::Main::events_pending()) {\n\t\t\t// Gtk::Main::iteration() returns true if Gtk::Main::quit() has been called, or if there's no Main yet.\n\t\t\t// debug_out_dump(\"app\", Gtk::Main::level() << \"\\n\");\n\t\t\tif (Gtk::Main::iteration() && Gtk::Main::level() > 0) {\n\t\t\t\tset_running_dialog_abort_mode(true);\n\t\t\t\treturn false;  // try to abort execution\n\t\t\t}\n\t\t}\n\n\t\tif (should_abort_) {\n\t\t\tshould_abort_ = false;\n\t\t\tset_running_dialog_abort_mode(true);\n\t\t\treturn false;  // try to abort execution\n\t\t}\n\n\t\t// the dialog may be shown only after some time has passed, to avoid quick show/hide.\n\t\t// this enables it.\n\t\tthis->update_dialog_show_timer();\n\n\t\treturn true;  // continue execution\n\t}\n\n\n\tif (status == TickStatus::Stopping) {\n\t\tif (Gtk::Main::iteration(false) && Gtk::Main::level() > 0) {\n\t\t\treturn false;  // we're exiting from the main loop, so return early\n\t\t}\n\n\t\t// show a dialog with \"Aborting...\"\n\t\tthis->update_dialog_show_timer();\n\t\treturn true;  // return value is ignored here\n\t}\n\n\n\tif (status == TickStatus::Stopped) {\n\t\t// close the dialog.\n\t\tthis->show_hide_dialog(false);\n\n\t\t// show error messages if needed (get_errors()).\n\t\t// Handled by sync_errors_warn(), nothing is needed here.\n\n\t\texecution_running_ = false;\n\t\treturn true;  // return value is ignored here\n\t}\n\n\n\treturn true;  // we shouldn't reach this\n}\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/command_executor_gui.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef COMMAND_EXECUTOR_GUI_H\n#define COMMAND_EXECUTOR_GUI_H\n\n#include <glibmm.h>\n#include <gtkmm.h>\n#include <memory>\n\n#include \"command_executor.h\"\n\n\n\n/// Same as CommandExecutor, but with GTK UI support.\n/// This one is noncopyable, because we can't copy the dialogs, etc.\nclass CommandExecutorGui : public CommandExecutor {\n\tpublic:\n\n\t\t/// Constructor\n\t\tCommandExecutorGui(std::string cmd, std::vector<std::string> cmdargs)\n\t\t\t\t: CommandExecutor(std::move(cmd), std::move(cmdargs))\n\t\t{\n\t\t\tsignal_execute_tick().connect(sigc::mem_fun(*this, &CommandExecutorGui::execute_tick_func));\n\t\t}\n\n\n\t\t/// Constructor\n\t\tCommandExecutorGui()\n\t\t{\n\t\t\tsignal_execute_tick().connect(sigc::mem_fun(*this, &CommandExecutorGui::execute_tick_func));\n\t\t}\n\n\n\n\t\t// Reimplemented from CommandExecutor\n\t\tbool execute() override;\n\n\n\t\t/// UI callbacks may use this to abort execution.\n\t\tvoid set_should_abort()\n\t\t{\n\t\t\tshould_abort_ = true;\n\t\t}\n\n\n\t\t/// Create a \"running\" dialog or return already existing one.\n\t\t/// The dialog will be auto-created and displayed on execute().\n\t\t/// You need the function only if you intend to modify it before execute().\n\t\tGtk::MessageDialog* create_running_dialog(Gtk::Window* parent = nullptr, const Glib::ustring& msg = Glib::ustring());\n\n\n\t\t/// Return the \"running\" dialog\n\t\t[[nodiscard]] Gtk::MessageDialog* get_running_dialog()\n\t\t{\n\t\t\treturn running_dialog_.get();\n\t\t}\n\n\n\t\t/// Show or hide the \"running\" dialog.\n\t\t/// This actually shows the dialog only after some time has passed,\n\t\t/// to avoid very quick show/hide in case the command exits very quickly.\n\t\tvoid show_hide_dialog(bool show);\n\n\n\t\t/// This function is called from ticker function in running mode\n\t\t/// to show the dialog when requested time elapses.\n\t\tvoid update_dialog_show_timer();\n\n\n\t\t/// Switch the dialog to \"aborting...\" mode\n\t\tvoid set_running_dialog_abort_mode(bool aborting);\n\n\n\tprivate:\n\n\t\t/// Dialog response callback.\n\t\tvoid on_running_dialog_response(int response_id)\n\t\t{\n\t\t\t// Try to abort if Cancel was clicked\n\t\t\tif (response_id == Gtk::RESPONSE_CANCEL)\n\t\t\t\tset_should_abort();\n\t\t}\n\n\n\t\t/// This function is attached to CommandExecutor::signal_execute_tick().\n\t\tbool execute_tick_func(TickStatus status);\n\n\n\t\tbool execution_running_ = false;  ///< If true, the execution is still in progress\n\t\tbool should_abort_ = false;  ///< GUI callbacks may set this to abort the execution\n\n\t\tstd::unique_ptr<Gtk::MessageDialog> running_dialog_;  ///< \"Running\" dialog\n\t\tbool running_dialog_shown_ = false;  ///< If true, the \"running\" dialog is visible\n\t\tbool running_dialog_abort_mode_ = false;  ///< If true, the \"running\" dialog is in \"aborting...\" mode.\n\t\tGlib::Timer running_dialog_timer_;  ///< \"Running\" dialog show timer.\n\n};\n\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/examples/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nif (NOT APP_BUILD_EXAMPLES)\n    set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL true)\nelse()\n    set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL false)\nendif()\n\n\nadd_executable(example_smartctl_executor)\ntarget_sources(example_smartctl_executor PRIVATE\n\texample_smartctl_executor.cpp\n)\ntarget_link_libraries(example_smartctl_executor PRIVATE\n\tapplib\n)\n\n\nadd_executable(example_smartctl_parser)\ntarget_sources(example_smartctl_parser PRIVATE\n\texample_smartctl_parser.cpp\n)\ntarget_link_libraries(example_smartctl_parser PRIVATE\n\tapplib\n)\n\n\nadd_executable(example_spawn)\ntarget_sources(example_spawn PRIVATE\n\texample_spawn.cpp\n)\ntarget_link_libraries(example_spawn PRIVATE\n\tapplib\n)\n\n\nadd_executable(example_storage_detector)\ntarget_sources(example_storage_detector PRIVATE\n\texample_storage_detector.cpp\n)\ntarget_link_libraries(example_storage_detector PRIVATE\n\tapplib\n)\n"
  },
  {
    "path": "src/applib/examples/example_smartctl_executor.cpp",
    "content": "/******************************************************************************\nLicense: BSD Zero Clause License\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib_examples\n/// \\weakgroup applib_examples\n/// @{\n\n// #include \"applib/smartctl_executor_gui.h\"\n#include \"applib/smartctl_executor.h\"\n\n#include <glibmm.h>\n#include <gtkmm.h>\n#include <iostream>\n\n#include \"hz/main_tools.h\"\n\n\n\n/// Main function of the test\nint main(int argc, char** argv)\n{\n\treturn hz::main_exception_wrapper([&argc, &argv]()\n\t{\n\t\tGtk::Main m(argc, argv);\n\n\t\t// NOTE: Don't use long options (e.g. --info). Use short ones (e.g. -i),\n\t\t// because long options may be unsupported on some platforms.\n\n\t//  SmartctlExecutor ex(\"smartctl\", \"-i /dev/sda\");\n\t// \tSmartctlExecutorGui ex(\"ls\", \"-l --color=no -R /dev\");\n\t// \tSmartctlExecutorGui ex(\"lsa\", \"-1 --color=no /sys/block\");\n\t// \tSmartctlExecutorGui ex(\"../../../0test_binary.sh\", \"\");\n\t\tSmartctlExecutor ex(\"../../../0test_binary.sh\", {});\n\n\t\tex.execute();\n\n\t\tconst std::string out_str = ex.get_stdout_str();\n\t// \tstd::cout << \"OUT:\\n\" << out_str << \"\\n\\n\";\n\t\tstd::cerr << \"OUT SIZE: \" << out_str.size() << \"\\n\";\n\n\t\tstd::cerr << \"STDERR:\\n\" << ex.get_stderr_str() << \"\\n\";\n\n\t\tstd::cerr << \"ERROR MSG:\\n\";\n\t\tstd::cerr << ex.get_error_msg();\n\n\t\t// execute it second time\n\t\tex.execute();\n\n\t// \tm.run();\n\t\treturn EXIT_SUCCESS;\n\t});\n}\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/examples/example_smartctl_parser.cpp",
    "content": "/******************************************************************************\nLicense: BSD Zero Clause License\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib_examples\n/// \\weakgroup applib_examples\n/// @{\n\n#undef HZ_USE_LIBDEBUG\n#define HZ_USE_LIBDEBUG 0\n// enable libdebug emulation through std::cerr\n#undef HZ_EMULATE_LIBDEBUG\n#define HZ_EMULATE_LIBDEBUG 1\n\n#include <iostream>\n#include <cstdlib>\n\n#include \"libdebug/libdebug.h\"\n#include \"hz/fs.h\"\n#include \"applib/storage_property.h\"\n#include \"applib/smartctl_text_ata_parser.h\"\n\n\n\n/// Main function of the test\nint main(int argc, char* argv[])\n{\n\tif (argc < 2) {\n\t\tstd::cout << \"Usage: \" << argv[0] << \" <file_to_parse>\\n\";\n\t\treturn EXIT_FAILURE;\n\t}\n\tdebug_register_domain(\"app\");\n\n\tconst hz::fs::path file(argv[1]);  // native encoding\n\tstd::string contents;\n\tauto ec = hz::fs_file_get_contents(file, contents, 10LLU*1024*1024);  // 10M\n\tif (ec) {\n\t\tdebug_out_error(\"app\", ec.message() << \"\\n\");\n\t\treturn EXIT_FAILURE;\n\t}\n\n\tSmartctlTextAtaParser parser;\n\tif (const auto parse_status = parser.parse(contents); !parse_status.has_value()) {\n\t\tdebug_out_error(\"app\", \"Cannot parse file contents: \" << parse_status.error().message() << \"\\n\");\n\t\treturn EXIT_FAILURE;\n\t}\n\n\tconst std::vector<StorageProperty>& props = parser.get_property_repository().get_properties();\n\tfor(const auto& prop : props) {\n\t\tdebug_out_dump(\"app\", prop << \"\\n\");\n\t}\n\n\treturn EXIT_SUCCESS;\n}\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/examples/example_spawn.cpp",
    "content": "/******************************************************************************\nLicense: BSD Zero Clause License\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib_examples\n/// \\weakgroup applib_examples\n/// @{\n\n#include <iostream>\n#include <glib.h>\n\n#include \"hz/main_tools.h\"\n\n\n\n/// Main function of the test\nint main()\n{\n\treturn hz::main_exception_wrapper([]()\n\t{\n\t\tGPid pid = {};\n\t\tint fd_stdout = 0, fd_stderr = 0;\n\n\t\tconst std::string cmd = \"iexplore\";\n\t// \tstd::vector<std::string> child_argv = Glib::shell_parse_argv(cmd);\n\n\t\tgchar* curr_dir = g_get_current_dir();\n\n\t\tint argcp = 0;  // number of args\n\t\tgchar** argvp = nullptr;  // args vector\n\t\tGError* shell_error = nullptr;\n\t\tg_shell_parse_argv(cmd.c_str(), &argcp, &argvp, &shell_error);\n\n\t\tGError* spawn_error = nullptr;\n\n\t\tg_spawn_async_with_pipes(curr_dir, argvp, nullptr,\n\t\t\t\tGSpawnFlags(G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD),\n\t\t\t\tnullptr, nullptr,  // child setup function\n\t\t\t\t&pid, nullptr, &fd_stdout, &fd_stderr, &spawn_error);\n\n\t\tg_free(curr_dir);\n\n\t\tg_strfreev(argvp);\n\n\t#ifdef _WIN32\n\t\tGIOChannel* channel_stdout = g_io_channel_win32_new_fd(fd_stdout);\n\t\tGIOChannel* channel_stderr = g_io_channel_win32_new_fd(fd_stderr);\n\t#else\n\t\tGIOChannel* channel_stdout = g_io_channel_unix_new(fd_stdout);\n\t\tGIOChannel* channel_stderr = g_io_channel_unix_new(fd_stderr);\n\t#endif\n\n\t\t// blocking writes if the pipe is full helps for small-pipe systems (see man 7 pipe).\n\t\tconst int channel_flags = ~G_IO_FLAG_NONBLOCK;\n\n\t\tif (channel_stdout) {\n\t\t\tg_io_channel_set_encoding(channel_stdout, nullptr, nullptr);  // binary IO\n\t\t\tg_io_channel_set_flags(channel_stdout, GIOFlags(g_io_channel_get_flags(channel_stdout) & channel_flags), nullptr);\n\t\t\tg_io_channel_set_buffer_size(channel_stdout, 10000);\n\t\t}\n\n\t\tif (channel_stderr) {\n\t\t\tg_io_channel_set_encoding(channel_stderr, nullptr, nullptr);  // binary IO\n\t\t\tg_io_channel_set_flags(channel_stderr, GIOFlags(g_io_channel_get_flags(channel_stderr) & channel_flags), nullptr);\n\t\t\tg_io_channel_set_buffer_size(channel_stderr, 10000);\n\t\t}\n\n\t\tg_main_loop_run(g_main_loop_new(nullptr, FALSE));\n\n\t\treturn EXIT_SUCCESS;\n\t});\n}\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/examples/example_storage_detector.cpp",
    "content": "/******************************************************************************\nLicense: BSD Zero Clause License\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib_examples\n/// \\weakgroup applib_examples\n/// @{\n\n#include <iostream>\n\n#include \"applib/storage_device.h\"\n#include \"applib/storage_detector.h\"\n#include \"applib/gsc_settings.h\"\n\n#include \"hz/main_tools.h\"\n\n\n\n/// Main function of the test\nint main()\n{\n\treturn hz::main_exception_wrapper([]()\n\t{\n\t\t// These settings contain device search paths, smartctl binary, etc.\n\t\tinit_default_settings();\n\n\t\tstd::vector<StorageDevicePtr> drives;\n\t// \tstd::vector<std::string> match_patterns;\n\t\tstd::vector<std::string> blacklist_patterns;  // additional parameters\n\n\t\tStorageDetector sd;\n\t// \tsd.add_match_patterns(match_patterns);\n\t\tsd.add_blacklist_patterns(blacklist_patterns);\n\n\t\tauto ex_factory = std::make_shared<CommandExecutorFactory>(false);\n\t\tauto fetch_error = sd.detect_and_fetch_basic_data(drives, ex_factory);\n\t\tif (!fetch_error) {\n\t\t\tstd::cerr << fetch_error.error().message() << \"\\n\";\n\n\t\t} else {\n\t\t\tfor (const auto& drive : drives) {\n\t\t\t\tstd::cerr << drive->get_device_with_type() <<\n\t\t\t\t\t\t\" (\" << StorageDeviceDetectedTypeExt::get_storable_name(drive->get_detected_type()) << \")\\n\";\n\t\t\t}\n\t\t}\n\t\treturn EXIT_SUCCESS;\n\t});\n}\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/gsc_settings.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef GSC_SETTINGS_H\n#define GSC_SETTINGS_H\n\n#include \"rconfig/rconfig.h\"\n#include \"build_config.h\"\n\n\n/// Initializes ALL default settings.\n/// \"default\" must provide every path which config may hold.\ninline void init_default_settings()\n{\n\t// Populate default\n\n\trconfig::set_default_data(\"system/config_autosave_timeout_sec\", 3*60);  // 3 minutes. 0 to disable.\n\t// rconfig::set_default_data(\"system/first_boot\", true);  // used to show the first-start warning.\n\n\tif constexpr(!BuildEnv::is_kernel_family_windows()) {\n\t\trconfig::set_default_data(\"system/smartctl_binary\", \"smartctl\");  // must be in PATH or use absolute path.\n\t\trconfig::set_default_data(\"system/tw_cli_binary\", \"tw_cli\");  // must be in PATH or use absolute path.\n\t} else {\n\t\trconfig::set_default_data(\"system/smartctl_binary\", \"smartctl-nc.exe\");  // use no-console version by default.\n\t\trconfig::set_default_data(\"system/tw_cli_binary\", \"tw_cli.exe\");\n\t\trconfig::set_default_data(\"system/areca_cli_binary\", \"cli.exe\");  // if relative, an installation path is prepended (if found).\n\t}\n\t// search for \"smartctl-nc.exe\" in smartmontools installation first.\n\trconfig::set_default_data(\"system/win32_search_smartctl_in_smartmontools\", true);\n\trconfig::set_default_data(\"system/win32_smartmontools_regpath\", \"SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\smartmontools\");  // in HKLM\n\trconfig::set_default_data(\"system/win32_smartmontools_regpath_wow\", \"SOFTWARE\\\\WOW6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\smartmontools\");  // in HKLM\n\trconfig::set_default_data(\"system/win32_smartmontools_regkey\", \"InstallLocation\");\n\trconfig::set_default_data(\"system/win32_smartmontools_smartctl_binary\", \"bin\\\\smartctl-nc.exe\");  // relative to smt install path\n\trconfig::set_default_data(\"system/win32_areca_scan_controllers\", 2);  // 0 - no, 1 - yes, 2 - auto (if areca tools are found)\n\trconfig::set_default_data(\"system/win32_areca_use_cli\", 2);  // 0 - no, 1 - yes, 2 - auto (if it's found)\n\trconfig::set_default_data(\"system/win32_areca_max_controllers\", 4);  // Maximum number of areca controllers (a safety measure). CLI supports 4.\n\trconfig::set_default_data(\"system/win32_areca_enc_max_scan_port\", 36);  // 1-128 (areca with enclosures). The last RAID port to scan if no other method is available\n\trconfig::set_default_data(\"system/win32_areca_enc_max_enclosure\", 3);  // 1-8 (areca with enclosures). The last RAID enclosure to scan if no other method is available\n\trconfig::set_default_data(\"system/win32_areca_neonc_max_scan_port\", 24);  // 1-24 (areca without enclosures). The last RAID port to scan if no other method is available\n\n\trconfig::set_default_data(\"system/smartctl_options\", \"\");  // default options on ALL commands\n\trconfig::set_default_data(\"system/smartctl_device_options\", \"\");  // dev1:val1;dev2:val2;... format, each bin2ascii-encoded.\n\trconfig::set_default_data(\"system/startup_manual_devices\", \"\");  // Auto-add devices on startup\n\n\trconfig::set_default_data(\"system/linux_udev_byid_path\", \"/dev/disk/by-id\");  // linux hard disk device links here\n\trconfig::set_default_data(\"system/linux_proc_partitions_path\", \"/proc/partitions\");  // file in linux /proc/partitions format\n\trconfig::set_default_data(\"system/linux_proc_devices_path\", \"/proc/devices\");  // file in linux /proc/devices format\n\trconfig::set_default_data(\"system/linux_proc_scsi_scsi_path\", \"/proc/scsi/scsi\");  // file in linux /proc/scsi/scsi format\n\trconfig::set_default_data(\"system/linux_proc_scsi_sg_devices_path\", \"/proc/scsi/sg/devices\");  // file in linux /proc/scsi/sg/devices format\n\trconfig::set_default_data(\"system/linux_3ware_max_scan_port\", 23);  // 0-127 (3ware). The last RAID port to scan if no other method is available\n\trconfig::set_default_data(\"system/linux_areca_enc_max_scan_port\", 36);  // 1-128 (areca with enclosures). The last RAID port to scan if no other method is available\n\trconfig::set_default_data(\"system/linux_areca_enc_max_enclosure\", 4);  // 1-8 (areca with enclosures). The last RAID enclosure to scan if no other method is available\n\trconfig::set_default_data(\"system/linux_areca_neonc_max_scan_port\", 24);  // 1-24 (areca without enclosures). The last RAID port to scan if no other method is available\n\trconfig::set_default_data(\"system/solaris_dev_path\", \"/dev/rdsk\");  // path to /dev/rdsk for solaris.\n\trconfig::set_default_data(\"system/unix_sdev_path\", \"/dev\");  // path to /dev. used by other unices\n// \trconfig::set_default_data(\"system/device_match_patterns\", \"\");  // semicolon-separated Regex patterns\n\trconfig::set_default_data(\"system/device_blacklist_patterns\", \"\");  // semicolon-separated Regex patterns\n\n\trconfig::set_default_data(\"gui/drive_data_open_save_dir\", \"\");\n\n\trconfig::set_default_data(\"gui/show_smart_capable_only\", false);  // show smart-capable drives only\n\trconfig::set_default_data(\"gui/scan_on_startup\", true);  // scan drives on startup\n\n\trconfig::set_default_data(\"gui/smartctl_output_filename_format\", \"{model}_{serial}_{date}.json\");  // when suggesting filename\n\n\trconfig::set_default_data(\"gui/icons_show_device_name\", false);  // text under icons\n\trconfig::set_default_data(\"gui/icons_show_serial_number\", false);  // text under icons\n\n\trconfig::set_default_data(\"gui/main_window/default_size_w\", 0);\n\trconfig::set_default_data(\"gui/main_window/default_size_h\", 0);\n\trconfig::set_default_data(\"gui/main_window/default_pos_x\", -1);\n\trconfig::set_default_data(\"gui/main_window/default_pos_y\", -1);\n\n\trconfig::set_default_data(\"gui/info_window/default_size_w\", 0);\n\trconfig::set_default_data(\"gui/info_window/default_size_h\", 0);\n}\n\n\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/gui_utils.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include \"gui_utils.h\"\n\n\nnamespace {\n\n\t/// Show a message dialog\n\tinline void show_dialog(const std::string& message, const std::string& sec_message,\n\t\t\tGtk::Window* parent, Gtk::MessageType type, bool sec_msg_markup)\n\t{\n\t\t// no markup, modal\n\t\tGtk::MessageDialog dialog(\"\\n\" + message + (sec_message.empty() ? \"\\n\" : \"\"),\n\t\t\t\tfalse, type, Gtk::BUTTONS_OK, true);\n\n\t\tif (!sec_message.empty())\n\t\t\tdialog.set_secondary_text(sec_message, sec_msg_markup);\n\n\t\tif (parent) {\n\t\t\tdialog.set_transient_for(*parent);\n\t\t\tdialog.set_position(Gtk::WIN_POS_CENTER_ON_PARENT);\n\t\t} else {\n\t\t\tdialog.set_position(Gtk::WIN_POS_MOUSE);\n\t\t}\n\n\t\tdialog.run();  // blocks until the dialog is closed\n\t}\n\n}\n\n\n\nvoid gui_show_error_dialog(const std::string& message, Gtk::Window* parent)\n{\n\tshow_dialog(message, \"\", parent, Gtk::MESSAGE_ERROR, false);\n}\n\nvoid gui_show_error_dialog(const std::string& message, const std::string& sec_message,\n\t\tGtk::Window* parent, bool sec_msg_markup)\n{\n\tshow_dialog(message, sec_message, parent, Gtk::MESSAGE_ERROR, sec_msg_markup);\n}\n\n\n\nvoid gui_show_warn_dialog(const std::string& message, Gtk::Window* parent)\n{\n\tshow_dialog(message, \"\", parent, Gtk::MESSAGE_WARNING, false);\n}\n\nvoid gui_show_warn_dialog(const std::string& message, const std::string& sec_message,\n\t\tGtk::Window* parent, bool sec_msg_markup)\n{\n\tshow_dialog(message, sec_message, parent, Gtk::MESSAGE_WARNING, sec_msg_markup);\n}\n\n\n\nvoid gui_show_info_dialog(const std::string& message, Gtk::Window* parent)\n{\n\tshow_dialog(message, \"\", parent, Gtk::MESSAGE_INFO, false);\n}\n\nvoid gui_show_info_dialog(const std::string& message, const std::string& sec_message,\n\t\tGtk::Window* parent, bool sec_msg_markup)\n{\n\tshow_dialog(message, sec_message, parent, Gtk::MESSAGE_INFO, sec_msg_markup);\n}\n\n\n\n// Returns false if Cancel was clicked. Puts the user-entered string into result otherwise.\nbool gui_show_text_entry_dialog(const std::string& title, const std::string& message,\n\t\tstd::string& result, const std::string& default_str, Gtk::Window* parent)\n{\n\treturn gui_show_text_entry_dialog(title, message, \"\", result, default_str, parent, false);\n}\n\n\nbool gui_show_text_entry_dialog(const std::string& title, const std::string& message, const std::string& sec_message,\n\t\tstd::string& result, const std::string& default_str, Gtk::Window* parent, bool sec_msg_markup)\n{\n\tint response = 0;\n\tstd::string input_str;\n\n\t{  // the dialog hides at the end of scope\n\t\tGtk::Dialog dialog(title, true);  // modal\n\n\t\tdialog.set_resizable(false);\n\t\tdialog.set_skip_taskbar_hint(true);\n\t\tdialog.set_border_width(5);\n\n\t\tif (parent) {\n\t\t\tdialog.set_transient_for(*parent);\n\t\t\tdialog.set_position(Gtk::WIN_POS_CENTER_ON_PARENT);\n\t\t} else {\n\t\t\tdialog.set_position(Gtk::WIN_POS_MOUSE);\n\t\t}\n\n\n\t\tGtk::Label main_label;\n\t\tmain_label.set_markup(\"<big><b>\" + Glib::Markup::escape_text(message)\n\t\t\t\t+ (sec_message.empty() ? \"\\n\" : \"\") + \"</b></big>\");\n\t\tmain_label.set_line_wrap(true);\n\t\tmain_label.set_selectable(true);\n\t\tmain_label.set_alignment(Gtk::ALIGN_START);\n\n\t\tGtk::Label sec_label;\n\t\tif (sec_msg_markup) {\n\t\t\tsec_label.set_markup(sec_message);\n\t\t} else {\n\t\t\tsec_label.set_text(sec_message);\n\t\t}\n\t\tsec_label.set_line_wrap(true);\n\t\tsec_label.set_selectable(true);\n\t\tsec_label.set_alignment(Gtk::ALIGN_START);\n\n\t\tGtk::Entry input_entry;\n\t\tinput_entry.set_activates_default(true);\n\t\tinput_entry.set_text(default_str);\n\n\t\tGtk::Box vbox;\n\t\tvbox.set_spacing(12);\n\t\tvbox.pack_start(main_label, false, false, 0);\n\t\tvbox.pack_start(sec_label, true, true, 0);\n\t\tvbox.pack_start(input_entry, true, true, 0);\n\t\tvbox.show_all();\n\n\t\tdialog.get_action_area()->set_border_width(5);\n\t\tdialog.get_action_area()->set_spacing(6);\n\n\t\tdialog.get_vbox()->set_spacing(14);  // as in MessageDialog\n\t\tdialog.get_vbox()->pack_start(vbox, false, false, 0);\n\n\n\t\tdialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);\n\n\t\tGtk::Button ok_button(Gtk::Stock::OK);\n\t\tok_button.set_can_default(true);\n\t\tok_button.show_all();\n\t\tdialog.add_action_widget(ok_button, Gtk::RESPONSE_OK);\n\t\tok_button.grab_default();  // make it the default widget\n\n\t\tresponse = dialog.run();  // blocks until the dialog is closed\n\n\t\tinput_str = input_entry.get_text();\n\t}\n\n\tif (response == Gtk::RESPONSE_OK) {\n\t\tresult = input_str;\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n\n\nbool gui_is_dark_theme_active()\n{\n\t// Try to get the GTK settings to check for dark theme preference.\n\t// If GTK is not available or not initialized, get_default() will return null.\n\tconst Glib::RefPtr<Gtk::Settings> settings = Gtk::Settings::get_default();\n\tif (settings) {\n\t\t// Check if the application prefers dark theme\n\t\tif (settings->property_gtk_application_prefer_dark_theme().get_value()) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Check theme name for common dark theme identifiers\n\t\tGlib::ustring theme_name;\n\t\tsettings->get_property(\"gtk-theme-name\", theme_name);\n\t\tconst std::string theme_str = theme_name.lowercase();\n\t\tif (theme_str.find(\"dark\") != std::string::npos ||\n\t\t\ttheme_str.find(\"black\") != std::string::npos) {\n\t\t\treturn true;\n\t\t}\n\t}\n\n\treturn false;\n}\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/gui_utils.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef GUI_UTILS_H\n#define GUI_UTILS_H\n\n#include <glibmm.h>\n#include <gtkmm.h>\n#include <string>\n\n\n\n// These functions won't return until the dialogs are closed.\n// Messages must not contain any markup.\n\n\n/// Show an error dialog\nvoid gui_show_error_dialog(const std::string& message, Gtk::Window* parent = nullptr);\n\n/// Show an error dialog with a (possibly markupped) secondary message\nvoid gui_show_error_dialog(const std::string& message, const std::string& sec_message,\n\t\tGtk::Window* parent = nullptr, bool sec_msg_markup = false);\n\n\n/// Show a warning dialog\nvoid gui_show_warn_dialog(const std::string& message, Gtk::Window* parent = nullptr);\n\n/// Show a warning dialog with a (possibly markupped) secondary message\nvoid gui_show_warn_dialog(const std::string& message, const std::string& sec_message,\n\t\tGtk::Window* parent = nullptr, bool sec_msg_markup = false);\n\n\n/// Show an informational dialog\nvoid gui_show_info_dialog(const std::string& message, Gtk::Window* parent = nullptr);\n\n/// Show an informational dialog with a (possibly markupped) secondary message\nvoid gui_show_info_dialog(const std::string& message, const std::string& sec_message,\n\t\tGtk::Window* parent = nullptr, bool sec_msg_markup = false);\n\n\n/// Show a text entry dialog. \\c result is filled with the user-entered string on success.\n/// \\return false if Cancel was clicked.\nbool gui_show_text_entry_dialog(const std::string& title, const std::string& message,\n\t\tstd::string& result, const std::string& default_str, Gtk::Window* parent = nullptr);\n\n/// Show a text entry dialog with a (possibly markupped) secondary message.\n/// \\c result is filled with the user-entered string on success.\n/// \\return false if Cancel was clicked.\nbool gui_show_text_entry_dialog(const std::string& title, const std::string& message, const std::string& sec_message,\n\t\tstd::string& result, const std::string& default_str, Gtk::Window* parent = nullptr, bool sec_msg_markup = false);\n\n\n/// Check if a dark GTK theme is currently active\nbool gui_is_dark_theme_active();\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/selftest.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include \"hz/error_container.h\"\n#include \"command_executor.h\"\n#include <glibmm.h>\n#include <algorithm>  // std::max, std::min\n#include <cmath>  // std::floor\n#include <chrono>\n#include <cstdint>\n#include <memory>\n#include <optional>\n#include <unordered_map>\n#include <string>\n#include <vector>\n\n#include \"fmt/format.h\"\n#include \"smartctl_parser_types.h\"\n#include \"smartctl_parser.h\"\n#include \"storage_device_detected_type.h\"\n#include \"storage_property.h\"\n#include \"smartctl_text_ata_parser.h\"\n#include \"selftest.h\"\n#include \"storage_property_descr.h\"\n#include \"smartctl_version_parser.h\"\n#include \"app_regex.h\"\n\n\n\nSelfTestStatusSeverity get_self_test_status_severity(SelfTestStatus s)\n{\n\tstatic const std::unordered_map<SelfTestStatus, SelfTestStatusSeverity> m {\n\t\t\t{SelfTestStatus::Unknown,                SelfTestStatusSeverity::None},\n\t\t\t{SelfTestStatus::CompletedNoError,       SelfTestStatusSeverity::None},\n\t\t\t{SelfTestStatus::ManuallyAborted,          SelfTestStatusSeverity::Warning},\n\t\t\t{SelfTestStatus::Interrupted,            SelfTestStatusSeverity::Warning},\n\t\t\t{SelfTestStatus::CompletedWithError,         SelfTestStatusSeverity::Error},\n\t\t\t{SelfTestStatus::InProgress,             SelfTestStatusSeverity::None},\n\t\t\t{SelfTestStatus::Reserved,               SelfTestStatusSeverity::None},\n\t};\n\tif (auto iter = m.find(s); iter != m.end()) {\n\t\treturn iter->second;\n\t}\n\treturn SelfTestStatusSeverity::None;\n}\n\n\n\nstd::string SelfTest::get_test_displayable_name(SelfTest::TestType type)\n{\n\tstatic const std::unordered_map<TestType, std::string> m {\n//\t\t\t{TestType::ImmediateOffline, _(\"Immediate Offline Test\")},\n\t\t\t{TestType::ShortTest,        _(\"Short Self-Test\")},\n\t\t\t{TestType::LongTest,         _(\"Extended Self-Test\")},\n\t\t\t{TestType::Conveyance,       _(\"Conveyance Self-Test\")},\n\t};\n\tif (auto iter = m.find(type); iter != m.end()) {\n\t\treturn iter->second;\n\t}\n\treturn \"[internal_error]\";\n}\n\n\n\nbool SelfTest::is_active() const\n{\n\treturn (status_ == SelfTestStatus::InProgress);\n}\n\n\n\nint8_t SelfTest::get_remaining_percent() const\n{\n\treturn remaining_percent_;\n}\n\n\n\n// Returns estimated time of completion for the test. returns -1 if n/a or unknown. 0 is a valid value.\nstd::chrono::seconds SelfTest::get_remaining_seconds() const\n{\n\tusing namespace std::literals;\n\n\tconst std::chrono::seconds total = get_min_duration_seconds();\n\tif (total <= 0s)\n\t\treturn -1s;  // unknown\n\n\tconst double gran = (double(total.count()) / 9.);  // seconds per 10%\n\t// since remaining_percent_ may be manually set to 100, we limit from the above.\n\tconst double rem_seconds_at_last_change = std::min(double(total.count()), gran * remaining_percent_ / 10.);\n\tconst double rem = rem_seconds_at_last_change - timer_.elapsed();\n\treturn std::chrono::seconds(std::max(int64_t(0), (int64_t)std::round(rem)));  // don't return negative values.\n}\n\n\n\nSelfTest::TestType SelfTest::get_test_type() const\n{\n\treturn type_;\n}\n\n\n\nSelfTestStatus SelfTest::get_status() const\n{\n\treturn status_;\n}\n\n\n\nstd::chrono::seconds SelfTest::get_poll_in_seconds() const\n{\n\treturn poll_in_seconds_;\n}\n\n\n\n// a drive reports a constant \"test duration during idle\" capability.\nstd::chrono::seconds SelfTest::get_min_duration_seconds() const\n{\n\tusing namespace std::literals;\n\n\tif (!drive_)\n\t\treturn -1s;  // n/a\n\n\tif (total_duration_ != -1s)  // cache\n\t\treturn total_duration_;\n\n\tif (drive_->get_detected_type() == StorageDeviceDetectedType::Nvme) {\n\t\treturn -1s;  // NVMe doesn't report this.\n\t}\n\n\t// ATA\n\tstd::string prop_name;\n\tswitch(type_) {\n//\t\tcase TestType::ImmediateOffline: prop_name = \"ata_smart_data/offline_data_collection/completion_seconds\"; break;\n\t\tcase TestType::ShortTest: prop_name = \"ata_smart_data/self_test/polling_minutes/short\"; break;\n\t\tcase TestType::LongTest: prop_name = \"ata_smart_data/self_test/polling_minutes/extended\"; break;\n\t\tcase TestType::Conveyance: prop_name = \"ata_smart_data/self_test/polling_minutes/conveyance\"; break;\n\t}\n\n\tconst StorageProperty p = drive_->get_property_repository().lookup_property(prop_name,\n\t\t\tStoragePropertySection::Capabilities);\n\n\t// p stores it as uint64_t\n\treturn (total_duration_ = (p.empty() ? 0s : p.get_value<std::chrono::seconds>()));\n}\n\n\n\nbool SelfTest::is_supported() const\n{\n\tif (!drive_)\n\t\treturn false;\n\n\tif (drive_->get_detected_type() == StorageDeviceDetectedType::Nvme) {\n\t\tswitch (type_) {\n//\t\t\tcase TestType::ImmediateOffline:\n\t\t\tcase TestType::Conveyance:\n\t\t\t\treturn false;  // not supported by nvme\n\t\t\tcase TestType::ShortTest:\n\t\t\tcase TestType::LongTest:\n\t\t\t{\n\t\t\t\t// Both short and long should be supported if the drive has a self-test log\n\t\t\t\tconst StorageProperty p = drive_->get_property_repository().lookup_property(\"nvme_self_test_log/_exists\");\n\t\t\t\treturn (!p.empty() && p.get_value<bool>());\n\t\t\t}\n\t\t}\n\n\t} else if (drive_->get_detected_type() == StorageDeviceDetectedType::AtaAny\n\t\t\t|| drive_->get_detected_type() == StorageDeviceDetectedType::AtaHdd\n\t\t\t|| drive_->get_detected_type() == StorageDeviceDetectedType::AtaSsd) {\n\n\t\t// Find appropriate capability\n\t\tstd::string prop_name;\n\t\tswitch(type_) {\n//\t\t\tcase TestType::ImmediateOffline:\n\t\t\t\t// prop_name = \"ata_smart_data/capabilities/exec_offline_immediate_supported\";\n\t\t\t\t// break;\n//\t\t\t\treturn false;  // disable this for now - it's unsupported by this application.\n\t\t\tcase TestType::ShortTest:\n\t\t\tcase TestType::LongTest:  // same for short and long\n\t\t\t\tprop_name = \"ata_smart_data/capabilities/self_tests_supported\";\n\t\t\t\tbreak;\n\t\t\tcase TestType::Conveyance:\n\t\t\t\tprop_name = \"ata_smart_data/capabilities/conveyance_self_test_supported\";\n\t\t\t\tbreak;\n\t\t}\n\n\t\tconst StorageProperty p = drive_->get_property_repository().lookup_property(prop_name);\n\t\treturn (!p.empty() && p.get_value<bool>());\n\t}\n\n\treturn false;\n}\n\n\n\n\n// start the test\nhz::ExpectedVoid<SelfTestExecutionError> SelfTest::start(const std::shared_ptr<CommandExecutor>& smartctl_ex)\n{\n\tif (!drive_) {\n\t\treturn hz::Unexpected(SelfTestExecutionError::InternalError, _(\"Internal Error: Drive must not be NULL.\"));\n\t}\n\tif (drive_->get_test_is_active()) {\n\t\treturn hz::Unexpected(SelfTestExecutionError::AlreadyRunning, _(\"A test is already running on this drive.\"));\n\t}\n\tif (!this->is_supported()) {\n\t\t// Translators: {} is a test name - Short test, etc.\n\t\tstd::string type_name = get_test_displayable_name(type_);\n\t\treturn hz::Unexpected(SelfTestExecutionError::UnsupportedTest,\n\t\t\t\tfmt::format(fmt::runtime(_(\"{} is unsupported by this drive.\")), type_name));\n\t}\n\n\tstd::string test_param;\n\tswitch(type_) {\n//\t\tcase TestType::ImmediateOffline: test_param = \"offline\"; break;\n\t\tcase TestType::ShortTest: test_param = \"short\"; break;\n\t\tcase TestType::LongTest: test_param = \"long\"; break;\n\t\tcase TestType::Conveyance: test_param = \"conveyance\"; break;\n\t\t// no default - this way we get warned by compiler if we're not listing all of them.\n\t}\n\tif (test_param.empty()) {\n\t\treturn hz::Unexpected(SelfTestExecutionError::InvalidTestType, _(\"Invalid test specified.\"));\n\t}\n\n\tstd::string output;\n\tauto execute_status = drive_->execute_device_smartctl({\"--test=\" + test_param}, smartctl_ex, output);\n\n\tif (!execute_status.has_value()) {\n\t\tstd::string message = execute_status.error().message();\n\t\treturn hz::Unexpected(SelfTestExecutionError::CommandFailed,\n\t\t\t\tfmt::format(fmt::runtime(_(\"Sending command to drive failed: {}\")), message));\n\t}\n\n\tconst bool ata_test_started = app_regex_partial_match(R\"(/^Drive command .* successful\\.\\nTesting has begun\\.$/mi)\", output);\n\tconst bool nvme_test_started = app_regex_partial_match(R\"(/^Self-test has begun$/mi)\", output);\n\tconst bool nvme_test_running = app_regex_partial_match(R\"(/^Can't start self-test without aborting current test/mi)\", output);\n\n\tif (!ata_test_started && !nvme_test_started && !nvme_test_running) {\n\t\treturn hz::Unexpected(SelfTestExecutionError::CommandUnknownError, _(\"Sending command to drive failed.\"));\n\t}\n\n\t// update our members\n// \terror_message = this->update(smartctl_ex);\n// \tif (!error_message.empty())  // update can error out too.\n// \t\treturn error_message;\n\n\t// Don't update here - the logs may not be updated this fast.\n\t// Better to wait several seconds and then call it manually.\n\n\t// Set up everything so that the caller won't have to.\n\n\tstatus_ = SelfTestStatus::InProgress;\n\n\tremaining_percent_ = 100;\n\t// set to 90 to avoid the 100->90 timer reset. this way we won't be looking at\n\t// \"remaining 60sec\" on 60sec test twice (5 seconds apart). Since the test starts\n\t// at 90% anyway, it's a good thing.\n\tlast_seen_percent_ = 90;\n\tpoll_in_seconds_ = std::chrono::seconds(5);  // first update() in 5 seconds\n\ttimer_.start();\n\n\tdrive_->set_test_is_active(true);\n\n\treturn {};  // everything ok\n}\n\n\n\n// abort test.\nhz::ExpectedVoid<SelfTestExecutionError> SelfTest::force_stop(const std::shared_ptr<CommandExecutor>& smartctl_ex)\n{\n\tif (!drive_) {\n\t\treturn hz::Unexpected(SelfTestExecutionError::InternalError, _(\"Internal Error: Drive must not be NULL.\"));\n\t}\n\tif (!drive_->get_test_is_active()) {\n\t\treturn hz::Unexpected(SelfTestExecutionError::NotRunning, _(\"No test is currently running on this drive.\"));\n\t}\n\n\t// To abort immediate offline test, the device MUST have\n\t// \"Abort Offline collection upon new command\" capability,\n\t// any command (e.g. \"--abort\") will abort it. If it has \"Suspend Offline...\",\n\t// there's no way to abort such test.\n//\tif (type_ == TestType::ImmediateOffline) {\n//\t\tconst StorageProperty p = drive_->get_property_repository().lookup_property(\n//\t\t\t\t\"ata_smart_data/capabilities/offline_is_aborted_upon_new_cmd\");\n//\t\tif (!p.empty() && p.get_value<bool>()) {  // if empty, give a chance to abort anyway.\n//\t\t\treturn hz::Unexpected(SelfTestError::StopUnsupported, _(\"Aborting this test is unsupported by the drive.\"));\n//\t\t}\n//\t\t// else, proceed as any other test\n//\t}\n\n\t// To abort non-captive short, long and conveyance tests, use \"--abort\".\n\tstd::string output;\n\tauto execute_status = drive_->execute_device_smartctl({\"--abort\"}, smartctl_ex, output);\n\n\tif (!execute_status) {\n\t\tstd::string message = execute_status.error().message();\n\t\treturn hz::Unexpected(SelfTestExecutionError::CommandFailed,\n\t\t\t\tfmt::format(fmt::runtime(_(\"Sending command to drive failed: {}\")), message));\n\t}\n\n\t// this command prints success even if no test was running.\n\tconst bool ata_aborted = app_regex_partial_match(\"/^Self-testing aborted!$/mi\", output);\n\tconst bool nvme_aborted = app_regex_partial_match(\"/^Self-test aborted!$/mi\", output);\n\n\tif (!ata_aborted && !nvme_aborted) {\n\t\treturn hz::Unexpected(SelfTestExecutionError::CommandUnknownError, _(\"Sending command to drive failed.\"));\n\t}\n\n\t// update our members\n\tauto update_status = this->update(smartctl_ex);\n\n\t// the thing is, update() may fail to actually update the statuses, so\n\t// do it manually.\n\tif (status_ == SelfTestStatus::InProgress) {  // update() couldn't do its job\n\t\tstatus_ = SelfTestStatus::ManuallyAborted;\n\t\tremaining_percent_ = -1;\n\t\tlast_seen_percent_ = -1;\n\t\tpoll_in_seconds_ = std::chrono::seconds(-1);\n\t\ttimer_.stop();\n\t\tdrive_->set_test_is_active(false);\n\t}\n\n\tif (!update_status) {  // update can error out too.\n\t\tstd::string message = update_status.error().message();\n\t\treturn hz::Unexpected(SelfTestExecutionError::UpdateError,\n\t\t\t\tfmt::format(fmt::runtime(_(\"Error fetching test progress information: {}\")), message));\n\t}\n\n\treturn {};  // everything ok\n}\n\n\n\n// update status variables. note: the returned error is an error in logic,\n// not a hw defect error.\nhz::ExpectedVoid<SelfTestExecutionError> SelfTest::update(const std::shared_ptr<CommandExecutor>& smartctl_ex)\n{\n\tusing namespace std::literals;\n\n\tif (!drive_) {\n\t\treturn hz::Unexpected(SelfTestExecutionError::InternalError, _(\"Internal Error: Drive must not be NULL.\"));\n\t}\n\n\tSmartctlParserType parser_type = SmartctlParserType::Ata;\n\tif (drive_->get_detected_type() == StorageDeviceDetectedType::Nvme) {\n\t\tparser_type = SmartctlParserType::Nvme;\n\t}\n\tauto parser_format = SmartctlVersionParser::get_default_format(parser_type);\n\n\t// ATA shows status in capabilities; NVMe shows it in self-test log.\n\tstd::vector<std::string> command_options = {\"--capabilities\", \"--log=selftest\"};\n\tif (parser_format == SmartctlOutputFormat::Json) {\n\t\t// --json flags: o means include original output (just in case).\n\t\tcommand_options.emplace_back(\"--json=o\");\n\t}\n\n\tstd::string output;\n\tauto execute_status = drive_->execute_device_smartctl(command_options, smartctl_ex, output);\n\n\tif (!execute_status) {\n\t\tstd::string message = execute_status.error().message();\n\t\treturn hz::Unexpected(SelfTestExecutionError::CommandFailed,\n\t\t\t\tfmt::format(fmt::runtime(_(\"Sending command to drive failed: {}\")), message));\n\t}\n\n\n\tstd::shared_ptr<SmartctlParser> parser = SmartctlParser::create(parser_type, parser_format);\n\tDBG_ASSERT_RETURN(parser, hz::Unexpected(SelfTestExecutionError::ParseError, _(\"Cannot create parser.\")));\n\n\tauto parse_status = parser->parse(output);\n\tif (!parse_status) {\n\t\treturn hz::Unexpected(SelfTestExecutionError::ParseError,\n\t\t\t\tfmt::format(fmt::runtime(_(\"Cannot parse smartctl output: {}\")), parse_status.error().message()));\n\t}\n\tconst auto property_repo = StoragePropertyProcessor::process_properties(\n\t\t\tparser->get_property_repository(), drive_->get_detected_type());\n\n\n\tif (drive_->get_detected_type() == StorageDeviceDetectedType::Nvme) {\n\n\t\tconst StorageProperty current_operation = property_repo.lookup_property(\"nvme_self_test_log/current_self_test_operation/value/_decoded\");\n\n\t\t// If no test is active, the property may be absent, or set to None.\n\t\tif (!current_operation.empty()\n\t\t\t\t&& current_operation.get_value<std::string>() != NvmeSelfTestCurrentOperationTypeExt::get_storable_name(NvmeSelfTestCurrentOperationType::None)) {\n\t\t\tstatus_ = SelfTestStatus::InProgress;\n\n\t\t\tauto remaining_percent = property_repo.lookup_property(\"nvme_self_test_log/current_self_test_completion_percent\");\n\t\t\tif (!remaining_percent.empty()) {\n\t\t\t\tremaining_percent_ = static_cast<int8_t>(100 - remaining_percent.get_value<int64_t>());\n\t\t\t}\n\t\t} else {  // no test is active\n\t\t\t// The first self-test table entry is the latest.\n\t\t\tstd::optional<NvmeStorageSelftestEntry> entry;\n\t\t\tfor (const auto& e : property_repo.get_properties()) {\n\t\t\t\tif (e.is_value_type<NvmeStorageSelftestEntry>() && e.get_value<NvmeStorageSelftestEntry>().test_num == 1) {\n\t\t\t\t\tentry = e.get_value<NvmeStorageSelftestEntry>();\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!entry) {\n\t\t\t\treturn hz::Unexpected(SelfTestExecutionError::ReportUnsupported, _(\"The drive doesn't report the test status.\"));\n\t\t\t}\n\n\t\t\tswitch (entry->result) {\n\t\t\t\tcase NvmeSelfTestResultType::Unknown:\n\t\t\t\t\tstatus_ = SelfTestStatus::Unknown;\n\t\t\t\t\tbreak;\n\t\t\t\tcase NvmeSelfTestResultType::CompletedNoError:\n\t\t\t\t\tstatus_ = SelfTestStatus::CompletedNoError;\n\t\t\t\t\tbreak;\n\t\t\t\tcase NvmeSelfTestResultType::AbortedSelfTestCommand:\n\t\t\t\t\tstatus_ = SelfTestStatus::ManuallyAborted;\n\t\t\t\t\tbreak;\n\t\t\t\tcase NvmeSelfTestResultType::AbortedControllerReset:\n\t\t\t\tcase NvmeSelfTestResultType::AbortedNamespaceRemoved:\n\t\t\t\tcase NvmeSelfTestResultType::AbortedFormatNvmCommand:\n\t\t\t\tcase NvmeSelfTestResultType::AbortedUnknownReason:\n\t\t\t\tcase NvmeSelfTestResultType::AbortedSanitizeOperation:\n\t\t\t\t\tstatus_ = SelfTestStatus::Interrupted;\n\t\t\t\t\tbreak;\n\t\t\t\tcase NvmeSelfTestResultType::FatalOrUnknownTestError:\n\t\t\t\tcase NvmeSelfTestResultType::CompletedUnknownFailedSegment:\n\t\t\t\tcase NvmeSelfTestResultType::CompletedFailedSegments:\n\t\t\t\t\tstatus_ = SelfTestStatus::CompletedWithError;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t} else {\n\t\t// ATA:\n\t\t// Note: Since the self-test log is sometimes late\n\t\t// and in undetermined order (sorting by hours is too rough),\n\t\t// we use the \"self-test status\" capability.\n\t\tStorageProperty p;\n\t\tfor (const auto& e : property_repo.get_properties()) {\n\t\t\tif (!e.is_value_type<AtaStorageSelftestEntry>() || e.get_value<AtaStorageSelftestEntry>().test_num != 0\n\t\t\t\t\t|| e.generic_name != \"ata_smart_data/self_test/status/_merged\")\n\t\t\t\tcontinue;\n\t\t\tp = e;\n\t\t}\n\t\tif (p.empty()) {\n\t\t\treturn hz::Unexpected(SelfTestExecutionError::ReportUnsupported, _(\"The drive doesn't report the test status.\"));\n\t\t}\n\n\t\tswitch (p.get_value<AtaStorageSelftestEntry>().status) {\n\t\t\tcase AtaStorageSelftestEntry::Status::InProgress:\n\t\t\t\tstatus_ = SelfTestStatus::InProgress;\n\t\t\t\tbreak;\n\t\t\tcase AtaStorageSelftestEntry::Status::Unknown:\n\t\t\t\tstatus_ = SelfTestStatus::Unknown;\n\t\t\t\tbreak;\n\t\t\tcase AtaStorageSelftestEntry::Status::Reserved:\n\t\t\t\tstatus_ = SelfTestStatus::Reserved;\n\t\t\t\tbreak;\n\t\t\tcase AtaStorageSelftestEntry::Status::CompletedNoError:\n\t\t\t\tstatus_ = SelfTestStatus::CompletedNoError;\n\t\t\t\tbreak;\n\t\t\tcase AtaStorageSelftestEntry::Status::AbortedByHost:\n\t\t\t\tstatus_ = SelfTestStatus::ManuallyAborted;\n\t\t\t\tbreak;\n\t\t\tcase AtaStorageSelftestEntry::Status::Interrupted:\n\t\t\t\tstatus_ = SelfTestStatus::Interrupted;\n\t\t\t\tbreak;\n\t\t\tcase AtaStorageSelftestEntry::Status::FatalOrUnknown:\n\t\t\tcase AtaStorageSelftestEntry::Status::ComplUnknownFailure:\n\t\t\tcase AtaStorageSelftestEntry::Status::ComplElectricalFailure:\n\t\t\tcase AtaStorageSelftestEntry::Status::ComplServoFailure:\n\t\t\tcase AtaStorageSelftestEntry::Status::ComplReadFailure:\n\t\t\tcase AtaStorageSelftestEntry::Status::ComplHandlingDamage:\n\t\t\t\tstatus_ = SelfTestStatus::CompletedWithError;\n\t\t\t\tbreak;\n\t\t}\n\n\t\tif (status_ == SelfTestStatus::InProgress) {\n\t\t\tremaining_percent_ = p.get_value<AtaStorageSelftestEntry>().remaining_percent;\n\t\t}\n\t}\n\n\t// Note that the test needs 90% to complete, not 100. It starts at 90%\n\t// and reaches 00% on completion. That's 9 pieces.\n\tif (status_ == SelfTestStatus::InProgress) {\n\t\tif (remaining_percent_ != last_seen_percent_) {\n\t\t\tlast_seen_percent_ = remaining_percent_;\n\t\t\ttimer_.start();  // restart the timer\n\t\t}\n\n\t\tconst std::chrono::seconds total = get_min_duration_seconds();\n\n\t\tif (total <= 0s) {  // unknown\n\t\t\tpoll_in_seconds_ = 15s;  // just a guess, quick enough for nvme\n\n\t\t} else {\n\t\t\t// seconds per 10%. use double, because e.g. 60sec test gives silly values with int.\n\t\t\tconst double gran = (double(total.count()) / 9.);\n\n\t\t\t// Add 1/10 for disk load delays, etc. Limit to 15sec, in case of very quick tests.\n\t\t\tpoll_in_seconds_ = std::chrono::seconds(std::max(int64_t(15), int64_t((gran / 3.) + (gran / 10.))));\n\n\t\t\t// for long tests we don't want to make the user wait too much, so\n\t\t\t// we need to poll more frequently by the end, in case it's completed.\n\t\t\tif (type_ == TestType::LongTest && remaining_percent_ == 10)\n\t\t\t\tpoll_in_seconds_ = std::chrono::seconds(std::max(int64_t(1*60), int64_t(gran / 10.)));  // that's 2 min for 180min extended test\n\n\t\t\tdebug_out_dump(\"app\", DBG_FUNC_MSG << \"total: \" << total.count() << \", gran: \" << gran\n\t\t\t\t\t<< \", poll in: \" << poll_in_seconds_.count() << \", remaining secs: \" << get_remaining_seconds().count()\n\t\t\t\t\t<< \", remaining %: \" << int(remaining_percent_) << \", last seen %: \" << int(last_seen_percent_) << \".\\n\");\n\t\t}\n\n\t} else {\n\t\tremaining_percent_ = -1;\n\t\tlast_seen_percent_ = -1;\n\t\tpoll_in_seconds_ = -1s;\n\t\ttimer_.stop();\n\t}\n\n\tdrive_->set_test_is_active(status_ == SelfTestStatus::InProgress);\n\n\treturn {};  // everything ok\n}\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/selftest.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef SELFTEST_H\n#define SELFTEST_H\n\n#include <glibmm.h>\n#include <memory>\n#include <string>\n#include <cstdint>\n#include <chrono>\n#include <unordered_map>\n\n#include \"storage_device.h\"\n#include \"command_executor.h\"\n#include \"hz/error_container.h\"\n\n\nenum class SelfTestExecutionError {\n\tInternalError,\n\tAlreadyRunning,\n\tUnsupportedTest,\n\tInvalidTestType,\n\tCommandFailed,\n\tCommandUnknownError,\n\tNotRunning,\n\tStopUnsupported,\n\tUpdateError,\n\tParseError,\n\tReportUnsupported,\n};\n\n\n/// Self-test status\nenum class SelfTestStatus {\n\tUnknown,\n\tInProgress,\n\tManuallyAborted,\n\tInterrupted,\n\tCompletedNoError,\n\tCompletedWithError,\n\tReserved,\n};\n\n\n\n/// Helper structure for enum-related functions\nstruct SelfTestStatusExt\n\t\t: public hz::EnumHelper<\n\t\t\t\tSelfTestStatus,\n\t\t\t\tSelfTestStatusExt,\n\t\t\t\tGlib::ustring>\n{\n\tstatic constexpr EnumType default_value = EnumType::Unknown;\n\n\tstatic std::unordered_map<EnumType, std::pair<std::string, Glib::ustring>> build_enum_map()\n\t{\n\t\treturn {\n\t\t\t{SelfTestStatus::Unknown, {\"unknown\", _(\"Unknown\")}},\n\t\t\t{SelfTestStatus::InProgress, {\"in_progress\", _(\"In Progress\")}},\n\t\t\t{SelfTestStatus::ManuallyAborted, {\"manually_aborted\", _(\"Manually Aborted\")}},\n\t\t\t{SelfTestStatus::Interrupted, {\"interrupted\", _(\"Interrupted\")}},\n\t\t\t{SelfTestStatus::CompletedNoError, {\"completed_no_error\", _(\"Completed Successfully\")}},\n\t\t\t{SelfTestStatus::CompletedWithError, {\"completed_with_error\", _(\"Completed With Errors\")}},\n\t\t\t{SelfTestStatus::Reserved, {\"reserved\", _(\"Reserved\")}},\n\t\t};\n\t}\n\n};\n\n\n\n\n/// Self-test error severity\nenum class SelfTestStatusSeverity {\n\tNone,\n\tWarning,\n\tError,\n};\n\n\n/// Get severity of error status\n[[nodiscard]] SelfTestStatusSeverity get_self_test_status_severity(SelfTestStatus s);\n\n\n\n\n/// SMART self-test runner.\nclass SelfTest {\n\tpublic:\n\n\t\t/// Test type\n\t\tenum class TestType {\n//\t\t\tImmediateOffline,  ///< Immediate offline, not supported\n\t\t\tShortTest,  ///< Short self-test\n\t\t\tLongTest,  ///< Extended (a.k.a. long) self-test\n\t\t\tConveyance  ///< Conveyance self-test\n\t\t};\n\n\n\t\t/// Get displayable name for a test type\n\t\t[[nodiscard]] static std::string get_test_displayable_name(TestType type);\n\n\n\t\t/// Constructor. \\c drive must have the capabilities present in its properties.\n\t\tSelfTest(StorageDevicePtr drive, TestType type)\n\t\t\t: drive_(std::move(drive)), type_(type)\n\t\t{ }\n\n\n\t\t/// Check if the test is currently active\n\t\t[[nodiscard]] bool is_active() const;\n\n\n\t\t/// Get remaining time percent until the test completion.\n\t\t/// \\return -1 if N/A or unknown.\n\t\t[[nodiscard]] int8_t get_remaining_percent() const;\n\n\n\t\t/// Get estimated time of completion for the test.\n\t\t/// \\return -1 if N/A or unknown. Note that 0 is a valid value.\n\t\t[[nodiscard]] std::chrono::seconds get_remaining_seconds() const;\n\n\n\t\t/// Get test type\n\t\t[[nodiscard]] TestType get_test_type() const;\n\n\n\t\t/// Get test status\n\t\t[[nodiscard]] SelfTestStatus get_status() const;\n\n\n\t\t/// Get the number of seconds after which the caller should call update().\n\t\t/// Returns -1 if the test is not running.\n\t\t[[nodiscard]] std::chrono::seconds get_poll_in_seconds() const;\n\n\n\t\t/// Get a constant \"test duration during idle\" capability drive's stored capabilities. -1 if N/A.\n\t\t[[nodiscard]] std::chrono::seconds get_min_duration_seconds() const;\n\n\n\t\t/// Gets the current test type support status from drive's stored capabilities.\n\t\t[[nodiscard]] bool is_supported() const;\n\n\n\t\t/// Start the test. Note that this object is not reusable, start() must be called\n\t\t/// only on newly constructed objects.\n\t\t/// \\return error message on error, empty string on success.\n\t\t[[nodiscard]] hz::ExpectedVoid<SelfTestExecutionError> start(const std::shared_ptr<CommandExecutor>& smartctl_ex = nullptr);\n\n\n\t\t/// Abort the running test.\n\t\t/// \\return error message on error, empty string on success.\n\t\t[[nodiscard]] hz::ExpectedVoid<SelfTestExecutionError> force_stop(const std::shared_ptr<CommandExecutor>& smartctl_ex = nullptr);\n\n\n\t\t/// Update status variables. The user should call this every get_poll_in_seconds() seconds.\n\t\t/// \\return error message on error, empty string on success.\n\t\t[[nodiscard]] hz::ExpectedVoid<SelfTestExecutionError> update(const std::shared_ptr<CommandExecutor>& smartctl_ex = nullptr);\n\n\n\tprivate:\n\n\t\tStorageDevicePtr drive_;  ///< Drive to run the tests on\n\t\tTestType type_ = TestType::ShortTest;  ///< Test type\n\n\t\t// status variables:\n\t\tSelfTestStatus status_ = SelfTestStatus::Unknown;  ///< Current status of the test as reported by the drive\n\t\tint8_t remaining_percent_ = -1;  ///< Remaining %. 0 means unknown, -1 means N/A. This is set to 100 on start.\n\t\tint8_t last_seen_percent_ = -1;  ///< Last reported %, to detect changes in percentage (needed for timer update).\n\t\tmutable std::chrono::seconds total_duration_ = std::chrono::seconds(-1);  ///< Total duration needed for the test, as reported by the drive. Constant. This variable acts as a cache.\n\t\tstd::chrono::seconds poll_in_seconds_ = std::chrono::seconds(-1);  ///< The user is asked to poll after this much seconds have passed.\n\n\t\tGlib::Timer timer_;  ///< Counts time since the last percent change\n\n};\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_executor.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include <glibmm.h>\n\n#include \"smartctl_executor.h\"\n#include \"hz/win32_tools.h\"\n#include \"rconfig/rconfig.h\"\n#include \"app_regex.h\"\n#include \"hz/fs.h\"\n#include \"build_config.h\"\n#include <vector>\n\n\n\nhz::fs::path get_smartctl_binary()\n{\n\tauto smartctl_binary = hz::fs_path_from_string(rconfig::get_data<std::string>(\"system/smartctl_binary\"));\n\n\tif (BuildEnv::is_kernel_family_windows()) {\n\t\t// Look in smartmontools installation directory.\n\t\thz::fs::path system_binary;\n\t\tdo {\n\t\t\tconst bool use_smt = rconfig::get_data<bool>(\"system/win32_search_smartctl_in_smartmontools\");\n\t\t\tif (!use_smt)\n\t\t\t\tbreak;\n\n\t\t\tauto smt_regpath = rconfig::get_data<std::string>(\"system/win32_smartmontools_regpath\");\n\t\t\tauto smt_regpath_wow = rconfig::get_data<std::string>(\"system/win32_smartmontools_regpath_wow\");  // same as above, but with WOW6432Node\n\t\t\tauto smt_regkey = rconfig::get_data<std::string>(\"system/win32_smartmontools_regkey\");\n\t\t\tauto smt_smartctl = rconfig::get_data<std::string>(\"system/win32_smartmontools_smartctl_binary\");\n\n\t\t\tif ((smt_regpath.empty() && smt_regpath_wow.empty()) || smt_regkey.empty() || smt_smartctl.empty())\n\t\t\t\tbreak;\n\n\t\t\tstd::string smt_inst_dir;\n\t\t\t#ifdef _WIN32\n\t\t\t\thz::win32_get_registry_value_string(HKEY_LOCAL_MACHINE, smt_regpath, smt_regkey, smt_inst_dir);\n\t\t\t\tif (smt_inst_dir.empty()) {\n\t\t\t\t\thz::win32_get_registry_value_string(HKEY_LOCAL_MACHINE, smt_regpath_wow, smt_regkey, smt_inst_dir);\n\t\t\t\t}\n\t\t\t#endif\n\n\t\t\tif (smt_inst_dir.empty()) {\n\t\t\t\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Smartmontools installation not found in \\\"HKLM\\\\\"\n\t\t\t\t\t\t<< smt_regpath << \"\\\\\" << smt_regkey << \"\\\".\\n\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Smartmontools installation found at \\\"\" << smt_inst_dir\n\t\t\t\t\t<< \"\\\", using \\\"\" << smt_smartctl << \"\\\".\\n\");\n\n\t\t\tauto p = hz::fs_path_from_string(smt_inst_dir) / hz::fs_path_from_string(smt_smartctl);\n\n\t\t\tif (!hz::fs::exists(p) || !hz::fs::is_regular_file(p))\n\t\t\t\tbreak;\n\n\t\t\tsystem_binary = p;\n\t\t} while (false);\n\n\t\tif (!system_binary.empty()) {\n\t\t\tsmartctl_binary = system_binary;\n\n\t\t} else if (smartctl_binary.is_relative()) {\n\t\t\t// If smartctl path is relative, and it's Windows, and the package seems to contain smartctl, use our own binary.\n\t\t\tif (auto app_dir = hz::fs_get_application_dir(); !app_dir.empty() && hz::fs::exists(app_dir / smartctl_binary)) {\n\t\t\t\tsmartctl_binary = app_dir / smartctl_binary;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn smartctl_binary;\n}\n\n\n\nhz::ExpectedVoid<SmartctlExecutorError> execute_smartctl(const std::string& device, const std::vector<std::string>& device_opts,\n\t\tconst std::vector<std::string>& command_options,\n\t\tstd::shared_ptr<CommandExecutor> smartctl_ex, std::string& smartctl_output)\n{\n\t// win32 doesn't have slashes in devices names. For others, check that slash is present.\n\tif (!BuildEnv::is_kernel_family_windows()) {\n\t\tconst std::string::size_type pos = device.rfind('/');  // find basename\n\t\tif (pos == std::string::npos) {\n\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Invalid device name \\\"\" << device << \"\\\".\\n\");\n\t\t\treturn hz::Unexpected(SmartctlExecutorError::InvalidDevice, _(\"Invalid device name specified.\"));\n\t\t}\n\t}\n\n\n\tif (!smartctl_ex)  // if it doesn't exist, create a default one\n\t\tsmartctl_ex = std::make_shared<SmartctlExecutor>();\n\n\tauto smartctl_binary = get_smartctl_binary();\n\n\tif (smartctl_binary.empty()) {\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Smartctl binary is not set in config.\\n\");\n\t\treturn hz::Unexpected(SmartctlExecutorError::NoBinary, _(\"Smartctl binary is not specified in configuration.\"));\n\t}\n\n\tauto smartctl_def_options_str = hz::string_trim_copy(rconfig::get_data<std::string>(\"system/smartctl_options\"));\n\tstd::vector<std::string> smartctl_options;\n\tif (!smartctl_def_options_str.empty()) {\n\t\ttry {\n\t\t\tsmartctl_options = Glib::shell_parse_argv(smartctl_def_options_str);\n\t\t}\n\t\tcatch(Glib::ShellError& e)\n\t\t{\n\t\t\treturn hz::Unexpected(SmartctlExecutorError::InvalidCommandLine, _(\"Invalid command line specified.\"));\n\t\t}\n\t}\n\tsmartctl_options.insert(smartctl_options.end(), device_opts.begin(), device_opts.end());\n\tsmartctl_options.insert(smartctl_options.end(), command_options.begin(), command_options.end());\n\tsmartctl_options.push_back(device);\n\n\tsmartctl_ex->set_command(hz::fs_path_to_string(smartctl_binary), smartctl_options);\n\n\tif (!smartctl_ex->execute() || !smartctl_ex->get_error_msg().empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Smartctl binary did not execute cleanly.\\n\");\n\n\t\tsmartctl_output = hz::string_trim_copy(hz::string_any_to_unix_copy(smartctl_ex->get_stdout_str()));\n\n\t\t// check if it's a device permission error.\n\t\t// Smartctl open device: /dev/sdb failed: Permission denied\n\t\tif (app_regex_partial_match(\"/Smartctl open device.+Permission denied/mi\", smartctl_output)) {\n\t\t\treturn hz::Unexpected(SmartctlExecutorError::PermissionDenied, _(\"Permission denied while opening device.\"));\n\t\t}\n\n\t\t// smartctl_output = smartctl_ex->get_stdout_str();\n\t\treturn hz::Unexpected(SmartctlExecutorError::ExecutionError, smartctl_ex->get_error_msg());\n\t}\n\n\t// any_to_unix is needed for windows\n\tsmartctl_output = hz::string_trim_copy(hz::string_any_to_unix_copy(smartctl_ex->get_stdout_str()));\n\tif (smartctl_output.empty()) {\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Smartctl returned an empty output.\\n\");\n\t\treturn hz::Unexpected(SmartctlExecutorError::EmptyOutput, _(\"Smartctl returned an empty output.\"));\n\t}\n\n\treturn {};\n}\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_executor.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef SMARTCTL_EXECUTOR_H\n#define SMARTCTL_EXECUTOR_H\n\n#include <glibmm.h>\n#include <glibmm/i18n.h>\n\n#include <ranges>\n#include <vector>\n\n#include \"async_command_executor.h\"\n#include \"command_executor.h\"\n#include \"hz/fs_ns.h\"\n#include \"hz/error_container.h\"\n\n\n\n\n\n/// Smartctl executor template.\ntemplate<class ExecutorSync>\nclass SmartctlExecutorGeneric : public ExecutorSync {\n\tpublic:\n\n\t\t/// Constructor\n\t\tSmartctlExecutorGeneric(std::string cmd, std::vector<std::string> cmdargs)\n\t\t\t: ExecutorSync(std::move(cmd), std::move(cmdargs))\n\t\t{\n\t\t\tthis->construct();\n\t\t}\n\n\n\t\t/// Constructor\n\t\tSmartctlExecutorGeneric()\n\t\t{\n\t\t\tthis->construct();\n\t\t}\n\n\n\n\tprotected:\n\n\t\t/// Called by constructors\n\t\tvoid construct()\n\t\t{\n\t\t\tExecutorSync::get_async_executor().set_exit_status_translator(&SmartctlExecutorGeneric::translate_exit_status);\n\t\t\tthis->set_error_header(std::string(_(\"An error occurred while executing smartctl:\")) + \"\\n\\n\");\n\t\t}\n\n\n\t\tenum {\n\t\t\texit_cant_parse = 1 << 0,  ///< Smartctl error code bit\n\t\t\texit_open_failed = 1 << 1,  ///< Smartctl error code bit\n\t\t\texit_smart_failed = 1 << 2,  ///< Smartctl error code bit\n\t\t\texit_disk_failing = 1 << 3,  ///< Smartctl error code bit\n\t\t\texit_prefail_threshold = 1 << 4,  ///< Smartctl error code bit\n\t\t\texit_threshold_in_past = 1 << 5,  ///< Smartctl error code bit\n\t\t\texit_error_log = 1 << 6,  ///< Smartctl error code bit\n\t\t\texit_self_test_log = 1 << 7  ///< Smartctl error code bit\n\t\t};\n\n\n\t\t/// Translate smartctl error code to a readable message\n\t\tstatic std::string translate_exit_status(int status)\n\t\t{\n\t\t\tstatic const std::vector<std::string> table = {\n\t\t\t\t_(\"Command line did not parse.\"),\n\t\t\t\t_(\"Device open failed, or device did not return an IDENTIFY DEVICE structure.\"),\n\t\t\t\t_(\"Some SMART command to the disk failed, or there was a checksum error in a SMART data structure\"),\n\t\t\t\t_(\"SMART status check returned \\\"DISK FAILING\\\"\"),\n\t\t\t\t_(\"SMART status check returned \\\"DISK OK\\\" but some prefail Attributes are less than threshold.\"),\n\t\t\t\t_(\"SMART status check returned \\\"DISK OK\\\" but we found that some (usage or prefail) Attributes have been less than threshold at some time in the past.\"),\n\t\t\t\t_(\"The device error log contains records of errors.\"),\n\t\t\t\t_(\"The device self-test log contains records of errors.\")\n\t\t\t};\n\n\t\t\tstd::string str;\n\n\t\t\t// check every bit\n\t\t\tfor (unsigned int i = 0; i <= 7; i++) {\n\t\t\t\tif ( (status & (1 << i)) != 0 ) {\n\t\t\t\t\tif (!str.empty())\n\t\t\t\t\t\tstr += \"\\n\";  // new-line-separate each entry\n\t\t\t\t\tstr += table[i];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn str;\n\t\t}\n\n\n\t\t/// Import the last error from command executor and clear all errors there\n\t\tvoid import_error() override\n\t\t{\n\t\t\tAsyncCommandExecutor& cmdex = this->get_async_executor();\n\n\t\t\tAsyncCommandExecutor::error_list_t errors = cmdex.get_errors();  // these are not clones\n\n\t\t\thz::ErrorBase* e = nullptr;\n\t\t\t// find the last relevant error.\n\t\t\tfor (const auto& error : std::ranges::reverse_view(errors)) {\n\t\t\t\t// ignore iochannel errors, they may mask the real errors\n\t\t\t\tif (error->get_type() != \"giochannel\" && error->get_type() != \"custom\") {\n\t\t\t\t\te = error->clone();\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcmdex.clear_errors();  // and clear them\n\n\t\t\tif (e) {  // if error is present, alert the user\n\t\t\t\ton_error_warn(e);\n\t\t\t}\n\t\t}\n\n\n\n\t\t/// This is called when an error occurs in command executor.\n\t\t/// Note: The warnings are already printed via debug_* in cmdex.\n\t\tvoid on_error_warn(hz::ErrorBase* e) override\n\t\t{\n\t\t\tif (!e)\n\t\t\t\treturn;\n\n\t\t\t// import the error only if it's relevant.\n\t\t\tconst std::string error_type = e->get_type();\n\t\t\t// accept all errors by default, except:\n\n\t\t\t// Treat most exit codes as non-errors.\n\t\t\tif (error_type == \"exit\") {\n\t\t\t\tint exit_code = 0;\n\t\t\t\t[[maybe_unused]] const bool status = e->get_code(exit_code);\n\t\t\t\t// Ignore everyone except this.\n\t\t\t\t// Note that we don't treat exit_open_failed as failure because:\n\t\t\t\t// * It may be returned from a DVD that returns product info but has no disk inside;\n\t\t\t\t// * It may be returned from a usb flash drive with -d scsi;\n\t\t\t\t// * exit_cant_parse is returned when opening unsupported usb drives without -d scsi.\n\t\t\t\tif ( !(exit_code & exit_cant_parse) )\n\t\t\t\t\treturn;\n\n\t\t\t\t// ignore giochannel errors - higher level errors will be triggered, and they more user-friendly.\n\t\t\t} else if (error_type == \"giochannel\" || error_type == \"custom\") {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis->set_error_msg(e->get_message());\n\t\t}\n\n};\n\n\n\n\n/// Smartctl executor without GUI support\nusing SmartctlExecutor = SmartctlExecutorGeneric<CommandExecutor>;\n\n\n\n/// Get smartctl binary (from config, etc.). Returns an empty string if not found.\n[[nodiscard]] hz::fs::path get_smartctl_binary();\n\n\n\nenum class SmartctlExecutorError {\n\tInvalidDevice,  ///< Device name is invalid\n\tNoBinary,  ///< Smartctl binary is not specified in configuration\n\tPermissionDenied,  ///< Permission denied while opening device\n\tExecutionError,  ///< Error executing smartctl\n\tEmptyOutput,  ///< Smartctl returned an empty output\n\tInvalidCommandLine,  ///< Invalid command line\n};\n\n\n/// Execute smartctl on device \\c device.\n/// \\return error message on error, empty string on success.\n[[nodiscard]] hz::ExpectedVoid<SmartctlExecutorError> execute_smartctl(const std::string& device, const std::vector<std::string>& device_opts,\n\t\tconst std::vector<std::string>& command_options,\n\t\tstd::shared_ptr<CommandExecutor> smartctl_ex, std::string& smartctl_output);\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_executor_gui.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef SMARTCTL_EXECUTOR_GUI_H\n#define SMARTCTL_EXECUTOR_GUI_H\n\n#include \"smartctl_executor.h\"\n#include \"command_executor_gui.h\"\n\n\n\n/// Smartctl executor with GUI support\nusing SmartctlExecutorGui = SmartctlExecutorGeneric<CommandExecutorGui>;\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_json_ata_parser.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2022 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include \"smartctl_json_ata_parser.h\"\n\n#include <cstdint>\n#include <optional>\n#include <string>\n#include <string_view>\n#include <tuple>\n#include <vector>\n#include <chrono>\n\n#include \"fmt/format.h\"\n#include \"nlohmann/json.hpp\"\n\n#include \"storage_property.h\"\n#include \"hz/debug.h\"\n#include \"hz/string_algo.h\"\n// #include \"smartctl_version_parser.h\"\n#include \"hz/format_unit.h\"\n#include \"hz/error_container.h\"\n#include \"hz/string_num.h\"\n#include \"smartctl_json_parser_helpers.h\"\n#include \"smartctl_parser_types.h\"\n\n\n/*\nInformation not printed in JSON yet:\n\n- Checksum warnings (smartctl.cpp: checksumwarning()).\n\tSmartctl output: Warning! SMART <section name> Structure error: invalid SMART checksum\n \tKeys:\n \t\t_text_only/attribute_data_checksum_error\n \t\t_text_only/attribute_thresholds_checksum_error\n \t\t_text_only/ata_error_log_checksum_error\n \t\t_text_only/selftest_log_checksum_error\n\n- Samsung warning\n \tSmartctl output: May need -F samsung or -F samsung2 enabled; see manual for details\n \tWe ignore this in text parser.\n\n- Warnings from drivedb.h in the middle of Info section\n\tSmartctl output (example):\n\t\tWARNING: A firmware update for this drive may be available,\n\t\tsee the following Seagate web pages:\n\t\t...\n\tKeys: _text_only/info_warning\n\n- Errors about consistency:\n\t\"Invalid Error Log index ...\"\n\t\"Warning: ATA error count %d inconsistent with error log pointer\"\n\tWe ignore this in text parser.\n\n- \"mandatory SMART command failed\" and similar errors.\n\tWe ignore this in text parser.\n\n- SMART support and some other Info keys\n\t_text_only/write_cache_reorder\n\t_text_only/power_mode\n\n - Directory log supported\n \tWe don't use this.\n \t_text_only/directory_log_supported\n\n*/\n\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse(std::string_view smartctl_output)\n{\n\tif (hz::string_trim_copy(smartctl_output).empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Empty string passed as an argument. Returning.\\n\");\n\t\treturn hz::Unexpected(SmartctlParserError::EmptyInput, \"Smartctl data is empty.\");\n\t}\n\n\tnlohmann::json json_root_node;\n\ttry {\n\t\tjson_root_node = nlohmann::json::parse(smartctl_output);\n\t} catch (const nlohmann::json::parse_error& e) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Error parsing smartctl output as JSON: \" << e.what() << \"\\n\");\n\t\treturn hz::Unexpected(SmartctlParserError::SyntaxError, std::string(\"Invalid JSON data: \") + e.what());\n\t}\n\n\tStorageProperty merged_property, full_property;\n\tauto version_parse_status = SmartctlJsonParserHelpers::parse_version(json_root_node, merged_property, full_property);\n\tif (!version_parse_status) {\n\t\treturn version_parse_status;\n\t}\n\tadd_property(merged_property);\n\tadd_property(full_property);\n\n\t// Info must be supported.\n\tauto info_parse_status = parse_section_info(json_root_node);\n\tif (!info_parse_status) {\n\t\treturn info_parse_status;\n\t}\n\n\t// Add properties for each parsed section so that the UI knows which tabs to show or hide\n\t{\n\t\tauto section_parse_status = parse_section_health(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::Health;\n//\t\tp.set_name(\"_parser/health_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\t{\n\t\tauto section_parse_status = parse_section_capabilities(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::Capabilities;\n//\t\tp.set_name(\"_parser/capabilities_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\t{\n\t\tauto section_parse_status = parse_section_attributes(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::Attributes;\n//\t\tp.set_name(\"_parser/attributes_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\t{\n\t\tauto section_parse_status = parse_section_directory_log(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::DirectoryLog;\n//\t\tp.set_name(\"_parser/directory_log_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\t{\n\t\tauto section_parse_status = parse_section_error_log(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::ErrorLog;\n//\t\tp.set_name(\"_parser/error_log_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\t{\n\t\tauto section_parse_status = parse_section_selftest_log(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::SelftestLog;\n//\t\tp.set_name(\"_parser/selftest_log_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\t{\n\t\tauto section_parse_status = parse_section_selective_selftest_log(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::SelectiveSelftestLog;\n//\t\tp.set_name(\"_parser/selective_selftest_log_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\t{\n\t\tauto section_parse_status = parse_section_scttemp_log(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::TemperatureLog;\n//\t\tp.set_name(\"_parser/temperature_log_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\t{\n\t\tauto section_parse_status = parse_section_scterc_log(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::ErcLog;\n//\t\tp.set_name(\"_parser/erc_log_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\t{\n\t\tauto section_parse_status = parse_section_devstat(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::Devstat;\n//\t\tp.set_name(\"_parser/devstat_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\t{\n\t\tauto section_parse_status = parse_section_sataphy(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::PhyLog;\n//\t\tp.set_name(\"_parser/phy_log_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse_section_info(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\n\t// This is very similar to Basic Parser, but the Basic Parser supports different drive types, while this\n\t// one is only for ATA.\n\n\tstatic const std::vector<std::tuple<std::string, std::string, PropertyRetrievalFunc>> json_keys = {\n\n\t\t\t{\"smartctl/output\", _(\"Smartctl Text Output\"),  // the old text format\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tauto table_node = get_node(root_node, \"smartctl/output\");\n\t\t\t\t\tif (table_node.has_value() && table_node->is_array() && !table_node.value().empty()) {\n\t\t\t\t\t\tstd::vector<std::string> lines;\n\t\t\t\t\t\tfor (const auto& entry : table_node.value()) {\n\t\t\t\t\t\t\tlines.emplace_back(entry.get<std::string>());\n\t\t\t\t\t\t}\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = hz::string_join(lines, \"\\n\");\n\t\t\t\t\t\tp.show_in_ui = false;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"device/type\", _(\"Smartctl Device Type\"),  // nvme, sat, etc.\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tif (auto jval = get_node_data<std::string>(root_node, \"device/type\"); jval.has_value()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = jval.value();\n\t\t\t\t\t\tp.show_in_ui = false;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"device/protocol\", _(\"Smartctl Device Protocol\"),  // NVMe, ...\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tif (auto jval = get_node_data<std::string>(root_node, \"device/protocol\"); jval.has_value()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = jval.value();\n\t\t\t\t\t\tp.show_in_ui = false;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"model_family\", _(\"Model Family\"), string_formatter()},\n\t\t\t{\"model_name\", _(\"Device Model\"), string_formatter()},\n\t\t\t{\"serial_number\", _(\"Serial Number\"), string_formatter()},\n\n\t\t\t{\"wwn/_merged\", _(\"World Wide Name\"),\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tauto jval1 = get_node_data<int64_t>(root_node, \"wwn/naa\");\n\t\t\t\t\tauto jval2 = get_node_data<int64_t>(root_node, \"wwn/oui\");\n\t\t\t\t\tauto jval3 = get_node_data<int64_t>(root_node, \"wwn/id\");\n\n\t\t\t\t\tif (jval1 && jval2 && jval3) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\t// p.readable_value = fmt::format(\"{:X} {:X} {:X}\", jval1.value(), jval2.value(), jval3.value());\n\t\t\t\t\t\tp.readable_value = fmt::format(\"{:X}-{:06X}-{:08X}\", jval1.value(), jval2.value(), jval3.value());\n\t\t\t\t\t\tp.value = p.readable_value;  // string-type value\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"firmware_version\", _(\"Firmware Version\"), string_formatter()},\n\n\t\t\t{\"user_capacity/bytes\", _(\"Capacity\"),\n\t\t\t\tcustom_string_formatter<int64_t>([](int64_t value)\n\t\t\t\t{\n\t\t\t\t\treturn fmt::format(\"{} [{}; {} bytes]\",\n\t\t\t\t\t\thz::format_size(static_cast<uint64_t>(value), true),\n\t\t\t\t\t\thz::format_size(static_cast<uint64_t>(value), false),\n\t\t\t\t\t\thz::number_to_string_locale(value));\n\t\t\t\t})\n\t\t\t},\n\n\t\t\t{\"user_capacity/bytes/_short\", _(\"Capacity\"),\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tif (auto jval = get_node_data<int64_t>(root_node, \"user_capacity/bytes\"); jval) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.readable_value = hz::format_size(static_cast<uint64_t>(jval.value()), true);\n\t\t\t\t\t\tp.value = jval.value();\n\t\t\t\t\t\tp.show_in_ui = false;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", \"user_capacity/bytes\"));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"physical_block_size/_and/logical_block_size\", _(\"Sector Size\"),\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tstd::vector<std::string> values;\n\t\t\t\t\tif (auto jval1 = get_node_data<int64_t>(root_node, \"logical_block_size\"); jval1) {\n\t\t\t\t\t\tvalues.emplace_back(fmt::format(\"{} bytes logical\", jval1.value()));\n\t\t\t\t\t}\n\t\t\t\t\tif (auto jval2 = get_node_data<int64_t>(root_node, \"physical_block_size\"); jval2) {\n\t\t\t\t\t\tvalues.emplace_back(fmt::format(\"{} bytes physical\", jval2.value()));\n\t\t\t\t\t}\n\t\t\t\t\tif (!values.empty()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.readable_value = hz::string_join(values, \", \");\n\t\t\t\t\t\tp.value = p.readable_value;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t// (S)ATA, used to detect HDD vs SSD\n\t\t\t{\"rotation_rate\", _(\"Rotation Rate\"), integer_formatter<int64_t>(\"{} RPM\")},\n\n\t\t\t{\"form_factor/name\", _(\"Form Factor\"), string_formatter()},\n\t\t\t{\"trim/supported\", _(\"TRIM Supported\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"in_smartctl_database\", _(\"In Smartctl Database\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"smartctl/drive_database_version/string\", _(\"Smartctl Database Version\"), string_formatter()},\n\t\t\t{\"ata_version/string\", _(\"ATA Version\"), string_formatter()},\n\t\t\t{\"sata_version/string\", _(\"SATA Version\"), string_formatter()},\n\n\t\t\t{\"interface_speed/_merged\", _(\"Interface Speed\"),\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tstd::vector<std::string> values;\n\t\t\t\t\tif (auto jval1 = get_node_data<std::string>(root_node, \"interface_speed/max/string\"); jval1) {\n\t\t\t\t\t\tvalues.emplace_back(fmt::format(\"Max: {}\", jval1.value()));\n\t\t\t\t\t}\n\t\t\t\t\tif (auto jval2 = get_node_data<std::string>(root_node, \"interface_speed/current/string\"); jval2) {\n\t\t\t\t\t\tvalues.emplace_back(fmt::format(\"Current: {}\", jval2.value()));\n\t\t\t\t\t}\n\t\t\t\t\tif (!values.empty()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.readable_value = hz::string_join(values, \", \");\n\t\t\t\t\t\tp.value = p.readable_value;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"local_time/asctime\", _(\"Scanned on\"), string_formatter()},\n\n\t\t\t{\"smart_support/available\", _(\"SMART Supported\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"smart_support/enabled\", _(\"SMART Enabled\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\n\t\t\t{\"ata_aam/enabled\", _(\"AAM Feature\"), bool_formatter(_(\"Enabled\"), _(\"Disabled\"))},\n\t\t\t{\"ata_aam/level\", _(\"AAM Level\"),\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tif (auto level_result = get_node_data<int64_t>(root_node, \"ata_aam/level\"); level_result.has_value()) {\n\t\t\t\t\t\tstd::string level_string = get_node_data<std::string>(root_node, \"ata_aam/string\").value_or(\"\");\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.readable_value = fmt::format(\"{} ({})\", level_string, level_result.value());\n\t\t\t\t\t\tp.value = level_result.value();\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\"ata_aam/recommended_level\", _(\"AAM Recommended Level\"), integer_formatter<int64_t>()},\n\n\t\t\t{\"ata_apm/enabled\", _(\"APM Feature\"), bool_formatter(_(\"Enabled\"), _(\"Disabled\"))},\n\t\t\t{\"ata_apm/level\", _(\"APM Level\"),\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tif (auto level_result = get_node_data<int64_t>(root_node, \"ata_apm/level\"); level_result.has_value()) {\n\t\t\t\t\t\tstd::string level_string = get_node_data<std::string>(root_node, \"ata_apm/string\").value_or(\"\");\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.readable_value = fmt::format(\"{} ({})\", level_string, level_result.value());\n\t\t\t\t\t\tp.value = level_result.value();\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"read_lookahead/enabled\", _(\"Read Look-Ahead\"), bool_formatter(_(\"Enabled\"), _(\"Disabled\"))},\n\t\t\t{\"write_cache/enabled\", _(\"Write Cache\"), bool_formatter(_(\"Enabled\"), _(\"Disabled\"))},\n\t\t\t{\"ata_dsn/enabled\", _(\"DSN Feature\"), bool_formatter(_(\"Enabled\"), _(\"Disabled\"))},\n\t\t\t{\"ata_security/string\", _(\"ATA Security\"), string_formatter()},\n\n\t\t\t// Protocol-independent JSON-only values\n\t\t\t{\"power_cycle_count\", _(\"Number of Power Cycles\"), integer_formatter<int64_t>()},\n\t\t\t{\"power_on_time/hours\", _(\"Powered for\"), integer_formatter<int64_t>(\"{} hours\")},\n\t\t\t{\"temperature/current\", _(\"Current Temperature\"), integer_formatter<int64_t>(\"{}° Celsius\")},\n\t};\n\n\tbool any_found = false;\n\tfor (const auto& [key, displayable_name, retrieval_func] : json_keys) {\n\t\tDBG_ASSERT(retrieval_func != nullptr);\n\n\t\tauto p = retrieval_func(json_root_node, key, displayable_name);\n\t\tif (p.has_value()) {  // ignore if not found\n\t\t\tp->section = StoragePropertySection::Info;\n\t\t\tadd_property(p.value());\n\t\t\tany_found = true;\n\t\t}\n\t}\n\n\tif (!any_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, \"No keys info found in JSON data.\");\n\t}\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse_section_health(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\n\tconst std::vector<std::tuple<std::string, std::string, PropertyRetrievalFunc>> health_keys = {\n\t\t\t{\"smart_status/passed\", _(\"Overall Health Self-Assessment Test\"), bool_formatter(_(\"PASSED\"), _(\"FAILED\"))},\n\t};\n\n\tfor (const auto& [key, displayable_name, retrieval_func] : health_keys) {\n\t\tDBG_ASSERT(retrieval_func != nullptr);\n\n\t\tauto p = retrieval_func(json_root_node, key, displayable_name);\n\t\tif (p.has_value()) {  // ignore if not found\n\t\t\tp->section = StoragePropertySection::OverallHealth;\n\t\t\tadd_property(p.value());\n\t\t}\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse_section_capabilities(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\n\tstatic const std::vector<std::tuple<std::string, std::string, PropertyRetrievalFunc>> json_keys = {\n//\t\t\t// FIXME Remove?\n//\t\t\t{\"ata_smart_data/capabilities/_group\", _(\"SMART Capabilities\"),\n//\t\t\t \t\tconditional_formatter(\"ata_smart_data/capabilities/values\",\n//\t\t\t\t\tAtaStorageProperty{} )},\n//\n//\t\t\t// FIXME Remove?\n//\t\t\t{\"ata_smart_data/capabilities/error_logging_supported/_group\", _(\"Error Logging Capabilities\"),\n//\t\t\t \t\tconditional_formatter(\"ata_smart_data/capabilities/error_logging_supported\",\n//\t\t\t\t\tAtaStorageProperty{} )},\n//\n\n\t\t\t{\"ata_smart_data/offline_data_collection/status/_auto_enabled\", _(\"Automatic offline data collection status\"),\n\t\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tauto value_val = get_node_data<int64_t>(root_node, \"ata_smart_data/offline_data_collection/status/value\");\n\t\t\t\t\tif (value_val.has_value()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = static_cast<bool>(value_val.value() & 0x80);  // taken from ataprint.cpp\n\t\t\t\t\t\tp.readable_value = p.get_value<bool>() ? _(\"Enabled\") : _(\"Disabled\");\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t// Last self-test status\n\t\t\t{\"ata_smart_data/offline_data_collection/status/value/_decoded\", \"Last offline data collection status\",\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tauto value_val = get_node_data<uint8_t>(root_node, \"ata_smart_data/offline_data_collection/status/value\");\n\t\t\t\t\tif (value_val.has_value()) {\n\t\t\t\t\t\tstd::string status_str;\n\t\t\t\t\t\tswitch (value_val.value() & 0x7f) {\n\t\t\t\t\t\t\t// Data from smartmontools/ataprint.cpp\n\t\t\t\t\t\t\tcase 0x00: status_str = _(\"Never started\"); break;\n\t\t\t\t\t\t\tcase 0x02: status_str = _(\"Completed without error\"); break;\n\t\t\t\t\t\t\tcase 0x03: status_str = (value_val.value() == 0x03 ? _(\"In progress\") : _(\"In reserved state\")); break;\n\t\t\t\t\t\t\tcase 0x04: status_str = _(\"Suspended by an interrupting command from host\"); break;\n\t\t\t\t\t\t\tcase 0x05: status_str = _(\"Aborted by an interrupting command from host\"); break;\n\t\t\t\t\t\t\tcase 0x06: status_str = _(\"Aborted by the device with a fatal error\"); break;\n\t\t\t\t\t\t\tdefault: status_str = ((value_val.value() & 0x7f) > 0x40 ? _(\"In vendor-specific state\") : _(\"In reserved state\")); break;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.section = StoragePropertySection::Capabilities;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = status_str;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"ata_smart_data/offline_data_collection/completion_seconds\", _(\"Time to complete offline data collection\"),\n\t\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tauto value_val = get_node_data<int64_t>(root_node, key);\n\t\t\t\t\tif (value_val.has_value()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = std::chrono::seconds(value_val.value());\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"ata_smart_data/self_test/status/_merged\", _(\"Self-test execution status\"),\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\t// Testing:\n\t\t\t\t\t// \"status\": {\n\t\t\t\t\t//   \"value\": 249,\n\t\t\t\t\t//   \"string\": \"in progress, 90% remaining\",\n\t\t\t\t\t//   \"remaining_percent\": 90\n\t\t\t\t\t// },\n\n\t\t\t\t\t// Not testing:\n\t\t\t\t\t// \"status\": {\n\t\t\t\t\t//   \"value\": 0,\n\t\t\t\t\t//   \"string\": \"completed without error\",\n\t\t\t\t\t//   \"passed\": true\n\t\t\t\t\t// },\n\n\t\t\t\t\tAtaStorageSelftestEntry::Status status = AtaStorageSelftestEntry::Status::Unknown;\n\n\t\t\t\t\tauto value_val = get_node_data<uint8_t>(root_node, \"ata_smart_data/self_test/status/value\");\n\t\t\t\t\tif (value_val.has_value()) {\n\t\t\t\t\t\tswitch (value_val.value() >> 4) {\n\t\t\t\t\t\t\t// Data from smartmontools/ataprint.cpp\n\t\t\t\t\t\t\tcase 0x0: status = AtaStorageSelftestEntry::Status::CompletedNoError; break;\n\t\t\t\t\t\t\tcase 0x1: status = AtaStorageSelftestEntry::Status::AbortedByHost; break;\n\t\t\t\t\t\t\tcase 0x2: status = AtaStorageSelftestEntry::Status::Interrupted; break;\n\t\t\t\t\t\t\tcase 0x3: status = AtaStorageSelftestEntry::Status::FatalOrUnknown; break;\n\t\t\t\t\t\t\tcase 0x4: status = AtaStorageSelftestEntry::Status::ComplUnknownFailure; break;\n\t\t\t\t\t\t\tcase 0x5: status = AtaStorageSelftestEntry::Status::ComplElectricalFailure; break;\n\t\t\t\t\t\t\tcase 0x6: status = AtaStorageSelftestEntry::Status::ComplServoFailure; break;\n\t\t\t\t\t\t\tcase 0x7: status = AtaStorageSelftestEntry::Status::ComplReadFailure; break;\n\t\t\t\t\t\t\tcase 0x8: status = AtaStorageSelftestEntry::Status::ComplHandlingDamage; break;\n\t\t\t\t\t\t\t// Special case\n\t\t\t\t\t\t\tcase 0xf: status = AtaStorageSelftestEntry::Status::InProgress; break;\n\t\t\t\t\t\t\tdefault: status = AtaStorageSelftestEntry::Status::Reserved; break;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tAtaStorageSelftestEntry sse;\n\t\t\t\t\t\tsse.test_num = 0;  // capability uses 0\n\t\t\t\t\t\tsse.status_str = AtaStorageSelftestEntry::get_readable_status_name(status);\n\t\t\t\t\t\tsse.status = status;\n\n\t\t\t\t\t\tsse.remaining_percent = -1;  // unknown or n/a\n\t\t\t\t\t\t// Present only when extended self-test log is supported\n\t\t\t\t\t\tif (auto remaining_percent_val = get_node_data<int8_t>(root_node, \"ata_smart_data/self_test/status/remaining_percent\"); remaining_percent_val.has_value()) {\n\t\t\t\t\t\t\tsse.remaining_percent = remaining_percent_val.value();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = sse;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t// Present only when extended self-test log is supported\n\t\t\t{\"ata_smart_data/self_test/status/remaining_percent\", _(\"Self-test remaining percentage\"), integer_formatter<int64_t>(\"{} %\")},\n\n\t\t\t{\"ata_smart_data/capabilities/self_tests_supported\", _(\"Self-tests supported\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\n\t\t\t{\"ata_smart_data/capabilities/exec_offline_immediate_supported\", _(\"Offline immediate test supported\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"ata_smart_data/capabilities/offline_is_aborted_upon_new_cmd\", _(\"Abort offline collection on new command\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"ata_smart_data/capabilities/offline_surface_scan_supported\", _(\"Offline surface scan supported\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\n\t\t\t{\"ata_smart_data/capabilities/conveyance_self_test_supported\", _(\"Conveyance self-test supported\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"ata_smart_data/capabilities/selective_self_test_supported\", _(\"Selective self-test supported\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\n\t\t\t{\"ata_smart_data/self_test/polling_minutes/short\", _(\"Short self-test status recommended polling time\"),\n\t\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tauto value_val = get_node_data<int64_t>(root_node, key);\n\t\t\t\t\tif (value_val.has_value()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = std::chrono::minutes(value_val.value());\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"ata_smart_data/self_test/polling_minutes/extended\", _(\"Extended self-test status recommended polling time\"),\n\t\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tauto value_val = get_node_data<int64_t>(root_node, key);\n\t\t\t\t\tif (value_val.has_value()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = std::chrono::minutes(value_val.value());\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"ata_smart_data/self_test/polling_minutes/conveyance\", _(\"Conveyance self-test status recommended polling time\"),\n\t\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tauto value_val = get_node_data<int64_t>(root_node, key);\n\t\t\t\t\tif (value_val.has_value()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = std::chrono::minutes(value_val.value());\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\n\t\t\t{\"ata_smart_data/capabilities/attribute_autosave_enabled\", _(\"Saves SMART data before entering power-saving mode\"), bool_formatter(_(\"Enabled\"), _(\"Disabled\"))},\n\n\t\t\t{\"ata_smart_data/capabilities/error_logging_supported\", _(\"Error logging supported\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"ata_smart_data/capabilities/gp_logging_supported\", _(\"General purpose logging supported\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\n\t\t\t{\"ata_sct_capabilities/_supported\", _(\"SCT capabilities supported\"),\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tif (auto value_val = get_node_exists(root_node, \"ata_sct_capabilities\"); value_val.has_value()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = value_val.value();\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\"ata_sct_capabilities/error_recovery_control_supported\", _(\"SCT error recovery control supported\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"ata_sct_capabilities/feature_control_supported\", _(\"SCT feature control supported\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"ata_sct_capabilities/data_table_supported\", _(\"SCT data table supported\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\n\t};\n\n\tbool section_properties_found = false;\n\n\tfor (const auto& [key, displayable_name, retrieval_func] : json_keys) {\n\t\tDBG_ASSERT(retrieval_func != nullptr);\n\t\tauto p = retrieval_func(json_root_node, key, displayable_name);\n\t\tif (p.has_value()) {  // ignore if not found\n\t\t\tp->section = StoragePropertySection::Capabilities;\n\t\t\tadd_property(p.value());\n\t\t\tsection_properties_found = true;\n\t\t}\n\t}\n\n\tif (!section_properties_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection,\n\t\t\t\tfmt::format(\"No section {} parsed.\", StoragePropertySectionExt::get_displayable_name(StoragePropertySection::Capabilities)));\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse_section_attributes(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\n\tbool section_properties_found = false;\n\n\t// Revision\n\tif (get_node_exists(json_root_node, \"ata_smart_attributes/revision\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_smart_attributes/revision\", _(\"Data structure revision number\"));\n\t\tp.section = StoragePropertySection::AtaAttributes;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_smart_attributes/revision\").value_or(0);\n\t\tadd_property(p);\n\t\tsection_properties_found = true;\n\t}\n\n\tconst std::string table_key = \"ata_smart_attributes/table\";\n\tauto table_node = get_node(json_root_node, table_key);\n\n\t// Entries\n\tif (table_node.has_value() && table_node->is_array()) {\n\t\tfor (const auto& table_entry : table_node.value()) {\n\t\t\tAtaStorageAttribute a;\n\n\t\t\ta.id = get_node_data<int32_t>(table_entry, \"id\").value_or(0);\n\t\t\ta.flag = get_node_data<std::string>(table_entry, \"flags/string\").value_or(\"\");\n\t\t\ta.value = (get_node_exists(table_entry, \"value\").value_or(false) ? std::optional<uint8_t>(get_node_data<uint8_t>(table_entry, \"value\").value_or(0)) : std::nullopt);\n\t\t\ta.worst = (get_node_exists(table_entry, \"worst\").value_or(false) ? std::optional<uint8_t>(get_node_data<uint8_t>(table_entry, \"worst\").value_or(0)) : std::nullopt);\n\t\t\ta.threshold = (get_node_exists(table_entry, \"thresh\").value_or(false) ? std::optional<uint8_t>(get_node_data<uint8_t>(table_entry, \"thresh\").value_or(0)) : std::nullopt);\n\t\t\ta.attr_type = get_node_data<bool>(table_entry, \"flags/prefailure\").value_or(false) ? AtaStorageAttribute::AttributeType::Prefail : AtaStorageAttribute::AttributeType::OldAge;\n\t\t\ta.update_type = get_node_data<bool>(table_entry, \"flags/updated_online\").value_or(false) ? AtaStorageAttribute::UpdateType::Always : AtaStorageAttribute::UpdateType::Offline;\n\n\t\t\tconst std::string when_failed = get_node_data<std::string>(table_entry, \"when_failed\").value_or(std::string());\n\t\t\tif (when_failed == \"now\") {\n\t\t\t\ta.when_failed = AtaStorageAttribute::FailTime::Now;\n\t\t\t} else if (when_failed == \"past\") {\n\t\t\t\ta.when_failed = AtaStorageAttribute::FailTime::Past;\n\t\t\t} else {  // \"\"\n\t\t\t\ta.when_failed = AtaStorageAttribute::FailTime::None;\n\t\t\t}\n\n\t\t\ta.raw_value = get_node_data<std::string>(table_entry, \"raw/string\").value_or(std::string());\n\t\t\ta.raw_value_int = get_node_data<int64_t>(table_entry, \"raw/value\").value_or(0);\n\n\t\t\tstd::string reported_name = get_node_data<std::string>(table_entry, \"name\").value_or(std::string());\n\n\t\t\tStorageProperty p;\n\t\t\tp.set_name(reported_name, reported_name, reported_name);  // The description database will correct this.\n\t\t\tp.section = StoragePropertySection::AtaAttributes;\n\t\t\tp.value = a;\n\t\t\tadd_property(p);\n\n\t\t\tsection_properties_found = true;\n\t\t}\n\t}\n\n\tif (!section_properties_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection,\n\t\t\t\tfmt::format(\"No section {} parsed.\", StoragePropertySectionExt::get_displayable_name(StoragePropertySection::AtaAttributes)));\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse_section_directory_log(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\tusing namespace std::string_literals;\n\n\tbool section_properties_found = false;\n\n\tstd::vector<std::string> lines;\n\n\tif (get_node_exists(json_root_node, \"ata_log_directory/gp_dir_version\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_log_directory/gp_dir_version\", _(\"General purpose log directory version\"));\n\t\tp.section = StoragePropertySection::DirectoryLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_log_directory/gp_dir_version\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"General Purpose Log Directory Version: {}\", p.get_value<int64_t>()));\n\t\tsection_properties_found = true;\n\t}\n\tif (get_node_exists(json_root_node, \"ata_log_directory/smart_dir_version\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_log_directory/smart_dir_version\", _(\"SMART log directory version\"));\n\t\tp.section = StoragePropertySection::DirectoryLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_log_directory/smart_dir_version\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"SMART Log Directory Version: {}\", p.get_value<int64_t>()));\n\t\tsection_properties_found = true;\n\t}\n\tif (get_node_exists(json_root_node, \"ata_log_directory/smart_dir_multi_sector\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_log_directory/smart_dir_multi_sector\", _(\"Multi-sector log support\"));\n\t\tp.section = StoragePropertySection::DirectoryLog;\n\t\tp.value = get_node_data<bool>(json_root_node, \"ata_log_directory/smart_dir_multi_sector\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Multi-sector log support: {}\", p.get_value<bool>() ? \"Yes\" : \"No\"));\n\t\tsection_properties_found = true;\n\t}\n\n\t// Table\n\tconst std::string table_key = \"ata_log_directory/table\";\n\tauto table_node = get_node(json_root_node, table_key);\n\n\t// Entries\n\tif (table_node.has_value() && table_node->is_array()) {\n\t\tlines.emplace_back();\n\n\t\tfor (const auto& table_entry : table_node.value()) {\n\t\t\tconst uint64_t address = get_node_data<uint64_t>(table_entry, \"address\").value_or(0);\n\t\t\tconst std::string name = get_node_data<std::string>(table_entry, \"name\").value_or(std::string());\n\t\t\tconst bool read = get_node_data<bool>(table_entry, \"read\").value_or(false);\n\t\t\tconst bool write = get_node_data<bool>(table_entry, \"write\").value_or(false);\n\t\t\tconst uint64_t gp_sectors = get_node_data<uint64_t>(table_entry, \"gp_sectors\").value_or(0);\n\t\t\tconst uint64_t smart_sectors = get_node_data<uint64_t>(table_entry, \"smart_sectors\").value_or(0);\n\n\t\t\t// Address, GPL/SL, RO/RW, Num Sectors (GPL, Smart) , Name\n\t\t\t// 0x00       GPL,SL  R/O      1  Log Directory\n\t\t\tlines.emplace_back(fmt::format(\n\t\t\t\t\t\"0x{:02X}    GPL Sectors: {:8}    SL Sectors: {:8}    {}{}    {}\",\n\t\t\t\t\taddress,\n\t\t\t\t\tgp_sectors == 0 ? \"-\" : std::to_string(gp_sectors),\n\t\t\t\t\tsmart_sectors == 0 ? \"-\" : std::to_string(smart_sectors),\n\t\t\t\t\t(read ? \"R\" : \"-\"),\n\t\t\t\t\t(write ? \"W\" : \"-\"),\n\t\t\t\t\tname));\n\t\t}\n\n\t\t// The whole section\n\t\t{\n\t\t\tStorageProperty p;\n\t\t\tp.set_name(\"ata_log_directory/_merged\", \"General Purpose Log Directory\");\n\t\t\tp.section = StoragePropertySection::DirectoryLog;\n\t\t\tp.reported_value = hz::string_join(lines, \"\\n\");\n\t\t\tp.value = p.reported_value;  // string-type value\n\n\t\t\tadd_property(p);\n\t\t}\n\n\t\tsection_properties_found = true;\n\t}\n\n\tif (!section_properties_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection,\n\t\t\t\tfmt::format(\"No section {} parsed.\", StoragePropertySectionExt::get_displayable_name(StoragePropertySection::DirectoryLog)));\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse_section_error_log(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\n\tbool section_properties_found = false;\n\n\t// Revision\n\tif (get_node_exists(json_root_node, \"ata_smart_error_log/extended/revision\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_smart_error_log/extended/revision\", _(\"SMART extended comprehensive error log version\"));\n\t\tp.section = StoragePropertySection::AtaErrorLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_smart_error_log/extended/revision\").value_or(0);\n\t\tadd_property(p);\n\t\tsection_properties_found = true;\n\t}\n\t// Count\n\tif (get_node_exists(json_root_node, \"ata_smart_error_log/extended/count\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_smart_error_log/extended/count\", _(\"ATA error count\"));\n\t\tp.section = StoragePropertySection::AtaErrorLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_smart_error_log/extended/count\").value_or(0);\n\t\tadd_property(p);\n\t\tsection_properties_found = true;\n\t}\n\n\tconst std::string table_key = \"ata_smart_error_log/extended/table\";\n\tauto table_node = get_node(json_root_node, table_key);\n\n\t// Entries\n\tif (table_node.has_value() && table_node->is_array()) {\n\t\tfor (const auto& table_entry : table_node.value()) {\n\t\t\tAtaStorageErrorBlock block;\n\t\t\tblock.error_num = get_node_data<uint32_t>(table_entry, \"error_number\").value_or(0);\n\t\t\tblock.log_index = get_node_data<uint64_t>(table_entry, \"log_index\").value_or(0);\n\t\t\tblock.lifetime_hours = get_node_data<uint32_t>(table_entry, \"lifetime_hours\").value_or(0);\n\t\t\tblock.device_state = get_node_data<std::string>(table_entry, \"device_state/string\").value_or(std::string());\n\t\t\tblock.lba = get_node_data<uint64_t>(table_entry, \"completion_registers/lba\").value_or(0);\n\t\t\tblock.type_more_info = get_node_data<std::string>(table_entry, \"error_description\").value_or(std::string());\n\n\t\t\tStorageProperty p;\n\t\t\tstd::string gen_name = fmt::format(\"{}/{}\", table_key, block.error_num);\n\t\t\tstd::string disp_name = fmt::format(\"Error {}\", block.error_num);\n\t\t\tp.set_name(gen_name, disp_name, gen_name);\n\t\t\tp.section = StoragePropertySection::AtaErrorLog;\n\t\t\tp.value = block;\n\t\t\tadd_property(p);\n\t\t}\n\n\t\tsection_properties_found = true;\n\t}\n\n\tif (!section_properties_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection,\n\t\t\t\tfmt::format(\"No section {} parsed.\", StoragePropertySectionExt::get_displayable_name(StoragePropertySection::AtaErrorLog)));\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse_section_selftest_log(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\n\tbool section_properties_found = false;\n\n\tconst bool extended = get_node_exists(json_root_node, \"ata_smart_self_test_log/extended/revision\").value_or(false);\n\tconst std::string log_key = extended ? \"ata_smart_self_test_log/extended\" : \"ata_smart_self_test_log/standard\";\n\n\t// Revision\n\tif (get_node_exists(json_root_node, log_key + \"/revision\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(log_key + \"/revision\", extended ? _(\"SMART extended self-test log version\") : _(\"SMART standard self-test log version\"));\n\t\tp.section = StoragePropertySection::SelftestLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, log_key + \"/revision\").value_or(0);\n\t\tadd_property(p);\n\t\tsection_properties_found = true;\n\t}\n\n\tstd::vector<std::string> counts;\n\n\t// Count\n\t{\n\t\tStorageProperty p;\n\t\tp.set_name(log_key + \"/count\", _(\"Self-test count\"));\n\t\tp.section = StoragePropertySection::SelftestLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, log_key + \"/count\").value_or(0);\n\t\tp.show_in_ui = false;\n\t\tadd_property(p);\n\t\tcounts.emplace_back(fmt::format(\"Self-test entries: {}\", p.get_value<int64_t>()));\n\t}\n\t// Error Count\n\t{\n\t\tStorageProperty p;\n\t\tp.set_name(log_key + \"/error_count_total\", _(\"Total error count\"));\n\t\tp.section = StoragePropertySection::SelftestLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, log_key + \"/error_count_total\").value_or(0);\n\t\tp.show_in_ui = false;\n\t\tadd_property(p);\n\t\tcounts.emplace_back(fmt::format(\"Total error count: {}\", p.get_value<int64_t>()));\n\t}\n\t// Outdated Error Count\n\t{\n\t\tStorageProperty p;\n\t\tp.set_name(log_key + \"/error_count_outdated\", _(\"Outdated error count\"));\n\t\tp.section = StoragePropertySection::SelftestLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, log_key + \"/error_count_outdated\").value_or(0);\n\t\tp.show_in_ui = false;\n\t\tadd_property(p);\n\t\tcounts.emplace_back(fmt::format(\"Outdated error count: {}\", p.get_value<int64_t>()));\n\t}\n\n\t// Displayed Counts\n\tif (!counts.empty()) {\n\t\tStorageProperty p;\n\t\tp.set_name(log_key + \"/_counts\", _(\"Entries\"));\n\t\tp.section = StoragePropertySection::SelftestLog;\n\t\tp.value = hz::string_join(counts, \"; \");\n\t\tadd_property(p);\n\n\t\tsection_properties_found = true;\n\t}\n\n\tconst std::string table_key = log_key + \"/table\";\n\tauto table_node = get_node(json_root_node, table_key);\n\n\t// Entries\n\tif (table_node.has_value() && table_node->is_array()) {\n\t\tuint32_t entry_num = 1;\n\t\tfor (const auto& table_entry : table_node.value()) {\n\t\t\tAtaStorageSelftestEntry entry;\n\t\t\tentry.test_num = entry_num;\n\t\t\tentry.type = get_node_data<std::string>(table_entry, \"type/string\").value_or(std::string());  // FIXME use type/value for i18n\n\t\t\tentry.status_str = get_node_data<std::string>(table_entry, \"status/string\").value_or(std::string());\n\t\t\tentry.remaining_percent = get_node_data<int8_t>(table_entry, \"status/remaining_percent\").value_or(-1);  // extended only\n\t\t\tentry.lifetime_hours = get_node_data<uint32_t>(table_entry, \"lifetime_hours\").value_or(0);\n\t\t\tentry.passed = get_node_data<bool>(table_entry, \"status/passed\").value_or(false);\n\n\t\t\tif (get_node_exists(table_entry, \"lba\").value_or(false)) {\n\t\t\t\tentry.lba_of_first_error = hz::number_to_string_locale(get_node_data<uint64_t>(table_entry, \"lba\").value_or(0));\n\t\t\t} else {\n\t\t\t\tentry.lba_of_first_error = \"-\";\n\t\t\t}\n\n\t\t\tif (get_node_exists(table_entry, \"status/value\").value_or(false)) {\n\t\t\t\tconst uint8_t status_value = get_node_data<uint8_t>(table_entry, \"status/value\").value_or(0);\n\t\t\t\tentry.status = static_cast<AtaStorageSelftestEntry::Status>(status_value >> 4);\n\t\t\t} else {\n\t\t\t\tentry.status = AtaStorageSelftestEntry::Status::Unknown;\n\t\t\t}\n\n\t\t\tStorageProperty p;\n\t\t\tstd::string gen_name = fmt::format(\"{}/{}\", table_key, entry_num);\n\t\t\tstd::string disp_name = fmt::format(\"Self-test entry {}\", entry.test_num);\n\t\t\tp.set_name(gen_name, disp_name);\n\t\t\tp.section = StoragePropertySection::SelftestLog;\n\t\t\tp.value = entry;\n\t\t\tadd_property(p);\n\n\t\t\t++entry_num;\n\t\t}\n\n\t\tsection_properties_found = true;\n\t}\n\n\tif (!section_properties_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection,\n\t\t\t\tfmt::format(\"No section {} parsed.\", StoragePropertySectionExt::get_displayable_name(StoragePropertySection::SelftestLog)));\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse_section_selective_selftest_log(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\tusing namespace std::string_literals;\n\n\tbool section_properties_found = false;\n\n\tstd::vector<std::string> lines;\n\n\tif (get_node_exists(json_root_node, \"ata_smart_selective_self_test_log/revision\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_smart_selective_self_test_log/revision\", _(\"SMART Selective self-test log data structure revision number\"));\n\t\tp.section = StoragePropertySection::SelectiveSelftestLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_smart_selective_self_test_log/revision\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"SMART Selective self-test log data structure revision number: {}\", p.get_value<int64_t>()));\n\t\tsection_properties_found = true;\n\t}\n\tif (get_node_exists(json_root_node, \"ata_smart_selective_self_test_log/power_up_scan_resume_minutes\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_smart_selective_self_test_log/power_up_scan_resume_minutes\",\n\t\t\t\t_(\"If Selective self-test is pending on power-up, resume delay (minutes)\"));\n\t\tp.section = StoragePropertySection::SelectiveSelftestLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_smart_selective_self_test_log/power_up_scan_resume_minutes\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"If Selective self-test is pending on power-up, resume delay: {} minutes\", p.get_value<int64_t>()));\n\t\tsection_properties_found = true;\n\t}\n\tif (get_node_exists(json_root_node, \"ata_smart_selective_self_test_log/flags/remainder_scan_enabled\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_smart_selective_self_test_log/flags/remainder_scan_enabled\",\n\t\t\t\t_(\"After scanning selected spans, scan remainder of the drive\"));\n\t\tp.section = StoragePropertySection::SelectiveSelftestLog;\n\t\tp.value = get_node_data<bool>(json_root_node, \"ata_smart_selective_self_test_log/flags/remainder_scan_enabled\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"After scanning selected spans, scan remainder of the drive: {}\", p.get_value<bool>() ? \"Yes\" : \"No\"));\n\t\tsection_properties_found = true;\n\t}\n\n\t// Table\n\tconst std::string table_key = \"ata_smart_selective_self_test_log/table\";\n\tauto table_node = get_node(json_root_node, table_key);\n\n\t// Entries\n\tif (table_node.has_value() && table_node->is_array()) {\n\t\tlines.emplace_back();\n\n\t\tint entry_num = 1;\n\t\tfor (const auto& table_entry : table_node.value()) {\n\t\t\tconst uint64_t lba_min = get_node_data<uint64_t>(table_entry, \"lba_min\").value_or(0);\n\t\t\tconst uint64_t lba_max = get_node_data<uint64_t>(table_entry, \"lba_max\").value_or(0);\n\t\t\tconst std::string status_str = get_node_data<std::string>(table_entry, \"status/string\").value_or(std::string());\n\n\t\t\tlines.emplace_back(fmt::format(\n\t\t\t\t\t\"Span: {:2}    Min LBA: {:020}    Max LBA: {:020}    Status: {}\",\n\t\t\t\t\tentry_num,\n\t\t\t\t\tlba_min,\n\t\t\t\t\tlba_max,\n\t\t\t\t\tstatus_str));\n\t\t\t++entry_num;\n\t\t}\n\n\t\t// The whole section\n\t\t{\n\t\t\tStorageProperty p;\n\t\t\tp.set_name(\"ata_smart_selective_self_test_log/_merged\", _(\"SMART selective self-test log\"));\n\t\t\tp.section = StoragePropertySection::SelectiveSelftestLog;\n\t\t\tp.reported_value = hz::string_join(lines, \"\\n\");\n\t\t\tp.value = p.reported_value;  // string-type value\n\n\t\t\tadd_property(p);\n\t\t}\n\n\t\tsection_properties_found = true;\n\t}\n\n\tif (!section_properties_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection,\n\t\t\t\tfmt::format(\"No section {} parsed.\", StoragePropertySectionExt::get_displayable_name(StoragePropertySection::SelectiveSelftestLog)));\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse_section_scttemp_log(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\tusing namespace std::string_literals;\n\n\tbool section_properties_found = false;\n\n\tstd::vector<std::string> lines;\n\n\tif (get_node_exists(json_root_node, \"ata_sct_status/format_version\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_status/format_version\", _(\"SCT status version\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_status/format_version\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"SCT status version: {}\", get_node_data<int64_t>(json_root_node, \"ata_sct_status/format_version\").value_or(0)));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_status/sct_version\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_status/sct_version\", _(\"SCT format version\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_status/sct_version\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"SCT format version: {}\", get_node_data<int64_t>(json_root_node, \"ata_sct_status/sct_version\").value_or(0)));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_status/device_state/string\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_status/device_state/string\", _(\"Device state\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<std::string>(json_root_node, \"ata_sct_status/device_state/string\").value_or(std::string());\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Device state: {}\", get_node_data<std::string>(json_root_node, \"ata_sct_status/device_state/string\").value_or(std::string())));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_status/temperature/current\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_status/temperature/current\", _(\"Current temperature (C)\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_status/temperature/current\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Current temperature: {}° Celsius\", get_node_data<int64_t>(json_root_node, \"ata_sct_status/temperature/current\").value_or(0)));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_status/temperature/power_cycle_min\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_status/temperature/power_cycle_min\", _(\"Power cycle min. temperature (C)\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_status/temperature/power_cycle_min\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Power cycle min. temperature: {}° Celsius\", get_node_data<int64_t>(json_root_node, \"ata_sct_status/temperature/power_cycle_min\").value_or(0)));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_status/temperature/power_cycle_max\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_status/temperature/power_cycle_max\", _(\"Power cycle max. temperature (C)\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_status/temperature/power_cycle_max\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Power cycle max. temperature: {}° Celsius\", get_node_data<int64_t>(json_root_node, \"ata_sct_status/temperature/power_cycle_max\").value_or(0)));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_status/temperature/lifetime_min\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_status/temperature/lifetime_min\", _(\"Lifetime min. temperature (C)\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_status/temperature/lifetime_min\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Lifetime min. temperature: {}° Celsius\", get_node_data<int64_t>(json_root_node, \"ata_sct_status/temperature/lifetime_min\").value_or(0)));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_status/temperature/lifetime_max\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_status/temperature/lifetime_max\", _(\"Lifetime max. temperature (C)\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_status/temperature/lifetime_max\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Lifetime max. temperature: {}° Celsius\", get_node_data<int64_t>(json_root_node, \"ata_sct_status/temperature/lifetime_max\").value_or(0)));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_status/temperature/under_limit_count\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_status/temperature/under_limit_count\", _(\"Under limit count\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_status/temperature/under_limit_count\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Under limit count: {}\", get_node_data<int64_t>(json_root_node, \"ata_sct_status/temperature/under_limit_count\").value_or(0)));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_status/temperature/over_limit_count\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_status/temperature/over_limit_count\", _(\"Over limit count\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_status/temperature/over_limit_count\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Over limit count: {}\", get_node_data<int64_t>(json_root_node, \"ata_sct_status/temperature/over_limit_count\").value_or(0)));\n\t}\n\n\tlines.emplace_back();\n\n\tif (get_node_exists(json_root_node, \"ata_sct_temperature_history/version\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_temperature_history/version\", _(\"SCT temperature history version\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_temperature_history/version\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"SCT temperature history version: {}\", get_node_data<int64_t>(json_root_node, \"ata_sct_temperature_history/version\").value_or(0)));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_temperature_history/sampling_period_minutes\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_temperature_history/sampling_period_minutes\", _(\"Temperature sampling period (min)\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_temperature_history/sampling_period_minutes\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Temperature sampling period: {} min.\", get_node_data<int64_t>(json_root_node, \"ata_sct_temperature_history/sampling_period_minutes\").value_or(0)));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_temperature_history/logging_interval_minutes\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_temperature_history/logging_interval_minutes\", _(\"Temperature logging interval (min)\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_temperature_history/logging_interval_minutes\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Temperature logging interval: {} min.\", get_node_data<int64_t>(json_root_node, \"ata_sct_temperature_history/logging_interval_minutes\").value_or(0)));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_temperature_history/temperature/op_limit_min\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_temperature_history/temperature/op_limit_min\", _(\"Recommended operating temperature (minimum) (C)\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_temperature_history/temperature/op_limit_min\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Recommended operating temperature (minimum): {}° Celsius\",\n\t\t\t\tget_node_data<int64_t>(json_root_node, \"ata_sct_temperature_history/temperature/op_limit_min\").value_or(0)));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_temperature_history/temperature/op_limit_max\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_temperature_history/temperature/op_limit_max\", _(\"Recommended operating temperature (maximum) (C)\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_temperature_history/temperature/op_limit_max\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Recommended operating temperature (maximum): {}° Celsius\",\n\t\t\t\tget_node_data<int64_t>(json_root_node, \"ata_sct_temperature_history/temperature/op_limit_max\").value_or(0)));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_temperature_history/temperature/limit_min\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_temperature_history/temperature/limit_min\", _(\"Allowed operating temperature (minimum) (C)\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_temperature_history/temperature/limit_min\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Allowed operating temperature (minimum): {}° Celsius\",\n\t\t\t\tget_node_data<int64_t>(json_root_node, \"ata_sct_temperature_history/temperature/limit_min\").value_or(0)));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_temperature_history/temperature/limit_max\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_temperature_history/temperature/limit_max\", _(\"Allowed operating temperature (maximum) (C)\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"ata_sct_temperature_history/temperature/limit_max\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Allowed operating temperature (maximum): {}° Celsius\",\n\t\t\t\tget_node_data<int64_t>(json_root_node, \"ata_sct_temperature_history/temperature/limit_max\").value_or(0)));\n\t}\n\n\t// The whole section\n\tif (!lines.empty()) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_status/_and/ata_sct_temperature_history/_merged\", _(\"Temperature log\"));\n\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\tp.reported_value = hz::string_join(lines, \"\\n\");\n\t\tp.value = p.reported_value;  // string-type value\n\t\tadd_property(p);\n\n\t\tsection_properties_found = true;\n\t}\n\n\t// TODO Temperature log graph\n\n\tif (!section_properties_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection,\n\t\t\t\tfmt::format(\"No section {} parsed.\", StoragePropertySectionExt::get_displayable_name(StoragePropertySection::TemperatureLog)));\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse_section_scterc_log(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\tusing namespace std::string_literals;\n\n\tbool section_properties_found = false;\n\n\tstd::vector<std::string> lines;\n\n\tif (get_node_exists(json_root_node, \"ata_sct_erc/read/enabled\").value_or(false)) {\n\t\tlines.emplace_back(fmt::format(\"SCT error recovery control (read): {}, {:.2f} seconds\",\n\t\t\t\t(get_node_data<bool>(json_root_node, \"ata_sct_erc/read/enabled\").value_or(false) ? \"enabled\" : \"disabled\"),\n\t\t\t\tget_node_data<double>(json_root_node, \"ata_sct_erc/read/deciseconds\").value_or(0.) / 10.));\n\t}\n\tif (get_node_exists(json_root_node, \"ata_sct_erc/write/enabled\").value_or(false)) {\n\t\tlines.emplace_back(fmt::format(\"SCT error recovery control (write): {}, {:.2f} seconds\",\n\t\t\t\t(get_node_data<bool>(json_root_node, \"ata_sct_erc/write/enabled\").value_or(false) ? \"enabled\" : \"disabled\"),\n\t\t\t\tget_node_data<double>(json_root_node, \"ata_sct_erc/write/deciseconds\").value_or(0.) / 10.));\n\t}\n\n\t// The whole section\n\tif (!lines.empty()) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"ata_sct_erc/_merged\", _(\"SCT error recovery log\"));\n\t\tp.section = StoragePropertySection::ErcLog;\n\t\tp.reported_value = hz::string_join(lines, \"\\n\");\n\t\tp.value = p.reported_value;  // string-type value\n\t\tadd_property(p);\n\n\t\tsection_properties_found = true;\n\t}\n\n\tif (!section_properties_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection,\n\t\t\t\tfmt::format(\"No section {} parsed.\", StoragePropertySectionExt::get_displayable_name(StoragePropertySection::ErcLog)));\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse_section_devstat(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\n\tbool section_properties_found = false;\n\n\tconst std::string pages_key = \"ata_device_statistics/pages\";\n\tauto page_node = get_node(json_root_node, pages_key);\n\n\t// Entries\n\tif (page_node.has_value() && page_node->is_array()) {\n\t\tfor (const auto& page_entry : page_node.value()) {\n\t\t\tAtaStorageStatistic page_stat;\n\t\t\tpage_stat.is_header = true;\n\t\t\tpage_stat.page = get_node_data<int64_t>(page_entry, \"number\").value_or(0);\n\n\t\t\tStorageProperty page_prop;\n\t\t\t{\n\t\t\t\tconst std::string gen_name = get_node_data<std::string>(page_entry, \"name\").value_or(std::string());\n\t\t\t\tconst std::string disp_name = gen_name;  // TODO: Translate\n\t\t\t\tpage_prop.set_name(gen_name, disp_name);\n\t\t\t\tpage_prop.section = StoragePropertySection::Statistics;\n\t\t\t\tpage_prop.value = page_stat;\n\t\t\t}\n\t\t\tadd_property(page_prop);\n\n\t\t\tconst std::string table_key = \"table\";\n\t\t\tauto table_node = get_node(page_entry, table_key);\n\n\t\t\tif (table_node.has_value() && table_node->is_array()) {\n\t\t\t\tfor (const auto& table_entry : table_node.value()) {\n\t\t\t\t\tAtaStorageStatistic s;\n\t\t\t\t\ts.page = page_stat.page;\n\t\t\t\t\ts.flags = get_node_data<std::string>(table_entry, \"flags/string\").value_or(std::string());\n\t\t\t\t\ts.value_int = get_node_data<int64_t>(table_entry, \"value\").value_or(0);\n\t\t\t\t\ts.value = std::to_string(get_node_data<int64_t>(table_entry, \"value\").value_or(0));\n\t\t\t\t\ts.offset = get_node_data<int64_t>(table_entry, \"offset\").value_or(0);\n\n\t\t\t\t\tStorageProperty p;\n\t\t\t\t\tconst std::string gen_name = get_node_data<std::string>(table_entry, \"name\").value_or(std::string());\n\t\t\t\t\tp.set_name(gen_name, gen_name, gen_name);  // The description database will correct this.\n\t\t\t\t\tp.section = StoragePropertySection::Statistics;\n\t\t\t\t\tp.value = s;\n\t\t\t\t\tadd_property(p);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tsection_properties_found = true;\n\t\t}\n\t}\n\n\tif (!section_properties_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection,\n\t\t\t\tfmt::format(\"No section {} parsed.\", StoragePropertySectionExt::get_displayable_name(StoragePropertySection::Statistics)));\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonAtaParser::parse_section_sataphy(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\tusing namespace std::string_literals;\n\n\tbool section_properties_found = false;\n\n\tstd::vector<std::string> lines;\n\n\t// Table\n\tconst std::string table_key = \"sata_phy_event_counters/table\";\n\tauto table_node = get_node(json_root_node, table_key);\n\n\t// Entries\n\tif (table_node.has_value() && table_node->is_array()) {\n\t\tfor (const auto& table_entry : table_node.value()) {\n\t\t\tconst uint64_t id = get_node_data<uint64_t>(table_entry, \"id\").value_or(0);\n\t\t\tconst std::string name = get_node_data<std::string>(table_entry, \"name\").value_or(std::string());\n\t\t\tconst uint64_t size = get_node_data<uint64_t>(table_entry, \"size\").value_or(0);\n\t\t\tconst int64_t value = get_node_data<int64_t>(table_entry, \"value\").value_or(0);\n//\t\t\tconst bool overflow = get_node_data<bool>(table_entry, \"overflow\").value_or(false);\n\n\t\t\tlines.emplace_back(fmt::format(\n\t\t\t\t\t\"ID: 0x{:04X}    Size: {:8}    Value: {:20}    Description: {}\",\n\t\t\t\t\tid,\n\t\t\t\t\tsize,\n\t\t\t\t\tvalue,\n\t\t\t\t\tname));\n\t\t}\n\n\t\t// The whole section\n\t\t{\n\t\t\tStorageProperty p;\n\t\t\tp.set_name(\"sata_phy_event_counters/_merged\", _(\"SATA Phy Log\"));\n\t\t\tp.section = StoragePropertySection::PhyLog;\n\t\t\tp.reported_value = hz::string_join(lines, \"\\n\");\n\t\t\tp.value = p.reported_value;  // string-type value\n\n\t\t\tadd_property(p);\n\t\t}\n\n\t\tsection_properties_found = true;\n\t}\n\n\tif (!section_properties_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection,\n\t\t\t\tfmt::format(\"No section {} parsed.\", StoragePropertySectionExt::get_displayable_name(StoragePropertySection::PhyLog)));\n\t}\n\n\treturn {};\n}\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_json_ata_parser.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2022 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef SMARTCTL_JSON_ATA_PARSER_H\n#define SMARTCTL_JSON_ATA_PARSER_H\n\n#include \"smartctl_parser.h\"\n\n#include <string_view>\n\n#include \"nlohmann/json.hpp\"\n\n#include \"hz/error_container.h\"\n#include \"smartctl_parser_types.h\"\n\n\n\n/// Smartctl (S)ATA JSON output parser\nclass SmartctlJsonAtaParser : public SmartctlParser {\n\tpublic:\n\n\t\t// Defaulted, used by make_unique.\n\t\tSmartctlJsonAtaParser() = default;\n\n\t\t// Overridden\n\t\t[[nodiscard]] hz::ExpectedVoid<SmartctlParserError> parse(std::string_view smartctl_output) override;\n\n\tprivate:\n\n\t\t/// Parse the info section (root node), filling in the properties\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_info(const nlohmann::json& json_root_node);\n\n\t\t/// Parse the health section (root node), filling in the properties\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_health(const nlohmann::json& json_root_node);\n\t\t\n\t\t/// Parse a section from json data\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_capabilities(const nlohmann::json& json_root_node);\n\n\t\t/// Parse a section from json data\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_attributes(const nlohmann::json& json_root_node);\n\n\t\t/// Parse a section from json data\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_directory_log(const nlohmann::json& json_root_node);\n\n\t\t/// Parse a section from json data\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_error_log(const nlohmann::json& json_root_node);\n\n\t\t/// Parse a section from json data\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_selftest_log(const nlohmann::json& json_root_node);\n\n\t\t/// Parse a section from json data\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_selective_selftest_log(const nlohmann::json& json_root_node);\n\n\t\t/// Parse a section from json data\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_scttemp_log(const nlohmann::json& json_root_node);\n\n\t\t/// Parse a section from json data\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_scterc_log(const nlohmann::json& json_root_node);\n\n\t\t/// Parse a section from json data\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_devstat(const nlohmann::json& json_root_node);\n\n\t\t/// Parse a section from json data\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_sataphy(const nlohmann::json& json_root_node);\n\n\n};\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_json_basic_parser.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include \"smartctl_json_basic_parser.h\"\n\n// #include <glibmm.h>\n//#include <clocale>  // localeconv\n#include <cstdint>\n#include <string_view>\n#include <tuple>\n#include <vector>\n\n#include \"fmt/format.h\"\n// #include \"hz/locale_tools.h\"  // ScopedCLocale, locale_c_get().\n#include \"hz/format_unit.h\"\n#include \"hz/string_algo.h\"  // string_*\n#include \"hz/string_num.h\"  // string_is_numeric, number_to_string\n//#include \"hz/debug.h\"  // debug_*\n#include \"nlohmann/json.hpp\"\n#include \"hz/error_container.h\"\n\n//#include \"smartctl_text_ata_parser.h\"\n//#include \"ata_storage_property_descr.h\"\n#include \"storage_property.h\"\n#include \"smartctl_json_parser_helpers.h\"\n#include \"smartctl_parser_types.h\"\n//#include \"smartctl_version_parser.h\"\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonBasicParser::parse(std::string_view smartctl_output)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\n\tif (hz::string_trim_copy(smartctl_output).empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Empty string passed as an argument. Returning.\\n\");\n\t\treturn hz::Unexpected(SmartctlParserError::EmptyInput, \"Smartctl data is empty.\");\n\t}\n\n\tnlohmann::json json_root_node;\n\ttry {\n\t\tjson_root_node = nlohmann::json::parse(smartctl_output);\n\t} catch (const nlohmann::json::parse_error& e) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Error parsing smartctl output as JSON: \" << e.what() << \"\\n\");\n\t\treturn hz::Unexpected(SmartctlParserError::SyntaxError, std::string(\"Invalid JSON data: \") + e.what());\n\t}\n\n\tStorageProperty merged_property, full_property;\n\tauto version_parse_status = SmartctlJsonParserHelpers::parse_version(json_root_node, merged_property, full_property);\n\tif (!version_parse_status) {\n\t\treturn version_parse_status;\n\t}\n\tadd_property(merged_property);\n\tadd_property(full_property);\n\n\treturn parse_section_basic_info(json_root_node);\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonBasicParser::parse_section_basic_info(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\n\t// Here we list the properties that are:\n\t// 1. Essential for all devices, due to them being used in StorageDevice.\n\t// 2. Present in devices for which we do not have specialized parsers (USB, etc.)\n\tstatic const std::vector<std::tuple<std::string, std::string, PropertyRetrievalFunc>> info_keys = {\n\n\t\t\t{\"smartctl/output\", _(\"Smartctl Text Output\"),  // the old text format\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tauto table_node = get_node(root_node, \"smartctl/output\");\n\t\t\t\t\tif (table_node.has_value() && table_node->is_array() && !table_node.value().empty()) {\n\t\t\t\t\t\tstd::vector<std::string> lines;\n\t\t\t\t\t\tfor (const auto& entry : table_node.value()) {\n\t\t\t\t\t\t\tlines.emplace_back(entry.get<std::string>());\n\t\t\t\t\t\t}\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = hz::string_join(lines, \"\\n\");\n\t\t\t\t\t\tp.show_in_ui = false;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"device/type\", _(\"Smartctl Device Type\"),  // nvme, sat, etc.\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tif (auto jval = get_node_data<std::string>(root_node, \"device/type\"); jval.has_value()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = jval.value();\n\t\t\t\t\t\tp.show_in_ui = false;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"device/protocol\", _(\"Smartctl Device Protocol\"),  // NVMe, ...\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tif (auto jval = get_node_data<std::string>(root_node, \"device/protocol\"); jval.has_value()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = jval.value();\n\t\t\t\t\t\tp.show_in_ui = false;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"vendor\", _(\"Vendor\"), string_formatter()},  // Flash drive\n\t\t\t{\"scsi_vendor\", _(\"Vendor\"), string_formatter()},  // Flash drive\n\n\t\t\t{\"product\", _(\"Product\"), string_formatter()},  // Flash drive\n\t\t\t{\"scsi_product\", _(\"Product\"), string_formatter()},  // Flash drive\n\n\t\t\t{\"model_family\", _(\"Model Family\"), string_formatter()},  // (S)ATA\n\n\t\t\t{\"model_name\", _(\"Device Model\"), string_formatter()},\n\t\t\t{\"scsi_model_name\", _(\"Device Model\"), string_formatter()},  // Flash drive\n\n\t\t\t{\"revision\", _(\"Revision\"), string_formatter()},  // Flash drive\n\t\t\t{\"scsi_revision\", _(\"Revision\"), string_formatter()},  // Flash drive\n\n\t\t\t{\"scsi_version\", _(\"SCSI Version\"), string_formatter()},  // Flash drive\n\n\t\t\t{\"user_capacity/bytes\", _(\"Capacity\"),\n\t\t\t\tcustom_string_formatter<int64_t>([](int64_t value)\n\t\t\t\t{\n\t\t\t\t\treturn fmt::format(\"{} [{}; {} bytes]\",\n\t\t\t\t\t\thz::format_size(static_cast<uint64_t>(value), true),\n\t\t\t\t\t\thz::format_size(static_cast<uint64_t>(value), false),\n\t\t\t\t\t\thz::number_to_string_locale(value));\n\t\t\t\t})\n\t\t\t},\n\n\t\t\t{\"user_capacity/bytes/_short\", _(\"Capacity\"),\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tif (auto jval = get_node_data<int64_t>(root_node, \"user_capacity/bytes\"); jval) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.readable_value = hz::format_size(static_cast<uint64_t>(jval.value()), true);\n\t\t\t\t\t\tp.value = jval.value();\n\t\t\t\t\t\tp.show_in_ui = false;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", \"user_capacity/bytes\"));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"physical_block_size/_and/logical_block_size\", _(\"Sector Size\"),\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tstd::vector<std::string> values;\n\t\t\t\t\tif (auto jval1 = get_node_data<int64_t>(root_node, \"logical_block_size\"); jval1) {\n\t\t\t\t\t\tvalues.emplace_back(fmt::format(\"{} bytes logical\", jval1.value()));\n\t\t\t\t\t}\n\t\t\t\t\tif (auto jval2 = get_node_data<int64_t>(root_node, \"physical_block_size\"); jval2) {\n\t\t\t\t\t\tvalues.emplace_back(fmt::format(\"{} bytes physical\", jval2.value()));\n\t\t\t\t\t}\n\t\t\t\t\tif (!values.empty()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.readable_value = hz::string_join(values, \", \");\n\t\t\t\t\t\tp.value = p.readable_value;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"serial_number\", _(\"Serial Number\"), string_formatter()},\n\t\t\t{\"firmware_version\", _(\"Firmware Version\"), string_formatter()},\n\t\t\t{\"trim/supported\", _(\"TRIM Supported\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"in_smartctl_database\", _(\"In Smartctl Database\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"smartctl/drive_database_version/string\", _(\"Smartctl Database Version\"), string_formatter()},\n\t\t\t{\"ata_version/string\", _(\"ATA Version\"), string_formatter()},\n\t\t\t{\"sata_version/string\", _(\"SATA Version\"), string_formatter()},\n\n\t\t\t{\"interface_speed/_merged\", _(\"Interface Speed\"),\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tstd::vector<std::string> values;\n\t\t\t\t\tif (auto jval1 = get_node_data<std::string>(root_node, \"interface_speed/max/string\"); jval1) {\n\t\t\t\t\t\tvalues.emplace_back(fmt::format(\"Max: {}\", jval1.value()));\n\t\t\t\t\t}\n\t\t\t\t\tif (auto jval2 = get_node_data<std::string>(root_node, \"interface_speed/current/string\"); jval2) {\n\t\t\t\t\t\tvalues.emplace_back(fmt::format(\"Current: {}\", jval2.value()));\n\t\t\t\t\t}\n\t\t\t\t\tif (!values.empty()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.readable_value = hz::string_join(values, \", \");\n\t\t\t\t\t\tp.value = p.readable_value;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"local_time/asctime\", _(\"Scanned on\"), string_formatter()},\n\n\t\t\t{\"smart_support/available\", _(\"SMART Supported\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"smart_support/enabled\", _(\"SMART Enabled\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\n\t\t\t{\"smart_status/passed\", _(\"Overall Health Self-Assessment Test\"), bool_formatter(_(\"PASSED\"), _(\"FAILED\"))},\n\n\t\t\t// (S)ATA, used to detect HDD vs SSD\n\t\t\t{\"rotation_rate\", _(\"Rotation Rate\"), integer_formatter<int64_t>(\"{} RPM\")},\n\n\t\t\t{\"form_factor/name\", _(\"Form Factor\"), string_formatter()},\n\t};\n\n\tfor (const auto& [key, displayable_name, retrieval_func] : info_keys) {\n\t\tDBG_ASSERT(retrieval_func != nullptr);\n\n\t\tauto p = retrieval_func(json_root_node, key, displayable_name);\n\t\tif (p.has_value()) {  // ignore if not found\n\t\t\tif (p->generic_name == \"smart_status/passed\") {\n\t\t\t\tp->section = StoragePropertySection::OverallHealth;\n\t\t\t} else {\n\t\t\t\tp->section = StoragePropertySection::Info;\n\t\t\t}\n\t\t\tadd_property(p.value());\n\t\t}\n\t}\n\n\treturn {};\n}\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_json_basic_parser.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef SMARTCTL_JSON_BASIC_PARSER_H\n#define SMARTCTL_JSON_BASIC_PARSER_H\n\n//#include <string>\n//#include <vector>\n\n#include \"nlohmann/json.hpp\"\n\n#include \"smartctl_parser.h\"\n\n\n\n/// Parse info output, regardless of device type\nclass SmartctlJsonBasicParser : public SmartctlParser {\n\tpublic:\n\n\t\t// Defaulted, used by make_unique.\n\t\tSmartctlJsonBasicParser() = default;\n\n\t\t// Overridden\n\t\t[[nodiscard]] hz::ExpectedVoid<SmartctlParserError> parse(std::string_view smartctl_output) override;\n\n\n\tprivate:\n\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_basic_info(const nlohmann::json& json_root_node);\n\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_json_nvme_parser.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include \"smartctl_json_nvme_parser.h\"\n\n#include <cstdint>\n#include <optional>\n#include <string>\n#include <string_view>\n#include <tuple>\n#include <vector>\n#include <chrono>\n\n#include \"fmt/format.h\"\n#include \"nlohmann/json.hpp\"\n\n#include \"storage_property.h\"\n#include \"hz/debug.h\"\n#include \"hz/string_algo.h\"\n// #include \"smartctl_version_parser.h\"\n#include \"hz/format_unit.h\"\n#include \"hz/error_container.h\"\n#include \"hz/string_num.h\"\n#include \"smartctl_json_parser_helpers.h\"\n#include \"smartctl_parser_types.h\"\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonNvmeParser::parse(std::string_view smartctl_output)\n{\n\tif (hz::string_trim_copy(smartctl_output).empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Empty string passed as an argument. Returning.\\n\");\n\t\treturn hz::Unexpected(SmartctlParserError::EmptyInput, \"Smartctl data is empty.\");\n\t}\n\n\tnlohmann::json json_root_node;\n\ttry {\n\t\tjson_root_node = nlohmann::json::parse(smartctl_output);\n\t} catch (const nlohmann::json::parse_error& e) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Error parsing smartctl output as JSON: \" << e.what() << \"\\n\");\n\t\treturn hz::Unexpected(SmartctlParserError::SyntaxError, std::string(\"Invalid JSON data: \") + e.what());\n\t}\n\n\tStorageProperty merged_property, full_property;\n\tauto version_parse_status = SmartctlJsonParserHelpers::parse_version(json_root_node, merged_property, full_property);\n\tif (!version_parse_status) {\n\t\treturn version_parse_status;\n\t}\n\tadd_property(merged_property);\n\tadd_property(full_property);\n\n\t// Info must be supported.\n\tauto info_parse_status = parse_section_info(json_root_node);\n\tif (!info_parse_status) {\n\t\treturn info_parse_status;\n\t}\n\n\t// Add properties for each parsed section so that the UI knows which tabs to show or hide\n\t{\n\t\tauto section_parse_status = parse_section_overall_health(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::Health;\n//\t\tp.set_name(\"_parser/health_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\t{\n\t\tauto section_parse_status = parse_section_nvme_health(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::Health;\n//\t\tp.set_name(\"_parser/health_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\t{\n\t\tauto section_parse_status = parse_section_nvme_error_log(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::ErrorLog;\n//\t\tp.set_name(\"_parser/error_log_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\t{\n\t\tauto section_parse_status = parse_section_selftest_log(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::SelftestLog;\n//\t\tp.set_name(\"_parser/selftest_log_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\t{\n\t\tauto section_parse_status = parse_section_nvme_attributes(json_root_node);\n//\t\tStorageProperty p;\n//\t\tp.section = StoragePropertySection::Devstat;\n//\t\tp.set_name(\"_parser/devstat_section_available\");\n//\t\tp.value = section_parse_status.has_value() || section_parse_status.error().data() != SmartctlParserError::NoSection;\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonNvmeParser::parse_section_info(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\n\t// This is very similar to Basic Parser, but the Basic Parser supports different drive types, while this\n\t// one is only for ATA.\n\n\tstatic const std::vector<std::tuple<std::string, std::string, PropertyRetrievalFunc>> json_keys = {\n\n\t\t\t{\"smartctl/output\", _(\"Smartctl Text Output\"),  // the old text format\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tauto table_node = get_node(root_node, \"smartctl/output\");\n\t\t\t\t\tif (table_node.has_value() && table_node->is_array() && !table_node.value().empty()) {\n\t\t\t\t\t\tstd::vector<std::string> lines;\n\t\t\t\t\t\tfor (const auto& entry : table_node.value()) {\n\t\t\t\t\t\t\tlines.emplace_back(entry.get<std::string>());\n\t\t\t\t\t\t}\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = hz::string_join(lines, \"\\n\");\n\t\t\t\t\t\tp.show_in_ui = false;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"device/type\", _(\"Smartctl Device Type\"),  // nvme, sat, etc.\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tif (auto jval = get_node_data<std::string>(root_node, \"device/type\"); jval.has_value()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = jval.value();\n\t\t\t\t\t\tp.show_in_ui = false;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"device/protocol\", _(\"Smartctl Device Protocol\"),  // NVMe, ...\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tif (auto jval = get_node_data<std::string>(root_node, \"device/protocol\"); jval.has_value()) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.value = jval.value();\n\t\t\t\t\t\tp.show_in_ui = false;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"model_name\", _(\"Device Model\"), string_formatter()},\n\t\t\t{\"serial_number\", _(\"Serial Number\"), string_formatter()},\n\t\t\t{\"firmware_version\", _(\"Firmware Version\"), string_formatter()},\n\n\t\t\t{\"nvme_total_capacity\", _(\"Total Capacity\"),\n\t\t\t\tcustom_string_formatter<int64_t>([](int64_t value)\n\t\t\t\t{\n\t\t\t\t\treturn fmt::format(\"{} [{}; {} bytes]\",\n\t\t\t\t\t\thz::format_size(static_cast<uint64_t>(value), true),\n\t\t\t\t\t\thz::format_size(static_cast<uint64_t>(value), false),\n\t\t\t\t\t\thz::number_to_string_locale(value));\n\t\t\t\t})\n\t\t\t},\n\n\t\t\t{\"nvme_unallocated_capacity\", _(\"Unallocated Capacity\"),\n\t\t\t\tcustom_string_formatter<int64_t>([](int64_t value)\n\t\t\t\t{\n\t\t\t\t\treturn fmt::format(\"{} [{}; {} bytes]\",\n\t\t\t\t\t\thz::format_size(static_cast<uint64_t>(value), true),\n\t\t\t\t\t\thz::format_size(static_cast<uint64_t>(value), false),\n\t\t\t\t\t\thz::number_to_string_locale(value));\n\t\t\t\t})\n\t\t\t},\n\n\t\t\t{\"user_capacity/bytes\", _(\"Capacity\"),\n\t\t\t\tcustom_string_formatter<int64_t>([](int64_t value)\n\t\t\t\t{\n\t\t\t\t\treturn fmt::format(\"{} [{}; {} bytes]\",\n\t\t\t\t\t\thz::format_size(static_cast<uint64_t>(value), true),\n\t\t\t\t\t\thz::format_size(static_cast<uint64_t>(value), false),\n\t\t\t\t\t\thz::number_to_string_locale(value));\n\t\t\t\t})\n\t\t\t},\n\n\t\t\t{\"user_capacity/bytes/_short\", _(\"Capacity\"),\n\t\t\t\t[](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t\t\t\t{\n\t\t\t\t\tif (auto jval = get_node_data<int64_t>(root_node, \"user_capacity/bytes\"); jval) {\n\t\t\t\t\t\tStorageProperty p;\n\t\t\t\t\t\tp.set_name(key, displayable_name);\n\t\t\t\t\t\tp.readable_value = hz::format_size(static_cast<uint64_t>(jval.value()), true);\n\t\t\t\t\t\tp.value = jval.value();\n\t\t\t\t\t\tp.show_in_ui = false;\n\t\t\t\t\t\treturn p;\n\t\t\t\t\t}\n\t\t\t\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", \"user_capacity/bytes\"));\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t{\"logical_block_size\", _(\"Logical Block Size\"), integer_formatter<int64_t>(\"{} bytes\")},\n\t\t\t{\"power_cycle_count\", _(\"Number of Power Cycles\"), integer_formatter<int64_t>()},\n\t\t\t{\"power_on_time/hours\", _(\"Powered for\"), integer_formatter<int64_t>(\"{} hours\")},\n\t\t\t{\"temperature/current\", _(\"Current Temperature\"), integer_formatter<int64_t>(\"{}° Celsius\")},\n\n\t\t\t{\"nvme_version/string\", _(\"NVMe Version\"), string_formatter()},\n\t\t\t{\"local_time/asctime\", _(\"Scanned on\"), string_formatter()},\n\n\t\t\t{\"smart_support/available\", _(\"SMART Supported\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"smart_support/enabled\", _(\"SMART Enabled\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t};\n\n\tbool any_found = false;\n\tfor (const auto& [key, displayable_name, retrieval_func] : json_keys) {\n\t\tDBG_ASSERT(retrieval_func != nullptr);\n\n\t\tauto p = retrieval_func(json_root_node, key, displayable_name);\n\t\tif (p.has_value()) {  // ignore if not found\n\t\t\tp->section = StoragePropertySection::Info;\n\t\t\tadd_property(p.value());\n\t\t\tany_found = true;\n\t\t}\n\t}\n\n\tif (!any_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, \"No keys info found in JSON data.\");\n\t}\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonNvmeParser::parse_section_overall_health(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\n\tbool section_properties_found = false;\n\n\tconst std::vector<std::tuple<std::string, std::string, PropertyRetrievalFunc>> health_keys = {\n\t\t\t{\"smart_status/passed\", _(\"Overall Health Self-Assessment Test\"), bool_formatter(_(\"PASSED\"), _(\"FAILED\"))},\n\t};\n\n\tfor (const auto& [key, displayable_name, retrieval_func] : health_keys) {\n\t\tDBG_ASSERT(retrieval_func != nullptr);\n\n\t\tauto p = retrieval_func(json_root_node, key, displayable_name);\n\t\tif (p.has_value()) {  // ignore if not found\n\t\t\tp->section = StoragePropertySection::OverallHealth;\n\t\t\tadd_property(p.value());\n\n\t\t\tsection_properties_found = true;\n\t\t}\n\t}\n\n\tif (!section_properties_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection,\n\t\t\t\tfmt::format(\"No section {} parsed.\", StoragePropertySectionExt::get_displayable_name(StoragePropertySection::OverallHealth)));\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonNvmeParser::parse_section_nvme_health(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\n\tbool section_properties_found = false;\n\n\tconst std::vector<std::tuple<std::string, std::string, PropertyRetrievalFunc>> health_keys = {\n\t\t\t// These are included when smart_status/passed is false\n\t\t\t{\"smart_status/nvme/spare_below_threshold\", _(\"Available Spare Fallen Below Threshold\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"smart_status/nvme/temperature_above_or_below_threshold\", _(\"Temperature Outside Limits\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"smart_status/nvme/reliability_degraded\", _(\"NVM Subsystem Reliability Degraded\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"smart_status/nvme/media_read_only\", _(\"Media Placed in Read-Only Mode\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"smart_status/nvme/volatile_memory_backup_failed\", _(\"Volatile Memory Backup Failed\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"smart_status/nvme/persistent_memory_region_unreliable\", _(\"Persistent Memory Region Is Read-Only or Unreliable\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t\t\t{\"smart_status/nvme/other\", _(\"Unknown Critical Warnings\"), bool_formatter(_(\"Yes\"), _(\"No\"))},\n\t};\n\n\tfor (const auto& [key, displayable_name, retrieval_func] : health_keys) {\n\t\tDBG_ASSERT(retrieval_func != nullptr);\n\n\t\tauto p = retrieval_func(json_root_node, key, displayable_name);\n\t\tif (p.has_value()) {  // ignore if not found\n\t\t\tp->section = StoragePropertySection::NvmeHealth;\n\t\t\tadd_property(p.value());\n\n\t\t\tsection_properties_found = true;\n\t\t}\n\t}\n\n\tif (!section_properties_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection,\n\t\t\t\tfmt::format(\"No section {} parsed.\", StoragePropertySectionExt::get_displayable_name(StoragePropertySection::NvmeHealth)));\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonNvmeParser::parse_section_nvme_error_log(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\n\tbool section_properties_found = false;\n\n\t// NOTE: nvme_error_information_log is not persistent across resets / restarts.\n\n\tstd::vector<std::string> lines;\n\n\tif (get_node_exists(json_root_node, \"nvme_error_information_log/size\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"nvme_error_information_log/size\", _(\"Non-Persistent Error Log Size\"));\n\t\tp.section = StoragePropertySection::NvmeErrorLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"nvme_error_information_log/size\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Non-Persistent Error Log Size: {}\", p.get_value<int64_t>()));\n\t\tsection_properties_found = true;\n\t}\n\tif (get_node_exists(json_root_node, \"nvme_error_information_log/read\").value_or(false)) {\n\t\tStorageProperty p;\n\t\t// Note: This number can be controlled using smartctl option.\n\t\tp.set_name(\"nvme_error_information_log/read\", _(\"Number of Error Log Entries Read\"));\n\t\tp.section = StoragePropertySection::NvmeErrorLog;\n\t\tp.value = get_node_data<int64_t>(json_root_node, \"nvme_error_information_log/size\").value_or(0);\n\t\tadd_property(p);\n\n\t\tlines.emplace_back(fmt::format(\"Number of Error Log Entries Read: {}\", p.get_value<int64_t>()));\n\t\tsection_properties_found = true;\n\t}\n\n\t// Table\n\tconst std::string table_key = \"nvme_error_information_log/table\";\n\tauto table_node = get_node(json_root_node, table_key);\n\n\t// Entries\n\tif (table_node.has_value() && table_node->is_array()) {\n\t\tlines.emplace_back();\n\n\t\tfor (const auto& table_entry : table_node.value()) {\n\t\t\tconst uint64_t error_count = get_node_data<uint64_t>(table_entry, \"error_count\").value_or(0);\n\t\t\tconst uint64_t command_id = get_node_data<uint64_t>(table_entry, \"command_id\").value_or(0);\n\t\t\tconst std::string status_str = get_node_data<std::string>(table_entry, \"status_field/string\").value_or(std::string());\n\t\t\tconst uint64_t lba = get_node_data<uint64_t>(table_entry, \"lba/value\").value_or(0);\n\n\t\t\t// Error #, Command ID, LBA, Status\n\t\t\tlines.emplace_back(fmt::format(\n\t\t\t\t\t\"Error {:3}    Command ID: {:04X}    LBA: {:020}    {}\",\n\t\t\t\t\terror_count,\n\t\t\t\t\tcommand_id,\n\t\t\t\t\tlba,\n\t\t\t\t\tstatus_str));\n\t\t}\n\n\t\tsection_properties_found = true;\n\t}\n\n\t// The whole section\n\tif (!lines.empty()) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"nvme_error_information_log/_merged\", _(\"NVMe Non-Persistent Error Information Log\"));\n\t\tp.section = StoragePropertySection::NvmeErrorLog;\n\t\tp.reported_value = hz::string_join(lines, \"\\n\");\n\t\tp.value = p.reported_value;  // string-type value\n\n\t\tadd_property(p);\n\t}\n\n\tif (!section_properties_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection,\n\t\t\t\tfmt::format(\"No section {} parsed.\", StoragePropertySectionExt::get_displayable_name(StoragePropertySection::NvmeErrorLog)));\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonNvmeParser::parse_section_selftest_log(const nlohmann::json& json_root_node)\n{\n\t// nvme_self_test_log\n\n\tusing namespace SmartctlJsonParserHelpers;\n\n\tbool section_properties_found = false;\n\n\t// If nvme_self_test_log is present, the drive supports tests.\n\t// Create this property only if supported, so that the UI can hide the tab if not needed.\n\tif (get_node_exists(json_root_node, \"nvme_self_test_log\").value_or(false)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"nvme_self_test_log/_exists\", _(\"Self-tests supported\"));\n\t\tp.section = StoragePropertySection::SelftestLog;\n\t\tp.value = true;\n\t\tadd_property(p);\n\t}\n\n\t{\n\t\tStorageProperty p;\n\t\tp.set_name(\"nvme_self_test_log/current_self_test_operation/value/_decoded\", _(\"Current Self-Test Operation\"));\n\t\tp.section = StoragePropertySection::SelftestLog;\n\n\t\tauto value_val = get_node_data<uint8_t>(json_root_node, \"nvme_self_test_log/current_self_test_operation/value\");\n\t\tNvmeSelfTestCurrentOperationType operation = NvmeSelfTestCurrentOperationType::Unknown;\n\t\tif (value_val.has_value()) {\n\t\t\tswitch (value_val.value()) {\n\t\t\t\t// Data from smartmontools/nvmeprint.cpp\n\t\t\t\tcase 0x0: operation = NvmeSelfTestCurrentOperationType::None; break;\n\t\t\t\tcase 0x1: operation = NvmeSelfTestCurrentOperationType::ShortInProgress; break;\n\t\t\t\tcase 0x2: operation = NvmeSelfTestCurrentOperationType::ExtendedInProgress; break;\n\t\t\t\tcase 0xe: operation = NvmeSelfTestCurrentOperationType::VendorSpecificInProgress; break;\n\t\t\t\tdefault: break;  // Unknown\n\t\t\t}\n\t\t\tp.value = NvmeSelfTestCurrentOperationTypeExt::get_storable_name(operation);\n\t\t\tp.readable_value = NvmeSelfTestCurrentOperationTypeExt::get_displayable_name(operation);\n\t\t\tadd_property(p);\n\n\t\t\tsection_properties_found = true;\n\t\t}\n\t}\n\n\t{\n\t\tStorageProperty p;\n\t\tp.set_name(\"nvme_self_test_log/current_self_test_completion_percent\", _(\"Current Self-Test Completion Percentage\"));\n\t\tp.section = StoragePropertySection::SelftestLog;\n\n\t\tauto value_val = get_node_data<uint8_t>(json_root_node, \"nvme_self_test_log/current_self_test_completion_percent\");\n\t\tif (value_val.has_value()) {\n\t\t\tp.value = value_val.value();\n\t\t\tp.readable_value = fmt::format(\"{} %\", value_val.value());\n\t\t\tadd_property(p);\n\t\t}\n\t}\n\n\tconst std::string table_key = \"nvme_self_test_log/table\";\n\tauto table_node = get_node(json_root_node, table_key);\n\n\t// Entries\n\tif (table_node.has_value() && table_node->is_array()) {\n\t\tuint32_t entry_num = 1;\n\t\tfor (const auto& table_entry : table_node.value()) {\n\t\t\tNvmeStorageSelftestEntry entry;\n\t\t\tentry.test_num = entry_num;\n\n\t\t\tNvmeSelfTestType test_type = NvmeSelfTestType::Unknown;\n\t\t\tif (get_node_exists(table_entry, \"self_test_code/value\").value_or(false)) {\n\t\t\t\tconst int32_t type_value = get_node_data<int32_t>(table_entry, \"self_test_code/value\").value_or(int(NvmeSelfTestType::Unknown));\n\t\t\t\tswitch(type_value) {\n\t\t\t\t\tcase 0x1: test_type = NvmeSelfTestType::Short; break;\n\t\t\t\t\tcase 0x2: test_type = NvmeSelfTestType::Extended; break;\n\t\t\t\t\tcase 0xe: test_type = NvmeSelfTestType::VendorSpecific; break;\n\t\t\t\t\tdefault: break;  // Unknown\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tNvmeSelfTestResultType test_result = NvmeSelfTestResultType::Unknown;\n\t\t\tif (get_node_exists(table_entry, \"self_test_result/value\").value_or(false)) {\n\t\t\t\tconst int32_t type_value = get_node_data<int32_t>(table_entry, \"self_test_result/value\").value_or(int(NvmeSelfTestType::Unknown));\n\t\t\t\tswitch(type_value) {\n\t\t\t\t\tcase 0x0: test_result = NvmeSelfTestResultType::CompletedNoError; break;\n\t\t\t\t\tcase 0x1: test_result = NvmeSelfTestResultType::AbortedSelfTestCommand; break;\n\t\t\t\t\tcase 0x2: test_result = NvmeSelfTestResultType::AbortedControllerReset; break;\n\t\t\t\t\tcase 0x3: test_result = NvmeSelfTestResultType::AbortedNamespaceRemoved; break;\n\t\t\t\t\tcase 0x4: test_result = NvmeSelfTestResultType::AbortedFormatNvmCommand; break;\n\t\t\t\t\tcase 0x5: test_result = NvmeSelfTestResultType::FatalOrUnknownTestError; break;\n\t\t\t\t\tcase 0x6: test_result = NvmeSelfTestResultType::CompletedUnknownFailedSegment; break;\n\t\t\t\t\tcase 0x7: test_result = NvmeSelfTestResultType::CompletedFailedSegments; break;\n\t\t\t\t\tcase 0x8: test_result = NvmeSelfTestResultType::AbortedUnknownReason; break;\n\t\t\t\t\tcase 0x9: test_result = NvmeSelfTestResultType::AbortedSanitizeOperation; break;\n\t\t\t\t\tdefault: break;  // Unknown\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tentry.type = test_type;\n\t\t\tentry.result = test_result;\n\t\t\tentry.power_on_hours = get_node_data<uint32_t>(table_entry, \"power_on_hours\").value_or(0);\n\t\t\tif (get_node_exists(table_entry, \"lba\").value_or(false)) {  // optional\n\t\t\t\tentry.lba = get_node_data<uint64_t>(table_entry, \"lba\").value();\n\t\t\t}\n\n\t\t\tStorageProperty p;\n\t\t\tstd::string gen_name = fmt::format(\"{}/{}\", table_key, entry_num);\n\t\t\tstd::string disp_name = fmt::format(\"Self-test entry {}\", entry.test_num);\n\t\t\tp.set_name(gen_name, disp_name);\n\t\t\tp.section = StoragePropertySection::SelftestLog;\n\t\t\tp.value = entry;\n\t\t\tadd_property(p);\n\n\t\t\t++entry_num;\n\t\t}\n\n\t\tsection_properties_found = true;\n\t}\n\n\tif (!section_properties_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection,\n\t\t\t\tfmt::format(\"No section {} parsed.\", StoragePropertySectionExt::get_displayable_name(StoragePropertySection::SelftestLog)));\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlJsonNvmeParser::parse_section_nvme_attributes(const nlohmann::json& json_root_node)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\n\tbool section_properties_found = false;\n\n\tconst std::vector<std::tuple<std::string, std::string, PropertyRetrievalFunc>> nvme_attr_keys = {\n\t\t\t{\"nvme_smart_health_information_log/temperature\", _(\"Current Temperature\"), integer_formatter<int64_t>(\"{}° Celsius\")},\n\t\t\t{\"nvme_smart_health_information_log/available_spare\", _(\"Available Spare\"), integer_formatter<int64_t>(\"{}%\")},\n\t\t\t{\"nvme_smart_health_information_log/available_spare_threshold\", _(\"Available Spare Threshold\"), integer_formatter<int64_t>(\"{}%\")},\n\t\t\t{\"nvme_smart_health_information_log/percentage_used\", _(\"Percentage Used\"), integer_formatter<int64_t>(\"{}%\")},\n\t\t\t{\"nvme_smart_health_information_log/data_units_read\", _(\"Data Units Read\"), integer_formatter<int64_t>()},\n\t\t\t{\"nvme_smart_health_information_log/data_units_written\", _(\"Data Units Written\"), integer_formatter<int64_t>()},\n\t\t\t{\"nvme_smart_health_information_log/host_reads\", _(\"Host Read Commands\"), integer_formatter<int64_t>()},\n\t\t\t{\"nvme_smart_health_information_log/host_writes\", _(\"Host Write Commands\"), integer_formatter<int64_t>()},\n\t\t\t{\"nvme_smart_health_information_log/controller_busy_time\", _(\"Controller Busy Time\"), integer_formatter<int64_t>()},\n\t\t\t{\"nvme_smart_health_information_log/power_cycles\", _(\"Power Cycles\"), integer_formatter<int64_t>()},\n\t\t\t{\"nvme_smart_health_information_log/power_on_hours\", _(\"Power On Hours\"), integer_formatter<int64_t>()},\n\t\t\t{\"nvme_smart_health_information_log/unsafe_shutdowns\", _(\"Unsafe Shutdowns\"), integer_formatter<int64_t>()},\n\t\t\t{\"nvme_smart_health_information_log/media_errors\", _(\"Media and Data Integrity Errors\"), integer_formatter<int64_t>()},\n\t\t\t{\"nvme_smart_health_information_log/num_err_log_entries\", _(\"Preserved Error Information Log Entries\"), integer_formatter<int64_t>()},  // preserved across resets\n\t\t\t{\"nvme_smart_health_information_log/warning_temp_time\", _(\"Warning  Composite Temperature Time\"), integer_formatter<int64_t>()},\n\t\t\t{\"nvme_smart_health_information_log/critical_comp_time\", _(\"Critical Composite Temperature Time\"), integer_formatter<int64_t>()},\n\t};\n\n\tfor (const auto& [key, displayable_name, retrieval_func] : nvme_attr_keys) {\n\t\tDBG_ASSERT(retrieval_func != nullptr);\n\n\t\tauto p = retrieval_func(json_root_node, key, displayable_name);\n\t\tif (p.has_value()) {  // ignore if not found\n\t\t\tp->section = StoragePropertySection::NvmeAttributes;\n\t\t\tadd_property(p.value());\n\n\t\t\tsection_properties_found = true;\n\t\t}\n\t}\n\n\tif (!section_properties_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection,\n\t\t\t\tfmt::format(\"No section {} parsed.\", StoragePropertySectionExt::get_displayable_name(StoragePropertySection::NvmeAttributes)));\n\t}\n\n\treturn {};\n}\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_json_nvme_parser.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef SMARTCTL_JSON_NVME_PARSER_H\n#define SMARTCTL_JSON_NVME_PARSER_H\n\n#include \"smartctl_parser.h\"\n\n#include <string_view>\n\n#include \"nlohmann/json.hpp\"\n\n#include \"hz/error_container.h\"\n#include \"smartctl_parser_types.h\"\n\n\n\n/// Smartctl NVMe JSON output parser\nclass SmartctlJsonNvmeParser : public SmartctlParser {\n\tpublic:\n\n\t\t// Defaulted, used by make_unique.\n\t\tSmartctlJsonNvmeParser() = default;\n\n\t\t// Overridden\n\t\t[[nodiscard]] hz::ExpectedVoid<SmartctlParserError> parse(std::string_view smartctl_output) override;\n\n\tprivate:\n\n\t\t/// Parse the info section (root node), filling in the properties\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_info(const nlohmann::json& json_root_node);\n\n\t\t/// Parse a section from json data\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_overall_health(const nlohmann::json& json_root_node);\n\n\t\t/// Parse a section from json data\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_nvme_health(const nlohmann::json& json_root_node);\n\n\t\t/// Parse a section from json data\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_nvme_error_log(const nlohmann::json& json_root_node);\n\n\t\t/// Parse a section from json data\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_selftest_log(const nlohmann::json& json_root_node);\n\n\t\t/// Parse a section from json data\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_nvme_attributes(const nlohmann::json& json_root_node);\n\n\n};\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_json_parser_helpers.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2022 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef SMARTCTL_JSON_PARSER_HELPERS_H\n#define SMARTCTL_JSON_PARSER_HELPERS_H\n\n#include <cstddef>\n#include <functional>\n#include <string>\n#include <string_view>\n#include <vector>\n\n#include \"fmt/format.h\"\n#include \"nlohmann/json.hpp\"\n#include \"hz/debug.h\"\n#include \"hz/string_algo.h\"\n#include \"smartctl_parser_types.h\"\n#include \"smartctl_version_parser.h\"\n#include \"hz/format_unit.h\"\n#include \"hz/error_container.h\"\n#include \"storage_property.h\"\n\n\n\nenum class SmartctlJsonParserError {\n\tUnexpectedObjectInPath,\n\tPathNotFound,\n\tTypeError,\n\tEmptyPath,\n\tInternalError,\n};\n\n\n\nnamespace SmartctlJsonParserHelpers {\n\n\n/// Get node from json data. The path is slash-separated string.\n[[nodiscard]] inline hz::ExpectedValue<nlohmann::json, SmartctlJsonParserError>\nget_node(const nlohmann::json& root, std::string_view path)\n{\n\tusing namespace std::literals;\n\n\tstd::vector<std::string> components;\n\thz::string_split(path, '/', components, true);\n\n\tif (components.empty()) {\n\t\treturn hz::Unexpected(SmartctlJsonParserError::EmptyPath, \"Cannot get node data: Empty path.\");\n\t}\n\n\tconst auto* curr = &root;\n\tfor (std::size_t comp_index = 0; comp_index < components.size(); ++comp_index) {\n\t\tconst std::string& comp_name = components[comp_index];\n\n\t\tif (!curr->is_object()) {  // we can't have non-object values in the middle of a path\n\t\t\treturn hz::Unexpected(SmartctlJsonParserError::UnexpectedObjectInPath,\n\t\t\t\t\tfmt::format(\"Cannot get node data \\\"{}\\\", component \\\"{}\\\" is not an object.\", path, comp_name));\n\t\t}\n\t\tif (auto iter = curr->find(comp_name); iter != curr->end()) {  // path component exists\n\t\t\tconst auto& jval = iter.value();\n\t\t\tif (comp_index + 1 == components.size()) {  // it's the \"value\" component\n\t\t\t\treturn jval;\n\t\t\t}\n\t\t\t// continue to the next component\n\t\t\tcurr = &jval;\n\n\t\t} else {  // path component doesn't exist\n\t\t\treturn hz::Unexpected(SmartctlJsonParserError::PathNotFound,\n\t\t\t\t\tfmt::format(\"Cannot get node data \\\"{}\\\", component \\\"{}\\\" does not exist.\", path, comp_name));\n\t\t}\n\t}\n\n\treturn hz::Unexpected(SmartctlJsonParserError::InternalError, \"Internal error.\");\n}\n\n\n\n\n/// Get json node data. The path is slash-separated string.\n/// \\return SmartctlJsonParserError on error.\ntemplate<typename T>\n[[nodiscard]] hz::ExpectedValue<T, SmartctlJsonParserError> get_node_data(const nlohmann::json& root, std::string_view path)\n{\n\tauto node_result = get_node(root, path);\n\tif (!node_result) {\n\t\treturn hz::UnexpectedFrom(node_result);\n\t}\n\n\ttry {\n\t\treturn node_result.value().get<T>();  // may throw json::type_error\n\t}\n\tcatch (nlohmann::json::type_error& ex) {\n\t\treturn hz::Unexpected(SmartctlJsonParserError::TypeError,\n\t\t\t\tfmt::format(\"Cannot get node data \\\"{}\\\", component has wrong type: {}.\", path, ex.what()));\n\t}\n}\n\n\n\n/// Get json node data. The path is slash-separated string.\n/// If the data is not is found, the default value is returned.\ntemplate<typename T>\n[[nodiscard]] hz::ExpectedValue<T, SmartctlJsonParserError> get_node_data(const nlohmann::json& root, std::string_view path, const T& default_value)\n{\n\tauto expected_data = get_node_data<T>(root, path);\n\n\tif (!expected_data.has_value()) {\n\t\tswitch(expected_data.error().data()) {\n\t\t\tcase SmartctlJsonParserError::PathNotFound:\n\t\t\t\treturn default_value;\n\n\t\t\tcase SmartctlJsonParserError::TypeError:\n\t\t\tcase SmartctlJsonParserError::UnexpectedObjectInPath:\n\t\t\tcase SmartctlJsonParserError::EmptyPath:\n\t\t\tcase SmartctlJsonParserError::InternalError:\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn expected_data;\n}\n\n\n\n/// Check if json node exists. The path is slash-separated string.\n[[nodiscard]] inline hz::ExpectedValue<bool, SmartctlJsonParserError>\nget_node_exists(const nlohmann::json& root, std::string_view path)\n{\n\tauto node_result = get_node(root, path);\n\tif (node_result.has_value()) {\n\t\treturn true;\n\t}\n\n\tswitch (node_result.error().data()) {\n\t\tcase SmartctlJsonParserError::PathNotFound:\n\t\t\treturn false;\n\n\t\tcase SmartctlJsonParserError::UnexpectedObjectInPath:\n\t\tcase SmartctlJsonParserError::EmptyPath:\n\t\tcase SmartctlJsonParserError::InternalError:\n\t\tcase SmartctlJsonParserError::TypeError:\n\t\t\tbreak;\n\t}\n\n\treturn hz::UnexpectedFrom(node_result);\n}\n\n\n\n/// A signature for a property retrieval function.\nusing PropertyRetrievalFunc = std::function<\n\t\tauto(const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError> >;\n\n\n\n/// Return a lambda which retrieves a key value as a string, and sets it as a property.\ninline auto string_formatter()\n{\n\treturn [](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t{\n\t\tif (auto jval = get_node_data<std::string>(root_node, key); jval) {\n\t\t\tStorageProperty p;\n\t\t\tp.set_name(key, displayable_name);\n\t\t\t// p.reported_value = jval.value();\n\t\t\tp.readable_value = jval.value();\n\t\t\tp.value = jval.value();\n\t\t\treturn p;\n\t\t}\n\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t};\n}\n\n\n\n/// Return a lambda which returns a return_property if conditional_path exists.\n/// If the path doesn't exist, an error is returned.\ninline auto conditional_formatter(const std::string_view conditional_path, StorageProperty return_property)\n{\n\treturn [conditional_path, return_property](const nlohmann::json& root_node, const std::string& key, [[maybe_unused]] const std::string& displayable_name) mutable\n\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t{\n\t\tauto node_exists_result = get_node_exists(root_node, conditional_path);\n\t\tif (!node_exists_result.has_value()) {\n\t\t\treturn hz::Unexpected(SmartctlParserError::DataError, node_exists_result.error().message());\n\t\t}\n\n\t\tif (node_exists_result.value()) {\n\t\t\treturn_property.generic_name = key;\n\t\t\treturn_property.displayable_name = displayable_name;\n\t\t\treturn return_property;\n\t\t}\n\n\t\treturn hz::Unexpected(SmartctlParserError::InternalError, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t};\n}\n\n\n\n/// Return a lambda which retrieves a key value as a bool (formatted according to parameters), and sets it as a property.\ninline auto bool_formatter(const std::string_view& true_str, const std::string_view& false_str)\n{\n\treturn [true_str, false_str](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t{\n\t\tif (auto jval = get_node_data<bool>(root_node, key); jval) {\n\t\t\tStorageProperty p;\n\t\t\tp.set_name(key, displayable_name);\n\t\t\t// p.reported_value = (jval.value() ? true_str : false_str);\n\t\t\tp.readable_value = (jval.value() ? true_str : false_str);\n\t\t\tp.value = jval.value();\n\t\t\treturn p;\n\t\t}\n\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t};\n}\n\n\n\n/// Return a lambda which retrieves a key value as an integer of type IntegerType\n/// and formats it using locale, placing it in format_string.\ntemplate<typename IntegerType>\nauto integer_formatter(const std::string& format_string = \"{}\")\n{\n\treturn [format_string](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t{\n\t\tif (auto jval = get_node_data<IntegerType>(root_node, key); jval) {\n\t\t\tStorageProperty p;\n\t\t\tp.set_name(key, displayable_name);\n\t\t\t// p.reported_value = (jval.value() ? true_str : false_str);\n\t\t\tstd::string num_str = hz::number_to_string_locale(jval.value());\n\t\t\tp.readable_value = fmt::format(fmt::runtime(format_string), num_str);\n\t\t\tp.value = jval.value();\n\t\t\treturn p;\n\t\t}\n\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t};\n}\n\n\n\n/// Return a lambda which retrieves a key value as a string (formatted using another lambda), and sets it as a property.\ntemplate<typename Type>\nauto custom_string_formatter(std::function<std::string(Type value)> formatter)\n{\n\treturn [formatter](const nlohmann::json& root_node, const std::string& key, const std::string& displayable_name)\n\t\t\t-> hz::ExpectedValue<StorageProperty, SmartctlParserError>\n\t{\n\t\tif (auto jval = get_node_data<Type>(root_node, key); jval) {\n\t\t\tStorageProperty p;\n\t\t\tp.set_name(key, displayable_name);\n\t\t\t// p.reported_value = formatter(jval.value());\n\t\t\tp.readable_value = formatter(jval.value());\n\t\t\tp.value = jval.value();\n\t\t\treturn p;\n\t\t}\n\t\treturn hz::Unexpected(SmartctlParserError::KeyNotFound, fmt::format(\"Error getting key {} from JSON data.\", key));\n\t};\n}\n\n\n\n/// Parse version from json output, returning 2 properties.\n[[nodiscard]] inline hz::ExpectedVoid<SmartctlParserError> parse_version(const nlohmann::json& json_root_node,\n\t\tStorageProperty& merged_property, StorageProperty& full_property)\n{\n\tusing namespace SmartctlJsonParserHelpers;\n\n\tstd::string smartctl_version;\n\n\tauto json_ver = get_node_data<std::vector<int>>(json_root_node, \"smartctl/version\");\n\n\tif (!json_ver.has_value()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Smartctl version not found in JSON.\\n\");\n\n\t\tif (json_ver.error().data() == SmartctlJsonParserError::PathNotFound) {\n\t\t\treturn hz::Unexpected(SmartctlParserError::NoVersion, \"Smartctl version not found in JSON data.\");\n\t\t}\n\t\tif (json_ver->size() < 2) {\n\t\t\treturn hz::Unexpected(SmartctlParserError::DataError, \"Error getting smartctl version from JSON data: Not enough version components.\");\n\t\t}\n\t\treturn hz::Unexpected(SmartctlParserError::DataError, fmt::format(\"Error getting smartctl version from JSON data: {}\", json_ver.error().message()));\n\t}\n\n\tsmartctl_version = fmt::format(\"{}.{}\", json_ver->at(0), json_ver->at(1));\n\n\t{\n\t\tmerged_property.set_name(\"smartctl/version/_merged\", _(\"Smartctl Version\"));\n\t\t// p.reported_value = smartctl_version;\n\t\tmerged_property.readable_value = smartctl_version;\n\t\tmerged_property.value = smartctl_version;  // string-type value\n\t\tmerged_property.section = StoragePropertySection::Info;  // add to info section\n\t}\n\t{\n\t\tfull_property.set_name(\"smartctl/version/_merged_full\", _(\"Smartctl Version\"));\n\t\tfull_property.readable_value = fmt::format(\"{}.{} r{} {} {}\", json_ver->at(0), json_ver->at(1),\n\t\t\t\tget_node_data<std::string>(json_root_node, \"smartctl/svn_revision\", {}).value_or(std::string()),\n\t\t\t\tget_node_data<std::string>(json_root_node, \"smartctl/platform_info\", {}).value_or(std::string()),\n\t\t\t\tget_node_data<std::string>(json_root_node, \"smartctl/build_info\", {}).value_or(std::string())\n\t\t);\n\t\tfull_property.value = full_property.readable_value;  // string-type value\n\t\tfull_property.section = StoragePropertySection::Info;  // add to info section\n\t}\n\tif (!SmartctlVersionParser::check_format_supported(SmartctlOutputFormat::Json, smartctl_version)) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Incompatible smartctl version. Returning.\\n\");\n\t\treturn hz::Unexpected(SmartctlParserError::IncompatibleVersion, \"Incompatible smartctl version.\");\n\t}\n\n\treturn {};\n}\n\n\n\n\n}  // namespace SmartctlJsonParserHelpers\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_parser.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2022 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include <locale>\n#include <cctype>  // isspace\n#include <utility>\n#include <string_view>\n\n#include \"smartctl_parser.h\"\n#include \"storage_property.h\"\n#include \"hz/error_container.h\"\n#include \"smartctl_text_ata_parser.h\"\n#include \"smartctl_json_ata_parser.h\"\n#include \"smartctl_json_basic_parser.h\"\n#include \"smartctl_text_basic_parser.h\"\n#include \"storage_property_repository.h\"\n#include \"smartctl_json_nvme_parser.h\"\n//#include \"ata_storage_property_descr.h\"\n\n\n\nstd::unique_ptr<SmartctlParser> SmartctlParser::create(SmartctlParserType type, SmartctlOutputFormat format)\n{\n\tswitch(type) {\n\t\tcase SmartctlParserType::Basic:\n\t\t\tswitch(format) {\n\t\t\t\tcase SmartctlOutputFormat::Json:\n\t\t\t\t\treturn std::make_unique<SmartctlJsonBasicParser>();\n\t\t\t\t\tbreak;\n\t\t\t\tcase SmartctlOutputFormat::Text:\n\t\t\t\t\treturn std::make_unique<SmartctlTextBasicParser>();\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbreak;\n\t\tcase SmartctlParserType::Ata:\n\t\t\tswitch(format) {\n\t\t\t\tcase SmartctlOutputFormat::Json:\n\t\t\t\t\treturn std::make_unique<SmartctlJsonAtaParser>();\n\t\t\t\tcase SmartctlOutputFormat::Text:\n\t\t\t\t\treturn std::make_unique<SmartctlTextAtaParser>();\n\t\t\t}\n\t\t\tbreak;\n\t\tcase SmartctlParserType::Nvme:\n\t\t\tswitch(format) {\n\t\t\t\tcase SmartctlOutputFormat::Json:\n\t\t\t\t\treturn std::make_unique<SmartctlJsonNvmeParser>();\n\t\t\t\tcase SmartctlOutputFormat::Text:\n\t\t\t\t\t// nothing\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tbreak;\n\t}\n\treturn nullptr;\n}\n\n\n\nhz::ExpectedValue<SmartctlOutputFormat, SmartctlParserError> SmartctlParser::detect_output_format(std::string_view smartctl_output)\n{\n\t// Look for the first non-whitespace symbol\n\tconst auto* first_symbol = std::find_if(smartctl_output.begin(), smartctl_output.end(), [&](char c) {\n\t\treturn !std::isspace(c, std::locale::classic());\n\t});\n\tif (first_symbol != smartctl_output.end()) {\n\t\tif (*first_symbol == '{') {\n\t\t\treturn SmartctlOutputFormat::Json;\n\t\t}\n\t\tif (smartctl_output.rfind(\"smartctl\", static_cast<std::size_t>(first_symbol - smartctl_output.begin())) == 0) {\n\t\t\treturn SmartctlOutputFormat::Text;\n\t\t}\n\t\treturn hz::Unexpected(SmartctlParserError::UnsupportedFormat, \"Unsupported format while trying to detect smartctl output format.\");\n\t}\n\treturn hz::Unexpected(SmartctlParserError::EmptyInput, \"Empty input while trying to detect smartctl output format.\");\n}\n\n\n\nconst StoragePropertyRepository& SmartctlParser::get_property_repository() const\n{\n\treturn properties_;\n}\n\n\n\n// adds a property into property list, looks up and sets its description.\n// Yes, there's no place for this in the Parser, but whatever...\nvoid SmartctlParser::add_property(StorageProperty p)\n{\n\tproperties_.add_property(std::move(p));\n}\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_parser.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2022 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef SMARTCTL_PARSER_H\n#define SMARTCTL_PARSER_H\n\n#include <string_view>\n#include <memory>\n\n#include \"storage_property.h\"\n#include \"smartctl_parser_types.h\"\n#include \"hz/error_container.h\"\n#include \"storage_property_repository.h\"\n\n\n\n\n/// Smartctl output parser.\nclass SmartctlParser {\n\tprotected:\n\n\t\t// Defaulted but hidden\n\t\tSmartctlParser() = default;\n\n\n\tpublic:\n\n\t\t// Deleted\n\t\tSmartctlParser(const SmartctlParser& other) = default;\n\n\t\t// Deleted\n\t\tSmartctlParser(SmartctlParser&& other) = delete;\n\n\t\t// Deleted\n\t\tSmartctlParser& operator=(const SmartctlParser& other) = delete;\n\n\t\t// Deleted\n\t\tSmartctlParser& operator=(SmartctlParser&& other) = delete;\n\n\t\t/// Virtual member requirement\n\t\tvirtual ~SmartctlParser() = default;\n\n\n\t\t/// Create an instance of this class.\n\t\t/// \\return nullptr if no such class exists\n\t\t[[nodiscard]] static std::unique_ptr<SmartctlParser> create(SmartctlParserType type, SmartctlOutputFormat format);\n\n\n\t\t/// Parse full \"smartctl -x\" output.\n\t\t/// Note: Once parsed, this function cannot be called again.\n\t\t[[nodiscard]] virtual hz::ExpectedVoid<SmartctlParserError> parse(std::string_view smartctl_output) = 0;\n\n\n\t\t/// Detect smartctl output type (text, json).\n\t\t[[nodiscard]] static hz::ExpectedValue<SmartctlOutputFormat, SmartctlParserError> detect_output_format(std::string_view smartctl_output);\n\n\n\t\t/// Get parsed properties.\n\t\t[[nodiscard]] const StoragePropertyRepository& get_property_repository() const;\n\n\n\tprotected:\n\n\t\t/// Add a property into property list, look up and set its description\n\t\tvoid add_property(StorageProperty p);\n\n\n\tprivate:\n\n\t\tStoragePropertyRepository properties_;  ///< Parsed data properties\n\n};\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_parser_types.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2022 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef SMARTCTL_PARSER_TYPES_H\n#define SMARTCTL_PARSER_TYPES_H\n\n#include <glibmm.h>\n#include <glibmm/i18n.h>\n\n#include \"hz/enum_helper.h\"\n\n\nenum class SmartctlParserError {\n\tEmptyInput,\n\tUnsupportedFormat,\n\tSyntaxError,\n\tNoVersion,\n\tIncompatibleVersion,\n\tNoSection,  ///< Returned by parser of each section if the section is not found\n\tUnknownSection,  ///< Local parsing function error\n\tInternalError,\n\tNoSubsectionsParsed,\n\tDataError,\n\tKeyNotFound,\n};\n\n\n\nenum class SmartctlParserType {\n\tBasic,  ///< Info only, supports all types of devices\n\tAta,  ///< (S)ATA\n\tNvme,  ///< NVMe\n//\tScsi,  ///< SCSI\n};\n\n\n\nenum class SmartctlOutputFormat {\n\tJson,\n\tText,\n};\n\n\n\nenum class SmartctlParserPreferenceType {\n\tAuto,\n\tJson,\n\tText,\n};\n\n\n\n/// Helper structure for enum-related functions\nstruct SmartctlParserPreferenceTypeExt\n\t\t: public hz::EnumHelper<\n\t\t\t\tSmartctlParserPreferenceType,\n\t\t\t\tSmartctlParserPreferenceTypeExt,\n\t\t\t\tGlib::ustring>\n{\n\tstatic constexpr SmartctlParserPreferenceType default_value = SmartctlParserPreferenceType::Auto;\n\n\tstatic std::unordered_map<EnumType, std::pair<std::string, Glib::ustring>> build_enum_map()\n\t{\n\t\treturn {\n\t\t\t{SmartctlParserPreferenceType::Auto, {\"auto\", _(\"Automatic\")}},\n\t\t\t{SmartctlParserPreferenceType::Json, {\"json\", _(\"JSON\")}},\n\t\t\t{SmartctlParserPreferenceType::Text, {\"text\", _(\"Text\")}},\n\t\t};\n\t}\n\n};\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_text_ata_parser.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include \"smartctl_text_ata_parser.h\"\n\n// #include <glibmm.h>\n#include <chrono>\n#include <clocale>  // localeconv\n#include <cstddef>\n#include <cstdint>\n#include <string>\n#include <string_view>\n#include <utility>\n#include <vector>\n\n#include \"fmt/format.h\"\n// #include \"hz/locale_tools.h\"  // ScopedCLocale, locale_c_get().\n#include \"storage_property.h\"\n#include \"hz/string_algo.h\"  // string_*\n#include \"hz/string_num.h\"  // string_is_numeric, number_to_string\n#include \"hz/debug.h\"  // debug_*\n\n#include \"app_regex.h\"\n//#include \"ata_storage_property_descr.h\"\n// #include \"warning_colors.h\"\n#include \"smartctl_parser_types.h\"\n#include \"smartctl_version_parser.h\"\n#include \"smartctl_text_parser_helper.h\"\n\n\n\nnamespace {\n\n\n\t/// Get storage property by checksum error name (which corresponds to\n\t/// an output section).\n\tinline StorageProperty app_get_checksum_error_property(const std::string& reported_section_name)\n\t{\n\t\tStorageProperty p;\n\t\tstd::string disp_name = \"Error in \" + reported_section_name + \" structure\";\n\n\t\tif (reported_section_name == \"Attribute Data\") {\n\t\t\tp.section = StoragePropertySection::AtaAttributes;\n\t\t\tp.set_name(\"_text_only/attribute_data_checksum_error\", disp_name);\n\n\t\t} else if (reported_section_name == \"Attribute Thresholds\") {\n\t\t\tp.section = StoragePropertySection::AtaAttributes;\n\t\t\tp.set_name(\"_text_only/attribute_thresholds_checksum_error\", disp_name);\n\n\t\t} else if (reported_section_name == \"ATA Error Log\") {\n\t\t\tp.section = StoragePropertySection::AtaErrorLog;\n\t\t\tp.set_name(\"_text_only/ata_error_log_checksum_error\", disp_name);\n\n\t\t} else if (reported_section_name == \"Self-Test Log\") {\n\t\t\tp.section = StoragePropertySection::SelftestLog;\n\t\t\tp.set_name(\"_text_only/selftest_log_checksum_error\", disp_name);\n\t\t}\n\n\t\tp.reported_value = \"checksum error\";\n\t\tp.value = p.reported_value;  // string-type value\n\n\t\treturn p;\n\t}\n\n\n}\n\n\n\n// Parse full \"smartctl -x\" output\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse(std::string_view smartctl_output)\n{\n\t// -------------------- Fix the output, so it doesn't interfere with proper parsing\n\n\t// perform any2unix\n\tstd::string s = hz::string_trim_copy(hz::string_any_to_unix_copy(smartctl_output));\n\n\tif (s.empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Empty string passed as an argument. Returning.\\n\");\n\t\treturn hz::Unexpected(SmartctlParserError::EmptyInput, \"Smartctl data is empty.\");\n\t}\n\n\n\t// The first line may be a command, filter it out. e.g.\n\t// # smartctl -a /dev/sda\n\t// NO NEED: We ignore everything non-section (except version info).\n\t// Note: We ignore non-section lines, so we don't need any filtering here.\n// \t{\n// \t\tapp_regex_replace_once(\"/^# .*$/\", \"\", s);  // replace first only, on the first line only.\n// \t}\n\n\n\t// Checksum warnings are kind of randomly distributed, so\n\t// extract and remove them.\n\t{\n\t\tconst auto re = app_regex_re(\"/\\\\nWarning! SMART (.+) Structure error: invalid SMART checksum\\\\.$/mi\");\n\t\tfor (auto it = std::sregex_iterator(s.begin(), s.end(), re), end = std::sregex_iterator(); it != end; ++it) {\n\t\t\tconst std::string structure_name = hz::string_trim_copy(it->str(1));\n\t\t\tadd_property(app_get_checksum_error_property(structure_name));\n\t\t}\n\t\tapp_regex_replace(re, \"\", s);  // remove them from s.\n\t}\n\n\t// Remove some additional stuff which doesn't fit\n\t// Display this warning somewhere? (info section?)\n\t// Or not, these options don't do anything crucial - just some translation stuff.\n\t{\n\t\tapp_regex_replace(\"/\\\\n.*May need -F samsung or -F samsung2 enabled; see manual for details\\\\.$/mi\",\n\t\t\t\t\"\", s);  // remove from s\n\t}\n\n\n\t// The Warning: parts also screw up newlines sometimes (making double-newlines,\n\t// confusing for section separation).\n\t{\n\t\tconst auto re = app_regex_re(\"/^(Warning: ATA error count.*\\\\n)\\\\n/mi\");\n\n\t\tstd::string match;\n\t\tif (app_regex_partial_match(re, s, &match)) {\n\t\t\tapp_regex_replace(re, match, s);  // make one newline less\n\t\t}\n\t}\n\n\n\t// If the device doesn't support many things, the warnings aren't separated (for sections).\n\t// Fix that. This affects old smartctl only (at least 6.5 fixed the warnings).\n\t{\n\t\tconst auto re1 = app_regex_re(\"/^(Warning: device does not support Error Logging)$/mi\");\n\t\tconst auto re2 = app_regex_re(\"/^(Warning: device does not support Self Test Logging)$/mi\");\n\t\tconst auto re3 = app_regex_re(\"/^(Device does not support Selective Self Tests\\\\/Logging)$/mi\");\n\t\tconst auto re4 = app_regex_re(\"/^(Warning: device does not support SCT Commands)$/mi\");\n\t\tstd::string match;\n\n\t\tif (app_regex_partial_match(re1, s, &match))\n\t\t\tapp_regex_replace(re1, \"\\n\" + match + \"\\n\", s);  // add extra newlines\n\n\t\tif (app_regex_partial_match(re2, s, &match))\n\t\t\tapp_regex_replace(re2, \"\\n\" + match + \"\\n\", s);  // add extra newlines\n\n\t\tif (app_regex_partial_match(re3, s, &match))\n\t\t\tapp_regex_replace(re3, \"\\n\" + match + \"\\n\", s);  // add extra newlines\n\n\t\tif (app_regex_partial_match(re4, s, &match))\n\t\t\tapp_regex_replace(re4, \"\\n\" + match + \"\\n\", s);  // add extra newlines\n\t}\n\n\t// Some errors get in the way of subsection detection and have little value, remove them.\n\t{\n\t\t// \"ATA_READ_LOG_EXT (addr=0x00:0x00, page=0, n=1) failed: 48-bit ATA commands not implemented\"\n\t\t// or \"ATA_READ_LOG_EXT (addr=0x11:0x00, page=0, n=1) failed: scsi error aborted command\"\n\t\t// in front of \"Read GP Log Directory failed\" and \"Read SATA Phy Event Counters failed\".\n\t\tconst auto re1 = app_regex_re(\"/^(ATA_READ_LOG_EXT \\\\([^)]+\\\\) failed: .*)$/mi\");\n\t\t// \"SMART WRITE LOG does not return COUNT and LBA_LOW register\"\n\t\t// in front of \"SCT (Get) Error Recovery Control command failed\" (scterc section)\n\t\tconst auto re2= app_regex_re(\"/^((?:Error )?SMART WRITE LOG does not return COUNT and LBA_LOW register)$/mi\");\n\t\t// \"Read SCT Status failed: scsi error aborted command\"\n\t\t// in front of \"Read SCT Temperature History failed\" and \"SCT (Get) Error Recovery Control command failed\"\n\t\tconst auto re3= app_regex_re(\"/^(Read SCT Status failed: .*)$/mi\");\n\t\t// \"Unknown SCT Status format version 0, should be 2 or 3.\"\n\t\tconst auto re4= app_regex_re(\"/^(Unknown SCT Status format version .*)$/mi\");\n\t\t// \"Read SCT Data Table failed: scsi error aborted command\"\n\t\tconst auto re5= app_regex_re(\"/^(Read SCT Data Table failed: .*)$/mi\");\n\t\t// \"Write SCT Data Table failed: Undefined error: 0\"\n\t\t// in front of \"Read SCT Temperature History failed\"\n\t\tconst auto re6= app_regex_re(\"/^(Write SCT Data Table failed: .*)$/mi\");\n\t\t// \"Unexpected SCT status 0x0000 (action_code=0, function_code=0)\"\n\t\t// in front of \"Read SCT Temperature History failed\"\n\t\tconst auto re7= app_regex_re(\"/^(Unexpected SCT status .*\\\\))$/mi\");\n\t\tstd::string match;\n\n\t\tif (app_regex_partial_match(re1, s, &match))\n\t\t\tapp_regex_replace(re1, \"\", s);  // add extra newlines\n\n\t\tif (app_regex_partial_match(re2, s, &match))\n\t\t\tapp_regex_replace(re2, \"\", s);  // add extra newlines\n\n\t\tif (app_regex_partial_match(re3, s, &match))\n\t\t\tapp_regex_replace(re3, \"\", s);  // add extra newlines\n\n\t\tif (app_regex_partial_match(re4, s, &match))\n\t\t\tapp_regex_replace(re4, \"\", s);  // add extra newlines\n\n\t\tif (app_regex_partial_match(re5, s, &match))\n\t\t\tapp_regex_replace(re5, \"\", s);  // add extra newlines\n\n\t\tif (app_regex_partial_match(re6, s, &match))\n\t\t\tapp_regex_replace(re6, \"\", s);  // add extra newlines\n\n\t\tif (app_regex_partial_match(re7, s, &match))\n\t\t\tapp_regex_replace(re7, \"\", s);  // add extra newlines\n\t}\n\n\n\t// ------------------- Parsing\n\n\t// version info\n\n\tstd::string version, version_full;\n\tif (!SmartctlVersionParser::parse_version_text(s, version, version_full)) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Cannot extract version information. Returning.\\n\");\n\t\treturn hz::Unexpected(SmartctlParserError::NoVersion, \"Cannot extract smartctl version information.\");\n\t}\n\n\t{\n\t\tStorageProperty p;\n\t\tp.set_name(\"smartctl/version/_merged\", _(\"Smartctl Version\"));\n\t\tp.reported_value = version;\n\t\tp.value = p.reported_value;  // string-type value\n\t\tp.section = StoragePropertySection::Info;  // add to info section\n\t\tadd_property(p);\n\t}\n\t{\n\t\tStorageProperty p;\n\t\tp.set_name(\"smartctl/version/_merged_full\", _(\"Smartctl Version\"));\n\t\tp.reported_value = version_full;\n\t\tp.value = p.reported_value;  // string-type value\n\t\tp.section = StoragePropertySection::Info;  // add to info section\n\t\tadd_property(p);\n\t}\n\n\tif (!SmartctlVersionParser::check_format_supported(SmartctlOutputFormat::Text, version)) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Incompatible smartctl version. Returning.\\n\");\n\t\treturn hz::Unexpected(SmartctlParserError::IncompatibleVersion, \"Incompatible smartctl version.\");\n\t}\n\n\t// Full text output\n\t{\n\t\tStorageProperty p;\n\t\tp.set_name(\"smartctl/output\", \"Smartctl Text Output\");\n\t\tp.reported_value = smartctl_output;\n\t\tp.value = p.reported_value;  // string-type value\n\t\tp.show_in_ui = false;\n\t\tadd_property(p);\n\t}\n\n\n\t// sections\n\n\tstd::string::size_type section_start_pos = 0, section_end_pos = 0, tmp_pos = 0;\n\tbool status = false;  // true if at least one section was parsed\n\n\t// sections are started by\n\t// === START OF <NAME> SECTION ===\n\twhile (section_start_pos != std::string::npos\n\t\t\t&& (section_start_pos = s.find(\"=== START\", section_start_pos)) != std::string::npos) {\n\n\t\ttmp_pos = s.find('\\n', section_start_pos);  // works with \\r\\n too. This may be npos if nothing follows the header.\n\n\t\t// trim is needed to remove potential \\r in the end\n\t\tconst std::string section_header = hz::string_trim_copy(s.substr(section_start_pos,\n\t\t\t\t(tmp_pos == std::string::npos ? tmp_pos : (tmp_pos - section_start_pos)) ));\n\n\t\tstd::string section_body_str;\n\t\tif (tmp_pos != std::string::npos) {\n\t\t\tsection_end_pos = s.find(\"=== START\", tmp_pos);  // start of the next section\n\t\t\tsection_body_str = hz::string_trim_copy(s.substr(tmp_pos,\n\t\t\t\t\t(section_end_pos == std::string::npos ? section_end_pos : section_end_pos - tmp_pos)));\n\t\t}\n\t\tstatus = parse_section(section_header, section_body_str).has_value() || status;\n\t\tsection_start_pos = (tmp_pos == std::string::npos ? std::string::npos : section_end_pos);\n\t}\n\n\tif (!status) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"No ATA sections could be parsed. Returning.\\n\");\n\t\treturn hz::Unexpected(SmartctlParserError::NoSection, \"No ATA sections could be parsed.\");\n\t}\n\n\treturn {};\n}\n\n\n\n// Parse the section part (with \"=== .... ===\" header) - info or data sections.\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section(const std::string& header, const std::string& body)\n{\n\tif (app_regex_partial_match(\"/START OF INFORMATION SECTION/mi\", header)) {\n\t\treturn parse_section_info(body);\n\t}\n\n\tif (app_regex_partial_match(\"/START OF READ SMART DATA SECTION/mi\", header)) {\n\t\treturn parse_section_data(body);\n\t}\n\n\t// These sections provide information about actions performed.\n\t// You may encounter this if e.g. executing \"smartctl -a -s on\".\n\n\t// example contents: \"SMART Enabled.\".\n\tif (app_regex_partial_match(\"/START OF READ SMART DATA SECTION/mi\", header)) {\n\t\treturn {};\n\t}\n\n\t// We don't parse this - it's parsed by the respective command issuer.\n\tif (app_regex_partial_match(\"/START OF ENABLE/DISABLE COMMANDS SECTION/mi\", header)) {\n\t\treturn {};\n\t}\n\n\t// This is printed when executing \"-t long\", etc. . Parsed by respective command issuer.\n\tif (app_regex_partial_match(\"/START OF OFFLINE IMMEDIATE AND SELF-TEST SECTION/mi\", header)) {\n\t\treturn {};\n\t}\n\n\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Unknown section encountered.\\n\");\n\tdebug_out_dump(\"app\", \"---------------- Begin unknown section header dump ----------------\\n\");\n\tdebug_out_dump(\"app\", header << \"\\n\");\n\tdebug_out_dump(\"app\", \"----------------- End unknown section header dump -----------------\\n\");\n\n\treturn hz::Unexpected(SmartctlParserError::UnknownSection, \"Unknown section encountered.\");\n}\n\n\n\n\n// ------------------------------------------------ INFO SECTION\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section_info(const std::string& body)\n{\n\tthis->set_data_section_info(body);\n\n\tconst StoragePropertySection section = StoragePropertySection::Info;\n\n\t// split by lines.\n\t// e.g. Device Model:     ST3500630AS\n\tconst auto re = app_regex_re(\"/^([^:]+):[ \\\\t]+(.*)$/i\");  // MUST BE Ungreedy!\n\n\tstd::vector<std::string> lines;\n\thz::string_split(body, '\\n', lines, false);\n\tstd::string name, value, warning_msg;\n\tbool expecting_warning_lines = false;\n\n// \twhile (re.FindAndConsume(&input, &name, &value)) {\n\tfor (auto line : lines) {\n\t\thz::string_trim(line);\n\n\t\tif (expecting_warning_lines) {\n\t\t\tif (!line.empty()) {\n\t\t\t\twarning_msg += \"\\n\" + line;\n\t\t\t} else {\n\t\t\t\texpecting_warning_lines = false;\n\t\t\t\tStorageProperty p;\n\t\t\t\tp.section = section;\n\t\t\t\tp.set_name(\"_text_only/info_warning\", _(\"Warning\"));\n\t\t\t\tp.reported_value = warning_msg;\n\t\t\t\tp.value = p.reported_value;  // string-type value\n\t\t\t\tadd_property(p);\n\t\t\t\twarning_msg.clear();\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (line.empty()) {\n\t\t\tcontinue;  // empty lines are part of Info section\n\t\t}\n\n\t\t// Sometimes, we get this in the middle of Info section (separated by double newlines):\n/*\n==> WARNING: A firmware update for this drive may be available,\nsee the following Seagate web pages:\nhttp://knowledge.seagate.com/articles/en_US/FAQ/207931en\nhttp://knowledge.seagate.com/articles/en_US/FAQ/213891en\n*/\n\t\tif (app_regex_partial_match(\"/^==> WARNING: /mi\", line)) {\n\t\t\tapp_regex_replace(\"^==> WARNING: \", \"\", line);\n\t\t\twarning_msg = hz::string_trim_copy(line);\n\t\t\texpecting_warning_lines = true;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// This is not an ordinary name / value pair, so filter it out (we don't need it anyway).\n\t\t// Usually this happens when smart is unsupported or disabled.\n\t\tif (app_regex_partial_match(\"/mandatory SMART command failed/mi\", line)) {\n\t\t\tcontinue;\n\t\t}\n\t\t// --get=all may cause these, ignore.\n\t\t\t\t// \"Unexpected SCT status 0x0010 (action_code=4, function_code=2)\"\n\t\tif (app_regex_partial_match(\"/^Unexpected SCT status/mi\", line)\n\t\t\t\t// \"Write SCT (Get) XXX Error Recovery Control Command failed: scsi error aborted command\"\n\t\t\t\t|| app_regex_partial_match(\"/^Write SCT \\\\(Get\\\\) XXX Error Recovery Control Command failed/mi\", line)\n\t\t\t\t// \"Write SCT (Get) Feature Control Command failed: scsi error aborted command\"\n\t\t\t\t|| app_regex_partial_match(\"/^Write SCT \\\\(Get\\\\) Feature Control Command failed/mi\", line)\n\t\t\t\t// \"Read SCT Status failed: scsi error aborted command\"\n\t\t\t\t|| app_regex_partial_match(\"/^Read SCT Status failed/mi\", line)\n\t\t\t\t// \"Read SMART Data failed: Input/output error\"  (just ignore this, the rest of the data seems fine)\n\t\t\t\t|| app_regex_partial_match(\"/^Read SMART Data failed/mi\", line)\n\t\t\t\t// \"Unknown SCT Status format version 0, should be 2 or 3.\"\n\t\t\t\t|| app_regex_partial_match(\"/^Unknown SCT Status format version/mi\", line)\n\t\t\t\t// \"Read SMART Thresholds failed: scsi error aborted command\"\n\t\t\t\t|| app_regex_partial_match(\"/^Read SMART Thresholds failed/mi\", line)\n\t\t\t\t// \"                  Enabled status cached by OS, trying SMART RETURN STATUS cmd.\"\n\t\t\t\t|| app_regex_partial_match(\"/Enabled status cached by OS, trying SMART RETURN STATUS cmd/mi\", line)\n\t\t\t\t|| app_regex_partial_match(\"/^>> Terminate command early due to bad response to IEC mode page/mi\", line)  // on a flash drive\n\t\t\t\t// \"scsiModePageOffset: response length too short, resp_len=4 offset=4 bd_len=0\"\n\t\t\t\t|| app_regex_partial_match(\"/^scsiModePageOffset: .+/mi\", line)  // on a flash drive\n\t\t   ) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (app_regex_full_match(re, line, {&name, &value})) {\n\t\t\thz::string_trim(name);\n\t\t\thz::string_trim(value);\n\n\t\t\tStorageProperty p;\n\t\t\tp.section = section;\n\t\t\tp.set_name(name, name, name);\n\t\t\tp.reported_value = value;\n\n\t\t\tauto result = parse_section_info_property(p);  // set type and the typed value. may change generic_name too.\n\t\t\tif (!result.has_value()) {  // internal error\n\t\t\t\treturn result;\n\t\t\t}\n\t\t\tadd_property(p);\n\n\t\t} else {\n\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Unknown Info line encountered.\\n\");\n\t\t\tdebug_out_dump(\"app\", \"---------------- Begin unknown Info line ----------------\\n\");\n\t\t\tdebug_out_dump(\"app\", line << \"\\n\");\n\t\t\tdebug_out_dump(\"app\", \"----------------- End unknown Info line -----------------\\n\");\n\t\t}\n\t}\n\n\treturn {};\n}\n\n\n\n// Parse a component (one line) of the info section\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section_info_property(StorageProperty& p)\n{\n\t// ---- Info\n\tif (p.section != StoragePropertySection::Info) {\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Called with non-info section!\\n\");\n\t\treturn hz::Unexpected(SmartctlParserError::InternalError, \"Internal parser error.\");\n\t}\n\n\n\tif (app_regex_partial_match(\"/^Model Family$/mi\", p.reported_name)) {\n\t\tp.set_name(\"model_family\", \"Model Family\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^(?:Device Model|Device|Product)$/mi\", p.reported_name)) {  // \"Device\" and \"Product\" are from scsi/usb\n\t\tp.set_name(\"model_name\", \"Device Model\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Vendor$/mi\", p.reported_name)) {  // From scsi/usb\n\t\tp.set_name(\"vendor\", \"Vendor\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Revision$/mi\", p.reported_name)) {  // From scsi/usb\n\t\tp.set_name(\"revision\", \"Revision\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Device type$/mi\", p.reported_name)) {  // From scsi/usb\n\t\tp.set_name(\"device_type/name\", \"Device Type\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Compliance$/mi\", p.reported_name)) {  // From scsi/usb\n\t\tp.set_name(\"scsi_version\", \"Compliance\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Serial Number$/mi\", p.reported_name)) {\n\t\tp.set_name(\"serial_number\", \"Serial Number\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^LU WWN Device Id$/mi\", p.reported_name)) {\n\t\tp.set_name(\"wwn/_merged\", \"World Wide Name\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Add. Product Id$/mi\", p.reported_name)) {\n\t\tp.set_name(\"ata_additional_product_id\", \"Additional Product ID\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Firmware Version$/mi\", p.reported_name)) {\n\t\tp.set_name(\"firmware_version\", \"Firmware Version\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^User Capacity$/mi\", p.reported_name)) {\n\t\tp.set_name(\"user_capacity/bytes\", \"Capacity\", p.reported_name);\n\t\tint64_t v = 0;\n\t\tp.readable_value = SmartctlTextParserHelper::parse_byte_size(p.reported_value, v, true);\n\t\tif (p.readable_value.empty()) {\n\t\t\tp.readable_value = \"[unknown]\";\n\t\t} else {\n\t\t\tp.value = v;  // integer-type value\n\t\t}\n\n\t} else if (app_regex_partial_match(\"/^Sector Sizes$/mi\", p.reported_name)) {\n\t\tp.set_name(\"physical_block_size/_and/logical_block_size\", \"Sector Sizes\", p.reported_name);\n\t\t// This contains 2 values (phys/logical, if they're different)\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Sector Size$/mi\", p.reported_name)) {\n\t\tp.set_name(\"physical_block_size/_and/logical_block_size\", \"Sector Size\", p.reported_name);\n\t\t// This contains a single value (if it's not 512)\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Logical block size$/mi\", p.reported_name)) {  // from scsi/usb\n\t\tp.set_name(\"logical_block_size\", \"Logical Block Size\", p.reported_name);\n\t\t// \"512 bytes\"\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Rotation Rate$/mi\", p.reported_name)) {\n\t\tp.set_name(\"rotation_rate\", \"Rotation Rate\", p.reported_name);\n\t\tp.value = hz::string_to_number_nolocale<int64_t>(p.reported_value, false);\n\n\t} else if (app_regex_partial_match(\"/^Form Factor$/mi\", p.reported_name)) {\n\t\tp.set_name(\"form_factor/name\", \"Form Factor\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Device is$/mi\", p.reported_name)) {\n\t\tp.set_name(\"in_smartctl_database\", \"In Smartctl Database\", p.reported_name);\n\t\tp.value = (!app_regex_partial_match(\"/Not in /mi\", p.reported_value));  // bool-type value\n\n\t} else if (app_regex_partial_match(\"/^ATA Version is$/mi\", p.reported_name)) {\n\t\tp.set_name(\"ata_version/string\", \"ATA Version\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^ATA Standard is$/mi\", p.reported_name)) {  // old, not present in smartctl 7.2\n\t\tp.set_name(\"ata_version/string\", \"ATA Standard\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^SATA Version is$/mi\", p.reported_name)) {\n\t\tp.set_name(\"sata_version/string\", \"SATA Version\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Local Time is$/mi\", p.reported_name)) {\n\t\tp.set_name(\"local_time/asctime\", \"Scanned on\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^SMART support is$/mi\", p.reported_name)) {\n\t\t// There are two different properties with this name - supported and enabled.\n\t\t// Don't put complete messages here - they change across smartctl versions.\n\n\t\tif (app_regex_partial_match(\"/Available - device has/mi\", p.reported_value)) {\n\t\t\tp.set_name(\"smart_support/available\", \"SMART Supported\", p.reported_name);\n\t\t\tp.value = true;\n\n\t\t} else if (app_regex_partial_match(\"/Enabled/mi\", p.reported_value)) {\n\t\t\tp.set_name(\"smart_support/enabled\", \"SMART Enabled\", p.reported_name);\n\t\t\tp.value = true;\n\n\t\t} else if (app_regex_partial_match(\"/Disabled/mi\", p.reported_value)) {\n\t\t\tp.set_name(\"smart_support/enabled\", \"SMART Enabled\", p.reported_name);\n\t\t\tp.value = false;\n\n\t\t} else if (app_regex_partial_match(\"/Unavailable/mi\", p.reported_value)) {\n\t\t\tp.set_name(\"smart_support/available\", \"SMART Supported\", p.reported_name);\n\t\t\tp.value = false;\n\n\t\t// this should be the last - when ambiguous state is detected, usually smartctl\n\t\t// retries with other methods and prints one of the above.\n\t\t} else if (app_regex_partial_match(\"/Ambiguous/mi\", p.reported_value)) {\n\t\t\tp.set_name(\"smart_support/available\", \"SMART Supported\", p.reported_name);\n\t\t\tp.value = true;  // let's be optimistic - just hope that it doesn't hurt.\n\t\t}\n\n\t// \"-g all\" stuff\n\t} else if (app_regex_partial_match(\"/^AAM feature is$/mi\", p.reported_name)) {\n\t\tp.set_name(\"ata_aam/enabled\", \"AAM Feature\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^AAM level is$/mi\", p.reported_name)) {\n\t\tp.set_name(\"ata_aam/level\", \"AAM Level\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^APM feature is$/mi\", p.reported_name)) {\n\t\tp.set_name(\"ata_apm/enabled\", \"APM Feature\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^APM level is$/mi\", p.reported_name)) {\n\t\tp.set_name(\"ata_apm/level\", \"APM Level\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Rd look-ahead is$/mi\", p.reported_name)) {\n\t\tp.set_name(\"read_lookahead/enabled\", \"Read Look-Ahead\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Write cache is$/mi\", p.reported_name)) {\n\t\tp.set_name(\"write_cache/enabled\", \"Write Cache\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Wt Cache Reorder$/mi\", p.reported_name)) {\n\t\tp.set_name(\"_text_only/write_cache_reorder\", \"Write Cache Reorder\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^DSN feature is$/mi\", p.reported_name)) {\n\t\tp.set_name(\"ata_dsn/enabled\", \"DSN Feature\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^Power mode (?:was|is)$/mi\", p.reported_name)) {\n\t\tp.set_name(\"_text_only/power_mode\", \"Power Mode\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t} else if (app_regex_partial_match(\"/^ATA Security is$/mi\", p.reported_name)) {\n\t\tp.set_name(\"ata_security/string\", \"ATA Security\", p.reported_name);\n\t\tp.value = p.reported_value;  // string-type value\n\n\t// These are some debug warnings from smartctl on usb flash drives\n\t} else if (app_regex_partial_match(\"/^scsiMode/mi\", p.reported_name)) {\n\t\tp.show_in_ui = false;\n\n\t} else {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Unknown property \\\"\" << p.reported_name << \"\\\"\\n\");\n\t\t// this is not an error, just unknown attribute. treat it as string.\n\t\t// Don't highlight it with warning, it may just be a new smartctl feature.\n\t\tp.value = p.reported_value;  // string-type value\n\t}\n\n\treturn {};\n}\n\n\n\n\n// ------------------------------------------------ DATA SECTION\n\n\n// Parse the Data section (without \"===\" header)\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section_data(const std::string& body)\n{\n\tthis->set_data_section_data(body);\n\n\t// perform any2unix\n// \tstd::string s = hz::string_any_to_unix_copy(body);\n\n\tstd::vector<std::string> split_subsections;\n\t// subsections are separated by double newlines, except:\n\t// - \"error log\" subsection, which contains double-newline-separated blocks.\n\t// - \"scttemp\" subsection, which has 3 blocks.\n\thz::string_split(body, \"\\n\\n\", split_subsections, true);\n\n\tbool status = false;  // at least one subsection was parsed\n\n\n\tstd::vector<std::string> subsections;\n\n\t// merge \"single \" parts. For error log, each part begins with a double-space or \"Error nn\".\n\t// For scttemp, parts begin with\n\t// \"SCT Temperature History Version\" or\n\t// \"Index    \" or\n\t// \"Read SCT Temperature History failed\".\n\tfor (auto sub : split_subsections) {\n\t\thz::string_trim(sub, \"\\t\\n\\r\");  // don't trim space\n\t\tif (app_regex_partial_match(\"^  \", sub)\n\t\t\t\t|| app_regex_partial_match(\"^Error [0-9]+\", sub)\n\t\t\t\t|| app_regex_partial_match(\"^SCT Temperature History Version\", sub)\n\t\t\t\t|| app_regex_partial_match(\"^Index[ \\t]+\", sub)\n\t\t\t\t|| app_regex_partial_match(\"^Read SCT Temperature History failed\", sub) ) {\n\t\t\tif (!subsections.empty()) {\n\t\t\t\tsubsections.back() += \"\\n\\n\" + sub;  // append to previous part\n\t\t\t} else {\n\t\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Error Log's Error block, or SCT Temperature History, or SCT Index found without any data subsections present.\\n\");\n\t\t\t}\n\t\t} else {  // not an Error block, process as usual\n\t\t\tsubsections.push_back(sub);\n\t\t}\n\t}\n\n\n\t// parse each subsection\n\tfor (auto sub : subsections) {\n\t\thz::string_trim(sub);\n\t\tif (sub.empty())\n\t\t\tcontinue;\n\n\t\tif (app_regex_partial_match(\"/^SMART overall-health self-assessment/mi\", sub)) {\n\t\t\tstatus = parse_section_data_subsection_health(sub).has_value() || status;\n\n\t\t} else if (app_regex_partial_match(\"/^General SMART Values/mi\", sub)) {\n\t\t\tstatus = parse_section_data_subsection_capabilities(sub).has_value() || status;\n\n\t\t} else if (app_regex_partial_match(\"/^SMART Attributes Data Structure/mi\", sub)) {\n\t\t\tstatus = parse_section_data_subsection_attributes(sub).has_value() || status;\n\n\t\t} else if (app_regex_partial_match(\"/^General Purpose Log Directory Version/mi\", sub)  // -l directory\n\t\t\t\t|| app_regex_partial_match(\"/^General Purpose Log Directory not supported/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^General Purpose Logging \\\\(GPL\\\\) feature set supported/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^Read GP Log Directory failed/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^Log Directories not read due to '-F nologdir' option/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^Read SMART Log Directory failed/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^SMART Log Directory Version/mi\", sub) ) {  // old smartctl\n\t\t\tstatus = parse_section_data_subsection_directory_log(sub).has_value() || status;\n\n\t\t} else if (app_regex_partial_match(\"/^SMART Error Log Version/mi\", sub)  // -l error\n\t\t\t\t|| app_regex_partial_match(\"/^SMART Extended Comprehensive Error Log Version/mi\", sub)  // -l xerror\n\t\t\t\t|| app_regex_partial_match(\"/^Warning: device does not support Error Logging/mi\", sub)  // -l error\n\t\t\t\t|| app_regex_partial_match(\"/^SMART Error Log not supported/mi\", sub)  // -l error\n\t\t\t\t|| app_regex_partial_match(\"/^Read SMART Error Log failed/mi\", sub) ) {  // -l error\n\t\t\tstatus = parse_section_data_subsection_error_log(sub).has_value() || status;\n\n\t\t} else if (app_regex_partial_match(\"/^SMART Extended Comprehensive Error Log \\\\(GP Log 0x03\\\\) not supported/mi\", sub)  // -l xerror\n\t\t\t\t|| app_regex_partial_match(\"/^SMART Extended Comprehensive Error Log size (.*) not supported/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^Read SMART Extended Comprehensive Error Log failed/mi\", sub) ) {  // -l xerror\n\t\t\t// These are printed with \"-l xerror,error\" if falling back to \"error\". They're in their own sections, ignore them.\n\t\t\t// We don't support showing these messages.\n\t\t\tstatus = false;\n\n\t\t} else if (app_regex_partial_match(\"/^SMART Self-test log/mi\", sub)  // -l selftest\n\t\t\t\t|| app_regex_partial_match(\"/^SMART Extended Self-test Log Version/mi\", sub)  // -l xselftest\n\t\t\t\t|| app_regex_partial_match(\"/^Warning: device does not support Self Test Logging/mi\", sub)  // -l selftest\n\t\t\t\t|| app_regex_partial_match(\"/^Read SMART Self-test Log failed/mi\", sub)  // -l selftest\n\t\t\t\t|| app_regex_partial_match(\"/^SMART Self-test Log not supported/mi\", sub)) {  // -l selftest\n\t\t\tstatus = parse_section_data_subsection_selftest_log(sub).has_value() || status;\n\n\t\t} else if (app_regex_partial_match(\"/^SMART Extended Self-test Log \\\\(GP Log 0x07\\\\) not supported/mi\", sub)  // -l xselftest\n\t\t\t\t|| app_regex_partial_match(\"/^SMART Extended Self-test Log size [0-9-]+ not supported/mi\", sub)  // -l xselftest\n\t\t\t\t|| app_regex_partial_match(\"/^Read SMART Extended Self-test Log failed/mi\", sub) ) {  // -l xselftest\n\t\t\t// These are printed with \"-l xselftest,selftest\" if falling back to \"selftest\". They're in their own sections, ignore them.\n\t\t\t// We don't support showing these messages.\n\t\t\tstatus = false;\n\n\t\t} else if (app_regex_partial_match(\"/^SMART Selective self-test log data structure/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^Device does not support Selective Self Tests\\\\/Logging/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^Selective Self-tests\\\\/Logging not supported/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^Read SMART Selective Self-test Log failed/mi\", sub) ) {\n\t\t\tstatus = parse_section_data_subsection_selective_selftest_log(sub).has_value() || status;\n\n\t\t} else if (app_regex_partial_match(\"/^SCT Status Version/mi\", sub)\n\t\t\t\t// \"SCT Commands not supported\"\n\t\t\t\t// \"SCT Commands not supported if ATA Security is LOCKED\"\n\t\t\t\t// \"Error unknown SCT Temperature History Format Version (3), should be 2.\"\n\t\t\t\t// \"Another SCT command is executing, abort Read Data Table\"\n\t\t\t\t|| app_regex_partial_match(\"/^SCT Commands not supported/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^SCT Data Table command not supported/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^Error unknown SCT Temperature History Format Version/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^Another SCT command is executing, abort Read Data Table/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^Warning: device does not support SCT Commands/mi\", sub) ) {  // old smartctl\n\t\t\tstatus = parse_section_data_subsection_scttemp_log(sub).has_value() || status;\n\n\t\t} else if (app_regex_partial_match(\"/^SCT Error Recovery Control/mi\", sub)\n\t\t\t\t// Can be the same \"SCT Commands not supported\" as scttemp.\n\t\t\t\t// \"Another SCT command is executing, abort Error Recovery Control\"\n\t\t\t\t|| app_regex_partial_match(\"/^SCT Error Recovery Control command not supported/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^SCT \\\\(Get\\\\) Error Recovery Control command failed/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^Another SCT command is executing, abort Error Recovery Control/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^Warning: device does not support SCT \\\\(Get\\\\) Error Recovery Control/mi\", sub) ) {  // old smartctl\n\t\t\tstatus = parse_section_data_subsection_scterc_log(sub).has_value() || status;\n\n\t\t} else if (app_regex_partial_match(\"/^Device Statistics \\\\([^)]+\\\\)$/mi\", sub)  // -l devstat\n\t\t\t\t|| app_regex_partial_match(\"/^Device Statistics \\\\([^)]+\\\\) not supported/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^Read Device Statistics page (?:.+) failed/mi\", sub) ) {\n\t\t\tstatus = parse_section_data_subsection_devstat(sub).has_value() || status;\n\n\t\t// \"Device Statistics (GP Log 0x04) supported pages\"\n\t\t} else if (app_regex_partial_match(\"/^Device Statistics \\\\([^)]+\\\\) supported pages/mi\", sub) ) {  // not sure where it came from\n\t\t\t// We don't support this section.\n\t\t\tstatus = false;\n\n\t\t} else if (app_regex_partial_match(\"/^SATA Phy Event Counters/mi\", sub)  // -l sataphy\n\t\t\t\t|| app_regex_partial_match(\"/^SATA Phy Event Counters \\\\(GP Log 0x11\\\\) not supported/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^SATA Phy Event Counters with [0-9-]+ sectors not supported/mi\", sub)\n\t\t\t\t|| app_regex_partial_match(\"/^Read SATA Phy Event Counters failed/mi\", sub) ) {\n\t\t\tstatus = parse_section_data_subsection_sataphy(sub).has_value() || status;\n\n\t\t} else {\n\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Unknown Data subsection encountered.\\n\");\n\t\t\tdebug_out_dump(\"app\", \"---------------- Begin unknown section dump ----------------\\n\");\n\t\t\tdebug_out_dump(\"app\", sub << \"\\n\");\n\t\t\tdebug_out_dump(\"app\", \"----------------- End unknown section dump -----------------\\n\");\n\t\t}\n\t}\n\n\treturn hz::Unexpected(SmartctlParserError::NoSubsectionsParsed, \"No subsections could be parsed.\");\n}\n\n\n\n\n// -------------------- Health\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section_data_subsection_health(const std::string& sub)\n{\n\t// Health section data (--info and --get=all):\n/*\nModel Family:     Hitachi/HGST Travelstar 5K750\nDevice Model:     Hitachi HTS547550A9E384\nFirmware Version: JE3OA40J\nUser Capacity:    500,107,862,016 bytes [500 GB]\nSector Sizes:     512 bytes logical, 4096 bytes physical\nRotation Rate:    5400 rpm\nForm Factor:      2.5 inches\nDevice is:        In smartctl database [for details use: -P show]\n*/\n\n\tStorageProperty pt;  // template for easy copying\n\tpt.section = StoragePropertySection::OverallHealth;\n\n\tstd::string name, value;\n\tif (app_regex_partial_match(\"/^([^:\\\\n]+):[ \\\\t]*(.*)$/mi\", sub, {&name, &value})) {\n\t\thz::string_trim(name);\n\t\thz::string_trim(value);\n\n\t\t// only one attribute in this section\n\t\tif (app_regex_partial_match(\"/SMART overall-health self-assessment/mi\", name)) {\n\t\t\tpt.set_name(name, \"smart_status/passed\", \"Overall Health Self-Assessment Test\");\n\t\t\tpt.reported_value = value;\n\t\t\tpt.value = (pt.reported_value == \"PASSED\");  // bool\n\t\t\tpt.readable_value = (pt.get_value<bool>() ? \"PASSED\" : \"FAILED\");\n\n\t\t\tadd_property(pt);\n\t\t}\n\n\t\treturn {};\n\t}\n\n\treturn hz::Unexpected(SmartctlParserError::DataError, \"Empty health subsection.\");\n}\n\n\n\n\n// -------------------- Capabilities\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section_data_subsection_capabilities(const std::string& sub_initial)\n{\n\t// Capabilities section data:\n/*\nGeneral SMART Values:\nOffline data collection status:  (0x82)\tOffline data collection activity\n\t\t\t\t\twas completed without error.\n\t\t\t\t\tAuto Offline Data Collection: Enabled.\nSelf-test execution status:      (   0)\tThe previous self-test routine completed\n\t\t\t\t\twithout error or no self-test has ever\n\t\t\t\t\tbeen run.\nTotal time to complete Offline\ndata collection: \t\t(   45) seconds.\nOffline data collection\ncapabilities: \t\t\t (0x5b) SMART execute Offline immediate.\n\t\t\t\t\tAuto Offline data collection on/off support.\n\t\t\t\t\tSuspend Offline collection upon new\n\t\t\t\t\tcommand.\n\t\t\t\t\tOffline surface scan supported.\n\t\t\t\t\tSelf-test supported.\n\t\t\t\t\tNo Conveyance Self-test supported.\n\t\t\t\t\tSelective Self-test supported.\nSMART capabilities:            (0x0003)\tSaves SMART data before entering\n\t\t\t\t\tpower-saving mode.\n\t\t\t\t\tSupports SMART auto save timer.\nError logging capability:        (0x01)\tError logging supported.\n\t\t\t\t\tGeneral Purpose Logging supported.\nShort self-test routine\nrecommended polling time: \t (   2) minutes.\nExtended self-test routine\nrecommended polling time: \t ( 152) minutes.\nSCT capabilities: \t       (0x003d)\tSCT Status supported.\n\t\t\t\t\tSCT Error Recovery Control supported.\n\t\t\t\t\tSCT Feature Control supported.\n\t\t\t\t\tSCT Data Table supported.\n*/\n\n\tStorageProperty pt;  // template for easy copying\n\tpt.section = StoragePropertySection::Capabilities;\n\n\tstd::string sub = sub_initial;\n\n\t// Fix some bugs in smartctl output (pre-5.39-final versions):\n\t// There is a stale newline in \"is in a Vendor Specific state\\n.\\n\" and\n\t// \"is in a Reserved state\\n.\\n\".\n// \tapp_regex_replace(\"/\\\\n\\\\.$/mi\", \".\", &sub);\n\tapp_regex_replace(\"/(is in a Vendor Specific state)\\\\n\\\\.$/mi\", \"\\\\1.\", sub);\n\tapp_regex_replace(\"/(is in a Reserved state)\\\\n\\\\.$/mi\", \"\\\\1.\", sub);\n\n\n\t// split to lines and merge them into blocks\n\tstd::vector<std::string> lines, blocks;\n\thz::string_split(sub, '\\n', lines, true);\n\tbool partial = false;\n\n\tfor(auto line : lines) {\n\t\tif (line.empty() || app_regex_partial_match(\"/General SMART Values/mi\", line))  // skip the non-informative lines\n\t\t\tcontinue;\n\t\tline += \"\\n\";  // avoid joining lines without separator. this will get stripped anyway.\n\n\t\tif (line.find_first_of(\" \\t\") != 0 && !partial) {  // new blocks don't start with whitespace\n\t\t\tblocks.emplace_back();  // new block\n\t\t\tblocks.back() += line;\n\t\t\tif (line.find(':') == std::string::npos)\n\t\t\t\tpartial = true;  // if the name spans several lines (they all start with non-whitespace)\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (partial && line.find(':') != std::string::npos)\n\t\t\tpartial = false;\n\n\t\tif (blocks.empty()) {\n\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Non-block related line found!\\n\");\n\t\t\tblocks.emplace_back();  // avoid segfault\n\t\t}\n\t\tblocks.back() += line;\n\t}\n\n\n\t// parse each block.\n\t// [\\s\\S] is equivalent to dot matching newlines.\n\tconst auto re = app_regex_re(R\"(/([^:]*):\\s*\\(([^)]+)\\)\\s*([\\s\\S]*)/m)\");\n\n\tbool cap_found = false;  // found at least one capability\n\n\tfor(std::size_t i = 0; i < blocks.size(); ++i) {\n\t\tconst std::string block = hz::string_trim_copy(blocks[i]);\n\n\t\tstd::string name_orig, numvalue_orig, strvalue_orig;\n\n\t\tif (!app_regex_full_match(re, block, {&name_orig, &numvalue_orig, &strvalue_orig})) {\n\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Block \"\n\t\t\t\t\t<< i << \" cannot be parsed.\\n\");\n\t\t\tdebug_out_dump(\"app\", \"---------------- Begin unparsable block dump ----------------\\n\");\n\t\t\tdebug_out_dump(\"app\", block << \"\\n\");\n\t\t\tdebug_out_dump(\"app\", \"----------------- End unparsable block dump -----------------\\n\");\n\t\t\tcontinue;\n\t\t}\n\n\t\t// flatten:\n\t\tconst std::string name = hz::string_trim_copy(hz::string_remove_adjacent_duplicates_copy(\n\t\t\t\thz::string_replace_chars_copy(name_orig, \"\\t\\n\", ' '), ' '));\n\n\t\tconst std::string strvalue = hz::string_trim_copy(hz::string_remove_adjacent_duplicates_copy(\n\t\t\t\thz::string_replace_chars_copy(strvalue_orig, \"\\t\\n\", ' '), ' '));\n\n\t\tint64_t numvalue = -1;\n\t\tif (!hz::string_is_numeric_nolocale<int64_t>(hz::string_trim_copy(numvalue_orig), numvalue, false)) {  // this will autodetect number base.\n\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG\n\t\t\t\t\t<< \"Numeric value: \\\"\" << numvalue_orig << \"\\\" cannot be parsed as number.\\n\");\n\t\t}\n\n\t\t// \t\tdebug_out_dump(\"app\", \"name: \\\"\" << name << \"\\\"\\n\\tnumvalue: \\\"\" << numvalue\n\t\t// \t\t\t\t<< \"\\\"\\n\\tstrvalue: \\\"\" << strvalue << \"\\\"\\n\\n\");\n\n\n\t\t// Time length properties\n\t\tif (hz::string_erase_right_copy(strvalue, \".\") == \"minutes\"\n\t\t\t\t|| hz::string_erase_right_copy(strvalue, \".\") == \"seconds\") {\n\n\t\t\t// const int numvalue_unmod = numvalue;\n\n\t\t\tif (hz::string_erase_right_copy(strvalue, \".\") == \"minutes\")\n\t\t\t\tnumvalue *= 60;  // convert to seconds\n\n\t\t\t// add as a time property\n\t\t\tStorageProperty p(pt);\n\t\t\tp.set_name(name, name, name);\n\t\t\t// well, not really as reported, but still...\n\t\t\tp.reported_value.append(numvalue_orig).append(\" | \").append(strvalue_orig);\n\t\t\tp.value = std::chrono::seconds(numvalue);  // always in seconds\n\n\t\t\t// Set some generic names on the recognized ones\n\t\t\tif (parse_section_data_internal_capabilities(p).has_value()) {\n\t\t\t\tadd_property(p);\n\t\t\t\tcap_found = true;\n\t\t\t}\n\n\n\t\t// AtaStorageCapability properties (capabilities are flag lists)\n\t\t} else {\n\n\t\t\tStorageProperty p(pt);\n\t\t\tp.set_name(name, name, name);\n\t\t\t// well, not really as reported, but still...\n\t\t\tp.reported_value.append(numvalue_orig).append(\" | \").append(strvalue_orig);\n\n\t\t\tAtaStorageTextCapability cap;\n\t\t\tcap.reported_flag_value = numvalue_orig;\n\t\t\tcap.flag_value = static_cast<uint16_t>(numvalue);  // full flag value\n\t\t\tcap.reported_strvalue = strvalue_orig;\n\n\t\t\t// split capability lines into a vector. every flag sentence ends with \".\"\n\t\t\thz::string_split(strvalue, '.', cap.strvalues, true);\n\t\t\tfor (auto&& v : cap.strvalues) {\n\t\t\t\thz::string_trim(v);\n\t\t\t}\n\n\t\t\tp.value = cap;  // Capability-type value\n\n\t\t\t// find some special capabilities we're interested in and add them. p is unmodified.\n\t\t\tif (parse_section_data_internal_capabilities(p)) {\n\t\t\t\tadd_property(p);\n\t\t\t\tcap_found = true;\n\t\t\t}\n\t\t}\n\n\t}\n\n\tif (!cap_found)\n\t\treturn hz::Unexpected(SmartctlParserError::DataError, \"No capabilities found in Capabilities section.\");\n\n\treturn {};\n}\n\n\n\n\n\n// Check the capabilities for internal properties we can use.\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section_data_internal_capabilities(StorageProperty& cap_prop)\n{\n\t// Some special capabilities we're interested in.\n\n\t// Note: Smartctl gradually changed spelling Off-line to Offline in some messages.\n\t// Also, some capitalization was changed (so the regexps are caseless).\n\n\t// \"Offline data collection not supported.\" (at all) - we don't need to check this,\n\t// because we look for immediate/automatic anyway.\n\n\t// \"was never started\", \"was completed without error\", \"is in progress\",\n\t// \"was suspended by an interrupting command from host\", etc.\n\tconst auto re_offline_status = app_regex_re(\"/^(Off-?line data collection) activity (?:is|was) (.*)$/mi\");\n\t// \"Enabled\", \"Disabled\". May not show up on older smartctl (< 5.1.10), so no way of knowing there.\n\tconst auto re_offline_enabled = app_regex_re(\"/^(Auto Off-?line Data Collection):[ \\\\t]*(.*)$/mi\");\n\tconst auto re_offline_immediate = app_regex_re(\"/^(SMART execute Off-?line immediate)$/mi\");\n\t// \"No Auto Offline data collection support.\", \"Auto Offline data collection on/off support.\".\n\tconst auto re_offline_auto = app_regex_re(\"/^(No |)(Auto Off-?line data collection (?:on\\\\/off )?support)$/mi\");\n\t// Same as above (smartctl <= 5.1-18). \"No Automatic timer ON/OFF support.\"\n\tconst auto re_offline_auto2 = app_regex_re(\"/^(No |)(Automatic timer ON\\\\/OFF support)$/mi\");\n\tconst auto re_offline_suspend = app_regex_re(\"/^(?:Suspend|Abort) (Off-?line collection upon new command)$/mi\");\n\tconst auto re_offline_surface = app_regex_re(\"/^(No |)(Off-?line surface scan supported)$/mi\");\n\n\tconst auto re_selftest_support = app_regex_re(\"/^(No |)(Self-test supported)$/mi\");\n\tconst auto re_conv_selftest_support = app_regex_re(\"/^(No |)(Conveyance Self-test supported)$/mi\");\n\tconst auto re_selective_selftest_support = app_regex_re(\"/^(No |)(Selective Self-test supported)$/mi\");\n\n\tconst auto re_sct_status = app_regex_re(\"/^(SCT Status supported)$/mi\");\n\tconst auto re_sct_control = app_regex_re(\"/^(SCT Feature Control supported)$/mi\");  // means can change logging interval\n\tconst auto re_sct_data = app_regex_re(\"/^(SCT Data Table supported)$/mi\");\n\n\t// these are matched on name\n\tconst auto re_offline_status_group = app_regex_re(\"/^(Off-?line data collection status)/mi\");\n\tconst auto re_offline_time = app_regex_re(\"/^(Total time to complete Off-?line data collection)/mi\");\n\tconst auto re_offline_cap_group = app_regex_re(\"/^(Off-?line data collection capabilities)/mi\");\n\tconst auto re_smart_cap_group = app_regex_re(\"/^(SMART capabilities)/mi\");\n\tconst auto re_error_log_cap_group = app_regex_re(\"/^(Error logging capability)/mi\");\n\tconst auto re_sct_cap_group = app_regex_re(\"/^(SCT capabilities)/mi\");\n\tconst auto re_selftest_status = app_regex_re(\"/^Self-test execution status/mi\");\n\tconst auto re_selftest_short_time = app_regex_re(\"/^(Short self-test routine recommended polling time)/mi\");\n\tconst auto re_selftest_long_time = app_regex_re(\"/^(Extended self-test routine recommended polling time)/mi\");\n\tconst auto re_conv_selftest_time = app_regex_re(\"/^(Conveyance self-test routine recommended polling time)/mi\");\n\n\tif (cap_prop.section != StoragePropertySection::Capabilities) {\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Non-capability property passed.\\n\");\n\t\treturn hz::Unexpected(SmartctlParserError::DataError, \"Non-capability property passed.\");\n\t}\n\n\n\t// Name the capability groups for easy matching when setting descriptions\n\tif (cap_prop.is_value_type<AtaStorageTextCapability>()) {\n\t\tif (app_regex_partial_match(re_offline_status_group, cap_prop.reported_name)) {\n\t\t\tcap_prop.generic_name = \"ata_smart_data/offline_data_collection/status/_group\";\n\n\t\t} else if (app_regex_partial_match(re_offline_cap_group, cap_prop.reported_name)) {\n\t\t\tcap_prop.generic_name = \"ata_smart_data/offline_data_collection/_group\";\n\n\t\t} else if (app_regex_partial_match(re_smart_cap_group, cap_prop.reported_name)) {\n\t\t\tcap_prop.generic_name = \"ata_smart_data/capabilities/_group\";\n\n\t\t} else if (app_regex_partial_match(re_error_log_cap_group, cap_prop.reported_name)) {\n\t\t\tcap_prop.generic_name = \"ata_smart_data/capabilities/error_logging_supported/_group\";\n\n\t\t} else if (app_regex_partial_match(re_sct_cap_group, cap_prop.reported_name)) {\n\t\t\tcap_prop.generic_name = \"ata_sct_capabilities/_group\";\n\n\t\t} else if (app_regex_partial_match(re_selftest_status, cap_prop.reported_name)) {\n\t\t\tcap_prop.generic_name = \"ata_smart_data/self_test/status/_group\";\n\t\t}\n\t}\n\n\n\t// Last self-test status\n\tif (app_regex_partial_match(re_selftest_status, cap_prop.reported_name)) {\n\t\t// The last self-test status. break up into pieces.\n\n\t\tStorageProperty p;\n//\t\tp.section = AtaStoragePropertySection::Internal;\n\t\tp.section = StoragePropertySection::Capabilities;\n\t\tp.set_name(\"ata_smart_data/self_test/status/_merged\", _(\"Self-test execution status\"));\n\n\t\tAtaStorageSelftestEntry sse;\n\t\tsse.test_num = 0;\n\t\tsse.remaining_percent = -1;  // unknown or n/a\n\n\t\t// check for lines in capability vector\n\t\tfor (const auto& sv : cap_prop.get_value<AtaStorageTextCapability>().strvalues) {\n\t\t\tstd::string value;\n\n\t\t\tif (app_regex_partial_match(\"/^([0-9]+)% of test remaining/mi\", sv, &value)) {\n\t\t\t\tint8_t v = 0;\n\t\t\t\tif (hz::string_is_numeric_nolocale(value, v))\n\t\t\t\t\tsse.remaining_percent = v;\n\n\t\t\t} else if (app_regex_partial_match(\"/^(The previous self-test routine completed without error or no .*)/mi\", sv, &value)) {\n\t\t\t\tsse.status_str = value;\n\t\t\t\tsse.status = AtaStorageSelftestEntry::Status::CompletedNoError;\n\n\t\t\t} else if (app_regex_partial_match(\"/^(The self-test routine was aborted by the host)/mi\", sv, &value)) {\n\t\t\t\tsse.status_str = value;\n\t\t\t\tsse.status = AtaStorageSelftestEntry::Status::AbortedByHost;\n\n\t\t\t} else if (app_regex_partial_match(\"/^(The self-test routine was interrupted by the host with a hard.*)/mi\", sv, &value)) {\n\t\t\t\tsse.status_str = value;\n\t\t\t\tsse.status = AtaStorageSelftestEntry::Status::Interrupted;\n\n\t\t\t} else if (app_regex_partial_match(\"/^(A fatal error or unknown test error occurred while the device was executing its .*)/mi\", sv, &value)) {\n\t\t\t\tsse.status_str = value;\n\t\t\t\tsse.status = AtaStorageSelftestEntry::Status::FatalOrUnknown;\n\n\t\t\t} else if (app_regex_partial_match(\"/^(The previous self-test completed having a test element that failed and the test element that failed is not known)/mi\", sv, &value)) {\n\t\t\t\tsse.status_str = value;\n\t\t\t\tsse.status = AtaStorageSelftestEntry::Status::ComplUnknownFailure;\n\n\t\t\t} else if (app_regex_partial_match(\"/^(The previous self-test completed having the electrical element of the test failed)/mi\", sv, &value)) {\n\t\t\t\tsse.status_str = value;\n\t\t\t\tsse.status = AtaStorageSelftestEntry::Status::ComplElectricalFailure;\n\n\t\t\t} else if (app_regex_partial_match(\"/^(The previous self-test completed having the servo .*)/mi\", sv, &value)) {\n\t\t\t\tsse.status_str = value;\n\t\t\t\tsse.status = AtaStorageSelftestEntry::Status::ComplServoFailure;\n\n\t\t\t} else if (app_regex_partial_match(\"/^(The previous self-test completed having the read element of the test failed)/mi\", sv, &value)) {\n\t\t\t\tsse.status_str = value;\n\t\t\t\tsse.status = AtaStorageSelftestEntry::Status::ComplReadFailure;\n\n\t\t\t} else if (app_regex_partial_match(\"/^(The previous self-test completed having a test element that failed and the device is suspected of having handling damage)/mi\", sv, &value)) {\n\t\t\t\tsse.status_str = value;\n\t\t\t\tsse.status = AtaStorageSelftestEntry::Status::ComplHandlingDamage;\n\n\t\t\t// samsung bug (?), as per smartctl sources.\n\t\t\t} else if (app_regex_partial_match(\"/^(The previous self-test routine completed with unknown result or self-test .*)/mi\", sv, &value)) {\n\t\t\t\tsse.status_str = value;\n\t\t\t\tsse.status = AtaStorageSelftestEntry::Status::ComplUnknownFailure;  // we'll use this again (correct?)\n\n\t\t\t} else if (app_regex_partial_match(\"/^(Self-test routine in progress)/mi\", sv, &value)) {\n\t\t\t\tsse.status_str = value;\n\t\t\t\tsse.status = AtaStorageSelftestEntry::Status::InProgress;\n\n\t\t\t} else if (app_regex_partial_match(\"/^(Reserved)/mi\", sv, &value)) {\n\t\t\t\tsse.status_str = value;\n\t\t\t\tsse.status = AtaStorageSelftestEntry::Status::Reserved;\n\t\t\t}\n\t\t}\n\n\t\tp.value = sse;  // AtaStorageSelftestEntry-type value\n\n\t\tadd_property(p);\n\n\t\treturn {};\n\t}\n\n\n\t// Check the time-related ones first.\n\t// Note: We only modify the existing property here!\n\t// Section is unmodified.\n\tif (cap_prop.is_value_type<std::chrono::seconds>()) {\n\n\t\tif (app_regex_partial_match(re_offline_time, cap_prop.reported_name)) {\n\t\t\tcap_prop.generic_name = \"ata_smart_data/offline_data_collection/completion_seconds\";\n\n\t\t} else if (app_regex_partial_match(re_selftest_short_time, cap_prop.reported_name)) {\n\t\t\tcap_prop.generic_name = \"ata_smart_data/self_test/polling_minutes/short\";\n\n\t\t} else if (app_regex_partial_match(re_selftest_long_time, cap_prop.reported_name)) {\n\t\t\tcap_prop.generic_name = \"ata_smart_data/self_test/polling_minutes/extended\";\n\n\t\t} else if (app_regex_partial_match(re_conv_selftest_time, cap_prop.reported_name)) {\n\t\t\tcap_prop.generic_name = \"ata_smart_data/self_test/polling_minutes/conveyance\";\n\t\t}\n\n\t\treturn {};\n\t}\n\n\n\t// Extract subcapabilities from capability vectors and assign to \"internal\" section.\n\tif (cap_prop.is_value_type<AtaStorageTextCapability>()) {\n\n\t\t// check for lines in capability vector\n\t\tfor (const auto& sv : cap_prop.get_value<AtaStorageTextCapability>().strvalues) {\n\n\t\t\t// debug_out_dump(\"app\", \"Looking for internal capability in: \\\"\" << sv << \"\\\"\\n\");\n\n\t\t\tStorageProperty p;\n//\t\t\tp.section = AtaStoragePropertySection::Internal;\n\t\t\tp.section = StoragePropertySection::Capabilities;\n\t\t\t// Note: We don't set reported_value on internal properties.\n\n\t\t\tstd::string name, value;\n\n\t\t\tif (app_regex_partial_match(re_offline_status, sv, {&name, &value})) {\n\t\t\t\tp.set_name(\"ata_smart_data/offline_data_collection/status/string\", name, name);\n\t\t\t\tp.value = hz::string_trim_copy(value);  // string-type value\n\n\t\t\t} else if (app_regex_partial_match(re_offline_enabled, sv, {&name, &value})) {\n\t\t\t\tp.set_name(\"ata_smart_data/offline_data_collection/status/value/_parsed\", name, name);\n\t\t\t\tp.value = (hz::string_trim_copy(value) == \"Enabled\");  // bool\n\n\t\t\t} else if (app_regex_partial_match(re_offline_immediate, sv, &name)) {\n\t\t\t\tp.set_name(\"ata_smart_data/capabilities/exec_offline_immediate_supported\", name, name);\n\t\t\t\tp.value = true;  // bool\n\n\t\t\t} else if (app_regex_partial_match(re_offline_auto, sv, {&value, &name})\n\t\t\t\t\t|| app_regex_partial_match(re_offline_auto2, sv, {&value, &name})) {\n\t\t\t\tp.set_name(\"_text_only/aodc_support\", \"Automatic Offline Data Collection toggle support\", name);\n\t\t\t\tp.value = (hz::string_trim_copy(value) != \"No\");  // bool\n\n\t\t\t} else if (app_regex_partial_match(re_offline_suspend, sv, {&value, &name})) {\n\t\t\t\tp.set_name(\"ata_smart_data/capabilities/offline_is_aborted_upon_new_cmd\", \"Offline Data Collection suspends upon new command\", name);\n\t\t\t\tp.value = (hz::string_trim_copy(value) == \"Suspend\");  // bool\n\n\t\t\t} else if (app_regex_partial_match(re_offline_surface, sv, {&value, &name})) {\n\t\t\t\tp.set_name(\"ata_smart_data/capabilities/offline_surface_scan_supported\", name, name);\n\t\t\t\tp.value = (hz::string_trim_copy(value) != \"No\");  // bool\n\n\n\t\t\t} else if (app_regex_partial_match(re_selftest_support, sv, {&value, &name})) {\n\t\t\t\tp.set_name(\"ata_smart_data/capabilities/self_tests_supported\", name, name);\n\t\t\t\tp.value = (hz::string_trim_copy(value) != \"No\");  // bool\n\n\t\t\t} else if (app_regex_partial_match(re_conv_selftest_support, sv, {&value, &name})) {\n\t\t\t\tp.set_name(\"ata_smart_data/capabilities/conveyance_self_test_supported\", name, name);\n\t\t\t\tp.value = (hz::string_trim_copy(value) != \"No\");  // bool\n\n\t\t\t} else if (app_regex_partial_match(re_selective_selftest_support, sv, {&value, &name})) {\n\t\t\t\tp.set_name(\"ata_smart_data/capabilities/selective_self_test_supported\", name, name);\n\t\t\t\tp.value = (hz::string_trim_copy(value) != \"No\");  // bool\n\n\n\t\t\t} else if (app_regex_partial_match(re_sct_status, sv, &name)) {\n\t\t\t\tp.set_name(\"ata_sct_capabilities/value/_present\", name, name);\n\t\t\t\tp.value = true;  // bool\n\n\t\t\t} else if (app_regex_partial_match(re_sct_control, sv, &name)) {\n\t\t\t\tp.set_name(\"ata_sct_capabilities/feature_control_supported\", name, name);\n\t\t\t\tp.value = true;  // bool\n\n\t\t\t} else if (app_regex_partial_match(re_sct_data, sv, &name)) {\n\t\t\t\tp.set_name(\"ata_sct_capabilities/data_table_supported\", name, name);\n\t\t\t\tp.value = true;  // bool\n\t\t\t}\n\n\t\t\tif (!p.empty())\n\t\t\t\tadd_property(p);\n\t\t}\n\n\t\treturn {};\n\t}\n\n\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Capability-section property has invalid value type.\\n\");\n\n\treturn hz::Unexpected(SmartctlParserError::DataError, \"Capability-section property has invalid value type.\");\n}\n\n\n\n\n\n// -------------------- Attributes\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section_data_subsection_attributes(const std::string& sub)\n{\n\tStorageProperty pt;  // template for easy copying\n\tpt.section = StoragePropertySection::AtaAttributes;\n\n\t// split to lines\n\tstd::vector<std::string> lines;\n\thz::string_split(sub, '\\n', lines, true);\n\n\t// Format notes:\n\t// * Before 5.1-14, no UPDATED column was present in \"old\" format.\n\n\t// * Most, but not all attribute names are with underscores. However, I encountered one\n\t// named \"Head flying hours\" and there are slashes sometimes as well.\n\t// So, parse until we encounter the next column. Supported in Old format only.\n\n\t// * SSD drives may show \"---\" in value/worst/threshold fields.\n\n\t// \"old\" format (used in -a):\n/*\nSMART Attributes Data Structure revision number: 4\nVendor Specific SMART Attributes with Thresholds:\nID# ATTRIBUTE_NAME          FLAG     VALUE WORST THRESH TYPE      UPDATED  WHEN_FAILED RAW_VALUE\n  5 Reallocated_Sector_Ct   0x0032   100   100   ---    Old_age   Always       -       0\n  9 Power_On_Hours          0x0032   253   100   ---    Old_age   Always       -       1720\n*/\n\n\t// \"brief\" format (used in -x):\n/*\nSMART Attributes Data Structure revision number: 16\nVendor Specific SMART Attributes with Thresholds:\nID# ATTRIBUTE_NAME          FLAGS    VALUE WORST THRESH FAIL RAW_VALUE\n  1 Raw_Read_Error_Rate     PO-R--   100   100   062    -    0\n  2 Throughput_Performance  P-S---   197   197   040    -    160\n194 Temperature_Celsius     -O----   222   222   000    -    27 (Min/Max 12/48)\n                            ||||||_ K auto-keep\n                            |||||__ C event count\n                            ||||___ R error rate\n                            |||____ S speed/performance\n                            ||_____ O updated online\n                            |______ P prefailure warning\n*/\n\tenum {\n\t\tFormatStyleOld,\n\t\tFormatStyleNoUpdated,  // old format without UPDATED column\n\t\tFormatStyleBrief\n\t};\n\n\tbool attr_found = false;  // at least one attribute was found\n\tint attr_format_style = FormatStyleOld;\n\n\tconst std::string space_re = \"[ \\\\t]+\";\n\n\tconst std::string old_flag_re = \"(0x[a-fA-F0-9]+)\";\n\tconst std::string brief_flag_re = \"([A-Z+-]{2,})\";\n\t// We allow name with spaces only in the old format, not in brief.\n\t// This has to do with the name end detection - it's either 0x (flag's start) in the old format,\n\t// or a space in the brief format.\n\tconst std::string old_base_re = R\"([ \\t]*([0-9]+) ([^ \\t\\n]+(?:[^0-9\\t\\n]+)*))\" + space_re + old_flag_re + space_re;  // ID / name / flag\n\tconst std::string brief_base_re = R\"([ \\t]*([0-9]+) ([^ \\t\\n]+))\" + space_re + brief_flag_re + space_re;  // ID / name / flag\n\tconst std::string vals_re = \"([0-9-]+)\" + space_re + \"([0-9-]+)\" + space_re + \"([0-9-]+)\" + space_re;  // value / worst / threshold\n\tconst std::string type_re = \"([^ \\\\t\\\\n]+)\" + space_re;\n\tconst std::string updated_re = \"([^ \\\\t\\\\n]+)\" + space_re;\n\tconst std::string failed_re = \"([^ \\\\t\\\\n]+)\" + space_re;\n\tconst std::string raw_re = \"(.+)[ \\\\t]*\";\n\n\tconst auto re_old_up = app_regex_re(\"/\" + old_base_re + vals_re + type_re + updated_re + failed_re + raw_re + \"/mi\");\n\tconst auto re_old_noup = app_regex_re(\"/\" + old_base_re + vals_re + type_re + failed_re + raw_re + \"/mi\");\n\tconst auto re_brief = app_regex_re(\"/\" + brief_base_re + vals_re + failed_re + raw_re + \"/mi\");\n\n\tconst auto re_flag_descr = app_regex_re(\"/^[\\\\t ]+\\\\|/mi\");\n\n\n\tfor (const auto& line : lines) {\n\t\t// skip the non-informative lines\n\t\tif (line.empty() || app_regex_partial_match(\"/SMART Attributes with Thresholds/mi\", line))\n\t\t\tcontinue;\n\n\t\tif (app_regex_partial_match(\"/ATTRIBUTE_NAME/mi\", line)) {\n\t\t\t// detect format type\n\t\t\tif (!app_regex_partial_match(\"/WHEN_FAILED/mi\", line)) {\n\t\t\t\tattr_format_style = FormatStyleBrief;\n\t\t\t} else if (!app_regex_partial_match(\"/UPDATED/mi\", line)) {\n\t\t\t\tattr_format_style = FormatStyleNoUpdated;\n\t\t\t}\n\t\t\tcontinue;  // we don't need this line\n\t\t}\n\n\t\tif (app_regex_partial_match(re_flag_descr, line)) {\n\t\t\tcontinue;  // skip flag description lines\n\t\t}\n\n\t\tif (app_regex_partial_match(\"/Data Structure revision number/mi\", line)) {\n\t\t\tconst auto re = app_regex_re(\"/^([^:\\\\n]+):[ \\\\t]*(.*)$/mi\");\n\t\t\tstd::string name, value;\n\t\t\tif (app_regex_partial_match(re, line, {&name, &value})) {\n\t\t\t\thz::string_trim(name);\n\t\t\t\thz::string_trim(value);\n\t\t\t\tint64_t value_num = 0;\n\t\t\t\thz::string_is_numeric_nolocale(value, value_num, false);\n\n\t\t\t\tStorageProperty p(pt);\n\t\t\t\tp.set_name(\"ata_smart_attributes/revision\", name, name);\n\t\t\t\tp.reported_value = value;\n\t\t\t\tp.value = value_num;  // integer-type value\n\n\t\t\t\tadd_property(p);\n\t\t\t\tattr_found = true;\n\t\t\t}\n\n\n\t\t} else {  // A line in attribute table\n\n\t\t\tstd::string id, name, flag, value, worst, threshold, attr_type,\n\t\t\t\t\tupdate_type, when_failed, raw_value;\n\n\t\t\tbool matched = true;\n\n\t\t\tif (attr_format_style == FormatStyleOld) {\n\t\t\t\tif (!app_regex_full_match(re_old_up, line,\n\t\t\t\t\t\t{&id, &name, &flag, &value, &worst, &threshold, &attr_type, &update_type, &when_failed, &raw_value})) {\n\t\t\t\t\tmatched = false;\n\t\t\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Cannot parse attribute line.\\n\");\n\t\t\t\t}\n\n\t\t\t} else if (attr_format_style == FormatStyleNoUpdated) {\n\t\t\t\tif (!app_regex_full_match(re_old_noup, line,\n\t\t\t\t\t\t{&id, &name, &flag, &value, &worst, &threshold, &attr_type, &when_failed, &raw_value})) {\n\t\t\t\t\tmatched = false;\n\t\t\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Cannot parse attribute line.\\n\");\n\t\t\t\t}\n\n\t\t\t} else if (attr_format_style == FormatStyleBrief) {\n\t\t\t\tif (!app_regex_full_match(re_brief, line,\n\t\t\t\t\t\t{&id, &name, &flag, &value, &worst, &threshold, &when_failed, &raw_value})) {\n\t\t\t\t\tmatched = false;\n\t\t\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Cannot parse attribute line.\\n\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!matched) {\n\t\t\t\tdebug_out_dump(\"app\", \"------------ Begin unparsable attribute line dump ------------\\n\");\n\t\t\t\tdebug_out_dump(\"app\", line << \"\\n\");\n\t\t\t\tdebug_out_dump(\"app\", \"------------- End unparsable attribute line dump -------------\\n\");\n\t\t\t\tcontinue;  // continue to the next line\n\t\t\t}\n\n\n\t\t\tAtaStorageAttribute attr;\n\t\t\thz::string_is_numeric_nolocale(hz::string_trim_copy(id), attr.id, true, 10);\n\t\t\tattr.flag = hz::string_trim_copy(flag);\n\t\t\tuint8_t norm_value = 0, worst_value = 0, threshold_value = 0;\n\n\t\t\tif (hz::string_is_numeric_nolocale(hz::string_trim_copy(value), norm_value, true, 10)) {\n\t\t\t\tattr.value = norm_value;\n\t\t\t}\n\t\t\tif (hz::string_is_numeric_nolocale(hz::string_trim_copy(worst), worst_value, true, 10)) {\n\t\t\t\tattr.worst = worst_value;\n\t\t\t}\n\t\t\tif (hz::string_is_numeric_nolocale(hz::string_trim_copy(threshold), threshold_value, true, 10)) {\n\t\t\t\tattr.threshold = threshold_value;\n\t\t\t}\n\n\t\t\tif (attr_format_style == FormatStyleBrief) {\n\t\t\t\tattr.attr_type = app_regex_partial_match(\"/P/\", attr.flag) ? AtaStorageAttribute::AttributeType::Prefail : AtaStorageAttribute::AttributeType::OldAge;\n\t\t\t} else {\n\t\t\t\tif (attr_type == \"Pre-fail\") {\n\t\t\t\t\tattr.attr_type = AtaStorageAttribute::AttributeType::Prefail;\n\t\t\t\t} else if (attr_type == \"Old_age\") {\n\t\t\t\t\tattr.attr_type = AtaStorageAttribute::AttributeType::OldAge;\n\t\t\t\t} else {\n\t\t\t\t\tattr.attr_type = AtaStorageAttribute::AttributeType::Unknown;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (attr_format_style == FormatStyleBrief) {\n\t\t\t\tattr.update_type = app_regex_partial_match(\"/O/\", attr.flag) ? AtaStorageAttribute::UpdateType::Always : AtaStorageAttribute::UpdateType::Offline;\n\t\t\t} else {\n\t\t\t\tif (update_type == \"Always\") {\n\t\t\t\t\tattr.update_type = AtaStorageAttribute::UpdateType::Always;\n\t\t\t\t} else if (update_type == \"Offline\") {\n\t\t\t\t\tattr.update_type = AtaStorageAttribute::UpdateType::Offline;\n\t\t\t\t} else {\n\t\t\t\t\tattr.update_type = AtaStorageAttribute::UpdateType::Unknown;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tattr.when_failed = AtaStorageAttribute::FailTime::Unknown;\n\t\t\thz::string_trim(when_failed);\n\t\t\tif (when_failed == \"-\") {\n\t\t\t\tattr.when_failed = AtaStorageAttribute::FailTime::None;\n\t\t\t} else if (when_failed == \"In_the_past\" || when_failed == \"Past\") {  // the second one if from brief format\n\t\t\t\tattr.when_failed = AtaStorageAttribute::FailTime::Past;\n\t\t\t} else if (when_failed == \"FAILING_NOW\" || when_failed == \"NOW\") {  // the second one if from brief format\n\t\t\t\tattr.when_failed = AtaStorageAttribute::FailTime::Now;\n\t\t\t}\n\n\t\t\tattr.raw_value = hz::string_trim_copy(raw_value);\n\t\t\thz::string_is_numeric_nolocale(hz::string_trim_copy(raw_value), attr.raw_value_int, false);  // same as raw_value, but parsed as int.\n\n\t\t\tStorageProperty p(pt);\n\t\t\tp.set_name(hz::string_trim_copy(name), hz::string_trim_copy(name), hz::string_trim_copy(name));\n\t\t\tp.reported_value = line;  // use the whole line here\n\t\t\tp.value = attr;  // attribute-type value;\n\n\t\t\tadd_property(p);\n\t\t\tattr_found = true;\n\t\t}\n\n\t}\n\n\tif (!attr_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::DataError, \"No attributes found in Attributes section.\");\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section_data_subsection_directory_log(const std::string& sub)\n{\n\tStorageProperty pt;  // template for easy copying\n\tpt.section = StoragePropertySection::DirectoryLog;\n\n\t// Directory log contains:\n/*\nGeneral Purpose Log Directory Version 1\nSMART           Log Directory Version 1 [multi-sector log support]\nAddress    Access  R/W   Size  Description\n0x00       GPL,SL  R/O      1  Log Directory\n0x01           SL  R/O      1  Summary SMART error log\n0x02           SL  R/O      5  Comprehensive SMART error log\n0x03       GPL     R/O      6  Ext. Comprehensive SMART error log\n0x04       GPL,SL  R/O      8  Device Statistics log\n0x06           SL  R/O      1  SMART self-test log\n0x07       GPL     R/O      1  Extended self-test log\n0x09           SL  R/W      1  Selective self-test log\n0x0a       GPL     R/W      8  Device Statistics Notification\n*/\n//\tbool data_found = false;  // true if something was found.\n\n\t// the whole subsection\n\t{\n\t\tStorageProperty p(pt);\n\t\tp.set_name(\"ata_log_directory/_merged\", \"General Purpose Log Directory\");\n\t\tp.reported_value = sub;\n\t\tp.value = p.reported_value;  // string-type value\n\n\t\tadd_property(p);\n//\t\tdata_found = true;\n\t}\n\n\t// supported / unsupported\n\t{\n\t\tStorageProperty p(pt);\n\t\tp.set_name(\"_text_only/directory_log_supported\", \"General Purpose Log Directory supported\");\n\n\t\t// p.reported_value;  // nothing\n\t\tp.value = !app_regex_partial_match(\"/General Purpose Log Directory not supported/mi\", sub);  // bool\n\n\t\tadd_property(p);\n//\t\tdata_found = true;\n\t}\n\n//\treturn data_found;\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section_data_subsection_error_log(const std::string& sub)\n{\n\tStorageProperty pt;  // template for easy copying\n\tpt.section = StoragePropertySection::AtaErrorLog;\n\n\t// Note: The format of this section was changed somewhere between 5.0-x and 5.30.\n\t// The old format is doesn't really give any useful info, and whatever's left is somewhat\n\t// parsable by this parser. Can't really improve that.\n\t// Also, type (e.g. UNC) is not always present (depends on the drive I guess).\n\n\t// Sample \"-l xerror\" output:\n/*\nSMART Extended Comprehensive Error Log Version: 1 (1 sectors)\nDevice Error Count: 1\n\tCR     = Command Register\n\tFEATR  = Features Register\n\tCOUNT  = Count (was: Sector Count) Register\n\tLBA_48 = Upper bytes of LBA High/Mid/Low Registers ]  ATA-8\n\tLH     = LBA High (was: Cylinder High) Register    ]   LBA\n\tLM     = LBA Mid (was: Cylinder Low) Register      ] Register\n\tLL     = LBA Low (was: Sector Number) Register     ]\n\tDV     = Device (was: Device/Head) Register\n\tDC     = Device Control Register\n\tER     = Error register\n\tST     = Status register\nPowered_Up_Time is measured from power on, and printed as\nDDd+hh:mm:SS.sss where DD=days, hh=hours, mm=minutes,\nSS=sec, and sss=millisec. It \"wraps\" after 49.710 days.\n\nError 1 [0] occurred at disk power-on lifetime: 1 hours (0 days + 1 hours)\n  When the command that caused the error occurred, the device was active or idle.\n\n  After command completion occurred, registers were:\n  ER -- ST COUNT  LBA_48  LH LM LL DV DC\n  -- -- -- == -- == == == -- -- -- -- --\n  02 -- 51 00 00 00 00 00 00 00 00 00 00  Error: TK0NF\n\n  Commands leading to the command that caused the error were:\n  CR FEATR COUNT  LBA_48  LH LM LL DV DC  Powered_Up_Time  Command/Feature_Name\n  -- == -- == -- == == == -- -- -- -- --  ---------------  --------------------\n  10 00 00 00 01 00 00 00 00 03 34 e0 ff     00:00:17.305  RECALIBRATE [OBS-4]\n  10 00 00 00 01 00 00 00 00 03 34 e0 08     00:00:17.138  RECALIBRATE [OBS-4]\n  91 40 00 01 3f 00 00 01 00 03 34 af 08     00:00:17.138  INITIALIZE DEVICE PARAMETERS [OBS-6]\n  c4 00 40 00 00 00 00 3f 00 00 00 e0 04     00:00:16.934  READ MULTIPLE\n  c4 00 40 00 01 00 00 3f 00 00 00 e0 00     00:00:07.959  READ MULTIPLE\n*/\n\tbool data_found = false;\n\n\t// Error log version\n\t{\n\t\t// \"SMART Error Log Version: 1\"\n\t\t// \"SMART Extended Comprehensive Error Log Version: 1 (1 sectors)\"\n\t\tconst auto re = app_regex_re(\"/^(SMART (Extended Comprehensive )?Error Log Version): ([0-9]+).*?$/mi\");\n\n\t\tstd::string name, value;\n\t\tif (app_regex_partial_match(re, sub, {&name, &value})) {\n\t\t\thz::string_trim(name);\n\t\t\thz::string_trim(value);\n\n\t\t\tStorageProperty p(pt);\n\t\t\t// Note: For extended logs, the path has \"extended\".\n\t\t\t// For standard logs, the path has \"summary\" (?)\n\t\t\tp.set_name(\"ata_smart_error_log/extended/revision\", name, name);\n\t\t\tp.reported_value = value;\n\n\t\t\tint64_t value_num = 0;\n\t\t\thz::string_is_numeric_nolocale(value, value_num, false);\n\t\t\tp.value = value_num;  // integer\n\n\t\t\tadd_property(p);\n\t\t\tdata_found = true;\n\t\t}\n\t}\n\n\t// Error log support\n\t{\n\t\tconst auto re = app_regex_re(\"/^(Warning: device does not support Error Logging)|(SMART Error Log not supported)$/mi\");\n\n\t\tif (app_regex_partial_match(re, sub)) {\n\t\t\tStorageProperty p(pt);\n\t\t\tp.set_name(\"_text_only/ata_smart_error_log/_not_present\", \"Error Log not supported\");\n\t\t\tp.displayable_name = \"Warning\";\n\t\t\tp.readable_value = \"Device does not support error logging\";\n\t\t\tadd_property(p);\n\t\t}\n\t}\n\n\t// Error log entry count\n\t{\n\t\t// note: these represent the same information\n\t\tconst auto re1 = app_regex_re(\"/^(?:ATA|Device) Error Count:[ \\\\t]*([0-9]+)/mi\");\n\t\tconst auto re2 = app_regex_re(\"/^No Errors Logged$/mi\");\n\n\t\tstd::string value;\n\t\tif (app_regex_partial_match(re1, sub, &value) || app_regex_partial_match(re2, sub)) {\n\t\t\thz::string_trim(value);\n\n\t\t\tStorageProperty p(pt);\n\t\t\t// Note: For Extended Error Log, the path has \"extended\".\n\t\t\t// For simple error log, the path has \"summary\".\n\t\t\tp.set_name(\"ata_smart_error_log/extended/count\", \"ATA Error Count\");\n\t\t\tp.reported_value = value;\n\n\t\t\tint64_t value_num = 0;\n\t\t\tif (!app_regex_partial_match(re2, sub)) {  // if no errors, when value should be zero. otherwise, this:\n\t\t\t\thz::string_is_numeric_nolocale(value, value_num, false);\n\t\t\t}\n\t\t\tp.value = value_num;  // integer\n\n\t\t\tadd_property(p);\n\t\t\tdata_found = true;\n\t\t}\n\t}\n\n\t// Individual errors\n\t{\n\t\t// Split by blocks:\n\t\t// \"Error 1 [0] occurred at disk power-on lifetime: 1 hours (0 days + 1 hours)\"\n\t\t// \"Error 25 occurred at disk power-on lifetime: 14799 hours\"\n\t\tconst auto re_block = app_regex_re(\n\t\t\t\tR\"(/^((Error[ \\t]*([0-9]+))[ \\t]*(?:\\[[0-9]+\\][ \\t])?occurred at disk power-on lifetime:[ \\t]*([0-9]+) hours(?:[^\\n]*)?.*(?:\\n(?:  |\\n  ).*)*)/mi)\");\n\n\t\t// \"  When the command that caused the error occurred, the device was active or idle.\"\n\t\t// Note: For \"in an unknown state\" - remove first two words.\n\t\tconst auto re_state = app_regex_re(R\"(/occurred, the device was[ \\t]*(?: in)?(?: an?)?[ \\t]+([^.\\n]*)\\.?/mi)\");\n\t\t// \"  84 51 2c 71 cd 3f e6  Error: ICRC, ABRT 44 sectors at LBA = 0x063fcd71 = 104844657\"\n\t\t// \"  40 51 00 f5 41 61 e0  Error: UNC at LBA = 0x006141f5 = 6373877\"\n\t\t// \"  02 -- 51 00 00 00 00 00 00 00 00 00 00  Error: TK0NF\"\n\t\tconst auto re_type = app_regex_re(R\"(/[ \\t]+Error:[ \\t]*([ ,a-z0-9]+)(?:[ \\t]+((?:[0-9]+|at )[ \\t]*.*))?$/mi)\");\n\n\t\tfor (auto it = std::sregex_iterator(sub.begin(), sub.end(), re_block), end = std::sregex_iterator(); it != end; ++it) {\n\t\t\tconst std::string block = hz::string_trim_copy(it->str(1));\n\t\t\tconst std::string name = hz::string_trim_copy(it->str(2));\n\t\t\tconst std::string value_num = hz::string_trim_copy(it->str(3));\n\t\t\tconst std::string value_time = hz::string_trim_copy(it->str(4));\n\n\t\t\t// debug_out_dump(\"app\", \"\\nBLOCK -------------------------------\\n\" << block);\n\n\t\t\tstd::string state, etypes_str, emore;\n\t\t\tapp_regex_partial_match(re_state, block, &state);\n\t\t\tapp_regex_partial_match(re_type, block, {&etypes_str, &emore});\n\n\t\t\tStorageProperty p(pt);\n\t\t\tstd::string gen_name = hz::string_trim_copy(name);\n\t\t\tp.set_name(gen_name, gen_name, gen_name);  // \"Error 6\"\n\t\t\tp.reported_value = block;\n\n\t\t\tAtaStorageErrorBlock eb;\n\t\t\thz::string_is_numeric_nolocale(value_num, eb.error_num, false);\n\t\t\thz::string_is_numeric_nolocale(value_time, eb.lifetime_hours, false);\n\n\t\t\tstd::vector<std::string> etypes;\n\t\t\thz::string_split(etypes_str, \",\", etypes, true);\n\t\t\tfor (auto&& v : etypes) {\n\t\t\t\thz::string_trim(v);\n\t\t\t}\n\n\t\t\teb.device_state = hz::string_trim_copy(state);\n\t\t\teb.reported_types = etypes;\n\t\t\teb.type_more_info = hz::string_trim_copy(emore);\n\n\t\t\tp.value = eb;  // Error block value\n\n\t\t\tadd_property(p);\n\t\t\tdata_found = true;\n\t\t}\n\n\n\t}\n\n\t// the whole subsection\n\t{\n\t\tStorageProperty p(pt);\n\t\tp.set_name(\"ata_smart_error_log/_merged\", \"SMART Error Log\");\n\t\tp.reported_value = sub;\n\t\tp.value = p.reported_value;  // string-type value\n\n\t\tadd_property(p);\n\t\t// data_found = true;\n\t}\n\n\t// We may further split this subsection by Error blocks, but it's unnecessary -\n\t// the data is too advanced to be of any use if parsed.\n\n\tif (!data_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::DataError, \"No error log entries found in Error Log section.\");\n\t}\n\n\treturn {};\n}\n\n\n\n\n// -------------------- Selftest Log\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section_data_subsection_selftest_log(const std::string& sub)\n{\n\tStorageProperty pt;  // template for easy copying\n\tpt.section = StoragePropertySection::SelftestLog;\n\n\t// Self-test log contains:\n\t// * structure revision number\n\t// * a list of current / previous tests performed, with each having:\n\t// num (the higher - the older).\n\t// test_description (Extended offline / Short offline / Conveyance offline / ... ?)\n\t// status (completed without error, interrupted (reason), aborted, fatal or unknown error, ?)\n\t// remaining % (this will be 00% for completed, and may be > 0 for interrupted).\n\t// lifetime (hours) - int.\n\t// LBA_of_first_error - \"-\" or int ?\n/*\nSMART Extended Self-test Log Version: 1 (1 sectors)\nNum  Test_Description    Status                  Remaining  LifeTime(hours)  LBA_of_first_error\n# 1  Extended offline    Completed without error       00%     43116         -\n# 2  Extended offline    Completed without error       00%     29867         -\n# 3  Extended offline    Completed without error       00%     19477         -\n*/\n\n\tbool data_found = false;  // true if something was found.\n\n\t// The whole subsection\n\t{\n\t\tStorageProperty p(pt);\n\t\tp.set_name(\"ata_smart_self_test_log/_merged\", \"SMART Self-Test Log\");\n\t\tp.reported_value = sub;\n\t\tp.value = p.reported_value;  // string-type value\n\n\t\tadd_property(p);\n//\t\tdata_found = true;\n\t}\n\n\n\t// Self-test log support\n\t{\n\t\tconst auto re = app_regex_re(\"/^(Warning: device does not support Self Test Logging)|(SMART Self-test Log not supported)$/mi\");\n\n\t\tif (app_regex_partial_match(re, sub)) {\n\t\t\tStorageProperty p(pt);\n\t\t\tp.set_name(\"ata_smart_self_test_log/_present\", \"Self-test Log supported\");\n\t\t\tp.displayable_name = \"Warning\";\n\t\t\tp.readable_value = \"Device does not support self-test logging\";\n\t\t\tadd_property(p);\n\n\t\t\tdata_found = true;\n\t\t}\n\t}\n\n\t// Self-test log version\n\t{\n\t\t// SMART Self-test log structure revision number 1\n\t\t// SMART Extended Self-test Log Version: 1 (1 sectors)\n\t\tconst auto re1 = app_regex_re(R\"(/(SMART Self-test log structure[^\\n0-9]*)([^ \\n]+)[ \\t]*$/mi)\");\n\t\tconst auto re1_ex = app_regex_re(\"/(SMART Extended Self-test Log Version): ([0-9]+).*$/mi\");\n\t\t// older smartctl (pre 5.1-16)\n\t\tconst auto re2 = app_regex_re(R\"(/(SMART Self-test log, version number[^\\n0-9]*)([^ \\n]+)[ \\t]*$/mi)\");\n\n\t\tstd::string name, value;\n\t\tif (app_regex_partial_match(re1, sub, {&name, &value})\n\t\t\t\t|| app_regex_partial_match(re1_ex, sub, {&name, &value})\n\t\t\t\t|| app_regex_partial_match(re2, sub, {&name, &value})) {\n\t\t\thz::string_trim(value);\n\n\t\t\tStorageProperty p(pt);\n\t\t\tp.set_name(\"ata_smart_self_test_log/extended/revision\", hz::string_trim_copy(name));\n\t\t\tp.reported_value = value;\n\n\t\t\tint64_t value_num = 0;\n\t\t\thz::string_is_numeric_nolocale(value, value_num, false);\n\t\t\tp.value = value_num;  // integer\n\n\t\t\tadd_property(p);\n\t\t\tdata_found = true;\n\t\t}\n\t}\n\n\n\tint64_t test_count = 0;  // type is of p.value_integer\n\n\n\t// individual entries\n\t{\n\t\t// split by columns.\n\t\t// num, type, status, remaining, hours, lba (optional).\n\t\tconst auto re = app_regex_re(\n\t\t\t\tR\"(/^(#[ \\t]*([0-9]+)[ \\t]+(\\S+(?: \\S+)*)  [ \\t]*(\\S.*) [ \\t]*([0-9]+%)  [ \\t]*([0-9]+)[ \\t]*((?:  [ \\t]*\\S.*)?))$/mi)\");\n\n\t\tfor (auto it = std::sregex_iterator(sub.begin(), sub.end(), re), end = std::sregex_iterator(); it != end; ++it) {\n\t\t\tconst std::string line = hz::string_trim_copy(it->str(1));\n\t\t\tconst std::string num = hz::string_trim_copy(it->str(2));\n\t\t\tconst std::string type = hz::string_trim_copy(it->str(3));\n\t\t\tconst std::string status_str = hz::string_trim_copy(it->str(4));\n\t\t\tconst std::string remaining = hz::string_trim_copy(it->str(5));\n\t\t\tconst std::string hours = hz::string_trim_copy(it->str(6));\n\t\t\tconst std::string lba = hz::string_trim_copy(it->str(7));\n\n\t\t\tStorageProperty p(pt);\n\t\t\tp.set_name(fmt::format(\"ata_smart_self_test_log/entry/{}\", num), \"Self-test entry \" + num);\n\t\t\tp.reported_value = hz::string_trim_copy(line);\n\n\t\t\tAtaStorageSelftestEntry sse;\n\n\t\t\thz::string_is_numeric_nolocale(num, sse.test_num, false);\n\t\t\thz::string_is_numeric_nolocale(hz::string_trim_copy(remaining), sse.remaining_percent, false);\n\t\t\thz::string_is_numeric_nolocale(hz::string_trim_copy(hours), sse.lifetime_hours, false);\n\n\t\t\tsse.type = hz::string_trim_copy(type);\n\t\t\tsse.lba_of_first_error = hz::string_trim_copy(lba);\n\t\t\t// old smartctls didn't print anything for lba if none, but newer ones print \"-\". normalize.\n\t\t\tif (sse.lba_of_first_error.empty())\n\t\t\t\tsse.lba_of_first_error = \"-\";\n\n\t\t\tAtaStorageSelftestEntry::Status status = AtaStorageSelftestEntry::Status::Unknown;\n\n\t\t\t// don't match end - some of them are not complete here\n\t\t\tif (app_regex_partial_match(\"/^Completed without error/mi\", status_str)) {\n\t\t\t\tstatus = AtaStorageSelftestEntry::Status::CompletedNoError;\n\t\t\t} else if (app_regex_partial_match(\"/^Aborted by host/mi\", status_str)) {\n\t\t\t\tstatus = AtaStorageSelftestEntry::Status::AbortedByHost;\n\t\t\t} else if (app_regex_partial_match(\"/^Interrupted \\\\(host reset\\\\)/mi\", status_str)) {\n\t\t\t\tstatus = AtaStorageSelftestEntry::Status::Interrupted;\n\t\t\t} else if (app_regex_partial_match(\"/^Fatal or unknown error/mi\", status_str)) {\n\t\t\t\tstatus = AtaStorageSelftestEntry::Status::FatalOrUnknown;\n\t\t\t} else if (app_regex_partial_match(\"/^Completed: unknown failure/mi\", status_str)) {\n\t\t\t\tstatus = AtaStorageSelftestEntry::Status::ComplUnknownFailure;\n\t\t\t} else if (app_regex_partial_match(\"/^Completed: electrical failure/mi\", status_str)) {\n\t\t\t\tstatus = AtaStorageSelftestEntry::Status::ComplElectricalFailure;\n\t\t\t} else if (app_regex_partial_match(\"/^Completed: servo\\\\/seek failure/mi\", status_str)) {\n\t\t\t\tstatus = AtaStorageSelftestEntry::Status::ComplServoFailure;\n\t\t\t} else if (app_regex_partial_match(\"/^Completed: read failure/mi\", status_str)) {\n\t\t\t\tstatus = AtaStorageSelftestEntry::Status::ComplReadFailure;\n\t\t\t} else if (app_regex_partial_match(\"/^Completed: handling damage/mi\", status_str)) {\n\t\t\t\tstatus = AtaStorageSelftestEntry::Status::ComplHandlingDamage;\n\t\t\t} else if (app_regex_partial_match(\"/^Self-test routine in progress/mi\", status_str)) {\n\t\t\t\tstatus = AtaStorageSelftestEntry::Status::InProgress;\n\t\t\t} else if (app_regex_partial_match(\"/^Unknown\\\\/reserved test status/mi\", status_str)) {\n\t\t\t\tstatus = AtaStorageSelftestEntry::Status::Reserved;\n\t\t\t}\n\n\t\t\tsse.status_str = status_str;\n\t\t\tsse.status = status;\n\n\t\t\tp.value = sse;  // AtaStorageSelftestEntry value\n\n\t\t\tadd_property(p);\n\t\t\tdata_found = true;\n\n\t\t\t++test_count;\n\t\t}\n\t}\n\n\n\t// number of tests.\n\t// Note: \"No self-tests have been logged\" is sometimes absent, so don't rely on it.\n\t{\n\t\tStorageProperty p(pt);\n\t\tp.set_name(\"ata_smart_self_test_log/extended/table/count\", \"Number of entries in self-test log\");\n\t\t// p.reported_value;  // nothing\n\t\tp.value = test_count;  // integer\n\n\t\tadd_property(p);\n\n\t\tif (test_count > 0) {\n\t\t\tdata_found = true;\n\t\t}\n\t}\n\n\tif (!data_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::DataError, \"No self-test log entries found in Self-test Log section.\");\n\t}\n\n\treturn {};\n}\n\n\n\n\n// -------------------- Selective Selftest Log\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section_data_subsection_selective_selftest_log(const std::string& sub)\n{\n\tStorageProperty pt;  // template for easy copying\n\tpt.section = StoragePropertySection::SelectiveSelftestLog;\n\n\t// Selective self-test log contains:\n/*\nSMART Selective self-test log data structure revision number 1\n SPAN  MIN_LBA  MAX_LBA  CURRENT_TEST_STATUS\n    1        0        0  Not_testing\n    2        0        0  Not_testing\n    3        0        0  Not_testing\n    4        0        0  Not_testing\n    5        0        0  Not_testing\nSelective self-test flags (0x0):\n  After scanning selected spans, do NOT read-scan remainder of disk.\nIf Selective self-test is pending on power-up, resume after 0 minute delay.\n*/\n\n\tbool data_found = false;  // true if something was found.\n\n\t// the whole subsection\n\t{\n\t\tStorageProperty p(pt);\n\t\tp.set_name(\"ata_smart_selective_self_test_log/_merged\", \"SMART selective self-test log\");\n\t\tp.reported_value = sub;\n\t\tp.value = p.reported_value;  // string-type value\n\n\t\tadd_property(p);\n\t\t// data_found = true;\n\t}\n\n\t// supported / unsupported\n\t{\n\t\tStorageProperty p(pt);\n\t\tp.set_name(\"ata_smart_data/capabilities/selective_self_test_supported\", \"Selective self-tests supported\");\n\n\t\t// p.reported_value;  // nothing\n\t\tp.value = !app_regex_partial_match(\"/Device does not support Selective Self Tests\\\\/Logging/mi\", sub);  // bool\n\n\t\tadd_property(p);\n\n\t\tif (!p.get_value<bool>()) {\n\t\t\tdata_found = true;\n\t\t}\n\t}\n\n\tif (!data_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::DataError, \"No selective self-test log entries found in Selective Self-test Log section.\");\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section_data_subsection_scttemp_log(const std::string& sub)\n{\n\tStorageProperty pt;  // template for easy copying\n\tpt.section = StoragePropertySection::TemperatureLog;\n\n\t// scttemp log contains:\n/*\nSCT Status Version:                  3\nSCT Version (vendor specific):       258 (0x0102)\nSCT Support Level:                   1\nDevice State:                        Active (0)\nCurrent Temperature:                    39 Celsius\nPower Cycle Min/Max Temperature:     25/39 Celsius\nLifetime    Min/Max Temperature:     17/50 Celsius\nUnder/Over Temperature Limit Count:   0/0\nVendor specific:\n01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n\nSCT Temperature History Version:     2\nTemperature Sampling Period:         1 minute\nTemperature Logging Interval:        1 minute\nMin/Max recommended Temperature:      0/60 Celsius\nMin/Max Temperature Limit:           -41/85 Celsius\nTemperature History Size (Index):    478 (361)\n\nIndex    Estimated Time   Temperature Celsius\n 362    2017-08-29 08:43    38  *******************\n ...    ..(119 skipped).    ..  *******************\n   4    2017-08-29 10:43    38  *******************\n   5    2017-08-29 10:44    39  ********************\n ...    ..( 91 skipped).    ..  ********************\n  97    2017-08-29 12:16    39  ********************\n  98    2017-08-29 12:17     ?  -\n  99    2017-08-29 12:18    25  ******\n*/\n\tbool data_found = false;  // true if something was found.\n\n\t// the whole subsection\n\t{\n\t\tStorageProperty p(pt);\n\t\tp.set_name(\"ata_sct_status/_and/ata_sct_temperature_history/_merged\", \"SCT temperature log\");\n\t\tp.reported_value = sub;\n\t\tp.value = p.reported_value;  // string-type value\n\n\t\tadd_property(p);\n//\t\tdata_found = true;\n\t}\n\n\t// supported / unsupported\n\t{\n\t\tStorageProperty p(pt);\n\t\tp.set_name(\"_text_only/ata_sct_status/_not_present\", \"SCT commands unsupported\");\n\n\t\t// p.reported_value;  // nothing\n\t\tp.value = app_regex_partial_match(\"/(SCT Commands not supported)|(SCT Data Table command not supported)/mi\", sub);  // bool\n\n\t\tadd_property(p);\n\n\t\tif (p.get_value<bool>()) {\n\t\t\tdata_found = true;\n\t\t}\n\t}\n\n\t// Find current temperature\n\t{\n\t\tstd::string name, value;\n\t\tif (app_regex_partial_match(\"/^(Current Temperature):[ \\\\t]+(.*) Celsius$/mi\", sub, {&name, &value})) {\n\t\t\tStorageProperty p;\n\t\t\tp.section = StoragePropertySection::TemperatureLog;\n\t\t\tp.set_name(\"ata_sct_status/temperature/current\", \"Current Temperature\");\n\t\t\tp.reported_value = value;\n\t\t\tp.value = hz::string_to_number_nolocale<int64_t>(value);  // integer\n\t\t\tadd_property(p);\n\n\t\t\tdata_found = true;\n\t\t}\n\t}\n\n\tif (!data_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::DataError, \"No temperature log entries found in SCT Temperature Log section.\");\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section_data_subsection_scterc_log(const std::string& sub)\n{\n\tStorageProperty pt;  // template for easy copying\n\tpt.section = StoragePropertySection::ErcLog;\n\n\t// scterc log contains:\n/*\nSCT Error Recovery Control:\n           Read:     70 (7.0 seconds)\n          Write:     70 (7.0 seconds)\n*/\n\tbool data_found = false;  // true if something was found.\n\n\t// the whole subsection\n\t{\n\t\tStorageProperty p(pt);\n\t\tp.set_name(\"ata_sct_erc/_merged\", \"SCT ERC log\");\n\t\tp.reported_value = sub;\n\t\tp.value = p.reported_value;  // string-type value\n\n\t\tadd_property(p);\n\t\t// data_found = true;\n\t}\n\n\t// supported / unsupported\n\t{\n\t\tStorageProperty p(pt);\n\t\tp.set_name(\"ata_sct_erc/_present\", \"SCT ERC supported\");\n\n\t\t// p.reported_value;  // nothing\n\t\tp.value = !app_regex_partial_match(\"/SCT Error Recovery Control command not supported/mi\", sub);  // bool\n\n\t\tadd_property(p);\n\n\t\tif (p.get_value<bool>()) {\n\t\t\tdata_found = true;\n\t\t}\n\t}\n\n\tif (!data_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::DataError, \"No entries found in SCT ERC Log section.\");\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section_data_subsection_devstat(const std::string& sub)\n{\n\tStorageProperty pt;  // template for easy copying\n\tpt.section = StoragePropertySection::Statistics;\n\n\t// devstat log contains:\n/*\nDevice Statistics (GP Log 0x04)\nPage  Offset Size        Value Flags Description\n0x01  =====  =               =  ===  == General Statistics (rev 1) ==\n0x01  0x008  4             569  -D-  Lifetime Power-On Resets\n0x01  0x010  4            6360  -D-  Power-on Hours\n0x01  0x018  6     17887792526  -D-  Logical Sectors Written\n0x01  0x020  6        51609191  -D-  Number of Write Commands\n0x01  0x028  6     17634698564  -D-  Logical Sectors Read\n0x01  0x030  6       179799274  -D-  Number of Read Commands\n0x01  0x038  6      1421163520  -D-  Date and Time TimeStamp\n0x01  0x048  2             202  ND-  Workload Utilization\n0x03  =====  =               =  ===  == Rotating Media Statistics (rev 1) ==\n0x03  0x008  4            6356  -D-  Spindle Motor Power-on Hours\n0x03  0x010  4            6356  -D-  Head Flying Hours\n                                |||_ C monitored condition met\n                                ||__ D supports DSN\n                                |___ N normalized value\n*/\n\n\t// Old (6.3) format:\n/*\nPage Offset Size         Value  Description\n  1  =====  =                =  == General Statistics (rev 2) ==\n  1  0x008  4                2  Lifetime Power-On Resets\n  1  0x018  6       1480289770  Logical Sectors Written\n  1  0x020  6         28939977  Number of Write Commands\n  1  0x028  6          3331436  Logical Sectors Read\n  1  0x030  6           122181  Number of Read Commands\n  1  0x038  6      12715200000  Date and Time TimeStamp\n  7  =====  =                =  == Solid State Device Statistics (rev 1) ==\n  7  0x008  1                1~ Percentage Used Endurance Indicator\n                              |_ ~ normalized value\n*/\n\n\tenum {\n\t\tFormatStyleNoFlags,  // 6.3 and older\n\t\tFormatStyleCurrent,  // 6.5\n\t};\n\n\n\t// supported / unsupported\n\tbool supported = true;\n\t{\n\t\tStorageProperty p(pt);\n\t\tp.set_name(\"ata_device_statistics/_present\", \"Device statistics supported\");\n\n\t\t// p.reported_value;  // nothing\n\t\tsupported = !app_regex_partial_match(R\"(/Device Statistics \\(GP\\/SMART Log 0x04\\) not supported/mi)\", sub);\n\t\tp.value = supported;  // bool\n\n\t\tadd_property(p);\n\t}\n\n\tif (!supported) {\n\t\treturn hz::Unexpected(SmartctlParserError::DataError, \"Device statistics not supported.\");\n\t}\n\n\tbool entries_found = false;  // at least one entry was found\n\n\t// split to lines\n\tstd::vector<std::string> lines;\n\thz::string_split(sub, '\\n', lines, true);\n\n\tconst std::string space_re = \"[ \\\\t]+\";\n\n\tconst std::string flag_re = \"([A-Z=-]{3,})\";\n\t// Page Offset Size Value Flags Description\n\tconst auto line_re = app_regex_re(\"/[ \\\\t]*([0-9a-z]+)\" + space_re + \"([0-9a-z=]+)\" + space_re + \"([0-9=]+)\"\n\t\t\t+ space_re + \"([0-9=-]+)\" + space_re + flag_re + space_re + \"(.+)/mi\");\n\t// Page Offset Size Value Description\n\tconst auto line_re_noflags = app_regex_re(\"/[ \\\\t]*([0-9a-z]+)\" + space_re + \"([0-9a-z=]+)\" + space_re + \"([0-9=]+)\"\n\t\t\t+ space_re + \"([0-9=~-]+)\" + space_re + \"(.+)/mi\");\n\t// flag description lines\n\tconst auto re_flag_descr = app_regex_re(\"/^[\\\\t ]+\\\\|/mi\");\n\n\n\tint devstat_format_style = FormatStyleCurrent;\n\n\tfor (const auto& line : lines) {\n\t\t// skip the non-informative lines\n\t\t// \"Device Statistics (GP Log 0x04)\"\n\t\t// \"Device Statistics (SMART Log 0x04)\"\n\t\t// \"ATA_SMART_READ_LOG failed: Undefined error: 0\"\n\t\t// \"Read Device Statistics page 0x00 failed\"\n\t\t// \"Read Device Statistics pages 0x00-0x07 failed\"\n\t\tif (line.empty()\n\t\t\t\t|| app_regex_partial_match(\"/^Device Statistics \\\\((?:GP|SMART) Log 0x04\\\\)/mi\", line)\n\t\t\t\t|| app_regex_partial_match(\"/^ATA_SMART_READ_LOG failed:/mi\", line)\n\t\t\t\t|| app_regex_partial_match(\"/^Read Device Statistics page (?:.+) failed/mi\", line)\n\t\t\t\t|| app_regex_partial_match(\"/^Read Device Statistics pages (?:.+) failed/mi\", line) ) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Table header\n\t\tif (app_regex_partial_match(\"/^Page[\\\\t ]+Offset[\\\\t ]+Size/mi\", line)) {\n\t\t\t// detect format type\n\t\t\tif (!app_regex_partial_match(\"/[\\\\t ]+Flags[\\\\t ]+/mi\", line)) {\n\t\t\t\tdevstat_format_style = FormatStyleNoFlags;\n\t\t\t}\n\t\t\tcontinue;  // we don't need this line\n\t\t}\n\n\t\tif (app_regex_partial_match(re_flag_descr, line)) {  // \"    |||_ C monitored condition met\", etc.\n\t\t\tcontinue;  // skip flag description lines\n\t\t}\n\n\t\tstd::string page, offset, size, value, flags, description;\n\n\t\tbool matched = false;\n\t\tif (devstat_format_style == FormatStyleCurrent) {\n\t\t\tif (app_regex_full_match(line_re, line, {&page, &offset, &size, &value, &flags, &description})) {\n\t\t\t\tmatched = true;\n\t\t\t}\n\t\t} else if (devstat_format_style == FormatStyleNoFlags) {\n\t\t\tif (app_regex_full_match(line_re_noflags, line, {&page, &offset, &size, &value, &description})) {\n\t\t\t\tmatched = true;\n\t\t\t\tflags = \"---\";  // to keep consistent with the Current format\n\t\t\t\tif (!value.empty() && value[value.size() - 1] == '~') {  // normalized\n\t\t\t\t\tflags = \"N--\";\n\t\t\t\t\tvalue.resize(value.size() - 1);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!matched) {\n\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Cannot parse devstat line.\\n\");\n\t\t\tdebug_out_dump(\"app\", \"------------ Begin unparsable devstat line dump ------------\\n\");\n\t\t\tdebug_out_dump(\"app\", line << \"\\n\");\n\t\t\tdebug_out_dump(\"app\", \"------------- End unparsable devstat line dump -------------\\n\");\n\t\t\tcontinue;  // continue to the next line\n\t\t}\n\n\n\t\tAtaStorageStatistic st;\n\t\tst.is_header = (hz::string_trim_copy(value) == \"=\");\n\t\tst.flags = st.is_header ? std::string() : hz::string_trim_copy(flags);\n\t\tst.value = st.is_header ? std::string() : hz::string_trim_copy(value);\n\t\thz::string_is_numeric_nolocale(st.value, st.value_int, false);\n\t\thz::string_is_numeric_nolocale(page, st.page, false, 16);\n\t\thz::string_is_numeric_nolocale(offset, st.offset, false, 16);\n\n\t\tif (st.is_header) {\n\t\t\tdescription = hz::string_trim_copy(hz::string_trim_copy(description, \"=\"));\n\t\t}\n\n\t\tStorageProperty p(pt);\n\t\tstd::string gen_name = hz::string_trim_copy(description);\n\t\tp.set_name(gen_name, gen_name, gen_name);\n\t\tp.reported_value = line;  // use the whole line here\n\t\tp.value = st;  // statistic-type value\n\n\t\tadd_property(p);\n\t\tentries_found = true;\n\t}\n\n\tif (!entries_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::DataError, \"No entries found in Device Statistics section.\");\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextAtaParser::parse_section_data_subsection_sataphy(const std::string& sub)\n{\n\tStorageProperty pt;  // template for easy copying\n\tpt.section = StoragePropertySection::PhyLog;\n\n\t// sataphy log contains:\n/*\nSATA Phy Event Counters (GP Log 0x11)\nID      Size     Value  Description\n0x0001  2            0  Command failed due to ICRC error\n0x0002  2            0  R_ERR response for data FIS\n0x0003  2            0  R_ERR response for device-to-host data FIS\n0x0004  2            0  R_ERR response for host-to-device data FIS\n0x0005  2            0  R_ERR response for non-data FIS\n0x0006  2            0  R_ERR response for device-to-host non-data FIS\n0x0007  2            0  R_ERR response for host-to-device non-data FIS\n0x0008  2            0  Device-to-host non-data FIS retries\n0x0009  2            1  Transition from drive PhyRdy to drive PhyNRdy\n*/\n\tbool data_found = false;  // true if something was found.\n\n\t// the whole subsection\n\t{\n\t\tStorageProperty p(pt);\n\t\tp.set_name(\"sata_phy_event_counters/_merged\", \"SATA Phy log\");\n\t\tp.reported_value = sub;\n\t\tp.value = p.reported_value;  // string-type value\n\n\t\tadd_property(p);\n\t\t// data_found = true;\n\t}\n\n\t// supported / unsupported\n\t{\n\t\tStorageProperty p(pt);\n\t\tp.set_name(\"sata_phy_event_counters/_present\", \"SATA Phy log supported\");\n\n\t\t// p.reported_value;  // nothing\n\t\tp.value = !app_regex_partial_match(\"/SATA Phy Event Counters \\\\(GP Log 0x11\\\\) not supported/mi\", sub)\n\t\t\t\t&& !app_regex_partial_match(\"/SATA Phy Event Counters with [0-9-]+ sectors not supported/mi\", sub);  // bool\n\n\t\tadd_property(p);\n\n\t\tif (p.get_value<bool>()) {\n\t\t\tdata_found = true;\n\t\t}\n\t}\n\n\tif (!data_found) {\n\t\treturn hz::Unexpected(SmartctlParserError::DataError, \"No entries found in SATA Phy Event Counters section.\");\n\t}\n\n\treturn {};\n}\n\n\n\nvoid SmartctlTextAtaParser::set_data_section_info(std::string s)\n{\n\tdata_section_info_ = std::move(s);\n}\n\n\n\nvoid SmartctlTextAtaParser::set_data_section_data(std::string s)\n{\n\tdata_section_data_ = std::move(s);\n}\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_text_ata_parser.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2022 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef SMARTCTL_TEXT_ATA_PARSER_H\n#define SMARTCTL_TEXT_ATA_PARSER_H\n\n#include <string>\n#include <vector>\n\n#include \"smartctl_parser.h\"\n\n\n\n/// Smartctl (S)ATA text output parser.\n/// Note: ALL parse_* functions (except parse())\n/// expect data in unix-newline format!\nclass SmartctlTextAtaParser : public SmartctlParser {\n\tpublic:\n\n\t\t// Defaulted, used by make_unique.\n\t\tSmartctlTextAtaParser() = default;\n\n\t\t// Overridden\n\t\t[[nodiscard]] hz::ExpectedVoid<SmartctlParserError> parse(std::string_view smartctl_output) override;\n\n\n\tprotected:\n\n\t\t/// Parse the section part (with \"=== .... ===\" header) - info or data sections.\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section(const std::string& header, const std::string& body);\n\n\n\t\t/// Parse the info section (without \"===\" header).\n\t\t/// This includes --info and --get=all.\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_info(const std::string& body);\n\n\t\t/// Parse a component (one line) of the info section\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_info_property(StorageProperty& p);\n\n\n\t\t/// Parse the Data section (without \"===\" header)\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_data(const std::string& body);\n\n\t\t/// Parse subsections of Data section\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_data_subsection_health(const std::string& sub);\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_data_subsection_capabilities(const std::string& sub);\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_data_subsection_attributes(const std::string& sub);\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_data_subsection_directory_log(const std::string& sub);\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_data_subsection_error_log(const std::string& sub);\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_data_subsection_selftest_log(const std::string& sub);\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_data_subsection_selective_selftest_log(const std::string& sub);\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_data_subsection_scttemp_log(const std::string& sub);\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_data_subsection_scterc_log(const std::string& sub);\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_data_subsection_devstat(const std::string& sub);\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_data_subsection_sataphy(const std::string& sub);\n\n\t\t/// Check the capabilities for internal properties we can use.\n\t\thz::ExpectedVoid<SmartctlParserError> parse_section_data_internal_capabilities(StorageProperty& cap_prop);\n\n\n\t\t/// Set \"info\" section data (\"smartctl -i\" output, or the first part of \"smartctl -x\" output)\n\t\tvoid set_data_section_info(std::string s);\n\n\t\t/// Parse \"data\" section data (the second part of \"smartctl -x\" output).\n\t\tvoid set_data_section_data(std::string s);\n\n\n\tprivate:\n\n\t\tstd::string data_section_info_;  ///< \"info\" section data, filled by parse_section_info()\n\t\tstd::string data_section_data_;  ///< \"data\" section data, filled by parse_section_data()\n\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_text_basic_parser.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include \"smartctl_text_basic_parser.h\"\n\n// #include <glibmm.h>\n//#include <cstdint>\n//#include <utility>\n#include <cstdint>\n#include <string>\n#include <string_view>\n\n// #include \"hz/locale_tools.h\"  // ScopedCLocale, locale_c_get().\n#include \"storage_device_detected_type.h\"\n#include \"storage_property.h\"\n#include \"hz/string_algo.h\"  // string_*\n#include \"hz/string_num.h\"  // string_is_numeric, number_to_string\n//#include \"hz/debug.h\"  // debug_*\n\n#include \"app_regex.h\"\n//#include \"ata_storage_property_descr.h\"\n// #include \"warning_colors.h\"\n#include \"smartctl_parser_types.h\"\n#include \"smartctl_version_parser.h\"\n#include \"smartctl_text_parser_helper.h\"\n\n\n\nhz::ExpectedVoid<SmartctlParserError> SmartctlTextBasicParser::parse(std::string_view smartctl_output)\n{\n\t// perform any2unix\n\tconst std::string output = hz::string_trim_copy(hz::string_any_to_unix_copy(smartctl_output));\n\n\tif (output.empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Empty string passed as an argument. Returning.\\n\");\n\t\treturn hz::Unexpected(SmartctlParserError::EmptyInput, \"Smartctl data is empty.\");\n\t}\n\n\t// Version\n\tstd::string version, version_full;\n\tif (!SmartctlVersionParser::parse_version_text(output, version, version_full)) {  // is this smartctl data at all?\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Cannot extract version information. Returning.\\n\");\n\t\treturn hz::Unexpected(SmartctlParserError::NoVersion, \"Cannot extract smartctl version information.\");\n\t}\n\t{\n\t\tStorageProperty p;\n\t\tp.set_name(\"smartctl/version/_merged\", \"Smartctl Version\");\n\t\tp.reported_value = version;\n\t\tp.value = p.reported_value;  // string-type value\n\t\tp.section = StoragePropertySection::Info;  // add to info section\n\t\tadd_property(p);\n\t}\n\t{\n\t\tStorageProperty p;\n\t\tp.set_name(\"smartctl/version/_merged_full\", \"Smartctl Version\");\n\t\tp.reported_value = version_full;\n\t\tp.value = p.reported_value;  // string-type value\n\t\tp.section = StoragePropertySection::Info;  // add to info section\n\t\tadd_property(p);\n\t}\n\n\t// Full text output\n\t{\n\t\tStorageProperty p;\n\t\tp.set_name(\"smartctl/output\", \"Smartctl Text Output\");\n\t\tp.reported_value = output;\n\t\tp.value = p.reported_value;  // string-type value\n\t\tp.show_in_ui = false;\n\t\tadd_property(p);\n\t}\n\n\tbool is_raid = false;\n\n\t// Detect type. note: we can't distinguish between sata and scsi (on linux, for -d ata switch).\n\t// Sample output line 1 (encountered on a CDRW drive):\n\t// SMART support is: Unavailable - Packet Interface Devices [this device: CD/DVD] don't support ATA SMART\n\t// Sample output line 2 (encountered on a BDRW drive):\n\t// Device type:          CD/DVD\n\t// NOTE: CD/DVD detection does not work in \"-d scsi\" mode.\n\tif (app_regex_partial_match(\"/this device: CD\\\\/DVD/mi\", output)\n\t\t\t|| app_regex_partial_match(\"/^Device type:\\\\s+CD\\\\/DVD/mi\", output)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"_text_only/custom/parser_detected_drive_type\", \"Parser-Detected Drive Type\");\n\t\tp.reported_value = \"CD/DVD\";\n\t\tp.value = StorageDeviceDetectedTypeExt::get_storable_name(StorageDeviceDetectedType::CdDvd);\n\t\tp.readable_value = StorageDeviceDetectedTypeExt::get_displayable_name(StorageDeviceDetectedType::CdDvd);\n\t\tp.section = StoragePropertySection::Info;  // add to info section\n\t\tadd_property(p);\n\n\t// This was encountered on a csmi soft-raid under windows with pd0.\n\t// The device reported that it had smart supported and enabled.\n\t// Product:              Raid 5 Volume\n\t} else if (app_regex_partial_match(\"/Product:[ \\\\t]*Raid/mi\", output)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"_text_only/custom/parser_detected_drive_type\", \"Parser-Detected Drive Type\");\n\t\tp.reported_value = \"RAID\";\n\t\tp.value = StorageDeviceDetectedTypeExt::get_storable_name(StorageDeviceDetectedType::UnsupportedRaid);\n\t\tp.readable_value = StorageDeviceDetectedTypeExt::get_displayable_name(StorageDeviceDetectedType::UnsupportedRaid);\n\t\tp.section = StoragePropertySection::Info;  // add to info section\n\t\tadd_property(p);\n\n\t\tis_raid = true;\n\n\t} else if (app_regex_partial_match(\"/ATA Version is:/mi\", output)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"_text_only/custom/parser_detected_drive_type\", \"Parser-Detected Drive Type\");\n\t\tp.reported_value = \"(S)ATA\";\n\t\tp.value = StorageDeviceDetectedTypeExt::get_storable_name(StorageDeviceDetectedType::AtaAny);\n\t\tp.readable_value = StorageDeviceDetectedTypeExt::get_displayable_name(StorageDeviceDetectedType::AtaAny);\n\t\tp.section = StoragePropertySection::Info;  // add to info section\n\t\tadd_property(p);\n\t}\n\n\tbool smart_supported = true;\n\tbool smart_enabled = true;\n\n\t// RAID volume may report that it has SMART, but it obviously doesn't.\n\tif (is_raid) {\n\t\tsmart_supported = false;\n\t\tsmart_enabled = false;\n\n\t} else {\n\t\t// Note: We don't use SmartctlTextAtaParser here, because this information\n\t\t// may be in some other format. If this information is valid, only then it's\n\t\t// passed to SmartctlTextAtaParser.\n\t\t// Compared to SmartctlTextAtaParser, this one is much looser.\n\n\t\t// Don't put complete messages here - they change across smartctl versions.\n\t\tif (app_regex_partial_match(\"/^SMART support is:[ \\\\t]*Unavailable/mi\", output)  // cdroms output this\n\t\t\t\t|| app_regex_partial_match(\"/Device does not support SMART/mi\", output)  // usb flash drives, non-smart hds\n\t\t\t\t|| app_regex_partial_match(\"/Device Read Identity Failed/mi\", output)) {  // solaris scsi, unsupported by smartctl (maybe others?)\n\t\t\tsmart_supported = false;\n\t\t\tsmart_enabled = false;\n\n\t\t} else if (app_regex_partial_match(\"/^SMART support is:[ \\\\t]*Available/mi\", output)\n\t\t\t\t|| app_regex_partial_match(\"/^SMART support is:[ \\\\t]*Ambiguous/mi\", output)) {\n\t\t\tsmart_supported = true;\n\n\t\t\tif (app_regex_partial_match(\"/^SMART support is:[ \\\\t]*Enabled/mi\", output)) {\n\t\t\t\tsmart_enabled = true;\n\t\t\t} else if (app_regex_partial_match(\"/^SMART support is:[ \\\\t]*Disabled/mi\", output)) {\n\t\t\t\tsmart_enabled = false;\n\t\t\t}\n\t\t}\n\t}\n\n\t{\n\t\tStorageProperty p;\n\t\tp.set_name(\"smart_support/available\", \"SMART Supported\");\n\t\tp.value = smart_supported;\n\t\tp.section = StoragePropertySection::Info;  // add to info section\n\t\tadd_property(p);\n\t}\n\t{\n\t\tStorageProperty p;\n\t\tp.set_name(\"smart_support/enabled\", \"SMART Enabled\");\n\t\tp.value = smart_enabled;\n\t\tp.section = StoragePropertySection::Info;  // add to info section\n\t\tadd_property(p);\n\t}\n\n\n\tstd::string model;\n\tif (app_regex_partial_match(\"/^Device Model:[ \\\\t]*(.*)$/mi\", output, &model)) {  // HDDs and CDROMs\n\t\tmodel = hz::string_remove_adjacent_duplicates_copy(hz::string_trim_copy(model), ' ');\n\t\tStorageProperty p;\n\t\tp.set_name(\"model_name\", \"Device Model\");\n\t\tp.value = model;  // string-type value\n\t\tadd_property(p);\n\n\t} else if (app_regex_partial_match(\"/^(?:Device|Product):[ \\\\t]*(.*)$/mi\", output, &model)) {  // usb flash drives\n\t\tmodel = hz::string_remove_adjacent_duplicates_copy(hz::string_trim_copy(model), ' ');\n\t\tStorageProperty p;\n\t\tp.set_name(\"model_name\", \"Device Model\");\n\t\tp.value = model;\n\t\tadd_property(p);\n\t}\n\n\n\tstd::string family;  // this is from smartctl's database\n\tif (app_regex_partial_match(\"/^Model Family:[ \\\\t]*(.*)$/mi\", output, &family)) {\n\t\tfamily = hz::string_remove_adjacent_duplicates_copy(hz::string_trim_copy(family), ' ');\n\t\tStorageProperty p;\n\t\tp.set_name(\"model_family\", \"Model Family\");\n\t\tp.value = family;\n\t\tadd_property(p);\n\t}\n\n\tstd::string serial;\n\tif (app_regex_partial_match(\"/^Serial Number:[ \\\\t]*(.*)$/mi\", output, &serial)) {\n\t\tserial = hz::string_remove_adjacent_duplicates_copy(hz::string_trim_copy(serial), ' ');\n\t\tStorageProperty p;\n\t\tp.set_name(\"serial_number\", \"Serial Number\");\n\t\tp.value = serial;\n\t\tadd_property(p);\n\t}\n\n\tstd::string rpm_str;\n\tif (app_regex_partial_match(\"/^Rotation Rate:[ \\\\t]*(.*)$/mi\", output, &rpm_str)) {\n\t\tStorageProperty p;\n\t\tp.set_name(\"rotation_rate\", \"Rotation Rate\");\n\t\tp.reported_value = rpm_str;\n\t\tp.value = hz::string_to_number_nolocale<int>(rpm_str, false);\n\t\tp.section = StoragePropertySection::Info;  // add to info section\n\t\tadd_property(p);\n\t}\n\n\n\t// Note: this property is present since 5.33.\n\tstd::string size;\n\tif (app_regex_partial_match(\"/^User Capacity:[ \\\\t]*(.*)$/mi\", output, &size)) {\n\t\tint64_t bytes = 0;\n\t\tconst std::string readable_size = SmartctlTextParserHelper::parse_byte_size(size, bytes, false);\n\t\tStorageProperty p;\n\t\tp.set_name(\"user_capacity/bytes/_short\", \"Capacity\");\n\t\tp.reported_value = size;\n\t\tp.value = bytes;\n\t\tp.readable_value = readable_size;\n\t\tp.section = StoragePropertySection::Info;  // add to info section\n\t\tadd_property(p);\n\t}\n\n\n\treturn {};\n}\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_text_basic_parser.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef SMARTCTL_TEXT_BASIC_PARSER_H\n#define SMARTCTL_TEXT_BASIC_PARSER_H\n\n//#include <string>\n//#include <vector>\n\n#include \"smartctl_parser.h\"\n\n\n\n/// Parse info output, regardless of device type\nclass SmartctlTextBasicParser : public SmartctlParser {\n\tpublic:\n\n\t\t// Defaulted, used by make_unique.\n\t\tSmartctlTextBasicParser() = default;\n\n\t\t// Overridden\n\t\t[[nodiscard]] hz::ExpectedVoid<SmartctlParserError> parse(std::string_view smartctl_output) override;\n\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_text_parser_helper.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2022 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include \"smartctl_text_parser_helper.h\"\n#include \"build_config.h\"\n#include \"hz/locale_tools.h\"\n#include \"hz/format_unit.h\"  // format_size\n#include \"hz/string_num.h\"  // string_is_numeric, number_to_string\n#include \"hz/string_algo.h\"  // string_*\n#include \"hz/debug.h\"\n\n\n\nstd::string SmartctlTextParserHelper::parse_byte_size(std::string str, int64_t& bytes, bool extended)\n{\n\t// E.g. \"500,107,862,016\" bytes or \"80'060'424'192 bytes\" or \"80 026 361 856 bytes\" or \"750,156,374,016 bytes [750 GB]\".\n\t// French locale inserts 0xA0 as a separator (non-breaking space, _not_ a valid utf8 char).\n\t// Finnish uses 0xC2 as a separator.\n\t// Added '.'-separated too, just in case.\n\t// Smartctl uses system locale's thousands_sep explicitly.\n\n\t// When launching smartctl, we use LANG=C for it, but it works only on POSIX.\n\t// Also, loading smartctl output files from different locales doesn't really work.\n\n\tstr = str.substr(0, str.find('['));\n\n\tstd::vector<std::string> to_replace = {\n\t\t\t\" \",\n\t\t\t\"'\",\n\t\t\t\",\",\n\t\t\t\".\",\n\t\t\tstd::string(1, static_cast<char>(0xa0)),\n\t\t\tstd::string(1, static_cast<char>(0xc2)),\n\t};\n\n\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\t// if current locale is C, then probably we didn't change it at application\n\t\t// startup, so set it now (temporarily). Otherwise, just use the current locale's\n\t\t// thousands separator.\n\t\t{\n\t\t\tconst std::string old_locale = hz::locale_c_get();\n\t\t\tconst hz::ScopedCLocale loc(\"\", old_locale == \"C\");  // set system locale if the current one is C\n\n\t\t\tstruct lconv* lc = std::localeconv();\n\t\t\tif (lc && lc->thousands_sep && lc->thousands_sep[0] != '\\0') {\n\t\t\t\tto_replace.emplace_back(lc->thousands_sep);\n\t\t\t}\n\t\t}  // the locale is restored here\n\t}\n\n\tto_replace.emplace_back(\"bytes\");\n\tstr = hz::string_replace_array_copy(hz::string_trim_copy(str), to_replace, \"\");\n\n\tint64_t v = 0;\n\tif (hz::string_is_numeric_nolocale(str, v, false)) {\n\t\tbytes = v;\n\t\treturn hz::format_size(static_cast<uint64_t>(v), true) + (extended ?\n\t\t\t\t\" [\" + hz::format_size(static_cast<uint64_t>(v), false) + \", \" + hz::number_to_string_locale(v) + \" bytes]\" : \"\");\n\t}\n\n\treturn {};\n}\n\n\n\n\n/// @}\n\n\n\n\n"
  },
  {
    "path": "src/applib/smartctl_text_parser_helper.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2022 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef SMARTCTL_TEXT_PARSER_HELPER_H\n#define SMARTCTL_TEXT_PARSER_HELPER_H\n\n#include <string>\n#include <cstdint>\n\n\n\n/// Helpers for smartctl text output parser\nclass SmartctlTextParserHelper {\n\tpublic:\n\n\t\t/// Convert e.g. \"1,000,204,886,016 bytes\" to \"1.00 TiB [931.51 GB, 1000204886016 bytes]\"\n\t\t/// \\param str String to parse\n\t\t/// \\param bytes Number of bytes\n\t\t/// \\param extended Return size in other units as well\n\t\t/// \\return Size as a displayable string\n\t\tstatic std::string parse_byte_size(std::string str, int64_t& bytes, bool extended);\n\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_version_parser.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2022 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n// #include <glibmm.h>\n\n#include \"hz/string_algo.h\"  // string_*\n#include \"hz/string_num.h\"  // string_is_numeric, number_to_string\n#include \"hz/debug.h\"  // debug_*\n\n#include \"app_regex.h\"\n#include \"smartctl_version_parser.h\"\n\n\n\n\nbool SmartctlVersionParser::parse_version_text(const std::string& s, std::string& version_only, std::string& version_full)\n{\n\t// e.g.\n\t// \"smartctl version 5.37\"\n\t// \"smartctl 5.39\"\n\t// \"smartctl 5.39 2009-06-03 20:10\" (cvs versions)\n\t// \"smartctl 5.39 2009-08-08 r2873\" (svn versions)\n\t// \"smartctl 7.3 (build date Feb 11 2022)\" (git versions)\n\t// \"smartctl pre-7.4 2023-06-13 r5481\" (pre-releases)\n\tif (!app_regex_partial_match(R\"(/^smartctl (?:version )?((?:pre-)?([0-9][^ \\t\\n\\r]+)(?: [0-9 r:-]+)?)/mi)\", s, {&version_full, &version_only})) {\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"No smartctl version information found in supplied string.\\n\");\n\t\treturn false;\n\t}\n\n\thz::string_trim(version_only);\n\thz::string_trim(version_full);\n\n\treturn true;\n}\n\n\n\nstd::optional<double> SmartctlVersionParser::get_numeric_version(const std::string& version_only)\n{\n\tdouble numeric_version = 0;\n\tif (!hz::string_is_numeric_nolocale<double>(version_only, numeric_version, false)) {\n\t\treturn std::nullopt;\n\t}\n\treturn numeric_version;\n}\n\n\n\nbool SmartctlVersionParser::check_format_supported(SmartctlOutputFormat format, const std::string& version_only)\n{\n\tif (auto numeric_version = get_numeric_version(version_only); numeric_version.has_value()) {\n\t\tswitch(format) {\n\t\t\tcase SmartctlOutputFormat::Text:\n\t\t\t\treturn numeric_version.value() >= minimum_req_text_version;\n\t\t\tcase SmartctlOutputFormat::Json:\n\t\t\t\treturn numeric_version.value() >= minimum_req_json_version;\n\t\t}\n\t}\n\treturn false;\n}\n\n\n\nnamespace {\n\nSmartctlOutputFormat s_smartctl_output_default_format = SmartctlOutputFormat::Json;\n\n}\n\n\n\nvoid SmartctlVersionParser::set_default_format(SmartctlOutputFormat format)\n{\n\ts_smartctl_output_default_format = format;\n}\n\n\n\nSmartctlOutputFormat SmartctlVersionParser::get_default_format([[maybe_unused]] SmartctlParserType parser_type)\n{\n\t// We no longer differentiate between parser types - they\n\t// all either use Text, or Json.\n//\tswitch (parser_type) {\n//\t\tcase SmartctlParserType::Basic:\n//\t\t\treturn SmartctlOutputFormat::Json;\n//\t\tcase SmartctlParserType::Ata:\n//\t\t\treturn SmartctlOutputFormat::Json;\n//\t\tcase SmartctlParserType::Nvme:\n//\t\t\treturn SmartctlOutputFormat::Json;\n//\t}\n\treturn s_smartctl_output_default_format;\n}\n\n\n\nSmartctlParserType SmartctlVersionParser::get_default_parser_type(StorageDeviceDetectedType detected_type)\n{\n\tswitch (detected_type) {\n\t\tcase StorageDeviceDetectedType::Unknown:\n\t\tcase StorageDeviceDetectedType::NeedsExplicitType:\n\t\tcase StorageDeviceDetectedType::BasicScsi:\n\t\tcase StorageDeviceDetectedType::CdDvd:\n\t\tcase StorageDeviceDetectedType::UnsupportedRaid:\n\t\t\tbreak;\n\t\tcase StorageDeviceDetectedType::AtaAny:\n\t\tcase StorageDeviceDetectedType::AtaHdd:\n\t\tcase StorageDeviceDetectedType::AtaSsd:\n\t\t\treturn SmartctlParserType::Ata;\n\t\tcase StorageDeviceDetectedType::Nvme:\n\t\t\treturn SmartctlParserType::Nvme;\n\t}\n\treturn SmartctlParserType::Basic;\n}\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/smartctl_version_parser.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef SMARTCTL_VERSION_PARSER_H\n#define SMARTCTL_VERSION_PARSER_H\n\n#include <glibmm.h>\n\n#include <string>\n#include <optional>\n\n#include \"smartctl_parser_types.h\"\n#include \"storage_property_descr.h\"\n#include \"storage_device_detected_type.h\"\n\n\n\n\n\n/// Smartctl version parser.\nclass SmartctlVersionParser {\n\tpublic:\n\n\t\t/// Supply any text (not JSON) output of smartctl here, the smartctl version will be retrieved.\n\t\t/// The text does not have to be in Unix newline format.\n\t\t/// \\param s \"smartctl -V\" command output.\n\t\t/// \\param[out] version_only A string similar to \"7.2\"\n\t\t/// \\param[out] version_full A string similar to \"smartctl 7.2 2020-12-30 r5155\"\n\t\t/// \\return false if the version could not be parsed.\n\t\tstatic bool parse_version_text(const std::string& s, std::string& version_only, std::string& version_full);\n\n\n\t\t/// Get numeric version as a double from a parsed version.\n\t\t/// \\param version_only A string similar to \"7.2\", as parsed by parse_version_text().\n\t\t/// \\return Numeric version as a double, e.g. 7.2. std::nullopt if the version could not be parsed.\n\t\tstatic std::optional<double> get_numeric_version(const std::string& version_only);\n\n\n\t\t/// Check that the version of smartctl output can be parsed with a parser.\n\t\tstatic bool check_format_supported(SmartctlOutputFormat format, const std::string& version_only);\n\n\n\t\t/// Get default output format for a parser type.\n\t\tstatic void set_default_format(SmartctlOutputFormat format);\n\n\t\t/// Get default output format for a parser type.\n\t\tstatic SmartctlOutputFormat get_default_format(SmartctlParserType parser_type);\n\n\n\t\t/// Get default output format for a parser type.\n\t\tstatic SmartctlParserType get_default_parser_type(StorageDeviceDetectedType detected_type);\n\n\n\t\t// We require this version at runtime to support --get=all.\n\t\tstatic constexpr double minimum_req_runtime_version = 5.43;\n\n\n\tprivate:\n\n\t\t// Text Parser:\n\t\t// Tested with 5.1-xx versions (1 - 18), and 5.[20 - 38].\n\t\t// Note: 5.1-11 (maybe others too) with scsi disk gives non-parsable output (why?).\n\t\t// 5.0-24, 5.0-36, 5.0-49 tested with data only, from smartmontools site.\n\t\t// Can't fully test 5.0-xx, they don't support sata, and I have only sata.\n\t\tstatic constexpr double minimum_req_text_version = 5.0;\n\n\t\t// 7.3 adds \"smart_support\" section in json.\n\t\t// 7.4 adds nvme testing, but it's an optional feature not related to parsing.\n\t\tstatic constexpr double minimum_req_json_version = 7.3;\n\n\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_detector.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include <glibmm.h>\n#include <gtkmm.h>  // compose()\n#include <algorithm>\n#include <memory>\n\n#include \"build_config.h\"\n\n#include \"hz/debug.h\"\n\n#include \"app_regex.h\"\n#include \"smartctl_executor.h\"\n#include \"storage_detector.h\"\n\n#include \"storage_detector_linux.h\"\n#include \"storage_detector_win32.h\"\n#include \"storage_detector_other.h\"\n\n\n\n\nhz::ExpectedVoid<StorageDetectorError> StorageDetector::detect(std::vector<StorageDevicePtr>& drives, const CommandExecutorFactoryPtr& ex_factory)\n{\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Starting drive detection.\\n\");\n\n\tstd::vector<StorageDevicePtr> all_detected;\n\thz::ExpectedVoid<StorageDetectorError> detect_status;\n\n\t// Try each one and move to next if it fails.\n\n\tif constexpr(BuildEnv::is_kernel_linux()) {\n\t\tdetect_status = detect_drives_linux(all_detected, ex_factory);  // linux /proc/partitions as fallback.\n\n\t} else if constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\tdetect_status = detect_drives_win32(all_detected, ex_factory);  // win32\n\n\t} else {  // freebsd, etc.\n\t\tdetect_status = detect_drives_other(all_detected, ex_factory);  // bsd, etc. . scans /dev.\n\t}\n\n\tif (all_detected.empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Cannot detect drives: None of the drive detection methods returned any drives.\\n\");\n\t\treturn detect_status;\n\t}\n\n\tfor (auto& drive : all_detected) {\n\t\t// try to match against patterns\n// \t\tfor (std::size_t i = 0; i < match_patterns_.size(); i++) {\n\t\t\t// try to match against general filter\n// \t\t\tif (!app_regex_partial_match(match_patterns_[i], dev))\n// \t\t\t\tcontinue;\n\n\t\t\t// matched, check the blacklist\n\t\t\tbool blacked = false;\n\t\t\tfor (const auto& blacklist_pattern : blacklist_patterns_) {\n\t\t\t\tif (app_regex_partial_match(blacklist_pattern, drive->get_device())) {  // matched the blacklist too\n\t\t\t\t\tblacked = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdebug_out_info(\"app\", \"Found device: \" << drive->get_device_with_type() << \".\\n\");\n\n\t\t\tif (!blacked) {\n\t\t\t\tdrives.push_back(drive);\n// \t\t\t\tbreak;  // no need to match other patters, go to next device\n\n\t\t\t} else {  // blacklisted\n\t\t\t\tdebug_out_info(\"app\", \"Device \" << drive->get_device_with_type() << \" is blacklisted, ignoring.\\n\");\n// \t\t\t\tbreak;  // go to next device\n\t\t\t}\n// \t\t}\n\t}\n\n\t// Sort the drives, because their order is not quite defined.\n\t// Natural sort is implemented in the StorageDevicePtr comparison operator.\n\tstd::sort(drives.begin(), drives.end());\n\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Drive detection finished.\\n\");\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<StorageDetectorError> StorageDetector::fetch_basic_data(std::vector<StorageDevicePtr>& drives,\n\t\tconst CommandExecutorFactoryPtr& ex_factory, bool return_first_error)\n{\n\tfetch_data_errors_.clear();\n\tfetch_data_error_outputs_.clear();\n\n\tstd::shared_ptr<CommandExecutor> smartctl_ex = ex_factory->create_executor(CommandExecutorFactory::ExecutorType::Smartctl);\n\n\tfor (auto& drive : drives) {\n\t\tdebug_out_info(\"app\", \"Retrieving basic information about the device...\\n\");\n\n\t\tsmartctl_ex->set_running_msg(Glib::ustring::compose(_(\"Running {command} on %1...\"), drive->get_device_with_type()));\n\n\t\t// don't show any errors here - we don't want a screen flood.\n\t\t// no need for gui-based executors here, we already show the message in\n\t\t// iconview background (if called from main window)\n\t\thz::ExpectedVoid<StorageDeviceError> fetch_status;\n\t\tif (drive->get_basic_output().empty()) {  // if not fetched during detection\n\t\t\tfetch_status = drive->fetch_basic_data_and_parse(smartctl_ex);\n\t\t}\n\n\t\t// normally we skip drives with errors - possibly scsi, etc.\n\t\tif (return_first_error && !fetch_status) {\n\t\t\treturn hz::Unexpected(StorageDetectorError::StorageDeviceError, fetch_status.error().message());\n\t\t}\n\n\t\tif (!fetch_status) {\n\t\t\t// use original executor error if present (permits matches by our users).\n\t\t\t// if (!smartctl_ex->get_error_msg().empty())\n\t\t\t//\terror_message = smartctl_ex->get_error_msg();\n\n\t\t\tfetch_data_errors_.push_back(fetch_status.error().message());\n\t\t\tfetch_data_error_outputs_.push_back(smartctl_ex->get_stdout_str());\n\t\t}\n\n\t\tdebug_out_dump(\"app\", \"Device information for \" << drive->get_device()\n\t\t\t\t<< \" (type: \\\"\" << drive->get_type_argument() << \"\\\"):\\n\"\n\t\t\t\t<< \"\\tModel: \" << drive->get_model_name() << \"\\n\"\n\t\t\t\t<< \"\\tDetected type: \" << StorageDeviceDetectedTypeExt::get_displayable_name(drive->get_detected_type()) << \"\\n\"\n\t\t\t\t<< \"\\tSMART status: \" << StorageDevice::get_status_displayable_name(drive->get_smart_status()) << \"\\n\"\n\t\t\t\t);\n\n\t}\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<StorageDetectorError> StorageDetector::detect_and_fetch_basic_data(std::vector<StorageDevicePtr>& put_drives_here,\n\t\tconst CommandExecutorFactoryPtr& ex_factory)\n{\n\tauto detect_status = detect(put_drives_here, ex_factory);\n\n\tif (!detect_status) {\n\t\t// ignore its errors, there may be plenty of them.\n\t\t[[maybe_unused]] auto fetch_status = fetch_basic_data(put_drives_here, ex_factory, false);\n\t}\n\n\treturn detect_status;\n}\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_detector.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef STORAGE_DETECTOR_H\n#define STORAGE_DETECTOR_H\n\n#include <vector>\n#include <string>\n\n#include \"storage_device.h\"\n#include \"command_executor.h\"\n#include \"command_executor_factory.h\"\n\n\nenum class StorageDetectorError {\n\tNoSmartctlBinary,\n\tNoHelperBinary,\n\tSmartctlExecutionError,\n\tEmptyCommandOutput,\n\tUnsupportedCommandVersion,\n\tParseError,\n\tStorageDeviceError,\n\tProcReadError,\n\tGeneralDetectionErrors,\n\tConfigError,\n\tDevOpenError,\n\tInvalidCommandLine,\n};\n\n\n\n/// Storage detector - detects available drives in the system.\nclass StorageDetector {\n\tpublic:\n\n\t\t/// Detects a list of drives. Returns detection error message if error occurs.\n\t\t[[nodiscard]] hz::ExpectedVoid<StorageDetectorError> detect(std::vector<StorageDevicePtr>& drives,\n\t\t\t\tconst CommandExecutorFactoryPtr& ex_factory);\n\n\n\t\t/// For each drive, fetch basic data and parse it.\n\t\t/// If \\c return_first_error is true, the function returns on the first error.\n\t\t/// \\return An empty string. Or, if return_first_error is true, the first error that occurs.\n\t\t[[nodiscard]] hz::ExpectedVoid<StorageDetectorError> fetch_basic_data(std::vector<StorageDevicePtr>& drives,\n\t\t\t\tconst CommandExecutorFactoryPtr& ex_factory, bool return_first_error = false);\n\n\n\t\t/// Run detect() and fetch_basic_data().\n\t\t/// \\return An error if such occurs.\n\t\t[[nodiscard]] hz::ExpectedVoid<StorageDetectorError> detect_and_fetch_basic_data(std::vector<StorageDevicePtr>& put_drives_here,\n\t\t\t\tconst CommandExecutorFactoryPtr& ex_factory);\n\n\n// \t\tvoid add_match_patterns(std::vector<std::string>& patterns)\n// \t\t{\n// \t\t\tmatch_patterns_.insert(match_patterns_.end(), patterns.begin(), patterns.end());\n// \t\t}\n\n\n\t\t/// Add device patterns to drive detection blacklist\n\t\tvoid add_blacklist_patterns(const std::vector<std::string>& patterns)\n\t\t{\n\t\t\tblacklist_patterns_.insert(blacklist_patterns_.end(), patterns.begin(), patterns.end());\n\t\t}\n\n\n\t\t/// Get all errors produced by fetch_basic_data().\n\t\t[[nodiscard]] const std::vector<std::string>& get_fetch_data_errors() const\n\t\t{\n\t\t\treturn fetch_data_errors_;\n\t\t}\n\n\n\t\t/// Get command output for each error in get_fetch_data_errors().\n\t\t[[nodiscard]] const std::vector<std::string>& get_fetch_data_error_outputs() const\n\t\t{\n\t\t\treturn fetch_data_error_outputs_;\n\t\t}\n\n\n\tprivate:\n\n// \t\tstd::vector<std::string> match_patterns_;  ///< First each file is matched against these\n\t\tstd::vector<std::string> blacklist_patterns_;  ///< If a device matches these, it's ignored.\n\n\t\tstd::vector<std::string> fetch_data_errors_;  ///< Errors that have occurred\n\t\tstd::vector<std::string> fetch_data_error_outputs_;  ///< Corresponding command outputs to fetch_data_errors_\n\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_detector_helpers.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef STORAGE_DETECTOR_HELPERS_H\n#define STORAGE_DETECTOR_HELPERS_H\n\n#include <string>\n#include <vector>\n\n#include <glibmm.h>  // Glib::compose\n\n#include \"build_config.h\"\n#include \"command_executor_factory.h\"\n#include \"storage_device.h\"\n#include \"rconfig/rconfig.h\"\n#include \"app_regex.h\"\n#include \"hz/string_num.h\"\n#include \"storage_detector.h\"\n#include \"hz/string_algo.h\"\n\n\n\n/// Find and execute tw_cli with specified options, return its output through \\c output.\n/// \\return error message\ninline hz::ExpectedVoid<StorageDetectorError> execute_tw_cli(const CommandExecutorFactoryPtr& ex_factory,\n\t\tconst std::vector<std::string>& command_options, std::string& output)\n{\n\tstd::shared_ptr<CommandExecutor> executor = ex_factory->create_executor(CommandExecutorFactory::ExecutorType::TwCli);\n\n\tauto binary = rconfig::get_data<std::string>(\"system/tw_cli_binary\");\n\n\tif (binary.empty()) {\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"tw_cli binary is not set in config.\\n\");\n\t\treturn hz::Unexpected(StorageDetectorError::NoHelperBinary,\n\t\t\t\t_(\"tw_cli binary is not specified in configuration.\"));\n\t}\n\n\tstd::vector<std::string> binaries;  // binaries to try\n\t// Note: tw_cli is automatically added to PATH in windows, no need to look for it.\n\tbinaries.push_back(binary);\n\tif constexpr(BuildEnv::is_kernel_linux()) {\n\t\t// tw_cli may be named tw_cli.x86 or tw_cli.x86_64 in linux\n\t\tbinaries.push_back(binary + \".x86_64\");  // try this first\n\t\tbinaries.push_back(binary + \".x86\");\n\t}\n\n\tfor (const auto& bin : binaries) {\n\t\texecutor->set_command(bin, command_options);\n\n\t\tif (!executor->execute() || !executor->get_error_msg().empty()) {\n\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Error while executing tw_cli binary.\\n\");\n\t\t} else {\n\t\t\tbreak;  // found it\n\t\t}\n\t}\n\n\t// any_to_unix is needed for windows\n\toutput = hz::string_trim_copy(hz::string_any_to_unix_copy(executor->get_stdout_str()));\n\tif (output.empty()) {\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"tw_cli returned an empty output.\\n\");\n\t\treturn hz::Unexpected(StorageDetectorError::EmptyCommandOutput,\n\t\t\t\t_(\"tw_cli returned an empty output.\"));\n\t}\n\n\treturn {};\n}\n\n\n\n/// Get the drives on a 3ware controller using tw_cli.\n/// Note that the drives are inserted in the order they are detected.\ninline hz::ExpectedVoid<StorageDetectorError> tw_cli_get_drives(const std::string& dev, int controller,\n\t\tstd::vector<StorageDevicePtr>& drives, const CommandExecutorFactoryPtr& ex_factory, bool use_tw_cli_dev)\n{\n\tdebug_out_info(\"app\", \"Getting available 3ware drives (ports) for controller \" << controller << \" through tw_cli...\\n\");\n\n\tstd::string output;\n\tauto exec_status = execute_tw_cli(ex_factory, {hz::string_sprintf(\"/c%d\", controller), \"show\", \"all\"}, output);\n\tif (!exec_status) {\n\t\treturn exec_status;\n\t}\n\n\t// split to lines\n\tstd::vector<std::string> lines;\n\thz::string_split(output, '\\n', lines, true);\n\n\t// Note that the ports may be printed in any order. We sort the drives themselves in the end.\n\tauto port_re = app_regex_re(R\"(/^p([0-9]+)[ \\t]+([^\\t\\n]+)/mi)\");\n\tfor (const auto& line : lines) {\n\t\tstd::string port_str, status;\n\t\tif (app_regex_partial_match(port_re, hz::string_trim_copy(line), {&port_str, &status})) {\n\t\t\tif (status != \"NOT-PRESENT\") {\n\t\t\t\tint port = -1;\n\t\t\t\tif (hz::string_is_numeric_nolocale(port_str, port)) {\n\t\t\t\t\tif (use_tw_cli_dev) {  // use \"tw_cli/cx/py\" device\n\t\t\t\t\t\tdrives.emplace_back(std::make_shared<StorageDevice>(\"tw_cli/c\"\n\t\t\t\t\t\t\t\t+ hz::number_to_string_nolocale(controller) + \"/p\" + hz::number_to_string_nolocale(port)));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdrives.emplace_back(std::make_shared<StorageDevice>(dev, \"3ware,\" + hz::number_to_string_nolocale(port)));\n\t\t\t\t\t}\n\t\t\t\t\tdebug_out_info(\"app\", \"Added 3ware drive \" << drives.back()->get_device_with_type() << \".\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {};\n}\n\n\n\n/// Return 3ware SCSI host numbers (same as /c switch to tw_cli).\n/// \\return error string on error\ninline hz::ExpectedVoid<StorageDetectorError> tw_cli_get_controllers(\n\t\tconst CommandExecutorFactoryPtr& ex_factory, std::vector<int>& controllers)\n{\n\tdebug_out_info(\"app\", \"Getting available 3ware controllers through tw_cli...\\n\");\n\n\tstd::string output;\n\tauto exec_status = execute_tw_cli(ex_factory, {\"show\"}, output);\n\tif (!exec_status) {\n\t\treturn exec_status;\n\t}\n\n\t// split to lines\n\tstd::vector<std::string> lines;\n\thz::string_split(output, '\\n', lines, true);\n\n\tauto controller_re = app_regex_re(\"/^c([0-9]+)[ \\\\t]+/mi\");\n\tfor (const auto& line : lines) {\n\t\tstd::string controller_str;\n\t\tif (app_regex_partial_match(controller_re, hz::string_trim_copy(line), &controller_str)) {\n\t\t\tint controller = -1;\n\t\t\tif (hz::string_is_numeric_nolocale(controller_str, controller)) {\n\t\t\t\tdebug_out_info(\"app\", \"Found 3ware controller \" << controller << \".\\n\");\n\t\t\t\tcontrollers.push_back(controller);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Sort them. This affects only the further detection order, since the drives\n\t// are sorted in the end anyway.\n\tstd::sort(controllers.begin(), controllers.end());\n\n\treturn {};\n}\n\n\n\n/// Get number of ports by sequentially running smartctl on each port, until\n/// one of the gives an error. \\c type contains a printf-formatted string with %d.\n/// \\return an error message on error.\ninline hz::ExpectedVoid<StorageDetectorError> smartctl_scan_drives_sequentially(const std::string& dev, const std::string& type,\n\t  int from, int to, std::vector<StorageDevicePtr>& drives, const CommandExecutorFactoryPtr& ex_factory, std::string& last_output)\n{\n\tstd::shared_ptr<CommandExecutor> smartctl_ex = ex_factory->create_executor(CommandExecutorFactory::ExecutorType::Smartctl);\n\n\tfor (int i = from; i <= to; ++i) {\n\t\tstd::string type_arg = hz::string_sprintf(type.c_str(), i);\n\t\tauto drive = std::make_shared<StorageDevice>(dev, type_arg);\n\n\t\t// This will generate an error if smartctl doesn't return 0, which is what happens\n\t\t// with non-populated ports.\n\t\t// Sometimes the output contains:\n\t\t// \"Read Device Identity failed: Input/output error\"\n\t\t// or\n\t\t// \"Read Device Identity failed: empty IDENTIFY data\"\n\t\tauto fetch_status = drive->fetch_basic_data_and_parse(smartctl_ex);\n\t\tlast_output = drive->get_basic_output();\n\n\t\t// If we've reached smartctl port limit (older versions may have smaller limits), abort.\n\t\tif (app_regex_partial_match(\"/VALID ARGUMENTS ARE/mi\", last_output)) {\n\t\t\tbreak;\n\t\t}\n\n\t\t// If we couldn't open the device, it means there is no such controller at specified device\n\t\t// and scanning the ports is useless.\n\t\tif (app_regex_partial_match(\"/No .* controller found/mi\", last_output)\n\t\t\t\t|| app_regex_partial_match(\"/Smartctl open device: .* failed: No such device/mi\", last_output) ) {\n\t\t\tbreak;\n\t\t}\n\n\t\tif (!fetch_status) {\n\t\t\tdebug_out_info(\"app\", \"Smartctl returned with an error: \" << fetch_status.error().message() << \"\\n\");\n\t\t\tdebug_out_dump(\"app\", \"Skipping drive \" << drive->get_device_with_type() << \" due to smartctl error.\\n\");\n\t\t} else {\n\t\t\tdrives.push_back(drive);\n\t\t\tdebug_out_info(\"app\", \"Added drive \" << drive->get_device_with_type() << \".\\n\");\n\t\t}\n\t}\n\n\treturn {};\n}\n\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_detector_linux.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include \"storage_detector_linux.h\"\n\n#include \"command_executor.h\"\n#include <glibmm.h>\n\n#include <algorithm>  // std::find\n#include <cstdio>  // std::fgets(), std::FILE\n// #include <cerrno>  // ENXIO\n#include <memory>\n#include <filesystem>\n#include <regex>\n#include <set>\n#include <map>\n#include <system_error>\n#include <vector>\n#include <utility>  // std::pair\n#include <string>\n\n#include \"build_config.h\"\n#include \"command_executor_factory.h\"\n#include \"hz/error_container.h\"\n#include \"hz/string_algo.h\"\n#include \"hz/debug.h\"\n#include \"hz/fs.h\"\n#include \"hz/string_num.h\"\n#include \"rconfig/rconfig.h\"\n#include \"app_regex.h\"\n#include \"storage_detector.h\"\n#include \"storage_detector_helpers.h\"\n#include \"storage_device.h\"\n\n\n\n\nnamespace {\n\n\n// Linux 2.6 with udev. Scan /dev/disk/by-id - the directory entries there\n// are symlinks to respective /dev devices. Some devices have multiple\n// links pointing to them, so unique filter is needed.\n/*\nSample listing:\n------------------------------------------------------------\n# ls -1 /dev/disk/by-id\nata-ST31000340AS_9QJ0FFG7\nata-ST31000340AS_9QJ0FFG7-part1\nata-ST31000340AS_9QJ0FFG7-part2\nata-ST3500630AS_9QG0R38D\nata-ST3500630AS_9QG0R38D-part1\nscsi-SATA_ST31000340AS_9QJ0FFG7\nscsi-SATA_ST31000340AS_9QJ0FFG7-part1\nscsi-SATA_ST31000340AS_9QJ0FFG7-part2\nscsi-SATA_ST3500630AS_9QG0R38D\nscsi-SATA_ST3500630AS_9QG0R38D-part1\n*/\n/*\n// We don't use udev anymore - not all distros have it, and e.g. Ubuntu\n// has it all wrong (two symlinks (sda, sdb) pointing both to sdb).\ninline std::string detect_drives_linux_udev_byid(std::vector<std::string>& devices)\n{\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Detecting through device scan directory /dev/disk/by-id...\\n\");\n\n\t// this defaults to \"/dev/disk/by-id\"\n\tauto dir = hz::fs_path_from_string(rconfig::get_data<std::string>(\"system/linux_udev_byid_path\"));\n\tif (dir.empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Device directory path is not set.\\n\");\n\t\treturn \"Device directory path is not set.\";\n\t}\n\n\tstd::error_code dummy_ec;\n\tif (!hz::fs::exists(dir, dummy_ec)) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Device directory doesn't exist.\\n\");\n\t\treturn \"Device directory does not exist.\";\n\t}\n\n\t// filter-out the ones with \"partN\" in them\n\tstatic const std::vector<std::string> blacklist = {\n\t\t\t\"/-part[0-9]+$/\"\n\t};\n\n\tstd::error_code ec;\n\tfor (const auto& entry : hz::fs::directory_iterator(dir, ec)) {  // this outputs to debug too.\n\t\tauto path = entry.path();\n\n\t\t// platform blacklist\n\t\tbool blacked = false;\n\t\tfor (const auto& bl_pattern : blacklist) {\n\t\t\tif (app_regex_partial_match(bl_pattern, path.string())) {\n\t\t\t\tblacked = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (blacked)\n\t\t\tcontinue;\n\n\t\t// those are usually relative links, so find out where they are pointing to.\n\t\tif (hz::fs::is_symlink(path, dummy_ec)) {\n\t\t\tif (!hz::fs::exists(path)) {  // broken symlink\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tpath = hz::fs::read_symlink(path, dummy_ec);\n\t\t\tif (path.is_relative()) {\n\t\t\t\tpath = hz::fs::canonical(entry.path() / path);  // remove ../-s\n\t\t\t}\n\t\t}\n\n\t\tif (std::find(devices.begin(), devices.end(), path.string()) == devices.end())  // there may be duplicates\n\t\t\tdevices.push_back(path.string());\n\t}\n\tif (ec) {\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Cannot list device directory entries.\\n\");\n\t\treturn hz::string_sprintf(\"Cannot list device directory entries: %s\", ec.message().c_str());\n\t}\n\n\treturn std::string();\n}\n*/\n\n\n\n/// Cache of file->contents, caches read files.\ninline std::map<hz::fs::path, std::string>& get_read_file_cache_ref()\n{\n\tstatic std::map<hz::fs::path, std::string> cache;\n\treturn cache;\n}\n\n\n\n/// Clear the read file cache.\ninline void clear_read_file_cache()\n{\n\tget_read_file_cache_ref().clear();\n}\n\n\n\n/// Read procfs file without using seeking.\ninline std::error_code read_proc_file(const hz::fs::path& file, std::string& contents)\n{\n\tauto& cache = get_read_file_cache_ref();\n\tif (auto iter = cache.find(file); iter != cache.end()) {\n\t\tcontents = iter->second;\n\t\treturn {};\n\t}\n\n\tauto ec = hz::fs_file_get_contents_unseekable(file, contents);\n\tif (ec) {\n\t\treturn ec;\n\t}\n\n\tcache[file] = contents;\n\n\tdebug_begin();  // avoiding printing prefix on every line\n\tdebug_out_dump(\"app\", DBG_FUNC_MSG << \"File contents (\\\"\" << file.string() << \"\\\"):\\n\" << contents << \"\\n\");\n\tdebug_end();\n\n\treturn {};\n}\n\n\n\n/// Same as read_proc_file(), but returns a vector of lines\ninline std::error_code read_proc_file_lines(const hz::fs::path& file, std::vector<std::string>& lines)\n{\n\tstd::string contents;\n\tauto ec = read_proc_file(file, contents);\n\tif (!ec) {\n\t\thz::string_split(contents, '\\n', lines, true);\n\t}\n\treturn ec;\n}\n\n\n\n\n/// Read /proc/partitions file. Return error message on error.\ninline std::string read_proc_partitions_file(std::vector<std::string>& lines)\n{\n\tauto file = hz::fs_path_from_string(rconfig::get_data<std::string>(\"system/linux_proc_partitions_path\"));\n\tif (file.empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Partitions file path is not set.\\n\");\n\t\treturn _(\"Partitions file path is not set.\");\n\t}\n\n\tauto ec = read_proc_file_lines(file, lines);\n\tif (ec) {\n\t\tstd::error_code dummy_ec;\n\t\tif (!hz::fs::exists(file, dummy_ec)) {\n\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Partitions file doesn't exist.\\n\");\n\t\t} else {\n\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Partitions file exists but cannot be read.\\n\");\n\t\t}\n\t\treturn ec.message();\n\t}\n\n\treturn {};\n}\n\n\n\n/// Read /proc/devices file. Return error message on error.\ninline std::string read_proc_devices_file(std::vector<std::string>& lines)\n{\n\tauto file = hz::fs_path_from_string(rconfig::get_data<std::string>(\"system/linux_proc_devices_path\"));\n\tif (file.empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Devices file path is not set.\\n\");\n\t\treturn _(\"Devices file path is not set.\");\n\t}\n\n\tauto ec = read_proc_file_lines(file, lines);\n\tif (ec) {\n\t\tstd::error_code dummy_ec;\n\t\tif (!hz::fs::exists(file, dummy_ec)) {\n\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Devices file doesn't exist.\\n\");\n\t\t} else {\n\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Devices file exists but cannot be read.\\n\");\n\t\t}\n\t\treturn ec.message();\n\t}\n\n\treturn {};\n}\n\n\n\n/// Read /proc/scsi/scsi file. Return error message on error.\n/// \\c vendors_models is filled with (scsi host #, trimmed vendors line) pairs.\n/// Note that scsi host # is not unique.\ninline std::string read_proc_scsi_scsi_file(std::vector< std::pair<int, std::string> >& vendors_models)\n{\n\tauto file = hz::fs_path_from_string(rconfig::get_data<std::string>(\"system/linux_proc_scsi_scsi_path\"));\n\tif (file.empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"SCSI file path is not set.\\n\");\n\t\treturn _(\"SCSI file path is not set.\");\n\t}\n\n\tstd::vector<std::string> lines;\n\tauto ec = read_proc_file_lines(file, lines);\n\tif (ec) {\n\t\tstd::error_code dummy_ec;\n\t\tif (!hz::fs::exists(file, dummy_ec)) {\n\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"SCSI file doesn't exist.\\n\");\n\t\t} else {\n\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"SCSI file exists but cannot be read.\\n\");\n\t\t}\n\t\treturn ec.message();\n\t}\n\n\tint last_scsi_host = -1;\n\tconst auto host_re = app_regex_re(\"^Host: scsi([0-9]+)\");\n\n\tfor (auto line : lines) {\n\t\thz::string_trim(line);\n\t\t// The format of this file is scsi host number on one line, vendor on another,\n\t\t// some other info on the third.\n\t\t// debug_out_error(\"app\", \"SCSI Line: \" << hz::string_trim_copy(lines[i]) << \"\\n\");\n\t\tstd::string scsi_host_str;\n\t\tif (app_regex_partial_match(host_re, line, &scsi_host_str)) {\n\t\t\t// debug_out_dump(\"app\", \"SCSI Host \" << scsi_host_str << \" found.\\n\");\n\t\t\thz::string_is_numeric_nolocale(scsi_host_str, last_scsi_host);\n\n\t\t} else if (last_scsi_host != -1 && app_regex_partial_match(\"/Vendor: /i\", line)) {\n\t\t\tvendors_models.emplace_back(last_scsi_host, line);\n\t\t}\n\t}\n\n\treturn {};\n}\n\n\n\n/// host\tchan\tid\tlun\ttype\topens\tqdepth\tbusy\tonline\n/// Read /proc/scsi/sg/devices file. Return error message on error.\n/// \\c sg_entries is filled with lines parsed as ints.\n/// Each line index corresponds to N in /dev/sgN.\ninline std::string read_proc_scsi_sg_devices_file(std::vector<std::vector<int>>& sg_entries)\n{\n\tauto file = hz::fs_path_from_string(rconfig::get_data<std::string>(\"system/linux_proc_scsi_sg_devices_path\"));\n\tif (file.empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Sg devices file path is not set.\\n\");\n\t\treturn _(\"SCSI sg devices file path is not set.\");\n\t}\n\n\tstd::vector<std::string> lines;\n\tauto ec = read_proc_file_lines(file, lines);\n\tif (ec) {\n\t\tstd::error_code dummy_ec;\n\t\tif (!hz::fs::exists(file, dummy_ec)) {\n\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Sg devices file doesn't exist.\\n\");\n\t\t} else {\n\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Sg devices file exists but cannot be read.\\n\");\n\t\t}\n\t\treturn ec.message();\n\t}\n\n\tauto parse_re = app_regex_re(\n\t\t\tR\"(^([0-9-]+)\\s+([0-9-]+)\\s+([0-9-]+)\\s+([0-9-]+)\\s+([0-9-]+)\\s+([0-9-]+)\\s+([0-9-]+)\\s+([0-9-]+)\\s+([0-9-]+))\");\n\n\tfor (std::size_t i = 0; i < lines.size(); ++i) {\n\t\tconst std::string trimmed = hz::string_trim_copy(lines[i]);\n\t\tstd::smatch matches;\n\t\tif (app_regex_partial_match(parse_re, trimmed, matches)) {\n\t\t\tDBG_ASSERT(matches.size() == 10);\n\n\t\t\tstd::vector<int> line_numbers(matches.size() - 1, -1);\n\t\t\tfor (std::size_t j = 0; j < line_numbers.size(); ++j) {\n\t\t\t\thz::string_is_numeric_nolocale(matches.str(j+1), line_numbers[j]);\n\t\t\t}\n\t\t\tsg_entries.resize(i+1);  // maintain the line indices\n\t\t\tsg_entries[i] = line_numbers;\n\n\t\t} else {\n\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Sg devices line offset \" << i << \" has invalid format.\\n\");\n\t\t}\n\t}\n\n\treturn {};\n}\n\n\n\n\n/**\n<pre>\nLinux (tested with 2.4 and 2.6) /proc/partitions. Parses the file, appends /dev to each entry.\nNote that file format changed from 2.4 to 2.6 (some statistics fields moved to another file).\nNo /proc/partitions on freebsd, solaris or osx, afaik.\n\nSample 1 (2.4, devfs, with statistics):\n------------------------------------------------------------\n# cat /proc/partitions\nmajor minor  #blocks  name     rio rmerge rsect ruse wio wmerge wsect wuse running use aveq\n\n3     0   60051600 ide/host0/bus0/target0/lun0/disc 72159 136746 1668720 467190 383039 658435 8342136 1659840 -429 17038808 22703194\n3     1    1638598 ide/host0/bus0/target0/lun0/part1 0 0 0 0 0 0 0 0 0 0 0\n3     2    1028160 ide/host0/bus0/target0/lun0/part2 0 0 0 0 0 0 0 0 0 0 0\n3     3    3092512 ide/host0/bus0/target0/lun0/part3 0 0 0 0 0 0 0 0 0 0 0\n3     4          1 ide/host0/bus0/target0/lun0/part4 0 0 0 0 0 0 0 0 0 0 0\n3     5     514048 ide/host0/bus0/target0/lun0/part5 1458 7877 74680 9070 2140 18285 166272 42490 0 10100 52000\n------------------------------------------------------------\n\nSample 2 (2.6):\n------------------------------------------------------------\n# cat /proc/partitions\nmajor minor  #blocks  name\n\n\t1     0       4096 ram0\n\t8     0  156290904 sda\n\t8     1   39070048 sda1\n\t8     2       7560 sda2\n\t8     3          1 sda3\n------------------------------------------------------------\n\nSample 3 (2.6 on nokia n8xx, spaces may be missing):\n------------------------------------------------------------\n# cat /proc/partitions\nmajor minor  #blocks  name\n\n31  0     128 mtdblock0\n31  1     384 mtdblock1\n31  2    2048 mtdblock2\n31  3    2048 mtdblock3\n31  4  257536 mtdblock4\n254 0 3932160 mmcblk0\n254 1 3928064 mmcblk0p1\n254 8 1966080 mmcblk1\n254 9 2007032 mmcblk1p1\n</pre> */\ninline hz::ExpectedVoid<StorageDetectorError> detect_drives_linux_proc_partitions(\n\t\tstd::vector<StorageDevicePtr>& drives, const CommandExecutorFactoryPtr& ex_factory)\n{\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Detecting drives through partitions file (/proc/partitions by default; set \\\"system/linux_proc_partitions_path\\\" config key to override).\\n\");\n\n\tstd::vector<std::string> lines;\n\tconst std::string error_msg = read_proc_partitions_file(lines);\n\tif (!error_msg.empty()) {\n\t\treturn hz::Unexpected(StorageDetectorError::ProcReadError, error_msg);\n\t}\n\n\tstatic const std::vector<std::string> blacklist = {\n\t\t\"/d[a-z][0-9]+$/\",  // sda1, hdb2 - partitions. twa0 and twe1 are drives, not partitions.\n\t\t\"/ram[0-9]+$/\",  // ramdisks?\n\t\t\"/loop[0-9]*$/\",  // not sure if loop devices go there, but anyway...\n\t\t\"/part[0-9]+$/\",  // devfs had them\n\t\t\"/p[0-9]+$/\",  // partitions are usually marked this way\n\t\t\"/md[0-9]*$/\",  // linux software raid\n\t\t\"/dm-[0-9]*$/\",  // linux device mapper\n\t};\n\n\tstd::vector<std::string> proc_devices;\n\n\tfor (auto line : lines) {\n\t\thz::string_trim(line);\n\t\tif (line.empty() || app_regex_partial_match(\"/^major/\", line))  // file header\n\t\t\tcontinue;\n\n\t\tstd::string dev;\n\t\tif (!app_regex_partial_match(R\"(/^[ \\t]*[^ \\t\\n]+[ \\t]+[^ \\t\\n]+[ \\t]+[^ \\t\\n]+[ \\t]+([^ \\t\\n]+)/)\", line, &dev) || dev.empty()) {\n\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Cannot parse line \\\"\" << line << \"\\\".\\n\");\n\t\t\tcontinue;\n\t\t}\n\n\t\t// platform blacklist\n\t\tbool blacked = false;\n\t\tfor (const auto& bl_pattern : blacklist) {\n\t\t\tif (app_regex_partial_match(bl_pattern, dev)) {\n\t\t\t\tblacked = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (blacked)\n\t\t\tcontinue;\n\n\t\tproc_devices.push_back(dev);\n\t}\n\n\t// In case if nvme, smartctl < 7.5 doesn't support self-tests for nvme devices with namespaces.\n\t// Remove the namespace portion from the device name, unless there are multiple namespaces.\n\tstd::vector<std::string> clean_devices;\n\tfor (const auto& dev : proc_devices) {\n\t\tstd::string no_ns_dev;\n\t\tif (app_regex_partial_match(\"/(nvme[0-9]+)n[0-9]+$/\", dev, &no_ns_dev)) {\n\t\t\tauto num_nvmes = std::count_if(proc_devices.begin(), proc_devices.end(), [&no_ns_dev](const std::string& d) {\n\t\t\t\treturn d.starts_with(no_ns_dev);\n\t\t\t});\n\t\t\tif (num_nvmes == 1) {\n\t\t\t\t// Only one namespace, remove the namespace portion.\n\t\t\t\tclean_devices.push_back(no_ns_dev);\n\t\t\t} else {\n\t\t\t\tclean_devices.push_back(dev);\n\t\t\t}\n\t\t} else {\n\t\t\tclean_devices.push_back(dev);\n\t\t}\n\t}\n\n\tstd::vector<std::string> devices;\n\tfor (auto& dev : clean_devices) {\n\t\tdev = \"/dev/\" + dev;  // let's just hope it's really /dev.\n\n\t\tif (std::find(devices.begin(), devices.end(), dev) == devices.end()) {  // there may be duplicates\n\t\t\tdevices.push_back(dev);\n\t\t}\n\t}\n\n\n\tstd::shared_ptr<CommandExecutor> smartctl_ex = ex_factory->create_executor(CommandExecutorFactory::ExecutorType::Smartctl);\n\n\tfor (const auto& device : devices) {\n\t\tauto drive = std::make_shared<StorageDevice>(device);\n\t\tauto fetch_status = drive->fetch_basic_data_and_parse(smartctl_ex);\n\t\tif (!fetch_status) {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// 3ware controllers also export themselves as sd*. Smartctl detects that,\n\t\t// so we can avoid adding them. Older smartctl (5.38) prints \"AMCC\", newer one\n\t\t// prints \"AMCC/3ware controller\". It's better to search it this way.\n\t\tif (app_regex_partial_match(\"/try adding '-d 3ware,N'/im\", drive->get_basic_output())) {\n\t\t\tdebug_out_dump(\"app\", \"Drive \" << drive->get_device_with_type() << \" seems to be a 3ware controller, ignoring.\\n\");\n\t\t} else {\n\t\t\tdrives.push_back(drive);\n\t\t\tdebug_out_info(\"app\", \"Added drive \" << drive->get_device_with_type() << \".\\n\");\n\t\t}\n\t}\n\n\treturn {};\n}\n\n\n\n/** <pre>\nDetect drives behind 3ware RAID controller.\n\n3ware Linux (3w-9xxx, 3w-xxxx, 3w-sas drivers):\nCall as: smartctl -i -d 3ware,[0-127] /dev/twa[0-15]  (or twe[0-15], or twl[0-15])\nUse twe* for [678]xxx series (), twa* for 9xxx series, twl* for 9750 series.\nNote: twe* devices are limited to [0-15] ports (not sure about this).\nNote: /dev/tw* devices may not exist, they are created by smartctl on the first run.\nNote: for twe*, /dev/sda may also exist (to be used with -d 3ware,N), we should\n\tsomehow detect and ignore them.\nNote: when specifying non-existent port, either a \"Device Read Identity Failed\"\n\terror, or a \"blank\" info may be returned.\n\nDetection:\nGrep /proc/devices for \"twa\", \"twe\" or \"twl\" (e.g. \"251 twa\"). Use this for /dev/tw* part.\nGrep /proc/scsi/scsi for AMCC or 3ware (LSI too?), use number of matched lines N\n\tfor /dev/tw*[0, N-1].\nFor detecting the number of ports, use \"tw_cli /cK show all\", K being the controller\n\tscsi number, which is displayed as scsiK in the scsi file.\n\tIf there's no tw_cli, we'll have to scan all the ports (up to supported maximum).\n\tThis is too slow however, so scan only 24.\n\nImplementation notes: it seems that twe uses \"3ware\" and twa uses \"AMCC\"\n(not sure about twl).\nWe can't handle a situation with mixed twa/twe/twl systems, since we don't know\nhow they will be ordered for tw_cli.\n</pre> */\ninline hz::ExpectedVoid<StorageDetectorError> detect_drives_linux_3ware(\n\t\tstd::vector<StorageDevicePtr>& drives, const CommandExecutorFactoryPtr& ex_factory)\n{\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Detecting drives behind 3ware controller(s)...\\n\");\n\n\tstd::vector<std::string> lines;\n\tstd::string error_msg = read_proc_devices_file(lines);\n\tif (!error_msg.empty()) {\n\t\treturn hz::Unexpected(StorageDetectorError::ProcReadError, error_msg);\n\t}\n\n\tbool twa_found = false;\n\tbool twe_found = false;\n\tbool twl_found = false;\n\n\t// Check /proc/devices for twa or twe\n\tfor (const auto& line : lines) {\n\t\tstd::string dev;\n\t\tif (app_regex_partial_match(R\"(/^[ \\t]*[0-9]+[ \\t]+(tw[ael])(?:[ \\t]*|$)/)\", hz::string_trim_copy(line), &dev)) {\n\t\t\tdebug_out_dump(\"app\", DBG_FUNC_MSG << \"Found 3ware \" << dev << \" entry in devices file.\\n\");\n\t\t\tif (dev == \"twa\") {\n\t\t\t\ttwa_found = true;\n\t\t\t} else if (dev == \"twe\") {\n\t\t\t\ttwe_found = true;\n\t\t\t} else if (dev == \"twl\") {\n\t\t\t\ttwl_found = true;\n\t\t\t} else {\n\t\t\t\tDBG_ASSERT(0);  // error in regexp?\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!twa_found && !twe_found && !twl_found) {\n\t\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"No 3ware-specific entries found in devices file.\\n\");\n\t\treturn {};  // no controllers\n\t}\n\n\tlines.clear();\n\n\tdebug_out_dump(\"app\", DBG_FUNC_MSG << \"Checking scsi file for 3ware controllers.\\n\");\n\tstd::vector< std::pair<int, std::string> > vendors_models;\n\terror_msg = read_proc_scsi_scsi_file(vendors_models);\n\tif (!error_msg.empty()) {\n\t\treturn hz::Unexpected(StorageDetectorError::ProcReadError, error_msg);\n\t}\n\n\n\tstd::set<int> controller_hosts;  // scsi hosts (controllers) found for tw*\n\tstd::map<std::string, int> device_numbers;  // device base (e.g. twa) -> number of times found\n\n\tfor (auto& vendor_model : vendors_models) {\n\t\t// twe: 3ware, twa: AMCC, twl: LSI.\n\t\tstd::string vendor;\n\t\tif (!app_regex_partial_match(\"/Vendor: (AMCC)|(3ware)|(LSI) /i\", vendor_model.second, &vendor)) {\n\t\t\tcontinue;  // not a supported controller\n\t\t}\n\n\t\tconst int host_num = vendor_model.first;\n\n\t\tdebug_out_dump(\"app\", \"Found LSI/AMCC/3ware controller in SCSI file, SCSI host \" << host_num << \".\\n\");\n\n\t\t// Skip additional adapters with the same host, since they are the same adapters\n\t\t// with different LUNs.\n\t\tif (controller_hosts.find(host_num) != controller_hosts.end()) {\n\t\t\tdebug_out_dump(\"app\", \"Skipping adapter with SCSI host \" << host_num << \", host already found.\\n\");\n\t\t\tcontinue;\n\t\t}\n\t\tcontroller_hosts.insert(host_num);\n\n\t\tstd::string dev_base = \"twe\";\n\t\tif (twa_found) {\n\t\t\tdev_base = \"twa\";\n\t\t} else if (twl_found) {\n\t\t\tdev_base = \"twl\";\n\t\t}\n\n\t\t// If there are several different tw* devices present (like 1 twa and 1 twe), we\n\t\t// use the vendor name to differentiate them.\n\t\tif (int(twa_found) + int(twe_found) + int(twl_found) > 1) {\n\t\t\tif (twa_found && hz::string_to_lower_copy(vendor) == \"amcc\") {\n\t\t\t\tdev_base = \"twa\";\n\t\t\t} else if (twe_found && hz::string_to_lower_copy(vendor) == \"3ware\") {\n\t\t\t\tdev_base = \"twe\";\n\t\t\t} else if (twl_found && hz::string_to_lower_copy(vendor) == \"lsi\") {\n\t\t\t\tdev_base = \"twl\";\n\t\t\t}\n\t\t\t// else we default to twl, twa, twe (in this order)\n\t\t}\n\n\t\t// We can't map twaX to scsiY, so let's assume the relative order is the same.\n\t\tstd::string dev = std::string(\"/dev/\") + dev_base + hz::number_to_string_nolocale(device_numbers[dev_base]);\n\t\t++device_numbers[dev_base];\n\n\t\tauto exec_status = tw_cli_get_drives(dev, vendor_model.first, drives, ex_factory, false);\n\t\tif (!exec_status) {  // no tw_cli\n\t\t\tint max_ports = rconfig::get_data<int>(\"system/linux_3ware_max_scan_port\");\n\t\t\tmax_ports = std::max(0, std::min(max_ports, 127));  // Sanity check\n\t\t\tdebug_out_dump(\"app\", \"Starting brute-force port scan on 0-\" << max_ports << \" ports, device \\\"\" << dev\n\t\t\t\t\t<< \"\\\". Change the maximum by setting \\\"system/linux_3ware_max_scan_port\\\" config key.\\n\");\n\t\t\tstd::string last_output;\n\t\t\texec_status = smartctl_scan_drives_sequentially(dev, \"3ware,%d\", 0, max_ports, drives, ex_factory, last_output);\n\t\t\tdebug_out_dump(\"app\", \"Brute-force port scan finished.\\n\");\n\t\t}\n\n\t\tif (!exec_status) {\n\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Couldn't get the drives on ports of LSI/AMCC/3ware controller: \" << exec_status.error().message() << \"\\n\");\n\t\t}\n\t}\n\n\tif (controller_hosts.empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"AMCC/LSI/3ware entry found in devices file, but SCSI file contains no known entries.\\n\");\n\t}\n\n\treturn {};\n}\n\n\n\n/** <pre>\nDetect drives behind Adaptec RAID controller (aacraid driver).\nTested using Adaptec RAID 5805 (SAS / SATA controller).\nUses /dev/sgN devices (with -d sat for SATA and -d scsi (default) for SCSI).\n\nAdaptec Linux detection strategy:\nCheck /proc/devices for \"aac\"; if it's there, continue.\nCheck /proc/scsi/scsi for \"Vendor: Adaptec\"; get its host (e.g. scsi6).\nCheck which lines correspond to e.g. host 6 in /proc/scsi/sg/devices; the\nline order (e.g. fourth and fifth lines) correspond to /dev/sg* device (e.g.\n/dev/sg3 and /dev/sg4).\nNote: This will get us at least 2 unusable devices - /dev/sdc (the volume)\nand /dev/sg3 (the controller). I think the controller can be filtered out\nusing \"id > 0\" requirement (the third column of /proc/scsi/sg/devices.\nTry \"-d sat\" by default. If it fails (\"Device Read Identity Failed:\" ? not\nsure how to detect the failure), fall back to \"-d scsi\".\n</pre> */\ninline hz::ExpectedVoid<StorageDetectorError> detect_drives_linux_adaptec(\n\t\tstd::vector<StorageDevicePtr>& drives, const CommandExecutorFactoryPtr& ex_factory)\n{\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Detecting drives behind Adaptec controller(s)...\\n\");\n\n\tstd::vector<std::string> lines;\n\tstd::string error_msg = read_proc_devices_file(lines);\n\tif (!error_msg.empty()) {\n\t\treturn hz::Unexpected(StorageDetectorError::ProcReadError, error_msg);\n\t}\n\n\tbool aac_found = false;\n\n\t// Check /proc/devices for \"aac\"\n\tfor (const auto& line : lines) {\n\t\tif (app_regex_partial_match(R\"(/^[ \\t]*[0-9]+[ \\t]+aac(?:[ \\t]*|$)/)\", hz::string_trim_copy(line))) {\n\t\t\tdebug_out_dump(\"app\", DBG_FUNC_MSG << \"Found aac entry in devices file.\\n\");\n\t\t\taac_found = true;\n\t\t}\n\t}\n\tif (!aac_found) {\n\t\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"No Adaptec-specific entries found in devices file.\\n\");\n\t\treturn {};  // no controllers\n\t}\n\n\tlines.clear();\n\n\tdebug_out_dump(\"app\", DBG_FUNC_MSG << \"Checking scsi file for Adaptec controllers.\\n\");\n\tstd::vector< std::pair<int, std::string> > vendors_models;\n\terror_msg = read_proc_scsi_scsi_file(vendors_models);\n\tif (!error_msg.empty()) {\n\t\treturn hz::Unexpected(StorageDetectorError::ProcReadError, error_msg);\n\t}\n\n\tstd::vector< std::vector<int> > sg_entries;\n\terror_msg = read_proc_scsi_sg_devices_file(sg_entries);\n\tif (!error_msg.empty()) {\n\t\treturn hz::Unexpected(StorageDetectorError::ProcReadError, error_msg);\n\t}\n\n\tstd::shared_ptr<CommandExecutor> smartctl_ex = ex_factory->create_executor(CommandExecutorFactory::ExecutorType::Smartctl);\n\n\tstd::set<int> controller_hosts;\n\n\tfor (const auto& vendors_model : vendors_models) {\n\t\tif (!app_regex_partial_match(\"/Vendor: Adaptec /i\", vendors_model.second)) {\n\t\t\tcontinue;  // not a supported controller\n\t\t}\n\t\tconst int host_num = vendors_model.first;\n\t\tdebug_out_dump(\"app\", \"Found Adaptec controller in SCSI file, SCSI host \" << host_num << \".\\n\");\n\n\t\t// Skip additional adapters with the same host, since they are the same adapters\n\t\t// with different LUNs.\n\t\tif (controller_hosts.find(host_num) != controller_hosts.end()) {\n\t\t\tdebug_out_dump(\"app\", \"Skipping adapter with SCSI host \" << host_num << \", host already found.\\n\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tcontroller_hosts.insert(host_num);\n\n\t\tfor (std::size_t sg_num = 0; sg_num < sg_entries.size(); ++sg_num) {\n\t\t\tif (sg_entries[sg_num].size() < 3) {\n\t\t\t\t// We need at least 3 columns in that file\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (sg_entries[sg_num][0] != host_num || sg_entries[sg_num][2] <= 0) {\n\t\t\t\t// Different scsi host, or scsi id is 0 (the controller, probably)\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst std::string dev = std::string(\"/dev/sg\") + hz::number_to_string_nolocale(sg_num);\n\t\t\tauto drive = std::make_shared<StorageDevice>(dev, std::string(\"sat\"));\n\n\t\t\tauto fetch_status = drive->fetch_basic_data_and_parse(smartctl_ex);\n\t\t\tconst std::string output = drive->get_basic_output();\n\n\t\t\t// Note: Not sure about this, have to check with real SAS drives\n\t\t\tif (app_regex_partial_match(\"/Device Read Identity Failed/mi\", output)) {\n\t\t\t\t// \"-d sat\" didn't work, default back to smartctl's \"-d scsi\"\n\t\t\t\tdrive->clear_parse_results();\n\t\t\t\tdrive->clear_outputs();\n\t\t\t\tdrive->set_type_argument(\"\");\n\t\t\t\tfetch_status = drive->fetch_basic_data_and_parse(smartctl_ex);\n\t\t\t}\n\n\t\t\tif (!fetch_status) {\n\t\t\t\tdebug_out_info(\"app\", \"Smartctl returned with an error: \" << fetch_status.error().message() << \"\\n\");\n\t\t\t\tdebug_out_dump(\"app\", \"Skipping drive \" << drive->get_device_with_type() << \".\\n\");\n\t\t\t} else {\n\t\t\t\tdrives.push_back(drive);\n\t\t\t\tdebug_out_info(\"app\", \"Added drive \" << drive->get_device_with_type() << \".\\n\");\n\t\t\t}\n\t\t}\n\t}\n\n\tif (controller_hosts.empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Adaptec entry found in devices file, but SCSI file contains no known entries.\\n\");\n\t}\n\n\treturn {};\n}\n\n\n\n/** <pre>\nAreca Linux (arcmsr driver):\nCall as:\n\t- smartctl -i -d areca,N /dev/sg0  (N is [1,24]).\n\t- smartctl -i -d areca,N/E /dev/sg0  (N is [1,128], E is [1,8]).\nThe models that have \"ix\" (case-insensitive) in their model names\nhave the enclosures and require the N,E syntax.\nNote: Using N/E syntax with non-enclosure cards seems to work\nregardless of the value of E.\n\nDetection:\n\tThere don't seem to be any entries in /proc/devices.\n\tCheck /proc/scsi/scsi for \"Vendor: Areca\".\n\t(Alternatively, /proc/scsi/sg/device_strs should contain \"Areca       RAID controller\").\nDetect SCSI host (N in hostN below):\n\tIt's N of scsiN in /proc/scsi/sg/device_strs of the entry with \"Vendor: Areca\",\n\twhich has type 3 in /proc/scsi/sg/devices (to filter out logical volumes) and id 16.\n\tThe line number in /proc/scsi/sg/devices is X in /dev/sgX.\nCheck /sys/bus/scsi/devices/hostN/scsi_host/hostN/host_fw_hd_channels, set\n\tits contents as the number of ports. If not present, use 24 (maximum).\n\tNo-enclosure models only, since we have no info yet for the enclosure ones.\nProbe each port for a valid drive: If the output contains \"Device Read Identity Failed\"\n\tor \"empty IDENTIFY data\", then there is no drive on that port (or Areca has an\n\told firmware, nothing we can do there).\nNotification: If /sys/bus/scsi/devices/hostN/scsi_host/hostN/host_fw_version\n\tis older than \"V1.46 2009-01-06\", (1.51 for enclosure-having cards) notify the user\n\t(maybe its better to grep the smartctl output for that on port 0?). NOT IMPLEMENTED YET.\n</pre> */\ninline hz::ExpectedVoid<StorageDetectorError> detect_drives_linux_areca(\n\t\tstd::vector<StorageDevicePtr>& drives, const CommandExecutorFactoryPtr& ex_factory)\n{\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Detecting drives behind Areca controller(s)...\\n\");\n\n\tstd::vector< std::pair<int, std::string> > vendors_models;\n\tstd::string error_msg = read_proc_scsi_scsi_file(vendors_models);\n\tif (!error_msg.empty()) {\n\t\treturn hz::Unexpected(StorageDetectorError::ProcReadError, error_msg);\n\t}\n\n\tstd::map<int, bool> controller_hosts;  // controller # -> has_enclosure\n\n\tfor (auto& vendor_model : vendors_models) {\n\t\tstd::string vendor_line = vendor_model.second;\n\t\tif (!app_regex_partial_match(\"/Vendor: Areca /i\", vendor_line)) {\n\t\t\tcontinue;  // not a supported controller\n\t\t}\n\t\tint host_num = vendor_model.first;\n\t\tdebug_out_dump(\"app\", \"Found Areca controller in SCSI file, SCSI host \" << host_num << \".\\n\");\n\n\t\t// Skip additional adapters with the same host (they are volumes)\n\t\tif (controller_hosts.find(host_num) != controller_hosts.end()) {\n\t\t\tdebug_out_dump(\"app\", \"Skipping adapter with SCSI host \" << host_num << \", host already found.\\n\");\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Check if it contains the \"ix\" string in its model name. It may not be exactly\n\t\t// a suffix (there may be \"-VOL#00\" or something like that appended as well).\n\t\tbool has_enclosure = app_regex_partial_match(\"/Model:.+ix.+Rev:/i\", vendor_line);\n\t\tdebug_out_dump(\"app\", DBG_FUNC_MSG << \"Areca controller (SCSI host \" << host_num\n\t\t\t\t<< \") seems to \" << (has_enclosure ? \"have enclosure(s)\" : \"have no enclosures\") << \".\\n\");\n\n\t\tcontroller_hosts[host_num] = has_enclosure;\n\t}\n\n\tif (controller_hosts.empty()) {\n\t\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"No Areca-specific entries found in SCSI file.\\n\");\n\t\treturn {};\n\t}\n\n\tstd::vector< std::vector<int> > sg_entries;\n\terror_msg = read_proc_scsi_sg_devices_file(sg_entries);\n\tif (!error_msg.empty()) {\n\t\treturn hz::Unexpected(StorageDetectorError::ProcReadError, error_msg);\n\t}\n\n\tstd::shared_ptr<CommandExecutor> smartctl_ex = ex_factory->create_executor(CommandExecutorFactory::ExecutorType::Smartctl);\n\n\tfor (auto& iter : controller_hosts) {\n\t\tconst int host_num = iter.first;\n\t\tconst bool has_enclosure = iter.second;\n\n\t\tfor (std::size_t sg_num = 0; sg_num < sg_entries.size(); ++sg_num) {\n\t\t\tif (sg_entries[sg_num].size() < 5) {\n\t\t\t\t// We need at least 5 columns in that file\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst int type = sg_entries[sg_num][4];  // type 3 is Areca controller\n\t\t\tconst int id = sg_entries[sg_num][2];  // id should be 16 (as per smartmontools)\n\t\t\tif (sg_entries[sg_num][0] != host_num || id != 16 || type != 3) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\thz::ExpectedVoid<StorageDetectorError> exec_status;\n\n\t\t\tif (has_enclosure) {\n\t\t\t\t// TODO We have no information on what \"/sys/bus/scsi/devices/host%d/scsi_host/host%d/host_fw_hd_channels\"\n\t\t\t\t// contains in case of enclosure-having cards.\n\n\t\t\t\tint max_enclosures = rconfig::get_data<int>(\"system/linux_areca_enc_max_enclosure\");\n\t\t\t\tmax_enclosures = std::max(1, std::min(8, max_enclosures));  // 1-8\n\n\t\t\t\tint max_ports = rconfig::get_data<int>(\"system/linux_areca_enc_max_scan_port\");\n\t\t\t\tmax_ports = std::max(1, std::min(128, max_ports));  // 1-128 sanity check\n\n\t\t\t\tstd::string dev = std::string(\"/dev/sg\") + hz::number_to_string_nolocale(sg_num);\n\n\t\t\t\tdebug_out_dump(\"app\", \"Starting brute-force port/enclosure scan on 1-\" << max_ports << \" ports, 1-\" << max_enclosures << \" enclosures, device \\\"\" << dev\n\t\t\t\t\t\t<< \"\\\". Change the maximums by setting \\\"system/linux_areca_enc_max_scan_port\\\" and \\\"system/linux_areca_enc_max_enclosure\\\" config keys.\\n\");\n\t\t\t\tstd::string last_output;\n\t\t\t\tfor (int enclosure_no = 1; enclosure_no < max_enclosures; ++enclosure_no) {\n\t\t\t\t\texec_status = smartctl_scan_drives_sequentially(dev, \"areca,%d/\" + hz::number_to_string_nolocale(enclosure_no), 1, max_ports, drives, ex_factory, last_output);\n\t\t\t\t}\n\t\t\t\tdebug_out_dump(\"app\", \"Brute-force port/enclosure scan finished.\\n\");\n\n\t\t\t} else {\n\t\t\t\tint max_ports = 0;\n\n\t\t\t\t// Read the number of ports.\n\t\t\t\tauto ports_file = hz::fs_path_from_string(hz::string_sprintf(\"/sys/bus/scsi/devices/host%d/scsi_host/host%d/host_fw_hd_channels\", host_num, host_num));\n\t\t\t\tstd::string ports_file_contents;\n\t\t\t\tauto ec = read_proc_file(ports_file, ports_file_contents);\n\t\t\t\tif (ec) {\n\t\t\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Couldn't read the number of ports on Areca controller (\\\"\" << ports_file.string() << \"\\\"): \"\n\t\t\t\t\t\t\t<< ec.message() << \", trying manually.\\n\");\n\t\t\t\t} else {\n\t\t\t\t\thz::string_is_numeric_nolocale(hz::string_trim_copy(ports_file_contents), max_ports);\n\t\t\t\t\tdebug_out_dump(\"app\", DBG_FUNC_MSG << \"Detected \" << max_ports << \" ports, through \\\"\" << ports_file.string() << \"\\\".\\n\");\n\t\t\t\t}\n\t\t\t\tif (max_ports == 0) {\n\t\t\t\t\tmax_ports = rconfig::get_data<int>(\"system/linux_areca_noenc_max_scan_port\");\n\t\t\t\t}\n\t\t\t\tmax_ports = std::max(1, std::min(24, max_ports));  // 1-24 sanity check\n\n\t\t\t\tstd::string dev = std::string(\"/dev/sg\") + hz::number_to_string_nolocale(sg_num);\n\t\t\t\tdebug_out_dump(\"app\", \"Starting brute-force port scan on 1-\" << max_ports << \" ports, device \\\"\" << dev\n\t\t\t\t\t\t<< \"\\\". Change the maximum by setting \\\"system/linux_areca_noenc_max_scan_port\\\" config key.\\n\");\n\t\t\t\tstd::string last_output;\n\t\t\t\texec_status = smartctl_scan_drives_sequentially(dev, \"areca,%d\", 1, max_ports, drives, ex_factory, last_output);\n\t\t\t\tdebug_out_dump(\"app\", \"Brute-force port scan finished.\\n\");\n\t\t\t}\n\n\t\t\tif (!exec_status) {\n\t\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Couldn't get the drives on ports of Areca controller: \" << exec_status.error().message() << \"\\n\");\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {};\n}\n\n\n\n/** <pre>\nDetect drives behind HP RAID controller (cciss driver).\n(aka CCISS (HP (Compaq) Smart Array Controller).\n\nNote: hpsa/hpahcisr have a different method of detection.\n\nCall as: smartctl -a -d cciss,[0-127] /dev/cciss/c0d0\n\tWith the cciss drivers , for the path /dev/cciss/c0d0, c0 means controler 0 and d0 means LUN 1.\n\tSo smarctl provides the same values for c0d0 and c0d1. There are just two logical drives\n\ton the same Smart Array controler.\nTo detect controller presence: cat /proc/devices, contains \"cciss0\"\n\tfor controller 0.\n\t/proc/scsi/scsi contains no entries for it.\n/dev/cciss/c0d0p1 is the first ordinary partition (used in mount, etc.).\n\nThe port limit had a brief regression in 5.39.x (limited to 15).\n5.39 doesn't seem to work at all (prereleases work with P400 controller)?\n\tPossibly a 64-bit-only issue?\nThere is a cciss_vol_status utility but it doesn't report anything except \"OK\" status.\nIt may not return the \"SMART Health Status: OK\" line in smartctl -i for some reason?\n\t(It returns it in -a though, and in some freebsd output I found).\nIf no /dev/cciss exists, you may need to run \"cd /dev; ./MAKEDEV cciss\".\n\nDetection:\n\tGrep /proc/devices for \"cciss([0-9]+)\" where the number is the controller #.\n\tRun \"smartctl -i -d cciss,[0-127] /dev/cciss/cNd0\" (where N is the controller #),\n\t\tuntil \"No such device or address\" or \"VALID ARGUMENTS ARE\" is encountered in the output.\n\tNote: We're not sure how to differentiate the outputs of free / non-existent ports,\n\t\tso scan them until 15, just in case.\n</pre> */\ninline hz::ExpectedVoid<StorageDetectorError> detect_drives_linux_cciss(\n\t\tstd::vector<StorageDevicePtr>& drives, const CommandExecutorFactoryPtr& ex_factory)\n{\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Detecting drives behind HP RAID (CCISS) controller(s)...\\n\");\n\n\tstd::vector<std::string> lines;\n\tstd::string error_msg = read_proc_devices_file(lines);\n\tif (!error_msg.empty()) {\n\t\treturn hz::Unexpected(StorageDetectorError::ProcReadError, error_msg);\n\t}\n\n\tstd::vector<int> controllers;\n\n\t// Check /proc/devices for ccissX (where X is the controller #).\n\tfor (const auto& line : lines) {\n\t\tstd::string controller_no_str;\n\t\tif (app_regex_partial_match(R\"(/^[ \\t]*[0-9]+[ \\t]+cciss([0-9]+)(?:[ \\t]*|$)/)\", hz::string_trim_copy(line), &controller_no_str)) {\n\t\t\tdebug_out_dump(\"app\", DBG_FUNC_MSG << \"Found cciss\" << controller_no_str << \" entry in devices file.\\n\");\n\t\t\tcontrollers.push_back(hz::string_to_number_nolocale<int>(controller_no_str));\n\t\t}\n\t}\n\tif (controllers.empty()) {\n\t\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"No cciss-specific entries found in devices file.\\n\");\n\t\treturn {};  // no controllers\n\t}\n\n\tstd::shared_ptr<CommandExecutor> smartctl_ex = ex_factory->create_executor(CommandExecutorFactory::ExecutorType::Smartctl);\n\n\tfor (int controller_no : controllers) {\n\t\tstd::string dev = std::string(\"/dev/cciss/c\") + hz::number_to_string_nolocale(controller_no) + \"d0\";\n\n\t\tconst int max_port = 127;\n\t\tdebug_out_dump(\"app\", \"Starting brute-force port scan on 1-\" << max_port << \" ports, device \\\"\" << dev << \"\\\".\\n\");\n\n\t\tfor (int port = 0; port <= max_port; ++port) {\n\t\t\tauto drive = std::make_shared<StorageDevice>(dev, std::string(\"cciss,\") + hz::number_to_string_nolocale(port));\n\n\t\t\tauto fetch_status = drive->fetch_basic_data_and_parse(smartctl_ex);\n\t\t\tstd::string output = drive->get_basic_output();\n\n\t\t\tif (!fetch_status) {\n\t\t\t\tdebug_out_info(\"app\", \"Smartctl returned with an error: \" << fetch_status.error().message() << \"\\n\");\n\t\t\t}\n\n\t\t\tif (app_regex_partial_match(\"/VALID ARGUMENTS ARE/mi\", output)) {\n\t\t\t\t// smartctl doesn't support this many ports, return.\n\t\t\t\tdebug_out_dump(\"app\", \"Reached smartctl port limit with port \" << port << \", stopping port scan.\\n\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (app_regex_partial_match(\"/No such device or address/mi\", output) && port > 15) {\n\t\t\t\t// we've reached the controller port limit\n\t\t\t\tdebug_out_dump(\"app\", \"Reached controller port limit with port \" << port << \", stopping port scan.\\n\");\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (fetch_status) {\n\t\t\t\tdrives.push_back(drive);\n\t\t\t\tdebug_out_info(\"app\", \"Added drive \" << drive->get_device_with_type() << \".\\n\");\n\t\t\t} else {\n\t\t\t\tdebug_out_dump(\"app\", \"Skipping drive \" << drive->get_device_with_type() << \" due to smartctl error.\\n\");\n\t\t\t}\n\t\t}\n\n\t\tdebug_out_dump(\"app\", \"Brute-force port scan finished.\\n\");\n\t}\n\n\treturn {};\n}\n\n\n\n/** <pre>\nDetect drives behind HP RAID controller (hpsa / hpahcisr drivers).\n\nMay need \"-d sat+cciss,N\" for SATA, but is usually auto-detected.\n\nsmartctl -a -d cciss,0 /dev/sg2    (hpsa or hpahcisr drivers under Linux)\n(\"lsscsi -g\" is helpful in determining which scsi generic device node corresponds to which device.)\nUse the nodes corresponding to the RAID controllers, not the nodes corresponding to logical drives.\nNo entry in /proc/devices.\n/dev/sda also seems to work?\n\nDetection:\n\tCheck /proc/scsi for \"Vendor: HP\", and NOT \"Model: LOGICAL VOLUME\". Take its scsi host number,\n\t\tmatch against /proc/scsi/sg/devices (first column). The matched line number\n\t\tis the one to use in /dev/sgN.\n\tRun smartctl -i -d cciss,[0-127] /dev/cciss/cNd0\n\t\tuntil \"No such device or address\" or \"VALID ARGUMENTS ARE\" is encountered in output.\n</pre> */\ninline hz::ExpectedVoid<StorageDetectorError> detect_drives_linux_hpsa(\n\t\tstd::vector<StorageDevicePtr>& drives, const CommandExecutorFactoryPtr& ex_factory)\n{\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Detecting drives behind HP RAID (hpsa/hpahcisr) controller(s)...\\n\");\n\n\tstd::vector< std::pair<int, std::string> > vendors_models;\n\tstd::string  error_msg = read_proc_scsi_scsi_file(vendors_models);\n\tif (!error_msg.empty()) {\n\t\treturn hz::Unexpected(StorageDetectorError::ProcReadError, error_msg);\n\t}\n\n\tstd::vector< std::vector<int> > sg_entries;\n\terror_msg = read_proc_scsi_sg_devices_file(sg_entries);\n\tif (!error_msg.empty()) {\n\t\treturn hz::Unexpected(StorageDetectorError::ProcReadError, error_msg);\n\t}\n\n\tstd::shared_ptr<CommandExecutor> smartctl_ex = ex_factory->create_executor(CommandExecutorFactory::ExecutorType::Smartctl);\n\n\tstd::set<int> controller_hosts;\n\n\tfor (auto& vendor_model : vendors_models) {\n\t\tif (!app_regex_partial_match(\"/Vendor: HP /i\", vendor_model.second)\n\t\t\t\t|| app_regex_partial_match(\"/LOGICAL VOLUME/i\", vendor_model.second)) {\n\t\t\tcontinue;  // not a supported controller, or a logical drive.\n\t\t}\n\t\tint host_num = vendor_model.first;\n\t\tdebug_out_dump(\"app\", \"Found HP controller in SCSI file, SCSI host \" << host_num << \".\\n\");\n\n\t\t// Skip additional adapters with the same host (not sure if this is needed, but it won't hurt).\n\t\tif (controller_hosts.find(host_num) != controller_hosts.end()) {\n\t\t\tdebug_out_dump(\"app\", \"Skipping adapter with SCSI host \" << host_num << \", host already found.\\n\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tcontroller_hosts.insert(host_num);\n\n\t\tfor (std::size_t sg_num = 0; sg_num < sg_entries.size(); ++sg_num) {\n\t\t\tif (sg_entries[sg_num].size() < 3) {\n\t\t\t\t// We need at least 3 columns in that file\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (sg_entries[sg_num][0] != host_num) {\n\t\t\t\t// Different scsi host\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tstd::string dev = std::string(\"/dev/sg\") + hz::number_to_string_nolocale(sg_num);\n\n\t\t\tconst int max_port = 127;\n\t\t\tdebug_out_dump(\"app\", \"Starting brute-force port scan on 0-\" << max_port << \" ports, device \\\"\" << dev << \"\\\".\\n\");\n\n\t\t\tfor (int port = 0; port <= max_port; ++port) {\n\t\t\t\tauto drive = std::make_shared<StorageDevice>(dev, std::string(\"cciss,\") + hz::number_to_string_nolocale(port));\n\n\t\t\t\tauto fetch_status = drive->fetch_basic_data_and_parse(smartctl_ex);\n\t\t\t\tstd::string output = drive->get_basic_output();\n\n\t\t\t\tif (app_regex_partial_match(\"/No such device or address/mi\", output)\n\t\t\t\t\t\t|| app_regex_partial_match(\"/VALID ARGUMENTS ARE/mi\", output)) {\n\t\t\t\t\t// We reached the controller port limit, or smartctl-supported port limit.\n\t\t\t\t\tdebug_out_dump(\"app\", \"Reached controller or smartctl port limit with port \" << port << \", stopping port scan.\\n\");\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (!fetch_status) {\n\t\t\t\t\tdebug_out_info(\"app\", \"Smartctl returned with an error: \" << fetch_status.error().message() << \"\\n\");\n\t\t\t\t\tdebug_out_dump(\"app\", \"Skipping drive \" << drive->get_device_with_type() << \" due to smartctl error.\\n\");\n\t\t\t\t} else {\n\t\t\t\t\tdrives.push_back(drive);\n\t\t\t\t\tdebug_out_info(\"app\", \"Added drive \" << drive->get_device_with_type() << \".\\n\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdebug_out_dump(\"app\", \"Brute-force port scan finished.\\n\");\n\t\t}\n\t}\n\n\tif (controller_hosts.empty()) {\n\t\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"No hpsa/hpahcisr-specific entries found in Sg devices file.\\n\");\n\t}\n\n\treturn {};\n}\n\n\n\n}  // anon ns\n\n\n\n\nhz::ExpectedVoid<StorageDetectorError> detect_drives_linux(\n\t\tstd::vector<StorageDevicePtr>& drives, const CommandExecutorFactoryPtr& ex_factory)\n{\n\tclear_read_file_cache();\n\n\tstd::vector<std::string> error_msgs;\n\thz::ExpectedVoid<StorageDetectorError> status;\n\n\t// Disable by-id detection - it's unreliable on broken systems.\n\t// For example, on Ubuntu 8.04, /dev/disk/by-id contains two device\n\t// links for two drives, but both point to the same sdb (instead of\n\t// sda and sdb). Plus, there are no \"*-partN\" files (not that we need them).\n// \terror_message = detect_drives_linux_udev_byid(devices);  // linux udev\n\n\tstatus = detect_drives_linux_proc_partitions(drives, ex_factory);\n\tif (!status) {\n\t\terror_msgs.push_back(status.error().message());\n\t}\n\n\tstatus = detect_drives_linux_3ware(drives, ex_factory);\n\tif (!status) {\n\t\terror_msgs.push_back(status.error().message());\n\t}\n\n\tstatus = detect_drives_linux_areca(drives, ex_factory);\n\tif (!status) {\n\t\terror_msgs.push_back(status.error().message());\n\t}\n\n\tstatus = detect_drives_linux_adaptec(drives, ex_factory);\n\tif (!status) {\n\t\terror_msgs.push_back(status.error().message());\n\t}\n\n\tstatus = detect_drives_linux_cciss(drives, ex_factory);\n\tif (!status) {\n\t\terror_msgs.push_back(status.error().message());\n\t}\n\n\tstatus = detect_drives_linux_hpsa(drives, ex_factory);\n\tif (!status) {\n\t\terror_msgs.push_back(status.error().message());\n\t}\n\n\tif (!error_msgs.empty()) {\n\t\treturn hz::Unexpected(StorageDetectorError::GeneralDetectionErrors, hz::string_join(error_msgs, \"\\n\"));\n\t}\n\n\treturn {};\n}\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_detector_linux.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef STORAGE_DETECTOR_LINUX_H\n#define STORAGE_DETECTOR_LINUX_H\n\n#include \"build_config.h\"\n\n\n#include <string>\n#include <vector>\n\n#include \"command_executor_factory.h\"\n#include \"storage_device.h\"\n#include \"storage_detector.h\"\n\n\n\n/// Detect drives in Linux\n[[nodiscard]] hz::ExpectedVoid<StorageDetectorError> detect_drives_linux(std::vector<StorageDevicePtr>& drives,\n\t\tconst CommandExecutorFactoryPtr& ex_factory);\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_detector_other.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include \"build_config.h\"\n\n#include <glibmm.h>\n#include <algorithm>  // std::sort\n#include <cerrno>\n\n#if defined CONFIG_KERNEL_OPENBSD || defined CONFIG_KERNEL_NETBSD\n\t#include <util.h>  // getrawpartition()\n#endif\n\n#include \"fmt/format.h\"\n#include \"hz/debug.h\"\n#include \"hz/fs.h\"\n#include \"rconfig/rconfig.h\"\n#include \"app_regex.h\"\n#include \"storage_detector_other.h\"\n\n\n\n\nhz::ExpectedVoid<StorageDetectorError> detect_drives_other(std::vector<StorageDevicePtr>& drives,\n\t\t[[maybe_unused]] const CommandExecutorFactoryPtr& ex_factory)\n{\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Detecting drives through /dev...\\n\");\n\n\tstd::vector<std::string> devices;\n\n\tstd::string sdev_config_path;\n\tif constexpr(BuildEnv::is_kernel_solaris()) {\n\t\tsdev_config_path = \"system/solaris_dev_path\";\n\t} else {  // other unixes\n\t\tsdev_config_path = \"system/unix_sdev_path\";\n\t}\n\n\t// defaults to /dev for freebsd, /dev/rdsk for solaris.\n\tauto dev_dir = rconfig::get_data<std::string>(sdev_config_path);\n\tif (dev_dir.empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Device directory path is not set.\\n\");\n\t\treturn hz::Unexpected(StorageDetectorError::ConfigError, _(\"Device directory path is not set.\"));\n\t}\n\n\tauto dir = hz::fs_path_from_string(dev_dir);\n\tstd::error_code dummy_ec;\n\tif (!hz::fs::exists(dir, dummy_ec)) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Device directory doesn't exist.\\n\");\n\t\treturn hz::Unexpected(StorageDetectorError::ConfigError, _(\"Device directory does not exist.\"));\n\t}\n\n\tstd::vector<std::string> whitelist;\n\n\n\tif constexpr(BuildEnv::is_kernel_freebsd() || BuildEnv::is_kernel_dragonfly()) {\n\t\t// FreeBSD example device names:\n\t\t// * /dev/ad0 - ide disk 0. /dev/ad0a - non-slice dos partition 1.\n\t\t// * /dev/da1s1e - scsi disk 1 (second disk), slice 1 (aka dos partition 1),\n\t\t// bsd partition e (multiple bsd partitions may be inside one dos partition).\n\t\t// * /dev/acd0 - first cdrom. /dev/acd0c - ?.\n\t\t// Info: http://www.freebsd.org/doc/en/books/handbook/disks-naming.html\n\n\t\t// Of two machines I had access to, freebsd 6.3 had only real devices,\n\t\t// while freebsd 4.10 had lots of dummy ones (ad1, ad2, ad3, da0, da1, da2, da3, sa0, ...).\n\n\t\twhitelist = {\n\t\t\t\"/^ad[0-9]+$/\",  // adN without suffix - fbsd ide\n\t\t\t\"/^da[0-9]+$/\",  // daN without suffix - fbsd scsi, usb\n\t\t\t\"/^ada[0-9]+$/\",  // adaN without suffix - fbsd ata cam\n\t\t// \t\"/\t^sa[0-9]+$/\",  // saN without suffix - fbsd scsi tape\n\t\t// \t\"/^ast[0-9]+$/\",  // astN without suffix - fbsd ide tape\n\t\t\t\"/^aacd[0-9]+$/\",  // fbsd adaptec raid\n\t\t\t\"/^mlxd[0-9]+$/\",  // fbsd mylex raid\n\t\t\t\"/^mlyd[0-9]+$/\",  // fbsd mylex raid\n\t\t\t\"/^amrd[0-9]+$/\",  // fbsd AMI raid\n\t\t\t\"/^idad[0-9]+$/\",  // fbsd compaq raid\n\t\t\t\"/^twed[0-9]+$/\",  // fbsd 3ware raid\n\t\t\t// these are checked by smartctl, but they are not mentioned in freebsd docs:\n\t\t\t\"/^tw[ae][0-9]+$/\",  // fbsd 3ware raid\n\t\t};\n\t\t// unused: acd - ide cdrom, cd - scsi cdrom, mcd - mitsumi cdrom, scd - sony cdrom,\n\t\t// fd - floppy, fla - diskonchip flash.\n\n\t} else if constexpr(BuildEnv::is_kernel_solaris()) {\n\n\t\t// /dev/rdsk contains \"raw\" physical char devices, as opposed to\n\t\t// \"filesystem\" block devices in /dev/dsk. smartctl needs rdsk.\n\n\t\t// x86:\n\n\t\t// /dev/rdsk/c0t0d0p0:1\n\t\t// Where c0 is the controller number.\n\t\t// t0 is the target (SCSI ID number) (omit for ATAPI)\n\t\t// d0 is always 0 for SCSI, the drive # for ATAPI\n\t\t// p0 is the partition (p0 is the entire disk, or p1 - p4)\n\t\t// :1 is the logical drive (c - z or 1 - 24) (that is, logical partitions inside extended partition)\n\n\t\t// /dev/rdsk/c0d0p2:1\n\t\t// where p2 means the extended partition (type 0x05) is partition 2 (out of 1-4) and\n\t\t// \":1\" is the 2nd extended partition (:0 would be the first extended partition).  -- not sure about this!\n\n\t\t// p0 whole physical disk\n\t\t// p1 - p4 Four primary fdisk partitions\n\t\t// p5 - p30 26 logical drives in extended DOS partition (not implemented by Solaris)\n\t\t// s0 - s15 16 slices in the Solaris FDISK partition (SPARC has only 8)\n\t\t// On Solaris, by convention, s2 is the whole Solaris partition.\n\n\t\t// SPARC: There are no *p* devices on sparc afaik, only slices. By convention,\n\t\t// s2 is used as a \"whole\" disk there (but not with EFI partitions!).\n\t\t// So, c0d0s2 is the whole disk there.\n\t\t// x86 has both c0d0s2 and c0d0p2, where s2 is a whole solaris partition.\n\n\t\t// NOTE: It seems that smartctl searches for s0 (aka root partition) in the end. I'm not sure\n\t\t// what implications this has for x86, but we replicate this behaviour.\n\n\t\t// Note: Cdroms are ATAPI, so they pose as SCSI, as opposed to IDE hds.\n\n\t\t// TODO: No idea how to implement /dev/rmt (scsi tape devices),\n\t\t// I have no files in that directory.\n\n\t\twhitelist = {\n\t\t\t\"/^c[0-9]+(?:t[0-9]+)?d[0-9]+s0$/\"\n\t\t};\n\n\t} else if constexpr(BuildEnv::is_kernel_openbsd() || BuildEnv::is_kernel_netbsd()) {\n\n\t\t// OpenBSD / NetBSD have /dev/wdNc for IDE/ATA, /dev/sdNc for SCSI disk,\n\t\t// /dev/stNc for scsi tape. N is [0-9]+. \"c\" means \"whole disk\" (not sure about\n\t\t// different architectures though). There are no \"sdN\" devices, only \"sdNP\".\n\t\t// Another manual says that wd0d would be a whole disk, while wd0c is its\n\t\t// bsd part only (on x86) (only on netbsd?). Anyway, getrawpartition() gives\n\t\t// us the letter we need.\n\t\t// There is no additional level of names for BSD subpartitions (unlike freebsd).\n\t\t// cd0a is cdrom.\n\t\t// Dummy devices are present.\n\n\t\t// Note: This detection may take a while (probably due to open() check).\n\n\t\tchar whole_part = 'a';\n\t#if defined CONFIG_KERNEL_OPENBSD || defined CONFIG_KERNEL_NETBSD\n\t\twhole_part = 'a' + getrawpartition();  // from bsd's util.h\n\t#endif\n\n\t\twhitelist = {\n\t\t\thz::string_sprintf(\"/^wd[0-9]+%c$/\", whole_part),\n\t\t\thz::string_sprintf(\"/^sd[0-9]+%c$/\", whole_part),\n\t\t\thz::string_sprintf(\"/^st[0-9]+%c$/\", whole_part),\n\t\t};\n\n\n\t} else if constexpr(BuildEnv::is_kernel_darwin()) {\n\t\t// Darwin has /dev/disk0, /dev/disk0s1, etc.\n\t\t// Only real devices are present, so no need for additional checks.\n\n\t\twhitelist = {\n\t\t\t\"/^disk[0-9]+$/\"\n\t\t};\n\n\n\t} else if constexpr(BuildEnv::is_kernel_qnx()) {\n\t\t// QNX has /dev/hd0, /dev/hd0t78 (partition?).\n\t\t// Afaik, IDE and SCSI have the same prefix. fd for floppy, cd for cdrom.\n\t\t// Not sure about the tapes.\n\t\t// Only real devices are present, so no need for additional checks.\n\n\t\twhitelist = {\n\t\t\t\"/^hd[0-9]+$/\",\n\t\t};\n\n\t}  // unix platforms\n\n\n\tstd::vector<hz::fs::path> matched_devices;\n\tstd::error_code ec;\n\tfor(const auto& entry : hz::fs::directory_iterator(dir, ec)) {\n\t\tconst auto& path = entry.path();\n\n\t\tbool matched = false;\n\t\tfor (const auto& wl_pattern : whitelist) {\n\t\t\tif (app_regex_partial_match(wl_pattern, path.filename().string())) {\n\t\t\t\tmatched = true;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!matched)\n\t\t\tcontinue;\n\n\t\t// In case these are links, check if the originals exists (solaris has dangling links, filter them out).\n\t\t// We don't replace /dev files with real devices - it leads to really bad paths (pci ids for solaris, etc.).\n\t\tif (!hz::fs::exists(path)) {\n\t\t\tcontinue;\n\t\t}\n\t\tmatched_devices.push_back(path);\n\t}\n\tif (ec) {\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Cannot list device directory entries.\\n\");\n\t\tstd::string message = ec.message();\n\t\treturn hz::Unexpected(StorageDetectorError::DevOpenError,\n\t\t\t\tfmt::format(fmt::runtime(_(\"Cannot list device directory entries: {}\")), message));\n\t}\n\n\n\t// List ones who need dummy device filtering\n\tif constexpr(BuildEnv::is_kernel_freebsd() || BuildEnv::is_kernel_dragonfly()\n\t\t\t|| BuildEnv::is_kernel_openbsd() || BuildEnv::is_kernel_netbsd()) {\n\t\t// Since we may have encountered dummy devices, we should check\n\t\t// if they really exist. Unfortunately, this involves opening them, which\n\t\t// is not good with some media (e.g. it may hang on audio cd (freebsd, maybe others), etc.).\n\t\t// That's why we don't whitelist cd devices. Let's hope usb and others are\n\t\t// ok with this. Dummy devices give errno ENXIO (6, Device not configured) on *bsd.\n\t\t// In Linux ENXIO is (6, No such device or address).\n\n\t\t// Don't do this on solaris - we can't distinguish between cdroms and hds there.\n\n\t\t// If there are less than 4 devices, they are probably not dummy (newer freebsd).\n\t\tbool open_needed = (matched_devices.size() >= 4);\n\t\tif (open_needed) {\n\t\t\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Number of matched devices is \"\n\t\t\t\t\t<< matched_devices.size() << \", will try to filter non-existent ones out.\\n\");\n\t\t} else {\n\t\t\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Number of matched devices is \"\n\t\t\t\t\t<< matched_devices.size() << \", no need for filtering them out.\\n\");\n\t\t}\n\n\t\tfor (const auto& dev : matched_devices) {\n\t\t\tif (open_needed) {\n\t\t\t\tstd::FILE* fp = hz::fs_platform_fopen(dev, \"rb\");\n\t\t\t\tif (!fp && errno == ENXIO) {\n\t\t\t\t\tdebug_out_dump(\"app\", DBG_FUNC_MSG << \"Device \\\"\" << dev.string() << \"\\\" failed to open, ignoring.\\n\");\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (fp) {\n\t\t\t\t\t[[maybe_unused]] auto close_status = std::fclose(fp);\n\t\t\t\t}\n\t\t\t\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Device \\\"\" << dev.string() << \"\\\" opened successfully, adding to device list.\\n\");\n\t\t\t}\n\t\t\tdevices.push_back(dev.string());\n\t\t}\n\n\t} else {  // not *BSD\n\t\tfor (const auto& dev : matched_devices) {\n\t\t\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Device \\\"\" << dev << \"\\\" matched the whitelist, adding to device list.\\n\");\n\t\t\tdevices.push_back(dev.string());\n\t\t}\n\t}\n\n\tfor (auto& device : devices) {\n\t\tdrives.emplace_back(std::make_shared<StorageDevice>(device));\n\t}\n\n\treturn {};\n}\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_detector_other.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef STORAGE_DETECTOR_OTHER_H\n#define STORAGE_DETECTOR_OTHER_H\n\n#include \"build_config.h\"\n\n#include <string>\n#include <vector>\n\n#include \"command_executor_factory.h\"\n#include \"storage_device.h\"\n#include \"storage_detector.h\"\n\n\n/// Detect drives in FreeBSD, Solaris, etc. (all except Linux and Windows).\n[[nodiscard]] hz::ExpectedVoid<StorageDetectorError> detect_drives_other(std::vector<StorageDevicePtr>& drives,\n\t\tconst CommandExecutorFactoryPtr& ex_factory);\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_detector_win32.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include \"build_config.h\"\n\n#include <glibmm.h>\n#include <set>\n#include <bitset>\n#include <map>\n#include <vector>\n#include <memory>\n\n#ifdef _WIN32\n\t#include <windows.h>  // CreateFileA(), CloseHandle(), etc.\n#endif\n\n#include \"hz/win32_tools.h\"\n#include \"hz/string_sprintf.h\"\n#include \"hz/fs.h\"\n#include \"hz/string_num.h\"\n#include \"rconfig/rconfig.h\"\n#include \"app_regex.h\"\n#include \"storage_detector_win32.h\"\n#include \"storage_detector_helpers.h\"\n#include \"smartctl_executor.h\"  // get_smartctl_binary\n\n\n\n/**\n\\file\n<pre>\n3ware Windows detection:\nFor 3ware 9xxx only.\nCall as: smartctl -i sd[a-z],N\n\tN is port, a-z is logical drive (unit) provided by controller.\n\tN is limited to [0, 31] in the code.\n\tThe sd[a-z] device actually exists as \\\\\\\\.\\\\PhysicalDrive[0-N].\n\t\tNo idea how to check if it's 3ware.\nCall as: smarctl -i tw_cli/cx/py\n\tThis runs tw_cli tool and parses the output; controller x, port y.\n\ttw_cli may be needed for older controllers / drivers.\n\tIn tw_cli mode only limited information-gathering is supported.\ntw_cli (part of 3DM2) is automatically added to system PATH,\n\tno need to look for it.\n3DM2 install can be detected by checking:\n\tHKEY_USERS\\\\.DEFAULT\\\\Software\\\\3ware\\\\3DM2, InstallPath\nAnother option for detection (whether it's 3ware) would be getting\n\t\\\\\\\\.\\\\PhysicalDrive0 properties, like smartctl does.\nNewer (added after 5.39.1) smartctl supports --scan-open, which will give us:\n\t/dev/sda,0 -d ata (opened)\n\t/dev/sda,1 -d ata (opened)\n-d 3ware is not needed under Windows. We should treat sda as pd0\n\tand remove pd0 from PhysicalDrive-detected list.\nRunning smartctl on sda gives almost the same result as on sda,0.\n\nsmartctl --scan-open output for win32 with 3ware RAID:\n------------------------------------------------------------------\n/dev/sda,0 -d ata (opened)\n/dev/sda,1 -d ata (opened)\n------------------------------------------------------------------\n\n\nIntel Matrix RAID (since smartmontools SVN version on 2011-02-04):\nCall as: \"/dev/csmi[0-9],N\" where N is the port behind the logical\n\tscsi controller \"\\\\\\\\.\\\\Scsi[0-9]:\".\n\tThe prefix \"/dev/\" is optional.\n\tThis is detected (with /dev/ prefix) by --scan-open.\nThe drives may be duplicated as pdX (with X and N being unrelated).\n\tThis usually happens when Intel RAID drivers are installed, even\n\tif no RAID configuration is present. For example, on a laptop with\n\tIntel chipset and Intel drivers installed, we have /dev/csmi0,0 and\n\tpd0 for the first HDD, and /dev/csmi0,1 for DVD (no pdX entry there).\n\tThis is what --scan-open looks like:\n------------------------------------------------------------------\n/dev/sda -d ata # /dev/sda, ATA device\n/dev/csmi0,0 -d ata # /dev/csmi0,0, ATA device\n/dev/csmi0,1 -d ata # /dev/csmi0,1, ATA device\n------------------------------------------------------------------\n\tWe filter out pdX devices using serial numbers (unless \"-q noserial\"\n\tis given to smartctl), and prefer csmi to pd (since csmi provides more features).\n\n</pre>\n*/\n\n\n\nstruct DriveLetterInfo {\n\tstd::set<int> physical_drives;  ///< N in pdN\n\tstd::string volume_name;  ///< Volume name\n};\n\n\nnamespace {\n\n\n/// Check which physical drives each drive letter (C, D, ...) spans across.\nstd::map<char, DriveLetterInfo> win32_get_drive_letter_map()\n{\n\tstd::map<char, DriveLetterInfo> drive_letter_map;\n\n#ifdef _WIN32\n\tstd::bitset<32> drives(GetLogicalDrives());\n\n\t// Check which drives are fixed\n\tstd::vector<char> good_drives;\n\tfor (char c = 'A'; c <= 'Z'; ++c) {\n\t\tif (drives[static_cast<unsigned char>(c) - 'A']) {  // drive is present\n\t\t\tdebug_out_dump(\"app\", \"Windows drive found: \" << c << \".\\n\");\n\t\t\tswitch (auto drive_type = GetDriveType((c + std::string(\":\\\\\")).c_str())) {\n\t\t\t\tcase DRIVE_FIXED:\n\t\t\t\t\tdebug_out_dump(\"app\", \"Windows reports the drive \" << c << \" as fixed.\\n\");\n\t\t\t\t\tgood_drives.push_back(c);\n\t\t\t\t\tbreak;\n\t\t\t\tcase DRIVE_REMOVABLE:\n\t\t\t\t\tdebug_out_dump(\"app\", \"Windows reports the drive \" << c << \" as removable.\\n\");\n\t\t\t\t\tgood_drives.push_back(c);\n\t\t\t\t\tbreak;\n\t\t\t\tcase DRIVE_CDROM:\n\t\t\t\t\tdebug_out_dump(\"app\", \"Windows reports the drive \" << c << \" as cdrom.\\n\");\n\t\t\t\t\tgood_drives.push_back(c);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tdebug_out_dump(\"app\", \"Windows reports the drive \" << c << \" as type \" << drive_type << \".\\n\");\n\t\t\t\t\t// don't add\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Try to open each drive, check its disk extents\n\tfor (const char drive : good_drives) {\n\t\tconst std::string drive_str = std::string(\"\\\\\\\\.\\\\\") + drive + \":\";\n\t\tHANDLE h = CreateFileA(\n\t\t\t\tdrive_str.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,\n\t\t\t\tOPEN_EXISTING, FILE_FLAG_NO_BUFFERING | FILE_FLAG_RANDOM_ACCESS, nullptr);\n\t\tif (h == INVALID_HANDLE_VALUE) {\n\t\t\tdebug_out_warn(\"app\", \"Windows drive \" << drive << \" cannot be opened.\\n\");\n\t\t\tcontinue;\n\t\t}\n\t\tDWORD bytesReturned = 0;\n\t\tVOLUME_DISK_EXTENTS vde;\n\t\tif (DeviceIoControl(\n\t\t\t\th, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,\n\t\t\t\tnullptr, 0, &vde, sizeof(vde), &bytesReturned, nullptr) == FALSE) {\n\t\t\tdebug_out_warn(\"app\", \"Windows drive \" << drive << \" is not mapped to any physical drives.\\n\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tstd::set<int> physical_drives;\n\t\tfor (std::size_t i = 0; i < vde.NumberOfDiskExtents; ++i) {\n\t\t\tphysical_drives.insert(int(vde.Extents[i].DiskNumber));\n\t\t\tdebug_out_dump(\"app\", \"Windows drive \" << drive << \" corresponds to physical drive \" << vde.Extents[i].DiskNumber << \".\\n\");\n\t\t}\n\n\t\tstd::string volume_name;\n\t\tstd::array<wchar_t, MAX_PATH+1> volume_name_w = {};\n\t\tDWORD dummy = 0;\n\t\tconst std::wstring drive_name = hz::win32_utf8_to_utf16(drive + std::string(\":\\\\\"));\n\t\tif (!drive_name.empty()\n\t\t\t\t&& GetVolumeInformationW(drive_name.c_str(),\n\t\t\t\t\tvolume_name_w.data(), MAX_PATH+1,\n\t\t\t\t\tnullptr, &dummy, &dummy, nullptr, 0) == TRUE) {\n\t\t\tvolume_name = hz::win32_utf16_to_utf8(volume_name_w);\n\t\t}\n\n\t\tDriveLetterInfo dli;\n\t\tdli.physical_drives = physical_drives;\n\t\tdli.volume_name = volume_name;\n\n\t\tdrive_letter_map[drive] = dli;\n\t}\n#endif\n\treturn drive_letter_map;\n}\n\n\n\n/// Run \"smartctl --scan-open\" and pick the devices which have\n/// a port parameter. We don't pick the others because the may\n/// conflict with pd* devices, and we like pd* better than sd*.\nhz::ExpectedVoid<StorageDetectorError> get_scan_open_multiport_devices(std::vector<StorageDevicePtr>& drives,\n\t\tconst CommandExecutorFactoryPtr& ex_factory,\n\t\tconst std::map<char, DriveLetterInfo>& drive_letter_map,\n\t\tstd::set<int>& equivalent_pds)\n{\n\tdebug_out_info(\"app\", \"Getting multi-port devices through smartctl --scan-open...\\n\");\n\n\tstd::shared_ptr<CommandExecutor> smartctl_ex = ex_factory->create_executor(CommandExecutorFactory::ExecutorType::Smartctl);\n\n\tauto smartctl_binary = get_smartctl_binary();\n\n\tif (smartctl_binary.empty()) {\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Smartctl binary is not set in config.\\n\");\n\t\treturn hz::Unexpected(StorageDetectorError::NoSmartctlBinary, _(\"Smartctl binary is not specified in configuration.\"));\n\t}\n\n\n\tauto smartctl_def_options_str = rconfig::get_data<std::string>(\"system/smartctl_options\");\n\tstd::vector<std::string> smartctl_options;\n\tif (!smartctl_def_options_str.empty()) {\n\t\ttry {\n\t\t\tsmartctl_options = Glib::shell_parse_argv(smartctl_def_options_str);\n\t\t}\n\t\tcatch(Glib::ShellError& e)\n\t\t{\n\t\t\treturn hz::Unexpected(StorageDetectorError::InvalidCommandLine, _(\"Invalid command line specified.\"));\n\t\t}\n\t}\n\tsmartctl_options.emplace_back(\"--scan-open\");\n\tsmartctl_ex->set_command(hz::fs_path_to_string(smartctl_binary), smartctl_options);\n\n\tif (const bool execute_status = smartctl_ex->execute(); !execute_status) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Smartctl binary did not execute cleanly.\\n\");\n\t\treturn hz::Unexpected(StorageDetectorError::SmartctlExecutionError, smartctl_ex->get_error_msg());\n\t}\n\n\t// any_to_unix is needed for windows\n\tconst std::string output = hz::string_trim_copy(hz::string_any_to_unix_copy(smartctl_ex->get_stdout_str()));\n\tif (output.empty()) {\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Smartctl returned an empty output.\\n\");\n\t\treturn hz::Unexpected(StorageDetectorError::EmptyCommandOutput, _(\"Smartctl returned an empty output.\"));\n\t}\n\n\tif (app_regex_partial_match(\"/UNRECOGNIZED OPTION/mi\", output)) {\n\t\t// Our requirements list smartctl with --scan-open support, so this should never happen.\n\t\t// Therefore, we don't translate it.\n\t\treturn hz::Unexpected(StorageDetectorError::UnsupportedCommandVersion,\n\t\t\t\t\"Unsupported smartctl version: Smartctl doesn't support --scan-open switch.\");\n\t}\n\n\n\tstd::vector<std::string> lines;\n\thz::string_split(output, '\\n', lines, true);\n\n// \t/dev/sda,0 -d ata (opened)\n// \t/dev/sda,1 -d ata (opened)\n// \t/dev/sda -d sat # /dev/sda [SAT], ATA device\n// /dev/sde,2 -d ata [ATA] (opened)\n\n\t// we only pick the ones with ports\n\tconst auto port_re = app_regex_re(\"/^(/dev/[a-z0-9]+),([0-9]+)[ \\\\t]+-d[ \\\\t]+([^ \\\\t\\\\n]+)/i\");\n\tconst auto dev_re = app_regex_re(\"/^/dev/sd([a-z])$/\");\n\n\tfor (const auto& line : lines) {\n\t\tstd::string dev, port_str, type;\n\t\tif (app_regex_partial_match(port_re, hz::string_trim_copy(line), {&dev, &port_str, &type})) {\n\t\t\tstd::string sd_letter;\n\t\t\tint drive_num = -1;\n\t\t\tif (app_regex_partial_match(dev_re, dev, &sd_letter)) {\n\t\t\t\t// don't use pd* devices equivalent to these sd* devices.\n\t\t\t\tdrive_num = sd_letter.at(0) - 'a';\n\t\t\t\tequivalent_pds.insert(drive_num);\n\t\t\t}\n\n\t\t\tconst std::string full_dev = dev + \",\" + port_str;\n\t\t\tauto drive = std::make_shared<StorageDevice>(full_dev, type);\n\n\t\t\tstd::map<char, std::string> letters_volnames;\n\t\t\tfor (const auto& iter : drive_letter_map) {\n\t\t\t\tif (iter.second.physical_drives.contains(drive_num)) {\n\t\t\t\t\tletters_volnames[iter.first] = iter.second.volume_name;\n\t\t\t\t}\n\t\t\t}\n\t\t\tdrive->set_drive_letters(letters_volnames);\n\n\t\t\tdrives.push_back(drive);\n\t\t}\n\t}\n\n\treturn {};\n}\n\n\n\n\n/// Find and execute areca cli with specified options, return its output through \\c output.\n/// \\return error message\ninline hz::ExpectedVoid<StorageDetectorError> execute_areca_cli(const CommandExecutorFactoryPtr& ex_factory, const std::string& cli_binary,\n\t\tconst std::vector<std::string>& command_options, std::string& output)\n{\n\tstd::shared_ptr<CommandExecutor> executor = ex_factory->create_executor(CommandExecutorFactory::ExecutorType::ArecaCli);\n\n\texecutor->set_command(cli_binary, command_options);\n\n\tif (!executor->execute() || !executor->get_error_msg().empty()) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Error while executing Areca cli binary.\\n\");\n\t}\n\n\t// any_to_unix is needed for windows\n\toutput = hz::string_trim_copy(hz::string_any_to_unix_copy(executor->get_stdout_str()));\n\tif (output.empty()) {\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Areca cli returned an empty output.\\n\");\n\t\treturn hz::Unexpected(StorageDetectorError::EmptyCommandOutput,\n\t\t\t\t\"Areca CLI returned an empty output.\");\n\t}\n\n\treturn {};\n}\n\n\n\n/** <pre>\nGet the drives on Areca controller using Areca cli tool.\nNote that the drives are inserted in the order they are detected.\n\nThere are 3 formats of \"disk info\" output:\n\n1. No expanders:\n------------------------------------------------------------\n  # Ch# ModelName                       Capacity  Usage\n===============================================================================\n  1  1  INTEL SSDSA2M160G2GC             160.0GB  System\n  2  2  INTEL SSDSA2M160G2GC             160.0GB  System\n  3  3  INTEL SSDSA2M160G2GC             160.0GB  System\n  4  4  INTEL SSDSA2M160G2GC             160.0GB  System\n  5  5  Hitachi HDS724040ALE640         4000.8GB  Storage\n  6  6  Hitachi HDS724040ALE640         4000.8GB  Storage\n  7  7  Hitachi HDS724040ALE640         4000.8GB  Storage\n  8  8  Hitachi HDS724040ALE640         4000.8GB  Storage\n  9  9  Hitachi HDS724040ALE640         4000.8GB  Storage\n 10 10  Hitachi HDS724040ALE640         4000.8GB  Storage\n 11 11  Hitachi HDS724040ALE640         4000.8GB  Backup\n 12 12  Hitachi HDS724040ALE640         4000.8GB  Backup\n===============================================================================\nGuiErrMsg<0x00>: Success.\n------------------------------------------------------------\n\n2. No expanders (this output comes from CLI documentation, possibly an old format):\n------------------------------------------------------------\n #   ModelName        Serial#          FirmRev     Capacity  State\n===============================================================================\n 1   ST3250620NS      5QE1CP8S         3.AEE        250.1GB  RaidSet Member(1)\n 2   ST3250620NS      5QE1CP8S         3.AEE        250.1GB  RaidSet Member(1)\n....(snip)....\n12   ST3250620NS      5QE1CP8S         3.AEE        250.1GB  RaidSet Member(1)\n===============================================================================\nGuiErrMsg<0x00>: Success.\n------------------------------------------------------------\n\n3. Expanders:\n------------------------------------------------------------\n  # Enc# Slot#   ModelName                        Capacity  Usage\n===============================================================================\n  1  01  Slot#1  N.A.                                0.0GB  N.A.\n  2  01  Slot#2  N.A.                                0.0GB  N.A.\n  3  01  Slot#3  N.A.                                0.0GB  N.A.\n  4  01  Slot#4  N.A.                                0.0GB  N.A.\n  5  01  Slot#5  N.A.                                0.0GB  N.A.\n  6  01  Slot#6  N.A.                                0.0GB  N.A.\n  7  01  Slot#7  N.A.                                0.0GB  N.A.\n  8  01  Slot#8  N.A.                                0.0GB  N.A.\n  9  02  SLOT 01 N.A.                                0.0GB  N.A.\n 10  02  SLOT 02 N.A.                                0.0GB  N.A.\n 11  02  SLOT 03 N.A.                                0.0GB  N.A.\n 12  02  SLOT 04 N.A.                                0.0GB  N.A.\n 13  02  SLOT 05 N.A.                                0.0GB  N.A.\n 14  02  SLOT 06 N.A.                                0.0GB  N.A.\n 15  02  SLOT 07 N.A.                                0.0GB  N.A.\n 16  02  SLOT 08 N.A.                                0.0GB  N.A.\n 17  02  SLOT 09 N.A.                                0.0GB  N.A.\n 18  02  SLOT 10 N.A.                                0.0GB  N.A.\n 19  02  SLOT 11 N.A.                                0.0GB  N.A.\n 20  02  SLOT 12 N.A.                                0.0GB  N.A.\n 21  02  SLOT 13 N.A.                                0.0GB  N.A.\n 22  02  SLOT 14 ST910021AS                        100.0GB  Free\n 23  02  SLOT 15 WDC WD3200BEVT-75A23T0            320.1GB  HotSpare[Global]\n 24  02  SLOT 16 N.A.                                0.0GB  N.A.\n 25  02  SLOT 17 Hitachi HDS724040ALE640          4000.8GB  Raid Set # 000\n 26  02  SLOT 18 ST31500341AS                     1500.3GB  Raid Set # 000\n 27  02  SLOT 19 ST3320620AS                       320.1GB  Raid Set # 000\n 28  02  SLOT 20 ST31500341AS                     1500.3GB  Raid Set # 000\n 29  02  SLOT 21 ST3500320AS                       500.1GB  Raid Set # 000\n 30  02  SLOT 22 Hitachi HDS724040ALE640          4000.8GB  Raid Set # 000\n 31  02  SLOT 23 Hitachi HDS724040ALE640          4000.8GB  Raid Set # 000\n 32  02  SLOT 24 Hitachi HDS724040ALE640          4000.8GB  Raid Set # 000\n 33  02  EXTP 01 N.A.                                0.0GB  N.A.\n 34  02  EXTP 02 N.A.                                0.0GB  N.A.\n 35  02  EXTP 03 N.A.                                0.0GB  N.A.\n 36  02  EXTP 04 N.A.                                0.0GB  N.A.\n===============================================================================\nGuiErrMsg<0x00>: Success.\n------------------------------------------------------------\n</pre> */\n[[nodiscard]] inline hz::ExpectedVoid<StorageDetectorError> areca_cli_get_drives(\n\t\tconst std::string& cli_binary, const std::string& dev, int controller,\n\t\tstd::vector<StorageDevicePtr>& drives, const CommandExecutorFactoryPtr& ex_factory)\n{\n\tdebug_out_info(\"app\", \"Getting available Areca drives (ports) for controller \" << controller << \" through Areca CLI...\\n\");\n\n\t// TODO Support controller number.\n\t// So far it seems the only way to pass the controller number to CLI is to use\n\t// the interactive mode.\n\n\tstd::string output;\n\tauto execute_status = execute_areca_cli(ex_factory, cli_binary, {\"disk\", \"info\"}, output);\n\tif (!execute_status) {\n\t\treturn execute_status;\n\t}\n\n\t// split to lines\n\tstd::vector<std::string> lines;\n\thz::string_split(output, '\\n', lines, true);\n\n\tenum class FormatType {\n\t\tUnknown,\n\t\tNoEnc1,\n\t\tNoEnc2,\n\t\tEnc\n\t};\n\n\tconst auto noenc1_header_re = app_regex_re(\"/^\\\\s*#\\\\s+Ch#/mi\");\n\tconst auto noenc2_header_re = app_regex_re(\"/^\\\\s*#\\\\s+ModelName/mi\");\n\tconst auto exp_header_re = app_regex_re(\"/^\\\\s*#\\\\s+Enc#/mi\");\n\n\tFormatType format_type = FormatType::Unknown;\n\tfor (const auto& line : lines) {\n\t\tif (app_regex_partial_match(noenc1_header_re, line)) {\n\t\t\tformat_type = FormatType::NoEnc1;\n\t\t\tbreak;\n\t\t}\n\t\tif (app_regex_partial_match(noenc2_header_re, line)) {\n\t\t\tformat_type = FormatType::NoEnc2;\n\t\t\tbreak;\n\t\t}\n\t\tif (app_regex_partial_match(exp_header_re, line)) {\n\t\t\tformat_type = FormatType::Enc;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (format_type == FormatType::Unknown) {\n\t\tdebug_out_warn(\"app\", \"Could not read Areca CLI output: No valid header found.\\n\");\n\t\treturn hz::Unexpected(StorageDetectorError::ParseError,\n\t\t\t\t_(\"Could not read Areca CLI output: No valid header found.\"));\n\t}\n\n\t// Note: These may not match the full model, but just the first part is sufficient for comparison with \"N.A.\".\n\tconst auto noexp1_port_re = app_regex_re(\"/^\\\\s*[0-9]+\\\\s+([0-9]+)\\\\s+([^\\\\s]+)/mi\");  // matches port, model.\n\tconst auto noexp2_port_re = app_regex_re(\"/^\\\\s*([0-9]+)\\\\s+([^\\\\s]+)/mi\");  // matches port, model.\n\tconst auto exp_port_re = app_regex_re(\"/^\\\\s*[0-9]+\\\\s+([0-9]+)\\\\s+(?:Slot#|SLOT\\\\s+)([0-9]+)\\\\s+([^\\\\s]+)/mi\");  // matches enclosure, port, model.\n\n\tconst bool has_enclosure = (format_type == FormatType::Enc);\n\tif (has_enclosure) {\n\t\tdebug_out_dump(\"app\", \"Areca controller seems to have enclosures.\\n\");\n\t} else {\n\t\tdebug_out_dump(\"app\", \"Areca controller doesn't have any enclosures.\\n\");\n\t}\n\n\tfor (const auto& line : lines) {\n\t\tstd::string port_str, model_str;\n\t\tif (has_enclosure) {\n\t\t\tstd::string enclosure_str;\n\t\t\tif (app_regex_partial_match(exp_port_re, hz::string_trim_copy(line), {&enclosure_str, &port_str, &model_str})) {\n\t\t\t\tif (model_str != \"N.A.\") {\n\t\t\t\t\tconst int port = hz::string_to_number_nolocale<int>(port_str);\n\t\t\t\t\tconst int enclosure = hz::string_to_number_nolocale<int>(enclosure_str);\n\t\t\t\t\tdrives.emplace_back(std::make_shared<StorageDevice>(dev,\n\t\t\t\t\t\t\t\"areca,\" + hz::number_to_string_nolocale(port) + \"/\" + hz::number_to_string_nolocale(enclosure)));\n\t\t\t\t\tdebug_out_info(\"app\", \"Added Areca drive \" << drives.back()->get_device_with_type() << \".\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t} else {  // no enclosures\n\t\t\tconst auto port_re = (format_type == FormatType::NoEnc1 ? noexp1_port_re : noexp2_port_re);\n\t\t\tif (app_regex_partial_match(port_re, hz::string_trim_copy(line), {&port_str, &model_str})) {\n\t\t\t\tif (model_str != \"N.A.\") {\n\t\t\t\t\tconst int port = hz::string_to_number_nolocale<int>(port_str);\n\t\t\t\t\tdrives.emplace_back(std::make_shared<StorageDevice>(dev, \"areca,\" + hz::number_to_string_nolocale(port)));\n\t\t\t\t\tdebug_out_info(\"app\", \"Added Areca drive \" << drives.back()->get_device_with_type() << \".\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {};\n}\n\n\n\n/** <pre>\nAreca Windows:\nCall as:\n\t- smartctl -i -d areca,N /dev/arcmsr0  (N is [1,24]).\n\t- smartctl -i -d areca,N/E /dev/arcmsr0  (N is [1,128], E is [1,8]).\nThe models that have \"ix\" (case-insensitive) in their model names\nhave the enclosures and require the N,E syntax.\nNote: Using N/E syntax with non-enclosure cards seems to work\nregardless of the value of E.\n\nDetection:\n\tCheck the registry keys for Areca management software and\n\tthe CLI utility. One of them has to be installed.\nCheck if CLI is installed. If it is, run it and parse the output. This should\n\tgive us the populated port list (non-enclosure), and the populated port\n\tand expander list (enclosure models).\n\tThere are two problems with CLI:\n\tIf no controller is present, it crashes.\n\t\tWe try the following first:\n\t\t\tsmartctl -i -d areca,1 /dev/arcmsr0\n\t\tIf it contains \"arcmsr0: No Areca controller found\" (Windows), or\n\t\t\t\"Smartctl open device: /dev/arcmsr0 [areca_disk#01_enc#01] failed: No such device\" (Linux),\n\t\t\tthen there's no controller there.\n\tI don't see how it reports different controllers (there's only one list).\n\t\tWe ignore this for now.\nIf CLI is not installed, do the brute-force way:\n\tFor arcmsr0, 1, ..., 8 (until we see \"No Areca controller found\"):\n\t\tScan -d areca,[1-24] /dev/arcmsrN\n\t\tIf at least one drive was found, it's a no-enclosure controller.\n\t\tIf no drives were found, it's probably an enclosure controller, try\n\t\t-d areca,[1-128]/[1-8] /dev/arcmsrN\n\t\t\tIt's 2-3 drives a second on an empty port, so some limits are set in config.\n</pre> */\ninline hz::ExpectedVoid<StorageDetectorError> detect_drives_win32_areca(std::vector<StorageDevicePtr>& drives, const CommandExecutorFactoryPtr& ex_factory)\n{\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Detecting drives behind Areca controller(s)...\\n\");\n\n\tconst int scan_controllers = rconfig::get_data<int>(\"system/win32_areca_scan_controllers\");\n\tif (scan_controllers == 0) {  // disabled\n\t\tdebug_out_info(\"app\", \"Areca controller scanning is disabled through config.\\n\");\n\t\treturn {};\n\t}\n\n\t// Check if Areca tools are installed\n\n\tstd::string cli_inst_path;\n#ifdef _WIN32\n\thz::win32_get_registry_value_string(HKEY_LOCAL_MACHINE,\n\t\t\t\"SOFTWARE\\\\Wow6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\CLI\", \"InstallPath\", cli_inst_path);\n\tif (cli_inst_path.empty()) {\n\t\thz::win32_get_registry_value_string(HKEY_LOCAL_MACHINE,\n\t\t\t\t\"SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\CLI\", \"InstallPath\", cli_inst_path);\n\t}\n#endif\n\tif (scan_controllers == 2) {  // auto\n\t\tstd::string http_inst_path;\n#ifdef _WIN32\n\t\thz::win32_get_registry_value_string(HKEY_LOCAL_MACHINE,\n\t\t\t\t\"SOFTWARE\\\\Wow6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\archttp\", \"InstallPath\", http_inst_path);\n\t\tif (http_inst_path.empty()) {\n\t\t\thz::win32_get_registry_value_string(HKEY_LOCAL_MACHINE,\n\t\t\t\t\t\"SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\archttp\", \"InstallPath\", http_inst_path);\n\t\t}\n#endif\n\t\tif (cli_inst_path.empty() && http_inst_path.empty()) {\n\t\t\tdebug_out_info(\"app\", \"No Areca software found. Install Areca CLI or set \\\"system/win32_areca_scan_controllers\\\" config key to 1 to force scanning.\\n\");\n\t\t\treturn {};\n\t\t}\n\t\tif (http_inst_path.empty()) {\n\t\t\tdebug_out_dump(\"app\", \"Areca HTTP installation not found.\\n\");\n\t\t} else {\n\t\t\tdebug_out_dump(\"app\", \"Areca HTTP installation found at: \\\"\" << http_inst_path << \"\\\".\\n\");\n\t\t}\n\t}\n\tif (cli_inst_path.empty()) {\n\t\tdebug_out_dump(\"app\", \"Areca CLI installation not found.\\n\");\n\t} else {\n\t\tdebug_out_dump(\"app\", \"Areca CLI installation found at: \\\"\" << cli_inst_path << \"\\\".\\n\");\n\t}\n\n\tconst bool cli_found = !cli_inst_path.empty();\n\n\tint use_cli = rconfig::get_data<int>(\"system/win32_areca_use_cli\");\n\tbool scan_detect = (use_cli != 1);  // Whether to detect using sequential port scanning. Only do that if CLI is not forced.\n\tif (use_cli == 2) {  // auto\n\t\tuse_cli = cli_found ? 1 : 0;\n\t}\n\n\thz::fs::path cli_binary;\n\tif (use_cli == 1) {\n\t\tcli_binary = hz::fs_path_from_string(rconfig::get_data<std::string>(\"system/areca_cli_binary\"));\n\t\tif (!cli_binary.is_absolute() && !cli_inst_path.empty()) {\n\t\t\tcli_binary = hz::fs_path_from_string(cli_inst_path) / cli_binary;\n\t\t}\n\t\tif (cli_binary.is_absolute() && !hz::fs::exists(cli_binary)) {\n\t\t\tuse_cli = 0;\n\t\t\tdebug_out_dump(\"app\", \"Areca CLI binary \\\"\" << cli_binary.string() << \"\\\" not found.\\n\");\n\t\t}\n\t}\n\n\tstd::shared_ptr<CommandExecutor> smartctl_ex = ex_factory->create_executor(CommandExecutorFactory::ExecutorType::Smartctl);\n\n\t// --- CLI mode\n\n\t// Since CLI may segfault if there are no drives, test the controller presence first.\n\t// It doesn't matter if we use areca,N or areca,N/E - we will still get a different\n\t// error if there's no controller.\n\tif (use_cli != 0) {\n\t\tdebug_out_dump(\"app\", \"Testing Areca controller presence using smartctl...\\n\");\n\n\t\tauto drive = std::make_shared<StorageDevice>(\"/dev/arcmsr0\", \"areca,1\");\n\t\t[[maybe_unused]] auto drive_status = drive->fetch_basic_data_and_parse(smartctl_ex);\n\t\tconst std::string output = drive->get_basic_output();\n\t\tif (app_regex_partial_match(\"/No Areca controller found/mi\", output)\n\t\t\t\t|| app_regex_partial_match(\"/Smartctl open device: .* failed: No such device/mi\", output) ) {\n\t\t\tuse_cli = 0;\n\t\t\tscan_detect = false;\n\t\t\tdebug_out_dump(\"app\", \"Areca controller not found.\\n\");\n\t\t}\n\t}\n\n\tif (use_cli != 0) {\n\t\tdebug_out_info(\"app\", \"Scanning Areca drives using CLI...\\n\");\n\t\tconst int cli_max_controllers = 1;  // TODO controller # with CLI.\n\t\tfor (int controller_no = 0; controller_no < cli_max_controllers; ++controller_no) {\n\t\t\tauto execute_status = areca_cli_get_drives(cli_binary.string(),\n\t\t\t\t\t\"/dev/arcmsr\" + hz::number_to_string_nolocale(controller_no), controller_no, drives, ex_factory);\n\t\t\t// If we get an error on controller 0, fall back to no-cli detection.\n\t\t\tif (!execute_status && controller_no == 0) {\n\t\t\t\tuse_cli = 0;\n\t\t\t\tdebug_out_warn(\"app\", \"Areca scan using CLI failed.\\n\");\n\t\t\t\tif (scan_detect) {\n\t\t\t\t\tdebug_out_dump(\"app\", \"Trying manual Areca scan because of a CLI error.\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// If CLI scanning failed (and it was not forced), or if there was no CLI, scan manually.\n\tif (use_cli == 0 && scan_detect) {\n\t\tdebug_out_info(\"app\", \"Manually scanning Areca controllers and ports...\\n\");\n\n\t\tconst int max_controllers = rconfig::get_data<int>(\"system/win32_areca_max_controllers\");\n\t\tint max_noenc_ports = rconfig::get_data<int>(\"system/win32_areca_neonc_max_scan_port\");\n\t\tmax_noenc_ports = std::max(1, std::min(24, max_noenc_ports));  // 1-24 sanity check\n\t\tint max_enc_ports = rconfig::get_data<int>(\"system/win32_areca_enc_max_scan_port\");\n\t\tmax_enc_ports = std::max(1, std::min(128, max_enc_ports));  // 1-128 sanity check\n\t\tint max_enclosures = rconfig::get_data<int>(\"system/win32_areca_enc_max_enclosure\");\n\t\tmax_enclosures = std::max(1, std::min(8, max_enclosures));  // 1-8 sanity check\n\n\t\tfor (int controller_no = 0; controller_no < max_controllers; ++controller_no) {\n\t\t\tconst std::string dev = std::string(\"/dev/arcmsr\") + hz::number_to_string_nolocale(controller_no);\n\n\t\t\t// First, scan using the areca,N format.\n\t\t\tdebug_out_dump(\"app\", \"Starting brute-force port scan (no-enclosure) on 1-\" << max_noenc_ports << \" ports, device \\\"\" << dev\n\t\t\t\t\t<< \"\\\". Change the maximum by setting \\\"system/win32_areca_neonc_max_scan_port\\\" config key.\\n\");\n\n\t\t\tconst std::size_t old_drive_count = drives.size();\n\t\t\tstd::string last_output;\n\t\t\tauto scan_status = smartctl_scan_drives_sequentially(dev, \"areca,%d\", 1, max_noenc_ports, drives, ex_factory, last_output);\n\t\t\t// If the scan stopped because of no controller, stop it all.\n\t\t\tif (!scan_status && (app_regex_partial_match(\"/No Areca controller found/mi\", last_output)\n\t\t\t\t\t|| app_regex_partial_match(\"/Smartctl open device: .* failed: No such device/mi\", last_output)) ) {\n\t\t\t\tdebug_out_dump(\"app\", \"Areca controller \" << controller_no << \" not present, stopping sequential scan.\\n\");\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// If no drives were added (but the controller is present), it may be due to\n\t\t\t// expander syntax requirement. Try that.\n\t\t\tif (drives.size() == old_drive_count) {\n\t\t\t\tfor (int enclosure_no = 1; enclosure_no < max_enclosures; ++enclosure_no) {\n\t\t\t\t\tdebug_out_dump(\"app\", \"Starting brute-force port scan (enclosure #\" << enclosure_no << \") on 1-\" << max_enc_ports << \" ports, device \\\"\" << dev\n\t\t\t\t\t\t\t<< \"\\\". Change the maximums by setting \\\"system/win32_areca_onc_max_scan_port\\\" and \\\"system/win32_areca_enc_max_enclosure\\\" config keys.\\n\");\n\t\t\t\t\t// FIXME Not sure whether we should ignore this error message\n\t\t\t\t\t[[maybe_unused]] auto encl_status = smartctl_scan_drives_sequentially(dev, \"areca,%d/\" + hz::number_to_string_nolocale(enclosure_no), 1, max_enc_ports, drives, ex_factory, last_output);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdebug_out_dump(\"app\", \"Brute-force port scan finished.\\n\");\n\t\t}\n\t}\n\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Finished scanning Areca controllers.\\n\");\n\n\treturn {};\n}\n\n\n\n}\n\n\n\n\n// smartctl accepts various variants, the most straight being pdN,\n// (or /dev/pdN, /dev/ being optional) where N comes from\n// \"\\\\.\\PhysicalDriveN\" (winnt only).\n// http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx\nhz::ExpectedVoid<StorageDetectorError> detect_drives_win32(std::vector<StorageDevicePtr>& drives,\n\t\tconst CommandExecutorFactoryPtr& ex_factory)\n{\n\tstd::vector<std::string> error_msgs;\n\n\t// Construct drive letter map\n\tdebug_out_info(\"app\", \"Checking which drive corresponds to which \\\\\\\\.\\\\PhysicalDriveN device...\\n\");\n\tconst std::map<char, DriveLetterInfo> drive_letter_map = win32_get_drive_letter_map();\n\n\n\tstd::shared_ptr<CommandExecutor> smartctl_ex = ex_factory->create_executor(CommandExecutorFactory::ExecutorType::Smartctl);\n\n\t// Fetch multiport devices using --scan-open.\n\t// Note that this may return duplicates (e.g. /dev/sda and /dev/csmi0,0)\n\n\tstd::set<int> used_pds;\n\tauto multiport_status = get_scan_open_multiport_devices(drives, ex_factory, drive_letter_map, used_pds);\n\tif (!multiport_status) {\n\t\terror_msgs.push_back(multiport_status.error().message());\n\t}\n\n\tconst bool multiport_found = !drives.empty();\n\tbool areca_open_found = false;  // whether areca devices were found at --scan-open time.\n\n\t// Find out their serial numbers and whether there are Arecas there.\n\tstd::map<std::string, StorageDevicePtr> serials;\n\tfor (auto& drive : drives) {\n\t\tconst auto local_status = drive->fetch_basic_data_and_parse(smartctl_ex);\n\t\tif (!local_status) {\n\t\t\tdebug_out_info(\"app\", \"Smartctl returned with an error: \" << local_status.error().message() << \"\\n\");\n\t\t\t// Don't exit, just report it.\n\t\t}\n\t\tif (!drive->get_serial_number().empty()) {\n\t\t\t// add model as well, who knows, there may be duplicates across vendors\n\t\t\tconst std::string drive_serial_id = drive->get_model_name() + \"_\" + drive->get_serial_number();\n\t\t\tserials[drive_serial_id] = drive;\n\t\t}\n\n\t\t// See if there are any areca devices. This is not implemented yet by smartctl (as of 6.0),\n\t\t// but if it ever is, we can skip our own detection below.\n\t\tconst std::string type_arg = drive->get_type_argument();\n\t\tif (type_arg.find(\"areca\") != std::string::npos) {\n\t\t\tareca_open_found = true;\n\t\t}\n\t}\n\n\n\t// Scan PhysicalDriveN entries\n\n\tdebug_out_info(\"app\", \"Starting sequential scan of \\\\\\\\.\\\\PhysicalDriveN devices...\\n\");\n\n\t[[maybe_unused]] int num_failed = 0;\n\tconst int max_drives = 255;  // arbitrary\n\tfor (int drive_num = 0; drive_num < max_drives; ++drive_num) {\n\n\t\t// If the drive was already encountered in --scan-open (with a port number), skip it.\n\t\tif (used_pds.contains(drive_num)) {\n\t\t\tdebug_out_dump(\"app\", \"pd\" << drive_num << \" already encountered in --scan-open output (as sd*), skipping.\\n\");\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst std::string phys_name = hz::string_sprintf(\"\\\\\\\\.\\\\PhysicalDrive%d\", drive_num);\n\n#ifdef _WIN32\n\t\t// If the drive is openable, then it's there. Yes, CreateFile() is open, not create.\n\t\t// NOTE: Administrative privileges are required to open it.\n\t\t// We don't use any long/unopenable files here, so use the ANSI version.\n\t\tHANDLE h = CreateFileA(phys_name.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE,\n\t\t\t\tnullptr, OPEN_EXISTING, 0, nullptr);\n\n\t\t// The numbers are usually consecutive, but sometimes there are holes when\n\t\t// removable devices are removed. Try 3 extra drives just in case.\n\t\tif (h == INVALID_HANDLE_VALUE) {\n\t\t\t++num_failed;\n\t\t\tdebug_out_dump(\"app\", \"Could not open \\\"\" << phys_name << \"\\\".\\n\");\n\t\t\tif (num_failed >= 3) {\n\t\t\t\tdebug_out_dump(\"app\", \"Stopping sequential scan.\\n\");\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tCloseHandle(h);\n#endif\n\n\t\tdebug_out_dump(\"app\", \"Successfully opened \\\"\" << phys_name << \"\\\".\\n\");\n\n\t\tauto drive = std::make_shared<StorageDevice>(hz::string_sprintf(\"pd%d\", drive_num));\n\n\t\tstd::map<char, std::string> letters_volnames;\n\t\tfor (const auto& iter : drive_letter_map) {\n\t\t\tif (iter.second.physical_drives.contains(drive_num)) {\n\t\t\t\tletters_volnames[iter.first] = iter.second.volume_name;\n\t\t\t}\n\t\t}\n\t\tdrive->set_drive_letters(letters_volnames);\n\t\tdebug_out_dump(\"app\", \"Drive letters for: \" << drive->get_device() << \": \" << drive->format_drive_letters(true) << \".\\n\");\n\n\t\t// Fetch the drive data\n\t\tauto local_status = drive->fetch_basic_data_and_parse(smartctl_ex);\n\t\tif (!local_status) {\n\t\t\tdebug_out_info(\"app\", \"Smartctl returned with an error: \" << local_status.error().message() << \"\\n\");\n\t\t\t// Don't exit, just report it.\n\t\t}\n\n\t\t// Sometimes, a single physical drive may be accessible from both \"/.//PhysicalDriveN\"\n\t\t// and \"/.//Scsi2\" (e.g. pd0 and csmi2,1). Prefer the port-having ones (which is from --scan-open),\n\t\t// they contain more information.\n\t\t// The only way to detect these duplicates is to compare them using serial numbers.\n\t\tif (!serials.empty()) {\n\t\t\tconst std::string drive_serial_id = drive->get_model_name() + \"_\" + drive->get_serial_number();\n\t\t\t// A serial may be empty if \"-q noserial\" was given to smartctl.\n\t\t\tif (!drive->get_serial_number().empty() && serials.contains(drive_serial_id)) {\n\t\t\t\tdebug_out_info(\"app\", \"Skipping drive due to duplicate S/N: model: \\\"\" << drive->get_model_name()\n\t\t\t\t\t\t<< \"\\\", S/N: \\\"\" << drive->get_serial_number() << \"\\\".\\n\");\n\t\t\t\t// Copy the drive letters over to previously detected one (since we can't detect drive letters there).\n\t\t\t\tserials[drive_serial_id]->set_drive_letters(drive->get_drive_letters());\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tdebug_out_info(\"app\", \"Added drive \" << drive->get_device_with_type() << \".\\n\");\n\t\tdrives.push_back(drive);\n\t}\n\n\n\t// If smartctl --scan-open returns no \"sd*,port\"-style devices,\n\t// check if 3dm2 is installed and execute \"tw_cli show\" to get\n\t// the controllers, then use the \"tw_cli/cN/pN\"-style drives with smartctl. This may\n\t// happen with older smartctl which doesn't support --scan-open, or with\n\t// drivers that don't allow proper SMART commands.\n\tif (!multiport_found) {\n\t\tdebug_out_info(\"app\", \"Checking for additional 3ware devices...\\n\");\n\t\tstd::string inst_path;\n#ifdef _WIN32\n\t\thz::win32_get_registry_value_string(HKEY_USERS, \".DEFAULT\\\\Software\\\\3ware\\\\3DM2\", \"InstallPath\", inst_path);\n#endif\n\t\tif (!inst_path.empty()) {\n\t\t\tdebug_out_dump(\"app\", \"3ware 3DM2 found at\\\"\" << inst_path << \"\\\".\\n\");\n\t\t\tstd::vector<int> controllers;\n\t\t\tauto tw_status = tw_cli_get_controllers(ex_factory, controllers);\n\t\t\t// ignore the error message above, it's of no use.\n\t\t\tfor (const int controller : controllers) {\n\t\t\t\t// don't specify device, it's ignored in tw_cli mode\n\t\t\t\t[[maybe_unused]] auto tw_drive_status = tw_cli_get_drives(\"\", controller, drives, ex_factory, true);\n\t\t\t}\n\t\t} else {\n\t\t\tdebug_out_info(\"app\", \"3ware 3DM2 not installed.\\n\");\n\t\t}\n\t}\n\n\n\tif (!areca_open_found) {\n\t\tauto areca_status = detect_drives_win32_areca(drives, ex_factory);\n\t\tif (!areca_status) {\n\t\t\terror_msgs.push_back(areca_status.error().message());\n\t\t}\n\t}\n\n\tif (!error_msgs.empty()) {\n\t\treturn hz::Unexpected(StorageDetectorError::GeneralDetectionErrors, hz::string_join(error_msgs, \"\\n\"));\n\t}\n\n\treturn {};\n}\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_detector_win32.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef STORAGE_DETECTOR_WIN32_H\n#define STORAGE_DETECTOR_WIN32_H\n\n#include \"build_config.h\"\n\n#include <string>\n#include <vector>\n\n#include \"command_executor_factory.h\"\n#include \"storage_device.h\"\n#include \"storage_detector.h\"\n\n\n/// Detect drives in Windows\n[[nodiscard]] hz::ExpectedVoid<StorageDetectorError> detect_drives_win32(std::vector<StorageDevicePtr>& drives,\n\t\tconst CommandExecutorFactoryPtr& ex_factory);\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_device.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include \"storage_device.h\"\n\n#include <glibmm.h>\n#include <cctype>\n#include <cstdint>\n#include <memory>\n#include <unordered_map>\n#include <utility>\n#include <string>\n#include <vector>\n\n#include \"fmt/format.h\"\n#include \"rconfig/rconfig.h\"\n#include \"hz/string_algo.h\"  // string_trim_copy, string_any_to_unix_copy\n#include \"hz/fs.h\"\n#include \"hz/format_unit.h\"  // hz::format_date\n#include \"hz/error_container.h\"\n\n#include \"app_regex.h\"\n#include \"smartctl_parser_types.h\"\n#include \"storage_device_detected_type.h\"\n#include \"storage_settings.h\"\n#include \"smartctl_executor.h\"\n#include \"smartctl_version_parser.h\"\n#include \"storage_property_descr.h\"\n#include \"build_config.h\"\n#include \"smartctl_parser.h\"\n//#include \"smartctl_text_parser_helper.h\"\n//#include \"ata_storage_property_descr.h\"\n\n\n\nstd::string StorageDevice::get_status_displayable_name(SmartStatus status)\n{\n\tstatic const std::unordered_map<SmartStatus, std::string> m {\n\t\t\t{SmartStatus::Enabled,     C_(\"status\", \"Enabled\")},\n\t\t\t{SmartStatus::Disabled,    C_(\"status\", \"Disabled\")},\n\t\t\t{SmartStatus::Unsupported, C_(\"status\", \"Unsupported\")},\n\t};\n\tif (auto iter = m.find(status); iter != m.end()) {\n\t\treturn iter->second;\n\t}\n\treturn \"[internal_error]\";\n}\n\n\n\nStorageDevice::StorageDevice(std::string dev_or_vfile, bool is_virtual)\n\t\t: is_virtual_(is_virtual)\n{\n\tif (is_virtual_) {\n\t\tvirtual_file_ = hz::fs_path_from_string(dev_or_vfile);\n\t} else {\n\t\tdevice_ = std::move(dev_or_vfile);\n\t}\n}\n\n\n\nStorageDevice::StorageDevice(std::string dev, std::string type_arg)\n\t\t: device_(std::move(dev)), type_arg_(std::move(type_arg))\n{ }\n\n\n\nvoid StorageDevice::clear_outputs()\n{\n\tbasic_output_.clear();\n\tfull_output_.clear();\n}\n\n\n\nvoid StorageDevice::clear_parse_results()\n{\n\tparse_status_ = ParseStatus::None;\n//\ttest_is_active_ = false;  // not sure\n\n\tproperty_repository_.clear();\n\n\tsmart_supported_.reset();\n\tsmart_enabled_.reset();\n\tmodel_name_.reset();\n\tfamily_name_.reset();\n\tsize_.reset();\n\thealth_property_.reset();\n}\n\n\n\nhz::ExpectedVoid<StorageDeviceError> StorageDevice::fetch_basic_data_and_parse(\n\t\tconst std::shared_ptr<CommandExecutor>& smartctl_ex)\n{\n\tif (this->test_is_active_) {\n\t\treturn hz::Unexpected(StorageDeviceError::TestRunning, _(\"A test is currently being performed on this drive.\"));\n\t}\n\n\t// Clear everything fetched before, including outputs\n\tthis->clear_parse_results();\n\tthis->clear_outputs();\n\n\t// We don't use \"--all\" - it may cause really screwed up the output (tests, etc.).\n\t// This looks just like \"--info\" only on non-smart devices.\n\tconst auto default_parser_type = SmartctlVersionParser::get_default_format(SmartctlParserType::Basic);\n\tstd::vector<std::string> command_options = {\"--info\", \"--health\", \"--capabilities\"};\n\tif (default_parser_type == SmartctlOutputFormat::Json) {\n\t\t// --json flags: o means include original output (just in case).\n\t\tcommand_options.push_back(\"--json=o\");\n\t}\n\n\tauto execute_status = execute_device_smartctl(command_options, smartctl_ex, this->basic_output_, true);  // set type to invalid if needed\n\n\t// Smartctl 5.39 cvs/svn version defaults to usb type on at least linux and windows.\n\t// This means that the old SCSI identify command isn't executed by default,\n\t// and there is no information about the device manufacturer/etc. in the output.\n\t// We detect this and set the device type to scsi to at least have _some_ info.\n\n\t// Note: This match works even with JSON (the text output is included in --json=o).\n\tif ((execute_status || execute_status.error().data() == StorageDeviceError::ExecutionError)\n\t\t\t&& get_detected_type() == StorageDeviceDetectedType::NeedsExplicitType\n\t\t\t&& get_type_argument().empty() ) {\n\t\tdebug_out_info(\"app\", \"The device seems to be of different type than auto-detected, trying again with scsi.\\n\");\n\t\tthis->set_type_argument(\"scsi\");\n\t\tthis->set_detected_type(StorageDeviceDetectedType::BasicScsi);\n\t\treturn this->fetch_basic_data_and_parse(smartctl_ex);  // try again with scsi\n\t}\n\n\t// Since the type error leads to \"command line didn't parse\" error here,\n\t// we do this after the scsi stuff.\n//\tif (!execute_status.has_value()) {\n\t\t// Still try to parse something. For some reason, running smartctl on usb flash drive\n\t\t// under winxp returns \"command line didn't parse\", while actually printing its name.\n//\t\tthis->parse_basic_data(false, true);\n//\t\treturn execute_status;\n//\t}\n\n\t// Set some properties too - they are needed for e.g. SMART on/off support, etc.\n\treturn this->parse_basic_data();\n}\n\n\n\nhz::ExpectedVoid<StorageDeviceError> StorageDevice::parse_basic_data()\n{\n\t// Clear everything fetched before, except outputs and type\n\tthis->clear_parse_results();\n\n\t// Detect the output format.\n\tauto detect_status = SmartctlParser::detect_output_format(this->get_basic_output());\n\tauto output_format = SmartctlOutputFormat::Text;\n\tif (detect_status.has_value()) {\n\t\toutput_format = detect_status.value();\n\t} else {\n\t\tdebug_out_warn(\"app\", \"Cannot detect smartctl output format. Assuming Text.\\n\");\n\t}\n\n\t// Parse using Basic parser. This supports all drive types.\n\tauto basic_parser = SmartctlParser::create(SmartctlParserType::Basic, output_format);\n\tDBG_ASSERT_RETURN(basic_parser, hz::Unexpected(StorageDeviceError::ParseError, _(\"Cannot create parser\")));\n\n\t// This also fills the drive type properties.\n\tauto parse_status = basic_parser->parse(this->get_basic_output());\n\tif (!parse_status) {\n\t\tstd::string message = parse_status.error().message();\n\t\treturn hz::Unexpected(StorageDeviceError::ParseError,\n\t\t\t\tfmt::format(fmt::runtime(_(\"Cannot parse smartctl output: {}\")), message));\n\t}\n\n\t// See if we can narrow down the drive type from what was detected\n\t// by StorageDetector and properties set by Basic parser.\n\tauto basic_property_repo = basic_parser->get_property_repository();\n\n\t// Make detected type more exact.\n\tdetect_drive_type_from_properties(basic_property_repo);\n\n\t// Add property descriptions and set to the drive.\n\tthis->set_property_repository(StoragePropertyProcessor::process_properties(basic_property_repo, get_detected_type()));\n\n\tdebug_out_dump(\"app\", \"Drive \" << get_device_with_type() << \" set to be \"\n\t\t\t<< StorageDeviceDetectedTypeExt::get_displayable_name(get_detected_type()) << \" device.\\n\");\n\n\tread_common_properties();  // sets model_name_, etc.\n\n\t// A model field (and its aliases) is a good indication whether there was any data or not\n\tset_parse_status(model_name_.has_value() ? ParseStatus::Basic : ParseStatus::None);\n\n\t// Try to parse the properties. ignore its errors - we already got what we came for.\n\t// Note that this may try to parse data the second time (it may already have\n\t// been parsed by parse_data() which failed at it).\n//\tif (do_set_properties) {\n//\t\tauto parser = SmartctlParser::create(SmartctlParserType::Ata, output_format);\n//\t\tDBG_ASSERT_RETURN(parser, hz::Unexpected(StorageDeviceError::ParseError, _(\"Cannot create parser\")));\n//\n//\t\tif (parser->parse(this->basic_output_)) {  // try to parse it\n//\t\t\tthis->set_property_repository(\n//\t\t\t\t\tStoragePropertyProcessor::process_properties(parser->get_property_repository(), disk_type));  // copy to our drive, overwriting old data\n//\t\t}\n//\t}\n\n\tsignal_changed().emit(this);  // notify listeners\n\n\treturn {};\n}\n\n\n\nhz::ExpectedVoid<StorageDeviceError> StorageDevice::fetch_full_data_and_parse(\n\t\tconst std::shared_ptr<CommandExecutor>& smartctl_ex)\n{\n\tif (this->test_is_active_) {\n\t\treturn hz::Unexpected(StorageDeviceError::TestRunning, _(\"A test is currently being performed on this drive.\"));\n\t}\n\n\t// Drive type must be already set at this point, using fetch_basic_data_and_parse().\n\tDBG_ASSERT(this->get_detected_type() != StorageDeviceDetectedType::Unknown);\n\n\t// Clear everything fetched before, including outputs\n\tthis->clear_parse_results();\n\tthis->clear_outputs();\n\n\t// Execute smartctl.\n\n\t// Instead of -x, we use all the individual options -x encompasses, so that\n\t// an addition to default -x output won't affect us.\n\tstd::vector<std::string> command_options;\n\n\t// Type was detected by Basic parser\n\tswitch (this->get_detected_type()) {\n\t\tcase StorageDeviceDetectedType::Unknown:\n\t\tcase StorageDeviceDetectedType::NeedsExplicitType:\n\t\t\tDBG_ASSERT(0);\n\t\t\tbreak;\n\t\tcase StorageDeviceDetectedType::AtaAny:\n\t\tcase StorageDeviceDetectedType::AtaHdd:\n\t\tcase StorageDeviceDetectedType::AtaSsd:\n\t\t\tcommand_options = {\n\t\t\t\t\t\"--health\",\n\t\t\t\t\t\"--info\",\n\t\t\t\t\t\"--get=all\",\n\t\t\t\t\t\"--capabilities\",\n\t\t\t\t\t\"--attributes\",\n\t\t\t\t\t\"--format=brief\",\n\t\t\t\t\t\"--log=xerror,50,error\",\n\t\t\t\t\t\"--log=xselftest,50,selftest\",\n\t\t\t\t\t\"--log=selective\",\n\t\t\t\t\t\"--log=directory\",\n\t\t\t\t\t\"--log=scttemp\",\n\t\t\t\t\t\"--log=scterc\",\n\t\t\t\t\t\"--log=devstat\",\n\t\t\t\t\t\"--log=sataphy\",\n\t\t\t};\n\t\t\tbreak;\n\t\tcase StorageDeviceDetectedType::Nvme:\n\t\t\t// We don't care if something is added to json output.\n\t\t\t// Same as: --health --info --capabilities --attributes --log=error --log=selftest\n\t\t\tcommand_options = {\"--xall\"};\n\t\t\tbreak;\n\t\tcase StorageDeviceDetectedType::BasicScsi:\n\t\tcase StorageDeviceDetectedType::CdDvd:\n\t\tcase StorageDeviceDetectedType::UnsupportedRaid:\n\t\t\t// SCSI equivalent of -x:\n\t\t\t// command_options = \"--health --info --attributes --log=error --log=selftest --log=background --log=sasphy\";\n\t\t\tcommand_options = {\"--xall\"};\n\t\t\tbreak;\n\t}\n\n\tauto parser_type = SmartctlVersionParser::get_default_parser_type(this->get_detected_type());\n\tauto parser_format = SmartctlVersionParser::get_default_format(parser_type);\n\tif (parser_format == SmartctlOutputFormat::Json) {\n\t\t// --json flags: o means include original output (just in case).\n\t\tcommand_options.push_back(\"--json=o\");\n\t}\n\n\tstd::string output;\n\tauto execute_status = execute_device_smartctl(command_options, smartctl_ex, output);\n\n//\tif (this->get_type_argument() == \"scsi\") {  // not sure about correctness... FIXME probably fails with RAID/scsi\n//\t\tconst auto default_parser_type = SmartctlVersionParser::get_default_format(SmartctlParserType::Basic);\n//\t\t// This doesn't do much yet, but just in case...\n//\t\t// SCSI equivalent of -x:\n//\t\tstd::string command_options = \"--health --info --attributes --log=error --log=selftest --log=background --log=sasphy\";\n//\t\tif (default_parser_type == SmartctlOutputFormat::Json) {\n//\t\t\t// --json flags: o means include original output (just in case).\n//\t\t\tcommand_options += \" --json=o\";\n//\t\t}\n//\n//\t\texecute_status = execute_device_smartctl(command_options, smartctl_ex, output);\n//\n//\t} else {\n//\t\tconst auto default_parser_type = SmartctlVersionParser::get_default_format(SmartctlParserType::Ata);\n//\t\t// ATA equivalent of -x.\n//\t\tstd::string command_options = \"--health --info --get=all --capabilities --attributes --format=brief --log=xerror,50,error --log=xselftest,50,selftest --log=selective --log=directory --log=scttemp --log=scterc --log=devstat --log=sataphy\";\n//\t\tif (default_parser_type == SmartctlOutputFormat::Json) {\n//\t\t\t// --json flags: o means include original output (just in case).\n//\t\t\tcommand_options += \" --json=o\";\n//\t\t}\n//\n//\t\texecute_status = execute_device_smartctl(command_options, smartctl_ex, output, true);  // set type to invalid if needed\n//\t}\n\t// See notes above (in fetch_basic_data_and_parse()).\n\t// No need to do this: if the basic data was fetched, the type is already set.\n//\tif ((execute_status || execute_status.error().data() == StorageDeviceError::ExecutionError)\n//\t\t\t&& get_detected_type() == DetectedType::NeedsExplicitType && get_type_argument().empty()) {\n//\t\tdebug_out_info(\"app\", \"The device seems to be of different type than auto-detected, trying again with scsi.\\n\");\n//\t\tthis->set_type_argument(\"scsi\");\n//\t\treturn this->fetch_full_data_and_parse(smartctl_ex);  // try again with scsi\n//\t}\n\t// Since the type error leads to \"command line didn't parse\" error here,\n\t// we do this after the scsi stuff.\n\n\n\tif (!execute_status)\n\t\treturn execute_status;\n\n\tthis->full_output_ = output;\n\treturn this->parse_full_data(parser_type, parser_format);\n}\n\n\n\nhz::ExpectedVoid<StorageDeviceError> StorageDevice::parse_full_data(SmartctlParserType parser_type, SmartctlOutputFormat format)\n{\n\t// Clear everything fetched before, except outputs and disk type\n\tclear_parse_results();\n\n\tauto parser = SmartctlParser::create(parser_type, format);\n\tDBG_ASSERT_RETURN(parser, hz::Unexpected(StorageDeviceError::ParseError, _(\"Cannot create parser\")));\n\n\tconst auto parse_status = parser->parse(this->full_output_);\n\tif (parse_status.has_value()) {\n\t\tset_parse_status(parser_type == SmartctlParserType::Basic ? ParseStatus::Basic : ParseStatus::Full);\n\n\t\t// Detect drive type based on parsed properties\n\t\tdetect_drive_type_from_properties(parser->get_property_repository());\n\n\t\t// Set the full properties, overwriting old data.\n\t\tset_property_repository(StoragePropertyProcessor::process_properties(parser->get_property_repository(), get_detected_type()));\n\n\t\t// Read common properties from the repository.\n\t\tread_common_properties();\n\n\t\tsignal_changed().emit(this);  // notify listeners\n\n\t\treturn {};\n\t}\n\n\tstd::string message = parse_status.error().message();\n\treturn hz::Unexpected(StorageDeviceError::ParseError,\n\t\t\tfmt::format(fmt::runtime(_(\"Cannot parse smartctl output: {}\")), message));\n}\n\n\n\nhz::ExpectedVoid<StorageDeviceError> StorageDevice::parse_any_data_for_virtual()\n{\n\t// Clear everything fetched before, except outputs and disk type\n\tthis->clear_parse_results();\n\n\tauto parser_format = SmartctlParser::detect_output_format(this->full_output_);\n\tif (!parser_format.has_value()) {\n\t\treturn hz::Unexpected(StorageDeviceError::ParseError, parser_format.error().message());\n\t}\n\n\tauto basic_parser = SmartctlParser::create(SmartctlParserType::Basic, parser_format.value());\n\tif (!basic_parser) {\n\t\treturn hz::Unexpected(StorageDeviceError::ParseError, _(\"Cannot create parser\"));\n\t}\n\n\t// This will add some properties and emit signal_changed().\n\tauto basic_parse_status = basic_parser->parse(this->full_output_);\n\tif (!basic_parse_status) {\n\t\tstd::string message = basic_parse_status.error().message();\n\t\treturn hz::Unexpected(StorageDeviceError::ParseError,\n\t\t\t\tfmt::format(fmt::runtime(_(\"Cannot parse smartctl output: {}\")), message));\n\t}\n\n\tauto basic_property_repo = basic_parser->get_property_repository();\n\n\t// Make detected type more exact.\n\tdetect_drive_type_from_properties(basic_property_repo);\n\n\t// Set properties from the basic parser.\n\tset_property_repository(StoragePropertyProcessor::process_properties(basic_property_repo, get_detected_type()));\n\n\t// Read common properties from the repository.\n\tread_common_properties();\n\n\n\t// Try to parse with a specialized parser based on drive type\n\tauto parser_type = SmartctlVersionParser::get_default_parser_type(this->get_detected_type());\n\n\tif (parser_type != SmartctlParserType::Basic) {\n\t\t// Try specialized parser\n\t\tauto parser = SmartctlParser::create(parser_type, parser_format.value());\n\t\tDBG_ASSERT_RETURN(parser, hz::Unexpected(StorageDeviceError::ParseError, _(\"Cannot create parser.\")));\n\n\t\tconst auto parse_status = parser->parse(this->full_output_);\n\t\tif (parse_status.has_value()) {\n\t\t\t// Call this after parse_basic_data(), since it sets parse status to \"info\".\n\t\t\tset_parse_status(StorageDevice::ParseStatus::Full);\n\n\t\t\t// set the full properties.\n\t\t\t// copy to our drive, overwriting old data.\n\t\t\tset_property_repository(StoragePropertyProcessor::process_properties(parser->get_property_repository(), get_detected_type()));\n\t\t}\n\t}\n\n\tif (get_parse_status() != ParseStatus::Full) {\n\t\t// Only basic data available\n\t\tset_parse_status(ParseStatus::Basic);\n\t\tset_property_repository(StoragePropertyProcessor::process_properties(basic_parser->get_property_repository(), get_detected_type()));\n\t}\n\n\tsignal_changed().emit(this);  // notify listeners\n\n\t// Don't show any GUI warnings on parse failure - it may just be an unsupported\n\t// drive (e.g. usb flash disk). Plus, it may flood the string. The data will be\n\t// parsed again in Info window, and we show the warnings there.\n//\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Cannot parse smartctl output.\\n\");\n\n\t// proper parsing failed. try to at least extract info section\n//\tthis->basic_output_ = this->full_output_;  // complete output here. sometimes it's only the info section\n\n\treturn {};\n}\n\n\n\nStorageDevice::ParseStatus StorageDevice::get_parse_status() const\n{\n\treturn parse_status_;\n}\n\n\n\nhz::ExpectedVoid<StorageDeviceError> StorageDevice::set_smart_enabled(bool b,\n\t\tconst std::shared_ptr<CommandExecutor>& smartctl_ex)\n{\n\tif (this->test_is_active_) {\n\t\treturn hz::Unexpected(StorageDeviceError::TestRunning, _(\"A test is currently being performed on this drive.\"));\n\t}\n\n\t// execute smartctl --smart=on|off /dev/...\n\t// --saveauto=on is also executed when enabling smart.\n\n\t// Output:\n/*\n=== START OF ENABLE/DISABLE COMMANDS SECTION ===\nSMART Enabled.\nSMART Attribute Autosave Enabled.\n--------------------------- OR ---------------------------\n=== START OF ENABLE/DISABLE COMMANDS SECTION ===\nSMART Disabled. Use option -s with argument 'on' to enable it.\n--------------------------- OR ---------------------------\nA mandatory SMART command failed: exiting. To continue, add one or more '-T permissive' options.\n*/\n\n\tstd::string output;\n\tstd::vector<std::string> on_command = {\"--smart=on\", \"--saveauto=on\"};\n\tstd::vector<std::string> off_command = {\"--smart=off\"};\n\tauto status = execute_device_smartctl((b ? on_command : off_command), smartctl_ex, output);\n\tif (!status) {\n\t\treturn status;\n\t}\n\n\t// search at line start, because they are sometimes present in other sentences too.\n\tif (app_regex_partial_match(\"/^SMART Enabled/mi\", output)\n\t\t\t|| app_regex_partial_match(\"/^SMART Disabled/mi\", output)) {\n\t\treturn {};  // success\n\t}\n\n\tif (app_regex_partial_match(\"/^A mandatory SMART command failed/mi\", output)) {\n\t\treturn hz::Unexpected(StorageDeviceError::CommandFailed, _(\"Mandatory SMART command failed.\"));\n\t}\n\n\treturn hz::Unexpected(StorageDeviceError::CommandUnknownError, _(\"Unknown error occurred.\"));\n}\n\n\n\nvoid StorageDevice::read_common_properties()\n{\n\tif (auto prop = property_repository_.lookup_property(\"smart_support/available\"); !prop.empty()) {\n\t\tsmart_supported_ = prop.get_value<bool>();\n\t}\n\tif (auto prop = property_repository_.lookup_property(\"smart_support/enabled\"); !prop.empty()) {\n\t\tsmart_enabled_ = prop.get_value<bool>();\n\t}\n\tif (auto prop = property_repository_.lookup_property(\"model_name\"); !prop.empty()) {\n\t\tmodel_name_ = prop.get_value<std::string>();\n\t} else if (prop = property_repository_.lookup_property(\"scsi_model_name\"); !prop.empty()) {  // USB flash\n\t\tmodel_name_ = prop.get_value<std::string>();\n\t}\n\tif (auto prop = property_repository_.lookup_property(\"model_family\"); !prop.empty()) {\n\t\tfamily_name_ = prop.get_value<std::string>();\n\t} else if (prop = property_repository_.lookup_property(\"scsi_vendor\"); !prop.empty()) {  // USB flash\n\t\tfamily_name_ = prop.get_value<std::string>();\n\t}\n\tif (auto prop = property_repository_.lookup_property(\"serial_number\"); !prop.empty()) {\n\t\tserial_number_ = prop.get_value<std::string>();\n\t}\n\tif (auto prop = property_repository_.lookup_property(\"user_capacity/bytes/_short\"); !prop.empty()) {\n\t\tsize_ = prop.readable_value;\n\t} else if (auto prop2 = property_repository_.lookup_property(\"user_capacity/bytes\"); !prop.empty()) {\n\t\tsize_ = prop2.readable_value;\n\t}\n}\n\n\n\nvoid StorageDevice::detect_drive_type_from_properties(const StoragePropertyRepository& property_repo)\n{\n\t// This is set by Text parser\n\tif (auto drive_type_prop = property_repo.lookup_property(\"_text_only/custom/parser_detected_drive_type\"); !drive_type_prop.empty()) {\n\t\tconst auto& drive_type_storable_str = drive_type_prop.get_value<std::string>();\n\t\tset_detected_type(StorageDeviceDetectedTypeExt::get_by_storable_name(drive_type_storable_str, StorageDeviceDetectedType::BasicScsi));\n\n\t\t// Find out if it's SSD or HDD\n\t\tif (get_detected_type() == StorageDeviceDetectedType::AtaAny) {\n\t\t\tauto rpm_prop = property_repo.lookup_property(\"rotation_rate\");\n\t\t\tif (rpm_prop.empty() || rpm_prop.get_value<std::int64_t>() == 0) {\n\t\t\t\tset_detected_type(StorageDeviceDetectedType::AtaSsd);\n\t\t\t} else {\n\t\t\t\tset_detected_type(StorageDeviceDetectedType::AtaHdd);\n\t\t\t}\n\t\t}\n\t}\n\n\t// This is set by JSON parser\n\tif (auto device_type_prop = property_repo.lookup_property(\"device/type\"); !device_type_prop.empty()) {\n\t\t// Note: USB flash drives in non-scsi mode do not have this property.\n\t\tconst auto& smartctl_type = device_type_prop.get_value<std::string>();\n\n\t\tstd::string lowercase_protocol;\n\t\tif (auto device_protocol_prop = property_repo.lookup_property(\"device/protocol\"); !device_protocol_prop.empty()) {\n\t\t\tlowercase_protocol = hz::string_to_lower_copy(device_protocol_prop.get_value<std::string>());\n\t\t}\n\n\t\t// USB flash in scsi mode, optical, scsi, etc.\n\t\t// Protocol is also \"SCSI\".\n\t\tif (smartctl_type == \"scsi\") {\n\t\t\tif (BuildEnv::is_kernel_linux() && get_device_base().starts_with(\"sr\")) {\n\t\t\t\tset_detected_type(StorageDeviceDetectedType::CdDvd);\n\t\t\t} else {\n\t\t\t\tset_detected_type(StorageDeviceDetectedType::BasicScsi);\n\t\t\t}\n\n\t\t// (S)ATA, including behind supported RAID controllers\n\t\t} else if (smartctl_type == \"sat\" || lowercase_protocol == \"ata\") {\n\t\t\t// Find out if it's SSD or HDD\n\t\t\tauto rpm_prop = property_repo.lookup_property(\"rotation_rate\");\n\t\t\tif (rpm_prop.empty() || rpm_prop.get_value<std::int64_t>() == 0) {\n\t\t\t\tset_detected_type(StorageDeviceDetectedType::AtaSsd);\n\t\t\t} else {\n\t\t\t\tset_detected_type(StorageDeviceDetectedType::AtaHdd);\n\t\t\t}\n\n\t\t// NVMe SSD.\n\t\t// Note: NVMe behind USB bridge may have type \"sntrealtek\" or similar, with protocol \"nvme\".\n\t\t} else if (smartctl_type == \"nvme\" || lowercase_protocol == \"nvme\") {\n\t\t\tset_detected_type(StorageDeviceDetectedType::Nvme);\n\n\t\t} else {\n\t\t\t// TODO Detect unsupported RAID\n\t\t\tdebug_out_warn(\"app\", \"Unsupported type \" << smartctl_type << \" (protocol: \" << lowercase_protocol << \") reported by smartctl for \" << get_device_with_type() << \"\\n\");\n\t\t}\n\t}\n\n\tif (get_detected_type() == StorageDeviceDetectedType::Unknown) {\n\t\tset_detected_type(StorageDeviceDetectedType::BasicScsi);  // fall back to basic scsi parser\n\t}\n\n\tdebug_out_info(\"app\", \"Device \" << get_device_with_type() << \" detected after parser to be of type \"\n\t\t\t<< StorageDeviceDetectedTypeExt::get_storable_name(get_detected_type()) << \"\\n\");\n}\n\n\n\nStorageDevice::SmartStatus StorageDevice::get_smart_status() const\n{\n\tSmartStatus status = SmartStatus::Unsupported;\n\tif (smart_enabled_.has_value()) {\n\t\tif (smart_enabled_.value()) {  // enabled, supported\n\t\t\tstatus = SmartStatus::Enabled;\n\t\t} else {  // if it's disabled, maybe it's unsupported, check that:\n\t\t\tif (smart_supported_.has_value()) {\n\t\t\t\tif (smart_supported_.value()) {  // disabled, supported\n\t\t\t\t\tstatus = SmartStatus::Disabled;\n\t\t\t\t} else {  // disabled, unsupported\n\t\t\t\t\tstatus = SmartStatus::Unsupported;\n\t\t\t\t}\n\t\t\t} else {  // disabled, support unknown\n\t\t\t\tstatus = SmartStatus::Disabled;\n\t\t\t}\n\t\t}\n\t} else {  // status unknown\n\t\tif (smart_supported_.has_value()) {\n\t\t\tif (smart_supported_.value()) {  // status unknown, supported\n\t\t\t\tstatus = SmartStatus::Disabled;  // at least give the user a chance to try enabling it\n\t\t\t} else {  // status unknown, unsupported\n\t\t\t\tstatus = SmartStatus::Unsupported;  // most likely\n\t\t\t}\n\t\t} else {  // status unknown, support unknown\n\t\t\tstatus = SmartStatus::Unsupported;\n\t\t}\n\t}\n\treturn status;\n}\n\n\n\nbool StorageDevice::get_smart_switch_supported() const\n{\n\tconst bool supported = get_smart_status() != SmartStatus::Unsupported;\n\t// NVMe does not support on/off\n\tconst bool is_nvme = get_detected_type() == StorageDeviceDetectedType::Nvme;\n\n\treturn !get_is_virtual() && supported && !is_nvme;\n}\n\n\n\nstd::string StorageDevice::get_device_size_str() const\n{\n\treturn (size_.has_value() ? size_.value() : \"\");\n}\n\n\n\nStorageProperty StorageDevice::get_health_property() const\n{\n\tif (health_property_.has_value())  // cached return value\n\t\treturn health_property_.value();\n\n\tStorageProperty p = property_repository_.lookup_property(\"smart_status/passed\",\n\t\t\tStoragePropertySection::OverallHealth);\n\tif (!p.empty())\n\t\thealth_property_ = p;  // store to cache\n\n\treturn p;\n}\n\n\n\nstd::string StorageDevice::get_device() const\n{\n\treturn device_;\n}\n\n\n\nstd::string StorageDevice::get_device_base() const\n{\n\tif (is_virtual_)\n\t\treturn \"\";\n\n\tconst std::string::size_type pos = device_.rfind('/');  // find basename\n\tif (pos == std::string::npos)\n\t\treturn device_;  // fall back\n\treturn device_.substr(pos+1, std::string::npos);\n}\n\n\n\nstd::string StorageDevice::get_device_with_type() const\n{\n\tif (this->get_is_virtual()) {\n\t\tconst std::string vf = this->get_virtual_filename();\n\t\t/// Translators: %1 is filename\n\t\tstd::string ret = Glib::ustring::compose(C_(\"filename\", \"Virtual (%1)\"), (vf.empty() ? (std::string(\"[\") + C_(\"filename\", \"empty\") + \"]\") : vf));\n\t\treturn ret;\n\t}\n\tstd::string device = get_device();\n\tif (!get_type_argument().empty()) {\n\t\tdevice = Glib::ustring::compose(_(\"%1 (%2)\"), device, get_type_argument());\n\t}\n\treturn device;\n}\n\n\n\nvoid StorageDevice::set_detected_type(StorageDeviceDetectedType t)\n{\n\tdetected_type_ = t;\n}\n\n\n\nStorageDeviceDetectedType StorageDevice::get_detected_type() const\n{\n\treturn detected_type_;\n}\n\n\n\nvoid StorageDevice::set_type_argument(std::string arg)\n{\n\ttype_arg_ = std::move(arg);\n}\n\n\n\nstd::string StorageDevice::get_type_argument() const\n{\n\treturn type_arg_;\n}\n\n\n\nvoid StorageDevice::set_extra_arguments(std::vector<std::string> args)\n{\n\textra_args_ = std::move(args);\n}\n\n\n\nstd::vector<std::string> StorageDevice::get_extra_arguments() const\n{\n\treturn extra_args_;\n}\n\n\n\nvoid StorageDevice::set_drive_letters(std::map<char, std::string> letters)\n{\n\tdrive_letters_ = std::move(letters);\n}\n\n\n\nconst std::map<char, std::string>& StorageDevice::get_drive_letters() const\n{\n\treturn drive_letters_;\n}\n\n\n\nstd::string StorageDevice::format_drive_letters(bool with_volnames) const\n{\n\tstd::vector<std::string> drive_letters_decorated;\n\tfor (const auto& iter : drive_letters_) {\n\t\tdrive_letters_decorated.push_back(std::string() + (char)std::toupper(iter.first) + \":\");\n\t\tif (with_volnames && !iter.second.empty()) {\n\t\t\t// e.g. \"C: (Local Drive)\"\n\t\t\tdrive_letters_decorated.back() = Glib::ustring::compose(_(\"%1 (%2)\"), drive_letters_decorated.back(), iter.second);\n\t\t}\n\t}\n\treturn hz::string_join(drive_letters_decorated, \", \");\n}\n\n\n\nbool StorageDevice::get_is_virtual() const\n{\n\treturn is_virtual_;\n}\n\n\n\nhz::fs::path StorageDevice::get_virtual_file() const\n{\n\treturn (is_virtual_ ? virtual_file_ : hz::fs::path());\n}\n\n\n\nstd::string StorageDevice::get_virtual_filename() const\n{\n\treturn (is_virtual_ ? hz::fs_path_to_string(virtual_file_.filename()) : std::string());\n}\n\n\n\nconst StoragePropertyRepository& StorageDevice::get_property_repository() const\n{\n\treturn property_repository_;\n}\n\n\n\nstd::string StorageDevice::get_model_name() const\n{\n\treturn (model_name_.has_value() ? model_name_.value() : \"\");\n}\n\n\n\nstd::string StorageDevice::get_family_name() const\n{\n\treturn (family_name_.has_value() ? family_name_.value() : \"\");\n}\n\n\n\nstd::string StorageDevice::get_serial_number() const\n{\n\treturn (serial_number_.has_value() ? serial_number_.value() : \"\");\n}\n\n\n\nvoid StorageDevice::set_info_output(std::string s)\n{\n\tbasic_output_ = std::move(s);\n}\n\n\n\nstd::string StorageDevice::get_basic_output() const\n{\n\treturn basic_output_;\n}\n\n\n\nvoid StorageDevice::set_full_output(std::string s)\n{\n\tfull_output_ = std::move(s);\n}\n\n\n\nstd::string StorageDevice::get_full_output() const\n{\n\treturn full_output_;\n}\n\n\n\nvoid StorageDevice::set_is_manually_added(bool b)\n{\n\tis_manually_added_ = b;\n}\n\n\n\nbool StorageDevice::get_is_manually_added() const\n{\n\treturn is_manually_added_;\n}\n\n\n\nvoid StorageDevice::set_test_is_active(bool b)\n{\n\tconst bool changed = (test_is_active_ != b);\n\ttest_is_active_ = b;\n\tif (changed) {\n\t\tsignal_changed().emit(this);  // so that everybody stops any test-aborting operations.\n\t}\n}\n\n\n\nbool StorageDevice::get_test_is_active() const\n{\n\treturn test_is_active_;\n}\n\n\n\nStorageDevice::SelfTestSupportStatus StorageDevice::get_self_test_support_status() const\n{\n\tif (get_parse_status() == ParseStatus::Full) {\n\t\treturn property_repository_.has_properties_for_section(StoragePropertySection::SelftestLog) ?\n\t\t\t\tSelfTestSupportStatus::Supported : SelfTestSupportStatus::Unsupported;\n\t}\n\tif (get_parse_status() == ParseStatus::Basic) {\n\t\treturn get_smart_status() == SmartStatus::Enabled ? SelfTestSupportStatus::Unknown : SelfTestSupportStatus::Unsupported;\n\t}\n\treturn StorageDevice::SelfTestSupportStatus::Unknown;\n}\n\n\n\nstd::string StorageDevice::get_save_filename() const\n{\n\tconst std::string model = this->get_model_name();  // may be empty\n\tconst std::string serial = this->get_serial_number();\n\tconst std::string date = hz::format_date(\"%Y-%m-%d_%H%M\", true);\n\n\tauto filename_format = rconfig::get_data<std::string>(\"gui/smartctl_output_filename_format\");\n\thz::string_replace(filename_format, \"{serial}\", serial);\n\thz::string_replace(filename_format, \"{model}\", model);\n\thz::string_replace(filename_format, \"{date}\", date);\n\n\treturn hz::fs_filename_make_safe(filename_format);\n}\n\n\n\nstd::vector<std::string> StorageDevice::get_device_options() const\n{\n\tif (is_virtual_) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Cannot get device options of a virtual device.\\n\");\n\t\treturn {};\n\t}\n\n\t// If we have some special type or option, specify it on the command line (like \"-d scsi\").\n\t// Note that the latter \"-d\" option overrides the former.\n\n\t// lowest priority - the detected type\n\tstd::vector<std::string> args;\n\tif (!get_type_argument().empty()) {\n\t\targs.emplace_back(\"-d\");\n\t\targs.push_back(get_type_argument());\n\t}\n\t// extra args, as specified manually in CLI or when adding the drive\n\tauto extra_args = get_extra_arguments();\n\targs.insert(args.end(), extra_args.begin(), extra_args.end());\n\n\t// config options, as specified in preferences.\n\tstd::vector<std::string> config_options = app_get_device_options(get_device(), get_type_argument());\n\targs.insert(args.end(), config_options.begin(), config_options.end());\n\n\treturn args;\n}\n\n\n\nhz::ExpectedVoid<StorageDeviceError> StorageDevice::execute_device_smartctl(const std::vector<std::string>& command_options,\n\t\tconst std::shared_ptr<CommandExecutor>& smartctl_ex, std::string& smartctl_output, bool check_type)\n{\n\t// don't forbid running on currently tested drive - we need to call this from the test code.\n\n\tif (is_virtual_) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Cannot execute smartctl on a virtual device.\\n\");\n\t\treturn hz::Unexpected(StorageDeviceError::CannotExecuteOnVirtual, _(\"Cannot execute smartctl on a virtual device.\"));\n\t}\n\n\tconst std::string device = get_device();\n\n\tauto smartctl_status = execute_smartctl(device, this->get_device_options(),\n\t\t\tcommand_options, smartctl_ex, smartctl_output);\n\n\tif (!smartctl_status) {\n\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Smartctl binary did not execute cleanly.\\n\");\n\n\t\t// Smartctl 5.39 cvs/svn version defaults to usb type on at least linux and windows.\n\t\t// This means that the old SCSI identify command isn't executed by default,\n\t\t// and there is no information about the device manufacturer/etc. in the output.\n\t\t// We detect this and set the device type to scsi to at least have _some_ info.\n\n\t\t// Note: This match works even with JSON (the text output is included in --json=o).\n\t\tif (check_type && this->get_detected_type() == StorageDeviceDetectedType::Unknown\n\t\t\t\t&& app_regex_partial_match(\"/specify device type with the -d option/mi\", smartctl_output)) {\n\t\t\tthis->set_detected_type(StorageDeviceDetectedType::NeedsExplicitType);\n\t\t}\n\n\t\treturn hz::Unexpected(StorageDeviceError::ExecutionError, smartctl_status.error().message());\n\t}\n\n\treturn {};\n}\n\n\n\nsigc::signal<void, StorageDevice*>& StorageDevice::signal_changed()\n{\n\treturn signal_changed_;\n}\n\n\n\nvoid StorageDevice::set_parse_status(ParseStatus value)\n{\n\tparse_status_ = value;\n}\n\n\nvoid StorageDevice::set_property_repository(StoragePropertyRepository repository)\n{\n\tproperty_repository_ = std::move(repository);\n}\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_device.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef STORAGE_DEVICE_H\n#define STORAGE_DEVICE_H\n\n#include <string>\n#include <map>\n#include <optional>\n#include <memory>\n#include <sigc++/sigc++.h>\n\n#include \"hz/fs_ns.h\"\n#include \"hz/string_algo.h\"\n#include \"storage_property.h\"\n#include \"smartctl_parser_types.h\"\n#include \"smartctl_executor.h\"\n#include \"storage_property_repository.h\"\n#include \"storage_device_detected_type.h\"\n\n\n\n\n\nclass StorageDevice;\n\n\n/// A reference-counting pointer to StorageDevice\nusing StorageDevicePtr = std::shared_ptr<StorageDevice>;\n\n\n\nenum class StorageDeviceError {\n\tTestRunning,  ///< A test is running, so the device cannot perform this operation.\n\tCannotExecuteOnVirtual,  ///< Cannot execute this operation on a virtual device.\n\tExecutionError,  ///< Error executing the command.\n\tCommandFailed,  ///< SMART command (e.g. enable/disable SMART) failed.\n\tCommandUnknownError,  ///< Unknown error from the command.\n\tParseError,  ///< Error parsing the output.\n};\n\n\n/// This class represents a single drive\nclass StorageDevice {\n\tpublic:\n\n\t\t/// Statuses of various states\n\t\tenum class SmartStatus {\n\t\t\tEnabled,\n\t\t\tDisabled,\n\t\t\tUnsupported,\n\t\t};\n\n\t\t/// Get displayable name for Status.\n\t\t[[nodiscard]] static std::string get_status_displayable_name(SmartStatus status);\n\n\n\t\t/// Statuses of various parse states\n\t\tenum class ParseStatus {\n\t\t\tFull,  ///< Parsed with specialized parser\n\t\t\tBasic,  ///< Only info section available, parsed with Basic parser\n\t\t\tNone,  ///< No data\n\t\t};\n\n\n\t\t/// Status of self-test support\n\t\tenum class SelfTestSupportStatus {\n\t\t\tUnknown,  ///< Full info not parsed yet\n\t\t\tSupported,  ///< Supported\n\t\t\tUnsupported,  ///< Not supported\n\t\t};\n\n\n\t\t/// Constructor\n\t\texplicit StorageDevice(std::string dev_or_vfile, bool is_virtual = false);\n\n\t\t/// Constructor\n\t\tStorageDevice(std::string dev, std::string type_arg);\n\n\n\t\t/// Clear fetched smartctl outputs\n\t\tvoid clear_outputs();\n\n\t\t/// Clear common properties previously imported from repository.\n\t\tvoid clear_parse_results();\n\n\n\t\t/// Calls \"smartctl -i -H -c\" (info section, health, capabilities), then parse_basic_data().\n\t\t/// Called during drive detection.\n\t\t/// Note: this will clear all previous properties!\n\t\t[[nodiscard]] hz::ExpectedVoid<StorageDeviceError> fetch_basic_data_and_parse(\n\t\t\t\tconst std::shared_ptr<CommandExecutor>& smartctl_ex = nullptr);\n\n\t\t/// Detects type, smart support, smart status (on / off).\n\t\t/// Note: this will clear all previous properties!\n\t\t[[nodiscard]] hz::ExpectedVoid<StorageDeviceError> parse_basic_data();\n\n\n\t\t/// Execute smartctl --all / -x (all sections), get output, parse it (basic data too), fill properties.\n\t\t[[nodiscard]] hz::ExpectedVoid<StorageDeviceError> fetch_full_data_and_parse(const std::shared_ptr<CommandExecutor>& smartctl_ex);\n\n\t\t/// Parse full info.\n\t\t[[nodiscard]] hz::ExpectedVoid<StorageDeviceError> parse_full_data(SmartctlParserType parser_type, SmartctlOutputFormat format);\n\n\t\t/// Try to detect the data type and parse it. This is used when loading virtual drives.\n\t\t[[nodiscard]] hz::ExpectedVoid<StorageDeviceError> parse_any_data_for_virtual();\n\n\t\t/// Get the \"fully parsed\" flag\n\t\t[[nodiscard]] ParseStatus get_parse_status() const;\n\n\n\t\t/// Try to enable SMART.\n\t\t[[nodiscard]] hz::ExpectedVoid<StorageDeviceError> set_smart_enabled(bool b, const std::shared_ptr<CommandExecutor>& smartctl_ex);\n\n\n\t\t/// Read common properties (smart supported, smart enabled, etc.) from the repository.\n\t\tvoid read_common_properties();\n\n\t\t/// Detect drive type based on parsed properties.\n\t\tvoid detect_drive_type_from_properties(const StoragePropertyRepository& property_repo);\n\n\n\t\t/// Get SMART status\n\t\t[[nodiscard]] SmartStatus get_smart_status() const;\n\n\t\t/// Get if SMART on/off is supported\n\t\t[[nodiscard]] bool get_smart_switch_supported() const;\n\n\n\t\t/// Get format size string, or an empty string on error.\n\t\t[[nodiscard]] std::string get_device_size_str() const;\n\n\t\t/// Get the overall health property\n\t\t[[nodiscard]] StorageProperty get_health_property() const;\n\n\n\t\t/// Get device name (e.g. /dev/sda)\n\t\t[[nodiscard]] std::string get_device() const;\n\n\t\t/// Get device name without path. For example, \"sda\".\n\t\t[[nodiscard]] std::string get_device_base() const;\n\n\t\t/// Get device name for display purposes (with a type argument in parentheses)\n\t\t[[nodiscard]] std::string get_device_with_type() const;\n\n\n\t\t/// Set detected type\n\t\tvoid set_detected_type(StorageDeviceDetectedType t);\n\n\t\t/// Get detected type\n\t\t[[nodiscard]] StorageDeviceDetectedType get_detected_type() const;\n\n\n\t\t/// Set argument for \"-d\" smartctl parameter\n\t\tvoid set_type_argument(std::string arg);\n\n\t\t/// Get argument for \"-d\" smartctl parameter\n\t\t[[nodiscard]] std::string get_type_argument() const;\n\n\n\t\t/// Set extra arguments smartctl\n\t\tvoid set_extra_arguments(std::vector<std::string> args);\n\n\t\t/// Get extra arguments smartctl\n\t\t[[nodiscard]] std::vector<std::string> get_extra_arguments() const;\n\n\n\t\t/// Set windows drive letters for this drive\n\t\tvoid set_drive_letters(std::map<char, std::string> letters_volnames);\n\n\t\t/// Get windows drive letters for this drive\n\t\t[[nodiscard]] const std::map<char, std::string>& get_drive_letters() const;\n\n\t\t/// Get comma-separated win32 drive letters (if present)\n\t\t[[nodiscard]] std::string format_drive_letters(bool with_volnames) const;\n\n\n\t\t/// Get \"virtual\" status\n\t\t[[nodiscard]] bool get_is_virtual() const;\n\n\t\t/// If the device is virtual, return its file\n\t\t[[nodiscard]] hz::fs::path get_virtual_file() const;\n\n\t\t/// Get only the filename portion of a virtual file\n\t\t[[nodiscard]] std::string get_virtual_filename() const;\n\n\n\t\t/// Get properties\n\t\t[[nodiscard]] const StoragePropertyRepository& get_property_repository() const;\n\n\n\t\t/// Get model name.\n\t\t/// \\return empty string if not found\n\t\t[[nodiscard]] std::string get_model_name() const;\n\n\t\t/// Get family name.\n\t\t/// \\return empty string if not found\n\t\t[[nodiscard]] std::string get_family_name() const;\n\n\t\t/// Get serial number.\n\t\t/// \\return empty string if not found\n\t\t[[nodiscard]] std::string get_serial_number() const;\n\n\n\t\t/// Set \"info\" output to parse\n\t\tvoid set_info_output(std::string s);\n\n\t\t/// Get \"info\" output to parse\n\t\t[[nodiscard]] std::string get_basic_output() const;\n\n\n\t\t/// Set \"full\" output to parse\n\t\tvoid set_full_output(std::string s);\n\n\t\t/// Get \"full\" output to parse\n\t\t[[nodiscard]] std::string get_full_output() const;\n\n\n\t\t/// Set \"manually added\" flag\n\t\tvoid set_is_manually_added(bool b);\n\n\t\t/// Get \"manually added\" flag\n\t\t[[nodiscard]] bool get_is_manually_added() const;\n\n\n\t\t/// Set \"test is active\" flag, emit the \"changed\" signal if needed.\n\t\tvoid set_test_is_active(bool b);\n\n\t\t/// Get \"test is active\" flag\n\t\t[[nodiscard]] bool get_test_is_active() const;\n\n\n\t\t/// Get whether the tests are supported, based on parsed properties\n\t\t[[nodiscard]] SelfTestSupportStatus get_self_test_support_status() const;\n\n\n\t\t/// Get the recommended filename to save output to. Includes model and date.\n\t\t[[nodiscard]] std::string get_save_filename() const;\n\n\n\t\t/// Get final smartctl options for this device from config and type info.\n\t\t[[nodiscard]] std::vector<std::string> get_device_options() const;\n\n\n\t\t/// Execute smartctl on this device. Nothing is modified in this class.\n\t\t[[nodiscard]] hz::ExpectedVoid<StorageDeviceError> execute_device_smartctl(const std::vector<std::string>& command_options,\n\t\t\t\tconst std::shared_ptr<CommandExecutor>& smartctl_ex, std::string& output, bool check_type = false);\n\n\n\t\t/// Emitted whenever new information is available\n\t\t[[nodiscard]] sigc::signal<void, StorageDevice*>& signal_changed();\n\n\n\tprotected:\n\n\t\t/// Set the \"fully parsed\" flag\n\t\tvoid set_parse_status(ParseStatus value);\n\n\t\t/// Set properties\n\t\tvoid set_property_repository(StoragePropertyRepository repository);\n\n\n\tprivate:\n\n\t\tstd::string device_;  ///< e.g. /dev/sda or pd0. empty if virtual.\n\t\tstd::string type_arg_;  ///< Device type (for -d smartctl parameter), as specified when adding the device.\n\t\tstd::vector<std::string> extra_args_;  ///< Extra parameters for smartctl, as specified when adding the device.\n\n\t\tstd::map<char, std::string> drive_letters_;  ///< Windows drive letters (if detected), with volume names\n\n\t\tbool is_virtual_ = false;  ///< If true, then this is not a real device - merely a loaded description of it.\n\t\thz::fs::path virtual_file_;  ///< A file (smartctl data) the virtual device was loaded from\n\t\tbool is_manually_added_ = false;  ///< StorageDevice doesn't use it, but it's useful for its users.\n\n\t\t/// Sort of a \"lock\". If true, the device is not allowed to perform any commands\n\t\t/// except \"-l selftest\" and maybe \"--capabilities\" and \"--info\" (not sure).\n\t\tbool test_is_active_ = false;\n\n\t\t// Outputs\n\t\tstd::string basic_output_;  ///< \"smartctl --info\" output\n\t\tstd::string full_output_;  ///< \"smartctl --all\" or \"-x\" output\n\n\t\tStorageDeviceDetectedType detected_type_ = StorageDeviceDetectedType::Unknown;  ///< Detected by basic parser\n\n\t\tParseStatus parse_status_ = ParseStatus::None;  ///< \"Fully parsed\" flag\n\n\t\tStoragePropertyRepository property_repository_;  ///< Parsed data properties\n\n\t\t// Common properties\n\t\tstd::optional<bool> smart_supported_;  ///< SMART support status\n\t\tstd::optional<bool> smart_enabled_;  ///< SMART enabled status\n\t\tstd::optional<std::string> model_name_;  ///< Model name\n\t\tstd::optional<std::string> family_name_;  ///< Family name\n\t\tstd::optional<std::string> serial_number_;  ///< Serial number\n\t\tstd::optional<std::string> size_;  ///< Formatted size\n\t\tmutable std::optional<StorageProperty> health_property_;  ///< Cached health property.\n\n\n\t\t/// Emitted whenever new information is available\n\t\tsigc::signal<void, StorageDevice*> signal_changed_;\n\n\n};\n\n\n\n\n/// Operator for sorting, hard drives first, then device name base\ninline bool operator< (const StorageDevicePtr& a, const StorageDevicePtr& b)\n{\n// \tif (a->get_detected_type() != a->get_detected_type()) {\n// \t\treturn (a->get_detected_type() == StorageDevice::DetectedType::unknown);  // hard drives first\n// \t}\n\tif (a->get_is_virtual() != b->get_is_virtual()) {\n\t\treturn int(a->get_is_virtual()) < int(b->get_is_virtual());\n\t}\n\tif (a->get_is_virtual()) {\n\t\treturn hz::string_natural_compare(a->get_virtual_filename(), b->get_virtual_filename()) < 0;\n\t}\n\tif (a->get_device_base() != b->get_device_base()) {\n\t\treturn hz::string_natural_compare(a->get_device_base(), b->get_device_base()) < 0;\n\t}\n\treturn hz::string_natural_compare(a->get_type_argument(), b->get_type_argument()) < 0;\n}\n\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_device_detected_type.cpp",
    "content": "\n\n\n/*\n *****************************************************************************\n License: GNU General Public License v3.0 only\n Copyright:\n \t(C) 2024 Alexander Shaduri <ashaduri@gmail.com>\n *****************************************************************************\n */\n#include \"storage_device_detected_type.h\"\n"
  },
  {
    "path": "src/applib/storage_device_detected_type.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n#ifndef STORAGE_DEVICE_DETECTED_TYPE_H\n#define STORAGE_DEVICE_DETECTED_TYPE_H\n\n#include <glibmm.h>\n#include <glibmm/i18n.h>\n#include <unordered_map>\n\n#include \"hz/enum_helper.h\"\n\n\n/// These may be used to force smartctl to a special type, as well as\n/// to display the correct icon\nenum class StorageDeviceDetectedType {\n\tUnknown,  ///< Unknown, default state.\n\tNeedsExplicitType,  ///< This is set by smartctl executor if it detects the need for -d option.\n\tAtaAny,  ///< Any ATA device (HDD or SSD), before it is detected whether it's HDD or SSD.\n\tAtaHdd,  ///< ATA HDD\n\tAtaSsd,  ///< ATA SSD\n\tNvme,  ///< NVMe device (SSD)\n\tBasicScsi,  ///< Basic SCSI device (no smart data). Usually flash drives, etc.\n\tCdDvd,  ///< CD/DVD/Blu-Ray. Blu-ray is not always detected.\n\tUnsupportedRaid,  ///< RAID controller or volume. Unsupported by smartctl, only basic info is given.\n};\n\n\n\n/// Helper structure for enum-related functions\nstruct StorageDeviceDetectedTypeExt\n\t\t: public hz::EnumHelper<\n\t\t\t\tStorageDeviceDetectedType,\n\t\t\t\tStorageDeviceDetectedTypeExt,\n\t\t\t\tGlib::ustring>\n{\n\tstatic constexpr StorageDeviceDetectedType default_value = StorageDeviceDetectedType::Unknown;\n\n\tstatic std::unordered_map<EnumType, std::pair<std::string, Glib::ustring>> build_enum_map()\n\t{\n\t\treturn {\n\t\t\t{StorageDeviceDetectedType::Unknown, {\"unknown\", _(\"Unknown\")}},\n\t\t\t{StorageDeviceDetectedType::NeedsExplicitType, {\"needs_explicit_type\", _(\"Needs Explicit Type\")}},\n\t\t\t{StorageDeviceDetectedType::AtaAny, {\"ata_any\", _(\"ATA Device (HDD or SSD)\")}},\n\t\t\t{StorageDeviceDetectedType::AtaHdd, {\"ata_hdd\", _(\"ATA HDD\")}},\n\t\t\t{StorageDeviceDetectedType::AtaSsd, {\"ata_ssd\", _(\"ATA SSD\")}},\n\t\t\t{StorageDeviceDetectedType::Nvme, {\"nvme\", _(\"NVMe Device\")}},\n\t\t\t{StorageDeviceDetectedType::BasicScsi, {\"basic_scsi\", _(\"Basic SCSI Device\")}},\n\t\t\t{StorageDeviceDetectedType::CdDvd, {\"cd_dvd\", _(\"CD/DVD/Blu-Ray\")}},\n\t\t\t{StorageDeviceDetectedType::UnsupportedRaid, {\"unsupported_raid\", _(\"Unsupported RAID Controller or Volume\")}},\n\t\t};\n\t}\n\n};\n\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_property.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include <glibmm.h>\n#include <cstddef>\n#include <chrono>\n#include <ios>\n#include <map>\n#include <ostream>  // not iosfwd - it doesn't work\n#include <sstream>\n#include <locale>\n#include <stdexcept>\n#include <string>\n#include <unordered_map>\n#include <variant>\n\n\n#include \"hz/string_num.h\"  // number_to_string\n#include \"hz/stream_cast.h\"  // stream_cast<>\n#include \"hz/format_unit.h\"  // format_time_length\n#include \"hz/string_algo.h\"  // string_join\n\n#include \"storage_property.h\"\n\n\n\nstd::ostream& operator<< (std::ostream& os, const AtaStorageTextCapability& p)\n{\n\tos\n\t\t\t// << p.name << \": \"\n\t\t\t<< p.flag_value;\n\tfor (auto&& v : p.strvalues) {\n\t\tos << \"\\n\\t\" << v;\n\t}\n\treturn os;\n}\n\n\n\nstd::string AtaStorageAttribute::get_readable_attribute_type_name(AttributeType type)\n{\n\tstatic const std::unordered_map<AttributeType, std::string> m {\n\t\t\t{AttributeType::Unknown, \"[unknown]\"},\n\t\t\t{AttributeType::Prefail, \"pre-failure\"},\n\t\t\t{AttributeType::OldAge,  \"old age\"},\n\t};\n\tif (auto iter = m.find(type); iter != m.end()) {\n\t\treturn iter->second;\n\t}\n\treturn \"[internal_error]\";\n}\n\n\n\nstd::string AtaStorageAttribute::get_readable_update_type_name(UpdateType type)\n{\n\tstatic const std::unordered_map<UpdateType, std::string> m {\n\t\t\t{UpdateType::Unknown, \"[unknown]\"},\n\t\t\t{UpdateType::Always,  \"continuously\"},\n\t\t\t{UpdateType::Offline, \"on offline data collect.\"},\n\t};\n\tif (auto iter = m.find(type); iter != m.end()) {\n\t\treturn iter->second;\n\t}\n\treturn \"[internal_error]\";\n}\n\n\n\nstd::string AtaStorageAttribute::get_readable_fail_time_name(FailTime type)\n{\n\tstatic const std::unordered_map<FailTime, std::string> m {\n\t\t\t{FailTime::Unknown, \"[unknown]\"},\n\t\t\t{FailTime::None,    \"never\"},\n\t\t\t{FailTime::Past,    \"in the past\"},\n\t\t\t{FailTime::Now,     \"now\"},\n\t};\n\tif (auto iter = m.find(type); iter != m.end()) {\n\t\treturn iter->second;\n\t}\n\treturn \"[internal_error]\";\n}\n\n\n\nstd::string AtaStorageAttribute::format_raw_value() const\n{\n\t// If it's fully a number, format it with commas\n\tif (hz::number_to_string_nolocale(raw_value_int) == raw_value) {\n\t\tstd::stringstream ss;\n\t\ttry {\n\t\t\tss.imbue(std::locale(\"\"));\n\t\t}\n\t\tcatch (const std::runtime_error& e) {\n\t\t\t// something is wrong with system locale, can't do anything here.\n\t\t}\n\t\tss << std::fixed << raw_value_int;\n\t\treturn ss.str();\n\t}\n\treturn raw_value;\n}\n\n\n\nstd::ostream& operator<< (std::ostream& os, const AtaStorageAttribute& p)\n{\n//\tos << p.name << \": \"\n\tif (p.value.has_value()) {\n\t\tos << static_cast<int>(p.value.value());\n\t} else {\n\t\tos << \"-\";\n\t}\n\tos << \" (\" << p.format_raw_value() << \")\";\n\treturn os;\n}\n\n\n\nbool AtaStorageStatistic::is_normalized() const\n{\n\treturn flags.find('N') != std::string::npos;\n}\n\n\n\nstd::string AtaStorageStatistic::format_value() const\n{\n\t// If it's fully a number, format it with commas\n\tif (hz::number_to_string_nolocale(value_int) == value) {\n\t\tstd::stringstream ss;\n\t\ttry {\n\t\t\tss.imbue(std::locale(\"\"));\n\t\t}\n\t\tcatch (const std::runtime_error& e) {\n\t\t\t// something is wrong with system locale, can't do anything here.\n\t\t}\n\t\tss << std::fixed << value_int;\n\t\treturn ss.str();\n\t}\n\treturn value;\n}\n\n\n\nstd::ostream& operator<<(std::ostream& os, const AtaStorageStatistic& p)\n{\n\tos << p.value;\n\treturn os;\n}\n\n\n\nstd::string AtaStorageErrorBlock::format_readable_error_types(const std::vector<std::string>& types)\n{\n\tstatic const std::map<std::string, std::string> m = {\n\t\t{\"ABRT\", _(\"Command aborted\")},\n\t\t{\"AMNF\", _(\"Address mark not found\")},\n\t\t{\"CCTO\", _(\"Command completion timed out\")},\n\t\t{\"EOM\", _(\"End of media\")},\n\t\t{\"ICRC\", _(\"Interface CRC error\")},\n\t\t{\"IDNF\", _(\"Identity not found\")},\n\t\t{\"ILI\", _(\"(Packet command-set specific)\")},\n\t\t{\"MC\", _(\"Media changed\")},\n\t\t{\"MCR\", _(\"Media change request\")},\n\t\t{\"NM\", _(\"No media\")},\n\t\t{\"obs\", _(\"Obsolete\")},\n\t\t{\"TK0NF\", _(\"Track 0 not found\")},\n\t\t{\"UNC\", _(\"Uncorrectable error in data\")},\n\t\t{\"WP\", _(\"Media is write protected\")},\n\t};\n\n\tstd::vector<std::string> sv;\n\tfor (const auto& type : types) {\n\t\tif (m.find(type) != m.end()) {\n\t\t\tsv.push_back(m.at(type));\n\t\t} else {\n\t\t\tstd::string name = _(\"Unknown type\");\n\t\t\tif (!type.empty()) {\n\t\t\t\tname = Glib::ustring::compose(_(\"Unknown type: %1\"), type);\n\t\t\t}\n\t\t\tsv.push_back(name);\n\t\t}\n\t}\n\n\treturn hz::string_join(sv, _(\", \"));\n}\n\n\n\nWarningLevel AtaStorageErrorBlock::get_warning_level_for_error_type(const std::string& type)\n{\n\tstatic const std::map<std::string, WarningLevel> m = {\n\t\t{\"ABRT\", WarningLevel::None},\n\t\t{\"AMNF\", WarningLevel::Alert},\n\t\t{\"CCTO\", WarningLevel::Warning},\n\t\t{\"EOM\", WarningLevel::Warning},\n\t\t{\"ICRC\", WarningLevel::Warning},\n\t\t{\"IDNF\", WarningLevel::Alert},\n\t\t{\"ILI\", WarningLevel::Notice},\n\t\t{\"MC\", WarningLevel::None},\n\t\t{\"MCR\", WarningLevel::None},\n\t\t{\"NM\", WarningLevel::None},\n\t\t{\"obs\", WarningLevel::None},\n\t\t{\"TK0NF\", WarningLevel::Alert},\n\t\t{\"UNC\", WarningLevel::Alert},\n\t\t{\"WP\", WarningLevel::None},\n\t};\n\n\tif (m.find(type) != m.end()) {\n\t\treturn m.at(type);\n\t}\n\treturn WarningLevel::None;  // unknown error\n}\n\n\n\nstd::ostream& operator<< (std::ostream& os, const AtaStorageErrorBlock& b)\n{\n\tos << \"Error number \" << b.error_num << \": \"\n\t\t<< hz::string_join(b.reported_types, \", \")\n\t\t<< \" [\" << AtaStorageErrorBlock::format_readable_error_types(b.reported_types) << \"]\";\n\treturn os;\n}\n\n\n\nstd::string AtaStorageSelftestEntry::get_readable_status_name(Status s)\n{\n\tstatic const std::unordered_map<Status, std::string> m {\n\t\t\t{Status::Unknown,                \"[unknown]\"},\n\t\t\t{Status::CompletedNoError,       \"Completed without error\"},\n\t\t\t{Status::AbortedByHost,          \"Manually aborted\"},\n\t\t\t{Status::Interrupted,            \"Interrupted (host reset)\"},\n\t\t\t{Status::FatalOrUnknown,         \"Fatal or unknown error\"},\n\t\t\t{Status::ComplUnknownFailure,    \"Completed with unknown failure\"},\n\t\t\t{Status::ComplElectricalFailure, \"Completed with electrical failure\"},\n\t\t\t{Status::ComplServoFailure,      \"Completed with servo/seek failure\"},\n\t\t\t{Status::ComplReadFailure,       \"Completed with read failure\"},\n\t\t\t{Status::ComplHandlingDamage,    \"Completed: handling damage\"},\n\t\t\t{Status::InProgress,             \"In progress\"},\n\t\t\t{Status::Reserved,               \"Unknown / reserved state\"},\n\t};\n\tif (auto iter = m.find(s); iter != m.end()) {\n\t\treturn iter->second;\n\t}\n\treturn \"[internal_error]\";\n}\n\n\n\nstd::string AtaStorageSelftestEntry::get_readable_status() const\n{\n\treturn (status == Status::Unknown ? status_str : get_readable_status_name(status));\n}\n\n\n\nstd::ostream& operator<< (std::ostream& os, const AtaStorageSelftestEntry& b)\n{\n\tos << \"Test entry \" << b.test_num << \": \"\n\t\t<< b.type << \", status: \" << b.get_readable_status() << \", remaining: \" << int(b.remaining_percent);\n\treturn os;\n}\n\n\n\nstd::ostream& operator<<(std::ostream& os, const NvmeStorageSelftestEntry& b)\n{\n\treturn os << \"Test entry \" << b.test_num << \": \"\n\t\t<< NvmeSelfTestTypeExt::get_storable_name(b.type)\n\t\t<< \", result: \" << NvmeSelfTestResultTypeExt::get_storable_name(b.result)\n\t\t<< \", power on hours: \" << int(b.power_on_hours)\n\t\t<< \", lba: \" << int(b.lba.value_or(0));\n}\n\n\n\nstd::string StorageProperty::get_storable_value_type_name() const\n{\n\tif (std::holds_alternative<std::monostate>(value))\n\t\treturn \"empty\";\n\tif (std::holds_alternative<std::string>(value))\n\t\treturn \"string\";\n\tif (std::holds_alternative<std::int64_t>(value))\n\t\treturn \"integer\";\n\tif (std::holds_alternative<bool>(value))\n\t\treturn \"bool\";\n\tif (std::holds_alternative<std::chrono::seconds>(value))\n\t\treturn \"time_length\";\n\tif (std::holds_alternative<AtaStorageTextCapability>(value))\n\t\treturn \"capability\";\n\tif (std::holds_alternative<AtaStorageAttribute>(value))\n\t\treturn \"attribute\";\n\tif (std::holds_alternative<AtaStorageStatistic>(value))\n\t\treturn \"statistic\";\n\tif (std::holds_alternative<AtaStorageErrorBlock>(value))\n\t\treturn \"error_block\";\n\tif (std::holds_alternative<AtaStorageSelftestEntry>(value))\n\t\treturn \"ata_selftest_entry\";\n\tif (std::holds_alternative<NvmeStorageSelftestEntry>(value))\n\t\treturn \"nvme_selftest_entry\";\n\treturn \"[internal_error]\";\n}\n\n\n\nbool StorageProperty::empty() const\n{\n\treturn std::holds_alternative<std::monostate>(value);\n}\n\n\n\nvoid StorageProperty::dump(std::ostream& os, std::size_t internal_offset) const\n{\n\tconst std::string offset(internal_offset, ' ');\n\n\tos << offset << \"[\" << StoragePropertySectionExt::get_storable_name(section) << \"]\"\n\t\t\t<< \" \" << generic_name\n\t\t\t// << (generic_name == reported_name ? \"\" : (\" (\" + reported_name + \")\"))\n\t\t\t<< \": [\" << get_storable_value_type_name() << \"] \";\n\n\t// if (!readable_value.empty())\n\t// \tos << readable_value;\n\n\tif (std::holds_alternative<std::monostate>(value)) {\n\t\tos << \"[empty]\";\n\t} else if (std::holds_alternative<std::string>(value)) {\n\t\tos << std::get<std::string>(value);\n\t} else if (std::holds_alternative<std::int64_t>(value)) {\n\t\tos << std::get<std::int64_t>(value) << \" [\" << reported_value << \"]\";\n\t} else if (std::holds_alternative<bool>(value)) {\n\t\tos << std::string(std::get<bool>(value) ? \"Yes\" : \"No\") << \" [\" << reported_value << \"]\";\n\t} else if (std::holds_alternative<std::chrono::seconds>(value)) {\n\t\tos << std::get<std::chrono::seconds>(value).count() << \" sec [\" << reported_value << \"]\";\n\t} else if (std::holds_alternative<AtaStorageTextCapability>(value)) {\n\t\tos << std::get<AtaStorageTextCapability>(value);\n\t} else if (std::holds_alternative<AtaStorageAttribute>(value)) {\n\t\tos << std::get<AtaStorageAttribute>(value);\n\t} else if (std::holds_alternative<AtaStorageStatistic>(value)) {\n\t\tos << std::get<AtaStorageStatistic>(value);\n\t} else if (std::holds_alternative<AtaStorageErrorBlock>(value)) {\n\t\tos << std::get<AtaStorageErrorBlock>(value);\n\t} else if (std::holds_alternative<AtaStorageSelftestEntry>(value)) {\n\t\tos << std::get<AtaStorageSelftestEntry>(value);\n\t} else if (std::holds_alternative<NvmeStorageSelftestEntry>(value)) {\n\t\tos << std::get<NvmeStorageSelftestEntry>(value);\n\t}\n}\n\n\n\nstd::string StorageProperty::format_value(bool add_reported_too) const\n{\n\tif (!readable_value.empty())\n\t\treturn readable_value;\n\n\tif (std::holds_alternative<std::monostate>(value))\n\t\treturn \"[unknown]\";\n\tif (std::holds_alternative<std::string>(value))\n\t\treturn std::get<std::string>(value);\n\tif (std::holds_alternative<int64_t>(value))\n\t\treturn hz::number_to_string_locale(std::get<int64_t>(value)) + (add_reported_too ? (\" [\" + reported_value + \"]\") : \"\");\n\tif (std::holds_alternative<bool>(value))\n\t\treturn std::string(std::get<bool>(value) ? \"Yes\" : \"No\") + (add_reported_too ? (\" [\" + reported_value + \"]\") : \"\");\n\tif (std::holds_alternative<std::chrono::seconds>(value))\n\t\treturn hz::format_time_length(std::get<std::chrono::seconds>(value)) + (add_reported_too ? (\" [\" + reported_value + \"]\") : \"\");\n\tif (std::holds_alternative<AtaStorageTextCapability>(value))\n\t\treturn hz::stream_cast<std::string>(std::get<AtaStorageTextCapability>(value));\n\tif (std::holds_alternative<AtaStorageAttribute>(value))\n\t\treturn hz::stream_cast<std::string>(std::get<AtaStorageAttribute>(value));\n\tif (std::holds_alternative<AtaStorageStatistic>(value))\n\t\treturn hz::stream_cast<std::string>(std::get<AtaStorageStatistic>(value));\n\tif (std::holds_alternative<AtaStorageErrorBlock>(value))\n\t\treturn hz::stream_cast<std::string>(std::get<AtaStorageErrorBlock>(value));\n\tif (std::holds_alternative<AtaStorageSelftestEntry>(value))\n\t\treturn hz::stream_cast<std::string>(std::get<AtaStorageSelftestEntry>(value));\n\tif (std::holds_alternative<NvmeStorageSelftestEntry>(value))\n\t\treturn hz::stream_cast<std::string>(std::get<NvmeStorageSelftestEntry>(value));\n\n\treturn \"[internal_error]\";\n}\n\n\n\nstd::string StorageProperty::get_description(bool clean) const\n{\n\tif (clean)\n\t\treturn this->description;\n\treturn (this->description.empty() ? \"No description available\" : this->description);\n}\n\n\n\nvoid StorageProperty::set_description(const std::string& descr)\n{\n\tthis->description = descr;\n}\n\n\n\nvoid StorageProperty::set_name(const std::string& gen_name, const std::string& disp_name, const std::string& rep_name)\n{\n//\tthis->generic_name = (gen_name.empty() ? this->reported_name : gen_name);\n//\tthis->displayable_name = (read_name.empty() ? this->reported_name : read_name);\n\tthis->generic_name = gen_name;\n\tthis->displayable_name = disp_name;\n\tthis->reported_name = rep_name;\n}\n\n\n\nstd::ostream& operator<<(std::ostream& os, const StorageProperty& p)\n{\n\tp.dump(os);\n\treturn os;\n}\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_property.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef STORAGE_PROPERTY_H\n#define STORAGE_PROPERTY_H\n\n#include <glibmm.h>\n#include <glibmm/i18n.h>\n\n#include <cstddef>  // std::size_t\n#include <string>\n#include <unordered_map>\n#include <utility>\n#include <vector>\n#include <iosfwd>\n#include <cstdint>\n#include <optional>\n#include <chrono>\n#include <variant>\n\n#include \"warning_level.h\"\n#include \"hz/enum_helper.h\"\n\n\n\n/// Holds one block of \"capabilities\" subsection (only for non-time-interval blocks).\n/// ATA Text parser only.\nclass AtaStorageTextCapability {\n\tpublic:\n\t\tstd::string reported_flag_value;  ///< original flag value as a string\n\t\tuint16_t flag_value = 0x0;  ///< Flag value. This is one or sometimes two bytes (maybe more?)\n\t\tstd::string reported_strvalue;  ///< Original flag descriptions\n\t\tstd::vector<std::string> strvalues;  ///< A list of capabilities in the block.\n};\n\n\n/// Output operator for debug purposes\nstd::ostream& operator<< (std::ostream& os, const AtaStorageTextCapability& p);\n\n\n\n\n\n/// Holds one line of \"attributes\" subsection.\n/// ATA only.\nclass AtaStorageAttribute {\n\tpublic:\n\n\t\t/// Disk type the attribute may match\n//\t\tenum class DiskType {\n//\t\t\tAny,  ///< Any disk type\n//\t\t\tHdd,  ///< HDD (rotational) only\n//\t\t\tSsd  ///< SSD only\n//\t\t};\n\n\t\t/// Attribute pre-failure / old-age type\n\t\tenum class AttributeType {\n\t\t\tUnknown,  ///< Unknown\n\t\t\tPrefail,  ///< Pre-failure (reported: Pre-fail)\n\t\t\tOldAge  ///< Old age (reported: Old_age)\n\t\t};\n\n\t\t/// Get readable attribute type name\n\t\t[[nodiscard]] static std::string get_readable_attribute_type_name(AttributeType type);\n\n\n\t\t/// Attribute when-updated type\n\t\tenum class UpdateType {\n\t\t\tUnknown,  ///< Unknown\n\t\t\tAlways,  ///< Continuously (reported: Always)\n\t\t\tOffline  ///< Only during offline data collection (reported: Offline)\n\t\t};\n\n\t\t/// Get readable when-updated type name\n\t\t[[nodiscard]] static std::string get_readable_update_type_name(UpdateType type);\n\n\n\t\t/// Attribute when-failed type\n\t\tenum class FailTime {\n\t\t\tUnknown,  ///< Unknown\n\t\t\tNone,  ///< Never (reported: -)\n\t\t\tPast,  ///< In the past (reported: In_the_past)\n\t\t\tNow  ///< Now (reported: FAILING_NOW)\n\t\t};\n\n\t\t/// Get a readable when-failed type name\n\t\t[[nodiscard]] static std::string get_readable_fail_time_name(FailTime type);\n\n\n\t\t/// Format raw value with commas (if it's a number)\n\t\t[[nodiscard]] std::string format_raw_value() const;\n\n\n\t\tstd::int32_t id = -1;  ///< Attribute ID (most vendors agree on this)\n\t\tstd::string flag;  ///< \"Old\" format is \"0xXXXX\", \"brief\" format is \"PO--C-\".\n\t\tstd::optional<uint8_t> value;  ///< Normalized value. May be unset (\"---\").\n\t\tstd::optional<uint8_t> worst;  ///< Worst ever value. May be unset (\"---\").\n\t\tstd::optional<uint8_t> threshold;  ///< Threshold for normalized value. May be unset (\"---\").\n\t\tAttributeType attr_type = AttributeType::Unknown;  ///< Attribute pre-fail / old-age type\n\t\tUpdateType update_type = UpdateType::Unknown;  ///< When-updated type\n\t\tFailTime when_failed = FailTime::Unknown;  ///< When-failed type\n\t\tstd::string raw_value;  ///< Raw value as a string, as presented by smartctl (formatted).\n\t\tstd::int64_t raw_value_int = 0;  ///< Same as raw_value, but parsed as int64. original value is 6 bytes I think.\n\n};\n\n\n/// Output operator for debug purposes\nstd::ostream& operator<< (std::ostream& os, const AtaStorageAttribute& p);\n\n\n\n\n/// Holds one line of \"devstat\" subsection.\n/// ATA only.\nclass AtaStorageStatistic {\n\tpublic:\n\n\t\t/// Whether the normalization flag is present\n\t\t[[nodiscard]] bool is_normalized() const;\n\n\t\t/// Format value with commas (if it's a number)\n\t\t[[nodiscard]] std::string format_value() const;\n\n\t\tbool is_header = false;  ///< If the line is a header\n\t\tstd::string flags;  ///< Flags in \"NDC\" / \"---\" format\n\t\tstd::string value;  ///< Value as a string, as presented by smartctl (formatted).\n\t\tstd::int64_t value_int = 0;  ///< Same as value, but parsed as int64.\n\t\tstd::int64_t page = 0;  ///< Page\n\t\tstd::int64_t offset = 0;  ///< Offset in page\n};\n\n\n/// Output operator for debug purposes\nstd::ostream& operator<< (std::ostream& os, const AtaStorageStatistic& p);\n\n\n\n/// Holds one error block of \"error log\" subsection.\n/// ATA only.\nclass AtaStorageErrorBlock {\n\tpublic:\n\n\t\t/// Get readable error types from reported types\n\t\t[[nodiscard]] static std::string format_readable_error_types(const std::vector<std::string>& types);\n\n\t\t/// Get warning level (Warning) for an error type\n\t\t[[nodiscard]] static WarningLevel get_warning_level_for_error_type(const std::string& type);\n\n\n\t\tstd::uint32_t error_num = 0;  ///< Error number\n\t\tstd::uint64_t log_index = 0;  ///< Log index\n\t\tstd::uint32_t lifetime_hours = 0;  ///< When the error occurred (in lifetime hours)\n\t\tstd::string device_state;  ///< Device state during the error - \"active or idle\", standby, etc.\n\t\tstd::vector<std::string> reported_types;  ///< Array of reported types (strings), e.g. \"UNC\".\n\t\tstd::string type_more_info;  ///< More info on error type (e.g. \"at LBA = 0x0253eac0 = 39054016\")\n\t\tstd::uint64_t lba = 0;  ///< LBA of the error\n};\n\n\n/// Output operator for debug purposes\nstd::ostream& operator<< (std::ostream& os, const AtaStorageErrorBlock& b);\n\n\n\n\n/// Holds one entry of selftest_log subsection.\n/// Also, holds \"Self-test execution status\" capability's \"internal\" section version.\n/// ATA only.\nclass AtaStorageSelftestEntry {\n\tpublic:\n\n\t\t/// Self-test log entry status.\n\t\tenum class Status {\n\t\t\tUnknown = -1,  ///< Initial state\n\t\t\tReserved = -2,  ///< Reserved\n\t\t\t// Values correspond to (ata_smart_self_test_log/extended/table/status/value >> 4).\n\t\t\tCompletedNoError = 0x0,  ///< Completed with no error, or no test was run\n\t\t\tAbortedByHost = 0x1,  ///< Aborted by host\n\t\t\tInterrupted = 0x2,  ///< Interrupted by user\n\t\t\tFatalOrUnknown = 0x3,  ///< Fatal or unknown error. Treated as test failure.\n\t\t\tComplUnknownFailure = 0x4,  ///< Completed with unknown error. Treated as test failure.\n\t\t\tComplElectricalFailure = 0x5,  ///< Completed with electrical error. Treated as test failure.\n\t\t\tComplServoFailure = 0x6,  ///< Completed with servo error. Treated as test failure.\n\t\t\tComplReadFailure = 0x7,  ///< Completed with read error. Treated as test failure.\n\t\t\tComplHandlingDamage = 0x8,  ///< Completed with handling damage error. Treated as test failure.\n\t\t\tInProgress = 0xf,  ///< Test in progress\n\t\t};\n\n\t\t/// Get log entry status displayable name\n\t\t[[nodiscard]] static std::string get_readable_status_name(Status s);\n\n\t\t/// Get error status as a string\n\t\t[[nodiscard]] std::string get_readable_status() const;\n\n\n\t\tstd::uint32_t test_num = 0;  ///< Test number. always starts from 1. larger means older or newer, depending on model. 0 for capability.\n\t\tstd::string type;  ///< Extended offline, Short offline, Conveyance offline, etc. . capability: unused.\n\t\tstd::string status_str;  ///< Self-test routine in progress, Completed without error, etc. (as reported by log or capability)\n\t\tStatus status = Status::Unknown;  ///< same as status_str, but from enum\n\t\tstd::int8_t remaining_percent = -1;  ///< Remaining %. 0% for completed, 90% for started. -1 if n/a.\n\t\tstd::uint32_t lifetime_hours = 0;  ///< When the test happened (in lifetime hours). capability: unused.\n\t\tstd::string lba_of_first_error;  ///< LBA of the first error. \"-\" or value (format? usually hex). capability: unused.\n\t\tbool passed = false;  ///< Test passed or not. capability: unused.\n};\n\n\n/// Output operator for debug purposes\nstd::ostream& operator<< (std::ostream& os, const AtaStorageSelftestEntry& b);\n\n\n\n\n/// Decoded of nvme_self_test_log/current_self_test_operation/value\nenum class NvmeSelfTestCurrentOperationType {\n\tUnknown = -1,\n\tNone = 0x0,\n\tShortInProgress = 0x1,\n\tExtendedInProgress = 0x2,\n\tVendorSpecificInProgress = 0xe,\n};\n\n\n\n/// Helper structure for enum-related functions\nstruct NvmeSelfTestCurrentOperationTypeExt\n\t\t: public hz::EnumHelper<\n\t\t\t\tNvmeSelfTestCurrentOperationType,\n\t\t\t\tNvmeSelfTestCurrentOperationTypeExt,\n\t\t\t\tGlib::ustring>\n{\n\tstatic constexpr NvmeSelfTestCurrentOperationType default_value = NvmeSelfTestCurrentOperationType::Unknown;\n\n\tstatic std::unordered_map<EnumType, std::pair<std::string, Glib::ustring>> build_enum_map()\n\t{\n\t\treturn {\n\t\t\t{NvmeSelfTestCurrentOperationType::Unknown, {\"unknown\", _(\"Unknown\")}},\n\t\t\t{NvmeSelfTestCurrentOperationType::None, {\"none\", _(\"None\")}},\n\t\t\t{NvmeSelfTestCurrentOperationType::ShortInProgress, {\"shortInProgress\", _(\"Short Test in Progress\")}},\n\t\t\t{NvmeSelfTestCurrentOperationType::ExtendedInProgress, {\"extendedInProgress\", _(\"Extended Test in Progress\")}},\n\t\t\t{NvmeSelfTestCurrentOperationType::VendorSpecificInProgress, {\"vendorSpecificInProgress\", _(\"Vendor-Specific Test in Progress\")}},\n\t\t};\n\t}\n};\n\n\n\n/// Self-test types in log\nenum class NvmeSelfTestType {\n\tUnknown = -1,\n\tShort = 0x1,\n\tExtended = 0x2,\n\tVendorSpecific = 0xe,  ///< Can be encountered in log\n};\n\n\n\n/// Helper structure for enum-related functions\nstruct NvmeSelfTestTypeExt\n\t\t: public hz::EnumHelper<\n\t\t\t\tNvmeSelfTestType,\n\t\t\t\tNvmeSelfTestTypeExt,\n\t\t\t\tGlib::ustring>\n{\n\tstatic constexpr NvmeSelfTestType default_value = NvmeSelfTestType::Unknown;\n\n\tstatic std::unordered_map<EnumType, std::pair<std::string, Glib::ustring>> build_enum_map()\n\t{\n\t\treturn {\n\t\t\t{NvmeSelfTestType::Unknown, {\"unknown\", _(\"Unknown\")}},\n\t\t\t{NvmeSelfTestType::Short, {\"short\", _(\"Short Test\")}},\n\t\t\t{NvmeSelfTestType::Extended, {\"extended\", _(\"Extended Test\")}},\n\t\t\t{NvmeSelfTestType::VendorSpecific, {\"vendorSpecific\", _(\"Vendor-Specific Test\")}},\n\t\t};\n\t}\n};\n\n\n\n/// Self-test log entry status.\nenum class NvmeSelfTestResultType {\n\tUnknown = -1,  ///< Unknown result\n\t// Values correspond to \"nvme_self_test_log/table/self_test_result/value\".\n\tCompletedNoError = 0x0,  ///< Completed with no error\n\tAbortedSelfTestCommand = 0x1,  ///< Aborted: Self-test command (manually aborted)\n\tAbortedControllerReset = 0x2,  ///< Aborted: Controller Reset\n\tAbortedNamespaceRemoved = 0x3,  ///< Aborted: Namespace removed\n\tAbortedFormatNvmCommand = 0x4,  ///< Aborted: Format NVM command\n\tFatalOrUnknownTestError = 0x5,  ///< Fatal or unknown test error\n\tCompletedUnknownFailedSegment = 0x6,  ///< Completed: unknown failed segment\n\tCompletedFailedSegments = 0x7,  ///< Completed: failed segments\n\tAbortedUnknownReason = 0x8,  ///< Aborted: unknown reason\n\tAbortedSanitizeOperation = 0x9,  ///< Aborted: sanitize operation\n};\n\n\n\n/// Helper structure for enum-related functions\nstruct NvmeSelfTestResultTypeExt\n\t\t: public hz::EnumHelper<\n\t\t\t\tNvmeSelfTestResultType,\n\t\t\t\tNvmeSelfTestResultTypeExt,\n\t\t\t\tGlib::ustring>\n{\n\tstatic constexpr NvmeSelfTestResultType default_value = NvmeSelfTestResultType::Unknown;\n\n\tstatic std::unordered_map<EnumType, std::pair<std::string, Glib::ustring>> build_enum_map()\n\t{\n\t\treturn {\n\t\t\t{NvmeSelfTestResultType::Unknown, {\"unknown\", _(\"Unknown\")}},\n\t\t\t{NvmeSelfTestResultType::CompletedNoError, {\"completedNoError\", _(\"Completed Without Errors\")}},\n\t\t\t{NvmeSelfTestResultType::AbortedSelfTestCommand, {\"abortedSelfTestCommand\", _(\"Aborted: Self-Test Command\")}},\n\t\t\t{NvmeSelfTestResultType::AbortedControllerReset, {\"abortedControllerReset\", _(\"Aborted: Controller Reset\")}},\n\t\t\t{NvmeSelfTestResultType::AbortedNamespaceRemoved, {\"abortedNamespaceRemoved\", _(\"Aborted: Namespace Removed\")}},\n\t\t\t{NvmeSelfTestResultType::AbortedFormatNvmCommand, {\"abortedFormatNvmCommand\", _(\"Aborted: Format NVM Command\")}},\n\t\t\t{NvmeSelfTestResultType::FatalOrUnknownTestError, {\"fatalOrUnknownTestError\", _(\"Fatal or Unknown Test Error\")}},\n\t\t\t{NvmeSelfTestResultType::CompletedUnknownFailedSegment, {\"completedUnknownFailedSegment\", _(\"Completed: Unknown Failed Segment\")}},\n\t\t\t{NvmeSelfTestResultType::CompletedFailedSegments, {\"completedFailedSegments\", _(\"Completed: Failed Segments\")}},\n\t\t\t{NvmeSelfTestResultType::AbortedUnknownReason, {\"abortedUnknownReason\", _(\"Aborted: Unknown Reason\")}},\n\t\t\t{NvmeSelfTestResultType::AbortedSanitizeOperation, {\"abortedSanitizeOperation\", _(\"Aborted: Sanitize Operation\")}},\n\t\t};\n\t}\n};\n\n\n\n/// Holds one entry of nvme_self_test_log section.\n/// NVMe only.\nclass NvmeStorageSelftestEntry {\n\tpublic:\n\n\t\t/// Self-test error severity\n\t\tenum class StatusSeverity {\n\t\t\tNone,\n\t\t\tWarning,\n\t\t\tError\n\t\t};\n\n\t\tstd::uint32_t test_num = 0;  ///< Test number, auto-generated\n\t\tNvmeSelfTestType type = NvmeSelfTestType::Unknown;  ///< Test type\n\t\tNvmeSelfTestResultType result = NvmeSelfTestResultType::Unknown;  ///< Test result\n\t\tstd::uint32_t power_on_hours = 0;  ///< When the test happened (in power-on hours).\n\t\tstd::optional<std::uint64_t> lba;  ///< LBA of the first error.\n};\n\n\n/// Output operator for debug purposes\nstd::ostream& operator<< (std::ostream& os, const NvmeStorageSelftestEntry& b);\n\n\n\n/// Sections in output\nenum class StoragePropertySection {\n\tUnknown,  ///< Used when searching in all sections\n\tInfo,  ///< Short info (--info)\n\tOverallHealth,  ///< Overall-health (-H, --health)\n\tCapabilities,  ///< General SMART Values, aka Capabilities (-c, --capabilities)\n\tAtaAttributes,  ///< Attributes (-A, --attributes). These need decoding.\n\tStatistics,  ///< Device statistics (--log=devstat). These need decoding.\n\tAtaErrorLog,  ///< Error Log (--log=error)\n\tSelftestLog,  ///< Self-test log (--log=selftest)\n\tSelectiveSelftestLog,  ///< Selective self-test log (--log=selective)\n\tTemperatureLog,  ///< SCT temperature (current and history) (--log=scttemp)\n\tErcLog,  ///< SCT Error Recovery Control settings (--log=scterc)\n\tPhyLog,  ///< Phy log (--log=sataphy)\n\tDirectoryLog,  ///< Directory log (--log=directory)\n\tNvmeHealth,  ///< NVMe health (-H, --health)\n\tNvmeAttributes,  ///< NVMe attributes (health log) (-A, --attributes)\n\tNvmeErrorLog,  ///< NVMe error log (--log=error)\n};\n\n\n\n/// Helper structure for enum-related functions\nstruct StoragePropertySectionExt\n\t\t: public hz::EnumHelper<\n\t\t\t\tStoragePropertySection,\n\t\t\t\tStoragePropertySectionExt,\n\t\t\t\tstd::string>\n{\n\tstatic constexpr StoragePropertySection default_value = StoragePropertySection::Unknown;\n\n\tstatic std::unordered_map<EnumType, std::pair<std::string, std::string>> build_enum_map()\n\t{\n\t\treturn {\n\t\t\t{StoragePropertySection::Unknown,              {\"unknown\",              _(\"Unknown\")}},\n\t\t\t{StoragePropertySection::Info,                 {\"info\",                 _(\"Short Info\")}},\n\t\t\t{StoragePropertySection::OverallHealth,        {\"overallHealth\",        _(\"Overall Health\")}},\n\t\t\t{StoragePropertySection::Capabilities,         {\"capabilities\",         _(\"Capabilities\")}},\n\t\t\t{StoragePropertySection::AtaAttributes,        {\"attributes\",           _(\"Attributes\")}},\n\t\t\t{StoragePropertySection::Statistics,           {\"statistics\",           _(\"Statistics\")}},\n\t\t\t{StoragePropertySection::AtaErrorLog,          {\"errorLog\",             _(\"Error Log\")}},\n\t\t\t{StoragePropertySection::SelftestLog,          {\"selftestLog\",          _(\"Self-test Log\")}},\n\t\t\t{StoragePropertySection::SelectiveSelftestLog, {\"selectiveSelftestLog\", _(\"Selective Self-test Log\")}},\n\t\t\t{StoragePropertySection::TemperatureLog,       {\"temperatureLog\",       _(\"Temperature Log\")}},\n\t\t\t{StoragePropertySection::ErcLog,               {\"ercLog\",               _(\"Error Recovery Control Log\")}},\n\t\t\t{StoragePropertySection::PhyLog,               {\"phyLog\",               _(\"Phy Log\")}},\n\t\t\t{StoragePropertySection::DirectoryLog,         {\"directoryLog\",         _(\"Directory Log\")}},\n\t\t\t{StoragePropertySection::NvmeHealth,           {\"nvmeHealth\",           _(\"NVMe Health\")}},\n\t\t\t{StoragePropertySection::NvmeAttributes,       {\"nvmeAttributes\",       _(\"NVMe Attributes\")}},\n\t\t\t{StoragePropertySection::NvmeErrorLog,         {\"nvmeErrorLog\",         _(\"NVMe Error Log\")}},\n\t\t};\n\t}\n};\n\n\n\n\n\n/// A single parser-extracted property\nclass StorageProperty {\n\tpublic:\n\n\t\tusing ValueVariantType = std::variant<\n\t\t\tstd::monostate,  ///< None\n\t\t\tstd::string,  ///< Value (if it's a string)\n\t\t\tstd::int64_t,   ///< Value (if it's an integer)\n\t\t\tbool,  ///< Value (if it's bool)\n\t\t\tstd::chrono::seconds,  ///< Value in seconds (if it's time interval)\n\t\t\tAtaStorageTextCapability,  ///< Value (if it's a capability)\n\t\t\tAtaStorageAttribute,  ///< Value (if it's an attribute)\n\t\t\tAtaStorageStatistic,  ///< Value (if it's a statistic from devstat)\n\t\t\tAtaStorageErrorBlock,  ///< Value (if it's a error block)\n\t\t\tAtaStorageSelftestEntry,  ///< Value (if it's ATA self-test log entry)\n\t\t\tNvmeStorageSelftestEntry  ///< Value (if it's NVMe self-test log entry)\n\t\t>;\n\n\n\t\t/// Constructor\n\t\tStorageProperty() = default;\n\n\t\t/// Constructor\n\t\tStorageProperty(StoragePropertySection section_, ValueVariantType value_)\n\t\t\t\t: section(section_), value(std::move(value_))\n\t\t{ }\n\n\n\t\t/// Get displayable value type name\n\t\t[[nodiscard]] std::string get_storable_value_type_name() const;\n\n\n\t\t/// Check if this is an empty object with no value set.\n\t\t[[nodiscard]] bool empty() const;\n\n\n\t\t/// Dump the property to a stream for debugging purposes\n\t\tvoid dump(std::ostream& os, std::size_t internal_offset = 0) const;\n\n\n\t\t/// Format this property for debugging purposes\n\t\t[[nodiscard]] std::string format_value(bool add_reported_too = false) const;\n\n\n\t\t/// Get value of type T\n\t\ttemplate<typename T>\n\t\t[[nodiscard]] const T& get_value() const;\n\n\n\t\t/// Check if value is of type T\n\t\ttemplate<typename T>\n\t\t[[nodiscard]] bool is_value_type() const;\n\n\n\t\t/// Get property description (used in tooltips)\n\t\t[[nodiscard]] std::string get_description(bool clean = false) const;\n\n\n\t\t/// Set property description (used in tooltips)\n\t\tvoid set_description(const std::string& descr);\n\n\n\t\t/// Set generic (internal) name, readable name, and smartctl-reported name (optional)\n\t\tvoid set_name(const std::string& gen_name, const std::string& disp_name, const std::string& rep_name = \"\");\n\n\n\t\tstd::string generic_name;  ///< Generic (internal) name. May be the same as reported_name, or something more program-identifiable.\n\t\tstd::string displayable_name;  ///< Readable property name. May be the same as reported_name, or something more user-readable. Possibly translatable.\n\t\tstd::string reported_name;  ///< Property name as reported by smartctl. Mainly used by Text parser.\n\n\t\tstd::string description;  ///< Property description (for tooltips, etc.). May contain markup.\n\n\t\tStoragePropertySection section = StoragePropertySection::Unknown;  ///< Section this property belongs to\n\n\t\tstd::string reported_value;  ///< String representation of the value as reported\n\t\tstd::string readable_value;  ///< User-friendly readable representation of value. if empty, use the other members.\n\n\t\tValueVariantType value;  ///< Stored value\n\n\t\tWarningLevel warning_level = WarningLevel::None;  ///< Warning severity for this property\n\t\tstd::string warning_reason;  // Warning reason (displayable)\n\n\t\tbool show_in_ui = true;  ///< Whether to show this property in UI or not\n\n};\n\n\n\n\n/// Output operator for debug purposes\nstd::ostream& operator<< (std::ostream& os, const StorageProperty& p);\n\n\n\n\n\n// ------------------------------------------- Implementation\n\n\n\ntemplate<typename T>\nconst T& StorageProperty::get_value() const\n{\n\treturn std::get<T>(value);\n}\n\n\n\ntemplate<typename T>\nbool StorageProperty::is_value_type() const\n{\n\treturn std::holds_alternative<T>(value);\n}\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_property_descr.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n//#include <glibmm.h>\n#include <utility>\n//#include <vector>\n//#include <map>\n//#include <unordered_map>\n\n#include \"hz/string_algo.h\"  // string_replace_copy\n#include \"applib/app_regex.h\"\n\n#include \"storage_property_descr.h\"\n#include \"warning_colors.h\"\n#include \"storage_property_descr_ata_attribute.h\"\n#include \"storage_property_descr_ata_statistic.h\"\n#include \"storage_property_descr_nvme_attribute.h\"\n\n\nnamespace {\n\n\n\t/// Check if a property matches a name (generic or reported)\n\tinline bool name_match(StorageProperty& p, const std::string& name)\n\t{\n\t\tif (p.generic_name.empty()) {\n\t\t\treturn hz::string_to_lower_copy(p.reported_name) == hz::string_to_lower_copy(name);\n\t\t}\n\t\treturn hz::string_to_lower_copy(p.generic_name) == hz::string_to_lower_copy(name);\n\t}\n\n\n\t/// Check if a property matches a name (generic or reported) and if it does,\n\t/// set a description on it.\n\tinline bool auto_set(StorageProperty& p, const std::string& name, const char* descr)\n\t{\n\t\tif (name_match(p, name)) {\n\t\t\tp.set_description(descr);\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t}\n\n}\n\n\n\nbool storage_property_autoset_description(StorageProperty& p, StorageDeviceDetectedType device_type)\n{\n\tbool found = false;\n\n\n\t// checksum errors first\n\tif (p.generic_name.find(\"_text_only/_checksum_error\") != std::string::npos) {\n\t\tp.set_description(\"Checksum errors indicate that SMART data is invalid. This shouldn't happen in normal circumstances.\");\n\t\tfound = true;\n\n\t// Section Info\n\t} else {\n\t\tswitch (p.section) {\n\t\t\tcase StoragePropertySection::Info:\n\t\t\t\tfound = auto_set(p, \"model_family\", \"Model family (from smartctl database)\")\n\t\t\t\t|| auto_set(p, \"model_name\", \"Device model\")\n\t\t\t\t|| auto_set(p, \"serial_number\", \"Serial number, unique to each physical drive\")\n\t\t\t\t|| auto_set(p, \"user_capacity/bytes/_short\", \"User-serviceable drive capacity as reported to an operating system\")\n\t\t\t\t|| auto_set(p, \"user_capacity/bytes\", \"User-serviceable drive capacity as reported to an operating system\")\n\t\t\t\t|| auto_set(p, \"in_smartctl_database\", \"Whether the device is in smartctl database or not. \"\n\t\t\t\t\t\t\"If it is, additional information may be provided; otherwise, Raw values of some attributes may be incorrectly formatted.\")\n\t\t\t\t|| auto_set(p, \"smart_support/available\", \"Whether the device supports SMART. If not, then only very limited information will be available.\")\n\t\t\t\t|| auto_set(p, \"smart_support/enabled\", \"Whether the device has SMART enabled. If not, most of the reported values will be incorrect.\")\n\t\t\t\t|| auto_set(p, \"ata_aam/enabled\", \"Automatic Acoustic Management (AAM) feature\")\n\t\t\t\t|| auto_set(p, \"ata_aam/level\", \"Automatic Acoustic Management (AAM) level\")\n\t\t\t\t|| auto_set(p, \"ata_apm/enabled\", \"Automatic Power Management (APM) feature\")\n\t\t\t\t|| auto_set(p, \"ata_apm/level\", \"Advanced Power Management (APM) level\")\n\t\t\t\t|| auto_set(p, \"ata_dsn/enabled\", \"Device Statistics Notification (DSN) feature\")\n\t\t\t\t|| auto_set(p, \"power_mode\", \"Power mode at the time of query\");\n\n\t\t\t\t// set just its name as a tooltip\n\t\t\t\tif (!found) {\n\t\t\t\t\tp.set_description(p.displayable_name);\n\t\t\t\t\tfound = true;\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::OverallHealth:\n\t\t\t\tfound = auto_set(p, \"smart_status/passed\", \"Overall health self-assessment test result. Note: If the drive passes this test, it doesn't mean it's OK. \"\n\t\t\t\t\t\t\"However, if the drive doesn't pass it, then it's either already dead, or it's predicting its own failure within the next 24 hours. In this case do a backup immediately!\");\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::Capabilities:\n\t\t\t\tfound = auto_set(p, \"ata_smart_data/offline_data_collection/status/_group\", \"Offline Data Collection (a.k.a. Offline test) is usually automatically performed when the device is idle or every fixed amount of time. \"\n\t\t\t\t\t\t\"This should show if Automatic Offline Data Collection is enabled.\")\n\t\t\t\t|| auto_set(p, \"ata_smart_data/offline_data_collection/completion_seconds\", \"Offline Data Collection (a.k.a. Offline test) is usually automatically performed when the device is idle or every fixed amount of time. \"\n\t\t\t\t\t\t\"This value shows the estimated time required to perform this operation in idle conditions. A value of 0 means unsupported.\")\n\t\t\t\t|| auto_set(p, \"ata_smart_data/self_test/polling_minutes/short\", \"This value shows the estimated time required to perform a short self-test in idle conditions. A value of 0 means unsupported.\")\n\t\t\t\t|| auto_set(p, \"ata_smart_data/self_test/polling_minutes/extended\", \"This value shows the estimated time required to perform a long self-test in idle conditions. A value of 0 means unsupported.\")\n\t\t\t\t|| auto_set(p, \"ata_smart_data/self_test/polling_minutes/conveyance\", \"This value shows the estimated time required to perform a conveyance self-test in idle conditions. \"\n\t\t\t\t\t\t\"A value of 0 means unsupported.\")\n\t\t\t\t|| auto_set(p, \"ata_smart_data/self_test/status/_group\", \"Status of the last self-test run.\")\n\t\t\t\t|| auto_set(p, \"ata_smart_data/offline_data_collection/_group\", \"Drive properties related to Offline Data Collection and self-tests.\")\n\t\t\t\t|| auto_set(p, \"ata_smart_data/capabilities/_group\", \"Drive properties related to SMART handling.\")\n\t\t\t\t|| auto_set(p, \"ata_smart_data/capabilities/error_logging_supported/_group\", \"Drive properties related to error logging.\")\n\t\t\t\t|| auto_set(p, \"ata_sct_capabilities/_group\", \"Drive properties related to temperature information.\");\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::AtaAttributes:\n\t\t\t\tfound = auto_set(p, \"ata_smart_attributes/revision\", p.displayable_name.c_str());\n\t\t\t\tif (!found) {\n\t\t\t\t\tauto_set_ata_attribute_description(p, device_type);\n\t\t\t\t\tfound = true;  // true, because auto_set_attr() may set \"Unknown attribute\", which is still \"found\".\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::Statistics:\n\t\t\t\tfound = auto_set_ata_statistic_description(p);\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::AtaErrorLog:\n\t\t\t\tfound = auto_set(p, \"ata_smart_error_log/extended/revision\", p.displayable_name.c_str())\n\t\t\t\t|| auto_set(p, \"ata_smart_error_log/extended/count\", \"Number of errors in error log. Note: Some manufacturers may list completely harmless errors in this log \"\n\t\t\t\t\t\"(e.g., command invalid, not implemented, etc.).\");\n// \t\t\t\t|| auto_set(p, \"error_log_unsupported\", \"This device does not support error logging.\");  // the property text already says that\n\t\t\t\tif (p.is_value_type<AtaStorageErrorBlock>()) {\n\t\t\t\t\tif (!p.get_value<AtaStorageErrorBlock>().reported_types.empty()) {  // Text parser only\n\t\t\t\t\t\tp.set_description(AtaStorageErrorBlock::format_readable_error_types(\n\t\t\t\t\t\t\t\tp.get_value<AtaStorageErrorBlock>().reported_types));\n\t\t\t\t\t}\n\t\t\t\t\t/// TODO JSON parser\n\t\t\t\t\tfound = true;\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::SelftestLog:\n\t\t\t\tfound = auto_set(p, \"ata_smart_self_test_log/extended/revision\", p.displayable_name.c_str())\n\t\t\t\t|| auto_set(p, \"ata_smart_self_test_log/standard/revision\", p.displayable_name.c_str())\n\t\t\t\t|| auto_set(p, \"ata_smart_self_test_log/extended/count\", \"Number of tests in selftest log. Note: The number of entries may be limited to the newest manual tests.\")\n\t\t\t\t|| auto_set(p, \"ata_smart_self_test_log/standard/count\", \"Number of tests in selftest log. Note: The number of entries may be limited to the newest manual tests.\");\n\t\t// \t\t|| auto_set(p, \"ata_smart_self_test_log/_present\", \"This device does not support self-test logging.\");  // the property text already says that\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::SelectiveSelftestLog:\n\t\t\t\t// nothing here\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::TemperatureLog:\n\t\t\t\tfound = auto_set(p, \"_text_only/ata_sct_status/_not_present\", \"SCT support is needed for SCT temperature logging.\");\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::NvmeHealth:\n\t\t\tcase StoragePropertySection::NvmeErrorLog:\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::NvmeAttributes:\n\t\t\t\tfound = auto_set_nvme_attribute_description(p);\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::ErcLog:\n\t\t\tcase StoragePropertySection::PhyLog:\n\t\t\tcase StoragePropertySection::DirectoryLog:\n\t\t\tcase StoragePropertySection::Unknown:\n\t\t\t\t// nothing\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn found;\n}\n\n\n\n\nvoid storage_property_autoset_warning(StorageProperty& p)\n{\n\tstd::optional<WarningLevel> w;\n\tstd::string reason;\n\n\t// checksum errors first\n\tif (p.generic_name.find(\"_text_only/_checksum_error\") != std::string::npos) {\n\t\tw = WarningLevel::Warning;\n\t\treason = \"The drive may have a broken implementation of SMART, or it's failing.\";\n\n\n\t// Section Info\n\t} else {\n\t\tswitch (p.section) {\n\t\t\tcase StoragePropertySection::Info:\n\t\t\t\tif (name_match(p, \"smart_support/available\") && !p.get_value<bool>()) {\n\t\t\t\t\tw = WarningLevel::Notice;\n\t\t\t\t\treason = \"SMART is not supported. You won't be able to read any SMART information from this drive.\";\n\n\t\t\t\t} else if (name_match(p, \"smart_support/enabled\") && !p.get_value<bool>()) {\n\t\t\t\t\tw = WarningLevel::Notice;\n\t\t\t\t\treason = \"SMART is disabled. You should enable it to read any SMART information from this drive. \"\n\t\t\t\t\t\t\t \"Additionally, some drives do not log useful data with SMART disabled, so it's advisable to keep it always enabled.\";\n\n\t\t\t\t} else if (name_match(p, \"_text_only/info_warning\")) {\n\t\t\t\t\tw = WarningLevel::Notice;\n\t\t\t\t\treason = \"Your drive may be affected by the warning, please see the details.\";\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::OverallHealth:\n\t\t\t\tif (name_match(p, \"smart_status/passed\") && !p.get_value<bool>()) {\n\t\t\t\t\tw = WarningLevel::Alert;\n\t\t\t\t\treason = \"The drive is reporting that it will FAIL very soon. Please back up as soon as possible!\";\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::Capabilities:\n\t\t\t\t// nothing\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::AtaAttributes:\n\t\t\t{\n\t\t\t\tstorage_property_ata_attribute_autoset_warning(p);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase StoragePropertySection::Statistics:\n\t\t\t{\n\t\t\t\tstorage_property_ata_statistic_autoset_warning(p);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase StoragePropertySection::AtaErrorLog:\n\t\t\t{\n\t\t\t\t// Note: The error list table doesn't display any descriptions, so if any\n\t\t\t\t// error-entry related descriptions are added here, don't forget to enable\n\t\t\t\t// the tooltips.\n\n\t\t\t\tif (name_match(p, \"ata_smart_error_log/extended/count\") && p.get_value<int64_t>() > 0) {\n\t\t\t\t\tw = WarningLevel::Notice;\n\t\t\t\t\treason = \"The drive is reporting internal errors. Usually this means uncorrectable data loss and similar severe errors. \"\n\t\t\t\t\t\t\t\"Check the actual errors for details.\";\n\n\t\t\t\t} else if (name_match(p, \"_text_only/ata_smart_error_log/_not_present\")) {\n\t\t\t\t\tw = WarningLevel::Notice;\n\t\t\t\t\treason = \"The drive does not support error logging. This means that SMART error history is unavailable.\";\n\t\t\t\t}\n\n\t\t\t\t// Rate individual error log entries.\n\t\t\t\tif (p.is_value_type<AtaStorageErrorBlock>()) {\n\t\t\t\t\tconst auto& eb = p.get_value<AtaStorageErrorBlock>();\n\t\t\t\t\tif (!eb.reported_types.empty()) {\n\t\t\t\t\t\tWarningLevel error_block_warning = WarningLevel::None;\n\t\t\t\t\t\tfor (const auto& reported_type : eb.reported_types) {\n\t\t\t\t\t\t\tconst WarningLevel individual_warning = AtaStorageErrorBlock::get_warning_level_for_error_type(reported_type);\n\t\t\t\t\t\t\tif (individual_warning > error_block_warning) {\n\t\t\t\t\t\t\t\terror_block_warning = WarningLevel(individual_warning);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (error_block_warning > WarningLevel::None) {\n\t\t\t\t\t\t\tw = error_block_warning;\n\t\t\t\t\t\t\treason = \"The drive is reporting internal errors. Your data may be at risk depending on error severity.\";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase StoragePropertySection::SelftestLog:\n\t\t\t{\n\t\t\t\t// Note: The error list table doesn't display any descriptions, so if any\n\t\t\t\t// error-entry related descriptions are added here, don't forget to enable\n\t\t\t\t// the tooltips.\n\n\t\t\t\t// Don't include selftest warnings - they may be old or something.\n\t\t\t\t// Self-tests are carried manually anyway, so the user is expected to check their status anyway.\n\n\t\t\t\tif (name_match(p, \"ata_smart_self_test_log/_present\")) {\n\t\t\t\t\tw = WarningLevel::Notice;\n\t\t\t\t\treason = \"The drive does not support self-test logging. This means that SMART test results won't be logged.\";\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase StoragePropertySection::SelectiveSelftestLog:\n\t\t\t\t// nothing here\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::TemperatureLog:\n\t\t\t\t// Don't highlight SCT Unsupported as warning, it's harmless.\n// \t\t\t\tif (name_match(p, \"_text_only/ata_sct_status/_not_present\") && p.value_bool) {\n// \t\t\t\t\tw = WarningLevel::notice;\n// \t\t\t\t\treason = \"The drive does not support SCT Temperature logging.\";\n// \t\t\t\t}\n\t\t\t\t// Current temperature\n\t\t\t\tif (name_match(p, \"ata_sct_status/temperature/current\") && p.get_value<int64_t>() > 50) {  // 50C\n\t\t\t\t\tw = WarningLevel::Notice;\n\t\t\t\t\treason = \"The temperature of the drive is higher than 50 degrees Celsius. \"\n\t\t\t\t\t\t\t\"This may shorten its lifespan and cause damage under severe load. Please install a cooling solution.\";\n\t\t\t\t}\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::NvmeHealth:\n\t\t\tcase StoragePropertySection::NvmeErrorLog:\n\t\t\t\tbreak;\n\n\t\t\tcase StoragePropertySection::NvmeAttributes:\n\t\t\t{\n\t\t\t\tstorage_property_nvme_attribute_autoset_warning(p);\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase StoragePropertySection::ErcLog:\n\t\t\tcase StoragePropertySection::PhyLog:\n\t\t\tcase StoragePropertySection::DirectoryLog:\n\t\t\tcase StoragePropertySection::Unknown:\n//\t\t\tcase AtaStoragePropertySection::Internal:\n\t\t\t\t// nothing here\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (w.has_value()) {\n\t\tp.warning_level = w.value();\n\t\tp.warning_reason = reason;\n\t}\n}\n\n\n\n/// Append warning text to description and set it on the property\ninline void storage_property_autoset_warning_descr(StorageProperty& p)\n{\n\tstd::string reason = storage_property_get_warning_reason(p);\n\tif (!reason.empty()) {\n\t\tp.set_description(p.get_description() + \"\\n\\n\" + reason);\n\t}\n}\n\n\n\nStoragePropertyRepository StoragePropertyProcessor::process_properties(\n\t\tStoragePropertyRepository properties, StorageDeviceDetectedType device_type)\n{\n\tfor (auto& p : properties.get_properties_ref()) {\n\t\tstorage_property_autoset_description(p, device_type);\n\t\tstorage_property_autoset_warning(p);\n\t\tstorage_property_autoset_warning_descr(p);  // append warning to description\n\t}\n\treturn properties;\n}\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_property_descr.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef STORAGE_PROPERTY_DESCR_H\n#define STORAGE_PROPERTY_DESCR_H\n\n#include \"storage_property_repository.h\"\n#include \"storage_device_detected_type.h\"\n\n\n\n\n\nclass StoragePropertyProcessor {\n\tpublic:\n\n\t\t/// Set descriptions, warnings, etc. on properties, and return them.\n\t\tstatic StoragePropertyRepository process_properties(StoragePropertyRepository properties,\n\t\t\t\tStorageDeviceDetectedType device_type);\n\n};\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_property_descr_ata_attribute.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include <glibmm.h>\n#include <utility>\n#include <vector>\n#include <map>\n#include <unordered_map>\n\n#include \"hz/string_algo.h\"  // string_replace_copy\n#include \"applib/app_regex.h\"\n\n#include \"storage_property_descr_ata_attribute.h\"\n//#include \"warning_colors.h\"\n#include \"storage_property_descr_helpers.h\"\n#include \"hz/string_num.h\"\n\n\nnamespace {\n\n\n\n\t/// Attribute description for attribute database\n\tstruct AtaAttributeDescription {\n\t\t/// Constructor\n\t\tAtaAttributeDescription() = default;\n\n\t\t/// Constructor\n\t\tAtaAttributeDescription(int32_t id_, std::optional<StorageDeviceDetectedType> type, std::string reported_name_,\n\t\t\t\tstd::string displayable_name_, std::string generic_name_, std::string description_)\n\t\t\t\t: id(id_), drive_type(type), reported_name(std::move(reported_name_)), displayable_name(std::move(displayable_name_)),\n\t\t\t\tgeneric_name(std::move(generic_name_)), description(std::move(description_))\n\t\t{ }\n\n\t\tint32_t id = -1;  ///< e.g. 190\n\t\tstd::optional<StorageDeviceDetectedType> drive_type;  ///< HDD-only, SSD-only or universal attribute\n\t\tstd::string reported_name;  ///< e.g. Airflow_Temperature_Cel\n\t\tstd::string displayable_name;  ///< e.g. Airflow Temperature (C). This is a translatable string.\n\t\tstd::string generic_name;  ///< Generic name to be set on the property, e.g. \"airflow_temperature\". For lookups.\n\t\tstd::string description;  ///< Attribute description, can be empty.\n\t};\n\n\n\n\t/// Attribute description database\n\tclass AtaAttributeDescriptionDatabase {\n\t\tpublic:\n\n\t\t\t/// Constructor\n\t\t\tAtaAttributeDescriptionDatabase()\n\t\t\t{\n\t\t\t\t// Note: The first one with the same ID is the one displayed in case smartctl\n\t\t\t\t// doesn't return a name. See atacmds.cpp (get_default_attr_name()) in smartmontools.\n\t\t\t\t// The rest are from drivedb.h, which contains overrides.\n\t\t\t\t// Based on: smartmontools r4430, 2017-05-03.\n\n\t\t\t\t// \"smartctl\" means it's in smartmontools' drivedb.h.\n\t\t\t\t// \"custom\" means it's somewhere else.\n\n\t\t\t\t// Descriptions are based on:\n\t\t\t\t// https://en.wikipedia.org/wiki/Self-Monitoring,_Analysis_and_Reporting_Technology\n\t\t\t\t// http://kb.acronis.com/taxonomy/term/1644\n\t\t\t\t// http://www.ariolic.com/activesmart/smart-attributes/\n\t\t\t\t// http://www.ocztechnologyforum.com/staff/ryderocz/misc/Sandforce.jpg\n\t\t\t\t// Intel Solid-State Drive Toolbox User Guide\n\t\t\t\t// as well as various other sources.\n\n\t\t\t\t// Raw read error rate (smartctl)\n\t\t\t\tadd(1, \"Raw_Read_Error_Rate\", \"Raw Read Error Rate\", \"\",\n\t\t\t\t\t\t\"Indicates the rate of read errors that occurred while reading the data. A non-zero Raw value may indicate a problem with either the disk surface or read/write heads. \"\n\t\t\t\t\t\t\"<i>Note:</i> Some drives (e.g. Seagate) are known to report very high Raw values for this attribute; this is not an indication of a problem.\");\n\t\t\t\t// Throughput Performance (smartctl)\n\t\t\t\tadd(2, \"Throughput_Performance\", \"Throughput Performance\", \"\",\n\t\t\t\t\t\t\"Average efficiency of a drive. Reduction of this attribute value can signal various internal problems.\");\n\t\t\t\t// Spin Up Time (smartctl) (some say it can also happen due to bad PSU or power connection (?))\n\t\t\t\tadd(3, \"Spin_Up_Time\", \"Spin-Up Time\", \"\",\n\t\t\t\t\t\t\"Average time of spindle spin-up time (from stopped to fully operational). Raw value may show this in milliseconds or seconds. \"\n\t\t\t\t\t\t\"Changes in spin-up time can reflect problems with the spindle motor or power.\");\n\t\t\t\t// Start/Stop Count (smartctl)\n\t\t\t\tadd(4, \"Start_Stop_Count\", \"Start / Stop Count\", \"\",\n\t\t\t\t\t\t\"Number of start/stop cycles of a spindle (Raw value). That is, number of drive spin-ups.\");\n\t\t\t\t// Reallocated Sector Count (smartctl)\n\t\t\t\tadd(5, StorageDeviceDetectedType::AtaHdd, \"Reallocated_Sector_Ct\", \"Reallocated Sector Count\", \"attr_reallocated_sector_count\",\n\t\t\t\t\t\t\"Number of reallocated sectors (Raw value). Non-zero Raw value indicates a disk surface failure.\"\n\t\t\t\t\t\t\"\\n\\n\" + get_suffix_for_uncorrectable_property_description());\n\t\t\t\t// SSD: Reallocated Sector Count (smartctl)\n\t\t\t\tadd(5, StorageDeviceDetectedType::AtaSsd, \"Reallocated_Sector_Ct\", \"Reallocated Sector Count\", \"attr_reallocated_sector_count\",\n\t\t\t\t\t\t\"Number of reallocated sectors (Raw value). High Raw value indicates an old age for an SSD.\");\n\t\t\t\t// SandForce SSD: Retired_Block_Count (smartctl)\n\t\t\t\tadd(5, StorageDeviceDetectedType::AtaSsd, \"Retired_Block_Count\", \"Retired Block Rate\", \"attr_ssd_life_left\",\n\t\t\t\t\t\t\"Indicates estimated remaining life of the drive. Normalized value is (100-100*RBC/MRB) where RBC is the number of retired blocks \"\n\t\t\t\t\t\t\"and MRB is the minimum required blocks.\");\n\t\t\t\t// Crucial/Micron SSD: Reallocate_NAND_Blk_Cnt (smartctl)\n\t\t\t\tadd(5, StorageDeviceDetectedType::AtaSsd, \"Reallocate_NAND_Blk_Cnt\", \"Reallocated NAND Block Count\", \"\",\n\t\t\t\t\t\t\"Number of reallocated blocks (Raw value). High Raw value indicates an old age for an SSD.\");\n\t\t\t\t// Micron SSD: Reallocate_NAND_Blk_Cnt (smartctl)\n\t\t\t\tadd(5, StorageDeviceDetectedType::AtaSsd, \"Reallocated_Block_Count\", \"Reallocated Block Count\", \"\",\n\t\t\t\t\t\t\"Number of reallocated blocks (Raw value). High Raw value indicates an old age for an SSD.\");\n\t\t\t\t// OCZ SSD (smartctl)\n\t\t\t\tadd(5, StorageDeviceDetectedType::AtaSsd, \"Runtime_Bad_Block\", \"Runtime Bad Block Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Innodisk SSD (smartctl)\n\t\t\t\tadd(5, StorageDeviceDetectedType::AtaSsd, \"Later_Bad_Block\", \"Later Bad Block\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Read Channel Margin (smartctl)\n\t\t\t\tadd(6, StorageDeviceDetectedType::AtaHdd, \"Read_Channel_Margin\", \"Read Channel Margin\", \"\",\n\t\t\t\t\t\t\"Margin of a channel while reading data. The function of this attribute is not specified.\");\n\t\t\t\t// Seek Error Rate (smartctl)\n\t\t\t\tadd(7, StorageDeviceDetectedType::AtaHdd, \"Seek_Error_Rate\", \"Seek Error Rate\", \"\",\n\t\t\t\t\t\t\"Frequency of errors appearance while positioning. When a drive reads data, it positions heads in the needed place. \"\n\t\t\t\t\t\t\"If there is a failure in the mechanical positioning system, a seek error arises. More seek errors indicate worse condition \"\n\t\t\t\t\t\t\"of a disk surface and disk mechanical subsystem. The exact meaning of the Raw value is manufacturer-dependent.\");\n\t\t\t\t// Seek Time Performance (smartctl)\n\t\t\t\tadd(8, StorageDeviceDetectedType::AtaHdd, \"Seek_Time_Performance\", \"Seek Time Performance\", \"\",\n\t\t\t\t\t\t\"Average efficiency of seek operations of the magnetic heads. If this value is decreasing, it is a sign of problems in the hard disk drive mechanical subsystem.\");\n\t\t\t\t// Power-On Hours (smartctl) (Maxtor may use minutes, Fujitsu may use seconds, some even temperature?)\n\t\t\t\tadd(9, \"Power_On_Hours\", \"Power-On Time\", \"\",\n\t\t\t\t\t\t\"Number of hours in power-on state. Raw value shows total count of hours (or minutes, or half-minutes, or seconds, depending on manufacturer) in power-on state.\");\n\t\t\t\t// SandForce, Intel SSD: Power_On_Hours_and_Msec (smartctl) (description?)\n\t\t\t\tadd(9, StorageDeviceDetectedType::AtaSsd, \"Power_On_Hours_and_Msec\");\n\t\t\t\t// Smart Storage Systems SSD (smartctl)\n\t\t\t\tadd(9, StorageDeviceDetectedType::AtaSsd, \"Proprietary_9\", \"Internal Attribute\", \"\",\n\t\t\t\t\t\t\"This attribute has been reserved by vendor as internal.\");\n\t\t\t\t// Spin-up Retry Count (smartctl)\n\t\t\t\tadd(10, StorageDeviceDetectedType::AtaHdd, \"Spin_Retry_Count\", \"Spin-Up Retry Count\", \"attr_spin_up_retry_count\",\n\t\t\t\t\t\t\"Number of retries of spin start attempts (Raw value). An increase of this attribute value is a sign of problems in the hard disk mechanical subsystem.\");\n\t\t\t\t// Calibration Retry Count (smartctl)\n\t\t\t\tadd(11, StorageDeviceDetectedType::AtaHdd, \"Calibration_Retry_Count\", \"Calibration Retry Count\", \"\",\n\t\t\t\t\t\t\"Number of times recalibration was requested, under the condition that the first attempt was unsuccessful (Raw value). \"\n\t\t\t\t\t\t\t\t\"A decrease is a sign of problems in the hard disk mechanical subsystem.\");\n\t\t\t\t// Power Cycle Count (smartctl)\n\t\t\t\tadd(12, \"Power_Cycle_Count\", \"Power Cycle Count\", \"\",\n\t\t\t\t\t\t\"Number of complete power start / stop cycles of a drive.\");\n\t\t\t\t// Soft Read Error Rate (smartctl) (same as 201 ?) (description sounds lame, fix?)\n\t\t\t\tadd(13, \"Read_Soft_Error_Rate\", \"Soft Read Error Rate\", \"attr_soft_read_error_rate\",\n\t\t\t\t\t\t\"Uncorrected read errors reported to the operating system (Raw value). If the value is non-zero, you should back up your data.\");\n\t\t\t\t// Sandforce SSD: Soft_Read_Error_Rate (smartctl)\n\t\t\t\tadd(13, StorageDeviceDetectedType::AtaSsd, \"Soft_Read_Error_Rate\");\n\t\t\t\t// Maxtor: Average FHC (custom) (description?)\n\t\t\t\tadd(99, StorageDeviceDetectedType::AtaHdd, \"\", \"Average FHC (Flying Height Control)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Sandforce SSD: Gigabytes_Erased (smartctl) (description?)\n\t\t\t\tadd(100, StorageDeviceDetectedType::AtaSsd, \"Gigabytes_Erased\", \"GiB Erased\", \"\",\n\t\t\t\t\t\t\"Number of GiB erased.\");\n\t\t\t\t// OCZ SSD (smartctl)\n\t\t\t\tadd(100, StorageDeviceDetectedType::AtaSsd, \"Total_Blocks_Erased\", \"Total Blocks Erased\", \"\",\n\t\t\t\t\t\t\"Number of total blocks erased.\");\n\t\t\t\t// STEC CF: (custom)\n\t\t\t\tadd(100, StorageDeviceDetectedType::AtaSsd, \"\", \"Erase / Program Cycles\", \"\",  // unused\n\t\t\t\t\t\t\"Number of Erase / Program cycles of the entire drive.\");\n\t\t\t\t// Maxtor: Maximum FHC (custom) (description?)\n\t\t\t\tadd(101, StorageDeviceDetectedType::AtaHdd, \"\", \"Maximum FHC (Flying Height Control)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Unknown (source says Maxtor, but it's an SSD thing and Maxtor doesn't have them at this point).\n\t// \t\t\tadd(101, \"\", \"Translation Table Rebuild\", \"\",\n\t// \t\t\t\t\t\"Indicates power backup fault or internal error resulting in loss of system unit tables.\");\n\t\t\t\t// STEC CF: Translation Table Rebuild (custom)\n\t\t\t\tadd(103, StorageDeviceDetectedType::AtaSsd, \"\", \"Translation Table Rebuild\", \"\",\n\t\t\t\t\t\t\"Indicates power backup fault or internal error resulting in loss of system unit tables.\");\n\t\t\t\t// Smart Storage Systems SSD (smartctl) (description?)\n\t\t\t\tadd(130, StorageDeviceDetectedType::AtaSsd, \"Minimum_Spares_All_Zs\", \"Minimum Spares All Zs\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// SiliconMotion SSDs (description?) (smartctl)\n\t\t\t\tadd(148, StorageDeviceDetectedType::AtaSsd, \"Total_SLC_Erase_Ct\", \"Total SLC Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// SiliconMotion SSDs (description?) (smartctl)\n\t\t\t\tadd(149, StorageDeviceDetectedType::AtaSsd, \"Max_SLC_Erase_Ct\", \"Maximum SLC Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// SiliconMotion SSDs (description?) (smartctl)\n\t\t\t\tadd(150, StorageDeviceDetectedType::AtaSsd, \"Min_SLC_Erase_Ct\", \"Minimum SLC Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// SiliconMotion SSDs (description?) (smartctl)\n\t\t\t\tadd(151, StorageDeviceDetectedType::AtaSsd, \"Average_SLC_Erase_Ct\", \"Average SLC Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Apacer Flash (description?) (smartctl)\n\t\t\t\tadd(160, StorageDeviceDetectedType::AtaSsd, \"Initial_Bad_Block_Count\", \"Initial Bad Block Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Samsung SSD, Intel SSD: Reported Uncorrectable (smartctl)\n\t\t\t\tadd(160, StorageDeviceDetectedType::AtaSsd, \"Uncorrectable_Error_Cnt\", \"Uncorrectable Error Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Apacer Flash (description?) (smartctl)\n\t\t\t\tadd(161, StorageDeviceDetectedType::AtaSsd, \"Bad_Block_Count\", \"Bad Block Count\", \"\",\n\t\t\t\t\t\t\"Number of bad blocks. SSDs reallocate blocks as part of their normal operation, so low bad block counts are not critical for them.\");\n\t\t\t\t// Innodisk (description?) (smartctl)\n\t\t\t\tadd(161, StorageDeviceDetectedType::AtaSsd, \"Number_of_Pure_Spare\", \"Number of Pure Spare\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Innodisk CF (description?) (smartctl)\n\t\t\t\tadd(161, StorageDeviceDetectedType::AtaSsd, \"Valid_Spare_Block_Cnt\", \"Valid Spare Block Count\", \"\",\n\t\t\t\t\t\t\"Number of available spare blocks. Spare blocks are used when bad blocks develop.\");\n\t\t\t\t// Apacer Flash (description?) (smartctl)\n\t\t\t\tadd(162, StorageDeviceDetectedType::AtaSsd, \"Spare_Block_Count\", \"Spare Block Count\", \"\",\n\t\t\t\t\t\t\"Number of spare blocks which are used when bad blocks develop.\");\n\t\t\t\t// Innodisk CF (smartctl)\n\t\t\t\tadd(162, StorageDeviceDetectedType::AtaSsd, \"Child_Pair_Count\", \"Child Pair Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Apacer Flash (description?) (smartctl)\n\t\t\t\tadd(163, StorageDeviceDetectedType::AtaSsd, \"Max_Erase_Count\", \"Maximum Erase Count\", \"\",\n\t\t\t\t\t\t\"The maximum of individual erase counts of all the blocks.\");\n\t\t\t\t// Innodisk SSD: (smartctl)\n\t\t\t\tadd(163, StorageDeviceDetectedType::AtaSsd, \"Initial_Bad_Block_Count\", \"Initial Bad Block Count\", \"\",\n\t\t\t\t\t\t\"Factory-determined number of initial bad blocks.\");\n\t\t\t\t// Innodisk SSD: (smartctl)\n\t\t\t\tadd(163, StorageDeviceDetectedType::AtaSsd, \"Total_Bad_Block_Count\", \"Total Bad Block Count\", \"\",\n\t\t\t\t\t\t\"Number of bad blocks. SSDs reallocate blocks as part of their normal operation, so low bad block counts are not critical for them.\");\n\t\t\t\t// Apacer Flash (description?) (smartctl)\n\t\t\t\tadd(164, StorageDeviceDetectedType::AtaSsd, \"Average_Erase_Count\", \"Average Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Innodisk SSD (description?) (smartctl)\n\t\t\t\tadd(164, StorageDeviceDetectedType::AtaSsd, \"Total_Erase_Count\", \"Total Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Apacer Flash (description?) (smartctl)\n\t\t\t\tadd(165, StorageDeviceDetectedType::AtaSsd, \"Average_Erase_Count\", \"Average Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Innodisk SSD (description?) (smartctl)\n\t\t\t\tadd(165, StorageDeviceDetectedType::AtaSsd, \"Max_Erase_Count\", \"Maximum Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Sandisk SSD (description?) (smartctl)\n\t\t\t\tadd(165, StorageDeviceDetectedType::AtaSsd, \"Total_Write/Erase_Count\", \"Total Write / Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Apacer Flash (description?) (smartctl)\n\t\t\t\tadd(166, StorageDeviceDetectedType::AtaSsd, \"Later_Bad_Block_Count\", \"Later Bad Block Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Innodisk SSD (description?) (smartctl)\n\t\t\t\tadd(166, StorageDeviceDetectedType::AtaSsd, \"Min_Erase_Count\", \"Minimum Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Sandisk SSD (description?) (smartctl)\n\t\t\t\tadd(166, StorageDeviceDetectedType::AtaSsd, \"Min_W/E_Cycle\", \"Minimum Write / Erase Cycles\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Apacer Flash, OCZ (description?) (smartctl)\n\t\t\t\tadd(167, StorageDeviceDetectedType::AtaSsd, \"SSD_Protect_Mode\", \"SSD Protect Mode\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Innodisk SSD (description?) (smartctl)\n\t\t\t\tadd(167, StorageDeviceDetectedType::AtaSsd, \"Average_Erase_Count\", \"Average Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Sandisk SSD (description?) (smartctl)\n\t\t\t\tadd(167, StorageDeviceDetectedType::AtaSsd, \"Min_Bad_Block/Die\", \"Minimum Bad Block / Die\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Apacer Flash (description?) (smartctl)\n\t\t\t\tadd(168, StorageDeviceDetectedType::AtaSsd, \"SATA_PHY_Err_Ct\", \"SATA Physical Error Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Various SSDs: (smartctl) (description?)\n\t\t\t\tadd(168, StorageDeviceDetectedType::AtaSsd, \"SATA_Phy_Error_Count\", \"SATA Physical Error Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Innodisk SSDs: (smartctl) (description?)\n\t\t\t\tadd(168, StorageDeviceDetectedType::AtaSsd, \"Max_Erase_Count_of_Spec\", \"Maximum Erase Count per Specification\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Sandisk SSD (description?) (smartctl)\n\t\t\t\tadd(168, StorageDeviceDetectedType::AtaSsd, \"Maximum_Erase_Cycle\", \"Maximum Erase Cycles\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Toshiba SSDs: (smartctl) (description?)\n\t\t\t\tadd(169, StorageDeviceDetectedType::AtaSsd, \"Bad_Block_Count\", \"Bad Block Count\", \"\",\n\t\t\t\t\t\t\"Number of bad blocks. SSDs reallocate blocks as part of their normal operation, so low bad block counts are not critical for them.\");\n\t\t\t\t// Sandisk SSD (description?) (smartctl)\n\t\t\t\tadd(169, StorageDeviceDetectedType::AtaSsd, \"Total_Bad_Blocks\", \"Total Bad Blocks\", \"\",\n\t\t\t\t\t\t\"Number of bad blocks. SSDs reallocate blocks as part of their normal operation, so low bad block counts are not critical for them.\");\n\t\t\t\t// Innodisk SSDs: (smartctl) (description?)\n\t\t\t\tadd(169, StorageDeviceDetectedType::AtaSsd, \"Remaining_Lifetime_Perc\", \"Remaining Lifetime %\", \"attr_ssd_life_left\",\n\t\t\t\t\t\t\"Remaining drive life in % (usually by erase count).\");\n\t\t\t\t// Intel SSD, STEC CF: Reserved Block Count (smartctl)\n\t\t\t\tadd(170, StorageDeviceDetectedType::AtaSsd, \"Reserve_Block_Count\", \"Reserved Block Count\", \"\",\n\t\t\t\t\t\t\"Number of reserved (spare) blocks for bad block handling.\");\n\t\t\t\t// Micron SSD: Reserved Block Count (smartctl)\n\t\t\t\tadd(170, StorageDeviceDetectedType::AtaSsd, \"Reserved_Block_Count\", \"Reserved Block Count\", \"\",\n\t\t\t\t\t\t\"Number of reserved (spare) blocks for bad block handling.\");\n\t\t\t\t// Crucial / Marvell SSD: Grown Failing Block Count (smartctl) (description?)\n\t\t\t\tadd(170, StorageDeviceDetectedType::AtaSsd, \"Grown_Failing_Block_Ct\", \"Grown Failing Block Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Intel SSD: (smartctl) (description?)\n\t\t\t\tadd(170, StorageDeviceDetectedType::AtaSsd, \"Available_Reservd_Space\", \"Available Reserved Space\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Various SSDs: (smartctl) (description?)\n\t\t\t\tadd(170, StorageDeviceDetectedType::AtaSsd, \"Bad_Block_Count\", \"Bad Block Count\", \"\",\n\t\t\t\t\t\t\"Number of bad blocks. SSDs reallocate blocks as part of their normal operation, so low bad block counts are not critical for them.\");\n\t\t\t\t// Kingston SSDs: (smartctl) (description?)\n\t\t\t\tadd(170, StorageDeviceDetectedType::AtaSsd, \"Bad_Blk_Ct_Erl/Lat\", \"Bad Block Early / Later\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Samsung SSDs: (smartctl) (description?)\n\t\t\t\tadd(170, StorageDeviceDetectedType::AtaSsd, \"Unused_Rsvd_Blk_Ct_Chip\", \"Unused Reserved Block Count (Chip)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Innodisk Flash (description?) (smartctl)\n\t\t\t\tadd(170, StorageDeviceDetectedType::AtaSsd, \"Spare_Block_Count\", \"Spare Block Count\", \"\",\n\t\t\t\t\t\t\"Number of spare blocks which are used in case bad blocks develop.\");\n\t\t\t\t// Intel SSD, Sandforce SSD, STEC CF, Crucial / Marvell SSD: Program Fail Count (smartctl)\n\t\t\t\tadd(171, StorageDeviceDetectedType::AtaSsd, \"Program_Fail_Count\", \"Program Fail Count\", \"\",\n\t\t\t\t\t\t\"Number of flash program (write) failures. High values may indicate old drive age or other problems.\");\n\t\t\t\t// Samsung SSDs: (smartctl) (description?)\n\t\t\t\tadd(171, StorageDeviceDetectedType::AtaSsd, \"Program_Fail_Count_Chip\", \"Program Fail Count (Chip)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// OCZ SSD (smartctl)\n\t\t\t\tadd(171, StorageDeviceDetectedType::AtaSsd, \"Avail_OP_Block_Count\", \"Available OP Block Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Intel SSD, Sandforce SSD, STEC CF, Crucial / Marvell SSD: Erase Fail Count (smartctl)\n\t\t\t\tadd(172, StorageDeviceDetectedType::AtaSsd, \"Erase_Fail_Count\", \"Erase Fail Count\", \"\",\n\t\t\t\t\t\t\"Number of flash erase command failures. High values may indicate old drive age or other problems.\");\n\t\t\t\t// Various SSDs (smartctl) (description?)\n\t\t\t\tadd(173, StorageDeviceDetectedType::AtaSsd, \"Erase_Count\", \"Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Samsung SSDs (smartctl) (description?)\n\t\t\t\tadd(173, StorageDeviceDetectedType::AtaSsd, \"Erase_Fail_Count_Chip\", \"Erase Fail Count (Chip)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Kingston SSDs (smartctl) (description?)\n\t\t\t\tadd(173, StorageDeviceDetectedType::AtaSsd, \"MaxAvgErase_Ct\", \"Maximum / Average Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Crucial/Micron SSDs (smartctl) (description?)\n\t\t\t\tadd(173, StorageDeviceDetectedType::AtaSsd, \"Ave_Block-Erase_Count\", \"Average Block-Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// STEC CF, Crucial / Marvell SSD: Wear Leveling Count (smartctl) (description?)\n\t\t\t\tadd(173, StorageDeviceDetectedType::AtaSsd, \"Wear_Leveling_Count\", \"Wear Leveling Count\", \"\",\n\t\t\t\t\t\t\"Indicates the difference between the most worn block and the least worn block.\");\n\t\t\t\t// Same as above, old smartctl\n\t\t\t\tadd(173, StorageDeviceDetectedType::AtaSsd, \"Wear_Levelling_Count\", \"Wear Leveling Count\", \"\",\n\t\t\t\t\t\t\"Indicates the difference between the most worn block and the least worn block.\");\n\t\t\t\t// Sandisk SSDs (smartctl) (description?)\n\t\t\t\tadd(173, StorageDeviceDetectedType::AtaSsd, \"Avg_Write/Erase_Count\", \"Average Write / Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Intel SSD, Sandforce SSD, Crucial / Marvell SSD: Unexpected Power Loss (smartctl)\n\t\t\t\tadd(174, StorageDeviceDetectedType::AtaSsd, \"Unexpect_Power_Loss_Ct\", \"Unexpected Power Loss Count\", \"\",\n\t\t\t\t\t\t\"Number of unexpected power loss events.\");\n\t\t\t\t// OCZ SSD (smartctl)\n\t\t\t\tadd(174, StorageDeviceDetectedType::AtaSsd, \"Pwr_Cycle_Ct_Unplanned\", \"Unexpected Power Loss Count\", \"\",\n\t\t\t\t\t\t\"Number of unexpected power loss events.\");\n\t\t\t\t// Apple SSD (smartctl)\n\t\t\t\tadd(174, StorageDeviceDetectedType::AtaSsd, \"Host_Reads_MiB\", \"Host Read (MiB)\", \"\",\n\t\t\t\t\t\t\"Total number of sectors read by the host system. The Raw value is increased by 1 for every MiB read by the host.\");\n\t\t\t\t// Program_Fail_Count_Chip (smartctl)\n\t\t\t\tadd(175, StorageDeviceDetectedType::AtaSsd, \"Program_Fail_Count_Chip\", \"Program Fail Count (Chip)\", \"\",\n\t\t\t\t\t\t\"Number of flash program (write) failures. High values may indicate old drive age or other problems.\");\n\t\t\t\t// Various SSDs: Bad_Cluster_Table_Count (smartctl) (description?)\n\t\t\t\tadd(175, StorageDeviceDetectedType::AtaSsd, \"Bad_Cluster_Table_Count\", \"Bad Cluster Table Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Intel SSD (smartctl) (description?)\n\t\t\t\tadd(175, StorageDeviceDetectedType::AtaSsd, \"Power_Loss_Cap_Test\", \"Power Loss Capacitor Test\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Intel SSD (smartctl) (description?)\n\t\t\t\tadd(175, StorageDeviceDetectedType::AtaSsd, \"Host_Writes_MiB\", \"Host Written (MiB)\", \"\",\n\t\t\t\t\t\t\"Total number of sectors written by the host system. The Raw value is increased by 1 for every MiB written by the host.\");\n\t\t\t\t// Erase_Fail_Count_Chip (smartctl)\n\t\t\t\tadd(176, StorageDeviceDetectedType::AtaSsd, \"Erase_Fail_Count_Chip\", \"Erase Fail Count (Chip)\", \"\",\n\t\t\t\t\t\t\"Number of flash erase command failures. High values may indicate old drive age or other problems.\");\n\t\t\t\t// Innodisk SSD (smartctl) (description?)\n\t\t\t\tadd(176, StorageDeviceDetectedType::AtaSsd, \"Uncorr_RECORD_Count\", \"Uncorrected RECORD Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Innodisk SSD (smartctl) (description?)\n\t\t\t\tadd(176, StorageDeviceDetectedType::AtaSsd, \"RANGE_RECORD_Count\", \"RANGE RECORD Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Wear_Leveling_Count (smartctl) (same as Wear_Range_Delta?)\n\t\t\t\tadd(177, StorageDeviceDetectedType::AtaSsd, \"Wear_Leveling_Count\", \"Wear Leveling Count\", \"\",\n\t\t\t\t\t\t\"Indicates the difference (in percent) between the most worn block and the least worn block.\");\n\t\t\t\t// Sandforce SSD: Wear_Range_Delta (smartctl)\n\t\t\t\tadd(177, StorageDeviceDetectedType::AtaSsd, \"Wear_Range_Delta\", \"Wear Range Delta\", \"\",\n\t\t\t\t\t\t\"Indicates the difference (in percent) between the most worn block and the least worn block.\");\n\t\t\t\t// Used_Rsvd_Blk_Cnt_Chip (smartctl)\n\t\t\t\tadd(178, StorageDeviceDetectedType::AtaSsd, \"Used_Rsvd_Blk_Cnt_Chip\", \"Used Reserved Block Count (Chip)\", \"\",\n\t\t\t\t\t\t\"Number of a chip's used reserved blocks. High values may indicate old drive age or other problems.\");\n\t\t\t\t// Innodisk SSD (smartctl)\n\t\t\t\tadd(178, StorageDeviceDetectedType::AtaSsd, \"Runtime_Invalid_Blk_Cnt\", \"Runtime Invalid Block Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Used_Rsvd_Blk_Cnt_Tot (smartctl) (description?)\n\t\t\t\tadd(179, StorageDeviceDetectedType::AtaSsd, \"Used_Rsvd_Blk_Cnt_Tot\", \"Used Reserved Block Count (Total)\", \"\",\n\t\t\t\t\t\t\"Number of used reserved blocks. High values may indicate old drive age or other problems.\");\n\t\t\t\t// Unused_Rsvd_Blk_Cnt_Tot (smartctl)\n\t\t\t\tadd(180, StorageDeviceDetectedType::AtaSsd, \"Unused_Rsvd_Blk_Cnt_Tot\", \"Unused Reserved Block Count (Total)\", \"\",\n\t\t\t\t\t\t\"Number of unused reserved blocks. High values may indicate old drive age or other problems.\");\n\t\t\t\t// Crucial / Micron SSDs (smartctl) (description?)\n\t\t\t\tadd(180, StorageDeviceDetectedType::AtaSsd, \"Unused_Reserve_NAND_Blk\", \"Unused Reserved NAND Blocks\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Program_Fail_Cnt_Total (smartctl)\n\t\t\t\tadd(181, \"Program_Fail_Cnt_Total\", \"Program Fail Count\", \"\",\n\t\t\t\t\t\t\"Number of flash program (write) failures. High values may indicate old drive age or other problems.\");\n\t\t\t\t// Sandforce SSD: Program_Fail_Count (smartctl) (Sandforce says it's identical to 171)\n\t\t\t\tadd(181, StorageDeviceDetectedType::AtaSsd, \"Program_Fail_Count\");\n\t\t\t\t// Crucial / Marvell SSD (smartctl) (description?)\n\t\t\t\tadd(181, StorageDeviceDetectedType::AtaSsd, \"Non4k_Aligned_Access\", \"Non-4k Aligned Access\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Erase_Fail_Count_Total (smartctl) (description?)\n\t\t\t\tadd(182, StorageDeviceDetectedType::AtaSsd, \"Erase_Fail_Count_Total\", \"Erase Fail Count\", \"\",\n\t\t\t\t\t\t\"Number of flash erase command failures. High values may indicate old drive age or other problems.\");\n\t\t\t\t// Sandforce SSD: Erase_Fail_Count (smartctl) (Sandforce says it's identical to 172)\n\t\t\t\tadd(182, StorageDeviceDetectedType::AtaSsd, \"Erase_Fail_Count\");\n\t\t\t\t// Runtime_Bad_Block (smartctl) (description?)\n\t\t\t\tadd(183, \"Runtime_Bad_Block\", \"Runtime Bad Blocks\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Samsung, WD, Crucial / Marvell SSD: SATA Downshift Error Count (smartctl) (description?)\n\t\t\t\tadd(183, \"SATA_Iface_Downshift\", \"SATA Downshift Error Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Crucial / Marvell SSD: SATA Downshift Error Count (smartctl) (description?)\n\t\t\t\tadd(183, \"SATA_Interfac_Downshift\", \"SATA Downshift Error Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Intel SSD, Ubtek SSD (smartctl) (description?)\n\t\t\t\tadd(183, StorageDeviceDetectedType::AtaSsd, \"SATA_Downshift_Count\", \"SATA Downshift Error Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// End to End Error (smartctl) (description?)\n\t\t\t\tadd(184, \"End-to-End_Error\", \"End to End Error\", \"\",\n\t\t\t\t\t\t\"Indicates discrepancy of data between the host and the drive cache.\");\n\t\t\t\t// Sandforce SSD: IO_Error_Detect_Code_Ct (smartctl)\n\t\t\t\tadd(184, StorageDeviceDetectedType::AtaSsd, \"IO_Error_Detect_Code_Ct\", \"Input/Output ECC Error Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// OCZ SSD (smartctl)\n\t\t\t\tadd(184, StorageDeviceDetectedType::AtaSsd, \"Factory_Bad_Block_Count\", \"Factory Bad Block Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Indilinx Barefoot SSD: IO_Error_Detect_Code_Ct (smartctl)\n\t\t\t\tadd(184, StorageDeviceDetectedType::AtaSsd, \"Initial_Bad_Block_Count\", \"Initial Bad Block Count\", \"\",\n\t\t\t\t\t\t\"Factory-determined number of initial bad blocks.\");\n\t\t\t\t// Crucial / Micron SSD (smartctl)\n\t\t\t\tadd(184, StorageDeviceDetectedType::AtaSsd, \"Error_Correction_Count\", \"Error Correction Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// WD: Head Stability (custom)\n\t\t\t\tadd(185, StorageDeviceDetectedType::AtaHdd, \"\", \"Head Stability\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// WD: Induced Op-Vibration Detection (custom)\n\t\t\t\tadd(185, StorageDeviceDetectedType::AtaHdd, \"\", \"Induced Op-Vibration Detection\", \"\",  // unused\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Reported Uncorrectable (smartctl)\n\t\t\t\tadd(187, \"Reported_Uncorrect\", \"Reported Uncorrectable\", \"\",\n\t\t\t\t\t\t\"Number of errors that could not be recovered using hardware ECC (Error-Correcting Code).\");\n\t\t\t\t// Innodisk SSD: Reported Uncorrectable (smartctl)\n\t\t\t\tadd(187, StorageDeviceDetectedType::AtaSsd, \"Uncorrectable_Error_Cnt\");\n\t\t\t\t// OCZ SSD (smartctl)\n\t\t\t\tadd(187, StorageDeviceDetectedType::AtaSsd, \"Total_Unc_NAND_Reads\", \"Total Uncorrectable NAND Reads\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Command Timeout (smartctl)\n\t\t\t\tadd(188, \"Command_Timeout\", \"Command Timeout\", \"\",\n\t\t\t\t\t\t\"Number of aborted operations due to drive timeout. High values may indicate problems with cabling or power supply.\");\n\t\t\t\t// Micron SSD (smartctl)\n\t\t\t\tadd(188, StorageDeviceDetectedType::AtaSsd, \"Command_Timeouts\", \"Command Timeout\", \"\",\n\t\t\t\t\t\t\"Number of aborted operations due to drive timeout. High values may indicate problems with cabling or power supply.\");\n\t\t\t\t// High Fly Writes (smartctl)\n\t\t\t\tadd(189, StorageDeviceDetectedType::AtaHdd, \"High_Fly_Writes\", \"High Fly Writes\", \"\",\n\t\t\t\t\t\t\"Some drives can detect when a recording head is flying outside its normal operating range. \"\n\t\t\t\t\t\t\"If an unsafe fly height condition is encountered, the write process is stopped, and the information \"\n\t\t\t\t\t\t\"is rewritten or reallocated to a safe region of the drive. This attribute indicates the count of \"\n\t\t\t\t\t\t\"these errors detected over the lifetime of the drive.\");\n\t\t\t\t// Crucial / Marvell SSD (smartctl)\n\t\t\t\tadd(189, StorageDeviceDetectedType::AtaSsd, \"Factory_Bad_Block_Ct\", \"Factory Bad Block Count\", \"\",\n\t\t\t\t\t\t\"Factory-determined number of initial bad blocks.\");\n\t\t\t\t// Various SSD (smartctl)\n\t\t\t\tadd(189, \"Airflow_Temperature_Cel\", \"Airflow Temperature\", \"\",\n\t\t\t\t\t\t\"Indicates temperature (in Celsius), 100 - temperature, or something completely different (highly depends on manufacturer and model).\");\n\t\t\t\t// Airflow Temperature (smartctl) (WD Caviar (may be 50 less), Samsung). Temperature or (100 - temp.) on Seagate/Maxtor.\n\t\t\t\tadd(190, \"Airflow_Temperature_Cel\", \"Airflow Temperature\", \"\",\n\t\t\t\t\t\t\"Indicates temperature (in Celsius), 100 - temperature, or something completely different (highly depends on manufacturer and model).\");\n\t\t\t\t// Samsung SSD (smartctl) (description?)\n\t\t\t\tadd(190, \"Temperature_Exceed_Cnt\", \"Temperature Exceed Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// OCZ SSD (smartctl)\n\t\t\t\tadd(190, \"Temperature_Celsius\", \"Temperature (Celsius)\", \"attr_temperature_celsius\",\n\t\t\t\t\t\t\"Drive temperature. The Raw value shows built-in heat sensor registrations (in Celsius).\");\n\t\t\t\t// Intel SSD\n\t\t\t\tadd(190, \"Temperature_Case\", \"Case Temperature (Celsius)\", \"\",\n\t\t\t\t\t\t\"Drive case temperature. The Raw value shows built-in heat sensor registrations (in Celsius).\");\n\t\t\t\t// G-sense error rate (smartctl) (same as 221?)\n\t\t\t\tadd(191, StorageDeviceDetectedType::AtaHdd, \"G-Sense_Error_Rate\", \"G-Sense Error Rate\", \"\",\n\t\t\t\t\t\t\"Number of errors caused by externally-induced shock and vibration (Raw value). May indicate incorrect installation.\");\n\t\t\t\t// Power-Off Retract Cycle (smartctl)\n\t\t\t\tadd(192, StorageDeviceDetectedType::AtaHdd, \"Power-Off_Retract_Count\", \"Head Retract Cycle Count\", \"\",\n\t\t\t\t\t\t\"Number of times the heads were loaded off the media (during power-offs or emergency conditions).\");\n\t\t\t\t// Intel SSD: Unsafe_Shutdown_Count (smartctl)\n\t\t\t\tadd(192, StorageDeviceDetectedType::AtaSsd, \"Unsafe_Shutdown_Count\", \"Unsafe Shutdown Count\", \"\",\n\t\t\t\t\t\t\"Raw value indicates the number of unsafe (unclean) shutdown events over the drive lifetime. \"\n\t\t\t\t\t\t\"An unsafe shutdown occurs whenever the device is powered off without \"\n\t\t\t\t\t\t\"STANDBY IMMEDIATE being the last command.\");\n\t\t\t\t// Various SSDs (smartctl)\n\t\t\t\tadd(192, StorageDeviceDetectedType::AtaSsd, \"Unexpect_Power_Loss_Ct\", \"Unexpected Power Loss Count\", \"\",\n\t\t\t\t\t\t\"Number of unexpected power loss events.\");\n\t\t\t\t// Fujitsu: Emergency Retract Cycle Count (smartctl)\n\t\t\t\tadd(192, StorageDeviceDetectedType::AtaHdd, \"Emerg_Retract_Cycle_Ct\", \"Emergency Retract Cycle Count\", \"\",\n\t\t\t\t\t\t\"Number of times the heads were loaded off the media during emergency conditions.\");\n\t\t\t\t// Load/Unload Cycle (smartctl)\n\t\t\t\tadd(193, StorageDeviceDetectedType::AtaHdd, \"Load_Cycle_Count\", \"Load / Unload Cycle\", \"\",\n\t\t\t\t\t\t\"Number of load / unload cycles into Landing Zone position.\");\n\t\t\t\t// Temperature Celsius (smartctl) (same as 231). This is the most common one. Some Samsungs: 10xTemp.\n\t\t\t\tadd(194, \"Temperature_Celsius\", \"Temperature (Celsius)\", \"attr_temperature_celsius\",\n\t\t\t\t\t\t\"Drive temperature. The Raw value shows built-in heat sensor registrations (in Celsius). \"\n\t\t\t\t\t\t\"Increases in average drive temperature often signal spindle motor problems (unless the increases are caused by environmental factors).\");\n\t\t\t\t// Samsung SSD: Temperature Celsius (smartctl) (not sure about the value)\n\t\t\t\tadd(194, StorageDeviceDetectedType::AtaSsd, \"Airflow_Temperature\", \"Airflow Temperature (Celsius)\", \"attr_temperature_celsius\",\n\t\t\t\t\t\t\"Drive temperature (Celsius)\");\n\t\t\t\t// Temperature Celsius x 10 (smartctl)\n\t\t\t\tadd(194, \"Temperature_Celsius_x10\", \"Temperature (Celsius) x 10\", \"attr_temperature_celsius_x10\",\n\t\t\t\t\t\t\"Drive temperature. The Raw value shows built-in heat sensor registrations (in Celsius * 10). \"\n\t\t\t\t\t\t\"Increases in average drive temperature often signal spindle motor problems (unless the increases are caused by environmental factors).\");\n\t\t\t\t// Smart Storage Systems SSD (smartctl)\n\t\t\t\tadd(194, StorageDeviceDetectedType::AtaSsd, \"Proprietary_194\", \"Internal Attribute\", \"\",\n\t\t\t\t\t\t\"This attribute has been reserved by vendor as internal.\");\n\t\t\t\t// Intel SSD (smartctl)\n\t\t\t\tadd(194, \"Temperature_Internal\", \"Internal Temperature (Celsius)\", \"attr_temperature_celsius\",\n\t\t\t\t\t\t\"Drive case temperature. The Raw value shows built-in heat sensor registrations (in Celsius).\");\n\t\t\t\t// Hardware ECC Recovered (smartctl)\n\t\t\t\tadd(195, \"Hardware_ECC_Recovered\", \"Hardware ECC Recovered\", \"\",\n\t\t\t\t\t\t\"Number of ECC on the fly errors (Raw value). Users are advised to ignore this attribute.\");\n\t\t\t\t// Fujitsu: ECC_On_The_Fly_Count (smartctl)\n\t\t\t\tadd(195, StorageDeviceDetectedType::AtaHdd, \"ECC_On_The_Fly_Count\");\n\t\t\t\t// Sandforce SSD: ECC_Uncorr_Error_Count (smartctl) (description?)\n\t\t\t\tadd(195, StorageDeviceDetectedType::AtaSsd, \"ECC_Uncorr_Error_Count\", \"Uncorrected ECC Error Count\", \"\",\n\t\t\t\t\t\t\"Number of uncorrectable errors (UECC).\");\n\t\t\t\t// Samsung SSD (smartctl) (description?)\n\t\t\t\tadd(195, StorageDeviceDetectedType::AtaSsd, \"ECC_Rate\", \"Uncorrected ECC Error Rate\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// OCZ SSD (smartctl)\n\t\t\t\tadd(195, StorageDeviceDetectedType::AtaSsd, \"Total_Prog_Failures\", \"Total Program Failures\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Indilinx Barefoot SSD: Program_Failure_Blk_Ct (smartctl) (description?)\n\t\t\t\tadd(195, StorageDeviceDetectedType::AtaSsd, \"Program_Failure_Blk_Ct\", \"Program Failure Block Count\", \"\",\n\t\t\t\t\t\t\"Number of flash program (write) failures.\");\n\t\t\t\t// Micron SSD (smartctl)\n\t\t\t\tadd(195, StorageDeviceDetectedType::AtaSsd, \"Cumulativ_Corrected_ECC\", \"Cumulative Corrected ECC Error Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Reallocation Event Count (smartctl)\n\t\t\t\tadd(196, std::nullopt, \"Reallocated_Event_Count\", \"Reallocation Event Count\", \"attr_reallocation_event_count\",\n\t\t\t\t\t\t\"Number of reallocation (remap) operations. Raw value <i>should</i> show the total number of attempts \"\n\t\t\t\t\t\t\"(both successful and unsuccessful) to reallocate sectors. An increase in Raw value indicates a disk surface failure.\"\n\t\t\t\t\t\t\"\\n\\n\" + get_suffix_for_uncorrectable_property_description());\n\t\t\t\t// Indilinx Barefoot SSD: Erase_Failure_Blk_Ct (smartctl) (description?)\n\t\t\t\tadd(196, StorageDeviceDetectedType::AtaSsd, \"Erase_Failure_Blk_Ct\", \"Erase Failure Block Count\", \"\",\n\t\t\t\t\t\t\"Number of flash erase failures.\");\n\t\t\t\t// OCZ SSD (smartctl)\n\t\t\t\tadd(196, StorageDeviceDetectedType::AtaSsd, \"Total_Erase_Failures\", \"Total Erase Failures\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Current Pending Sector Count (smartctl)\n\t\t\t\tadd(197, \"Current_Pending_Sector\", \"Current Pending Sector Count\", \"attr_current_pending_sector_count\",\n\t\t\t\t\t\t\"Number of &quot;unstable&quot; (waiting to be remapped) sectors (Raw value). \"\n\t\t\t\t\t\t\"If the unstable sector is subsequently read from or written to successfully, this value is decreased and the sector is not remapped. \"\n\t\t\t\t\t\t\"An increase in Raw value indicates a disk surface failure.\"\n\t\t\t\t\t\t\"\\n\\n\" + get_suffix_for_uncorrectable_property_description());\n\t\t\t\t// Indilinx Barefoot SSD: Read_Failure_Blk_Ct (smartctl) (description?)\n\t\t\t\tadd(197, StorageDeviceDetectedType::AtaSsd, \"Read_Failure_Blk_Ct\", \"Read Failure Block Count\", \"\",\n\t\t\t\t\t\t\"Number of blocks that failed to be read.\");\n\t\t\t\t// Samsung: Total_Pending_Sectors (smartctl). From smartctl man page:\n\t\t\t\t// unlike Current_Pending_Sector, this won't decrease on reallocation.\n\t\t\t\tadd(197, \"Total_Pending_Sectors\", \"Total Pending Sectors\", \"attr_total_pending_sectors\",\n\t\t\t\t\t\t\"Number of &quot;unstable&quot; (waiting to be remapped) sectors and already remapped sectors (Raw value). \"\n\t\t\t\t\t\t\"An increase in Raw value indicates a disk surface failure.\"\n\t\t\t\t\t\t\"\\n\\n\" + get_suffix_for_uncorrectable_property_description());\n\t\t\t\t// OCZ SSD (smartctl)\n\t\t\t\tadd(197, StorageDeviceDetectedType::AtaSsd, \"Total_Unc_Read_Failures\", \"Total Uncorrectable Read Failures\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Offline Uncorrectable (smartctl)\n\t\t\t\tadd(198, \"Offline_Uncorrectable\", \"Offline Uncorrectable\", \"attr_offline_uncorrectable\",\n\t\t\t\t\t\t\"Number of sectors which couldn't be corrected during Offline Data Collection (Raw value). \"\n\t\t\t\t\t\t\"An increase in Raw value indicates a disk surface failure. \"\n\t\t\t\t\t\t\"The value may be decreased automatically when the errors are corrected (e.g., when an unreadable sector is \"\n\t\t\t\t\t\t\"reallocated and the next Offline test is run to see the change).\"\n\t\t\t\t\t\t\"\\n\\n\" + get_suffix_for_uncorrectable_property_description());\n\t\t\t\t// Samsung: Offline Uncorrectable (smartctl). From smartctl man page:\n\t\t\t\t// unlike Current_Pending_Sector, this won't decrease on reallocation.\n\t\t\t\tadd(198, \"Total_Offl_Uncorrectabl\", \"Total Offline Uncorrectable\", \"attr_total_attr_offline_uncorrectable\",\n\t\t\t\t\t\t\"Number of sectors which couldn't be corrected during Offline Data Collection (Raw value), currently and in the past. \"\n\t\t\t\t\t\t\"An increase in Raw value indicates a disk surface failure.\"\n\t\t\t\t\t\t\"\\n\\n\" + get_suffix_for_uncorrectable_property_description());\n\t\t\t\t// Sandforce SSD: Uncorrectable_Sector_Ct (smartctl) (same description?)\n\t\t\t\tadd(198, StorageDeviceDetectedType::AtaSsd, \"Uncorrectable_Sector_Ct\");\n\t\t\t\t// Indilinx Barefoot SSD: Read_Sectors_Tot_Ct (smartctl) (description?)\n\t\t\t\tadd(198, StorageDeviceDetectedType::AtaSsd, \"Read_Sectors_Tot_Ct\", \"Total Read Sectors\", \"\",\n\t\t\t\t\t\t\"Total count of read sectors.\");\n\t\t\t\t// OCZ SSD\n\t\t\t\tadd(198, StorageDeviceDetectedType::AtaSsd, \"Host_Reads_GiB\", \"Host Read (GiB)\", \"\",\n\t\t\t\t\t\t\"Total number of sectors read by the host system. The Raw value is increased by 1 for every GiB read by the host.\");\n\t\t\t\t// Fujitsu: Offline_Scan_UNC_SectCt (smartctl)\n\t\t\t\tadd(198, StorageDeviceDetectedType::AtaHdd, \"Offline_Scan_UNC_SectCt\");\n\t\t\t\t// Fujitsu version of Offline Uncorrectable (smartctl) (old, not in current smartctl)\n\t\t\t\tadd(198, StorageDeviceDetectedType::AtaHdd, \"Off-line_Scan_UNC_Sector_Ct\");\n\t\t\t\t// UDMA CRC Error Count (smartctl)\n\t\t\t\tadd(199, \"UDMA_CRC_Error_Count\", \"UDMA CRC Error Count\", \"\",\n\t\t\t\t\t\t\"Number of errors in data transfer via the interface cable in UDMA mode, as determined by ICRC \"\n\t\t\t\t\t\t\"(Interface Cyclic Redundancy Check) (Raw value).\");\n\t\t\t\t// Sandforce SSD: SATA_CRC_Error_Count (smartctl) (description?)\n\t\t\t\tadd(199, \"SATA_CRC_Error_Count\", \"SATA CRC Error Count\", \"\",\n\t\t\t\t\t\t\"Number of errors in data transfer via the SATA interface cable (Raw value).\");\n\t\t\t\t// Sandisk SSD: SATA_CRC_Error_Count (smartctl) (description?)\n\t\t\t\tadd(199, \"SATA_CRC_Error\", \"SATA CRC Error Count\", \"\",\n\t\t\t\t\t\t\"Number of errors in data transfer via the SATA interface cable (Raw value).\");\n\t\t\t\t// Intel SSD, Samsung SSD (smartctl) (description?)\n\t\t\t\tadd(199, \"CRC_Error_Count\", \"CRC Error Count\", \"\",\n\t\t\t\t\t\t\"Number of errors in data transfer via the interface cable (Raw value).\");\n\t\t\t\t// Indilinx Barefoot SSD: Write_Sectors_Tot_Ct (smartctl) (description?)\n\t\t\t\tadd(199, StorageDeviceDetectedType::AtaSsd, \"Write_Sectors_Tot_Ct\", \"Total Written Sectors\", \"\",\n\t\t\t\t\t\t\"Total count of written sectors.\");\n\t\t\t\t// OCZ SSD\n\t\t\t\tadd(198, StorageDeviceDetectedType::AtaSsd, \"Host_Writes_GiB\", \"Host Written (GiB)\", \"\",\n\t\t\t\t\t\t\"Total number of sectors written by the host system. The Raw value is increased by 1 for every GiB written by the host.\");\n\t\t\t\t// WD: Multi-Zone Error Rate (smartctl). (maybe head flying height too (?))\n\t\t\t\tadd(200, StorageDeviceDetectedType::AtaHdd, \"Multi_Zone_Error_Rate\", \"Multi Zone Error Rate\", \"\",\n\t\t\t\t\t\t\"Number of errors found when writing to sectors (Raw value). The higher the value, the worse the disk surface condition and/or mechanical subsystem is.\");\n\t\t\t\t// Fujitsu: Write Error Rate (smartctl)\n\t\t\t\tadd(200, StorageDeviceDetectedType::AtaHdd, \"Write_Error_Count\", \"Write Error Count\", \"\",\n                        \"Number of errors found when writing to sectors (Raw value). The higher the value, the worse the disk surface condition and/or mechanical subsystem is.\");\n\t\t\t\t// Indilinx Barefoot SSD: Read_Commands_Tot_Ct (smartctl) (description?)\n\t\t\t\tadd(200, StorageDeviceDetectedType::AtaSsd, \"Read_Commands_Tot_Ct\", \"Total Read Commands Issued\", \"\",\n\t\t\t\t\t\t\"Total count of read commands issued.\");\n\t\t\t\t// Soft Read Error Rate (smartctl) (description?)\n\t\t\t\tadd(201, StorageDeviceDetectedType::AtaHdd, \"Soft_Read_Error_Rate\", \"Soft Read Error Rate\", \"attr_soft_read_error_rate\",\n\t\t\t\t\t\t\"Uncorrected read errors reported to the operating system (Raw value). If the value is non-zero, you should back up your data.\");\n\t\t\t\t// Sandforce SSD: Unc_Soft_Read_Err_Rate (smartctl)\n\t\t\t\tadd(201, StorageDeviceDetectedType::AtaSsd, \"Unc_Soft_Read_Err_Rate\");\n\t\t\t\t// Samsung SSD: (smartctl) (description?)\n\t\t\t\tadd(201, StorageDeviceDetectedType::AtaSsd, \"Supercap_Status\", \"Supercapacitor Health\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Maxtor: Off Track Errors (custom)\n// \t\t\t\tadd(201, StorageDeviceDetectedType::AtaHdd, \"\", \"Off Track Errors\", \"\",  // unused\n// \t\t\t\t\t\t\"\");\n\t\t\t\t// Fujitsu: Detected TA Count (smartctl) (description?)\n\t\t\t\tadd(201, StorageDeviceDetectedType::AtaHdd, \"Detected_TA_Count\", \"Torque Amplification Count\", \"\",\n\t\t\t\t\t\t\"Number of attempts to compensate for platter speed variations.\");\n\t\t\t\t// Indilinx Barefoot SSD: Write_Commands_Tot_Ct (smartctl) (description?)\n\t\t\t\tadd(201, StorageDeviceDetectedType::AtaSsd, \"Write_Commands_Tot_Ct\", \"Total Write Commands Issued\", \"\",\n\t\t\t\t\t\t\"Total count of write commands issued.\");\n\t\t\t\t// WD: Data Address Mark Errors (smartctl)\n\t\t\t\tadd(202, StorageDeviceDetectedType::AtaHdd, \"Data_Address_Mark_Errs\", \"Data Address Mark Errors\", \"\",\n\t\t\t\t\t\t\"Frequency of the Data Address Mark errors.\");\n\t\t\t\t// Fujitsu: TA Increase Count (same as 227?)\n\t\t\t\tadd(202, StorageDeviceDetectedType::AtaHdd, \"TA_Increase_Count\", \"TA Increase Count\", \"\",\n\t\t\t\t\t\t\"Number of attempts to compensate for platter speed variations.\");\n\t\t\t\t// Indilinx Barefoot SSD: Error_Bits_Flash_Tot_Ct (smartctl) (description?)\n\t\t\t\tadd(202, StorageDeviceDetectedType::AtaSsd, \"Error_Bits_Flash_Tot_Ct\", \"Total Count of Error Bits\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Crucial / Marvell SSD: Percent_Lifetime_Used (smartctl) (description?)\n\t\t\t\tadd(202, StorageDeviceDetectedType::AtaSsd, \"Percent_Lifetime_Used\", \"Rated Life Used (%)\", \"attr_ssd_life_used\",\n\t\t\t\t\t\t\"Used drive life in %.\");\n\t\t\t\t// Samsung SSD: (smartctl) (description?)\n\t\t\t\tadd(202, StorageDeviceDetectedType::AtaSsd, \"Exception_Mode_Status\", \"Exception Mode Status\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(202, StorageDeviceDetectedType::AtaSsd, \"Total_Read_Bits_Corr_Ct\", \"Total Read Bits Corrected\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Micron SSD (smartctl) (description?)\n\t\t\t\tadd(202, StorageDeviceDetectedType::AtaSsd, \"Percent_Lifetime_Remain\", \"Remaining Lifetime (%)\", \"attr_ssd_life_left\",\n\t\t\t\t\t\t\"Remaining drive life in %.\");\n\t\t\t\t// Run Out Cancel (smartctl). (description?)\n\t\t\t\tadd(203, \"Run_Out_Cancel\", \"Run Out Cancel\", \"\",\n\t\t\t\t\t\t\"Number of ECC errors.\");\n\t\t\t\t// Maxtor: ECC Errors (smartctl) (description?)\n\t\t\t\tadd(203, StorageDeviceDetectedType::AtaHdd, \"Corr_Read_Errors_Tot_Ct\", \"ECC Errors\", \"\",\n\t\t\t\t\t\t\"Number of ECC errors.\");\n\t\t\t\t// Indilinx Barefoot SSD: Corr_Read_Errors_Tot_Ct (smartctl) (description?)\n\t\t\t\tadd(203, StorageDeviceDetectedType::AtaSsd, \"Corr_Read_Errors_Tot_Ct\", \"Total Corrected Read Errors\", \"\",\n\t\t\t\t\t\t\"Total cound of read sectors with correctable errors.\");\n\t\t\t\t// Maxtor: Soft ECC Correction (smartctl)\n\t\t\t\tadd(204, StorageDeviceDetectedType::AtaHdd, \"Soft_ECC_Correction\", \"Soft ECC Correction\", \"\",\n\t\t\t\t\t\t\"Number of errors corrected by software ECC (Error-Correcting Code).\");\n\t\t\t\t// Fujitsu: Shock_Count_Write_Opern (smartctl) (description?)\n\t\t\t\tadd(204, StorageDeviceDetectedType::AtaHdd, \"Shock_Count_Write_Opern\", \"Shock Count During Write Operation\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Sandforce SSD: Soft_ECC_Correct_Rate (smartctl) (description?)\n\t\t\t\tadd(204, StorageDeviceDetectedType::AtaSsd, \"Soft_ECC_Correct_Rate\", \"Soft ECC Correction Rate\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Indilinx Barefoot SSD: Bad_Block_Full_Flag (smartctl) (description?)\n\t\t\t\tadd(204, StorageDeviceDetectedType::AtaSsd, \"Bad_Block_Full_Flag\", \"Bad Block Area Is Full\", \"\",\n\t\t\t\t\t\t\"Indicates whether the bad block (reserved) area is full or not.\");\n\t\t\t\t// Thermal Asperity Rate (TAR) (smartctl)\n\t\t\t\tadd(205, \"Thermal_Asperity_Rate\", \"Thermal Asperity Rate\", \"\",\n\t\t\t\t\t\t\"Number of problems caused by high temperature.\");\n\t\t\t\t// Fujitsu: Shock_Rate_Write_Opern (smartctl) (description?)\n\t\t\t\tadd(205, StorageDeviceDetectedType::AtaHdd, \"Shock_Rate_Write_Opern\", \"Shock Rate During Write Operation\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Indilinx Barefoot SSD: Max_PE_Count_Spec (smartctl) (description?)\n\t\t\t\tadd(205, StorageDeviceDetectedType::AtaSsd, \"Max_PE_Count_Spec\", \"Maximum Program-Erase Count Specification\", \"\",\n\t\t\t\t\t\t\"Maximum Program / Erase cycle count as per specification.\");\n\t\t\t\t// OCZ SSD (smartctl)\n\t\t\t\tadd(205, StorageDeviceDetectedType::AtaSsd, \"Max_Rated_PE_Count\", \"Maximum Rated Program-Erase Count\", \"\",\n\t\t\t\t\t\t\"Maximum Program / Erase cycle count as per specification.\");\n\t\t\t\t// Flying Height (smartctl)\n\t\t\t\tadd(206, StorageDeviceDetectedType::AtaHdd, \"Flying_Height\", \"Head Flying Height\", \"\",\n\t\t\t\t\t\t\"The height of the disk heads above the disk surface. A downward trend will often predict a head crash, \"\n\t\t\t\t\t\t\"while high values may cause read / write errors.\");\n\t\t\t\t// Indilinx Barefoot SSD, OCZ SSD: Min_Erase_Count (smartctl) (description?)\n\t\t\t\tadd(206, StorageDeviceDetectedType::AtaSsd, \"Min_Erase_Count\", \"Minimum Erase Count\", \"\",\n\t\t\t\t\t\t\"The minimum of individual erase counts of all the blocks.\");\n\t\t\t\t// Crucial / Marvell SSD: Write_Error_Rate (smartctl) (description?)\n\t\t\t\tadd(206, StorageDeviceDetectedType::AtaSsd, \"Write_Error_Rate\", \"Write Error Rate\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Spin High Current (smartctl)\n\t\t\t\tadd(207, StorageDeviceDetectedType::AtaHdd, \"Spin_High_Current\", \"Spin High Current\", \"\",\n\t\t\t\t\t\t\"Amount of high current needed or used to spin up the drive.\");\n\t\t\t\t// Indilinx Barefoot SSD, OCZ SSD: Max_Erase_Count (smartctl) (description?)\n\t\t\t\tadd(207, StorageDeviceDetectedType::AtaSsd, \"Max_Erase_Count\", \"Maximum Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Spin Buzz (smartctl)\n\t\t\t\tadd(208, StorageDeviceDetectedType::AtaHdd, \"Spin_Buzz\", \"Spin Buzz\", \"\",\n\t\t\t\t\t\t\"Number of buzz routines (retries because of low current) to spin up the drive.\");\n\t\t\t\t// Indilinx Barefoot SSD, OCZ SSD: Average_Erase_Count (smartctl) (description?)\n\t\t\t\tadd(208, StorageDeviceDetectedType::AtaSsd, \"Average_Erase_Count\", \"Average Erase Count\", \"\",\n\t\t\t\t\t\t\"The average of individual erase counts of all the blocks.\");\n\t\t\t\t// Offline Seek Performance (smartctl) (description?)\n\t\t\t\tadd(209, StorageDeviceDetectedType::AtaHdd, \"Offline_Seek_Performnce\", \"Offline Seek Performance\", \"\",\n\t\t\t\t\t\t\"Seek performance during Offline Data Collection operations.\");\n\t\t\t\t// Indilinx Barefoot SSD, OCZ SSD: Remaining_Lifetime_Perc (smartctl) (description?)\n\t\t\t\tadd(209, StorageDeviceDetectedType::AtaSsd, \"Remaining_Lifetime_Perc\", \"Remaining Lifetime (%)\", \"attr_ssd_life_left\",\n\t\t\t\t\t\t\"Remaining drive life in % (usually by erase count).\");\n\t\t\t\t// Vibration During Write (custom). wikipedia says 211, but it's wrong. (description?)\n\t\t\t\tadd(210, StorageDeviceDetectedType::AtaHdd, \"\", \"Vibration During Write\", \"\",\n\t\t\t\t\t\t\"Vibration encountered during write operations.\");\n\t\t\t\t// OCZ SSD (smartctl)\n\t\t\t\tadd(210, StorageDeviceDetectedType::AtaSsd, \"SATA_CRC_Error_Count\", \"SATA CRC Error Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Indilinx Barefoot SSD: Indilinx_Internal (smartctl) (description?)\n\t\t\t\tadd(210, StorageDeviceDetectedType::AtaSsd, \"Indilinx_Internal\", \"Internal Attribute\", \"\",\n\t\t\t\t\t\t\"This attribute has been reserved by vendor as internal.\");\n\t\t\t\t// Crucial / Micron SSD (smartctl)\n\t\t\t\tadd(210, StorageDeviceDetectedType::AtaSsd, \"Success_RAIN_Recov_Cnt\", \"Success RAIN Recovered Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Vibration During Read (description?)\n\t\t\t\tadd(211, StorageDeviceDetectedType::AtaHdd, \"\", \"Vibration During Read\", \"\",\n\t\t\t\t\t\t\"Vibration encountered during read operations.\");\n\t\t\t\t// Indilinx Barefoot SSD (smartctl) (description?)\n\t\t\t\tadd(211, StorageDeviceDetectedType::AtaSsd, \"SATA_Error_Ct_CRC\", \"SATA CRC Error Count\", \"\",\n\t\t\t\t\t\t\"Number of errors in data transfer via the SATA interface cable\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(211, StorageDeviceDetectedType::AtaSsd, \"SATA_UNC_Count\", \"SATA Uncorrectable Error Count\", \"\",\n\t\t\t\t\t\t\"Number of errors in data transfer via the SATA interface cable\");\n\t\t\t\t// Shock During Write (custom) (description?)\n\t\t\t\tadd(212, StorageDeviceDetectedType::AtaHdd, \"\", \"Shock During Write\", \"\",\n\t\t\t\t\t\t\"Shock encountered during write operations\");\n\t\t\t\t// Indilinx Barefoot SSD: SATA_Error_Ct_Handshake (smartctl) (description?)\n\t\t\t\tadd(212, StorageDeviceDetectedType::AtaSsd, \"SATA_Error_Ct_Handshake\", \"SATA Handshake Error Count\", \"\",\n\t\t\t\t\t\t\"Number of errors occurring during SATA handshake.\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(212, StorageDeviceDetectedType::AtaSsd, \"Pages_Requiring_Rd_Rtry\", \"Pages Requiring Read Retry\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(212, StorageDeviceDetectedType::AtaSsd, \"NAND_Reads_with_Retry\", \"Number of NAND Reads with Retry\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Sandisk SSDs: (smartctl) (description?)\n\t\t\t\tadd(212, StorageDeviceDetectedType::AtaSsd, \"SATA_PHY_Error\", \"SATA Physical Error Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Indilinx Barefoot SSD: Indilinx_Internal (smartctl) (description?)\n\t\t\t\tadd(213, StorageDeviceDetectedType::AtaSsd, \"Indilinx_Internal\", \"Internal Attribute\", \"\",\n\t\t\t\t\t\t\"This attribute has been reserved by vendor as internal.\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(213, StorageDeviceDetectedType::AtaSsd, \"Simple_Rd_Rtry_Attempts\", \"Simple Read Retry Attempts\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(213, StorageDeviceDetectedType::AtaSsd, \"Snmple_Retry_Attempts\", \"Simple Retry Attempts\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(213, StorageDeviceDetectedType::AtaSsd, \"Simple_Retry_Attempts\", \"Simple Retry Attempts\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(213, StorageDeviceDetectedType::AtaSsd, \"Adaptv_Rd_Rtry_Attempts\", \"Adaptive Read Retry Attempts\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(214, StorageDeviceDetectedType::AtaSsd, \"Adaptive_Retry_Attempts\", \"Adaptive Retry Attempts\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Kingston SSD (smartctl)\n\t\t\t\tadd(218, StorageDeviceDetectedType::AtaSsd, \"CRC_Error_Count\", \"CRC Error Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Disk Shift (smartctl)\n\t\t\t\t// Note: There's also smartctl shortcut option \"-v 220,temp\" (possibly for Temperature Celsius),\n\t\t\t\t// but it's not used anywhere, so we ignore it.\n\t\t\t\tadd(220, StorageDeviceDetectedType::AtaHdd, \"Disk_Shift\", \"Disk Shift\", \"\",\n\t\t\t\t\t\t\"Shift of disks towards spindle. Shift of disks is possible as a result of a strong shock or a fall, high temperature, or some other reasons.\");\n\t\t\t\t// G-sense error rate (smartctl)\n\t\t\t\tadd(221, StorageDeviceDetectedType::AtaHdd, \"G-Sense_Error_Rate\", \"G-Sense Error Rate\", \"\",\n\t\t\t\t\t\t\"Number of errors resulting from externally-induced shock and vibration (Raw value). May indicate incorrect installation.\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(213, StorageDeviceDetectedType::AtaSsd, \"Int_Data_Path_Prot_Unc\", \"Internal Data Path Protection Uncorrectable\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Loaded Hours (smartctl)\n\t\t\t\tadd(222, StorageDeviceDetectedType::AtaHdd, \"Loaded_Hours\", \"Loaded Hours\", \"\",\n\t\t\t\t\t\t\"Number of hours spent operating under load (movement of magnetic head armature) (Raw value)\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(222, StorageDeviceDetectedType::AtaSsd, \"RAID_Recovery_Count\", \"RAID Recovery Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Load/Unload Retry Count (smartctl) (description?)\n\t\t\t\tadd(223, StorageDeviceDetectedType::AtaHdd, \"Load_Retry_Count\", \"Load / Unload Retry Count\", \"\",\n\t\t\t\t\t\t\"Number of times the head armature entered / left the data zone.\");\n\t\t\t\t// Load Friction (smartctl)\n\t\t\t\tadd(224, StorageDeviceDetectedType::AtaHdd, \"Load_Friction\", \"Load Friction\", \"\",\n\t\t\t\t\t\t\"Resistance caused by friction in mechanical parts while operating. An increase of Raw value may mean that there is \"\n\t\t\t\t\t\t\"a problem with the mechanical subsystem of the drive.\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(224, StorageDeviceDetectedType::AtaSsd, \"In_Warranty\", \"In Warranty\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Load/Unload Cycle Count (smartctl) (description?)\n\t\t\t\tadd(225, StorageDeviceDetectedType::AtaHdd, \"Load_Cycle_Count\", \"Load / Unload Cycle Count\", \"\",\n\t\t\t\t\t\t\"Total number of load cycles.\");\n\t\t\t\t// Intel SSD: Host_Writes_32MiB (smartctl) (description?)\n\t\t\t\tadd(225, StorageDeviceDetectedType::AtaSsd, \"Host_Writes_32MiB\", \"Host Written (32 MiB)\", \"\",\n\t\t\t\t\t\t\"Total number of sectors written by the host system. The Raw value is increased by 1 for every 32 MiB written by the host.\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(225, StorageDeviceDetectedType::AtaSsd, \"DAS_Polarity\", \"DAS Polarity\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Innodisk SSDs: (smartctl) (description?)\n\t\t\t\tadd(225, StorageDeviceDetectedType::AtaSsd, \"Data_Log_Write_Count\", \"Data Log Write Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Load-in Time (smartctl)\n\t\t\t\tadd(226, StorageDeviceDetectedType::AtaHdd, \"Load-in_Time\", \"Load-in Time\", \"\",\n\t\t\t\t\t\t\"Total time of loading on the magnetic heads actuator. Indicates total time in which the drive was under load \"\n\t\t\t\t\t\t\"(on the assumption that the magnetic heads were in operating mode and out of the parking area).\");\n\t\t\t\t// Intel SSD: Intel_Internal (smartctl)\n\t\t\t\tadd(226, StorageDeviceDetectedType::AtaSsd, \"Intel_Internal\", \"Internal Attribute\", \"\",\n\t\t\t\t\t\t\"This attribute has been reserved by vendor as internal.\");\n\t\t\t\t// Intel SSD: Workld_Media_Wear_Indic (smartctl)\n\t\t\t\tadd(226, StorageDeviceDetectedType::AtaSsd, \"Workld_Media_Wear_Indic\", \"Timed Workload Media Wear\", \"\",\n\t\t\t\t\t\t\"Timed workload media wear indicator (percent*1024)\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(226, StorageDeviceDetectedType::AtaSsd, \"Partial_Pfail\", \"Partial Program Fail\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Torque Amplification Count (aka TA) (smartctl)\n\t\t\t\tadd(227, StorageDeviceDetectedType::AtaHdd, \"Torq-amp_Count\", \"Torque Amplification Count\", \"\",\n\t\t\t\t\t\t\"Number of attempts to compensate for platter speed variations.\");\n\t\t\t\t// Intel SSD: Intel_Internal (smartctl)\n\t\t\t\tadd(227, StorageDeviceDetectedType::AtaSsd, \"Intel_Internal\", \"Internal Attribute\", \"\",\n\t\t\t\t\t\t\"This attribute has been reserved by vendor as internal.\");\n\t\t\t\t// Intel SSD: Workld_Host_Reads_Perc (smartctl)\n\t\t\t\tadd(227, StorageDeviceDetectedType::AtaSsd, \"Workld_Host_Reads_Perc\", \"Timed Workload Host Reads %\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Power-Off Retract Count (smartctl)\n\t\t\t\tadd(228, \"Power-off_Retract_Count\", \"Power-Off Retract Count\", \"\",\n\t\t\t\t\t\t\"Number of times the magnetic armature was retracted automatically as a result of power loss.\");\n\t\t\t\t// Intel SSD: Intel_Internal (smartctl)\n\t\t\t\tadd(228, StorageDeviceDetectedType::AtaSsd, \"Intel_Internal\", \"Internal Attribute\", \"\",\n\t\t\t\t\t\t\"This attribute has been reserved by vendor as internal.\");\n\t\t\t\t// Intel SSD: Workload_Minutes (smartctl)\n\t\t\t\tadd(228, StorageDeviceDetectedType::AtaSsd, \"Workload_Minutes\", \"Workload (Minutes)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Transcend SSD: Halt_System_ID (smartctl) (description?)\n\t\t\t\tadd(229, StorageDeviceDetectedType::AtaSsd, \"Halt_System_ID\", \"Halt System ID\", \"\",\n\t\t\t\t\t\t\"Halt system ID and flash ID\");\n\t\t\t\t// InnoDisk SSD (smartctl)\n\t\t\t\tadd(229, StorageDeviceDetectedType::AtaSsd, \"Flash_ID\", \"Flash ID\", \"\",\n\t\t\t\t\t\t\"Flash ID\");\n\t\t\t\t// IBM: GMR Head Amplitude (smartctl)\n\t\t\t\tadd(230, StorageDeviceDetectedType::AtaHdd, \"Head_Amplitude\", \"GMR Head Amplitude\", \"\",\n\t\t\t\t\t\t\"Amplitude of heads trembling (GMR-head) in running mode.\");\n\t\t\t\t// Sandforce SSD: Life_Curve_Status (smartctl) (description?)\n\t\t\t\tadd(230, StorageDeviceDetectedType::AtaSsd, \"Life_Curve_Status\", \"Life Curve Status\", \"\",\n\t\t\t\t\t\t\"Current state of drive operation based upon the Life Curve.\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(230, StorageDeviceDetectedType::AtaSsd, \"SuperCap_Charge_Status\", \"Super-Capacitor Charge Status\", \"\",\n\t\t\t\t\t\t\"0 means not charged, 1 - fully charged, 2 - unknown.\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(230, StorageDeviceDetectedType::AtaSsd, \"Write_Throttling\", \"Write Throttling\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Sandisk SSD (smartctl) (description?)\n\t\t\t\tadd(230, StorageDeviceDetectedType::AtaSsd, \"Perc_Write/Erase_Count\", \"Write / Erase Count (%)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Temperature (Some drives) (smartctl)\n\t\t\t\tadd(231, \"Temperature_Celsius\", \"Temperature\", \"attr_temperature_celsius\",\n\t\t\t\t\t\t\"Drive temperature. The Raw value shows built-in heat sensor registrations (in Celsius). \"\n\t\t\t\t\t\t\"Increases in average drive temperature often signal spindle motor problems (unless the increases are caused by environmental factors).\");\n\t\t\t\t// Sandforce SSD: SSD_Life_Left\n\t\t\t\tadd(231, StorageDeviceDetectedType::AtaSsd, \"SSD_Life_Left\", \"SSD Life Left\", \"attr_ssd_life_left\",\n\t\t\t\t\t\t\"A measure of drive's estimated life left. A Normalized value of 100 indicates a new drive. \"\n\t\t\t\t\t\t\"10 means there are reserved blocks left but Program / Erase cycles have been used. \"\n\t\t\t\t\t\t\"0 means insufficient reserved blocks, drive may be in read-only mode to allow recovery of the data.\");\n\t\t\t\t// Intel SSD: Available_Reservd_Space (smartctl) (description?)\n\t\t\t\tadd(232, StorageDeviceDetectedType::AtaSsd, \"Available_Reservd_Space\", \"Available reserved space\", \"\",\n\t\t\t\t\t\t\"Number of reserved blocks remaining. The Normalized value indicates percentage, with 100 meaning new and 10 meaning the drive being close to its end of life.\");\n\t\t\t\t// Transcend SSD: Firmware_Version_information (smartctl) (description?)\n\t\t\t\tadd(232, StorageDeviceDetectedType::AtaSsd, \"Firmware_Version_Info\", \"Firmware Version Information\", \"\",\n\t\t\t\t\t\t\"Firmware version information (year, month, day, channels, banks).\");\n\t\t\t\t// Same as Firmware_Version_Info, but in older smartctl versions.\n\t\t\t\tadd(232, StorageDeviceDetectedType::AtaSsd, \"Firmware_Version_information\", \"Firmware Version Information\", \"\",\n\t\t\t\t\t\t\"Firmware version information (year, month, day, channels, banks).\");\n\t\t\t\t// OCZ SSD (description?) (smartctl)\n\t\t\t\tadd(232, StorageDeviceDetectedType::AtaSsd, \"Lifetime_Writes\", \"Lifetime_Writes\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Kingston SSD (description?) (smartctl)\n\t\t\t\tadd(232, StorageDeviceDetectedType::AtaSsd, \"Flash_Writes_GiB\", \"Flash Written (GiB)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Innodisk SSD (description?) (smartctl)\n\t\t\t\tadd(232, StorageDeviceDetectedType::AtaSsd, \"Spares_Remaining_Perc\", \"Spare Blocks Remaining (%)\", \"attr_ssd_life_left\",\n\t\t\t\t\t\t\"Percentage of spare blocks remaining. Spare blocks are used when bad blocks develop.\");\n\t\t\t\t// Innodisk SSD (description?) (smartctl)\n\t\t\t\tadd(232, StorageDeviceDetectedType::AtaSsd, \"Perc_Avail_Resrvd_Space\", \"Available Reserved Space (%)\", \"attr_ssd_life_left\",\n\t\t\t\t\t\t\"Percentage of spare blocks remaining. Spare blocks are used when bad blocks develop.\");\n\t\t\t\t// Intel SSD: Media_Wearout_Indicator (smartctl) (description?)\n\t\t\t\tadd(233, StorageDeviceDetectedType::AtaSsd, \"Media_Wearout_Indicator\", \"Media Wear Out Indicator\", \"attr_ssd_life_left\",\n\t\t\t\t\t\t\"Number of cycles the NAND media has experienced. The Normalized value decreases linearly from 100 to 1 as the average erase cycle \"\n\t\t\t\t\t\t\"count increases from 0 to the maximum rated cycles.\");\n\t\t\t\t// OCZ SSD\n\t\t\t\tadd(233, StorageDeviceDetectedType::AtaSsd, \"Remaining_Lifetime_Perc\", \"Remaining Lifetime %\", \"attr_ssd_life_left\",\n\t\t\t\t\t\t\"Remaining drive life in % (usually by erase count).\");\n\t\t\t\t// Sandforce SSD: SandForce_Internal (smartctl) (description?)\n\t\t\t\tadd(233, StorageDeviceDetectedType::AtaSsd, \"SandForce_Internal\", \"Internal Attribute\", \"\",\n\t\t\t\t\t\t\"This attribute has been reserved by vendor as internal.\");\n\t\t\t\t// Transcend SSD: ECC_Fail_Record (smartctl) (description?)\n\t\t\t\tadd(233, StorageDeviceDetectedType::AtaSsd, \"ECC_Fail_Record\", \"ECC Failure Record\", \"\",\n\t\t\t\t\t\t\"Indicates rate of ECC (error-correcting code) failures.\");\n\t\t\t\t// Innodisk SSD (smartctl) (description?)\n\t\t\t\tadd(233, StorageDeviceDetectedType::AtaSsd, \"Flash_Writes_32MiB\", \"Flash Written (32MiB)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Innodisk SSD (smartctl) (description?)\n\t\t\t\tadd(233, StorageDeviceDetectedType::AtaSsd, \"Total_NAND_Writes_GiB\", \"Total NAND Written (GiB)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Sandforce SSD: SandForce_Internal (smartctl) (description?)\n\t\t\t\tadd(234, StorageDeviceDetectedType::AtaSsd, \"SandForce_Internal\", \"Internal Attribute\", \"\",\n\t\t\t\t\t\t\"This attribute has been reserved by vendor as internal.\");\n\t\t\t\t// Intel SSD (smartctl)\n\t\t\t\tadd(234, StorageDeviceDetectedType::AtaSsd, \"Thermal_Throttle\", \"Thermal Throttle\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Transcend SSD: Erase_Count_Avg (smartctl) (description?)\n\t\t\t\tadd(234, StorageDeviceDetectedType::AtaSsd, \"Erase_Count_Avg/Max\", \"Erase Count Average / Maximum\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Innodisk SSD (smartctl) (description?)\n\t\t\t\tadd(234, StorageDeviceDetectedType::AtaSsd, \"Flash_Reads_32MiB\", \"Flash Read (32MiB)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Sandisk SSD (smartctl) (description / name?)\n\t\t\t\tadd(234, StorageDeviceDetectedType::AtaSsd, \"Perc_Write/Erase_Ct_BC\", \"Write / Erase Count BC (%)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Sandforce SSD: SuperCap_Health (smartctl) (description?)\n\t\t\t\tadd(235, StorageDeviceDetectedType::AtaSsd, \"SuperCap_Health\", \"Supercapacitor Health\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Transcend SSD: Block_Count_Good/System (smartctl) (description?)\n\t\t\t\tadd(235, StorageDeviceDetectedType::AtaSsd, \"Block_Count_Good/System\", \"Good / System Free Block Count\", \"\",\n\t\t\t\t\t\t\"Good block count and system free block count.\");\n\t\t\t\t// InnoDisk SSD (smartctl). (description / name?)\n\t\t\t\tadd(235, StorageDeviceDetectedType::AtaSsd, \"Later_Bad_Block\", \"Later Bad Block\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// InnoDisk SSD (smartctl). (description / name?)\n\t\t\t\tadd(235, StorageDeviceDetectedType::AtaSsd, \"Later_Bad_Blk_Inf_R/W/E\", \"Later Bad Block Read / Write / Erase\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Samsung SSD (smartctl). (description / name?)\n\t\t\t\tadd(235, StorageDeviceDetectedType::AtaSsd, \"POR_Recovery_Count\", \"POR Recovery Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// InnoDisk SSD (smartctl). (description / name?)\n\t\t\t\tadd(236, StorageDeviceDetectedType::AtaSsd, \"Unstable_Power_Count\", \"Unstable Power Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Head Flying Hours (smartctl)\n\t\t\t\tadd(240, StorageDeviceDetectedType::AtaHdd, \"Head_Flying_Hours\", \"Head Flying Hours\", \"\",\n\t\t\t\t\t\t\"Time spent on head is positioning.\");\n\t\t\t\t// Fujitsu: Transfer_Error_Rate (smartctl) (description?)\n\t\t\t\tadd(240, StorageDeviceDetectedType::AtaHdd, \"Transfer_Error_Rate\", \"Transfer Error Rate\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// InnoDisk SSD (smartctl). (description / name?)\n\t\t\t\tadd(240, StorageDeviceDetectedType::AtaSsd, \"Write_Head\", \"Write Head\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Total_LBAs_Written (smartctl) (description?)\n\t\t\t\tadd(241, \"Total_LBAs_Written\", \"Total LBAs Written\", \"\",\n\t\t\t\t\t\t\"Logical blocks written during lifetime.\");\n\t\t\t\t// Sandforce SSD: Lifetime_Writes_GiB (smartctl) (maybe in 64GiB increments?)\n\t\t\t\tadd(241, StorageDeviceDetectedType::AtaSsd, \"Lifetime_Writes_GiB\", \"Total GiB Written\", \"\",\n\t\t\t\t\t\t\"Total GiB written during lifetime.\");\n\t\t\t\t// Intel SSD: Host_Writes_32MiB (smartctl) (description?)\n\t\t\t\tadd(241, StorageDeviceDetectedType::AtaSsd, \"Host_Writes_32MiB\", \"Host Written (32 MiB)\", \"\",\n\t\t\t\t\t\t\"Total number of sectors written by the host system. The Raw value is increased by 1 for every 32 MiB written by the host.\");\n\t\t\t\t// OCZ SSD (smartctl)\n\t\t\t\tadd(241, StorageDeviceDetectedType::AtaSsd, \"Host_Writes_GiB\", \"Host Written (GiB)\", \"\",\n\t\t\t\t\t\t\"Total number of sectors written by the host system. The Raw value is increased by 1 for every GiB written by the host.\");\n\t\t\t\t// Sandisk SSD (smartctl)\n\t\t\t\tadd(241, StorageDeviceDetectedType::AtaSsd, \"Total_Writes_GiB\", \"Total Written (GiB)\", \"\",\n\t\t\t\t\t\t\"Total GiB written.\");\n\t\t\t\t// Toshiba SSD (smartctl)\n\t\t\t\tadd(241, StorageDeviceDetectedType::AtaSsd, \"Host_Writes\", \"Host Written\", \"\",\n\t\t\t\t\t\t\"Total number of sectors written by the host system.\");\n\t\t\t\t// Total_LBAs_Read (smartctl) (description?)\n\t\t\t\tadd(242, \"Total_LBAs_Read\", \"Total LBAs Read\", \"\",\n\t\t\t\t\t\t\"Logical blocks read during lifetime.\");\n\t\t\t\t// Sandforce SSD: Lifetime_Writes_GiB (smartctl) (maybe in 64GiB increments?)\n\t\t\t\tadd(242, StorageDeviceDetectedType::AtaSsd, \"Lifetime_Reads_GiB\", \"Total GiB Read\", \"\",\n\t\t\t\t\t\t\"Total GiB read during lifetime.\");\n\t\t\t\t// Intel SSD: Host_Reads_32MiB (smartctl) (description?)\n\t\t\t\tadd(242, StorageDeviceDetectedType::AtaSsd, \"Host_Reads_32MiB\", \"Host Read (32 MiB)\", \"\",\n\t\t\t\t\t\t\"Total number of sectors read by the host system. The Raw value is increased by 1 for every 32 MiB read by the host.\");\n\t\t\t\t// OCZ SSD (smartctl)\n\t\t\t\tadd(242, StorageDeviceDetectedType::AtaSsd, \"Host_Reads_GiB\", \"Host Read (GiB)\", \"\",\n\t\t\t\t\t\t\"Total number of sectors read by the host system. The Raw value is increased by 1 for every GiB read by the host.\");\n\t\t\t\t// Marvell SSD (smartctl)\n\t\t\t\tadd(242, StorageDeviceDetectedType::AtaSsd, \"Host_Reads\", \"Host Read\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Sandisk SSD (smartctl)\n\t\t\t\tadd(241, StorageDeviceDetectedType::AtaSsd, \"Total_Reads_GiB\", \"Total Read (GiB)\", \"\",\n\t\t\t\t\t\t\"Total GiB read.\");\n\t\t\t\t// Intel SSD: (smartctl) (description?)\n\t\t\t\tadd(243, StorageDeviceDetectedType::AtaSsd, \"NAND_Writes_32MiB\", \"NAND Written (32MiB)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Samsung SSD (smartctl). (description / name?)\n\t\t\t\tadd(243, StorageDeviceDetectedType::AtaSsd, \"SATA_Downshift_Ct\", \"SATA Downshift Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Kingston SSDs (description?) (smartctl)\n\t\t\t\tadd(244, StorageDeviceDetectedType::AtaSsd, \"Average_Erase_Count\", \"Average Erase Count\", \"\",\n\t\t\t\t\t\t\"The average of individual erase counts of all the blocks\");\n\t\t\t\t// Samsung SSDs (description?) (smartctl)\n\t\t\t\tadd(244, StorageDeviceDetectedType::AtaSsd, \"Thermal_Throttle_St\", \"Thermal Throttle Status\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Sandisk SSDs (description?) (smartctl)\n\t\t\t\tadd(244, StorageDeviceDetectedType::AtaSsd, \"Thermal_Throttle\", \"Thermal Throttle Status\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Kingston SSDs (smartctl)\n\t\t\t\tadd(245, StorageDeviceDetectedType::AtaSsd, \"Max_Erase_Count\", \"Maximum Erase Count\", \"\",\n\t\t\t\t\t\t\"The maximum of individual erase counts of all the blocks.\");\n\t\t\t\t// Innodisk SSD (smartctl) (description?)\n\t\t\t\tadd(245, StorageDeviceDetectedType::AtaSsd, \"Flash_Writes_32MiB\", \"Flash Written (32MiB)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Samsung SSD (smartctl) (description?)\n\t\t\t\tadd(245, StorageDeviceDetectedType::AtaSsd, \"Timed_Workld_Media_Wear\", \"Timed Workload Media Wear\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// SiliconMotion SSD (smartctl) (description?)\n\t\t\t\tadd(245, StorageDeviceDetectedType::AtaSsd, \"TLC_Writes_32MiB\", \"TLC Written (32MiB)\", \"\",\n\t\t\t\t\t\t\"Total number of sectors written to TLC. The Raw value is increased by 1 for every 32 MiB written by the host.\");\n\t\t\t\t// Crucial / Micron SSD (smartctl)\n\t\t\t\tadd(246, StorageDeviceDetectedType::AtaSsd, \"Total_Host_Sector_Write\", \"Total Host Sectors Written\", \"\",\n\t\t\t\t\t\t\"Total number of sectors written by the host system.\");\n\t\t\t\t// Kingston SSDs (description?) (smartctl)\n\t\t\t\tadd(246, StorageDeviceDetectedType::AtaSsd, \"Total_Erase_Count\", \"Total Erase Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Samsung SSD (smartctl) (description?)\n\t\t\t\tadd(246, StorageDeviceDetectedType::AtaSsd, \"Timed_Workld_RdWr_Ratio\", \"Timed Workload Read/Write Ratio\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// SiliconMotion SSD (smartctl) (description?)\n\t\t\t\tadd(246, StorageDeviceDetectedType::AtaSsd, \"SLC_Writes_32MiB\", \"SLC Written (32MiB)\", \"\",\n\t\t\t\t\t\t\"Total number of sectors written to SLC. The Raw value is increased by 1 for every 32 MiB written by the host.\");\n\t\t\t\t// Crucial / Micron SSD (smartctl)\n\t\t\t\tadd(247, StorageDeviceDetectedType::AtaSsd, \"Host_Program_Page_Count\", \"Host Program Page Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Samsung SSD (smartctl)\n\t\t\t\tadd(247, StorageDeviceDetectedType::AtaSsd, \"Timed_Workld_Timer\", \"Timed Workload Timer\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// SiliconMotion SSD (smartctl) (description?)\n\t\t\t\tadd(247, StorageDeviceDetectedType::AtaSsd, \"Raid_Recoverty_Ct\", \"RAID Recovery Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\tadd(248, StorageDeviceDetectedType::AtaSsd, \"Bckgnd_Program_Page_Cnt\", \"Background Program Page Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Intel SSD: NAND_Writes_1GiB (smartctl) (description?)\n\t\t\t\tadd(249, StorageDeviceDetectedType::AtaSsd, \"NAND_Writes_1GiB\", \"NAND Written (1GiB)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// OCZ SSD: Total_NAND_Prog_Ct_GiB (smartctl) (description?)\n\t\t\t\tadd(249, StorageDeviceDetectedType::AtaSsd, \"Total_NAND_Prog_Ct_GiB\", \"Total NAND Written (1GiB)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Read Error Retry Rate (smartctl) (description?)\n\t\t\t\tadd(250, \"Read_Error_Retry_Rate\", \"Read Error Retry Rate\", \"\",\n\t\t\t\t\t\t\"Number of errors found while reading.\");\n\t\t\t\t// Samsung SSD: (smartctl) (description?)\n\t\t\t\tadd(183, std::nullopt, \"SATA_Iface_Downshift\", \"SATA Downshift Error Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// OCZ SSD (smartctl) (description?)\n\t\t\t\tadd(251, StorageDeviceDetectedType::AtaSsd, \"Total_NAND_Read_Ct_GiB\", \"Total NAND Read (1GiB)\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Samsung SSD: (smartctl) (description?)\n\t\t\t\tadd(251, std::nullopt, \"NAND_Writes\", \"NAND Write Count\", \"\",\n\t\t\t\t\t\t\"\");\n\t\t\t\t// Free Fall Protection (smartctl) (seagate laptop drives)\n\t\t\t\tadd(254, StorageDeviceDetectedType::AtaHdd, \"Free_Fall_Sensor\", \"Free Fall Protection\", \"\",\n\t\t\t\t\t\t\"Number of free fall events detected by accelerometer sensor.\");\n\t\t\t}\n\n\n\t\t\t/// Add an attribute description to the attribute database\n\t\t\tvoid add(AtaAttributeDescription descr)\n\t\t\t{\n\t\t\t\tid_db[descr.id].emplace_back(std::move(descr));\n\t\t\t}\n\n\n\t\t\t/// Add an attribute description to the attribute database\n\t\t\tvoid add(int32_t id, std::string reported_name, std::string displayable_name,\n\t\t\t\t\tstd::string generic_name, std::string description)\n\t\t\t{\n\t\t\t\tadd(AtaAttributeDescription(id, std::nullopt,\n\t\t\t\t\t\tstd::move(reported_name), std::move(displayable_name), std::move(generic_name), std::move(description)));\n\t\t\t}\n\n\n\t\t\t/// Add a previously added description to the attribute database under a\n\t\t\t/// different smartctl name (fill the other members from the previous attribute).\n// \t\t\tvoid add(int32_t id, const std::string& reported_name)\n// \t\t\t{\n// \t\t\t\tauto iter = id_db.find(id);\n// \t\t\t\tDBG_ASSERT(iter != id_db.end() && !iter->second.empty());\n// \t\t\t\tif (iter != id_db.end() || iter->second.empty()) {\n// \t\t\t\t\tAttributeDescription attr = iter->second.front();\n// \t\t\t\t\tadd(AttributeDescription(id, std::nullopt, reported_name, attr.displayable_name, attr.generic_name, attr.description));\n// \t\t\t\t}\n// \t\t\t}\n\n\t\t\t/// Add an attribute description to the attribute database\n\t\t\tvoid add(int32_t id, std::optional<StorageDeviceDetectedType> type, std::string reported_name, std::string displayable_name,\n\t\t\t\t\tstd::string generic_name, std::string description)\n\t\t\t{\n\t\t\t\tadd(AtaAttributeDescription(id, type, std::move(reported_name), std::move(displayable_name), std::move(generic_name), std::move(description)));\n\t\t\t}\n\n\n\t\t\t/// Add a previously added description to the attribute database under a\n\t\t\t/// different smartctl name (fill the other members from the previous attribute).\n\t\t\tvoid add(int32_t id, std::optional<StorageDeviceDetectedType> type, std::string reported_name)\n\t\t\t{\n\t\t\t\tauto iter = id_db.find(id);\n\t\t\t\tDBG_ASSERT(iter != id_db.end() && !iter->second.empty());\n\t\t\t\tif (iter != id_db.end() && !iter->second.empty()) {\n\t\t\t\t\tAtaAttributeDescription attr = iter->second.front();\n\t\t\t\t\tadd(AtaAttributeDescription(id, type,\n\t\t\t\t\t\t\tstd::move(reported_name), std::move(attr.displayable_name), std::move(attr.generic_name), std::move(attr.description)));\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t\t/// Find the description by smartctl name or id, merging them if they're partial.\n\t\t\t[[nodiscard]] AtaAttributeDescription find(const std::string& reported_name, int32_t id, std::optional<StorageDeviceDetectedType> type) const\n\t\t\t{\n\t\t\t\t// search by ID first\n\t\t\t\tauto id_iter = id_db.find(id);\n\t\t\t\tif (id_iter == id_db.end()) {\n\t\t\t\t\treturn {};  // not found\n\t\t\t\t}\n\t\t\t\tDBG_ASSERT(!id_iter->second.empty());\n\t\t\t\tif (id_iter->second.empty()) {\n\t\t\t\t\treturn {};  // invalid DB?\n\t\t\t\t}\n\n\t\t\t\tstd::vector<AtaAttributeDescription> type_matched;\n\t\t\t\tfor (const auto& attr_iter : id_iter->second) {\n\t\t\t\t\tif (!attr_iter.drive_type.has_value() || !type.has_value() || attr_iter.drive_type == type) {\n\t\t\t\t\t\ttype_matched.push_back(attr_iter);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (type_matched.empty()) {\n\t\t\t\t\treturn {};  // not found\n\t\t\t\t}\n\n\t\t\t\t// search by smartctl name in ID-supplied vector\n\t\t\t\tfor (const auto& attr_iter : type_matched) {\n\t\t\t\t\t// compare them case-insensitively, just in case\n\t\t\t\t\tif ( hz::string_to_lower_copy(attr_iter.reported_name) == hz::string_to_lower_copy(reported_name)) {\n\t\t\t\t\t\treturn attr_iter;  // found it\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// nothing was found by name, return the first one by that ID.\n\t\t\t\treturn type_matched.front();\n\t\t\t}\n\n\n\t\tprivate:\n\n\t\t\tstd::map< int32_t, std::vector<AtaAttributeDescription> > id_db;  ///< id => attribute descriptions\n\n\t};\n\n\n\n\n\t/// Get program-wide attribute description database\n\t[[nodiscard]] inline const AtaAttributeDescriptionDatabase& get_ata_attribute_description_db()\n\t{\n\t\tstatic const AtaAttributeDescriptionDatabase attribute_db;\n\t\treturn attribute_db;\n\t}\n\n\n\n\t/// Check if a property is an attribute and matches a generic name\n\tinline bool attr_match(StorageProperty& p, const std::string& generic_name)\n\t{\n\t\treturn (p.is_value_type<AtaStorageAttribute>() && p.generic_name == generic_name);\n\t}\n\n\n}\n\n\n\nvoid auto_set_ata_attribute_description(StorageProperty& p, StorageDeviceDetectedType drive_type)\n{\n\tAtaAttributeDescription attr = get_ata_attribute_description_db().find(p.reported_name, p.get_value<AtaStorageAttribute>().id, drive_type);\n\n\tstd::string humanized_reported_name;\n\tstd::string ssd_hdd_str;\n\tconst bool known_by_smartctl = !app_regex_partial_match(\"/Unknown_(HDD|SSD)_?Attr.*/i\", p.reported_name, &ssd_hdd_str);\n\tif (known_by_smartctl) {\n\t\thumanized_reported_name = \" \" + p.reported_name + \" \";  // spaces are for easy replacements\n\n\t\tstatic const std::unordered_map<std::string, std::string> replacement_map = {\n\t\t\t\t{\"_\", \" \"},\n\t\t\t\t{\"/\", \" / \"},\n\t\t\t\t{\" Ct \", \" Count \"},\n\t\t\t\t{\" Tot \", \" Total \"},\n\t\t\t\t{\" Blk \", \" Block \"},\n\t\t\t\t{\" Cel \", \" Celsius \"},\n\t\t\t\t{\" Uncorrect \", \" Uncorrectable \"},\n\t\t\t\t{\" Cnt \", \" Count \"},\n\t\t\t\t{\" Offl \", \" Offline \"},\n\t\t\t\t{\" UNC \", \" Uncorrectable \"},\n\t\t\t\t{\" Err \", \" Error \"},\n\t\t\t\t{\" Errs \", \" Errors \"},\n\t\t\t\t{\" Perc \", \" Percent \"},\n\t\t\t\t{\" Ct \", \" Count \"},\n\t\t\t\t{\" Avg \", \" Average \"},\n\t\t\t\t{\" Max \", \" Maximum \"},\n\t\t\t\t{\" Min \", \" Minimum \"}\n\t\t};\n\n\t\thz::string_replace_array(humanized_reported_name, replacement_map);\n\t\thz::string_trim(humanized_reported_name);\n\t\thz::string_remove_adjacent_duplicates(humanized_reported_name, ' ');  // may happen with slashes\n\t}\n\n\tif (attr.displayable_name.empty()) {\n\t\t// try to display something sensible (use humanized form of smartctl name)\n\t\tif (!humanized_reported_name.empty()) {\n\t\t\tattr.displayable_name = humanized_reported_name;\n\n\t\t} else {  // unknown to smartctl\n\t\t\tif (hz::string_to_upper_copy(ssd_hdd_str) == \"SSD\") {\n\t\t\t\tattr.displayable_name = \"Unknown SSD Attribute\";\n\t\t\t} else if (hz::string_to_upper_copy(ssd_hdd_str) == \"HDD\") {\n\t\t\t\tattr.displayable_name = \"Unknown HDD Attribute\";\n\t\t\t} else {\n\t\t\t\tattr.displayable_name = \"Unknown Attribute\";\n\t\t\t}\n\t\t}\n\t}\n\n\n\n\tif (attr.description.empty()) {\n\t\tattr.description = \"No description is available for this attribute.\";\n\n\t} else {\n\t\tbool same_names = true;\n\t\tif (known_by_smartctl) {\n\t\t\t// See if humanized smartctl-reported name looks like our found name.\n\t\t\t// If not, show it in description.\n\t\t\tstd::string match = \" \" + humanized_reported_name + \" \";\n\t\t\tstd::string against = \" \" + attr.displayable_name + \" \";\n\n\t\t\tstatic const std::unordered_map<std::string, std::string> replacement_map = {\n\t\t\t\t\t{\" Percent \", \" % \"},\n\t\t\t\t\t{\"-\", \t\"\"},\n\t\t\t\t\t{\"(\", \t\"\"},\n\t\t\t\t\t{\")\", \t\"\"},\n\t\t\t\t\t{\" \", \t\"\"},\n\t\t\t};\n\t\t\thz::string_replace_array(match, replacement_map);\n\t\t\thz::string_replace_array(against, replacement_map);\n\n\t\t\tsame_names = app_regex_partial_match(\"/^\" + app_regex_escape(match) + \"$/i\", against);\n\t\t}\n\n\t\tstd::string descr =  std::string(\"<b>\") + Glib::Markup::escape_text(attr.displayable_name) + \"</b>\";\n\t\tif (!same_names) {\n\t\t\tconst std::string reported_name_for_descr = Glib::Markup::escape_text(hz::string_replace_copy(p.reported_name, '_', ' '));\n\t\t\tdescr += \"\\n<small>Reported by smartctl as <b>\\\"\" + reported_name_for_descr + \"\\\"</b></small>\\n\";\n\t\t}\n\t\tdescr += \"\\n\";\n\t\tdescr += attr.description;\n\n\t\tattr.description = descr;\n\t}\n\n\tp.displayable_name = attr.displayable_name;\n\tp.set_description(attr.description);\n\tp.generic_name = attr.generic_name;\n}\n\n\n\nvoid storage_property_ata_attribute_autoset_warning(StorageProperty& p)\n{\n\tstd::optional<WarningLevel> w;\n\tstd::string reason;\n\n\tif (p.section == StoragePropertySection::AtaAttributes && p.is_value_type<AtaStorageAttribute>()) {\n\t\tconst auto& attr = p.get_value<AtaStorageAttribute>();\n\n\t\t// Set notices for known pre-fail attributes. These are notices only, since the warnings\n\t\t// and alerts are shown only in case of attribute failure.\n\n\t\t// Reallocated Sector Count\n\t\tif (attr_match(p, \"attr_reallocated_sector_count\") && attr.raw_value_int > 0) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The drive has a non-zero Raw value, but there is no SMART warning yet. \"\n\t\t\t\t\t\"This could be an indication of future failures and/or potential data loss in bad sectors.\";\n\n\t\t// Spin-up Retry Count\n\t\t} else if (attr_match(p, \"attr_spin_up_retry_count\") && attr.raw_value_int > 0) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The drive has a non-zero Raw value, but there is no SMART warning yet. \"\n\t\t\t\t\t\"Your drive may have problems spinning up, which could lead to a complete mechanical failure. Please back up.\";\n\n\t\t// Soft Read Error Rate\n\t\t} else if (attr_match(p, \"attr_soft_read_error_rate\") && attr.raw_value_int > 0) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The drive has a non-zero Raw value, but there is no SMART warning yet. \"\n\t\t\t\t\t\"This could be an indication of future failures and/or potential data loss in bad sectors.\";\n\n\t\t// Temperature (for some it may be 10xTemp, so limit the upper bound.)\n\t\t} else if (attr_match(p, \"attr_temperature_celsius\")) {\n\t\t\t// Raw value may be 27, or 253403791387 (which encodes min/max values as well).\n\t\t\t// Use string instead.\n\t\t\tstd::int64_t temp_int = 0;\n\t\t\tif (hz::string_is_numeric_nolocale(attr.raw_value, temp_int, false)\n\t\t\t\t\t&& temp_int > 50 && temp_int <= 120) {  // 50C\n\t\t\t\tw = WarningLevel::Notice;\n\t\t\t\treason = \"The temperature of the drive is higher than 50 degrees Celsius. \"\n\t\t\t\t\t\t\t\"This may shorten its lifespan and cause damage under severe load. Please install a cooling solution.\";\n\t\t\t}\n\t\t// Temperature (for some it may be 10xTemp, so limit the upper bound.)\n\t\t} else if (attr_match(p, \"attr_temperature_celsius_x10\") && attr.raw_value_int > 500) {  // 50C\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The temperature of the drive is higher than 50 degrees Celsius. \"\n\t\t\t\t\t\"This may shorten its lifespan and cause damage under severe load. Please install a cooling solution.\";\n\n\t\t// Reallocation Event Count\n\t\t} else if (attr_match(p, \"attr_reallocation_event_count\") && attr.raw_value_int > 0) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The drive has a non-zero Raw value, but there is no SMART warning yet. \"\n\t\t\t\t\t\"This could be an indication of future failures and/or potential data loss in bad sectors.\";\n\n\t\t// Current Pending Sector Count\n\t\t} else if ((attr_match(p, \"attr_current_pending_sector_count\") || attr_match(p, \"attr_total_pending_sectors\"))\n\t\t\t\t\t&& attr.raw_value_int > 0) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The drive has a non-zero Raw value, but there is no SMART warning yet. \"\n\t\t\t\t\t\"This could be an indication of future failures and/or potential data loss in bad sectors.\";\n\n\t\t// Uncorrectable Sector Count\n\t\t} else if ((attr_match(p, \"attr_offline_uncorrectable\") || attr_match(p, \"attr_total_attr_offline_uncorrectable\"))\n\t\t\t\t\t&& attr.raw_value_int > 0) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The drive has a non-zero Raw value, but there is no SMART warning yet. \"\n\t\t\t\t\t\"This could be an indication of future failures and/or potential data loss in bad sectors.\";\n\n\t\t// SSD Life Left (%)\n\t\t} else if ((attr_match(p, \"attr_ssd_life_left\"))\n\t\t\t\t\t&& attr.value.value() < 50) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The drive has less than half of its estimated life left.\";\n\n\t\t// SSD Life Used (%)\n\t\t} else if ((attr_match(p, \"attr_ssd_life_used\"))\n\t\t\t\t\t&& attr.raw_value_int >= 50) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The drive has less than half of its estimated life left.\";\n\t\t}\n\n\t\t// Now override this with reported SMART attribute failure warnings / errors\n\n\t\tif (attr.when_failed == AtaStorageAttribute::FailTime::Now) {  // NOW\n\n\t\t\tif (attr.attr_type == AtaStorageAttribute::AttributeType::OldAge) {  // old-age\n\t\t\t\tw = WarningLevel::Warning;\n\t\t\t\treason = \"The drive has a failing old-age attribute. Usually this indicates a wear-out. You should consider replacing the drive.\";\n\t\t\t} else {  // pre-fail\n\t\t\t\tw = WarningLevel::Alert;\n\t\t\t\treason = \"The drive has a failing pre-fail attribute. Usually this indicates a that the drive will FAIL soon. Please back up immediately!\";\n\t\t\t}\n\n\t\t} else if (attr.when_failed == AtaStorageAttribute::FailTime::Past) {  // PAST\n\n\t\t\tif (attr.attr_type == AtaStorageAttribute::AttributeType::OldAge) {  // old-age\n\t\t\t\t// nothing. we don't warn about e.g. temperature increase in the past\n\t\t\t} else {  // pre-fail\n\t\t\t\tw = WarningLevel::Warning;  // there was a problem, it got corrected (hopefully)\n\t\t\t\treason = \"The drive had a failing pre-fail attribute, but it has been restored to a normal value. \"\n\t\t\t\t\t\t\"This may be a serious problem, you should consider replacing the drive.\";\n\t\t\t}\n\t\t}\n\t}\n\n\tif (w.has_value()) {\n\t\tp.warning_level = w.value();\n\t\tp.warning_reason = reason;\n\t}\n}\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_property_descr_ata_attribute.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef STORAGE_PROPERTY_DESCR_ATA_ATTRIBUTE_H\n#define STORAGE_PROPERTY_DESCR_ATA_ATTRIBUTE_H\n\n//#include \"storage_property_repository.h\"\n#include \"storage_device_detected_type.h\"\n#include \"storage_property.h\"\n\n\n/// Find a property's attribute in the attribute database and fill the property\n/// with all the readable information we can gather.\nvoid auto_set_ata_attribute_description(StorageProperty& p, StorageDeviceDetectedType drive_type);\n\n\n/// If p is of appropriate type, set the warning on it if needed.\nvoid storage_property_ata_attribute_autoset_warning(StorageProperty& p);\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_property_descr_ata_statistic.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include <glibmm.h>\n#include <utility>\n//#include <vector>\n#include <map>\n//#include <unordered_map>\n\n#include \"hz/string_algo.h\"  // string_replace_copy\n#include \"applib/app_regex.h\"\n\n#include \"storage_property_descr_ata_statistic.h\"\n//#include \"warning_colors.h\"\n#include \"storage_property_descr_helpers.h\"\n\n\nnamespace {\n\n\n\t/// Attribute description for attribute database\n\tstruct AtaStatisticDescription {\n\t\t/// Constructor\n\t\tAtaStatisticDescription() = default;\n\n\t\t/// Constructor\n\t\tAtaStatisticDescription(std::string reported_name_,\n\t\t\t\tstd::string displayable_name_, std::string generic_name_, std::string description_)\n\t\t\t\t: reported_name(std::move(reported_name_)), displayable_name(std::move(displayable_name_)),\n\t\t\t\tgeneric_name(std::move(generic_name_)), description(std::move(description_))\n\t\t{ }\n\n\t\tstd::string reported_name;  ///< e.g. Highest Temperature\n\t\tstd::string displayable_name;  ///< e.g. Highest Temperature (C)\n\t\tstd::string generic_name;  ///< Generic name to be set on the property.\n\t\tstd::string description;  ///< Attribute description, can be \"\".\n\t};\n\n\n\n\t/// Devstat entry description database\n\tclass AtaStatisticDescriptionDatabase {\n\t\tpublic:\n\n\t\t\t/// Constructor\n\t\t\tAtaStatisticDescriptionDatabase()\n\t\t\t{\n\t\t\t\t// See http://www.t13.org/documents/UploadedDocuments/docs2016/di529r14-ATAATAPI_Command_Set_-_4.pdf\n\n\t\t\t\t// General Statistics\n\n\t\t\t\tadd(\"Lifetime Power-On Resets\", \"\", \"\",\n\t\t\t\t\t\t\"The number of times the device has processed a power-on reset.\");\n\n\t\t\t\tadd(\"Power-on Hours\", \"\", \"\",\n\t\t\t\t\t\t\"The amount of time that the device has been operational since it was manufactured.\");\n\n\t\t\t\tadd(\"Logical Sectors Written\", \"\", \"\",\n\t\t\t\t\t\t\"The number of logical sectors received from the host. \"\n\t\t\t\t\t\t\"This statistic is incremented by one for each logical sector that was received from the host without an error.\");\n\n\t\t\t\tadd(\"Number of Write Commands\", \"\", \"\",\n\t\t\t\t\t\t\"The number of write commands that returned command completion without an error. \"\n\t\t\t\t\t\t\"This statistic is incremented by one for each write command that returns command completion without an error.\");\n\n\t\t\t\tadd(\"Logical Sectors Read\", \"\", \"\",\n\t\t\t\t\t\t\"The number of logical sectors sent to the host. \"\n\t\t\t\t\t\t\"This statistic is incremented by one for each logical sector that was sent to the host without an error.\");\n\n\t\t\t\tadd(\"Number of Read Commands\", \"\", \"\",\n\t\t\t\t\t\t\"The number of read commands that returned command completion without an error. \"\n\t\t\t\t\t\t\"This statistic is incremented by one for each read command that returns command completion without an error.\");\n\n\t\t\t\tadd(\"Date and Time TimeStamp\", \"\", \"\",\n\t\t\t\t\t\t\"a) the TimeStamp set by the most recent SET DATE &amp; TIME EXT command plus the number of \"\n\t\t\t\t\t\t\"milliseconds that have elapsed since that SET DATE &amp; TIME EXT command was processed;\\n\"\n\t\t\t\t\t\t\"or\\n\"\n\t\t\t\t\t\t\"b) a copy of the Power-on Hours statistic (see A.5.4.4) with the hours unit of measure changed to milliseconds as described\");\n\n\t\t\t\tadd(\"Pending Error Count\", \"\", \"\",\n\t\t\t\t\t\t\"The number of logical sectors listed in the Pending Errors log.\");\n\n\t\t\t\tadd(\"Workload Utilization\", \"\", \"\",\n\t\t\t\t\t\t\"An estimate of device utilization as a percentage of the manufacturer's designs for various wear factors \"\n\t\t\t\t\t\t\"(e.g., wear of the medium, head load events), if any. The reported value can be greater than 100%.\");\n\n\t\t\t\tadd(\"Utilization Usage Rate\", \"\", \"\",\n\t\t\t\t\t\t\"An estimate of the rate at which device wear factors (e.g., damage to the recording medium) \"\n\t\t\t\t\t\t\"are being used during a specified interval of time. This statistic is expressed as a percentage of the manufacturer's designs.\");\n\n\t\t\t\t// Free-Fall Statistics\n\n\t\t\t\tadd(\"Number of Free-Fall Events Detected\", \"\", \"\",\n\t\t\t\t\t\t\"The number of free-fall events detected by the device.\");\n\n\t\t\t\tadd(\"Overlimit Shock Events\", \"\", \"\",\n\t\t\t\t\t\t\"The number of shock events detected by the device \"\n\t\t\t\t\t\t\"with the magnitude higher than the maximum rating of the device.\");\n\n\t\t\t\t// Rotating Media Statistics\n\n\t\t\t\tadd(\"Spindle Motor Power-on Hours\", \"\", \"\",\n\t\t\t\t\t\t\"The amount of time that the spindle motor has been powered on since the device was manufactured. \");\n\n\t\t\t\tadd(\"Head Flying Hours\", \"\", \"\",\n\t\t\t\t\t\t\"The number of hours that the device heads have been flying over the surface of the media since the device was manufactured. \");\n\n\t\t\t\tadd(\"Head Load Events\", \"\", \"\",\n\t\t\t\t\t\t\"The number of head load events. A head load event is defined as:\\n\"\n\t\t\t\t\t\t\"a) when the heads are loaded from the ramp to the media for a ramp load device;\\n\"\n\t\t\t\t\t\t\"or\\n\"\n\t\t\t\t\t\t\"b) when the heads take off from the landing zone for a contact start stop device.\");\n\n\t\t\t\tadd(\"Number of Reallocated Logical Sectors\", \"\", \"\",\n\t\t\t\t\t\t\"The number of logical sectors that have been reallocated after device manufacture.\\n\\n\"\n\t\t\t\t\t\t\"If the value is normalized, this is the whole number percentage of the available logical sector reallocation \"\n\t\t\t\t\t\t\"resources that have been used (i.e., 0-100).\"\n\t\t\t\t\t\t\"\\n\\n\" + get_suffix_for_uncorrectable_property_description());\n\n\t\t\t\tadd(\"Read Recovery Attempts\", \"\", \"\",\n\t\t\t\t\t\t\"The number of logical sectors that require three or more attempts to read the data from the media for each read command. \"\n\t\t\t\t\t\t\"This statistic is incremented by one for each logical sector that encounters a read recovery attempt. \"\n\t\t\t\t\t\t\"These events may be caused by external environmental conditions (e.g., operating in a moving vehicle).\");\n\n\t\t\t\tadd(\"Number of Mechanical Start Failures\", \"\", \"\",\n\t\t\t\t\t\t\"The number of mechanical start failures after device manufacture. \"\n\t\t\t\t\t\t\"A mechanical start failure is a failure that prevents the device from achieving a normal operating condition\");\n\n\t\t\t\tadd(\"Number of Realloc. Candidate Logical Sectors\", \"Number of Reallocation Candidate Logical Sectors\", \"\",\n\t\t\t\t\t\t\"The number of logical sectors that are candidates for reallocation. \"\n\t\t\t\t\t\t\"A reallocation candidate sector is a logical sector that the device has determined may need to be reallocated.\"\n\t\t\t\t\t\t\"\\n\\n\" + get_suffix_for_uncorrectable_property_description());\n\n\t\t\t\tadd(\"Number of High Priority Unload Events\", \"\", \"\",\n\t\t\t\t\t\t\"The number of emergency head unload events.\");\n\n\t\t\t\t// General Errors Statistics\n\n\t\t\t\tadd(\"Number of Reported Uncorrectable Errors\", \"\", \"\",\n\t\t\t\t\t\t\"The number of errors that are reported as an Uncorrectable Error. \"\n\t\t\t\t\t\t\"Uncorrectable errors that occur during background activity shall not be counted. \"\n\t\t\t\t\t\t\"Uncorrectable errors reported by reads to flagged uncorrectable logical blocks should not be counted\"\n\t\t\t\t\t\t\"\\n\\n\" + get_suffix_for_uncorrectable_property_description());\n\n\t\t\t\tadd(\"Resets Between Cmd Acceptance and Completion\", \"\", \"\",\n\t\t\t\t\t\t\"The number of software reset or hardware reset events that occur while one or more commands have \"\n\t\t\t\t\t\t\"been accepted by the device but have not reached command completion.\");\n\n\t\t\t\t// Temperature Statistics\n\n\t\t\t\tadd(\"Current Temperature\", \"Current Temperature (C)\", \"stat_temperature_celsius\",\n\t\t\t\t\t\t\"Drive temperature (Celsius)\");\n\n\t\t\t\tadd(\"Average Short Term Temperature\", \"Average Short Term Temperature (C)\", \"\",\n\t\t\t\t\t\t\"A value based on the most recent 144 temperature samples in a 24 hour period.\");\n\n\t\t\t\tadd(\"Average Long Term Temperature\", \"Average Long Term Temperature (C)\", \"\",\n\t\t\t\t\t\t\"A value based on the most recent 42 Average Short Term Temperature values (1,008 recorded hours).\");\n\n\t\t\t\tadd(\"Highest Temperature\", \"Highest Temperature (C)\", \"\",\n\t\t\t\t\t\t\"The highest temperature measured after the device is manufactured.\");\n\n\t\t\t\tadd(\"Lowest Temperature\", \"Lowest Temperature (C)\", \"\",\n\t\t\t\t\t\t\"The lowest temperature measured after the device is manufactured.\");\n\n\t\t\t\tadd(\"Highest Average Short Term Temperature\", \"Highest Average Short Term Temperature (C)\", \"\",\n\t\t\t\t\t\t\"The highest device Average Short Term Temperature after the device is manufactured.\");\n\n\t\t\t\tadd(\"Lowest Average Short Term Temperature\", \"Lowest Average Short Term Temperature (C)\", \"\",\n\t\t\t\t\t\t\"The lowest device Average Short Term Temperature after the device is manufactured.\");\n\n\t\t\t\tadd(\"Highest Average Long Term Temperature\", \"Highest Average Long Term Temperature (C)\", \"\",\n\t\t\t\t\t\t\"The highest device Average Long Term Temperature after the device is manufactured.\");\n\n\t\t\t\tadd(\"Lowest Average Long Term Temperature\", \"Lowest Average Long Term Temperature (C)\", \"\",\n\t\t\t\t\t\t\"The lowest device Average Long Term Temperature after the device is manufactured.\");\n\n\t\t\t\tadd(\"Time in Over-Temperature\", \"Time in Over-Temperature (Minutes)\", \"\",\n\t\t\t\t\t\t\"The number of minutes that the device has been operational while the device temperature specification has been exceeded.\");\n\n\t\t\t\tadd(\"Specified Maximum Operating Temperature\", \"Specified Maximum Operating Temperature (C)\", \"\",\n\t\t\t\t\t\t\"The maximum operating temperature device is designed to operate.\");\n\n\t\t\t\tadd(\"Time in Under-Temperature\", \"Time in Under-Temperature (C)\", \"\",\n\t\t\t\t\t\t\"The number of minutes that the device has been operational while the temperature is lower than the device minimum temperature specification.\");\n\n\t\t\t\tadd(\"Specified Minimum Operating Temperature\", \"Specified Minimum Operating Temperature (C)\", \"\",\n\t\t\t\t\t\t\"The minimum operating temperature device is designed to operate.\");\n\n\t\t\t\t// Transport Statistics\n\n\t\t\t\tadd(\"Number of Hardware Resets\", \"\", \"\",\n\t\t\t\t\t\t\"The number of hardware resets received by the device.\");\n\n\t\t\t\tadd(\"Number of ASR Events\", \"\", \"\",\n\t\t\t\t\t\t\"The number of ASR (Asynchronous Signal Recovery) events.\");\n\n\t\t\t\tadd(\"Number of Interface CRC Errors\", \"\", \"\",\n\t\t\t\t\t\t\"the number of Interface CRC (checksum) errors reported in the ERROR field since the device was manufactured.\");\n\n\t\t\t\t// Solid State Device Statistics\n\n\t\t\t\tadd(\"Percentage Used Endurance Indicator\", \"\", \"\",\n\t\t\t\t\t\t\"A vendor specific estimate of the percentage of device life used based on the actual device usage \"\n\t\t\t\t\t\t\"and the manufacturer's prediction of device life. A value of 100 indicates that the estimated endurance \"\n\t\t\t\t\t\t\"of the device has been consumed, but may not indicate a device failure (e.g., minimum \"\n\t\t\t\t\t\t\"power-off data retention capability reached for devices using NAND flash technology).\");\n\n\t\t\t}\n\n\n\t\t\t/// Add an attribute description to the attribute database\n\t\t\tvoid add(const std::string& reported_name, const std::string& displayable_name,\n\t\t\t\t\tconst std::string& generic_name, const std::string& description)\n\t\t\t{\n\t\t\t\tadd(AtaStatisticDescription(reported_name, displayable_name, generic_name, description));\n\t\t\t}\n\n\n\t\t\t/// Add an devstat entry description to the devstat database\n\t\t\tvoid add(const AtaStatisticDescription& descr)\n\t\t\t{\n\t\t\t\tdevstat_db[descr.reported_name] = descr;\n\t\t\t}\n\n\n\t\t\t/// Find the description by smartctl name or id, merging them if they're partial.\n\t\t\t[[nodiscard]] AtaStatisticDescription find(const std::string& reported_name) const\n\t\t\t{\n\t\t\t\t// search by ID first\n\t\t\t\tauto iter = devstat_db.find(reported_name);\n\t\t\t\tif (iter == devstat_db.end()) {\n\t\t\t\t\treturn {};  // not found\n\t\t\t\t}\n\t\t\t\treturn iter->second;\n\t\t\t}\n\n\n\t\tprivate:\n\n\t\t\tstd::map<std::string, AtaStatisticDescription> devstat_db;  ///< reported_name => devstat entry description\n\n\t};\n\n\n\n\t/// Get program-wide devstat description database\n\t[[nodiscard]] inline const AtaStatisticDescriptionDatabase& get_ata_statistic_description_db()\n\t{\n\t\tstatic const AtaStatisticDescriptionDatabase devstat_db;\n\t\treturn devstat_db;\n\t}\n\n\n\n\t/// Check if a property matches a name (generic or reported)\n\tinline bool name_match(StorageProperty& p, const std::string& name)\n\t{\n\t\tif (p.generic_name.empty()) {\n\t\t\treturn hz::string_to_lower_copy(p.reported_name) == hz::string_to_lower_copy(name);\n\t\t}\n\t\treturn hz::string_to_lower_copy(p.generic_name) == hz::string_to_lower_copy(name);\n\t}\n\n}\n\n\n\n/// Find a property's statistic in the statistics database and fill the property\n/// with all the readable information we can gather.\nbool auto_set_ata_statistic_description(StorageProperty& p)\n{\n\tAtaStatisticDescription sd = get_ata_statistic_description_db().find(p.reported_name);\n\n\tconst std::string displayable_name = (sd.displayable_name.empty() ? sd.reported_name : sd.displayable_name);\n\n\tconst bool found = !sd.description.empty();\n\tif (!found) {\n\t\tsd.description = \"No description is available for this entry.\";\n\n\t} else {\n\t\tstd::string descr =  std::string(\"<b>\") + Glib::Markup::escape_text(displayable_name) + \"</b>\\n\";\n\t\tdescr += sd.description;\n\n\t\tif (p.get_value<AtaStorageStatistic>().is_normalized()) {\n\t\t\tdescr += \"\\n\\nNote: The value is normalized.\";\n\t\t}\n\n\t\tsd.description = descr;\n\t}\n\n\tif (!displayable_name.empty()) {\n\t\tp.displayable_name = displayable_name;\n\t}\n\tp.set_description(sd.description);\n\tp.generic_name = sd.generic_name;\n\n\treturn found;\n}\n\n\n\nvoid storage_property_ata_statistic_autoset_warning(StorageProperty& p)\n{\n\tstd::optional<WarningLevel> w = WarningLevel::None;\n\tstd::string reason;\n\n\tif (p.section == StoragePropertySection::Statistics && p.is_value_type<AtaStorageStatistic>()) {\n\t\tconst auto& statistic = p.get_value<AtaStorageStatistic>();\n\n\t\tif (name_match(p, \"Pending Error Count\") && statistic.value_int > 0) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The drive is reporting surface errors. This could be an indication of future failures and/or potential data loss in bad sectors.\";\n\n\t\t// \"Workload Utilization\" is either normalized, or encodes several values, so we can't use it.\n/*\n\t\t} else if (name_match(p, \"Workload Utilization\") && statistic.value_int >= 50) {\n\t\t\tw = WarningLevel::notice;\n\t\t\treason = \"The drive has less than half of its estimated life left.\";\n\n\t\t} else if (name_match(p, \"Workload Utilization\") && statistic.value_int >= 100) {\n\t\t\tw = WarningLevel::warning;\n\t\t\treason = \"The drive is past its estimated lifespan.\";\n*/\n\n\t\t} else if (name_match(p, \"Utilization Usage Rate\") && statistic.value_int >= 50) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The drive has less than half of its estimated life left.\";\n\n\t\t} else if (name_match(p, \"Utilization Usage Rate\") && statistic.value_int >= 100) {\n\t\t\tw = WarningLevel::Warning;\n\t\t\treason = \"The drive is past its estimated lifespan.\";\n\n\t\t} else if (name_match(p, \"Number of Reallocated Logical Sectors\") && !statistic.is_normalized() && statistic.value_int > 0) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The drive is reporting surface errors. This could be an indication of future failures and/or potential data loss in bad sectors.\";\n\n\t\t} else if (name_match(p, \"Number of Reallocated Logical Sectors\") && statistic.is_normalized() && statistic.value_int <= 0) {\n\t\t\tw = WarningLevel::Warning;\n\t\t\treason = \"The drive is reporting surface errors. This could be an indication of future failures and/or potential data loss in bad sectors.\";\n\n\t\t} else if (name_match(p, \"Number of Mechanical Start Failures\") && statistic.value_int > 0) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The drive is reporting mechanical errors.\";\n\n\t\t} else if (name_match(p, \"Number of Realloc. Candidate Logical Sectors\") && statistic.value_int > 0) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The drive is reporting surface errors. This could be an indication of future failures and/or potential data loss in bad sectors.\";\n\n\t\t} else if (name_match(p, \"Number of Reported Uncorrectable Errors\") && statistic.value_int > 0) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The drive is reporting surface errors. This could be an indication of future failures and/or potential data loss in bad sectors.\";\n\n\t\t} else if (name_match(p, \"Current Temperature\") && statistic.value_int > 50) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The temperature of the drive is higher than 50 degrees Celsius. \"\n\t\t\t\t\t\"This may shorten its lifespan and cause damage under severe load. Please install a cooling solution.\";\n\n\t\t} else if (name_match(p, \"Time in Over-Temperature\") && statistic.value_int > 0) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The temperature of the drive is or was over the manufacturer-specified maximum. \"\n\t\t\t\t\t\"This may have shortened its lifespan and caused damage. Please install a cooling solution.\";\n\n\t\t} else if (name_match(p, \"Time in Under-Temperature\") && statistic.value_int > 0) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The temperature of the drive is or was under the manufacturer-specified minimum. \"\n\t\t\t\t\t\"This may have shortened its lifespan and caused damage. Please operate the drive within manufacturer-specified temperature range.\";\n\n\t\t} else if (name_match(p, \"Percentage Used Endurance Indicator\") && statistic.value_int >= 50) {\n\t\t\tw = WarningLevel::Notice;\n\t\t\treason = \"The drive has less than half of its estimated life left.\";\n\n\t\t} else if (name_match(p, \"Percentage Used Endurance Indicator\") && statistic.value_int >= 100) {\n\t\t\tw = WarningLevel::Warning;\n\t\t\treason = \"The drive is past its estimated lifespan.\";\n\t\t}\n\t}\n\n\tif (w.has_value()) {\n\t\tp.warning_level = w.value();\n\t\tp.warning_reason = reason;\n\t}\n}\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_property_descr_ata_statistic.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef STORAGE_PROPERTY_DESCR_ATA_STATISTIC_H\n#define STORAGE_PROPERTY_DESCR_ATA_STATISTIC_H\n\n#include \"storage_property_repository.h\"\n//#include \"storage_device_detected_type.h\"\n\n\n\n/// Find a property's statistic in the statistic database and fill the property\n/// with all the readable information we can gather.\nbool auto_set_ata_statistic_description(StorageProperty& p);\n\n\n/// If p is of appropriate type, set the warning on it if needed.\nvoid storage_property_ata_statistic_autoset_warning(StorageProperty& p);\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_property_descr_helpers.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef STORAGE_PROPERTY_DESCR_HELPERS_H\n#define STORAGE_PROPERTY_DESCR_HELPERS_H\n\n#include <glibmm.h>\n#include <glibmm/i18n.h>\n#include <string>\n\n\n/// Get text related to \"uncorrectable sectors\"\ninline const std::string& get_suffix_for_uncorrectable_property_description()\n{\n\tstatic const std::string text = Glib::Markup::escape_text(\n\t\t\t_(\"When a drive encounters a surface error, it marks that sector as \\\"unstable\\\" (also known as \\\"pending reallocation\\\"). \"\n\t\t\t\"If the sector is successfully read from or written to at some later point, it is unmarked. If the sector continues to be inaccessible, \"\n\t\t\t\"the drive reallocates (remaps) it to a specially reserved area as soon as it has a chance (usually during write request or successful read), \"\n\t\t\t\"transferring the data so that no changes are reported to the operating system. This is why you generally don't see \\\"bad blocks\\\" \"\n\t\t\t\"on modern drives - if you do, it means that either they have not been remapped yet, or the drive is out of reserved area.\"\n\t\t\t\"\\n\\nNote: SSDs reallocate blocks as part of their normal operation, so low reallocation counts are not critical for them.\"));\n\treturn text;\n}\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_property_descr_nvme_attribute.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include <glibmm.h>\n#include <utility>\n#include <map>\n#include <optional>\n#include <string>\n\n#include \"hz/string_algo.h\"  // string_replace_copy\n//#include \"applib/app_regex.h\"\n\n#include \"storage_property_descr_nvme_attribute.h\"\n//#include \"warning_colors.h\"\n//#include \"storage_property_descr_helpers.h\"\n\n\nnamespace {\n\n\n\t/// Attribute description for attribute database\n\tstruct NvmeAttributeDescription {\n\t\t/// Constructor\n\t\tNvmeAttributeDescription() = default;\n\n\t\t/// Constructor\n\t\tNvmeAttributeDescription(std::string generic_name_, std::string description_)\n\t\t\t\t: generic_name(std::move(generic_name_)), description(std::move(description_))\n\t\t{ }\n\n//\t\tstd::string reported_name;  ///< e.g. Highest Temperature\n//\t\tstd::string displayable_name;  ///< e.g. Highest Temperature (C)\n\t\tstd::string generic_name;  ///< Generic name to be set on the property.\n\t\tstd::string description;  ///< Attribute description, can be \"\".\n\t};\n\n\n\n\t/// Devstat entry description database\n\tclass NvmeAttributeDescriptionDatabase {\n\t\tpublic:\n\n\t\t\t/// Constructor\n\t\t\tNvmeAttributeDescriptionDatabase()\n\t\t\t{\n\t\t\t\tadd(\"nvme_smart_health_information_log/temperature\",\n\t\t\t\t\t\t_(\"Drive temperature (Celsius)\"));\n\n\t\t\t\tadd(\"nvme_smart_health_information_log/available_spare\",\n\t\t\t\t\t\t_(\"Normalized percentage (0% to 100%) of the remaining space capacity. \"\n\t\t\t\t\t\t  \"If Available Spare is lower than Available Space Threshold, the drive is considered to be in a critical state.\"));\n\n\t\t\t\tadd(\"nvme_smart_health_information_log/available_spare_threshold\",\n\t\t\t\t\t\t_(\"Normalized percentage (0% to 100%). If the Available Spare is lower than this threshold, the drive is considered to be in a critical state.\"));\n\n\t\t\t\tadd(\"nvme_smart_health_information_log/percentage_used\",\n\t\t\t\t\t\t_(\"Vendor-specific estimate of the percentage of device life based on the actual device usage and the manufacturer's prediction of the device life. \"\n\t\t\t\t\t\t  \"A value of 100 indicates that the estimated endurance of the device has been consumed, but may not indicate a device failure. \"\n\t\t\t\t\t\t  \"This value is allowed to exceed 100. Percentage values greater than 254 are be represented as 255. This value is updated once \"\n\t\t\t\t\t\t  \"per power-on hour (when the controller is not in a sleep state).\"));\n\n\t\t\t\tadd(\"nvme_smart_health_information_log/data_units_read\",\n\t\t\t\t\t\t_(\"The number of 512-byte data units the host has read from the controller. \"\n\t\t\t\t\t\t  \"This value does not include metadata. \"\n\t\t\t\t\t\t  \"The value is reported in thousands (i.e. a value of 1 corresponds to 1000 units of 512 bytes read) and is rounded up. \"\n\t\t\t\t\t\t  \"When the LBA size is a value other than 512 bytes, the controller converts the amount of data read to 512-byte units.\"));\n\n\t\t\t\tadd(\"nvme_smart_health_information_log/data_units_written\",\n\t\t\t\t\t\t_(\"The number of 512-byte data units the host has written to the controller. \"\n\t\t\t\t\t\t  \"This value does not include metadata. \"\n\t\t\t\t\t\t  \"The value is reported in thousands (i.e. a value of 1 corresponds to 1000 units of 512 bytes read) and is rounded up.\"));\n\n\t\t\t\tadd(\"nvme_smart_health_information_log/host_reads\",\n\t\t\t\t\t\t_(\"Number of read commands completed by the controller\"));\n\n\t\t\t\tadd(\"nvme_smart_health_information_log/host_writes\",\n\t\t\t\t\t\t_(\"Number of write commands completed by the controller\"));\n\n\t\t\t\tadd(\"nvme_smart_health_information_log/controller_busy_time\",\n\t\t\t\t\t\t_(\"The amount of time the controller is busy with I/O commands.\"));\n\n\t\t\t\tadd(\"nvme_smart_health_information_log/power_cycles\",\n\t\t\t\t\t\t_(\"Number of power cycles experienced by the drive\"));\n\n\t\t\t\tadd(\"nvme_smart_health_information_log/power_on_hours\",\n\t\t\t\t\t\t_(\"Number of hours in power-on state. This does not include the time that the controller was powered in a low power state condition.\"));\n\n\t\t\t\tadd(\"nvme_smart_health_information_log/unsafe_shutdowns\",\n\t\t\t\t\t\t_(\"Number of unsafe shutdowns. This value is incremented when a shutdown notification is not received prior to loss of power.\"));\n\n\t\t\t\tadd(\"nvme_smart_health_information_log/media_errors\",\n\t\t\t\t\t\t_(\"Number of occurrences where the controller detected an unrecovered data integrity error. Errors such as uncorrectable ECC, \"\n\t\t\t\t\t\t  \"CRC checksum failure or LBA tag mismatch are included in this field.\"));\n\n\t\t\t\tadd(\"nvme_smart_health_information_log/num_err_log_entries\",\n\t\t\t\t\t\t_(\"Maximum number of possible Error Information Log entries preserved over the life of the controller\"));\n\n\t\t\t\t/// FIXME unit?\n\t\t\t\tadd(\"nvme_smart_health_information_log/warning_temp_time\",\n\t\t\t\t\t\t_(\"The minimum Composite Temperature field value indicates an overheating condition during which the controller operation continues. \"\n\t\t\t\t\t\t  \"Immediate remediation is recommended (e.g. additional cooling or workload reduction).\"));\n\n\t\t\t\tadd(\"nvme_smart_health_information_log/critical_comp_time\",\n\t\t\t\t\t\t_(\"The amount of time in minutes that the controller is operational and the Composite Temperature is >= Critical Composite Temperature Threshold (CCTEMP).\"));\n\t\t\t}\n\n\n\t\t\t/// Add an attribute description to the attribute database.\n\t\t\t/// Note: Displayable names for known attributes have already been added while parsing.\n\t\t\t/// For unknown attributes, the displayable name is derived from generic name.\n\t\t\tvoid add(const std::string& generic_name, const std::string& description)\n\t\t\t{\n\t\t\t\tadd(NvmeAttributeDescription(generic_name, description));\n\t\t\t}\n\n\n\t\t\t/// Add an devstat entry description to the devstat database\n\t\t\tvoid add(const NvmeAttributeDescription& descr)\n\t\t\t{\n\t\t\t\tnvme_attr_db[descr.generic_name] = descr;\n\t\t\t}\n\n\n\t\t\t/// Find the description by smartctl generic name.\n\t\t\t[[nodiscard]] NvmeAttributeDescription find(const std::string& reported_name) const\n\t\t\t{\n\t\t\t\t// search by ID first\n\t\t\t\tauto iter = nvme_attr_db.find(reported_name);\n\t\t\t\tif (iter == nvme_attr_db.end()) {\n\t\t\t\t\treturn {};  // not found\n\t\t\t\t}\n\t\t\t\treturn iter->second;\n\t\t\t}\n\n\n\t\tprivate:\n\n\t\t\tstd::map<std::string, NvmeAttributeDescription> nvme_attr_db;  ///< generic_name => description\n\n\t};\n\n\n\n\t/// Get program-wide devstat description database\n\t[[nodiscard]] inline const NvmeAttributeDescriptionDatabase& get_nvme_attribute_description_db()\n\t{\n\t\tstatic const NvmeAttributeDescriptionDatabase nvme_attr_db;\n\t\treturn nvme_attr_db;\n\t}\n\n\n\n\t/// Check if a property matches a name (generic or reported)\n\tinline bool name_match(StorageProperty& p, const std::string& name)\n\t{\n\t\tif (p.generic_name.empty()) {\n\t\t\treturn hz::string_to_lower_copy(p.reported_name) == hz::string_to_lower_copy(name);\n\t\t}\n\t\treturn hz::string_to_lower_copy(p.generic_name) == hz::string_to_lower_copy(name);\n\t}\n\n\n}\n\n\n/// Find a property's statistic in the statistics database and fill the property\n/// with all the readable information we can gather.\nbool auto_set_nvme_attribute_description(StorageProperty& p)\n{\n\tNvmeAttributeDescription attr_descr = get_nvme_attribute_description_db().find(p.generic_name);\n\n//\t\tconst std::string displayable_name = (attr_descr.displayable_name.empty() ? attr_descr.reported_name : attr_descr.displayable_name);\n\n\tconst bool found = !attr_descr.description.empty();\n\tif (!found) {\n\t\t// Derive displayable name from generic name\n//\t\t\tp.displayable_name = hz::string_replace_copy(p.generic_name, \"_\", \" \");\n\n\t\tattr_descr.description = \"No description is available for this attribute.\";\n\n\t} else {\n\t\tstd::string descr =  std::string(\"<b>\") + Glib::Markup::escape_text(p.displayable_name) + \"</b>\\n\";\n\t\tdescr += attr_descr.description;\n\n\t\tattr_descr.description = descr;\n\t}\n\n\tp.set_description(attr_descr.description);\n//\t\tp.generic_name = attr_descr.generic_name;\n\n\treturn found;\n}\n\n\n\n\n\nvoid storage_property_nvme_attribute_autoset_warning(StorageProperty& p)\n{\n\tstd::optional<WarningLevel> w = WarningLevel::None;\n\tstd::string reason;\n\n\tif (p.section != StoragePropertySection::NvmeAttributes) {\n\t\treturn;\n\t}\n\n\tif (name_match(p, \"nvme_smart_health_information_log/temperature\") && p.is_value_type<int64_t >() && p.get_value<int64_t>() > 50) {  // 50C\n\t\tw = WarningLevel::Notice;\n\t\treason = \"The temperature of the drive is higher than 50 degrees Celsius. \"\n\t\t\t\t\"This may shorten its lifespan and cause damage under severe load. Please install a cooling solution.\";\n\n\t} else if (name_match(p, \"nvme_smart_health_information_log/available_spare\") && p.is_value_type<int64_t >()\n\t\t\t&& p.get_value<int64_t>() <= 10) {  // 10% (arbitrary value)\n\t\tw = WarningLevel::Warning;\n\t\treason = \"The drive has less than 10% available spare lifetime left.\";\n\n\t} else if (name_match(p, \"nvme_smart_health_information_log/percentage_used\") && p.is_value_type<int64_t >()\n\t\t\t&& p.get_value<int64_t>() >= 90) {  // 90% (arbitrary value)\n\t\tw = WarningLevel::Warning;\n\t\treason = \"The estimate drive lifetime is nearing its limit.\";\n\n\t} else if (name_match(p, \"nvme_smart_health_information_log/media_errors\") && p.is_value_type<int64_t >()\n\t\t\t&& p.get_value<int64_t>() > 0) {\n\t\tw = WarningLevel::Notice;\n\t\treason = \"There are media errors present on this drive.\";\n\n//\t} else if (name_match(p, \"nvme_smart_health_information_log/num_err_log_entries\") && p.is_value_type<int64_t >()\n//\t\t\t&& p.get_value<int64_t>() > 0) {\n//\t\tw = WarningLevel::Warning;\n//\t\treason = \"The drive has errors in its persistent error log.\";\n\n\t} else if (name_match(p, \"nvme_smart_health_information_log/warning_temp_time\") && p.is_value_type<int64_t >()\n\t\t\t&& p.get_value<int64_t>() > 0) {\n\t\tw = WarningLevel::Notice;\n\t\treason = \"The drive detected is or was overheating. \"\n\t\t\t\t \"This may have shortened its lifespan and caused damage. Please install a cooling solution.\";\n\n\t} else if (name_match(p, \"nvme_smart_health_information_log/critical_comp_time\") && p.is_value_type<int64_t >()\n\t\t\t&& p.get_value<int64_t>() > 0) {\n\t\tw = WarningLevel::Notice;\n\t\treason = \"The drive detected is or was overheating. \"\n\t\t\t\t \"This may have shortened its lifespan and caused damage. Please install a cooling solution.\";\n\t}\n\n\tif (w.has_value()) {\n\t\tp.warning_level = w.value();\n\t\tp.warning_reason = reason;\n\t}\n}\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_property_descr_nvme_attribute.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef STORAGE_PROPERTY_DESCR_NVME_ATTRIBUTE_H\n#define STORAGE_PROPERTY_DESCR_NVME_ATTRIBUTE_H\n\n#include \"storage_property_repository.h\"\n//#include \"storage_device_detected_type.h\"\n\n\n\n/// Find a property's statistic in the statistic database and fill the property\n/// with all the readable information we can gather.\nbool auto_set_nvme_attribute_description(StorageProperty& p);\n\n\n/// If p is of appropriate type, set the warning on it if needed.\nvoid storage_property_nvme_attribute_autoset_warning(StorageProperty& p);\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/storage_property_repository.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n\n#include \"storage_property_repository.h\"\n\n#include <algorithm>\n#include <string>\n#include <utility>\n\n\n\n\nconst std::vector<StorageProperty>& StoragePropertyRepository::get_properties() const\n{\n\treturn properties_;\n}\n\n\n\nstd::vector<StorageProperty>& StoragePropertyRepository::get_properties_ref()\n{\n\treturn properties_;\n}\n\n\n\nStorageProperty StoragePropertyRepository::lookup_property(\n\t\tconst std::string& generic_name, StoragePropertySection section) const\n{\n\tfor (const auto& p : properties_) {\n\t\tif (section != StoragePropertySection::Unknown && p.section != section)\n\t\t\tcontinue;\n\n\t\tif (p.generic_name == generic_name)\n\t\t\treturn p;\n\t}\n\treturn {};  // check with .empty()\n}\n\n\n\nvoid StoragePropertyRepository::set_properties(std::vector<StorageProperty> properties)\n{\n\tproperties_ = std::move(properties);\n}\n\n\n\nvoid StoragePropertyRepository::add_property(StorageProperty property)\n{\n\tproperties_.push_back(std::move(property));\n}\n\n\n\nvoid StoragePropertyRepository::clear()\n{\n\tproperties_.clear();\n}\n\n\n\nbool StoragePropertyRepository::has_properties_for_section(StoragePropertySection section) const\n{\n\treturn std::any_of(properties_.begin(), properties_.end(),\n\t\t\t[section](const StorageProperty& p) { return p.section == section; });\n}\n\n"
  },
  {
    "path": "src/applib/storage_property_repository.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n#ifndef STORAGE_PROPERTY_REPOSITORY_H\n#define STORAGE_PROPERTY_REPOSITORY_H\n\n#include <string>\n#include <vector>\n#include \"storage_property.h\"\n\n\n/// A repository of properties. Used to store and look up drive properties.\nclass StoragePropertyRepository {\n\tpublic:\n\n\t\t/// Get all properties\n\t\t[[nodiscard]] const std::vector<StorageProperty>& get_properties() const;\n\n\t\t/// Get all properties\n\t\t[[nodiscard]] std::vector<StorageProperty>& get_properties_ref();\n\n\n\t\t/// Find a property.\n\t\t/// If section is Section::Unknown, search in all sections.\n\t\t[[nodiscard]] StorageProperty lookup_property(const std::string& generic_name,\n\t\t\t\tStoragePropertySection section = StoragePropertySection::Unknown) const;\n\n\n\t\t/// Set properties\n\t\tvoid set_properties(std::vector<StorageProperty> properties);\n\n\t\t/// Add a property\n\t\tvoid add_property(StorageProperty property);\n\n\t\t/// Clear all properties\n\t\tvoid clear();\n\n\n\t\t/// Check if there are any properties for a given section\n\t\t[[nodiscard]] bool has_properties_for_section(StoragePropertySection section) const;\n\n\n\tprivate:\n\n\t\tstd::vector<StorageProperty> properties_;  ///< Parsed data properties\n\n};\n\n\n#endif  // STORAGE_PROPERTY_REPOSITORY_H\n"
  },
  {
    "path": "src/applib/storage_settings.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef STORAGE_SETTINGS_H\n#define STORAGE_SETTINGS_H\n\n#include <string>\n#include <map>\n#include <utility>\n#include <set>\n\n#include \"rconfig/rconfig.h\"\n#include \"hz/debug.h\"\n\n\n/// Device name, device type (optional)\nusing AppDeviceWithType = std::pair<std::string, std::string>;\n\n/// Device name + type -> options.\n/// We need a separate struct for this, to work with json (otherwise it gives errors with std::map).\nstruct AppDeviceOptionMap {\n\tstd::map<AppDeviceWithType, std::string> value;\n\n\tbool operator==(const AppDeviceOptionMap& other) const\n\t{\n\t\treturn value == other.value;\n\t}\n\n\tbool operator!=(const AppDeviceOptionMap& other) const\n\t{\n\t\treturn !(*this == other);\n\t}\n};\n\n\n/// json serializer for rconfig\ninline void to_json(rconfig::json& j, const AppDeviceOptionMap& devmap)\n{\n\tfor (const auto& iter : devmap.value) {\n\t\tstd::string dev = iter.first.first, type = iter.first.second, options = iter.second;\n\t\tif (!dev.empty() && !options.empty()) {\n\t\t\tj.push_back(rconfig::json {\n\t\t\t\t{\"device\", dev},\n\t\t\t\t{\"type\", type},\n\t\t\t\t{\"options\", options}\n\t\t\t});\n\t\t}\n\t}\n}\n\n\n/// json deserializer for rconfig\ninline void from_json(const rconfig::json& j, AppDeviceOptionMap& devmap)\n{\n\tfor (const auto& obj : j) {\n\t\ttry {\n\t\t\tif (obj.contains(\"device\") && obj.contains(\"type\") && obj.contains(\"options\")) {\n\t\t\t\tdevmap.value.insert_or_assign(\n\t\t\t\t\t{obj.at(\"device\").get<std::string>(), obj.at(\"type\").get<std::string>()},\n\t\t\t\t\tobj.at(\"options\").get<std::string>()\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tcatch(std::exception& e) {\n\t\t\t// ignore \"not found\"\n\t\t}\n\t}\n}\n\n\n\n/// Get per-device options from config\ninline AppDeviceOptionMap app_config_get_device_option_map()\n{\n\treturn rconfig::get_data<AppDeviceOptionMap>(\"system/smartctl_device_options\");\n}\n\n\n\n/// Read device option map from config and get the options for (dev, type_arg) pair.\ninline std::vector<std::string> app_get_device_options(const std::string& dev, const std::string& type_arg)\n{\n\tif (dev.empty())\n\t\treturn {};\n\n\tauto devmap = app_config_get_device_option_map().value;\n\tif (auto iter = devmap.find(std::pair(dev, type_arg)); iter != devmap.end()) {\n\t\tif (iter->second.empty()) {\n\t\t\treturn {};\n\t\t}\n\t\ttry {\n\t\t\treturn Glib::shell_parse_argv(iter->second);\n\t\t}\n\t\tcatch(Glib::ShellError& e)\n\t\t{\n\t\t\tdebug_out_warn(\"app\", DBG_FUNC_MSG << \"Cannot parse device options for \\\"\"\n\t\t\t\t\t<< dev << \"\\\": \" << e.what() << \"\\n\");\n\t\t\treturn {};\n\t\t}\n\t}\n\treturn {};\n}\n\n\n\n/// Device information used when manually adding devices\nstruct AppAddDeviceOption {\n\tAppAddDeviceOption() = default;\n\n\tAppAddDeviceOption(std::string par_device, std::string par_type, std::string par_options)\n\t\t\t: device(std::move(par_device)), type(std::move(par_type)), options(std::move(par_options))\n\t{ }\n\n\tstd::string device;  ///< Device name, e.g. /dev/sda\n\tstd::string type;  ///< Smartctl type\n\tstd::string options;  ///< Additional smartctl options\n\n\n\t/// Compare two AppAddDeviceOption objects\n\tbool operator==(const AppAddDeviceOption& other) const\n\t{\n\t\treturn device == other.device && type == other.type && options == other.options;\n\t}\n\n\t/// Compare two AppAddDeviceOption objects\n\tbool operator!=(const AppAddDeviceOption& other) const\n\t{\n\t\treturn !(*this == other);\n\t}\n\n\t/// Compare two AppAddDeviceOption objects\n\tbool operator<(const AppAddDeviceOption& other) const\n\t{\n\t\tif (device != other.device)\n\t\t\treturn device < other.device;\n\t\tif (type != other.type)\n\t\t\treturn type < other.type;\n\t\treturn options < other.options;\n\t}\n};\n\n\n\n/// json serializer for rconfig\ninline void to_json(rconfig::json& j, const std::set<AppAddDeviceOption>& devices)\n{\n\tfor (const auto& dev : devices) {\n\t\tif (!dev.device.empty()) {\n\t\t\tj.push_back(rconfig::json {\n\t\t\t\t{\"device\", dev.device},\n\t\t\t\t{\"type\", dev.type},\n\t\t\t\t{\"options\", dev.options}\n\t\t\t});\n\t\t}\n\t}\n}\n\n\n\n/// json deserializer for rconfig\ninline void from_json(const rconfig::json& j, std::set<AppAddDeviceOption>& devices)\n{\n\tif (!j.is_array()) {\n\t\treturn;\n\t}\n\tfor (const auto& obj : j) {\n\t\ttry {\n\t\t\tif (obj.contains(\"device\") && obj.contains(\"type\") && obj.contains(\"options\")) {\n\t\t\t\tdevices.emplace(\n\t\t\t\t\tobj.at(\"device\").get<std::string>(),\n\t\t\t\t\tobj.at(\"type\").get<std::string>(),\n\t\t\t\t\tobj.at(\"options\").get<std::string>()\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tcatch(std::exception& e) {\n\t\t\t// ignore \"not found\"\n\t\t}\n\t}\n}\n\n\n\n/// Get devices to manually add on startup\ninline std::set<AppAddDeviceOption> app_get_startup_manual_devices()\n{\n\treturn rconfig::get_data<std::set<AppAddDeviceOption>>(\"system/startup_manual_devices\");\n}\n\n\n\n/// Set devices to manually add on startup\ninline void app_set_startup_manual_devices(std::set<AppAddDeviceOption> devices)\n{\n\trconfig::set_data(\"system/startup_manual_devices\", std::move(devices));\n}\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/tests/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nif (NOT APP_BUILD_TESTS)\n    set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL true)\nelse()\n    set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL false)\nendif()\n\n\n# Use Object libraries to allow runtime test discovery\nadd_library(applib_tests OBJECT)\ntarget_sources(applib_tests PRIVATE\n\ttest_app_regex.cpp\n\ttest_smartctl_parser.cpp\n\ttest_smartctl_version_parser.cpp\n)\ntarget_link_libraries(applib_tests PRIVATE\n\tapplib\n\tCatch2\n)\n\n"
  },
  {
    "path": "src/applib/tests/test_app_regex.cpp",
    "content": "/******************************************************************************\nLicense: BSD Zero Clause License\nCopyright:\n\t(C) 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib_tests\n/// \\weakgroup applib_tests\n/// @{\n\n#include \"catch2/catch.hpp\"\n\n#include \"applib/app_regex.h\"\n#include <regex>\n\n\n\nTEST_CASE(\"AppRegexFlags\", \"[app][regex]\")\n{\n\t// Default flags\n\tREQUIRE(static_cast<bool>(app_regex_get_options(\"i\") & std::regex::icase) == true);\n\tREQUIRE(static_cast<bool>(app_regex_get_options(\"m\") & std::regex::multiline) == true);\n\n\t// Multiple flags\n\tREQUIRE(static_cast<bool>(app_regex_get_options(\"im\") & std::regex::icase) == true);\n\tREQUIRE(static_cast<bool>(app_regex_get_options(\"im\") & std::regex::multiline) == true);\n}\n\n\n\nTEST_CASE(\"AppRegexBasic\", \"[app][regex]\")\n{\n\tconst std::vector<std::string> input_lines = {\n\t\t\"major minor\",\n\t\t\"31  0     128 mtdblock0\",\n\t\t\"3     1    1638598 ide/host0/bus0/target0/lun0/part1 0 0 0 0 0 0 0 0 0 0 0\",\n\t\t\"\\t8     0  156290904 sda\",\n\t};\n\n\t{\n\t\tstd::smatch matches;\n\t\tconst bool matched = app_regex_partial_match(R\"(/^[ \\t]*[^ \\t\\n]+[ \\t]+[^ \\t\\n]+[ \\t]+[^ \\t\\n]+[ \\t]+([^ \\t\\n]+)/)\", input_lines.at(0), matches);\n\t\tREQUIRE(matched == false);\n\t}\n\t{\n\t\tstd::smatch matches;\n\t\tconst bool matched = app_regex_partial_match(R\"(/^[ \\t]*[^ \\t\\n]+[ \\t]+[^ \\t\\n]+[ \\t]+[^ \\t\\n]+[ \\t]+([^ \\t\\n]+)/)\", input_lines.at(1), matches);\n\t\tREQUIRE(matched == true);\n\t\tREQUIRE(matches.size() == 2);\n\t\tREQUIRE(matches[1].str() == \"mtdblock0\");\n\t}\n\t{\n\t\tstd::smatch matches;\n\t\tconst bool matched = app_regex_partial_match(R\"(/^[ \\t]*[^ \\t\\n]+[ \\t]+[^ \\t\\n]+[ \\t]+[^ \\t\\n]+[ \\t]+([^ \\t\\n]+)/)\", input_lines.at(2), matches);\n\t\tREQUIRE(matched == true);\n\t\tREQUIRE(matches.size() == 2);\n\t\tREQUIRE(matches[1].str() == \"ide/host0/bus0/target0/lun0/part1\");\n\t}\n\t{\n\t\tstd::smatch matches;\n\t\tconst bool matched = app_regex_partial_match(R\"(/^[ \\t]*[^ \\t\\n]+[ \\t]+[^ \\t\\n]+[ \\t]+[^ \\t\\n]+[ \\t]+([^ \\t\\n]+)/)\", input_lines.at(3), matches);\n\t\tREQUIRE(matched == true);\n\t\tREQUIRE(matches.size() == 2);\n\t\tREQUIRE(matches[1].str() == \"sda\");\n\t}\n}\n\n\n\nTEST_CASE(\"AppRegexLines\", \"[app][regex]\")\n{\n\tconst std::string input = R\"(Device Model:     ST3500630AS)\";\n\n\tstd::string name, value;\n\tconst bool matched = app_regex_full_match(\"/^([^:]+):[ \\\\t]+(.*)$/i\", input, {&name, &value});\n\tREQUIRE(matched == true);\n\tREQUIRE(name == \"Device Model\");\n\tREQUIRE(value == \"ST3500630AS\");\n}\n\n\n\nTEST_CASE(\"AppRegexMultiline\", \"[app][regex]\")\n{\n\tconst std::string input = R\"(\nCopyright (C) 2002-23, Bruce Allen, Christian Franke, www.smartmontools.org\n\n=== START OF OFFLINE IMMEDIATE AND SELF-TEST SECTION ===\nSending command: \"Execute SMART Short self-test routine immediately in off-line mode\".\nDrive command \"Execute SMART Short self-test routine immediately in off-line mode\" successful.\nTesting has begun.\nPlease wait 2 minutes for test to complete.\nTest will complete after Thu May 16 14:31:06 2024 +04\nUse smartctl -X to abort test.\n)\";\n\n\tconst bool matched = app_regex_partial_match(R\"(/^Drive command .* successful\\.\\nTesting has begun\\.$/mi)\", input);\n\tREQUIRE(matched == true);\n}\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/tests/test_smartctl_parser.cpp",
    "content": "/******************************************************************************\nLicense: BSD Zero Clause License\nCopyright:\n\t(C) 2022 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib_tests\n/// \\weakgroup applib_tests\n/// @{\n\n#include \"catch2/catch.hpp\"\n\n#include \"applib/smartctl_parser.h\"\n\n\n\nTEST_CASE(\"SmartctlFormatDetection\", \"[app][parser]\")\n{\n\tREQUIRE(SmartctlParser::detect_output_format({}).error().data() == SmartctlParserError::EmptyInput);\n\n\tREQUIRE(SmartctlParser::detect_output_format(\"smart\").error().data() == SmartctlParserError::UnsupportedFormat);\n\n\tREQUIRE(SmartctlParser::detect_output_format(\"{  }\").value() == SmartctlOutputFormat::Json);\n\n\tREQUIRE(SmartctlParser::detect_output_format(\" \\n {  } \").value() == SmartctlOutputFormat::Json);\n\n\tREQUIRE(SmartctlParser::detect_output_format(\"smartctl\").value() == SmartctlOutputFormat::Text);\n\n\tREQUIRE(SmartctlParser::detect_output_format(\nR\"(smartctl 7.2 2020-12-30 r5155 [x86_64-linux-5.3.18-lp152.66-default] (SUSE RPM)\nCopyright (C) 2002-20, Bruce Allen, Christian Franke, www.smartmontools.org\n\n)\").value() == SmartctlOutputFormat::Text);\n\n}\n\n\n\n/// @}\n\n\n\n\n"
  },
  {
    "path": "src/applib/tests/test_smartctl_version_parser.cpp",
    "content": "/******************************************************************************\nLicense: BSD Zero Clause License\nCopyright:\n\t(C) 2022 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib_tests\n/// \\weakgroup applib_tests\n/// @{\n\n#include \"catch2/catch.hpp\"\n\n#include \"applib/smartctl_version_parser.h\"\n\n\n\nTEST_CASE(\"SmartctlVersionParser\", \"[app][parser]\")\n{\n\tstd::string version_only, version_full;\n\n\tSECTION(\"Parse with version keyword\") {\n\t\tSmartctlVersionParser::parse_version_text(\"smartctl version 5.37\", version_only, version_full);\n\t\tREQUIRE(version_only == \"5.37\");\n\t\tREQUIRE(version_full == \"5.37\");\n\t}\n\n\tSECTION(\"Parse without version keyword\") {\n\t\tSmartctlVersionParser::parse_version_text(\"smartctl 5.39\", version_only, version_full);\n\t\tREQUIRE(version_only == \"5.39\");\n\t\tREQUIRE(version_full == \"5.39\");\n\t}\n\n\tSECTION(\"Parse with date (CVS)\") {\n\t\tSmartctlVersionParser::parse_version_text(\"smartctl 5.39 2009-06-03 20:10\", version_only, version_full);\n\t\tREQUIRE(version_only == \"5.39\");\n\t\tREQUIRE(version_full == \"5.39 2009-06-03 20:10\");\n\t}\n\n\tSECTION(\"Parse with date (SVN)\") {\n\t\tSmartctlVersionParser::parse_version_text(\"smartctl 5.39 2009-08-08 r2873\", version_only, version_full);\n\t\tREQUIRE(version_only == \"5.39\");\n\t\tREQUIRE(version_full == \"5.39 2009-08-08 r2873\");\n\t}\n\n\tSECTION(\"Parse pre-releases\") {\n\t\tSmartctlVersionParser::parse_version_text(\"smartctl pre-7.4 2023-06-13 r5481\", version_only, version_full);\n\t\tREQUIRE(version_only == \"7.4\");\n\t\tREQUIRE(version_full == \"pre-7.4 2023-06-13 r5481\");\n\t}\n\n\tSECTION(\"Parse old 5.0\") {\n\t\tSmartctlVersionParser::parse_version_text(\"smartctl version 5.0-49\", version_only, version_full);\n\t\tREQUIRE(version_only == \"5.0-49\");\n\t\tREQUIRE(version_full == \"5.0-49\");\n\t}\n\n\tSECTION(\"Parse full output (SVN)\") {\n\t\tconst std::string output =\nR\"(smartctl 7.2 2020-12-30 r5155 [x86_64-linux-5.3.18-lp152.66-default] (SUSE RPM)\nCopyright (C) 2002-20, Bruce Allen, Christian Franke, www.smartmontools.org\n\nsmartctl comes with ABSOLUTELY NO WARRANTY. This is free\nsoftware, and you are welcome to redistribute it under\nthe terms of the GNU General Public License; either\nversion 2, or (at your option) any later version.\nSee http://www.gnu.org for further details.\n\nsmartmontools release 7.2 dated 2020-12-30 at 16:48:30 UTC\nsmartmontools SVN rev 5155 dated 2020-12-30 at 16:49:18\nsmartmontools build host: x86_64-suse-linux-gnu\nsmartmontools build with: C++14, GCC 7.5.0\nsmartmontools configure arguments: '--host=x86_64-suse-linux-gnu' '--build=x86_64-suse-linux-gnu' '--program-prefix=' '--prefix=/usr' '--exec-prefix=/usr' '--bindir=/usr/bin' '--sbindir=/usr/sbin' '--sysconfdir=/etc' '--datadir=/usr/share' '--includedir=/usr/include' '--libdir=/usr/lib64' '--libexecdir=/usr/lib' '--localstatedir=/var' '--sharedstatedir=/var/lib' '--mandir=/usr/share/man' '--infodir=/usr/share/info' '--disable-dependency-tracking' '--docdir=/usr/share/doc/packages/smartmontools' '--with-selinux' '--with-libsystemd' '--with-systemdsystemunitdir=/usr/lib/systemd/system' '--with-savestates' '--with-attributelog' '--with-nvme-devicescan' 'build_alias=x86_64-suse-linux-gnu' 'host_alias=x86_64-suse-linux-gnu' 'CXXFLAGS=-O2 -g -m64 -fmessage-length=0 -D_FORTIFY_SOURCE=2 -fstack-protector -funwind-tables -fasynchronous-unwind-tables -fPIE ' 'LDFLAGS=-pie' 'CFLAGS=-O2 -g -m64 -fmessage-length=0 -D_FORTIFY_SOURCE=2 -fstack-protector -funwind-tables -fasynchronous-unwind-tables  -fPIE' 'PKG_CONFIG_PATH=:/usr/lib64/pkgconfig:/usr/share/pkgconfig'\n)\";\n\t\tSmartctlVersionParser::parse_version_text(output, version_only, version_full);\n\t\tREQUIRE(version_only == \"7.2\");\n\t\tREQUIRE(version_full == \"7.2 2020-12-30 r5155\");\n\t}\n\n\tSECTION(\"Parse full output (git)\") {\n\t\tconst std::string output =\nR\"(smartctl 7.3 (build date Feb 11 2022) [x86_64-linux-5.3.18-lp152.66-default] (local build)\nCopyright (C) 2002-22, Bruce Allen, Christian Franke, www.smartmontools.org\n\nsmartctl comes with ABSOLUTELY NO WARRANTY. This is free\nsoftware, and you are welcome to redistribute it under\nthe terms of the GNU General Public License; either\nversion 2, or (at your option) any later version.\nSee https://www.gnu.org for further details.\n\nsmartmontools release 7.3 dated 2020-12-30 at 16:48:30 UTC\nsmartmontools SVN rev is unknown\nsmartmontools build host: x86_64-pc-linux-gnu\nsmartmontools build with: C++11, GCC 7.5.0\nsmartmontools configure arguments: [no arguments given]\n\n)\";\n\t\tSmartctlVersionParser::parse_version_text(output, version_only, version_full);\n\t\tREQUIRE(version_only == \"7.3\");\n\t\tREQUIRE(version_full == \"7.3\");\n\t}\n\n}\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/warning_colors.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2026 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#include <glibmm.h>\n\n#include \"warning_colors.h\"\n#include \"gui_utils.h\"\n\n\nbool app_property_get_row_highlight_colors(bool dark_mode, WarningLevel warning, std::string& fg, std::string& bg)\n{\n\t// Note: we're setting both fg and bg, to avoid theme conflicts.\n\tif (warning == WarningLevel::Notice) {\n\t\tfg = dark_mode ? \"#FFFFFF\" : \"#000000\";  // white for dark themes, black for light themes\n\t\tbg = dark_mode ? \"#6B2050\" : \"#FFD5EE\";  // dark pinkish for dark themes, pinkish for light themes\n\n\t} else if (warning == WarningLevel::Warning) {\n\t\tfg = dark_mode ? \"#FFFFFF\" : \"#000000\";  // white for dark themes, black for light themes\n\t\tbg = dark_mode ? \"#802020\" : \"#FFA0A0\";  // dark red for dark themes, light red for light themes\n\n\t} else if (warning == WarningLevel::Alert) {\n\t\tfg = dark_mode ? \"#FFFFFF\" : \"#000000\";  // white for dark themes, black for light themes\n\t\tbg = dark_mode ? \"#AA0000\" : \"#FF0000\";  // darker red for dark themes, bright red for light themes\n\t}\n\n\treturn !(fg.empty());\n}\n\n\n\nbool app_property_get_label_highlight_color(bool dark_mode, WarningLevel warning, std::string& fg)\n{\n\tif (warning == WarningLevel::None) {\n\t\treturn false;\n\t}\n\n\tif (warning == WarningLevel::Notice) {\n\t\tfg = dark_mode ? \"#FF9999\" : \"#770000\";  // lighter red for dark themes, very dark red for light themes\n\n\t} else if (warning == WarningLevel::Warning) {\n\t\tfg = dark_mode ? \"#FF6666\" : \"#C00000\";  // lighter red for dark themes, dark red for light themes\n\n\t} else if (warning == WarningLevel::Alert) {\n\t\tfg = dark_mode ? \"#FF4444\" : \"#FF0000\";  // lighter/pink red for dark themes, bright red for light themes\n\t}\n\n\treturn !(fg.empty());\n}\n\n\n\nstd::string storage_property_get_warning_reason(const StorageProperty& p)\n{\n\tstd::string fg, start = \"<b>\", stop = \"</b>\";\n\tif (app_property_get_label_highlight_color(gui_is_dark_theme_active(), p.warning_level, fg)) {\n\t\tstart += \"<span color=\\\"\" + fg + \"\\\">\";\n\t\tstop = \"</span>\" + stop;\n\t}\n\n\tswitch (p.warning_level) {\n\t\tcase WarningLevel::None:\n\t\t\t// nothing\n\t\t\tbreak;\n\t\tcase WarningLevel::Notice:\n\t\t\t/// Translators: %1 and %2 are HTML tags, %3 is a message.\n\t\t\treturn Glib::ustring::compose(_(\"%1Notice:%2 %3\"), start, stop, Glib::Markup::escape_text(p.warning_reason));\n\t\tcase WarningLevel::Warning:\n\t\t\t/// Translators: %1 and %2 are HTML tags, %3 is a message.\n\t\t\treturn Glib::ustring::compose(_(\"%1Warning:%2 %3\"), start, stop, Glib::Markup::escape_text(p.warning_reason));\n\t\tcase WarningLevel::Alert:\n\t\t\t/// Translators: %1 and %2 are HTML tags, %3 is a message.\n\t\t\treturn Glib::ustring::compose(_(\"%1ALERT:%2 %3\"), start, stop, Glib::Markup::escape_text(p.warning_reason));\n\t}\n\n\treturn {};\n}\n\n\n\n/// @}\n"
  },
  {
    "path": "src/applib/warning_colors.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2026 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef WARNING_COLORS_H\n#define WARNING_COLORS_H\n\n#include <string>\n\n#include \"storage_property.h\"\n#include \"warning_level.h\"\n\n\n/// Get colors for tree rows according to warning severity.\n/// \\return true if the colors were changed.\nbool app_property_get_row_highlight_colors(bool dark_mode, WarningLevel warning, std::string& fg, std::string& bg);\n\n\n/// Get color for labels according to warning severity.\n/// \\return true if the color was changed.\nbool app_property_get_label_highlight_color(bool dark_mode, WarningLevel warning, std::string& fg);\n\n\n/// Format warning text, but without description\nstd::string storage_property_get_warning_reason(const StorageProperty& p);\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/warning_level.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef WARNING_LEVEL_H\n#define WARNING_LEVEL_H\n\n\n\n/// Warning type\nenum class WarningLevel {\n\tNone,  ///< No warning\n\tNotice,  ///< A known attribute is somewhat disturbing, but no smart error\n\tWarning,  ///< SMART warning is raised by old-age attribute\n\tAlert  ///< SMART warning is raised by pre-fail attribute, and similar errors\n};\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/applib/window_instance_manager.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup applib\n/// \\weakgroup applib\n/// @{\n\n#ifndef WINDOW_INSTANCE_MANAGER_H\n#define WINDOW_INSTANCE_MANAGER_H\n\n#include <memory>\n#include <unordered_set>\n#include <glibmm.h>\n#include <gtkmm.h>\n\n\n\nclass WindowInstanceManagerStorage {\n\tpublic:\n\n\t\t/// Store an instance and keep it alive.\n\t\t/// Return a newly stored shared pointer to the instance.\n\t\tstatic std::shared_ptr<Gtk::Window> store_instance(Gtk::Window* obj)\n\t\t{\n\t\t\tstd::shared_ptr<Gtk::Window> obj_sptr(obj);\n\t\t\tinstances_.insert(obj_sptr);\n\t\t\treturn obj_sptr;\n\t\t}\n\n\n\t\t/// Destroy a previously stored instance\n\t\tstatic void destroy_instance(Gtk::Window* window)\n\t\t{\n\t\t\tauto found = std::find_if(instances_.begin(), instances_.end(), [window](const std::shared_ptr<Gtk::Window>& elem) { return elem.get() == window; });\n\t\t\tif (found != instances_.end()) {\n\t\t\t\tinstances_.erase(found);\n\t\t\t}\n\t\t}\n\n\n\t\t/// Destroy all stored instances\n\t\tstatic void destroy_all_instances()\n\t\t{\n\t\t\tinstances_.clear();\n\t\t}\n\n\n\tprivate:\n\t\t/// All instances of created objects, kept alive by shared_ptr\n\t\tstatic inline std::unordered_set<std::shared_ptr<Gtk::Window>> instances_;\n\n};\n\n\n\n/// Inherit this class template to have a single- or multi-instance objects, e.g. windows.\n/// This is a multi-instance implementation.\ntemplate<class Child, bool MultiInstance>\nclass WindowInstanceManager {\n\tprotected:\n\n\t\t/// Can't construct / delete this directly! use create() and destroy()\n\t\tWindowInstanceManager() = default;\n\n\n\tpublic:\n\n\t\t/// Deleted\n\t\tWindowInstanceManager(const WindowInstanceManager& other) = delete;\n\n\t\t/// Deleted\n\t\tWindowInstanceManager(WindowInstanceManager&& other) = delete;\n\n\t\t/// Deleted\n\t\tWindowInstanceManager& operator=(const WindowInstanceManager&) = delete;\n\n\t\t/// Deleted\n\t\tWindowInstanceManager& operator=(WindowInstanceManager&&) = delete;\n\n\t\t/// Default, must be polymorphic for casts to succeed\n\t\tvirtual ~WindowInstanceManager() = default;\n\n\n\t\t/// The default multi-instance implementation doesn't support `instance()`\n\t\tstatic Child* instance() = delete;\n\n\n\t\t/// Destroy a previously stored instance\n\t\tvoid destroy_instance()\n\t\t{\n\t\t\tWindowInstanceManagerStorage::destroy_instance(dynamic_cast<Gtk::Window*>(this));  // side-cast\n\t\t}\n\n\n\tprotected:\n\n\t\t/// Store an instance and keep it alive.\n\t\t/// Return a newly stored shared pointer to the instance.\n\t\tstatic std::shared_ptr<Child> store_instance(Child* obj)\n\t\t{\n\t\t\treturn std::dynamic_pointer_cast<Child>(WindowInstanceManagerStorage::store_instance(obj));\n\t\t}\n\n};\n\n\n\n\n/// Single-instance specialization. This deletes the instance on program exit.\ntemplate<class Child>\nclass WindowInstanceManager<Child, false> {\n\tprotected:\n\n\t\t/// Can't construct / delete this directly! use create() and destroy()\n\t\tWindowInstanceManager() = default;\n\n\n\tpublic:\n\n\t\t/// Deleted\n\t\tWindowInstanceManager(const WindowInstanceManager& other) = delete;\n\n\t\t/// Deleted\n\t\tWindowInstanceManager(WindowInstanceManager&& other) = delete;\n\n\t\t/// Deleted\n\t\tWindowInstanceManager& operator=(const WindowInstanceManager&) = delete;\n\n\t\t/// Deleted\n\t\tWindowInstanceManager& operator=(WindowInstanceManager&&) = delete;\n\n\t\t/// Default, must be polymorphic for casts to succeed\n\t\tvirtual ~WindowInstanceManager() = default;\n\n\n\t\t/// Return a single existing instance of this template instantiation.\n\t\t/// \\return nullptr if no instances were created yet.\n\t\tstatic std::shared_ptr<Child> instance()\n\t\t{\n\t\t\treturn instance_.lock();\n\t\t}\n\n\n\t\t/// Destroy a previously stored instance\n\t\tvoid destroy_instance()\n\t\t{\n\t\t\tWindowInstanceManagerStorage::destroy_instance(dynamic_cast<Gtk::Window*>(this));  // side-cast\n\t\t}\n\n\n\tprotected:\n\n\t\t/// Store an instance and keep it alive.\n\t\t/// Return a newly stored shared pointer to the instance.\n\t\tstatic std::shared_ptr<Child> store_instance(Child* obj)\n\t\t{\n\t\t\tauto inst = std::dynamic_pointer_cast<Child>(WindowInstanceManagerStorage::store_instance(obj));\n\t\t\tinstance_ = inst;\n\t\t\treturn inst;\n\t\t}\n\n\n\tprivate:\n\n\t\tstatic inline std::weak_ptr<Child> instance_;  ///< Single instance pointer\n\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/build_config/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n# We print these here (as opposed to compiler_options.cmake) because it's the final state of these variables.\n\n# Host system\nmessage(STATUS \"Host system:\")\nmessage(STATUS \"CMAKE_HOST_SYSTEM_NAME: ${CMAKE_HOST_SYSTEM_NAME}\")\nmessage(STATUS \"CMAKE_HOST_SYSTEM_VERSION: ${CMAKE_HOST_SYSTEM_VERSION}\")\nmessage(STATUS \"CMAKE_HOST_SYSTEM_PROCESSOR: ${CMAKE_HOST_SYSTEM_PROCESSOR}\")\n\n# Target system\nmessage(STATUS \"Target system:\")\nmessage(STATUS \"CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}\")\nmessage(STATUS \"CMAKE_SYSTEM_VERSION: ${CMAKE_SYSTEM_VERSION}\")\nmessage(STATUS \"CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}\")\nmessage(STATUS \"CMAKE_SIZEOF_VOID_P: ${CMAKE_SIZEOF_VOID_P}\")\n\n# Compiler\nmessage(STATUS \"CMAKE_CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}\")\nmessage(STATUS \"CMAKE_CXX_COMPILER_VERSION: ${CMAKE_CXX_COMPILER_VERSION}\")\nmessage(STATUS \"CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER}\")\nmessage(STATUS \"CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN: ${CMAKE_CXX_COMPILER_EXTERNAL_TOOLCHAIN}\")\nmessage(STATUS \"CMAKE_CXX_COMPILER_TARGET: ${CMAKE_CXX_COMPILER_TARGET}\")\nmessage(STATUS \"CMAKE_CXX_COMPILE_FEATURES: ${CMAKE_CXX_COMPILE_FEATURES}\")\n\nmessage(STATUS \"CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}\")\nmessage(STATUS \"CMAKE_CXX_FLAGS_DEBUG: ${CMAKE_CXX_FLAGS_DEBUG}\")\nmessage(STATUS \"CMAKE_CXX_FLAGS_MINSIZEREL: ${CMAKE_CXX_FLAGS_MINSIZEREL}\")\nmessage(STATUS \"CMAKE_CXX_FLAGS_RELEASE: ${CMAKE_CXX_FLAGS_RELEASE}\")\nmessage(STATUS \"CMAKE_CXX_FLAGS_RELWITHDEBINFO: ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}\")\n\nmessage(STATUS \"CMAKE_STATIC_LINKER_FLAGS: ${CMAKE_STATIC_LINKER_FLAGS}\")\nmessage(STATUS \"CMAKE_STATIC_LINKER_FLAGS_DEBUG: ${CMAKE_STATIC_LINKER_FLAGS_DEBUG}\")\nmessage(STATUS \"CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL: ${CMAKE_STATIC_LINKER_FLAGS_MINSIZEREL}\")\nmessage(STATUS \"CMAKE_STATIC_LINKER_FLAGS_RELEASE: ${CMAKE_STATIC_LINKER_FLAGS_RELEASE}\")\nmessage(STATUS \"CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO: ${CMAKE_STATIC_LINKER_FLAGS_RELWITHDEBINFO}\")\n\nmessage(STATUS \"CMAKE_SHARED_LINKER_FLAGS: ${CMAKE_SHARED_LINKER_FLAGS}\")\nmessage(STATUS \"CMAKE_SHARED_LINKER_FLAGS_DEBUG: ${CMAKE_SHARED_LINKER_FLAGS_DEBUG}\")\nmessage(STATUS \"CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL: ${CMAKE_SHARED_LINKER_FLAGS_MINSIZEREL}\")\nmessage(STATUS \"CMAKE_SHARED_LINKER_FLAGS_RELEASE: ${CMAKE_SHARED_LINKER_FLAGS_RELEASE}\")\nmessage(STATUS \"CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO: ${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO}\")\n\nmessage(STATUS \"CMAKE_EXE_LINKER_FLAGS: ${CMAKE_EXE_LINKER_FLAGS}\")\nmessage(STATUS \"CMAKE_EXE_LINKER_FLAGS_DEBUG: ${CMAKE_EXE_LINKER_FLAGS_DEBUG}\")\nmessage(STATUS \"CMAKE_EXE_LINKER_FLAGS_MINSIZEREL: ${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL}\")\nmessage(STATUS \"CMAKE_EXE_LINKER_FLAGS_RELEASE: ${CMAKE_EXE_LINKER_FLAGS_RELEASE}\")\nmessage(STATUS \"CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO: ${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}\")\n\n# Suppress warning about manually specified variables not being used (this is a very common variable)\nmessage(STATUS \"CMAKE_C_COMPILER: ${CMAKE_C_COMPILER}\")\n\n\n# These variables are used in build_config.in.h\nif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"Windows\"\n\t\tOR \"${CMAKE_SYSTEM_NAME}\" MATCHES \"CYGWIN.*\")\n\tif (CMAKE_SIZEOF_VOID_P EQUAL 4)\n\t\tset(CONFIG_KERNEL_WINDOWS32 TRUE)\n\telse()\n\t\tset(CONFIG_KERNEL_WINDOWS64 TRUE)\n\tendif()\nelseif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"Linux\")\n\tset(CONFIG_KERNEL_LINUX TRUE)\nelseif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"FreeBSD\"\n\t\tOR \"${CMAKE_SYSTEM_NAME}\" MATCHES \"GNU/kFreeBSD\")\n\tset(CONFIG_KERNEL_FREEBSD TRUE)\nelseif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"DragonFly\")\n\tset(CONFIG_KERNEL_DRAGONFLY TRUE)\nelseif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"NetBSD\")\n\tset(CONFIG_KERNEL_NETBSD TRUE)\nelseif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"OpenBSD\")\n\tset(CONFIG_KERNEL_OPENBSD TRUE)\nelseif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"SunOS\" OR \"${CMAKE_SYSTEM_NAME}\" MATCHES \"Solaris\")\n\tset(CONFIG_KERNEL_SOLARIS TRUE)\nelseif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"Darwin\")\n\tset(CONFIG_KERNEL_DARWIN TRUE)\nelseif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"QNX\")\n\tset(CONFIG_KERNEL_QNX TRUE)\nendif()\n\n\n# Additionally, these macros are defined by OS / compilers:\n\n# Linux: gcc defines __linux__ on linux. Other defines include: __linux, __unix__, __gnu_linux__,\n# linux, unix, __i386__, i386.\n\n# Windows 32: mingw defines _WIN32. msvc defines _WIN32 (built-in), and WIN32 via windows.h.\n# Other mingw defines include: __WINNT, __WINNT__, __WIN32__, __i386, _X86_,\n# i386, __i386__, WIN32, __MINGW32__, WINNT (all equal to 1).\n\n# Windows 64: mingw64 defines the same stuff as 32-bit one, plus _WIN64, __MINGW64__, etc. .\n# Keep in mind that if you're generating a 32-bit application, the kernel will\n# be windows32 even if you run it on 64-bit Windows.\n\n# FreeBSD: gcc built-in defines (to check for freebsd kernel):\n# defined(__FreeBSD__) || defined(__FreeBSD_kernel__).\n# See http://glibc-bsd.alioth.debian.org/porting/PORTING .\n\n\n\n#include(CheckSymbolExists)\n\n# getrawpartition() for netbsd and openbsd in -lutil.\n# This is just a check to see whether we have it. The code uses getrawpartition() without any checks on *BSD.\n#check_symbol_exists(getrawpartition \"util.h\" HAVE_GETRAWPARTITION)\n#message(STATUS \"getrawpartition() detected: ${HAVE_GETRAWPARTITION}\")\n\n\n# System-specific definitions\nadd_library(build_config INTERFACE)\nconfigure_file(\"build_config.in.h\" \"build_config.h\" ESCAPE_QUOTES @ONLY)\ntarget_sources(build_config INTERFACE\n\t${CMAKE_CURRENT_BINARY_DIR}/build_config.h\n)\ntarget_include_directories(build_config INTERFACE \"${CMAKE_CURRENT_BINARY_DIR}\")\n\n"
  },
  {
    "path": "src/build_config/build_config.in.h",
    "content": "/******************************************************************************\nLicense: BSD Zero Clause License\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup build_config\n/// \\weakgroup build_config\n/// @{\n\n#ifndef BUILD_CONFIG_H\n#define BUILD_CONFIG_H\n\n// NOTE: Parts of this file are replaced by cmake\n\n#include <stdexcept>\n\n#cmakedefine CONFIG_KERNEL_WINDOWS32\n#cmakedefine CONFIG_KERNEL_WINDOWS64\n#cmakedefine CONFIG_KERNEL_LINUX\n#cmakedefine CONFIG_KERNEL_FREEBSD\n#cmakedefine CONFIG_KERNEL_OPENBSD\n#cmakedefine CONFIG_KERNEL_NETBSD\n#cmakedefine CONFIG_KERNEL_DRAGONFLY\n#cmakedefine CONFIG_KERNEL_SOLARIS\n#cmakedefine CONFIG_KERNEL_DARWIN\n#cmakedefine CONFIG_KERNEL_QNX\n\n#if defined CONFIG_KERNEL_WINDOWS32 || defined CONFIG_KERNEL_WINDOWS64\n\t#define CONFIG_KERNEL_FAMILY_WINDOWS\n#endif\n\n\n/// Build environment constexpr variables\nstruct BuildEnv {\n\n\t/// See if this is a debug build.\n\tstatic constexpr bool debug_build()\n\t{\n\t#ifdef DEBUG_BUILD\n\t\treturn true;\n\t#else\n\t\treturn false;\n\t#endif\n\t}\n\n\t/// CMake package name\n\tstatic constexpr const char* package_name() { return \"@CMAKE_PROJECT_NAME@\"; }\n\n\t/// CMake package version\n\tstatic constexpr const char* package_version() { return \"@CMAKE_PROJECT_VERSION@\"; }\n\n\t/// pkgdata directory - /usr/share\n\tstatic constexpr const char* package_pkgdata_dir() { return \"@CMAKE_INSTALL_FULL_DATADIR@\"; }\n\n\t/// sysconf directory - /etc\n\tstatic constexpr const char* package_sysconf_dir() { return \"@CMAKE_INSTALL_FULL_SYSCONFDIR@\"; }\n\n\t/// locale directory - /usr/share/locale\n\tstatic constexpr const char* package_locale_dir() { return \"@CMAKE_INSTALL_FULL_LOCALEDIR@\"; }\n\n\t/// doc directory - /usr/share/doc/package_name\n\tstatic constexpr const char* package_doc_dir() { return \"@CMAKE_INSTALL_FULL_DOCDIR@\"; }\n\n\t/// Top source directory. Available in debug build only.\n\tstatic constexpr const char* package_top_source_dir()\n\t{\n\t#ifdef DEBUG_BUILD\n\t\t// Hide it in ifdef so that the binary does not contain it unless required.\n\t\treturn \"@CMAKE_SOURCE_DIR@\";\n\t#else\n\t\treturn \"\";  // not available in non-debug builds\n\t#endif\n\t}\n\n\n\t/// Check if target kernel is 32-bit Windows\n\tstatic constexpr bool is_kernel_windows32()\n\t{\n\t#ifdef CONFIG_KERNEL_WINDOWS32\n\t\treturn true;\n\t#else\n\t\treturn false;\n\t#endif\n\t}\n\n\t/// Check if target kernel is 64-bit Windows\n\tstatic constexpr bool is_kernel_windows64()\n\t{\n\t#ifdef CONFIG_KERNEL_WINDOWS64\n\t\treturn true;\n\t#else\n\t\treturn false;\n\t#endif\n\t}\n\n\t/// Check if target kernel is Windows.\n\t/// Note: This slightly differs from _WIN32 - the macro indicates that win32 API is available.\n\tstatic constexpr bool is_kernel_family_windows()\n\t{\n\t\treturn is_kernel_windows32() || is_kernel_windows64();\n\t}\n\n\n\t/// Check if target kernel is Linux.\n\tstatic constexpr bool is_kernel_linux()\n\t{\n\t#ifdef CONFIG_KERNEL_LINUX\n\t\treturn true;\n\t#else\n\t\treturn false;\n\t#endif\n\t}\n\n\t/// Check if target kernel is FreeBSD.\n\tstatic constexpr bool is_kernel_freebsd()\n\t{\n\t#ifdef CONFIG_KERNEL_FREEBSD\n\t\treturn true;\n\t#else\n\t\treturn false;\n\t#endif\n\t}\n\n\t/// Check if target kernel is OpenBSD.\n\tstatic constexpr bool is_kernel_openbsd()\n\t{\n\t#ifdef CONFIG_KERNEL_OPENBSD\n\t\treturn true;\n\t#else\n\t\treturn false;\n\t#endif\n\t}\n\n\t/// Check if target kernel is NetBSD.\n\tstatic constexpr bool is_kernel_netbsd()\n\t{\n\t#ifdef CONFIG_KERNEL_NETBSD\n\t\treturn true;\n\t#else\n\t\treturn false;\n\t#endif\n\t}\n\n\t/// Check if target kernel is DragonflyBSD.\n\tstatic constexpr bool is_kernel_dragonfly()\n\t{\n\t#ifdef CONFIG_KERNEL_DRAGONFLY\n\t\treturn true;\n\t#else\n\t\treturn false;\n\t#endif\n\t}\n\n\t/// Check if target kernel is Solaris.\n\tstatic constexpr bool is_kernel_solaris()\n\t{\n\t#ifdef CONFIG_KERNEL_SOLARIS\n\t\treturn true;\n\t#else\n\t\treturn false;\n\t#endif\n\t}\n\n\t/// Check if target kernel is Darwin.\n\tstatic constexpr bool is_kernel_darwin()\n\t{\n\t#ifdef CONFIG_KERNEL_DARWIN\n\t\treturn true;\n\t#else\n\t\treturn false;\n\t#endif\n\t}\n\n\t/// Check if target kernel is QNX.\n\tstatic constexpr bool is_kernel_qnx()\n\t{\n\t#ifdef CONFIG_KERNEL_QNX\n\t\treturn true;\n\t#else\n\t\treturn false;\n\t#endif\n\t}\n\n};\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/build_config/compiler_options.cmake",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n# This file is included from root CMakeLists.txt\n\nset(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} \"${CMAKE_SOURCE_DIR}/data/cmake/\")\n\n\n# --- Set the default C++ standard\nset(CMAKE_CXX_STANDARD 20)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\n# C++17 filesystem (including experimental)\n#find_package(Filesystem REQUIRED COMPONENTS Experimental Final)\n\n\n# Enable PIC, required on many systems\nset(CMAKE_POSITION_INDEPENDENT_CODE ON)\n\n# Hide symbols by default\nset(CMAKE_CXX_VISIBILITY_PRESET hidden)\nset(CMAKE_VISIBILITY_INLINES_HIDDEN ON)\n\n\n# --- OS / compiler extensions\n\n# These extensions are useful to enable OS-specific functionality like 64-bit file offsets.\n\n# Enable compiler extensions (like -std=gnu++17 instead of -std=c++17)\nset(CMAKE_CXX_EXTENSIONS ON)\n\n# glibc: all-extensions macro: _GNU_SOURCE.\n# Defined automatically when used with glibc, but not others.\n# We define it for all gcc systems.\nif(\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"GNU\")\n\tadd_compile_definitions(_GNU_SOURCE)\nendif()\n\n# Solaris: The problem with solaris is that it has too many incompatible\n# feature test macros, and there is no enable-all macro. Autoconf\n# assumes that __EXTENSIONS__ is the one, but it should be defined\n# _in addition_ to feature test macros. Note that on its own, __EXTENSIONS__\n# still enables some useful stuff like _LARGEFILE64_SOURCE.\n# To support some posix macros, additional link objects are also required.\n# Some *_SOURCE feature test macros may be incompatible with C++.\n# http://docs.sun.com/app/docs/doc/816-5175/standards-5?a=view\n\n# Mingw: This enables standards-compliant stdio behaviour (regarding printf and\n# friends), as opposed to msvc-compatible one. This is usually enabled\n# by default if one of the usual macros are encountered (_XOPEN_SOURCE,\n# _GNU_SOURCE, etc.).\n# See _mingw.h for details.\nif (WIN32)\n\t# No effect in MSVC, doesn't hurt.\n\tadd_compile_definitions(__USE_MINGW_ANSI_STDIO=1)\n\n\t# Enable Vista winapi\n\tadd_compile_definitions(WINVER=0x0600)\nendif()\n\n# Darwin: Enable large file support\nif (\"${CMAKE_SYSTEM_NAME}\" MATCHES \"Darwin\")\n\tadd_compile_definitions(_DARWIN_USE_64_BIT_INODE=1)\nendif()\n\n# In many environments this enables large file support. For others, it's harmless.\nadd_compile_definitions(_FILE_OFFSET_BITS=64)\n\n\n\n# --- Compiler flags\n\n# Note: Some operating systems / compilers have built-in defines.\n# For gcc, check with\n# $ gcc -dM -E - < /dev/null\n\n\n# MT flags\n# -pthread -D_MT -D_THREAD_SAFE (linux/gcc, linux/clang, freebsd, dragonfly)\n# -mthreads -D_THREAD_SAFE (win/gcc, win/clang)\n# -pthread -D_REENTRANT (openbsd, netbsd)\n# -pthreads -D_MT -D_THREAD_SAFE -D_REENTRANT (solaris)\n# darwin, qnx - MT enabled by default.\n\n\n# Enable compiler warnings\noption(APP_COMPILER_ENABLE_WARNINGS \"Enable compiler warnings\" OFF)\n\n# Warnings are for latest versions of compilers, for developers only.\nif (APP_COMPILER_ENABLE_WARNINGS)\n\tif (${CMAKE_CXX_COMPILER_ID} STREQUAL Clang\n\t\t\tOR ${CMAKE_CXX_COMPILER_ID} STREQUAL GNU\n\t\t\tOR ${CMAKE_CXX_COMPILER_ID} STREQUAL AppleClang)\n\t\tadd_compile_options(-Wall -Wextra\n\t\t\t-Wcast-qual -Wconversion -Wfloat-equal -Wnon-virtual-dtor -Woverloaded-virtual\n\t\t\t-Wpointer-arith -Wshadow -Wsign-compare -Wsign-promo -Wundef -Wwrite-strings\n\t\t)\n\t\tif (${CMAKE_CXX_COMPILER_ID} STREQUAL Clang)\n\t\t\t# libdebug needs custom format strings\n\t\t\tadd_compile_options(-Wno-format-security)\n\t\tendif()\n\telseif (${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC)\n\t\tadd_compile_options(/W4)\n\tendif()\nendif()\n\n\n# GTK uses MS bitfields, so we need this in mingw\nif (MINGW)\n\tadd_compile_options(-mms-bitfields)\nendif()\n\n# Define macros to check the debug build\nadd_compile_definitions(\"$<$<CONFIG:DEBUG>:DEBUG>\")\nadd_compile_definitions(\"$<$<CONFIG:DEBUG>:DEBUG_BUILD>\")\n\n"
  },
  {
    "path": "src/gui/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n# gsmartcontrol binary\nadd_executable(gsmartcontrol WIN32)\n\ntarget_sources(gsmartcontrol PRIVATE\n\tgsc_about_dialog.cpp\n\tgsc_about_dialog.h\n\tgsc_add_device_window.cpp\n\tgsc_add_device_window.h\n\tgsc_executor_error_dialog.cpp\n\tgsc_executor_error_dialog.h\n\tgsc_executor_log_window.cpp\n\tgsc_executor_log_window.h\n\tgsc_info_window.cpp\n\tgsc_info_window.h\n\tgsc_init.cpp\n\tgsc_init.h\n\tgsc_main.cpp\n\tgsc_main_window.cpp\n\tgsc_main_window.h\n\tgsc_main_window_iconview.cpp\n\tgsc_main_window_iconview.h\n\tgsc_preferences_window.cpp\n\tgsc_preferences_window.h\n\tgsc_startup_settings.h\n\tgsc_text_window.h\n)\n\n# Win32 resource file\nif (WIN32)\n\tconfigure_file(\"gsc_winres.in.rc\" \"${CMAKE_CURRENT_BINARY_DIR}/gsc_winres.rc\" ESCAPE_QUOTES @ONLY NEWLINE_STYLE DOS)\n\tfile(COPY \"${CMAKE_SOURCE_DIR}/data/gsmartcontrol.ico\" DESTINATION \"${CMAKE_CURRENT_BINARY_DIR}\")  # required by winres\n\ttarget_sources(gsmartcontrol PRIVATE \"${CMAKE_CURRENT_BINARY_DIR}/gsc_winres.rc\")\nendif()\n\n# Win32 manifest\nif (WIN32)\n\tset(WINDOWS_ARCH \"x86\")  # Needed for manifest .in file\n\tif (CMAKE_SIZEOF_VOID_P EQUAL 8)\n\t\tset(WINDOWS_ARCH \"amd64\")\n\tendif()\n\tconfigure_file(\"gsmartcontrol.exe.in.manifest\" \"${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol.exe.manifest\"\n\t\tESCAPE_QUOTES @ONLY NEWLINE_STYLE DOS)\n\n\t# We link the manifest into the binary instead of installing it.\n\t# install(FILES \"${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol.exe.manifest\" DESTINATION \"${CMAKE_INSTALL_SBINDIR}/\")\n\ttarget_sources(gsmartcontrol PRIVATE \"${CMAKE_CURRENT_BINARY_DIR}/gsmartcontrol.exe.manifest\")\nendif()\n\n\ntarget_link_libraries(gsmartcontrol\n\tPRIVATE\n\t\tapplib\n\t\tapp_gtkmm_interface\n\t\tbuild_config\n)\n\nif (WIN32)\n\tinstall(TARGETS gsmartcontrol DESTINATION .)\nelse()\n\tinstall(TARGETS gsmartcontrol DESTINATION \"${CMAKE_INSTALL_SBINDIR}/\")\nendif()\n\n\nadd_subdirectory(ui)\n\n"
  },
  {
    "path": "src/gui/gsc_about_dialog.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2025 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#include <vector>\n\n#include \"hz/debug.h\"\n#include \"hz/string_algo.h\"  // hz::string_*\n#include \"hz/launch_url.h\"\n#include \"hz/data_file.h\"\n\n#include \"gsc_about_dialog.h\"\n\n#include \"build_config.h\"  // VERSION\n\n\n\n// GtkBuilder needs this constructor\nGscAboutDialog::GscAboutDialog(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui)\n\t\t: AppBuilderWidget<GscAboutDialog, false, Gtk::AboutDialog>(gtkcobj, std::move(ui))\n{\n\t// Connect callbacks\n\n\t// Note: The dialogs have ESC accelerator attached by default.\n\n\tset_version(BuildEnv::package_version());\n\n\t// set these properties here (after setting hooks) to make the links work.\n\tset_website(\"https://gsmartcontrol.shaduri.dev\");\n\n//\tset_license_type(Gtk::LICENSE_GPL_3_0_ONLY);  // this overrides set_license()\n//\tset_license(hz::data_file_get_contents(\"doc\", \"LICENSE.txt\", 1*1024*1024));  // 1M\n\n\t// spammers go away\n\tset_copyright(Glib::ustring::compose(\"Copyright (C) %1\",\n\t\t\t\"2008 - 2025 Alexander Shaduri <ashaduri@gmail.com>\"));\n\n\t// set_authors({\"Alexander Shaduri <ashaduri@gmail.com>\"});\n\t// set_documenters({\"Alexander Shaduri <ashaduri@gmail.com>\"});\n\t// set_translator_credits({\"Alexander Shaduri <ashaduri@gmail.com>\"});\n\n\t// std::string authors_str = hz::data_file_get_contents(\"doc\", \"AUTHORS.txt\", 1*1024*1024);  // 1M\n\t// hz::string_any_to_unix(authors_str);\n\n\t// std::vector<Glib::ustring> authors;\n\t// hz::string_split(authors_str, '\\n', authors, true);\n\t//\n\t// for (auto& author : authors) {\n\t// \tstd::string s = author;\n\t// \thz::string_replace(s, \" '@' \", \"@\");  // despammer\n\t// \thz::string_replace(s, \" 'at' \", \"@\");  // despammer\n\t// \tauthor = s;\n\t// }\n\t// set_authors(authors);\n\n\t// set_documenters(authors);\n\n\t// std::string translators_str = hz::data_file_get_contents(\"doc\", \"TRANSLATORS.txt\", 10*1024*1024);  // 10M\n\t// set_translator_credits(translators_str);\n\n// \trun();  // don't use run - it's difficult to exit it manually.\n// \tshow();  // shown by the caller to enable setting the parent window.\n}\n\n\n\nvoid GscAboutDialog::on_response(int response_id)\n{\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Response ID: \" << response_id << \"\\n\");\n\n\tif (response_id == Gtk::RESPONSE_NONE || response_id == Gtk::RESPONSE_DELETE_EVENT\n\t\t\t|| response_id == Gtk::RESPONSE_CANCEL || response_id == Gtk::RESPONSE_CLOSE) {\n\t\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Closing the dialog.\\n\");\n\t\tdestroy_instance();  // close the window and delete the object\n\t}\n}\n\n\n\nbool GscAboutDialog::on_activate_link(const std::string& uri)\n{\n\t// The default handler, gtk_show_uri_on_window() doesn't work with mailto: URIs in Windows.\n\t// Our handler does.\n\treturn hz::launch_url(GTK_WINDOW(this->gobj()), uri).empty();\n}\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_about_dialog.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#ifndef GSC_ABOUT_DIALOG_H\n#define GSC_ABOUT_DIALOG_H\n\n#include <gtkmm.h>\n\n#include \"applib/app_builder_widget.h\"\n\n\n\n\n/// The About dialog.\n/// Use create() / destroy() with this class instead of new / delete!\nclass GscAboutDialog : public AppBuilderWidget<GscAboutDialog, false, Gtk::AboutDialog> {\n\tpublic:\n\n\t\t// name of ui file (without .ui extension) for AppBuilderWidget\n\t\tstatic inline const std::string_view ui_name = \"gsc_about_dialog\";\n\n\n\t\t/// Constructor, GtkBuilder needs this.\n\t\tGscAboutDialog(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui);\n\n\n\n\tprotected:\n\n\t\t// -------------------- Callbacks\n\n\n\t\t/// Callback - dialog response\n\t\tvoid on_response(int response_id) override;\n\n\n\t\tbool on_activate_link(const std::string& uri) override;\n\n\t\t// ---------- override virtual methods\n\n\t\t// we use .run(), so we don't need this\n/*\n\t\t// by default, delete_event calls hide().\n\t\tbool on_delete_event(GdkEventAny* e) override\n\t\t{\n\t\t\tdestroy(this);  // deletes this object and nullifies instance\n\t\t\treturn true;  // event handled, don't call default virtual handler\n\t\t}\n*/\n\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_add_device_window.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#include <glibmm.h>\n#include <gdk/gdk.h>  // GDK_KEY_Escape\n#include <memory>\n\n#include \"hz/fs_ns.h\"\n#include \"hz/string_sprintf.h\"\n#include \"applib/app_gtkmm_tools.h\"\n\n#include \"gsc_add_device_window.h\"\n#include \"gsc_main_window.h\"\n#include \"build_config.h\"\n#include \"applib/storage_settings.h\"\n\n\n\nGscAddDeviceWindow::GscAddDeviceWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui)\n\t\t: AppBuilderWidget<GscAddDeviceWindow, true>(gtkcobj, std::move(ui))\n{\n\t// Connect callbacks\n\n\tGtk::Button* window_cancel_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(window_cancel_button, clicked);\n\n\tGtk::Button* window_ok_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(window_ok_button, clicked);\n\n\tGtk::Button* device_name_browse_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(device_name_browse_button, clicked);\n\n\n\tauto* top_info_link_label = lookup_widget<Gtk::Label*>(\"top_info_link_label\");\n\tconst std::string man_url = \"https://gsmartcontrol.shaduri.dev/smartctl_man.html\";\n\ttop_info_link_label->set_text(Glib::ustring::compose(top_info_link_label->get_text(), man_url));\n\n\tGlib::ustring device_name_tooltip = _(\"Device name\");\n\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\tdevice_name_tooltip = _(\"Device name (for example, use \\\"pd0\\\" for the first physical drive)\");\n\t} else if constexpr(BuildEnv::is_kernel_linux()) {\n\t\tdevice_name_tooltip = _(\"Device name (for example, /dev/sda or /dev/twa0)\");\n\t}\n\tif (auto* device_name_label = lookup_widget<Gtk::Label*>(\"device_name_label\")) {\n\t\tapp_gtkmm_set_widget_tooltip(*device_name_label, device_name_tooltip);\n\t}\n\n\tGtk::Entry* device_name_entry = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(device_name_entry, changed);\n\tif (device_name_entry) {\n\t\tapp_gtkmm_set_widget_tooltip(*device_name_entry, device_name_tooltip);\n\t}\n\n\n\tGlib::ustring device_type_tooltip = _(\"Smartctl -d option parameter\");\n\tif constexpr(BuildEnv::is_kernel_family_windows() || BuildEnv::is_kernel_linux()) {\n\t\tdevice_type_tooltip = _(\"Smartctl -d option parameter. For example, use areca,1 for the first drive behind Areca RAID controller.\");\n\t}\n\tif (auto* device_type_label = lookup_widget<Gtk::Label*>(\"device_type_label\")) {\n\t\tapp_gtkmm_set_widget_tooltip(*device_type_label, device_type_tooltip);\n\t}\n\tif (auto* type_combo = lookup_widget<Gtk::ComboBoxText*>(\"device_type_combo\")) {\n\t\tapp_gtkmm_set_widget_tooltip(*type_combo, device_type_tooltip);\n\t}\n\n\n\t// Accelerators\n\n\tGlib::RefPtr<Gtk::AccelGroup> accel_group = this->get_accel_group();\n\tif (window_cancel_button) {\n\t\twindow_cancel_button->add_accelerator(\"clicked\", accel_group, GDK_KEY_Escape,\n\t\t\t\tGdk::ModifierType(0), Gtk::AccelFlags(0));\n\t}\n\n\n\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\t// \"Browse\" doesn't make sense in win32, hide it.\n\t\tif (device_name_browse_button) {\n\t\t\tdevice_name_browse_button->hide();\n\t\t}\n\t}\n\n\n\t// Populate type combo with common types\n\tif (auto* type_combo = lookup_widget<Gtk::ComboBoxText*>(\"device_type_combo\")) {\n\t\ttype_combo->append(\"sat,12\");\n\t\ttype_combo->append(\"sat,16\");\n\t\ttype_combo->append(\"nvme\");\n\t\ttype_combo->append(\"usbcypress\");\n\t\ttype_combo->append(\"usbjmicron\");\n\t\ttype_combo->append(\"usbsunplus\");\n\t\ttype_combo->append(\"ata\");\n\t\ttype_combo->append(\"scsi\");\n\t\tif constexpr(BuildEnv::is_kernel_linux()) {\n\t\t\ttype_combo->append(\"marvell\");\n\t\t\ttype_combo->append(\"megaraid,N\");\n\t\t\ttype_combo->append(\"areca,N\");\n\t\t\ttype_combo->append(\"areca,N/E\");\n\t\t}\n\t\tif constexpr(BuildEnv::is_kernel_linux() || BuildEnv::is_kernel_freebsd() ||BuildEnv::is_kernel_dragonfly()) {\n\t\t\ttype_combo->append(\"3ware,N\");  // this option is not needed in windows\n\t\t\ttype_combo->append(\"cciss,N\");\n\t\t\ttype_combo->append(\"hpt,L/M\");\n\t\t\ttype_combo->append(\"hpt,L/M/N\");\n\t\t}\n\t}\n\n\n\t// This sets the initial state of OK button\n\ton_device_name_entry_changed();\n\n\t// show();\n}\n\n\n\nvoid GscAddDeviceWindow::set_main_window(GscMainWindow* main_window)\n{\n\tmain_window_ = main_window;\n}\n\n\n\nbool GscAddDeviceWindow::on_delete_event([[maybe_unused]] GdkEventAny* e)\n{\n\ton_window_cancel_button_clicked();\n\treturn true;  // event handled\n}\n\n\n\nvoid GscAddDeviceWindow::on_window_cancel_button_clicked()\n{\n\tthis->destroy_instance();\n}\n\n\n\nvoid GscAddDeviceWindow::on_window_ok_button_clicked()\n{\n\tstd::string dev, type;\n\tstd::vector<std::string> params;\n\tstd::string params_str;\n\tif (auto* entry = lookup_widget<Gtk::Entry*>(\"device_name_entry\")) {\n\t\tdev = entry->get_text();\n\t}\n\tif (auto* type_combo = lookup_widget<Gtk::ComboBoxText*>(\"device_type_combo\")) {\n\t\ttype = type_combo->get_entry_text();\n\t}\n\tif (auto* entry = lookup_widget<Gtk::Entry*>(\"smartctl_params_entry\")) {\n\t\tparams_str = entry->get_text();\n\t\tif (!params_str.empty()) {\n\t\t\ttry {\n\t\t\t\tparams = Glib::shell_parse_argv(params_str);\n\t\t\t}\n\t\t\tcatch(Glib::ShellError& e)\n\t\t\t{\n\t\t\t\t// TODO Alert\n\t\t\t}\n\t\t}\n\t}\n\n\tbool added = false;\n\tif (main_window_ && !dev.empty()) {\n\t\tadded = main_window_->add_device_interactive(dev, type, params);\n\t}\n\n\tif (added) {\n\t\tbool auto_add = false;\n\t\tif (auto* check = lookup_widget<Gtk::CheckButton*>(\"auto_add_device_check\")) {\n\t\t\tauto_add = check->get_active();\n\t\t}\n\n\t\t// If auto-add is checked, save the device info to config\n\t\tif (auto_add && !dev.empty()) {\n\t\t\t// Get existing auto-add devices\n\t\t\tauto devices = app_get_startup_manual_devices();\n\n\t\t\t// Add or update this device in the map\n\t\t\tdevices.emplace(dev, type, params_str);\n\n\t\t\t// Save the updated map back to config\n\t\t\tapp_set_startup_manual_devices(devices);\n\t\t}\n\t}\n\n\tdestroy_instance();\n}\n\n\n\nvoid GscAddDeviceWindow::on_device_name_browse_button_clicked()\n{\n\tauto* entry = this->lookup_widget<Gtk::Entry*>(\"device_name_entry\");\n\tif (!entry)\n\t\treturn;\n\n\tauto path = hz::fs_path_from_string(std::string(entry->get_text()));\n\n\tint result = 0;\n\n#if GTK_CHECK_VERSION(3, 20, 0)\n\tstd::unique_ptr<GtkFileChooserNative, decltype(&g_object_unref)> dialog(gtk_file_chooser_native_new(\n\t\t\t_(\"Choose Device...\"), this->gobj(), GTK_FILE_CHOOSER_ACTION_OPEN, nullptr, nullptr),\n\t\t\t&g_object_unref);\n\n\tif (path.is_absolute())\n\t\tgtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog.get()), hz::fs_path_to_string(path).c_str());\n\n\tresult = gtk_native_dialog_run(GTK_NATIVE_DIALOG(dialog.get()));\n\n#else\n\tGtk::FileChooserDialog dialog(*this, _(\"Choose Device...\"),\n\t\t\tGtk::FILE_CHOOSER_ACTION_OPEN);\n\n\t// Add response buttons the the dialog\n\tdialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);\n\tdialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_ACCEPT);\n\n\t// Note: This works on absolute paths only (otherwise it's gtk warning).\n\tif (path.is_absolute())\n\t\tdialog.set_filename(hz::fs_path_to_string(path));  // change to its dir and select it if exists.\n\n\t// Show the dialog and wait for a user response\n\tresult = dialog.run();  // the main cycle blocks here\n#endif\n\n\t// Handle the response\n\tswitch (result) {\n\t\tcase Gtk::RESPONSE_ACCEPT:\n\t\t{\n\t\t\tGlib::ustring file;\n#if GTK_CHECK_VERSION(3, 20, 0)\n\t\t\tfile = app_ustring_from_gchar(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog.get())));\n#else\n\t\t\tfile = dialog.get_filename();  // in fs encoding\n#endif\n\t\t\tentry->set_text(file);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase Gtk::RESPONSE_CANCEL: case Gtk::RESPONSE_DELETE_EVENT:\n\t\t\t// nothing, the dialog is closed already\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Unknown dialog response code: \" << result << \".\\n\");\n\t\t\tbreak;\n\t}\n}\n\n\n\nvoid GscAddDeviceWindow::on_device_name_entry_changed()\n{\n\t// Allow OK only if name is not empty\n\tauto* entry = lookup_widget<Gtk::Entry*>(\"device_name_entry\");\n\tauto* ok_button = lookup_widget<Gtk::Button*>(\"window_ok_button\");\n\tif (entry && ok_button) {\n\t\tok_button->set_sensitive(!entry->get_text().empty());\n\t}\n}\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_add_device_window.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#ifndef GSC_ADD_DEVICE_WINDOW_H\n#define GSC_ADD_DEVICE_WINDOW_H\n\n#include <gtkmm.h>\n\n#include \"applib/app_builder_widget.h\"\n\n\n\nclass GscMainWindow;\n\n\n/// The \"Add Device\" window.\n/// Use create() / destroy() with this class instead of new / delete!\nclass GscAddDeviceWindow : public AppBuilderWidget<GscAddDeviceWindow, true> {\n\tpublic:\n\n\t\t// name of ui file (without .ui extension) for AppBuilderWidget\n\t\tstatic inline const std::string_view ui_name = \"gsc_add_device_window\";\n\n\n\t\t/// Constructor, GtkBuilder needs this.\n\t\tGscAddDeviceWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui);\n\n\n\t\t/// Set the main window.\n\t\t/// On OK button click main_window->add_device() will be called.\n\t\tvoid set_main_window(GscMainWindow* main_window);\n\n\n\tprotected:\n\n\n\t\t// ---------- overridden virtual methods\n\n\t\t/// Destroy this object on delete event (by default it calls hide()).\n\t\t/// Reimplemented from Gtk::Window.\n\t\tbool on_delete_event(GdkEventAny* e) override;\n\n\n\t\t// ---------- other callbacks\n\n\t\t/// Button click callback\n\t\tvoid on_window_cancel_button_clicked();\n\n\t\t/// Button click callback\n\t\tvoid on_window_ok_button_clicked();\n\n\t\t/// Button click callback\n\t\tvoid on_device_name_browse_button_clicked();\n\n\t\t/// Entry text change callback\n\t\tvoid on_device_name_entry_changed();\n\n\n\tprivate:\n\n\t\tGscMainWindow* main_window_ = nullptr;  ///< The main window that created us\n\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_executor_error_dialog.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#include <glibmm.h>\n#include <glibmm/i18n.h>\n#include <gtkmm.h>\n\n#include \"gsc_executor_log_window.h\"\n#include \"gsc_executor_error_dialog.h\"\n#include \"gsc_text_window.h\"\n\n\n\n\nnamespace {\n\n\t/// Helper function\n\tinline int show_executor_dialog(Gtk::MessageType type,\n\t\t\tconst std::string& message, const std::string& sec_message,\n\t\t\tGtk::Window* parent, bool sec_msg_markup, bool show_output_button)\n\t{\n\t\t// no markup, modal\n\t\tGtk::MessageDialog dialog(\"\\n\" + message + (sec_message.empty() ? \"\\n\" : \"\"),\n\t\t\t\tfalse, type, Gtk::BUTTONS_NONE, true);\n\n\t\tif (!sec_message.empty())\n\t\t\tdialog.set_secondary_text(sec_message, sec_msg_markup);\n\n\t\tif (parent) {\n\t\t\tdialog.set_transient_for(*parent);\n\t\t\tdialog.set_position(Gtk::WIN_POS_CENTER_ON_PARENT);\n\t\t} else {\n\t\t\tdialog.set_position(Gtk::WIN_POS_MOUSE);\n\t\t}\n\n\n\t\tGtk::Button ok_button(Gtk::Stock::OK);\n\t\tok_button.show_all();\n\t\tok_button.set_can_default(true);\n\t\tdialog.add_action_widget(ok_button, Gtk::RESPONSE_OK);\n\n\n\t\tGtk::Button output_button(_(\"_Show Output\"), true);  // don't put this inside if, it needs to live beyond it.\n\t\tif (show_output_button) {\n\t\t\toutput_button.show_all();\n\t\t\tdialog.add_action_widget(output_button, Gtk::RESPONSE_HELP);\n\t\t}\n\n\t\tdialog.set_default_response(Gtk::RESPONSE_OK);\n\n\n\t\tconst int response = dialog.run();  // blocks until the dialog is closed\n\n\t\treturn response;\n\t}\n\n}\n\n\n\n\n\nvoid gsc_executor_error_dialog_show(const std::string& message, const std::string& sec_message,\n\t\tGtk::Window* parent, bool sec_msg_markup, bool show_output_button)\n{\n\tconst int response = show_executor_dialog(Gtk::MESSAGE_ERROR, message, sec_message,\n\t\t\tparent, sec_msg_markup, show_output_button);\n\n\tif (response == Gtk::RESPONSE_HELP) {\n\t\t// this one will only hide on close.\n\t\tauto win = GscExecutorLogWindow::create();  // probably already created\n\t\t// win->set_transient_for(*this);  // don't do this - it will make it always-on-top of this.\n\t\twin->show_last();  // show the window and select last entry\n\t}\n}\n\n\n\nvoid gsc_no_info_dialog_show(const std::string& message, const std::string& sec_message,\n\t\tGtk::Window* parent, bool sec_msg_markup, const std::string& output,\n\t\tconst std::string& output_window_title, const std::string& default_save_filename)\n{\n\tconst int response = show_executor_dialog(Gtk::MESSAGE_WARNING, message, sec_message,\n\t\t\tparent, sec_msg_markup, !output.empty());\n\n\tif (response == Gtk::RESPONSE_HELP) {\n\t\tauto win = GscTextWindow<SmartctlOutputInstance>::create();\n\t\twin->set_text_from_command(output_window_title, output);\n\n\t\tif (!default_save_filename.empty())\n\t\t\twin->set_save_filename(default_save_filename);\n\n\t\twin->show();\n\t}\n\n}\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_executor_error_dialog.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#ifndef GSC_EXECUTOR_ERROR_DIALOG_H\n#define GSC_EXECUTOR_ERROR_DIALOG_H\n\n#include <string>\n#include <gtkmm.h>\n\n\n\n/// Show a dialog when an execution error occurs. A dialog\n/// will have a \"Show Output\" button, which shows the last executed\n/// command details.\nvoid gsc_executor_error_dialog_show(const std::string& message, const std::string& sec_message,\n\t\tGtk::Window* parent, bool sec_msg_markup = false, bool show_output_button = true);\n\n\n/// Show a dialog when no additional information is available.\n/// If \\c output is not empty, a \"Show Output\" button will be displayed\n/// which shows this output.\nvoid gsc_no_info_dialog_show(const std::string& message, const std::string& sec_message,\n\t\tGtk::Window* parent, bool sec_msg_markup, const std::string& output,\n\t\tconst std::string& output_window_title, const std::string& default_save_filename);\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_executor_log_window.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#include \"hz/string_algo.h\"\n#include <glibmm.h>\n#include <gtkmm.h>\n#include <gdk/gdk.h>  // GDK_KEY_Escape\n#include <sstream>\n#include <cstddef>  // std::size_t\n#include <memory>\n#include <vector>\n\n#include \"applib/app_gtkmm_tools.h\"  // app_gtkmm_create_tree_view_column\n#include \"hz/fs.h\"\n#include \"rconfig/rconfig.h\"\n\n#include \"gsc_executor_log_window.h\"\n#include \"gsc_init.h\"  // app_get_debug_buffer_str()\n\n\n\n\nGscExecutorLogWindow::GscExecutorLogWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui)\n\t\t: AppBuilderWidget<GscExecutorLogWindow, false>(gtkcobj, std::move(ui))\n{\n\t// Connect callbacks\n\n\tGtk::Button* window_close_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(window_close_button, clicked);\n\n\tGtk::Button* window_save_current_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(window_save_current_button, clicked);\n\n\tGtk::Button* window_save_all_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(window_save_all_button, clicked);\n\n\n\tGtk::Button* clear_command_list_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(clear_command_list_button, clicked);\n\n\n\n\t// Accelerators\n\n\tconst Glib::RefPtr<Gtk::AccelGroup> accel_group = this->get_accel_group();\n\tif (window_close_button) {\n\t\twindow_close_button->add_accelerator(\"clicked\", accel_group, GDK_KEY_Escape,\n\t\t\t\tGdk::ModifierType(0), Gtk::AccelFlags(0));\n\t}\n\n\n\t// --------------- Make a treeview\n\n\tauto* treeview = this->lookup_widget<Gtk::TreeView*>(\"command_list_treeview\");\n\tif (treeview) {\n\t\tGtk::TreeModelColumnRecord model_columns;\n\n\t\t// #, Command + parameters, [EntryPtr]\n\n\t\tmodel_columns.add(col_num_);\n\t\tapp_gtkmm_create_tree_view_column(col_num_, *treeview,\n\t\t\t\t\"#\", _(\"# of executed command\"), true);  // sortable\n\n\t\tmodel_columns.add(col_command_);\n\t\tapp_gtkmm_create_tree_view_column(col_command_, *treeview,\n\t\t\t\t_(\"Command\"), _(\"Command with parameters\"), true);  // sortable\n\n\t\tmodel_columns.add(col_entry_);\n\n\n\t\t// create a TreeModel (ListStore)\n\t\tlist_store_ = Gtk::ListStore::create(model_columns);\n\t\t// list_store->set_sort_column(col_num, Gtk::SORT_DESCENDING);  // default sort\n\t\ttreeview->set_model(list_store_);\n\n\n\t\tselection_ = treeview->get_selection();\n\t\tselection_->signal_changed().connect(sigc::mem_fun(*this,\n\t\t\t\t&GscExecutorLogWindow::on_tree_selection_changed) );\n\n\t}\n\n\n\n\t// Hide command text entry in win32.\n\t// Setting text on this entry segfaults under win32, (utf8 conversion doesn't\n\t// help, not that it should). Seems to be connected to non-english locale.\n\t// Surprisingly, the treeview column text still works.\n\n\t// The problem seems to have disappeared (new compiler/runtime?)\n// if constexpr(BuildEnv::is_kernel_family_windows()) {\n// \tGtk::Box* command_hbox = this->lookup_widget<Gtk::Box*>(\"command_hbox\");\n// \tif (command_hbox)\n// \t\tcommand_hbox->hide();\n// }\n\n\t// ---------------\n\n\t// Connect to CommandExecutor signal\n\tcmdex_sync_signal_execute_finish().connect(sigc::mem_fun(*this,\n\t\t\t&GscExecutorLogWindow::on_command_output_received));\n\n\t// show();\n}\n\n\n\nvoid GscExecutorLogWindow::show_last()\n{\n\tauto* treeview = this->lookup_widget<Gtk::TreeView*>(\"command_list_treeview\");\n\n\tif (treeview != nullptr && !list_store_->children().empty()) {\n// \t\tGtk::TreeRow row = *(list_store->children().rbegin());  // this causes invalid read error in valgrind\n\t\tconst Gtk::TreeRow row = *(--(list_store_->children().end()));\n\t\tselection_->select(row);\n\t\t// you would think that scroll_to_row would accept a TreeRow for a change (shock!)\n\t\ttreeview->scroll_to_row(list_store_->get_path(row));\n\t}\n\n\tshow();\n}\n\n\n\nvoid GscExecutorLogWindow::clear_view_widgets()\n{\n\tauto* window_save_current_button = this->lookup_widget<Gtk::Button*>(\"window_save_current_button\");\n\tif (window_save_current_button)\n\t\twindow_save_current_button->set_sensitive(false);\n\n\tauto* output_textview = this->lookup_widget<Gtk::TextView*>(\"output_textview\");\n\tif (output_textview) {\n\t\tconst Glib::RefPtr<Gtk::TextBuffer> buffer = output_textview->get_buffer();\n\t\tbuffer->set_text(\"\");\n\t}\n\n\tauto* command_entry = this->lookup_widget<Gtk::Entry*>(\"command_entry\");\n\tif (command_entry)\n\t\tcommand_entry->set_text(\"\");\n}\n\n\n\nvoid GscExecutorLogWindow::on_command_output_received(const CommandExecutorResult& info)\n{\n\tauto entry = std::make_shared<CommandExecutorResult>(info);\n\tentries_.push_back(entry);\n\n\tstd::vector<std::string> command = {info.command};\n\tcommand.insert(command.end(), info.parameters.begin(), info.parameters.end());\n\n\t// update tree model\n\tconst Gtk::TreeRow row = *(list_store_->append());\n\trow[col_num_] = entries_.size();\n\trow[col_command_] = hz::string_join(command, \" \");\n\trow[col_entry_] = entry;\n\n\t// if visible, set the selection to it\n\tif (auto* treeview = this->lookup_widget<Gtk::TreeView*>(\"command_list_treeview\")) {\n\t\tselection_->select(row);\n\t\ttreeview->scroll_to_row(list_store_->get_path(row));\n\t}\n}\n\n\n\nbool GscExecutorLogWindow::on_delete_event([[maybe_unused]] GdkEventAny* e)\n{\n\ton_window_close_button_clicked();\n\treturn true;  // event handled\n}\n\n\n\nvoid GscExecutorLogWindow::on_window_close_button_clicked()\n{\n\tthis->hide();  // hide only, don't destroy\n}\n\n\n\nvoid GscExecutorLogWindow::on_window_save_current_button_clicked()\n{\n\tif (selection_->count_selected_rows() == 0)\n\t\treturn;\n\n\tconst Gtk::TreeIter iter = selection_->get_selected();\n\tconst std::shared_ptr<CommandExecutorResult> entry = (*iter)[col_entry_];\n\n\tstatic std::string last_dir;\n\tif (last_dir.empty()) {\n\t\tlast_dir = rconfig::get_data<std::string>(\"gui/drive_data_open_save_dir\");\n\t}\n\tint result = 0;\n\n\tconst Glib::RefPtr<Gtk::FileFilter> specific_filter = Gtk::FileFilter::create();\n\tspecific_filter->set_name(_(\"JSON and Text Files\"));\n\tspecific_filter->add_pattern(\"*.json\");\n\tspecific_filter->add_pattern(\"*.txt\");\n\n\tconst Glib::RefPtr<Gtk::FileFilter> all_filter = Gtk::FileFilter::create();\n\tall_filter->set_name(_(\"All Files\"));\n\tall_filter->add_pattern(\"*\");\n\n#if GTK_CHECK_VERSION(3, 20, 0)\n\tconst std::unique_ptr<GtkFileChooserNative, decltype(&g_object_unref)> dialog(gtk_file_chooser_native_new(\n\t\t\t_(\"Save Data As...\"), this->gobj(), GTK_FILE_CHOOSER_ACTION_SAVE, nullptr, nullptr),\n\t\t\t&g_object_unref);\n\n\tgtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog.get()), TRUE);\n\n\tgtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), specific_filter->gobj());\n\tgtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), all_filter->gobj());\n\n\tif (!last_dir.empty())\n\t\tgtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog.get()), last_dir.c_str());\n\n\tgtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog.get()), \".txt\");\n\n\tresult = gtk_native_dialog_run(GTK_NATIVE_DIALOG(dialog.get()));\n\n#else\n\tGtk::FileChooserDialog dialog(*this, _(\"Save Data As...\"),\n\t\t\tGtk::FILE_CHOOSER_ACTION_SAVE);\n\n\t// Add response buttons the the dialog\n\tdialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);\n\tdialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);\n\n\tdialog.set_do_overwrite_confirmation(true);\n\n\tdialog.add_filter(specific_filter);\n\tdialog.add_filter(all_filter);\n\n\tif (!last_dir.empty())\n\t\tdialog.set_current_folder(last_dir);\n\n\tdialog.set_current_name(\".json\");\n\n\t// Show the dialog and wait for a user response\n\tresult = dialog.run();  // the main cycle blocks here\n#endif\n\n\t// Handle the response\n\tswitch (result) {\n\t\tcase Gtk::RESPONSE_ACCEPT:\n\t\t{\n\t\t\thz::fs::path file;\n#if GTK_CHECK_VERSION(3, 20, 0)\n\t\t\tfile = hz::fs_path_from_string(app_string_from_gchar(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog.get()))));\n\t\t\tlast_dir = hz::fs_path_to_string(file.parent_path());\n#else\n\t\t\tfile = hz::fs_path_from_string(dialog.get_filename());  // in fs encoding\n\t\t\tlast_dir = dialog.get_current_folder();  // save for the future\n#endif\n\t\t\trconfig::set_data(\"gui/drive_data_open_save_dir\", last_dir);\n\n\t\t\tif (file.extension() != \".json\" && file.extension() != \".txt\") {\n\t\t\t\tfile += \".json\";\n\t\t\t}\n\n\t\t\tauto ec = hz::fs_file_put_contents(file, entry->std_output);\n\t\t\tif (ec) {\n\t\t\t\tgui_show_error_dialog(_(\"Cannot save data to file\"), ec.message(), this);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\tcase Gtk::RESPONSE_CANCEL: case Gtk::RESPONSE_DELETE_EVENT:\n\t\t\t// nothing, the dialog is closed already\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Unknown dialog response code: \" << result << \".\\n\");\n\t\t\tbreak;\n\t}\n\n}\n\n\n\n\nvoid GscExecutorLogWindow::on_window_save_all_button_clicked()\n{\n\t// complete libdebug output + execution logs\n\n\tstd::ostringstream exss;\n\n\texss << \"\\n------------------------- LIBDEBUG LOG -------------------------\\n\\n\\n\";\n\texss << app_get_debug_buffer_str() << \"\\n\\n\\n\";\n\n\texss << \"\\n\\n\\n------------------------- EXECUTION LOG -------------------------\\n\\n\\n\";\n\n\tfor (std::size_t i = 0; i < entries_.size(); ++i) {\n\t\texss << \"\\n\\n\\n------------------------- EXECUTED COMMAND \" << (i+1) << \" -------------------------\\n\\n\";\n\t\texss << \"\\n---------------\" << \"Command\" << \"---------------\\n\";\n\t\texss << entries_[i]->command << \"\\n\";\n\t\texss << \"\\n---------------\" << \"Parameters\" << \"---------------\\n\";\n\t\tfor (const auto& param : entries_[i]->parameters) {\n\t\t\texss << param << \"\\n\";\n\t\t}\n\t\texss << \"\\n---------------\" << \"STDOUT\" << \"---------------\\n\";\n\t\texss << entries_[i]->std_output << \"\\n\\n\";\n\t\texss << \"\\n---------------\" << \"STDERR\" << \"---------------\\n\";\n\t\texss << entries_[i]->std_error << \"\\n\\n\";\n\t\texss << \"\\n---------------\" << \"Error Message\" << \"---------------\\n\";\n\t\texss << entries_[i]->error_message << \"\\n\\n\";\n\t}\n\n\n\tstatic std::string last_dir;\n\tif (last_dir.empty()) {\n\t\tlast_dir = rconfig::get_data<std::string>(\"gui/drive_data_open_save_dir\");\n\t}\n\tint result = 0;\n\n\tconst Glib::RefPtr<Gtk::FileFilter> specific_filter = Gtk::FileFilter::create();\n\tspecific_filter->set_name(_(\"Text Files\"));\n\tspecific_filter->add_pattern(\"*.txt\");\n\n\tconst Glib::RefPtr<Gtk::FileFilter> all_filter = Gtk::FileFilter::create();\n\tall_filter->set_name(_(\"All Files\"));\n\tall_filter->add_pattern(\"*\");\n\n#if GTK_CHECK_VERSION(3, 20, 0)\n\tconst std::unique_ptr<GtkFileChooserNative, decltype(&g_object_unref)> dialog(gtk_file_chooser_native_new(\n\t\t\t_(\"Save Data As...\"), this->gobj(), GTK_FILE_CHOOSER_ACTION_SAVE, nullptr, nullptr),\n\t\t\t&g_object_unref);\n\n\tgtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog.get()), TRUE);\n\n\tgtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), specific_filter->gobj());\n\tgtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), all_filter->gobj());\n\n\tif (!last_dir.empty())\n\t\tgtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog.get()), last_dir.c_str());\n\n\tgtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog.get()), \".txt\");\n\n\tresult = gtk_native_dialog_run(GTK_NATIVE_DIALOG(dialog.get()));\n\n#else\n\tGtk::FileChooserDialog dialog(*this, _(\"Save Data As...\"),\n\t\t\tGtk::FILE_CHOOSER_ACTION_SAVE);\n\n\t// Add response buttons the the dialog\n\tdialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);\n\tdialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);\n\n\tdialog.set_do_overwrite_confirmation(true);\n\n\tdialog.add_filter(specific_filter);\n\tdialog.add_filter(all_filter);\n\n\tif (!last_dir.empty())\n\t\tdialog.set_current_folder(last_dir);\n\n\tdialog.set_current_name(\".txt\");\n\n\t// Show the dialog and wait for a user response\n\tresult = dialog.run();  // the main cycle blocks here\n#endif\n\n\t// Handle the response\n\tswitch (result) {\n\t\tcase Gtk::RESPONSE_ACCEPT:\n\t\t{\n\t\t\thz::fs::path file;\n#if GTK_CHECK_VERSION(3, 20, 0)\n\t\t\tfile = hz::fs_path_from_string(app_string_from_gchar(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog.get()))));\n\t\t\tlast_dir = hz::fs_path_to_string(file.parent_path());\n#else\n\t\t\tfile = hz::fs_path_from_string(dialog.get_filename());  // in fs encoding\n\t\t\tlast_dir = dialog.get_current_folder();  // save for the future\n#endif\n\t\t\trconfig::set_data(\"gui/drive_data_open_save_dir\", last_dir);\n\n\t\t\tif (file.extension() != \".txt\") {\n\t\t\t\tfile += \".txt\";\n\t\t\t}\n\n\t\t\tauto ec = hz::fs_file_put_contents(file, exss.str());\n\t\t\tif (ec) {\n\t\t\t\tgui_show_error_dialog(_(\"Cannot save data to file\"), ec.message(), this);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\tcase Gtk::RESPONSE_CANCEL: case Gtk::RESPONSE_DELETE_EVENT:\n\t\t\t// nothing, the dialog is closed already\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Unknown dialog response code: \" << result << \".\\n\");\n\t\t\tbreak;\n\t}\n\n}\n\n\n\nvoid GscExecutorLogWindow::on_clear_command_list_button_clicked()\n{\n\tentries_.clear();\n\tlist_store_->clear();  // this will unselect & clear widgets too.\n}\n\n\n\nvoid GscExecutorLogWindow::on_tree_selection_changed()\n{\n\tthis->clear_view_widgets();\n\n\tif (selection_->count_selected_rows() > 0) {\n\t\tconst Gtk::TreeIter iter = selection_->get_selected();\n\t\tconst Gtk::TreeRow& row = *iter;\n\n\t\tconst std::shared_ptr<CommandExecutorResult> entry = row[col_entry_];\n\n\t\tif (auto* output_textview = this->lookup_widget<Gtk::TextView*>(\"output_textview\")) {\n\t\t\tconst Glib::RefPtr<Gtk::TextBuffer> buffer = output_textview->get_buffer();\n\t\t\tif (buffer) {\n\t\t\t\tbuffer->set_text(app_make_valid_utf8_from_command_output(entry->std_output));\n\n\t\t\t\tGlib::RefPtr<Gtk::TextTag> tag;\n\t\t\t\tconst Glib::RefPtr<Gtk::TextTagTable> table = buffer->get_tag_table();\n\t\t\t\tif (table)\n\t\t\t\t\ttag = table->lookup(\"font\");\n\t\t\t\tif (!tag)\n\t\t\t\t\ttag = buffer->create_tag(\"font\");\n\n\t\t\t\ttag->property_family() = \"Monospace\";\n\t\t\t\tbuffer->apply_tag(tag, buffer->begin(), buffer->end());\n\t\t\t}\n\t\t}\n\n\t\tif (auto* command_entry = this->lookup_widget<Gtk::Entry*>(\"command_entry\")) {\n\t\t\tstd::vector<std::string> command = {entry->command};\n\t\t\tcommand.insert(command.end(), entry->parameters.begin(), entry->parameters.end());\n\t\t\tcommand_entry->set_text(app_make_valid_utf8_from_command_output(hz::string_join(command, \" \")));\n\t\t}\n\n\t\tif (auto* window_save_current_button = this->lookup_widget<Gtk::Button*>(\"window_save_current_button\"))\n\t\t\twindow_save_current_button->set_sensitive(true);\n\t}\n\n}\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_executor_log_window.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#ifndef GSC_EXECUTOR_LOG_WINDOW_H\n#define GSC_EXECUTOR_LOG_WINDOW_H\n\n#include <vector>\n#include <cstddef>  // std::size_t\n#include <gtkmm.h>\n#include <memory>\n\n#include \"applib/app_builder_widget.h\"\n#include \"applib/command_executor.h\"\n\n\n\n\n/// The \"Execution Log\" window.\n/// Use create() / destroy() with this class instead of new / delete!\nclass GscExecutorLogWindow : public AppBuilderWidget<GscExecutorLogWindow, false> {\n\tpublic:\n\n\t\t// name of ui file (without .ui extension) for AppBuilderWidget\n\t\tstatic inline const std::string_view ui_name = \"gsc_executor_log_window\";\n\n\n\t\t/// Constructor, GtkBuilder needs this.\n\t\tGscExecutorLogWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui);\n\n\n\t\t/// Show this window and select the last entry\n\t\tvoid show_last();\n\n\n\tprotected:\n\n\n\t\t/// Clear entries and textviews\n\t\tvoid clear_view_widgets();\n\n\n\n\t\t// -------------------- callbacks\n\n\t\t/// Callback attached to external source, adds entries in real time.\n\t\tvoid on_command_output_received(const CommandExecutorResult& info);\n\n\n\n\t\t// ---------- overriden virtual methods\n\n\t\t/// Hide the window, don't destroy.\n\t\t/// Reimplemented from Gtk::Window.\n\t\tbool on_delete_event(GdkEventAny* e) override;\n\n\n\t\t// ---------- other callbacks\n\n\t\t/// Button click callback\n\t\tvoid on_window_close_button_clicked();\n\n\t\t/// Button click callback\n\t\tvoid on_window_save_current_button_clicked();\n\n\t\t/// Button click callback\n\t\tvoid on_window_save_all_button_clicked();\n\n\t\t/// Button click callback\n\t\tvoid on_clear_command_list_button_clicked();\n\n\t\t/// Callback\n\t\tvoid on_tree_selection_changed();\n\n\n\tprivate:\n\n\t\tstd::vector<std::shared_ptr<CommandExecutorResult>> entries_;  ///< Command information entries\n\n\n\t\tGlib::RefPtr<Gtk::ListStore> list_store_;  ///< List store\n\t\tGlib::RefPtr<Gtk::TreeSelection> selection_;  ///< Tree selection\n\n\t\tGtk::TreeModelColumn<std::size_t> col_num_;  ///< Tree column\n\t\tGtk::TreeModelColumn<std::string> col_command_;  ///< Tree column\n\t\tGtk::TreeModelColumn<std::shared_ptr<CommandExecutorResult>> col_entry_;  ///< Tree column\n\n\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_info_window.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#include <glibmm.h>\n#include <gtkmm.h>\n#include <gdk/gdk.h>  // GDK_KEY_Escape\n#include <vector>  // better use vector, it's needed by others too\n#include <algorithm>  // std::min, std::max\n#include <memory>\n#include <string>\n\n#include \"hz/string_num.h\"  // number_to_string\n#include \"hz/string_sprintf.h\"  // string_sprintf\n#include \"hz/string_algo.h\"  // string_join\n#include \"hz/fs.h\"\n#include \"hz/format_unit.h\"  // format_time_length\n#include \"rconfig/rconfig.h\"  // rconfig::*\n\n#include \"applib/app_gtkmm_tools.h\"  // app_gtkmm_*\n#include \"applib/warning_colors.h\"\n#include \"applib/gui_utils.h\"  // gui_show_error_dialog\n#include \"applib/smartctl_executor_gui.h\"\n#include \"applib/storage_property.h\"\n#include \"applib/storage_device_detected_type.h\"\n\n#include \"gsc_text_window.h\"\n#include \"gsc_info_window.h\"\n#include \"gsc_executor_error_dialog.h\"\n#include \"gsc_startup_settings.h\"\n\n\n\nusing namespace std::literals;\n\n\n\n/// A label for AtaStorageProperty\nstruct PropertyLabel {\n\t/// Constructor\n\tPropertyLabel(std::string label_, const StorageProperty* prop, bool markup_ = false) :\n\t\tlabel(std::move(label_)), property(prop), markup(markup_)\n\t{ }\n\n\tstd::string label;  ///< Label text\n\tconst StorageProperty* property = nullptr;  ///< Storage property\n\tbool markup = false;  ///< Whether the label text uses markup\n};\n\n\n\n\nnamespace {\n\n\n\t/// Set \"top\" labels - the generic text at the top of each tab page.\n\tinline void app_set_top_labels(Gtk::Box* vbox, const std::vector<PropertyLabel>& label_strings)\n\t{\n\t\tif (!vbox)\n\t\t\treturn;\n\n\t\t// remove all first\n\t\tfor (auto& w : vbox->get_children()) {\n\t\t\tvbox->remove(*w);\n\t\t\tdelete w;  // since it's without parent anymore, it won't be auto-deleted.\n\t\t}\n\n\t\tvbox->set_visible(!label_strings.empty());\n\n\t\tif (label_strings.empty()) {\n\t\t\t// add one label only\n// \t\t\tGtk::Label* label = Gtk::manage(new Gtk::Label(\"No data available\", Gtk::ALIGN_START));\n// \t\t\tlabel->set_padding(6, 0);\n// \t\t\tvbox->pack_start(*label, false, false);\n\n\t\t} else {\n\t\t\tconst bool dark_mode = gui_is_dark_theme_active();\n\n\t\t\t// add one label per element\n\t\t\tfor (const auto& label_string : label_strings) {\n\t\t\t\tconst std::string label_text = (label_string.markup ? Glib::ustring(label_string.label) : Glib::Markup::escape_text(\n\t\t\t\t\t\tlabel_string.label));\n\t\t\t\tGtk::Label* label = Gtk::manage(new Gtk::Label());\n\t\t\t\tlabel->set_markup(label_text);\n\t\t\t\tlabel->set_padding(6, 0);\n\t\t\t\tlabel->set_alignment(Gtk::ALIGN_START);\n\t\t\t\t// label->set_ellipsize(Pango::ELLIPSIZE_END);\n\t\t\t\tlabel->set_selectable(true);\n\t\t\t\tlabel->set_can_focus(false);\n\n\t\t\t\tstd::string fg;\n\t\t\t\tif (app_property_get_label_highlight_color(dark_mode, label_string.property->warning_level, fg)) {\n\t\t\t\t\tlabel->set_markup(\n\t\t\t\t\t\t\tstd::string(\"<span color=\\\"\").append(fg).append(\"\\\">\")\n\t\t\t\t\t\t\t.append(label_text).append(\"</span>\") );\n\t\t\t\t}\n\t\t\t\tvbox->pack_start(*label, false, false);\n\n\t\t\t\t// set it after packing, else the old tooltips api won't have anything to attach them to.\n\t\t\t\tapp_gtkmm_set_widget_tooltip(*label, // label_text + \"\\n\\n\" +  // add label text too, in case it's ellipsized\n\t\t\t\t\t\tlabel_string.property->get_description(), true);  // already markupped\n\n\t\t\t\tlabel->show();\n\t\t\t}\n\t\t}\n\n\t\tvbox->show_all_children(true);\n\t}\n\n\n\n\t/// Highlight a tab label according to \\c warning\n\tinline void app_highlight_tab_label(Gtk::Widget* label_widget,\n\t\t\tWarningLevel warning, const Glib::ustring& original_label)\n\t{\n\t\tauto* label = dynamic_cast<Gtk::Label*>(label_widget);\n\t\tif (!label)\n\t\t\treturn;\n\n\t\tif (warning == WarningLevel::None) {\n\t\t\tlabel->set_markup_with_mnemonic(original_label);\n\t\t\treturn;\n\t\t}\n\n\t\tstd::string fg;\n\t\tif (app_property_get_label_highlight_color(gui_is_dark_theme_active(), warning, fg))\n\t\t\tlabel->set_markup_with_mnemonic(\"<span color=\\\"\" + fg + \"\\\">\" + original_label + \"</span>\");\n\t}\n\n\n\n\t/// Scroll to appropriate error in text when row is selected in tree.\n\tinline void on_error_log_treeview_row_selected(GscInfoWindow* window,\n\t\t\tGtk::TreeModelColumn<Glib::ustring> mark_name_column)\n\t{\n\t\tauto* treeview = window->lookup_widget<Gtk::TreeView*>(\"error_log_treeview\");\n\t\tauto* textview = window->lookup_widget<Gtk::TextView*>(\"error_log_textview\");\n\t\tGlib::RefPtr<Gtk::TextBuffer> buffer;\n\t\tif (treeview != nullptr && textview != nullptr && (buffer = textview->get_buffer())) {\n\t\t\tauto iter = treeview->get_selection()->get_selected();\n\t\t\tif (iter) {\n\t\t\t\tauto mark = buffer->get_mark((*iter)[mark_name_column]);\n\t\t\t\tif (mark)\n\t\t\t\t\ttextview->scroll_to(mark, 0., 0., 0.);\n\t\t\t}\n\t\t}\n\t}\n\n\n\n}\n\n\n\n\nGscInfoWindow::GscInfoWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui)\n\t\t: AppBuilderWidget<GscInfoWindow, true>(gtkcobj, std::move(ui))\n{\n\t// Size\n\t{\n\t\tconst int def_size_w = rconfig::get_data<int>(\"gui/info_window/default_size_w\");\n\t\tconst int def_size_h = rconfig::get_data<int>(\"gui/info_window/default_size_h\");\n\t\tif (def_size_w > 0 && def_size_h > 0) {\n\t\t\tset_default_size(def_size_w, def_size_h);\n\t\t}\n\t}\n\n\t// Create missing widgets\n\tauto* device_name_hbox = lookup_widget<Gtk::Box*>(\"device_name_label_hbox\");\n\tif (device_name_hbox) {\n\t\tdevice_name_label_ = Gtk::manage(new Gtk::Label(_(\"No data available\"), Gtk::ALIGN_START));\n\t\tdevice_name_label_->set_selectable(true);\n\t\tdevice_name_label_->show();\n\t\tdevice_name_hbox->pack_start(*device_name_label_, true, true);\n\t}\n\n\n\t// Connect callbacks\n\n\tGtk::Button* refresh_info_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(refresh_info_button, clicked);\n\n\tGtk::Button* view_output_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(view_output_button, clicked);\n\n\tGtk::Button* save_info_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(save_info_button, clicked);\n\n\tGtk::Button* close_window_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(close_window_button, clicked);\n\n\tGtk::ComboBox* test_type_combo = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(test_type_combo, changed);\n\n\tGtk::Button* test_execute_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(test_execute_button, clicked);\n\n\tGtk::Button* test_stop_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(test_stop_button, clicked);\n\n\n\t// Accelerators\n\tif (close_window_button) {\n\t\tclose_window_button->add_accelerator(\"clicked\", this->get_accel_group(), GDK_KEY_Escape,\n\t\t\t\tGdk::ModifierType(0), Gtk::AccelFlags(0));\n\t}\n\n\t// Context menu in treeviews\n\t{\n\t\tstatic const std::vector<std::string> treeview_names {\n\t\t\t\"attributes_treeview\",\n\t\t\t\"nvme_attributes_treeview\",\n\t\t\t\"statistics_treeview\",\n\t\t\t\"selftest_log_treeview\"\n\t\t};\n\n\t\tfor (const auto& treeview_name : treeview_names) {\n\t\t\tauto* treeview = lookup_widget<Gtk::TreeView*>(treeview_name);\n\t\t\ttreeview_menus_[treeview_name] = new Gtk::Menu();  // deleted in window destructor\n\n\t\t\ttreeview->signal_button_press_event().connect(\n\t\t\t\t\tsigc::bind(sigc::bind(sigc::mem_fun(*this, &GscInfoWindow::on_treeview_button_press_event), treeview), treeview_menus_[treeview_name]), false);  // before\n\n\t\t\tGtk::MenuItem* item = Gtk::manage(new Gtk::MenuItem(_(\"Copy Selected Data\"), true));\n\t\t\titem->signal_activate().connect(\n\t\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscInfoWindow::on_treeview_menu_copy_clicked), treeview) );\n\t\t\ttreeview_menus_[treeview_name]->append(*item);\n\n\t\t\ttreeview_menus_[treeview_name]->show_all();  // Show all menu items when the menu pops up\n\t\t}\n\t}\n\n\n\t// ---------------\n\n\t// Create columns of treeviews\n\tcolumns_ = std::make_unique<GscInfoWindowColumns>();\n\n\n\t// Set default texts on TextView-s, because glade's \"text\" property doesn't work\n\t// on them in gtkbuilder.\n\tif (auto* textview = lookup_widget<Gtk::TextView*>(\"error_log_textview\")) {\n\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\tbuffer->set_text(\"\\n\"s + _(\"No data available\"));\n\t}\n\tif (auto* textview = lookup_widget<Gtk::TextView*>(\"nvme_error_log_textview\")) {\n\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\tbuffer->set_text(\"\\n\"s + _(\"No data available\"));\n\t}\n\tif (auto* textview = lookup_widget<Gtk::TextView*>(\"selective_selftest_log_textview\")) {\n\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\tbuffer->set_text(\"\\n\"s + _(\"No data available\"));\n\t}\n\tif (auto* textview = lookup_widget<Gtk::TextView*>(\"temperature_log_textview\")) {\n\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\tbuffer->set_text(\"\\n\"s + _(\"No data available\"));\n\t}\n\tif (auto* textview = lookup_widget<Gtk::TextView*>(\"erc_log_textview\")) {\n\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\tbuffer->set_text(\"\\n\"s + _(\"No data available\"));\n\t}\n\tif (auto* textview = lookup_widget<Gtk::TextView*>(\"phy_log_textview\")) {\n\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\tbuffer->set_text(\"\\n\"s + _(\"No data available\"));\n\t}\n\tif (auto* textview = lookup_widget<Gtk::TextView*>(\"directory_log_textview\")) {\n\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\tbuffer->set_text(\"\\n\"s + _(\"No data available\"));\n\t}\n\n\n\t// Save their original texts so that we can apply markup to them.\n\tGtk::Label* tab_label = nullptr;\n\n\ttab_label = lookup_widget<Gtk::Label*>(\"general_tab_label\");\n\ttab_names_.identity = (tab_label ? tab_label->get_label() : \"\");\n\n\ttab_label = lookup_widget<Gtk::Label*>(\"attributes_tab_label\");\n\ttab_names_.ata_attributes = (tab_label ? tab_label->get_label() : \"\");\n\n\ttab_label = lookup_widget<Gtk::Label*>(\"nvme_attributes_tab_label\");\n\ttab_names_.nvme_attributes = (tab_label ? tab_label->get_label() : \"\");\n\n\ttab_label = lookup_widget<Gtk::Label*>(\"statistics_tab_label\");\n\ttab_names_.statistics = (tab_label ? tab_label->get_label() : \"\");\n\n\ttab_label = lookup_widget<Gtk::Label*>(\"test_tab_label\");\n\ttab_names_.test = (tab_label ? tab_label->get_label() : \"\");\n\n\ttab_label = lookup_widget<Gtk::Label*>(\"error_log_tab_label\");\n\ttab_names_.ata_error_log = (tab_label ? tab_label->get_label() : \"\");\n\n\ttab_label = lookup_widget<Gtk::Label*>(\"nvme_error_log_tab_label\");\n\ttab_names_.nvme_error_log = (tab_label ? tab_label->get_label() : \"\");\n\n\ttab_label = lookup_widget<Gtk::Label*>(\"temperature_log_tab_label\");\n\ttab_names_.temperature = (tab_label ? tab_label->get_label() : \"\");\n\n\ttab_label = lookup_widget<Gtk::Label*>(\"advanced_tab_label\");\n\ttab_names_.advanced = (tab_label ? tab_label->get_label() : \"\");\n\n\ttab_label = lookup_widget<Gtk::Label*>(\"capabilities_tab_label\");\n\ttab_names_.capabilities = (tab_label ? tab_label->get_label() : \"\");\n\n\ttab_label = lookup_widget<Gtk::Label*>(\"erc_tab_label\");\n\ttab_names_.erc = (tab_label ? tab_label->get_label() : \"\");\n\n\ttab_label = lookup_widget<Gtk::Label*>(\"selective_selftest_tab_label\");\n\ttab_names_.selective_selftest = (tab_label ? tab_label->get_label() : \"\");\n\n\ttab_label = lookup_widget<Gtk::Label*>(\"phy_tab_label\");\n\ttab_names_.phy = (tab_label ? tab_label->get_label() : \"\");\n\n\ttab_label = lookup_widget<Gtk::Label*>(\"directory_tab_label\");\n\ttab_names_.directory = (tab_label ? tab_label->get_label() : \"\");\n\n\t// show();  // don't show here, removing tabs later is ugly.\n}\n\n\n\nGscInfoWindow::~GscInfoWindow()\n{\n\t// Store window size. We don't store position to avoid overlaps.\n\t{\n\t\tint window_w = 0, window_h = 0;\n\t\tget_size(window_w, window_h);\n\t\trconfig::set_data(\"gui/info_window/default_size_w\", window_w);\n\t\trconfig::set_data(\"gui/info_window/default_size_h\", window_h);\n\t}\n\n\tfor (auto& iter : treeview_menus_) {\n\t\tdelete iter.second;\n\t}\n}\n\n\n\nvoid GscInfoWindow::set_drive(StorageDevicePtr d)\n{\n\tif (drive_)  // if an old drive is present, disconnect our callback from it.\n\t\tdrive_changed_connection_.disconnect();\n\tdrive_ = std::move(d);\n\tdrive_changed_connection_ = drive_->signal_changed().connect(sigc::mem_fun(this,\n\t\t\t&GscInfoWindow::on_drive_changed));\n}\n\n\n\nvoid GscInfoWindow::fill_ui_with_info(bool scan, bool clear_ui, bool clear_tests)\n{\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Scan \" << (scan ? \"\" : \"not \") << \"requested.\\n\");\n\n\tif (clear_ui) {\n\t\tclear_ui_info(clear_tests);\n\t}\n\n\tif (!drive_->get_is_virtual()) {\n\t\t// fetch all smartctl info, even if it already has it (to refresh it).\n\t\tif (scan) {\n\t\t\tstd::shared_ptr<SmartctlExecutorGui> ex(new SmartctlExecutorGui());\n\t\t\tex->create_running_dialog(this, Glib::ustring::compose(_(\"Running {command} on %1...\"), drive_->get_device_with_type()));\n\t\t\tauto fetch_status = drive_->fetch_full_data_and_parse(ex);  // run it with GUI support\n\n\t\t\tif (!fetch_status) {\n\t\t\t\tgsc_executor_error_dialog_show(_(\"Cannot retrieve SMART data\"), fetch_status.error().message(), this);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t// disable refresh button if virtual\n\tif (drive_->get_is_virtual()) {\n\t\tauto* b = lookup_widget<Gtk::Button*>(\"refresh_info_button\");\n\t\tif (b) {\n\t\t\tb->set_sensitive(false);\n\t\t\tapp_gtkmm_set_widget_tooltip(*b, _(\"Cannot re-read information from virtual drive\"));\n\t\t}\n\t}\n\n\t// Hide tabs which have no properties associated with them\n\t{\n\t\tconst auto& prop_repo = drive_->get_property_repository();\n\t\tGtk::Widget* note_page_box = nullptr;\n\n\t\tconst bool has_ata_attributes = prop_repo.has_properties_for_section(StoragePropertySection::AtaAttributes);\n\t\tif (note_page_box = lookup_widget(\"attributes_tab_vbox\"); note_page_box != nullptr) {\n\t\t\tnote_page_box->set_visible(has_ata_attributes);\n\t\t}\n\n\t\tconst bool has_nvme_attributes = prop_repo.has_properties_for_section(StoragePropertySection::NvmeAttributes);\n\t\tif (note_page_box = lookup_widget(\"nvme_attributes_tab_vbox\"); note_page_box != nullptr) {\n\t\t\tnote_page_box->set_visible(has_nvme_attributes);\n\t\t}\n\n\t\tconst bool has_statistics = prop_repo.has_properties_for_section(StoragePropertySection::Statistics);\n\t\tif (note_page_box = lookup_widget(\"statistics_tab_vbox\"); note_page_box != nullptr) {\n\t\t\tnote_page_box->set_visible(has_statistics);\n\t\t}\n\n\t\tconst bool has_selftest = (drive_->get_self_test_support_status() == StorageDevice::SelfTestSupportStatus::Supported);\n\t\tif (note_page_box = lookup_widget(\"test_tab_vbox\"); note_page_box != nullptr) {\n\t\t\t// Some USB flash drives erroneously report SMART as enabled.\n\t\t\t// note_page_box->set_visible(drive->get_smart_status() == StorageDevice::Status::Enabled);\n\t\t\tnote_page_box->set_visible(has_selftest);\n\t\t\tif (has_selftest) {\n\t\t\t\tbook_selftest_page_no_ = 4;\n\t\t\t} else {\n\t\t\t\tbook_selftest_page_no_ = -1;\n\t\t\t}\n\t\t}\n\n\t\tconst bool has_ata_error_log = prop_repo.has_properties_for_section(StoragePropertySection::AtaErrorLog);\n\t\tif (note_page_box = lookup_widget(\"error_log_tab_vbox\"); note_page_box != nullptr) {\n\t\t\tnote_page_box->set_visible(has_ata_error_log);\n\t\t}\n\n\t\tconst bool has_nvme_error_log = prop_repo.has_properties_for_section(StoragePropertySection::NvmeErrorLog);\n\t\tif (note_page_box = lookup_widget(\"nvme_error_log_tab_vbox\"); note_page_box != nullptr) {\n\t\t\tnote_page_box->set_visible(has_nvme_error_log);\n\t\t}\n\n\t\tconst bool has_temperature_log = prop_repo.has_properties_for_section(StoragePropertySection::TemperatureLog);\n\t\tif (note_page_box = lookup_widget(\"temperature_log_tab_vbox\"); note_page_box != nullptr) {\n\t\t\tnote_page_box->set_visible(has_temperature_log);\n\t\t}\n\n\t\t// Advanced tab's subtabs\n\t\tconst bool has_capabilities = prop_repo.has_properties_for_section(StoragePropertySection::Capabilities);\n\t\tif (note_page_box = lookup_widget(\"capabilities_scrolledwindow\"); note_page_box != nullptr) {\n\t\t\tnote_page_box->set_visible(has_capabilities);\n\t\t}\n\n\t\tconst bool has_erc = prop_repo.has_properties_for_section(StoragePropertySection::ErcLog);\n\t\tif (note_page_box = lookup_widget(\"erc_scrolledwindow\"); note_page_box != nullptr) {\n\t\t\tnote_page_box->set_visible(has_erc);\n\t\t}\n\n\t\tconst bool has_selective = prop_repo.has_properties_for_section(StoragePropertySection::SelectiveSelftestLog);\n\t\tif (note_page_box = lookup_widget(\"selective_selftest_scrolledwindow\"); note_page_box != nullptr) {\n\t\t\tnote_page_box->set_visible(has_selective);\n\t\t}\n\n\t\tconst bool has_phy = prop_repo.has_properties_for_section(StoragePropertySection::PhyLog);\n\t\tif (note_page_box = lookup_widget(\"phy_scrolledwindow\"); note_page_box != nullptr) {\n\t\t\tnote_page_box->set_visible(has_phy);\n\t\t}\n\n\t\tconst bool has_dir = prop_repo.has_properties_for_section(StoragePropertySection::DirectoryLog);\n\t\tif (note_page_box = lookup_widget(\"directory_scrolledwindow\"); note_page_box != nullptr) {\n\t\t\tnote_page_box->set_visible(has_dir);\n\t\t}\n\n\t\tconst bool has_advanced =\n\t\t\t\thas_capabilities\n\t\t\t\t|| has_erc\n\t\t\t\t|| has_selective\n\t\t\t\t|| has_phy\n\t\t\t\t|| has_dir;\n\t\tif (note_page_box = lookup_widget(\"advanced_tab_vbox\"); note_page_box != nullptr) {\n\t\t\tnote_page_box->set_visible(has_advanced);\n\t\t}\n\n\t\t// Hide tab titles if only one tab is visible\n\t\tif (auto* notebook = lookup_widget<Gtk::Notebook*>(\"main_notebook\")) {\n\t\t\tnotebook->set_show_tabs(\n\t\t\t\t\thas_ata_attributes\n\t\t\t\t\t|| has_nvme_attributes\n\t\t\t\t\t|| has_statistics\n\t\t\t\t\t|| has_selftest\n\t\t\t\t\t|| has_ata_error_log\n\t\t\t\t\t|| has_nvme_error_log\n\t\t\t\t\t|| has_temperature_log\n\t\t\t\t\t|| has_advanced);\n\t\t}\n\t}\n\n\t// Top label - short device information\n\t{\n\t\tconst std::string device = Glib::Markup::escape_text(drive_->get_device_with_type());\n\t\tconst std::string model = Glib::Markup::escape_text(drive_->get_model_name().empty() ? _(\"Unknown model\") : drive_->get_model_name());\n\t\tconst std::string drive_letters = Glib::Markup::escape_text(drive_->format_drive_letters(false));\n\n\t\t/// Translators: %1 is device name, %2 is device model.\n\t\tthis->set_title(Glib::ustring::compose(_(\"Device Information - %1: %2 - GSmartControl\"), device, model));\n\n\t\t// Gtk::Label* device_name_label = lookup_widget<Gtk::Label*>(\"device_name_label\");\n\t\tif (device_name_label_) {\n\t\t\t/// Translators: %1 is device name, %2 is drive letters (if not empty), %3 is device model.\n\t\t\tdevice_name_label_->set_markup(Glib::ustring::compose(_(\"<b>Device:</b> %1%2  <b>Model:</b> %3\"),\n\t\t\t\t\tdevice, (drive_letters.empty() ? \"\" : (\" (<b>\" + drive_letters + \"</b>)\")), model));\n\t\t}\n\t}\n\n\n\t// Fill the tabs with info\n\n\t// we need reference here - we take addresses of the elements\n\tconst auto& property_repo = drive_->get_property_repository();  // it's a vector\n\n\tfill_ui_general(property_repo);\n\tfill_ui_ata_attributes(property_repo);\n\tfill_ui_nvme_attributes(property_repo);\n\tfill_ui_statistics(property_repo);\n\tif (clear_tests) {\n\t\tfill_ui_self_test_info();\n\t}\n\tfill_ui_self_test_log(property_repo);\n\tfill_ui_ata_error_log(property_repo);\n\tfill_ui_nvme_error_log(property_repo);\n\tfill_ui_temperature_log(property_repo);\n\n\t// Advanced tab\n\tauto caps_warning_level = fill_ui_capabilities(property_repo);\n\tauto errc_warning_level = fill_ui_error_recovery(property_repo);\n\tauto selective_warning_level = fill_ui_selective_self_test_log(property_repo);\n\tauto dir_warning_level = fill_ui_directory(property_repo);\n\tauto phy_warning_level = fill_ui_physical(property_repo);\n\n\tauto max_advanced_tab_warning = std::max({\n\t\tcaps_warning_level,\n\t\terrc_warning_level,\n\t\tselective_warning_level,\n\t\tdir_warning_level,\n\t\tphy_warning_level\n\t});\n\n\t// Advanced tab label\n\tapp_highlight_tab_label(lookup_widget(\"advanced_tab_label\"), max_advanced_tab_warning, tab_names_.advanced);\n}\n\n\n\nvoid GscInfoWindow::clear_ui_info(bool clear_tests_too)\n{\n\t// Note: We do NOT show/hide the notebook tabs here.\n\t// fill_ui_with_info() will do it all by itself.\n\n\t{\n\t\tthis->set_title(_(\"Device Information - GSmartControl\"));\n\n\t\t// Gtk::Label* device_name_label = lookup_widget<Gtk::Label*>(\"device_name_label\");\n\t\tif (device_name_label_) {\n\t\t\tdevice_name_label_->set_text(_(\"No data available\"));\n\t\t}\n\t}\n\n\t{\n\t\tauto* identity_table = lookup_widget<Gtk::Grid*>(\"identity_table\");\n\t\tif (identity_table) {\n\t\t\t// manually remove all children. without this visual corruption occurs.\n\t\t\tauto children = identity_table->get_children();\n\t\t\tfor (auto& widget : children) {\n\t\t\t\tidentity_table->remove(*widget);\n\t\t\t}\n\t\t}\n\n\t\t// tab label\n\t\tapp_highlight_tab_label(lookup_widget(\"general_tab_label\"), WarningLevel::None, tab_names_.identity);\n\t}\n\n\t{\n\t\tauto* label_vbox = lookup_widget<Gtk::Box*>(\"attributes_label_vbox\");\n\t\tapp_set_top_labels(label_vbox, std::vector<PropertyLabel>());\n\n\t\tif (auto* treeview = lookup_widget<Gtk::TreeView*>(\"attributes_treeview\")) {\n// \t\t\tGlib::RefPtr<Gtk::ListStore> model = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(treeview->get_model());\n// \t\t\tif (model)\n// \t\t\t\tmodel->clear();\n\t\t\ttreeview->remove_all_columns();\n\t\t\ttreeview->unset_model();\n\t\t}\n\n\t\t// tab label\n\t\tapp_highlight_tab_label(lookup_widget(\"attributes_tab_label\"), WarningLevel::None, tab_names_.ata_attributes);\n\t}\n\n\t{\n\t\tauto* label_vbox = lookup_widget<Gtk::Box*>(\"nvme_attributes_label_vbox\");\n\t\tapp_set_top_labels(label_vbox, std::vector<PropertyLabel>());\n\n\t\tif (auto* treeview = lookup_widget<Gtk::TreeView*>(\"nvme_attributes_treeview\")) {\n// \t\t\tGlib::RefPtr<Gtk::ListStore> model = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(treeview->get_model());\n// \t\t\tif (model)\n// \t\t\t\tmodel->clear();\n\t\t\ttreeview->remove_all_columns();\n\t\t\ttreeview->unset_model();\n\t\t}\n\n\t\t// tab label\n\t\tapp_highlight_tab_label(lookup_widget(\"nvme_attributes_tab_label\"), WarningLevel::None, tab_names_.nvme_attributes);\n\t}\n\n\t{\n\t\tauto* label_vbox = lookup_widget<Gtk::Box*>(\"statistics_label_vbox\");\n\t\tapp_set_top_labels(label_vbox, std::vector<PropertyLabel>());\n\n\t\tif (auto* treeview = lookup_widget<Gtk::TreeView*>(\"statistics_treeview\")) {\n\t\t\ttreeview->remove_all_columns();\n\t\t\ttreeview->unset_model();\n\t\t}\n\n\t\t// tab label\n\t\tapp_highlight_tab_label(lookup_widget(\"statistics_tab_label\"), WarningLevel::None, tab_names_.statistics);\n\t}\n\n\t{\n\t\tauto* label_vbox = lookup_widget<Gtk::Box*>(\"selftest_log_label_vbox\");\n\t\tapp_set_top_labels(label_vbox, std::vector<PropertyLabel>());\n\n\t\tif (auto* treeview = lookup_widget<Gtk::TreeView*>(\"selftest_log_treeview\")) {\n// \t\t\tGlib::RefPtr<Gtk::ListStore> model = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(treeview->get_model());\n// \t\t\tif (model)\n// \t\t\t\tmodel->clear();\n\t\t\ttreeview->remove_all_columns();\n\t\t\ttreeview->unset_model();\n\t\t}\n\n\t\tif (clear_tests_too) {\n\t\t\tauto* test_type_combo = lookup_widget<Gtk::ComboBox*>(\"test_type_combo\");\n\t\t\tif (test_type_combo) {\n\t\t\t\ttest_type_combo->set_sensitive(false);  // true if testing is possible and not active.\n\t\t\t\t// test_type_combo->clear();  // clear cellrenderers\n\t\t\t\tif (test_combo_model_)\n\t\t\t\t\ttest_combo_model_->clear();\n\t\t\t}\n\n\t\t\tif (auto* min_duration_label = lookup_widget<Gtk::Label*>(\"min_duration_label\"))\n\t\t\t\tmin_duration_label->set_text(\"N/A\");  // set on test selection\n\n\t\t\tif (auto* test_execute_button = lookup_widget<Gtk::Button*>(\"test_execute_button\"))\n\t\t\t\ttest_execute_button->set_sensitive(false);  // true if testing is possible and not active\n\n\n\t\t\tauto* test_description_textview = lookup_widget<Gtk::TextView*>(\"test_description_textview\");\n\t\t\tif (test_description_textview != nullptr && test_description_textview->get_buffer())\n\t\t\t\ttest_description_textview->get_buffer()->set_text(\"\");  // set on test selection\n\n\t\t\tif (auto* test_completion_progressbar = lookup_widget<Gtk::ProgressBar*>(\"test_completion_progressbar\")) {\n\t\t\t\ttest_completion_progressbar->set_text(\"\");  // set when test is run or completed\n\t\t\t\ttest_completion_progressbar->set_sensitive(false);  // set when test is run or completed\n\t\t\t\ttest_completion_progressbar->hide();\n\t\t\t}\n\n\t\t\tif (auto* test_stop_button = lookup_widget<Gtk::Button*>(\"test_stop_button\")) {\n\t\t\t\ttest_stop_button->set_sensitive(false);  // true when test is active\n\t\t\t\ttest_stop_button->hide();\n\t\t\t}\n\n\t\t\tif (auto* test_result_hbox = lookup_widget<Gtk::Box*>(\"test_result_hbox\"))\n\t\t\t\ttest_result_hbox->hide();  // hide by default. show when test is completed.\n\t\t}\n\n\t\t// tab label\n\t\tapp_highlight_tab_label(lookup_widget(\"test_tab_label\"), WarningLevel::None, tab_names_.test);\n\t}\n\n\t{\n\t\tauto* label_vbox = lookup_widget<Gtk::Box*>(\"error_log_label_vbox\");\n\t\tapp_set_top_labels(label_vbox, std::vector<PropertyLabel>());\n\n\t\tauto* treeview = lookup_widget<Gtk::TreeView*>(\"error_log_treeview\");\n\t\tif (treeview) {\n// \t\t\tGlib::RefPtr<Gtk::ListStore> model = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(treeview->get_model());\n// \t\t\tif (model)\n// \t\t\t\tmodel->clear();\n\t\t\ttreeview->remove_all_columns();\n\t\t\ttreeview->unset_model();\n\t\t}\n\n\t\tauto* textview = lookup_widget<Gtk::TextView*>(\"error_log_textview\");\n\t\tif (textview) {\n\t\t\t// we re-create the buffer to get rid of all the Marks\n\t\t\ttextview->set_buffer(Gtk::TextBuffer::create());\n\t\t\ttextview->get_buffer()->set_text(\"\\n\"s + _(\"No data available\"));\n\t\t}\n\n\t\t// tab label\n\t\tapp_highlight_tab_label(lookup_widget(\"error_log_tab_label\"), WarningLevel::None, tab_names_.ata_error_log);\n\t}\n\n\t{\n\t\tauto* label_vbox = lookup_widget<Gtk::Box*>(\"nvme_error_log_label_vbox\");\n\t\tapp_set_top_labels(label_vbox, std::vector<PropertyLabel>());\n\n\t\tauto* textview = lookup_widget<Gtk::TextView*>(\"nvme_error_log_textview\");\n\t\tif (textview) {\n\t\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\t\tbuffer->set_text(\"\\n\"s + _(\"No data available\"));\n\t\t}\n\n\t\t// tab label\n\t\tapp_highlight_tab_label(lookup_widget(\"nvme_error_log_tab_label\"), WarningLevel::None, tab_names_.nvme_error_log);\n\t}\n\n\t{\n\t\tauto* textview = lookup_widget<Gtk::TextView*>(\"temperature_log_textview\");\n\t\tif (textview) {\n\t\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\t\tbuffer->set_text(\"\\n\"s + _(\"No data available\"));\n\t\t}\n\n\t\t// tab label\n\t\tapp_highlight_tab_label(lookup_widget(\"temperature_log_tab_label\"), WarningLevel::None, tab_names_.temperature);\n\t}\n\n\t// tab label\n\tapp_highlight_tab_label(lookup_widget(\"advanced_tab_label\"), WarningLevel::None, tab_names_.advanced);\n\n\t{\n\t\tif (auto* treeview = lookup_widget<Gtk::TreeView*>(\"capabilities_treeview\")) {\n\t\t\t// It's better to clear the model rather than unset it. If we unset it, we'll have\n\t\t\t// to deattach the callbacks too. But if we clear it, we have to remember column vars.\n// \t\t\tGlib::RefPtr<Gtk::ListStore> model = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(treeview->get_model());\n// \t\t\tif (model)\n// \t\t\t\tmodel->clear();\n\t\t\ttreeview->remove_all_columns();\n\t\t\ttreeview->unset_model();\n\t\t}\n\n\t\t// tab label\n\t\tapp_highlight_tab_label(lookup_widget(\"capabilities_tab_label\"), WarningLevel::None, tab_names_.capabilities);\n\t}\n\n\t{\n\t\tif (auto* textview = lookup_widget<Gtk::TextView*>(\"erc_log_textview\")) {\n\t\t\ttextview->get_buffer()->set_text(\"\\n\"s + _(\"No data available\"));\n\t\t}\n\n\t\t// tab label\n\t\tapp_highlight_tab_label(lookup_widget(\"erc_tab_label\"), WarningLevel::None, tab_names_.erc);\n\t}\n\n\t{\n\t\tif (auto* textview = lookup_widget<Gtk::TextView*>(\"selective_selftest_log_textview\")) {\n\t\t\ttextview->get_buffer()->set_text(\"\\n\"s + _(\"No data available\"));\n\t\t}\n\n\t\t// tab label\n\t\tapp_highlight_tab_label(lookup_widget(\"selective_selftest_tab_label\"), WarningLevel::None, tab_names_.selective_selftest);\n\t}\n\n\t{\n\t\tif (auto* textview = lookup_widget<Gtk::TextView*>(\"phy_log_textview\")) {\n\t\t\ttextview->get_buffer()->set_text(\"\\n\"s + _(\"No data available\"));\n\t\t}\n\n\t\t// tab label\n\t\tapp_highlight_tab_label(lookup_widget(\"phy_tab_label\"), WarningLevel::None, tab_names_.phy);\n\t}\n\n\t{\n\t\tif (auto* textview = lookup_widget<Gtk::TextView*>(\"directory_log_textview\")) {\n\t\t\ttextview->get_buffer()->set_text(\"\\n\"s + _(\"No data available\"));\n\t\t}\n\n\t\t// tab label\n\t\tapp_highlight_tab_label(lookup_widget(\"directory_tab_label\"), WarningLevel::None, tab_names_.directory);\n\t}\n\n\t// Delete columns. We need to do this because otherwise,\n\t// the column objects store their old indexes and will break when re-added\n\tcolumns_.reset();\n\tcolumns_ = std::make_unique<GscInfoWindowColumns>();\n}\n\n\n\nvoid GscInfoWindow::refresh_info(bool clear_tests_too)\n{\n\tthis->set_sensitive(false);  // make insensitive until filled. helps with pressed F5 problem.\n\n\t// this->clear_ui_info();  // no need, fill_ui_with_info() will call it.\n\tthis->fill_ui_with_info(true, true, clear_tests_too);\n\n\tthis->set_sensitive(true);  // make sensitive again.\n}\n\n\n\nvoid GscInfoWindow::show_tests()\n{\n\tif (auto* book = lookup_widget<Gtk::Notebook*>(\"main_notebook\")) {\n\t\tif (book_selftest_page_no_ >= 0) {\n\t\t\tbook->set_current_page(book_selftest_page_no_);  // the Tests tab\n\t\t} else {\n\t\t\tgui_show_warn_dialog(_(\"Self-Tests Not Supported\"), _(\"Self-tests are not supported on this drive.\"), this);\n\t\t}\n\t}\n}\n\n\n\nbool GscInfoWindow::on_delete_event([[maybe_unused]] GdkEventAny* e)\n{\n\ton_close_window_button_clicked();\n\treturn true;  // event handled\n}\n\n\n\nvoid GscInfoWindow::on_refresh_info_button_clicked()\n{\n\tthis->refresh_info();\n}\n\n\n\nvoid GscInfoWindow::on_view_output_button_clicked()\n{\n\tauto win = GscTextWindow<SmartctlOutputInstance>::create();\n\t// make save visible and enable monospace font\n\n\tstd::string output = this->drive_->get_full_output();\n\tif (output.empty()) {\n\t\toutput = this->drive_->get_basic_output();\n\t}\n\n\twin->set_text_from_command(_(\"Smartctl Output\"), output);\n\n\tconst std::string filename = drive_->get_save_filename();\n\tif (!filename.empty())\n\t\twin->set_save_filename(filename);\n\n\twin->show();\n}\n\n\n\nvoid GscInfoWindow::on_save_info_button_clicked()\n{\n\tstatic std::string last_dir;\n\tif (last_dir.empty()) {\n\t\tlast_dir = rconfig::get_data<std::string>(\"gui/drive_data_open_save_dir\");\n\t}\n\tint result = 0;\n\n\tconst std::string filename = drive_->get_save_filename();\n\n\tGlib::RefPtr<Gtk::FileFilter> specific_filter = Gtk::FileFilter::create();\n\tspecific_filter->set_name(_(\"JSON and Text Files\"));\n\tspecific_filter->add_pattern(\"*.json\");\n\tspecific_filter->add_pattern(\"*.txt\");\n\n\tGlib::RefPtr<Gtk::FileFilter> json_filter = Gtk::FileFilter::create();\n\tjson_filter->set_name(_(\"JSON Files\"));\n\tjson_filter->add_pattern(\"*.json\");\n\n\tGlib::RefPtr<Gtk::FileFilter> txt_filter = Gtk::FileFilter::create();\n\ttxt_filter->set_name(_(\"Text Files\"));\n\ttxt_filter->add_pattern(\"*.txt\");\n\n\tGlib::RefPtr<Gtk::FileFilter> all_filter = Gtk::FileFilter::create();\n\tall_filter->set_name(_(\"All Files\"));\n\tall_filter->add_pattern(\"*\");\n\n#if GTK_CHECK_VERSION(3, 20, 0)\n\tstd::unique_ptr<GtkFileChooserNative, decltype(&g_object_unref)> dialog(gtk_file_chooser_native_new(\n\t\t\t_(\"Save Data As...\"), this->gobj(), GTK_FILE_CHOOSER_ACTION_SAVE, nullptr, nullptr),\n\t\t\t&g_object_unref);\n\n\tgtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog.get()), TRUE);\n\n\tgtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), specific_filter->gobj());\n\tgtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), json_filter->gobj());\n\tgtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), txt_filter->gobj());\n\tgtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), all_filter->gobj());\n\n\tif (!last_dir.empty())\n\t\tgtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog.get()), last_dir.c_str());\n\n\tif (!filename.empty())\n\t\tgtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog.get()), filename.c_str());\n\n\tresult = gtk_native_dialog_run(GTK_NATIVE_DIALOG(dialog.get()));\n\n#else\n\tGtk::FileChooserDialog dialog(*this, _(\"Save Data As...\"),\n\t\t\tGtk::FILE_CHOOSER_ACTION_SAVE);\n\n\t// Add response buttons the the dialog\n\tdialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);\n\tdialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);\n\n\tdialog.set_do_overwrite_confirmation(true);\n\n\tdialog.add_filter(specific_filter);\n\tdialog.add_filter(json_filter);\n\tdialog.add_filter(txt_filter);\n\tdialog.add_filter(all_filter);\n\n\tif (!last_dir.empty())\n\t\tdialog.set_current_folder(last_dir);\n\n\tif (!filename.empty())\n\t\tdialog.set_current_name(filename);\n\n\t// Show the dialog and wait for a user response\n\tresult = dialog.run();  // the main cycle blocks here\n#endif\n\n\t// Handle the response\n\tswitch (result) {\n\t\tcase Gtk::RESPONSE_ACCEPT:\n\t\t{\n\t\t\thz::fs::path file;\n#if GTK_CHECK_VERSION(3, 20, 0)\n\t\t\tfile = hz::fs_path_from_string(app_string_from_gchar(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog.get()))));\n\t\t\tlast_dir = hz::fs_path_to_string(file.parent_path());\n#else\n\t\t\tfile = hz::fs_path_from_string(dialog.get_filename());  // in fs encoding\n\t\t\tlast_dir = dialog.get_current_folder();  // save for the future\n#endif\n\t\t\trconfig::set_data(\"gui/drive_data_open_save_dir\", last_dir);\n\n\t\t\tbool txt_selected = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(dialog.get())) == txt_filter->gobj();\n\n\t\t\tif (file.extension() != \".json\" && file.extension() != \".txt\") {\n\t\t\t\tfile += (txt_selected ? \".txt\" : \".json\");\n\t\t\t}\n\n\t\t\tbool save_txt = txt_selected || file.extension() == \".txt\";\n\n\t\t\tstd::string data = this->drive_->get_full_output();\n\t\t\tif (data.empty()) {\n\t\t\t\tdata = this->drive_->get_basic_output();\n\t\t\t}\n\t\t\tif (save_txt) {\n\t\t\t\tif (auto p = this->drive_->get_property_repository().lookup_property(\"smartctl/output\"); !p.empty()) {\n\t\t\t\t\tconst std::string text_output = p.get_value<std::string>();\n\t\t\t\t\tif (!text_output.empty()) {\n\t\t\t\t\t\tdata = text_output;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst std::error_code ec = hz::fs_file_put_contents(file, data);\n\t\t\tif (ec) {\n\t\t\t\tgui_show_error_dialog(_(\"Cannot save SMART data to file\"), ec.message(), this);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\tcase Gtk::RESPONSE_CANCEL: case Gtk::RESPONSE_DELETE_EVENT:\n\t\t\t// nothing, the dialog is closed already\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Unknown dialog response code: \" << result << \".\\n\");\n\t\t\tbreak;\n\t}\n}\n\n\n\nvoid GscInfoWindow::on_close_window_button_clicked()\n{\n\tif (drive_ && drive_->get_test_is_active()) {  // disallow close if test is active.\n\t\tgui_show_warn_dialog(_(\"Please wait until all tests are finished.\"), this);\n\t} else {\n\t\tdestroy_instance();  // deletes this object and nullifies instance\n\t}\n}\n\n\n\nvoid GscInfoWindow::on_test_type_combo_changed()\n{\n\tauto* test_type_combo = lookup_widget<Gtk::ComboBox*>(\"test_type_combo\");\n\n\tGtk::TreeRow row = *(test_type_combo->get_active());\n\tif (row) {\n\t\tstd::shared_ptr<SelfTest> test = row[test_combo_columns_.self_test];\n\n\t\t//debug_out_error(\"app\", test->get_min_duration_seconds() << \"\\n\");\n\t\tif (auto* min_duration_label = lookup_widget<Gtk::Label*>(\"min_duration_label\")) {\n\t\t\tauto duration = test->get_min_duration_seconds();\n\t\t\tmin_duration_label->set_text(duration == std::chrono::seconds(-1) ? C_(\"duration\", \"N/A\")\n\t\t\t\t\t: (duration.count() == 0 ? C_(\"duration\", \"Unknown\") : hz::format_time_length(duration)));\n\t\t}\n\n\t\tauto* test_description_textview = lookup_widget<Gtk::TextView*>(\"test_description_textview\");\n\t\tif (test_description_textview != nullptr && test_description_textview->get_buffer())\n\t\t\ttest_description_textview->get_buffer()->set_text(row[test_combo_columns_.description]);\n\t}\n}\n\n\n\nvoid GscInfoWindow::fill_ui_general(const StoragePropertyRepository& property_repo)\n{\n\tconst auto& props = property_repo.get_properties();\n\n\t// filter out some properties\n\tstd::vector<StorageProperty> general_props, version_props, overall_health_props, nvme_health_props;\n\n\tfor (auto&& p : props) {\n\t\tif (p.section == StoragePropertySection::Info) {\n\t\t\tif (p.generic_name == \"smartctl/version/_merged_full\") {\n\t\t\t\tversion_props.push_back(p);\n\t\t\t} else if (p.generic_name == \"smartctl/version/_merged\") {\n\t\t\t\tcontinue;  // we use the full version string instead.\n\t\t\t} else {\n\t\t\t\tgeneral_props.push_back(p);\n\t\t\t}\n\t\t} else if (p.section == StoragePropertySection::OverallHealth) {\n\t\t\toverall_health_props.push_back(p);\n\t\t} else if (p.section == StoragePropertySection::NvmeHealth) {\n\t\t\tnvme_health_props.push_back(p);\n\t\t}\n\t}\n\n\t// put version after all the info\n\tfor (auto&& p : version_props) {\n\t\tgeneral_props.push_back(p);\n\t}\n\n\t// health at the bottom\n\tfor (auto&& p : overall_health_props) {\n\t\tgeneral_props.push_back(p);\n\t}\n\n\t// nvme health properties are only present if there is health failure\n\tfor (auto&& p : nvme_health_props) {\n\t\tgeneral_props.push_back(p);\n\t}\n\n\n\n\tauto* identity_table = lookup_widget<Gtk::Grid*>(\"identity_table\");\n\n\tidentity_table->hide();\n\n\tWarningLevel max_tab_warning = WarningLevel::None;\n\tconst bool dark_mode = gui_is_dark_theme_active();\n\tint row = 0;\n\n\tfor (auto&& p : general_props) {\n\t\tif (!p.show_in_ui) {\n\t\t\tcontinue;  // hide debug messages from smartctl\n\t\t}\n\n\t\tif (p.generic_name == \"smart_status/passed\") {  // a little distance for this one\n\t\t\tGtk::Label* empty_label = Gtk::manage(new Gtk::Label());\n\t\t\tempty_label->set_can_focus(false);\n\t\t\tidentity_table->attach(*empty_label, 0, row, 2, 1);\n\t\t\t++row;\n\t\t}\n\n\t\tGtk::Label* name = Gtk::manage(new Gtk::Label());\n\t\t// name->set_ellipsize(Pango::ELLIPSIZE_END);\n\t\tname->set_alignment(Gtk::ALIGN_END);  // right-align\n\t\tname->set_selectable(true);\n\t\tname->set_can_focus(false);\n\t\tname->set_markup(\"<b>\" + Glib::Markup::escape_text(p.displayable_name) + \"</b>\");\n\n\t\t// If the above is Label, then this has to be Label too, else it will shrink\n\t\t// and \"name\" will take most of the horizontal space. If \"name\" is set to shrink,\n\t\t// then it stops being right-aligned.\n\t\tGtk::Label* value = Gtk::manage(new Gtk::Label());\n\t\t// value->set_ellipsize(Pango::ELLIPSIZE_END);\n\t\tvalue->set_alignment(Gtk::ALIGN_START);  // left-align\n\t\tvalue->set_selectable(true);\n\t\tvalue->set_can_focus(false);\n\t\tvalue->set_markup(Glib::Markup::escape_text(p.format_value()));\n\n\t\tstd::string fg;\n\t\tif (app_property_get_label_highlight_color(dark_mode, p.warning_level, fg)) {\n\t\t\tname->set_markup(\"<span color=\\\"\" + fg + \"\\\">\" + name->get_label() + \"</span>\");\n\t\t\tvalue->set_markup(\"<span color=\\\"\" + fg + \"\\\">\" + value->get_label() + \"</span>\");\n\t\t}\n\n\t\tidentity_table->attach(*name, 0, row, 1, 1);\n\t\tidentity_table->attach(*value, 1, row, 1, 1);\n\n\t\tapp_gtkmm_set_widget_tooltip(*name, p.get_description(), true);\n\t\tapp_gtkmm_set_widget_tooltip(*value, // value->get_label() + \"\\n\\n\" +\n\t\t\t\tp.get_description(), true);\n\n\t\tif (int(p.warning_level) > int(max_tab_warning)) {\n\t\t\tmax_tab_warning = p.warning_level;\n\t\t}\n\n\t\t++row;\n\t}\n\n\tidentity_table->show_all();\n\n\t// tab label\n\tapp_highlight_tab_label(lookup_widget(\"general_tab_label\"), max_tab_warning, tab_names_.identity);\n}\n\n\n\nvoid GscInfoWindow::fill_ui_ata_attributes(const StoragePropertyRepository& property_repo)\n{\n\tconst auto& props = property_repo.get_properties();\n\n\tauto* treeview = lookup_widget<Gtk::TreeView*>(\"attributes_treeview\");\n\n\tGtk::TreeModelColumnRecord model_columns;\n\t[[maybe_unused]] int num_tree_col = 0;\n\n\t// ID (int), Name, Flag (hex), Normalized Value (uint8), Worst (uint8), Thresh (uint8), Raw (int64), Type (string),\n\t// Updated (string), When Failed (string)\n\n\tmodel_columns.add(columns_->ata_attribute_table_columns.id);  // we can use the column variable by value after this.\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.id, *treeview, _(\"ID\"), _(\"Attribute ID\"), true);\n\n\tmodel_columns.add(columns_->ata_attribute_table_columns.displayable_name);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.displayable_name, *treeview,\n\t\t\t_(\"Name\"), _(\"Attribute name (this is deduced from ID by smartctl and may be incorrect, as it's highly vendor-specific)\"), true);\n\ttreeview->set_search_column(columns_->ata_attribute_table_columns.displayable_name.index());\n\n\tmodel_columns.add(columns_->ata_attribute_table_columns.when_failed);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.when_failed, *treeview,\n\t\t\t_(\"Failed\"), _(\"When failed (that is, the normalized value became equal to or less than threshold)\"), true, true);\n\n\tmodel_columns.add(columns_->ata_attribute_table_columns.normalized_value);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.normalized_value, *treeview,\n\t\t\tC_(\"value\", \"Normalized\"), _(\"Normalized value (highly vendor-specific; converted from Raw value by the drive's firmware)\"), false);\n\n\tmodel_columns.add(columns_->ata_attribute_table_columns.worst);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.worst, *treeview,\n\t\t\tC_(\"value\", \"Worst\"), _(\"The worst normalized value recorded for this attribute during the drive's lifetime (with SMART enabled)\"), false);\n\n\tmodel_columns.add(columns_->ata_attribute_table_columns.threshold);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.threshold, *treeview,\n\t\t\tC_(\"value\", \"Threshold\"), _(\"Threshold for normalized value. Normalized value should be greater than threshold (unless vendor thinks otherwise).\"), false);\n\n\tmodel_columns.add(columns_->ata_attribute_table_columns.raw);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.raw, *treeview,\n\t\t\t_(\"Raw value\"), _(\"Raw value as reported by drive. May or may not be sensible.\"), false);\n\n\tmodel_columns.add(columns_->ata_attribute_table_columns.type);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.type, *treeview,\n\t\t\t_(\"Type\"), _(\"Alarm condition is reached when normalized value becomes less than or equal to threshold. Type indicates whether it's a signal of drive's pre-failure time or just an old age.\"), false, true);\n\n\t// Doesn't carry that much info. Advanced users can look at the flags.\n// \t\tmodel_columns.add(attribute_table_columns.updated);\n// \t\ttree_col = app_gtkmm_create_tree_view_column(attribute_table_columns.updated, *treeview,\n// \t\t\t\t\"Updated\", \"The attribute is usually updated continuously, or during Offline Data Collection only. This column indicates that.\", true);\n\n\tmodel_columns.add(columns_->ata_attribute_table_columns.flag_value);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->ata_attribute_table_columns.flag_value, *treeview,\n\t\t\t_(\"Flags\"), _(\"Flags\") + \"\\n\\n\"s\n\t\t\t\t\t+ Glib::ustring::compose(_(\"If given in %1 format, the presence of each letter indicates that the flag is on.\"), \"POSRCK+\") + \"\\n\"\n\t\t\t\t\t+ _(\"P: pre-failure attribute (if the attribute failed, the drive is failing)\") + \"\\n\"\n\t\t\t\t\t+ _(\"O: updated continuously (as opposed to updated on offline data collection)\") + \"\\n\"\n\t\t\t\t\t+ _(\"S: speed / performance attribute\") + \"\\n\"\n\t\t\t\t\t+ _(\"R: error rate\") + \"\\n\"\n\t\t\t\t\t+ _(\"C: event count\") + \"\\n\"\n\t\t\t\t\t+ _(\"K: auto-keep\") + \"\\n\"\n\t\t\t\t\t+ _(\"+: undocumented bits present\"), false);\n\n\tmodel_columns.add(columns_->ata_attribute_table_columns.tooltip);\n\ttreeview->set_tooltip_column(columns_->ata_attribute_table_columns.tooltip.index());\n\n\tmodel_columns.add(columns_->ata_attribute_table_columns.storage_property);\n\n\n\t// create a TreeModel (ListStore)\n\tGlib::RefPtr<Gtk::ListStore> list_store = Gtk::ListStore::create(model_columns);\n\tlist_store->set_sort_column(columns_->ata_attribute_table_columns.id, Gtk::SORT_ASCENDING);  // default sort\n\ttreeview->set_model(list_store);\n\n\tfor (int i = 0; i < int(treeview->get_n_columns()); ++i) {\n\t\tGtk::TreeViewColumn* tcol = treeview->get_column(i);\n\t\ttcol->set_cell_data_func(*(tcol->get_first_cell()),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscInfoWindow::cell_renderer_for_ata_attributes), i));\n\t}\n\n\n\tWarningLevel max_tab_warning = WarningLevel::None;\n\tstd::vector<PropertyLabel> label_strings;  // outside-of-tree properties\n\n\tfor (const auto& p : props) {\n\t\tif (p.section != StoragePropertySection::AtaAttributes || !p.show_in_ui)\n\t\t\tcontinue;\n\n\t\t// add non-attribute-type properties to label above\n\t\tif (!p.is_value_type<AtaStorageAttribute>()) {\n\t\t\tlabel_strings.emplace_back(p.displayable_name + \": \" + p.format_value(), &p);\n\n\t\t\tif (int(p.warning_level) > int(max_tab_warning))\n\t\t\t\tmax_tab_warning = p.warning_level;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst auto& attr = p.get_value<AtaStorageAttribute>();\n\n\t\tGtk::TreeRow row = *(list_store->append());\n\t\trow[columns_->ata_attribute_table_columns.id] = attr.id;\n\t\trow[columns_->ata_attribute_table_columns.displayable_name] = Glib::Markup::escape_text(p.displayable_name);\n\t\trow[columns_->ata_attribute_table_columns.flag_value] = Glib::Markup::escape_text(attr.flag);  // it's a string, not int.\n\t\trow[columns_->ata_attribute_table_columns.normalized_value] = Glib::Markup::escape_text(attr.value.has_value() ? hz::number_to_string_locale(attr.value.value()) : \"-\");\n\t\trow[columns_->ata_attribute_table_columns.worst] = Glib::Markup::escape_text(attr.worst.has_value() ? hz::number_to_string_locale(attr.worst.value()) : \"-\");\n\t\trow[columns_->ata_attribute_table_columns.threshold] = Glib::Markup::escape_text(attr.threshold.has_value() ? hz::number_to_string_locale(attr.threshold.value()) : \"-\");\n\t\trow[columns_->ata_attribute_table_columns.raw] = Glib::Markup::escape_text(attr.format_raw_value());\n\t\trow[columns_->ata_attribute_table_columns.type] = Glib::Markup::escape_text(\n\t\t\t\tAtaStorageAttribute::get_readable_attribute_type_name(attr.attr_type));\n// \t\trow[attribute_table_columns.updated] = Glib::Markup::escape_text(AtaStorageAttribute::get_update_type_name(attr.update_type));\n\t\trow[columns_->ata_attribute_table_columns.when_failed] = Glib::Markup::escape_text(\n\t\t\t\tAtaStorageAttribute::get_readable_fail_time_name(attr.when_failed));\n\t\trow[columns_->ata_attribute_table_columns.tooltip] = p.get_description();  // markup\n\t\trow[columns_->ata_attribute_table_columns.storage_property] = &p;\n\n\t\tif (int(p.warning_level) > int(max_tab_warning))\n\t\t\tmax_tab_warning = p.warning_level;\n\t}\n\n\n\tauto* label_vbox = lookup_widget<Gtk::Box*>(\"attributes_label_vbox\");\n\tapp_set_top_labels(label_vbox, label_strings);\n\n\t// tab label\n\tapp_highlight_tab_label(lookup_widget(\"attributes_tab_label\"), max_tab_warning, tab_names_.ata_attributes);\n}\n\n\n\nvoid GscInfoWindow::fill_ui_nvme_attributes(const StoragePropertyRepository& property_repo)\n{\n\tconst auto& props = property_repo.get_properties();\n\n\tauto* treeview = lookup_widget<Gtk::TreeView*>(\"nvme_attributes_treeview\");\n\n\tGtk::TreeModelColumnRecord model_columns;\n\t[[maybe_unused]] int num_tree_col = 0;\n\n\tmodel_columns.add(columns_->nvme_attribute_table_columns.displayable_name);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->nvme_attribute_table_columns.displayable_name, *treeview,\n\t\t\t_(\"Description\"), _(\"Entry description\"), true);\n\ttreeview->set_search_column(columns_->nvme_attribute_table_columns.displayable_name.index());\n\n\tmodel_columns.add(columns_->nvme_attribute_table_columns.value);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->nvme_attribute_table_columns.value, *treeview,\n\t\t\t_(\"Value\"), _(\"Value\"), false);\n\n\tmodel_columns.add(columns_->nvme_attribute_table_columns.tooltip);\n\ttreeview->set_tooltip_column(columns_->nvme_attribute_table_columns.tooltip.index());\n\n\tmodel_columns.add(columns_->nvme_attribute_table_columns.storage_property);\n\n\n\t// create a TreeModel (ListStore)\n\tGlib::RefPtr<Gtk::ListStore> list_store = Gtk::ListStore::create(model_columns);\n\ttreeview->set_model(list_store);\n\n\tfor (int i = 0; i < int(treeview->get_n_columns()); ++i) {\n\t\tGtk::TreeViewColumn* tcol = treeview->get_column(i);\n\t\ttcol->set_cell_data_func(*(tcol->get_first_cell()),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscInfoWindow::cell_renderer_for_nvme_attributes), i));\n\t}\n\n\tWarningLevel max_tab_warning = WarningLevel::None;\n\tstd::vector<PropertyLabel> label_strings;  // outside-of-tree properties\n\n\tfor (const auto& p : props) {\n\t\tif (p.section != StoragePropertySection::NvmeAttributes || !p.show_in_ui)\n\t\t\tcontinue;\n\n\t\tGtk::TreeRow row = *(list_store->append());\n\n\t\tconst auto& value = p.format_value();\n\t\trow[columns_->nvme_attribute_table_columns.displayable_name] = Glib::Markup::escape_text(p.displayable_name);\n\t\trow[columns_->nvme_attribute_table_columns.value] = Glib::Markup::escape_text(value);\n\t\trow[columns_->nvme_attribute_table_columns.tooltip] = p.get_description();  // markup\n\t\trow[columns_->nvme_attribute_table_columns.storage_property] = &p;\n\n\t\tif (int(p.warning_level) > int(max_tab_warning))\n\t\t\tmax_tab_warning = p.warning_level;\n\t}\n\n\tauto* label_vbox = lookup_widget<Gtk::Box*>(\"nvme_attributes_label_vbox\");\n\tapp_set_top_labels(label_vbox, label_strings);\n\n\t// tab label\n\tapp_highlight_tab_label(lookup_widget(\"nvme_attributes_tab_label\"), max_tab_warning, tab_names_.nvme_attributes);\n}\n\n\n\nvoid GscInfoWindow::fill_ui_statistics(const StoragePropertyRepository& property_repo)\n{\n\tconst auto& props = property_repo.get_properties();\n\n\tauto* treeview = lookup_widget<Gtk::TreeView*>(\"statistics_treeview\");\n\n\tGtk::TreeModelColumnRecord model_columns;\n\t[[maybe_unused]] int num_tree_col = 0;\n\n\tmodel_columns.add(columns_->statistics_table_columns.displayable_name);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->statistics_table_columns.displayable_name, *treeview,\n\t\t\t_(\"Description\"), _(\"Entry description\"), true);\n\ttreeview->set_search_column(columns_->statistics_table_columns.displayable_name.index());\n\n\tmodel_columns.add(columns_->statistics_table_columns.value);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->statistics_table_columns.value, *treeview,\n\t\t\t_(\"Value\"), Glib::ustring::compose(_(\"Value (can be normalized if '%1' flag is present)\"), \"N\"), false);\n\n\tmodel_columns.add(columns_->statistics_table_columns.flags);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->statistics_table_columns.flags, *treeview,\n\t\t\t_(\"Flags\"), _(\"Flags\") + \"\\n\\n\"s\n\t\t\t\t\t+ _(\"V: valid\") + \"\\n\"\n\t\t\t\t\t+ _(\"N: value is normalized\") + \"\\n\"\n\t\t\t\t\t+ _(\"D: supports Device Statistics Notification (DSN)\") + \"\\n\"\n\t\t\t\t\t+ _(\"C: monitored condition met\") + \"\\n\"  // Related to DSN? From the specification, it looks like something user-controllable.\n\t\t\t\t\t+ _(\"+: undocumented bits present\"), false);\n\n\tmodel_columns.add(columns_->statistics_table_columns.page_offset);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->statistics_table_columns.page_offset, *treeview,\n\t\t\t_(\"Page, Offset\"), _(\"Page and offset of the entry\"), false);\n\n\tmodel_columns.add(columns_->statistics_table_columns.tooltip);\n\ttreeview->set_tooltip_column(columns_->statistics_table_columns.tooltip.index());\n\n\tmodel_columns.add(columns_->statistics_table_columns.storage_property);\n\n\n\t// create a TreeModel (ListStore)\n\tGlib::RefPtr<Gtk::ListStore> list_store = Gtk::ListStore::create(model_columns);\n\ttreeview->set_model(list_store);\n\t// No sorting (we don't want to screw up the headers).\n\n\tfor (int i = 0; i < int(treeview->get_n_columns()); ++i) {\n\t\tGtk::TreeViewColumn* tcol = treeview->get_column(i);\n\t\ttcol->set_cell_data_func(*(tcol->get_first_cell()),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscInfoWindow::cell_renderer_for_statistics), i));\n\t}\n\n\tWarningLevel max_tab_warning = WarningLevel::None;\n\tstd::vector<PropertyLabel> label_strings;  // outside-of-tree properties\n\n\tfor (const auto& p : props) {\n\t\tif (p.section != StoragePropertySection::Statistics || !p.show_in_ui)\n\t\t\tcontinue;\n\n\t\t// add non-entry-type properties to label above\n\t\tif (!p.is_value_type<AtaStorageStatistic>()) {\n\t\t\tlabel_strings.emplace_back(p.displayable_name + \": \" + p.format_value(), &p);\n\n\t\t\tif (int(p.warning_level) > int(max_tab_warning))\n\t\t\t\tmax_tab_warning = p.warning_level;\n\t\t\tcontinue;\n\t\t}\n\n\t\tGtk::TreeRow row = *(list_store->append());\n\n\t\tconst auto& st = p.get_value<AtaStorageStatistic>();\n\t\trow[columns_->statistics_table_columns.displayable_name] = Glib::Markup::escape_text(st.is_header ? p.displayable_name : (\"    \" + p.displayable_name));\n\t\trow[columns_->statistics_table_columns.value] = Glib::Markup::escape_text(st.format_value());\n\t\trow[columns_->statistics_table_columns.flags] = Glib::Markup::escape_text(st.flags);  // it's a string, not int.\n\t\trow[columns_->statistics_table_columns.page_offset] = Glib::Markup::escape_text(st.is_header ? std::string()\n\t\t\t\t: hz::string_sprintf(\"0x%02x, 0x%03x\", int(st.page), int(st.offset)));\n\t\trow[columns_->statistics_table_columns.tooltip] = p.get_description();  // markup\n\t\trow[columns_->statistics_table_columns.storage_property] = &p;\n\n\t\tif (int(p.warning_level) > int(max_tab_warning))\n\t\t\tmax_tab_warning = p.warning_level;\n\t}\n\n\tauto* label_vbox = lookup_widget<Gtk::Box*>(\"statistics_label_vbox\");\n\tapp_set_top_labels(label_vbox, label_strings);\n\n\t// tab label\n\tapp_highlight_tab_label(lookup_widget(\"statistics_tab_label\"), max_tab_warning, tab_names_.statistics);\n}\n\n\n\nvoid GscInfoWindow::fill_ui_self_test_info()\n{\n\tauto* test_type_combo = lookup_widget<Gtk::ComboBox*>(\"test_type_combo\");\n\n\t// don't check with get_model(), it comes pre-modeled from glade.\n\tif (!test_combo_model_) {\n\t\tGtk::TreeModelColumnRecord model_columns;\n\n\t\t// Test name, [description], [selftest_obj]\n\t\tmodel_columns.add(test_combo_columns_.name);  // we can use the column variable by value after this.\n\t\tmodel_columns.add(test_combo_columns_.description);\n\t\tmodel_columns.add(test_combo_columns_.self_test);\n\n\t\ttest_combo_model_ = Gtk::ListStore::create(model_columns);\n\t\ttest_type_combo->set_model(test_combo_model_);\n\n\t\t// visible columns\n\t\ttest_type_combo->clear();  // clear old (glade) cellrenderers\n\n\t\ttest_type_combo->pack_start(test_combo_columns_.name);\n\t}\n\n\t// add possible tests\n\n\tGtk::TreeModel::Row row;\n\n//\tauto test_ioffline = std::make_shared<SelfTest>(drive, SelfTest::TestType::ImmediateOffline);\n//\tif (test_ioffline->is_supported()) {\n//\t\trow = *(test_combo_model->append());\n//\t\trow[test_combo_columns.name] = SelfTest::get_test_displayable_name(SelfTest::TestType::ImmediateOffline);\n//\t\trow[test_combo_columns.description] =\n//\t\t\t\t_(\"Immediate Offline Test (also known as Immediate Offline Data Collection)\"\n//\t\t\t\t\" is the manual version of Automatic Offline Data Collection, which, if enabled, is automatically run\"\n//\t\t\t\t\" every four hours. If an error occurs during this test, it will be reported in Error Log. Besides that,\"\n//\t\t\t\t\" its effects are visible only in that it updates the \\\"Offline\\\" Attribute values.\");\n//\t\trow[test_combo_columns.self_test] = test_ioffline;\n//\t}\n\n\tauto test_short = std::make_shared<SelfTest>(drive_, SelfTest::TestType::ShortTest);\n\tif (test_short->is_supported()) {\n\t\trow = *(test_combo_model_->append());\n\t\trow[test_combo_columns_.name] = SelfTest::get_test_displayable_name(SelfTest::TestType::ShortTest);\n\t\trow[test_combo_columns_.description] =\n\t\t\t\t_(\"Short self-test consists of a collection of test routines that have the highest chance\"\n\t\t\t\t\" of detecting drive problems. Its result is reported in the Self-Test Log.\"\n\t\t\t\t\" Note that this test is in no way comprehensive. Its main purpose is to detect totally damaged\"\n\t\t\t\t\" drives without running a full surface scan.\"\n\t\t\t\t\"\\nNote: On some drives this actually runs several consequent tests, which may\"\n\t\t\t\t\" cause the program to display the test progress incorrectly.\");  // seagate multi-pass test on 7200.11.\n\t\trow[test_combo_columns_.self_test] = test_short;\n\t}\n\n\tauto test_long = std::make_shared<SelfTest>(drive_, SelfTest::TestType::LongTest);\n\tif (test_long->is_supported()) {\n\t\trow = *(test_combo_model_->append());\n\t\trow[test_combo_columns_.name] = SelfTest::get_test_displayable_name(SelfTest::TestType::LongTest);\n\t\trow[test_combo_columns_.description] =\n\t\t\t\t_(\"Extended self-test examines complete disk surface and performs various test routines\"\n\t\t\t\t\" built into the drive. Its result is reported in the Self-Test Log.\");\n\t\trow[test_combo_columns_.self_test] = test_long;\n\t}\n\n\tauto test_conveyance = std::make_shared<SelfTest>(drive_, SelfTest::TestType::Conveyance);\n\tif (test_conveyance->is_supported()) {\n\t\trow = *(test_combo_model_->append());\n\t\trow[test_combo_columns_.name] = SelfTest::get_test_displayable_name(SelfTest::TestType::Conveyance);\n\t\trow[test_combo_columns_.description] =\n\t\t\t\t_(\"Conveyance self-test is intended to identify damage incurred during transporting of the drive.\");\n\t\trow[test_combo_columns_.self_test] = test_conveyance;\n\t}\n\n\tif (!test_combo_model_->children().empty()) {\n\t\ttest_type_combo->set_sensitive(true);\n\t\ttest_type_combo->set_active(test_combo_model_->children().begin());  // select first entry\n\n\t\t// At least one test is possible, so enable test button.\n\t\t// Note: we disable only Execute button on virtual drives. The combo is left\n\t\t// sensitive so that the user can see which tests are supported by the drive.\n\t\tauto* test_execute_button = lookup_widget<Gtk::Button*>(\"test_execute_button\");\n\t\tif (test_execute_button)\n\t\t\ttest_execute_button->set_sensitive(!drive_->get_is_virtual());\n\t}\n}\n\n\n\nvoid GscInfoWindow::fill_ui_self_test_log(const StoragePropertyRepository& property_repo)\n{\n\tconst auto& props = property_repo.get_properties();\n\n\tauto* treeview = lookup_widget<Gtk::TreeView*>(\"selftest_log_treeview\");\n\n\tGtk::TreeModelColumnRecord model_columns;\n\t[[maybe_unused]] int num_tree_col = 0;\n\n\t// Test num., Type, Status, % Completed, Lifetime hours, LBA of the first error\n\n\tmodel_columns.add(columns_->self_test_log_table_columns.log_entry_index);  // we can use the column variable by value after this.\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->self_test_log_table_columns.log_entry_index, *treeview,\n\t\t\t_(\"Test #\"), _(\"Test # (greater may mean newer or older depending on drive model)\"), true);\n\n\tmodel_columns.add(columns_->self_test_log_table_columns.type);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->self_test_log_table_columns.type, *treeview,\n\t\t\t_(\"Type\"), _(\"Type of the test performed\"), true);\n\ttreeview->set_search_column(columns_->self_test_log_table_columns.type.index());\n\n\tmodel_columns.add(columns_->self_test_log_table_columns.status);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->self_test_log_table_columns.status, *treeview,\n\t\t\t_(\"Status\"), _(\"Test completion status\"), true);\n\n\tmodel_columns.add(columns_->self_test_log_table_columns.percent);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->self_test_log_table_columns.percent, *treeview,\n\t\t\t_(\"% Completed\"), _(\"Percentage of the test completed. Instantly-aborted tests have 10%, while unsupported ones <i>may</i> have 100%.\"), true, false, true);\n\n\tmodel_columns.add(columns_->self_test_log_table_columns.hours);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->self_test_log_table_columns.hours, *treeview,\n\t\t\t_(\"Lifetime hours\"), _(\"Hour of the drive's powered-on lifetime when the test completed or aborted.\\nThe value wraps after 65535 hours.\"), true);\n\n\tmodel_columns.add(columns_->self_test_log_table_columns.lba);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->self_test_log_table_columns.lba, *treeview,\n\t\t\t_(\"LBA of the first error\"), _(\"LBA of the first error (if an LBA-related error happened)\"), true);\n\n\tmodel_columns.add(columns_->self_test_log_table_columns.tooltip);\n\ttreeview->set_tooltip_column(columns_->self_test_log_table_columns.tooltip.index());\n\n\tmodel_columns.add(columns_->self_test_log_table_columns.storage_property);\n\n\n\t// create a TreeModel (ListStore)\n\tGlib::RefPtr<Gtk::ListStore> list_store = Gtk::ListStore::create(model_columns);\n\tlist_store->set_sort_column(columns_->self_test_log_table_columns.log_entry_index, Gtk::SORT_ASCENDING);  // default sort\n\ttreeview->set_model(list_store);\n\n\tfor (int i = 0; i < int(treeview->get_n_columns()); ++i) {\n\t\tGtk::TreeViewColumn* tcol = treeview->get_column(i);\n\t\ttcol->set_cell_data_func(*(tcol->get_first_cell()),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscInfoWindow::cell_renderer_for_self_test_log), i));\n\t}\n\n\n\tWarningLevel max_tab_warning = WarningLevel::None;\n\tstd::vector<PropertyLabel> label_strings;  // outside-of-tree properties\n\n\tbool ata_entries_found = false;\n\n\tfor (auto&& p : props) {\n\t\tif (p.section != StoragePropertySection::SelftestLog || !p.show_in_ui)\n\t\t\tcontinue;\n\n\t\tif (p.generic_name == \"ata_smart_self_test_log/_merged\")  // the whole section, we don't need it\n\t\t\tcontinue;\n\n\t\tif (p.is_value_type<AtaStorageSelftestEntry>()) {\n\t\t\tata_entries_found = true;\n\t\t}\n\n\t\t// add non-entry properties to label above\n\t\tif (!p.is_value_type<AtaStorageSelftestEntry>() && !p.is_value_type<NvmeStorageSelftestEntry>()) {\n\t\t\tlabel_strings.emplace_back(p.displayable_name + \": \" + p.format_value(), &p);\n\n\t\t\tif (int(p.warning_level) > int(max_tab_warning))\n\t\t\t\tmax_tab_warning = p.warning_level;\n\t\t\tcontinue;\n\t\t}\n\n\t\tGtk::TreeRow row = *(list_store->append());\n\n\t\tif (p.is_value_type<AtaStorageSelftestEntry>()) {\n\t\t\tconst auto& entry = p.get_value<AtaStorageSelftestEntry>();\n\t\t\trow[columns_->self_test_log_table_columns.log_entry_index] = entry.test_num;\n\t\t\trow[columns_->self_test_log_table_columns.type] = Glib::Markup::escape_text(entry.type);\n\t\t\trow[columns_->self_test_log_table_columns.status] = Glib::Markup::escape_text(entry.get_readable_status());\n\t\t\tif (entry.remaining_percent != -1) {  // only extended log supports this\n\t\t\t\trow[columns_->self_test_log_table_columns.percent] = Glib::Markup::escape_text(hz::number_to_string_locale(100 - entry.remaining_percent) + \"%\");\n\t\t\t}\n\t\t\trow[columns_->self_test_log_table_columns.hours] = Glib::Markup::escape_text(hz::number_to_string_locale(entry.lifetime_hours));\n\t\t\trow[columns_->self_test_log_table_columns.lba] = Glib::Markup::escape_text(entry.lba_of_first_error);\n\n\t\t} else if (p.is_value_type<NvmeStorageSelftestEntry>()) {\n\t\t\tconst auto& entry = p.get_value<NvmeStorageSelftestEntry>();\n\t\t\trow[columns_->self_test_log_table_columns.log_entry_index] = entry.test_num;\n\t\t\trow[columns_->self_test_log_table_columns.type] = Glib::Markup::escape_text(NvmeSelfTestTypeExt::get_displayable_name(entry.type));\n\t\t\trow[columns_->self_test_log_table_columns.status] = Glib::Markup::escape_text(NvmeSelfTestResultTypeExt::get_displayable_name(entry.result));\n\t\t\trow[columns_->self_test_log_table_columns.hours] = Glib::Markup::escape_text(hz::number_to_string_locale(entry.power_on_hours));\n\t\t\trow[columns_->self_test_log_table_columns.lba] = Glib::Markup::escape_text(entry.lba.has_value() ? hz::number_to_string_locale(entry.lba.value()) : std::string(\"-\"));\n\t\t}\n\t\t// There are no descriptions in self-test log entries, so don't display\n\t\t// \"No description available\" for all of them.\n\t\t// row[columns_->self_test_log_table_columns.tooltip] = p.get_description();\n\t\trow[columns_->self_test_log_table_columns.storage_property] = &p;\n\n\t\tif (int(p.warning_level) > int(max_tab_warning))\n\t\t\tmax_tab_warning = p.warning_level;\n\t}\n\n\t// Hide percentage column if NVMe as there is no such field in output.\n\ttreeview->get_column(3)->set_visible(ata_entries_found);  // % Completed\n\n\n\tauto* label_vbox = lookup_widget<Gtk::Box*>(\"selftest_log_label_vbox\");\n\tapp_set_top_labels(label_vbox, label_strings);\n\n\t// tab label\n\tapp_highlight_tab_label(lookup_widget(\"test_tab_label\"), max_tab_warning, tab_names_.test);\n}\n\n\n\nvoid GscInfoWindow::fill_ui_ata_error_log(const StoragePropertyRepository& property_repo)\n{\n\tconst auto& props = property_repo.get_properties();\n\n\tauto* treeview = lookup_widget<Gtk::TreeView*>(\"error_log_treeview\");\n\n\tGtk::TreeModelColumnRecord model_columns;\n\t[[maybe_unused]] int num_tree_col = 0;\n\n\t// Error Number, Lifetime Hours, State, Type, Details, [tooltips]\n\n\tmodel_columns.add(columns_->error_log_table_columns.log_entry_index);  // we can use the column variable by value after this.\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->error_log_table_columns.log_entry_index, *treeview,\n\t\t\t_(\"Error #\"), _(\"Error # in the error log (greater means newer)\"), true);\n\n\tmodel_columns.add(columns_->error_log_table_columns.hours);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->error_log_table_columns.hours, *treeview,\n\t\t\t_(\"Lifetime hours\"), _(\"Hour of the drive's powered-on lifetime when the error occurred\"), true);\n\n\tmodel_columns.add(columns_->error_log_table_columns.state);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->error_log_table_columns.state, *treeview,\n\t\t\tC_(\"power\", \"State\"), _(\"Power state of the drive when the error occurred\"), false);\n\n\tmodel_columns.add(columns_->error_log_table_columns.lba);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->error_log_table_columns.lba, *treeview,\n\t\t\t_(\"LBA\"), _(\"LBA Address\"), true);\n\n\tmodel_columns.add(columns_->error_log_table_columns.details);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->error_log_table_columns.details, *treeview,\n\t\t\t_(\"Details\"), _(\"Additional details\"), true);\n\n\tmodel_columns.add(columns_->error_log_table_columns.tooltip);\n\ttreeview->set_tooltip_column(columns_->error_log_table_columns.tooltip.index());\n\n\tmodel_columns.add(columns_->error_log_table_columns.storage_property);\n\n\tmodel_columns.add(columns_->error_log_table_columns.mark_name);\n\n\n\t// create a TreeModel (ListStore)\n\tGlib::RefPtr<Gtk::ListStore> list_store = Gtk::ListStore::create(model_columns);\n\tlist_store->set_sort_column(columns_->error_log_table_columns.log_entry_index, Gtk::SORT_DESCENDING);  // default sort\n\ttreeview->set_model(list_store);\n\n\tfor (int i = 0; i < int(treeview->get_n_columns()); ++i) {\n\t\tGtk::TreeViewColumn* tcol = treeview->get_column(i);\n\t\ttcol->set_cell_data_func(*(tcol->get_first_cell()),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscInfoWindow::cell_renderer_for_error_log), i));\n\t}\n\n\n\tWarningLevel max_tab_warning = WarningLevel::None;\n\tstd::vector<PropertyLabel> label_strings;  // outside-of-tree properties\n\n\tbool supports_details = false;\n\n\tfor (auto&& p : props) {\n\t\tif (p.section != StoragePropertySection::AtaErrorLog || !p.show_in_ui)\n\t\t\tcontinue;\n\n\t\t// Note: Don't use property description as a tooltip here. It won't be available if there's no property.\n\t\tif (p.generic_name == \"ata_smart_error_log/_merged\") {\n\t\t\tsupports_details = true;  // Text parser only\n\t\t\tif (auto* textview = lookup_widget<Gtk::TextView*>(\"error_log_textview\")) {\n\t\t\t\t// Add complete error log to textview window.\n\t\t\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\t\t\tif (buffer) {\n\t\t\t\t\tbuffer->set_text(\"\\n\" + Glib::ustring::compose(_(\"Complete error log: %1\"), \"\\n\\n\" + p.get_value<std::string>()));\n\n\t\t\t\t\t// Make the text monospace (the 3.16+ glade property does not work anymore for some reason).\n\t\t\t\t\tGlib::RefPtr<Gtk::TextTag> tag = buffer->create_tag();\n\t\t\t\t\ttag->property_family() = \"Monospace\";\n\t\t\t\t\tbuffer->apply_tag(tag, buffer->begin(), buffer->end());\n\n\t\t\t\t\t// Set marks so we can scroll to them\n\t\t\t\t\tif (!error_log_row_selected_conn_.connected()) {  // avoid double-connect\n\t\t\t\t\t\terror_log_row_selected_conn_ = treeview->get_selection()->signal_changed().connect(\n\t\t\t\t\t\t\t\tsigc::bind(sigc::bind(sigc::ptr_fun(on_error_log_treeview_row_selected), columns_->error_log_table_columns.mark_name), this));\n\t\t\t\t\t}\n\n\t\t\t\t\tGtk::TextIter titer = buffer->begin();\n\t\t\t\t\tGtk::TextIter match_start, match_end;\n\t\t\t\t\t// TODO Change this for json\n\t\t\t\t\twhile (titer.forward_search(\"\\nError \", Gtk::TEXT_SEARCH_TEXT_ONLY, match_start, match_end)) {\n\t\t\t\t\t\tmatch_start.forward_char();  // place after newline\n\t\t\t\t\t\tmatch_end.forward_word_end();  // include error number\n\t\t\t\t\t\ttiter = match_end;  // continue searching from here\n\n\t\t\t\t\t\tconst Glib::ustring mark_name = match_start.get_slice(match_end);  // e.g. \"Error 3\"\n\t\t\t\t\t\tbuffer->create_mark(mark_name, titer);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t\t// add non-tree properties to label above\n\t\t} else if (!p.is_value_type<AtaStorageErrorBlock>()) {\n\t\t\tlabel_strings.emplace_back(p.displayable_name + \": \" + p.format_value(), &p);\n\t\t\tif (p.generic_name == \"ata_smart_error_log/extended/count\")\n\t\t\t\tlabel_strings.back().label += \" \"s + _(\"(Note: The number of entries may be limited to the newest ones)\");\n\n\t\t} else {\n\t\t\tconst auto& eb = p.get_value<AtaStorageErrorBlock>();\n\n\t\t\tGtk::TreeRow row = *(list_store->append());\n\t\t\trow[columns_->error_log_table_columns.log_entry_index] = eb.error_num;\n\t\t\trow[columns_->error_log_table_columns.hours] = Glib::Markup::escape_text(hz::number_to_string_locale(eb.lifetime_hours));\n\t\t\trow[columns_->error_log_table_columns.state] = Glib::Markup::escape_text(eb.device_state);\n\n\t\t\tstd::string details_str = eb.type_more_info;  // parsed in JSON\n\t\t\tif (details_str.empty()) {\n\t\t\t\tdetails_str = AtaStorageErrorBlock::format_readable_error_types(eb.reported_types);  // parsed in Text\n\t\t\t}\n\n\t\t\trow[columns_->error_log_table_columns.lba] = Glib::Markup::escape_text(hz::number_to_string_locale(eb.lba));\n\t\t\trow[columns_->error_log_table_columns.details] = Glib::Markup::escape_text(details_str.empty() ? \"-\" : details_str);\n\t\t\trow[columns_->error_log_table_columns.tooltip] = p.get_description();  // markup\n\t\t\trow[columns_->error_log_table_columns.storage_property] = &p;\n\t\t\trow[columns_->error_log_table_columns.mark_name] = Glib::ustring::compose(_(\"Error %1\"), eb.error_num);\n\t\t}\n\n\t\tif (int(p.warning_level) > int(max_tab_warning))\n\t\t\tmax_tab_warning = p.warning_level;\n\t}\n\n\t// JSON parser does not support details, so hide the bottom area.\n\tauto* details_area = lookup_widget<Gtk::ScrolledWindow*>(\"error_log_details_scrolledwindow\");\n\tdetails_area->set_visible(supports_details);\n\n\tauto* label_vbox = lookup_widget<Gtk::Box*>(\"error_log_label_vbox\");\n\tapp_set_top_labels(label_vbox, label_strings);\n\n\t// inner tab label\n\tapp_highlight_tab_label(lookup_widget(\"error_log_tab_label\"), max_tab_warning, tab_names_.ata_error_log);\n}\n\n\n\nvoid GscInfoWindow::fill_ui_nvme_error_log(const StoragePropertyRepository& property_repo)\n{\n\tconst auto& props = property_repo.get_properties();\n\n\tauto* textview = lookup_widget<Gtk::TextView*>(\"nvme_error_log_textview\");\n\n\tWarningLevel max_tab_warning = WarningLevel::None;\n\n\tfor (auto&& p : props) {\n\t\tif (p.section != StoragePropertySection::NvmeErrorLog || !p.show_in_ui)\n\t\t\tcontinue;\n\n\t\t// Note: Don't use property description as a tooltip here. It won't be available if there's no property.\n\t\tif (p.generic_name == \"nvme_error_information_log/_merged\") {\n\t\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\t\tbuffer->set_text(\"\\n\" + Glib::ustring::compose(_(\"NVMe Non-Persistent Error Information Log: %1\"), \"\\n\\n\" + p.get_value<std::string>()));\n\n\t\t\t// Make the text monospace (the 3.16+ glade property does not work anymore for some reason).\n\t\t\tGlib::RefPtr<Gtk::TextTag> tag = buffer->create_tag();\n\t\t\ttag->property_family() = \"Monospace\";\n\t\t\tbuffer->apply_tag(tag, buffer->begin(), buffer->end());\n\t\t}\n\t}\n\n\t// tab label\n\tapp_highlight_tab_label(lookup_widget(\"nvme_error_log_tab_label\"), max_tab_warning, tab_names_.nvme_error_log);\n}\n\n\n\nvoid GscInfoWindow::fill_ui_temperature_log(const StoragePropertyRepository& property_repo)\n{\n\tconst auto& props = property_repo.get_properties();\n\n\tauto* textview = lookup_widget<Gtk::TextView*>(\"temperature_log_textview\");\n\n\tWarningLevel max_tab_warning = WarningLevel::None;\n\tstd::vector<PropertyLabel> label_strings;  // outside-of-tree properties\n\n\tstd::string temperature;\n\tStorageProperty temp_property;\n\tenum { temp_attr2 = 1, temp_attr1, temp_stat, temp_sct, temp_info };  // less important to more important\n\tint temp_prop_source = 0;\n\n\tfor (const auto& p : props) {\n\t\t// Find temperature\n\t\tif (temp_prop_source < temp_info && p.generic_name == \"temperature/current\") {  // Protocol-independent temperature\n\t\t\ttemperature = hz::number_to_string_locale(p.get_value<int64_t>());\n\t\t\ttemp_property = p;\n\t\t\ttemp_prop_source = temp_info;\n\t\t}\n\t\tif (temp_prop_source < temp_sct && p.generic_name == \"ata_sct_status/temperature/current\") {\n\t\t\ttemperature = hz::number_to_string_locale(p.get_value<int64_t>());\n\t\t\ttemp_property = p;\n\t\t\ttemp_prop_source = temp_sct;\n\t\t}\n\t\tif (temp_prop_source < temp_stat && p.generic_name == \"stat_temperature_celsius\") {\n\t\t\ttemperature = hz::number_to_string_locale(p.get_value<AtaStorageStatistic>().value_int);\n\t\t\ttemp_property = p;\n\t\t\ttemp_prop_source = temp_stat;\n\t\t}\n\t\tif (temp_prop_source < temp_attr1 && p.generic_name == \"attr_temperature_celsius\") {\n\t\t\t// Note: raw value may encode min/max as well, leading to very large values.\n\t\t\t// Instead, convert the string value (can be \"27\" or \"27 (Min/Max 11/59)\").\n\t\t\tstd::int64_t temp_int = 0;\n\t\t\tif (hz::string_is_numeric_nolocale(p.get_value<AtaStorageAttribute>().raw_value, temp_int, false)) {\n\t\t\t\ttemperature = hz::number_to_string_locale(temp_int);\n\t\t\t\ttemp_property = p;\n\t\t\t\ttemp_prop_source = temp_attr1;\n\t\t\t}\n\t\t}\n\t\tif (temp_prop_source < temp_attr2 && p.generic_name == \"attr_temperature_celsius_x10\") {\n\t\t\ttemperature = hz::number_to_string_locale(p.get_value<AtaStorageAttribute>().raw_value_int / 10);\n\t\t\ttemp_property = p;\n\t\t\ttemp_prop_source = temp_attr2;\n\t\t}\n\n\t\tif (p.section != StoragePropertySection::TemperatureLog || !p.show_in_ui)\n\t\t\tcontinue;\n\n\t\tif (p.generic_name == \"_text_only/ata_sct_status/_not_present\" && p.get_value<bool>()) {  // only show if unsupported\n\t\t\tlabel_strings.emplace_back(_(\"SCT temperature commands not supported.\"), &p);\n\t\t\tif (int(p.warning_level) > int(max_tab_warning))\n\t\t\t\tmax_tab_warning = p.warning_level;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Note: Don't use property description as a tooltip here. It won't be available if there's no property.\n\t\tif (p.generic_name == \"ata_sct_status/_and/ata_sct_temperature_history/_merged\") {\n\t\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\t\tbuffer->set_text(\"\\n\" + Glib::ustring::compose(_(\"Complete SCT temperature log: %1\"), \"\\n\\n\" + p.get_value<std::string>()));\n\n\t\t\t// Make the text monospace (the 3.16+ glade property does not work anymore for some reason).\n\t\t\tGlib::RefPtr<Gtk::TextTag> tag = buffer->create_tag();\n\t\t\ttag->property_family() = \"Monospace\";\n\t\t\tbuffer->apply_tag(tag, buffer->begin(), buffer->end());\n\t\t}\n\t}\n\n\tif (temperature.empty()) {\n\t\ttemperature = C_(\"value\", \"Unknown\");\n\t} else {\n\t\ttemperature = Glib::ustring::compose(C_(\"temperature\", \"%1° C\"), temperature);\n\t}\n\ttemp_property.set_description(_(\"Current drive temperature in Celsius.\"));  // overrides attribute description\n\tlabel_strings.emplace_back(Glib::ustring::compose(_(\"Current temperature: %1\"),\n\t\t\t\"<b>\" + Glib::Markup::escape_text(temperature) + \"</b>\"), &temp_property, true);\n\tif (int(temp_property.warning_level) > int(max_tab_warning))\n\t\tmax_tab_warning = temp_property.warning_level;\n\n\n\tauto* label_vbox = lookup_widget<Gtk::Box*>(\"temperature_log_label_vbox\");\n\tapp_set_top_labels(label_vbox, label_strings);\n\n\t// tab label\n\tapp_highlight_tab_label(lookup_widget(\"temperature_log_tab_label\"), max_tab_warning, tab_names_.temperature);\n}\n\n\n\nWarningLevel GscInfoWindow::fill_ui_capabilities(const StoragePropertyRepository& property_repo)\n{\n\tconst auto& props = property_repo.get_properties();\n\n\tauto* treeview = lookup_widget<Gtk::TreeView*>(\"capabilities_treeview\");\n\n\tGtk::TreeModelColumnRecord model_columns;\n\t[[maybe_unused]] int num_tree_col = 0;\n\n\t// N, Name, Flag, Capabilities, [tooltips]\n\n\tmodel_columns.add(columns_->capabilities_table_columns.entry_index);  // we can use the column variable by value after this.\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->capabilities_table_columns.entry_index, *treeview, _(\"#\"), _(\"Entry #\"), true);\n\n\tmodel_columns.add(columns_->capabilities_table_columns.name);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->capabilities_table_columns.name, *treeview, _(\"Name\"), _(\"Name\"), true);\n\ttreeview->set_search_column(columns_->capabilities_table_columns.name.index());\n\n\tmodel_columns.add(columns_->capabilities_table_columns.flag_value);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->capabilities_table_columns.flag_value, *treeview, _(\"Flags\"), _(\"Flags\"), false);\n//\ttreeview->get_column(num_tree_col - 1)->set_visible(false);  //\n\n\tmodel_columns.add(columns_->capabilities_table_columns.str_values);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->capabilities_table_columns.str_values, *treeview, _(\"Capabilities\"), _(\"Capabilities\"), false);\n\n\tmodel_columns.add(columns_->capabilities_table_columns.value);\n\tnum_tree_col = app_gtkmm_create_tree_view_column(columns_->capabilities_table_columns.value, *treeview, _(\"Value\"), _(\"Value\"), false);\n\n\tmodel_columns.add(columns_->capabilities_table_columns.tooltip);\n\ttreeview->set_tooltip_column(columns_->capabilities_table_columns.tooltip.index());\n\n\tmodel_columns.add(columns_->capabilities_table_columns.storage_property);\n\n\n\t// create a TreeModel (ListStore)\n\tGlib::RefPtr<Gtk::ListStore> list_store = Gtk::ListStore::create(model_columns);\n\tlist_store->set_sort_column(columns_->capabilities_table_columns.entry_index, Gtk::SORT_ASCENDING);  // default sort\n\ttreeview->set_model(list_store);\n\n\tfor (int i = 0; i < int(treeview->get_n_columns()); ++i) {\n\t\tGtk::TreeViewColumn* tcol = treeview->get_column(i);\n\t\ttcol->set_cell_data_func(*(tcol->get_first_cell()),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscInfoWindow::cell_renderer_for_capabilities), i));\n\t}\n\n\n\tWarningLevel max_tab_warning = WarningLevel::None;\n\tint index = 1;\n\n\tbool has_text_parser_capabilities = false;\n\n\tfor (auto&& p : props) {\n\t\tif (p.section != StoragePropertySection::Capabilities || !p.show_in_ui)\n\t\t\tcontinue;\n\n\t\tstd::string flag_value;\n\t\tGlib::ustring str_value;\n\n\t\tif (p.is_value_type<AtaStorageTextCapability>()) {\n\t\t\tflag_value = hz::number_to_string_nolocale(p.get_value<AtaStorageTextCapability>().flag_value, 16);  // 0xXX\n\t\t\tstr_value = hz::string_join(p.get_value<AtaStorageTextCapability>().strvalues, \"\\n\");\n\t\t\thas_text_parser_capabilities = true;\n\t\t} else {\n\t\t\t// no flag value here\n\t\t\tstr_value = p.format_value();\n\t\t}\n\n\t\tGtk::TreeRow row = *(list_store->append());\n\t\trow[columns_->capabilities_table_columns.entry_index] = index;\n\t\trow[columns_->capabilities_table_columns.name] = Glib::Markup::escape_text(p.displayable_name);\n\t\trow[columns_->capabilities_table_columns.flag_value] = Glib::Markup::escape_text(flag_value.empty() ? \"-\" : flag_value);\n\t\trow[columns_->capabilities_table_columns.str_values] = Glib::Markup::escape_text(str_value);\n\t\trow[columns_->capabilities_table_columns.value] = Glib::Markup::escape_text(str_value);\n\t\trow[columns_->capabilities_table_columns.tooltip] = p.get_description();  // markup\n\t\trow[columns_->capabilities_table_columns.storage_property] = &p;\n\n\t\tif (int(p.warning_level) > int(max_tab_warning))\n\t\t\tmax_tab_warning = p.warning_level;\n\n\t\t++index;\n\t}\n\n\t// Show/hide columns according to parser type.\n\ttreeview->get_column(2)->set_visible(has_text_parser_capabilities);  // flag_value\n\ttreeview->get_column(3)->set_visible(has_text_parser_capabilities);  // str_values\n\ttreeview->get_column(4)->set_visible(!has_text_parser_capabilities);  // value\n\n\t// tab label\n\tapp_highlight_tab_label(lookup_widget(\"capabilities_tab_label\"), max_tab_warning, tab_names_.capabilities);\n\n\treturn max_tab_warning;\n}\n\n\n\nWarningLevel GscInfoWindow::fill_ui_error_recovery(const StoragePropertyRepository& property_repo)\n{\n\tconst auto& props = property_repo.get_properties();\n\n\tauto* textview = lookup_widget<Gtk::TextView*>(\"erc_log_textview\");\n\n\tWarningLevel max_tab_warning = WarningLevel::None;\n\n\tfor (auto&& p : props) {\n\t\tif (p.section != StoragePropertySection::ErcLog || !p.show_in_ui)\n\t\t\tcontinue;\n\n\t\t// Note: Don't use property description as a tooltip here. It won't be available if there's no property.\n\t\tif (p.generic_name == \"ata_sct_erc/_merged\") {\n\t\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\t\tbuffer->set_text(\"\\n\" + Glib::ustring::compose(_(\"Complete SCT Error Recovery Control settings: %1\"), \"\\n\\n\" + p.get_value<std::string>()));\n\n\t\t\t// Make the text monospace (the 3.16+ glade property does not work anymore for some reason).\n\t\t\tGlib::RefPtr<Gtk::TextTag> tag = buffer->create_tag();\n\t\t\ttag->property_family() = \"Monospace\";\n\t\t\tbuffer->apply_tag(tag, buffer->begin(), buffer->end());\n\t\t}\n\t}\n\n\t// tab label\n\tapp_highlight_tab_label(lookup_widget(\"erc_tab_label\"), max_tab_warning, tab_names_.erc);\n\n\treturn max_tab_warning;\n}\n\n\n\nWarningLevel GscInfoWindow::fill_ui_selective_self_test_log(const StoragePropertyRepository& property_repo)\n{\n\tconst auto& props = property_repo.get_properties();\n\n\tauto* textview = lookup_widget<Gtk::TextView*>(\"selective_selftest_log_textview\");\n\n\tWarningLevel max_tab_warning = WarningLevel::None;\n\n\tfor (auto&& p : props) {\n\t\tif (p.section != StoragePropertySection::SelectiveSelftestLog || !p.show_in_ui)\n\t\t\tcontinue;\n\n\t\t// Note: Don't use property description as a tooltip here. It won't be available if there's no property.\n\t\tif (p.generic_name == \"ata_smart_selective_self_test_log/_merged\") {\n\t\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\t\tbuffer->set_text(\"\\n\" + Glib::ustring::compose(_(\"Complete selective self-test log: %1\"), \"\\n\\n\" + p.get_value<std::string>()));\n\n\t\t\t// Make the text monospace (the 3.16+ glade property does not work anymore for some reason).\n\t\t\tGlib::RefPtr<Gtk::TextTag> tag = buffer->create_tag();\n\t\t\ttag->property_family() = \"Monospace\";\n\t\t\tbuffer->apply_tag(tag, buffer->begin(), buffer->end());\n\t\t}\n\t}\n\n\t// tab label\n\tapp_highlight_tab_label(lookup_widget(\"selective_selftest_tab_label\"), max_tab_warning, tab_names_.selective_selftest);\n\n\treturn max_tab_warning;\n}\n\n\n\nWarningLevel GscInfoWindow::fill_ui_physical(const StoragePropertyRepository& property_repo)\n{\n\tconst auto& props = property_repo.get_properties();\n\n\tauto* textview = lookup_widget<Gtk::TextView*>(\"phy_log_textview\");\n\n\tWarningLevel max_tab_warning = WarningLevel::None;\n\n\tfor (auto&& p : props) {\n\t\tif (p.section != StoragePropertySection::PhyLog || !p.show_in_ui)\n\t\t\tcontinue;\n\n\t\t// Note: Don't use property description as a tooltip here. It won't be available if there's no property.\n\t\tif (p.generic_name == \"sata_phy_event_counters/_merged\") {\n\t\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\t\tbuffer->set_text(\"\\n\" + Glib::ustring::compose(_(\"Complete phy log: %1\"), \"\\n\\n\" + p.get_value<std::string>()));\n\n\t\t\t// Make the text monospace (the 3.16+ glade property does not work anymore for some reason).\n\t\t\tGlib::RefPtr<Gtk::TextTag> tag = buffer->create_tag();\n\t\t\ttag->property_family() = \"Monospace\";\n\t\t\tbuffer->apply_tag(tag, buffer->begin(), buffer->end());\n\t\t}\n\t}\n\n\t// tab label\n\tapp_highlight_tab_label(lookup_widget(\"phy_tab_label\"), max_tab_warning, tab_names_.phy);\n\n\treturn max_tab_warning;\n}\n\n\n\nWarningLevel GscInfoWindow::fill_ui_directory(const StoragePropertyRepository& property_repo)\n{\n\tconst auto& props = property_repo.get_properties();\n\n\tauto* textview = lookup_widget<Gtk::TextView*>(\"directory_log_textview\");\n\n\tWarningLevel max_tab_warning = WarningLevel::None;\n\n\tfor (auto&& p : props) {\n\t\tif (p.section != StoragePropertySection::DirectoryLog || !p.show_in_ui)\n\t\t\tcontinue;\n\n\t\t// Note: Don't use property description as a tooltip here. It won't be available if there's no property.\n\t\tif (p.generic_name == \"ata_log_directory/_merged\") {\n\t\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\t\t\tbuffer->set_text(\"\\n\" + Glib::ustring::compose(_(\"Complete directory log: %1\"), \"\\n\\n\" + p.get_value<std::string>()));\n\n\t\t\t// Make the text monospace (the 3.16+ glade property does not work anymore for some reason).\n\t\t\tGlib::RefPtr<Gtk::TextTag> tag = buffer->create_tag();\n\t\t\ttag->property_family() = \"Monospace\";\n\t\t\tbuffer->apply_tag(tag, buffer->begin(), buffer->end());\n\t\t}\n\t}\n\n\t// tab label\n\tapp_highlight_tab_label(lookup_widget(\"directory_tab_label\"), max_tab_warning, tab_names_.directory);\n\n\treturn max_tab_warning;\n}\n\n\n\n/// Set cell renderer's foreground and background colors according to property warning level.\ninline void cell_renderer_set_warning_fg_bg(Gtk::CellRendererText* crt, const StorageProperty& p)\n{\n\tstd::string fg, bg;\n\tif (app_property_get_row_highlight_colors(gui_is_dark_theme_active(), p.warning_level, fg, bg)) {\n\t\t// Note: property_cell_background makes horizontal tree lines disappear around it,\n\t\t// but property_background doesn't play nice with sorted column color.\n\t\tcrt->property_cell_background() = bg;\n\t\tcrt->property_foreground() = fg;\n\t} else {\n\t\t// this is needed because cellrenderer is shared in column, so the previous call\n\t\t// may set the color for all subsequent cells.\n\t\tcrt->property_cell_background().reset_value();\n\t\tcrt->property_foreground().reset_value();\n\t}\n}\n\n\n\nvoid GscInfoWindow::cell_renderer_for_ata_attributes(Gtk::CellRenderer* cr,\n\t\tconst Gtk::TreeModel::iterator& iter, [[maybe_unused]] int column_index) const\n{\n\tconst StorageProperty* prop = (*iter)[columns_->ata_attribute_table_columns.storage_property];\n\tif (!prop) {\n\t\treturn;\n\t}\n\tconst auto& attribute = prop->get_value<AtaStorageAttribute>();\n\n\tif (auto* crt = dynamic_cast<Gtk::CellRendererText*>(cr)) {\n\t\tcell_renderer_set_warning_fg_bg(crt, *prop);\n\n\t\tif (column_index == columns_->ata_attribute_table_columns.displayable_name.index()) {\n\t\t\tcrt->property_weight() = Pango::WEIGHT_BOLD;\n\t\t}\n\t\tif (column_index == columns_->ata_attribute_table_columns.type.index()) {\n\t\t\tif (attribute.attr_type == AtaStorageAttribute::AttributeType::Prefail) {\n\t\t\t\tcrt->property_weight() = Pango::WEIGHT_BOLD;\n\t\t\t} else {  // reset to default value if reloading\n\t\t\t\tcrt->property_weight().reset_value();\n\t\t\t}\n\t\t}\n\t\tif (column_index == columns_->ata_attribute_table_columns.when_failed.index()) {\n\t\t\tif (attribute.when_failed != AtaStorageAttribute::FailTime::None) {\n\t\t\t\tcrt->property_weight() = Pango::WEIGHT_BOLD;\n\t\t\t} else {  // reset to default value if reloading\n\t\t\t\t// Do not use WEIGHT_NORMAL here, it interferes with cell markup\n\t\t\t\tcrt->property_weight().reset_value();\n\t\t\t}\n\t\t}\n\n\t\t// Monospace, align all numeric values\n\t\tif (column_index == columns_->ata_attribute_table_columns.normalized_value.index()\n\t\t\t\t|| column_index == columns_->ata_attribute_table_columns.worst.index()\n\t\t\t\t|| column_index == columns_->ata_attribute_table_columns.threshold.index()\n\t\t\t\t|| column_index == columns_->ata_attribute_table_columns.raw.index() ) {\n\t\t\tcrt->property_family() = \"Monospace\";\n\t\t\tcrt->property_xalign() = 1.;  // right-align\n\t\t}\n\t\tif (column_index == columns_->ata_attribute_table_columns.id.index()\n\t\t\t\t|| column_index == columns_->ata_attribute_table_columns.flag_value.index()) {\n\t\t\tcrt->property_family() = \"Monospace\";\n\t\t\tcrt->property_xalign() = .5;  // center-align\n\t\t}\n\t\tif (column_index == columns_->ata_attribute_table_columns.type.index()) {\n\t\t\tcrt->property_xalign() = .5;  // center-align\n\t\t}\n\t}\n}\n\n\n\nvoid GscInfoWindow::cell_renderer_for_nvme_attributes(Gtk::CellRenderer* cr,\n\t\tconst Gtk::TreeModel::iterator& iter, int column_index) const\n{\n\tconst StorageProperty* prop = (*iter)[this->columns_->nvme_attribute_table_columns.storage_property];\n\tif (!prop) {\n\t\treturn;\n\t}\n\n\tif (auto* crt = dynamic_cast<Gtk::CellRendererText*>(cr)) {\n\t\tcell_renderer_set_warning_fg_bg(crt, *prop);\n\n\t\tif (column_index == columns_->nvme_attribute_table_columns.displayable_name.index()) {\n\t\t\tcrt->property_weight() = Pango::WEIGHT_BOLD;\n\t\t}\n\n\t\tif (column_index == columns_->nvme_attribute_table_columns.value.index()) {\n\t\t\tcrt->property_family() = \"Monospace\";\n\t\t\tcrt->property_xalign() = 1.;  // right-align\n\t\t}\n\t}\n}\n\n\n\nvoid GscInfoWindow::cell_renderer_for_statistics(Gtk::CellRenderer* cr,\n\t\tconst Gtk::TreeModel::iterator& iter, [[maybe_unused]] int column_index) const\n{\n\tconst StorageProperty* prop = (*iter)[this->columns_->statistics_table_columns.storage_property];\n\tif (!prop) {\n\t\treturn;\n\t}\n\tconst auto& statistic = prop->get_value<AtaStorageStatistic>();\n\n\tif (auto* crt = dynamic_cast<Gtk::CellRendererText*>(cr)) {\n\t\tcell_renderer_set_warning_fg_bg(crt, *prop);\n\n\t\tif (statistic.is_header) {  // subheader\n\t\t\tcrt->property_weight() = Pango::WEIGHT_BOLD;\n\t\t} else {  // reset to default value if reloading\n\t\t\tcrt->property_weight().reset_value();\n\t\t}\n\n\t\t// Monospace, align all numeric values\n\t\tif (column_index == columns_->statistics_table_columns.value.index()) {\n\t\t\tcrt->property_family() = \"Monospace\";\n\t\t\tcrt->property_xalign() = 1.;  // right-align\n\t\t}\n\t\tif (column_index == columns_->statistics_table_columns.flags.index()\n\t\t\t\t|| column_index == columns_->statistics_table_columns.page_offset.index() ) {\n\t\t\tcrt->property_family() = \"Monospace\";\n\t\t\tcrt->property_xalign() = .5;  // center-align\n\t\t}\n\t}\n}\n\n\n\nvoid GscInfoWindow::cell_renderer_for_self_test_log(Gtk::CellRenderer* cr,\n\t\tconst Gtk::TreeModel::iterator& iter, [[maybe_unused]] int column_index) const\n{\n\tconst StorageProperty* prop = (*iter)[this->columns_->self_test_log_table_columns.storage_property];\n\tif (!prop) {\n\t\treturn;\n\t}\n\n\tif (auto* crt = dynamic_cast<Gtk::CellRendererText*>(cr)) {\n\t\tcell_renderer_set_warning_fg_bg(crt, *prop);\n\n\t\tif (column_index == columns_->self_test_log_table_columns.log_entry_index.index()) {\n\t\t\tcrt->property_weight() = Pango::WEIGHT_BOLD;\n\t\t}\n\n\t\t// Monospace, align all numeric values\n\t\tif (column_index == columns_->self_test_log_table_columns.log_entry_index.index()\n\t\t\t\t|| column_index == columns_->self_test_log_table_columns.percent.index()\n\t\t\t\t|| column_index == columns_->self_test_log_table_columns.hours.index() ) {\n\t\t\tcrt->property_family() = \"Monospace\";\n\t\t\tcrt->property_xalign() = 1.;  // right-align\n\t\t}\n\t\tif (column_index == columns_->self_test_log_table_columns.log_entry_index.index()) {\n\t\t\tcrt->property_family() = \"Monospace\";\n\t\t\tcrt->property_xalign() = .5;  // center-align\n\t\t}\n\t\tif (column_index == columns_->self_test_log_table_columns.lba.index()) {\n\t\t\tcrt->property_family() = \"Monospace\";\n\t\t}\n\t}\n}\n\n\n\nvoid GscInfoWindow::cell_renderer_for_error_log(Gtk::CellRenderer* cr,\n\t\tconst Gtk::TreeModel::iterator& iter, [[maybe_unused]] int column_index) const\n{\n\tconst StorageProperty* prop = (*iter)[this->columns_->error_log_table_columns.storage_property];\n\tif (!prop) {\n\t\treturn;\n\t}\n\n\tif (auto* crt = dynamic_cast<Gtk::CellRendererText*>(cr)) {\n\t\tcell_renderer_set_warning_fg_bg(crt, *prop);\n\n\t\tif (column_index == columns_->error_log_table_columns.log_entry_index.index()) {\n\t\t\tcrt->property_weight() = Pango::WEIGHT_BOLD;\n\t\t}\n\n\t\t// Monospace, align all numeric values\n\t\tif (column_index == columns_->error_log_table_columns.log_entry_index.index()) {\n\t\t\tcrt->property_family() = \"Monospace\";\n\t\t\tcrt->property_xalign() = .5;  // center-align\n\t\t}\n\t\tif (column_index == columns_->error_log_table_columns.hours.index()) {\n\t\t\tcrt->property_family() = \"Monospace\";\n\t\t\tcrt->property_xalign() = 1.;  // right-align\n\t\t}\n\t}\n}\n\n\n\nvoid GscInfoWindow::cell_renderer_for_capabilities(Gtk::CellRenderer* cr,\n\t\tconst Gtk::TreeModel::iterator& iter, [[maybe_unused]] int column_index) const\n{\n\tconst StorageProperty* prop = (*iter)[this->columns_->capabilities_table_columns.storage_property];\n\tif (!prop) {\n\t\treturn;\n\t}\n\n\tif (auto* crt = dynamic_cast<Gtk::CellRendererText*>(cr)) {\n\t\tcell_renderer_set_warning_fg_bg(crt, *prop);\n\n\t\tif (column_index == columns_->capabilities_table_columns.name.index()) {\n\t\t\tcrt->property_weight() = Pango::WEIGHT_BOLD;\n\t\t}\n\n\t\t// Monospace, align all numeric values\n\t\tif (column_index == columns_->capabilities_table_columns.entry_index.index()\n\t\t\t\t|| column_index == columns_->capabilities_table_columns.flag_value.index() ) {\n\t\t\tcrt->property_family() = \"Monospace\";\n\t\t\tcrt->property_xalign() = .5;  // center-align\n\t\t}\n\t}\n}\n\n\n\n// Note: Another loop like this may run inside it for another drive.\ngboolean GscInfoWindow::test_idle_callback(void* data)\n{\n\tauto* self = static_cast<GscInfoWindow*>(data);\n\tDBG_ASSERT_RETURN(self, false);\n\n\tif (!self->current_test_)  // shouldn't happen\n\t\treturn FALSE;  // stop\n\n\tauto* test_completion_progressbar =\n\t\t\tself->lookup_widget<Gtk::ProgressBar*>(\"test_completion_progressbar\");\n\n\n\tbool active = true;\n\n\tdo {  // goto\n\t\tif (!self->current_test_->is_active()) {  // check status\n\t\t\tactive = false;\n\t\t\tbreak;\n\t\t}\n\n\t\tconst std::int8_t rem_percent = self->current_test_->get_remaining_percent();\n\t\tconst std::string rem_percent_str = (rem_percent == -1 ? C_(\"value\", \"Unknown\") : hz::number_to_string_locale(100 - rem_percent));\n\n\t\tauto poll_in = self->current_test_->get_poll_in_seconds();  // sec\n\n\n\t\t// One update() is performed by start(), so do the timeout first.\n\n\t\t// Wait until next poll (up to several minutes). Meanwhile, interpolate\n\t\t// the remaining time, update the progressbar, etc.\n\t\tif (self->test_timer_poll_.elapsed() < static_cast<double>(poll_in.count())) {  // elapsed() is seconds in double.\n\n\t\t\t// Update progress bar right after poll, plus every 5 seconds.\n\t\t\tif (self->test_force_bar_update_ || self->test_timer_bar_.elapsed() >= 5.) {\n\n\t\t\t\tauto rem_seconds = self->current_test_->get_remaining_seconds();\n\n\t\t\t\tif (test_completion_progressbar) {\n\t\t\t\t\tconst std::string rem_seconds_str = (rem_seconds == std::chrono::seconds(-1) ? C_(\"duration\", \"Unknown\") : hz::format_time_length(rem_seconds));\n\n\t\t\t\t\tGlib::ustring bar_str;\n\n\t\t\t\t\tif (self->test_error_msg_.empty()) {\n\t\t\t\t\t\tbar_str = Glib::ustring::compose(_(\"Test completion: %1%%; ETA: %2\"), rem_percent_str, rem_seconds_str);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbar_str = self->test_error_msg_;  // better than popup every few seconds\n\t\t\t\t\t}\n\n\t\t\t\t\ttest_completion_progressbar->set_text(bar_str);\n\t\t\t\t\ttest_completion_progressbar->set_fraction(std::max(0., std::min(1., 1. - (rem_percent / 100.))));\n\t\t\t\t}\n\n\t\t\t\tself->test_force_bar_update_ = false;\n\t\t\t\tself->test_timer_bar_.start();  // restart\n\t\t\t}\n\n\t\t\tif (!self->current_test_->is_active()) {  // check status\n\t\t\t\tactive = false;\n\t\t\t\tbreak;\n\t\t\t}\n\n\n\t\t} else {  // it's poll time\n\n\t\t\tif (!self->current_test_->is_active()) {  // the inner loop stopped, stop this one too\n\t\t\t\tactive = false;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tstd::shared_ptr<SmartctlExecutorGui> ex(new SmartctlExecutorGui());\n\t\t\tex->create_running_dialog(self);\n\n\t\t\tauto test_status = self->current_test_->update(ex);\n\t\t\tself->test_error_msg_ = (!test_status ? test_status.error().message() : \"\");\n\t\t\tif (!self->test_error_msg_.empty()) {\n// \t\t\t\tgui_show_error_dialog(\"Cannot monitor test progress\", self->test_error_msg, this);  // better show in progressbar.\n\t\t\t\t[[maybe_unused]] auto result = self->current_test_->force_stop(ex);  // what else can we do?\n\t\t\t\tactive = false;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tself->test_timer_poll_.start();  // restart it\n\t\t\tself->test_force_bar_update_ = true;  // force progressbar / ETA update on the next tick\n\t\t}\n\n\n\t} while (false);\n\n\n\tif (active) {\n\t\treturn TRUE;  // continue the idle callback\n\t}\n\n\n\t// Test is finished, clean up\n\n\tself->test_timer_poll_.stop();  // just in case\n\tself->test_timer_bar_.stop();  // just in case\n\n\tauto status = self->current_test_->get_status();\n\n\tbool aborted = false;\n\tSelfTestStatusSeverity severity = SelfTestStatusSeverity::None;\n\tstd::string result_details_msg;\n\n\tif (!self->test_error_msg_.empty()) {\n\t\taborted = true;\n\t\tseverity = SelfTestStatusSeverity::Error;\n\t\tresult_details_msg = Glib::ustring::compose(_(\"<b>Test aborted: %1</b>\"), Glib::Markup::escape_text(self->test_error_msg_));\n\n\t} else {\n\t\tseverity = get_self_test_status_severity(status);\n\t\tif (status == SelfTestStatus::ManuallyAborted) {\n\t\t\taborted = true;\n\t\t\tresult_details_msg = \"<b>\"s + _(\"Test was manually aborted.\") + \"</b>\";  // it's a StatusSeverity::none message\n\n\t\t} else {\n\t\t\tresult_details_msg = Glib::ustring::compose(_(\"<b>Test result: %1</b>.\"),\n\t\t\t\t\tGlib::Markup::escape_text(SelfTestStatusExt::get_displayable_name(status)));\n\n\t\t\t// It may not reach 100% somehow, so do it manually.\n\t\t\tif (test_completion_progressbar)\n\t\t\t\ttest_completion_progressbar->set_fraction(1.);  // yes, we're at 100% anyway (at least logically).\n\t\t}\n\t}\n\n\tstd::string result_main_msg;\n\tif (aborted) {\n\t\tresult_main_msg = _(\"TEST ABORTED!\");\n\t} else {\n\t\tswitch (status) {\n\t\t\tcase SelfTestStatus::Unknown:\n\t\t\t\tresult_main_msg = _(\"TEST STATUS UNKNOWN.\");\n\t\t\t\tbreak;\n\t\t\tcase SelfTestStatus::InProgress:\n\t\t\t\tresult_main_msg = _(\"TEST IN PROGRESS.\");\n\t\t\t\tbreak;\n\t\t\tcase SelfTestStatus::ManuallyAborted:\n\t\t\t\tresult_main_msg = _(\"TEST ABORTED!\");\n\t\t\t\tbreak;\n\t\t\tcase SelfTestStatus::Interrupted:\n\t\t\t\tresult_main_msg = _(\"TEST INTERRUPTED!\");\n\t\t\t\tbreak;\n\t\t\tcase SelfTestStatus::CompletedNoError:\n\t\t\t\tresult_main_msg = _(\"TEST SUCCESSFUL.\");\n\t\t\t\tbreak;\n\t\t\tcase SelfTestStatus::CompletedWithError:\n\t\t\t\tresult_main_msg = _(\"TEST FAILED!\");\n\t\t\t\tbreak;\n\t\t\tcase SelfTestStatus::Reserved:\n\t\t\t\tresult_main_msg = _(\"TEST STATUS UNKNOWN.\");\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tswitch (severity) {\n\t\tcase SelfTestStatusSeverity::None:\n\t\t\tbreak;\n\t\tcase SelfTestStatusSeverity::Warning:\n\t\t\tresult_details_msg = \"\\n\"s + _(\"Check the Self-Test Log for more information.\");\n\t\t\tbreak;\n\t\tcase SelfTestStatusSeverity::Error:\n\t\t\tif (!result_main_msg.empty()) {  // Highlight in red\n\t\t\t\tstd::string alert_color;\n\t\t\t\t// Use the same color as Alert level warnings for consistency\n\t\t\t\tif (app_property_get_label_highlight_color(gui_is_dark_theme_active(), WarningLevel::Alert, alert_color) && !alert_color.empty()) {\n\t\t\t\t\tresult_main_msg = \"<span color=\\\"\" + alert_color + \"\\\">\"s + result_main_msg + \"</span>\";\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult_details_msg += \"\\n\"s + _(\"Check the Self-Test Log for more information.\");\n\t\t\tbreak;\n\t}\n\n\tif (!result_main_msg.empty()) {\n\t\tresult_main_msg = \"<b>\"s + result_main_msg + \"</b>\\n\";\n\t}\n\tstd::string result_msg = result_main_msg + result_details_msg;\n\n\n\tif (auto* test_type_combo = self->lookup_widget<Gtk::ComboBox*>(\"test_type_combo\"))\n\t\ttest_type_combo->set_sensitive(true);\n\n\tif (auto* test_execute_button = self->lookup_widget<Gtk::Button*>(\"test_execute_button\"))\n\t\ttest_execute_button->set_sensitive(true);\n\n\tif (test_completion_progressbar) {\n\t\ttest_completion_progressbar->set_text(\"\");\n\t}\n\n\tif (auto* test_stop_button = self->lookup_widget<Gtk::Button*>(\"test_stop_button\"))\n\t\ttest_stop_button->set_sensitive(false);\n\n\tstd::string icon_name = \"dialog-error\";\n\tif (severity == SelfTestStatusSeverity::None) {\n\t\ticon_name = \"dialog-information\";\n\t} else if (severity == SelfTestStatusSeverity::Warning) {\n\t\ticon_name = \"dialog-warning\";\n\t}\n\n\t// we use large icon size here because the icons we use are from dialogs.\n\t// unfortunately, there are no non-dialog icons of this sort.\n\tif (auto* test_result_image = self->lookup_widget<Gtk::Image*>(\"test_result_image\")) {\n\t\ttest_result_image->set_from_icon_name(icon_name, Gtk::ICON_SIZE_DND);\n\t}\n\n\tif (auto* test_result_label = self->lookup_widget<Gtk::Label*>(\"test_result_label\")) {\n\t\ttest_result_label->set_markup(result_msg);\n\t}\n\n\tif (auto* test_result_hbox = self->lookup_widget<Gtk::Box*>(\"test_result_hbox\")) {\n\t\ttest_result_hbox->show();\n\t}\n\n\tself->refresh_info(false);  // don't clear the tests tab\n\n\treturn FALSE;  // stop idle callback\n}\n\n\n\n\n\nvoid GscInfoWindow::on_test_execute_button_clicked()\n{\n\tauto* test_type_combo = lookup_widget<Gtk::ComboBox*>(\"test_type_combo\");\n\tDBG_ASSERT_RETURN_NONE(test_type_combo);\n\n\tGtk::TreeRow row = *(test_type_combo->get_active());\n\tif (!row)\n\t\treturn;\n\n\tstd::shared_ptr<SelfTest> test_from_combo = row[test_combo_columns_.self_test];\n\tauto test = std::make_shared<SelfTest>(drive_, test_from_combo->get_test_type());\n\tif (!test)\n\t\treturn;\n\n\t// hide previous test results from GUI\n\tif (auto* test_result_hbox = this->lookup_widget<Gtk::Box*>(\"test_result_hbox\"))\n\t\ttest_result_hbox->hide();\n\n\tstd::shared_ptr<SmartctlExecutorGui> ex(new SmartctlExecutorGui());\n\tex->create_running_dialog(this);\n\n\tauto test_status = test->start(ex);  // this runs update() too.\n\tif (!test_status) {\n\t\t/// Translators: %1 is test name\n\t\tgui_show_error_dialog(Glib::ustring::compose(_(\"Cannot run %1\"),\n\t\t\t\tSelfTest::get_test_displayable_name(test->get_test_type())), test_status.error().message(), this);\n\t\treturn;\n\t}\n\n\tcurrent_test_ = test;\n\n\n\t// Switch GUI to \"running test\" mode\n\ttest_type_combo->set_sensitive(false);\n\n\tif (auto* test_execute_button = lookup_widget<Gtk::Button*>(\"test_execute_button\"))\n\t\ttest_execute_button->set_sensitive(false);\n\n\tif (auto* test_completion_progressbar = lookup_widget<Gtk::ProgressBar*>(\"test_completion_progressbar\")) {\n\t\ttest_completion_progressbar->set_text(\"\");\n\t\ttest_completion_progressbar->set_sensitive(true);\n\t\ttest_completion_progressbar->show();\n\t}\n\n\tif (auto* test_stop_button = lookup_widget<Gtk::Button*>(\"test_stop_button\")) {\n\t\ttest_stop_button->set_sensitive(true);  // true while test is active\n\t\ttest_stop_button->show();\n\t}\n\n\n\t// reset these\n\ttest_error_msg_.clear();\n\ttest_timer_poll_.start();\n\ttest_timer_bar_.start();\n\ttest_force_bar_update_ = true;\n\n\n\t// We don't use idle function here, because it has the following problem:\n\t// CommandExecutor::execute() (which is called on force_stop()) calls g_main_context_pending(),\n\t// which returns true EVERY time, until the idle callback returns false.\n\t// So, force_stop() exits its \"execute abort\" command only when the\n\t// idle callback polls the drive on the next timeout and sees that the test\n\t// has been actually aborted.\n\t// So, we use timeout callback - and hope that there are no usleeps\n\t// with 300ms so that g_main_context_pending() returns false at least once,\n\t// to escape the execute() loop.\n\n// \tg_idle_add(test_idle_callback, this);\n\tg_timeout_add(300, test_idle_callback, this);  // every 300ms\n}\n\n\n\n\nvoid GscInfoWindow::on_test_stop_button_clicked()\n{\n\tif (!current_test_)\n\t\treturn;\n\n\tstd::shared_ptr<SmartctlExecutorGui> ex(new SmartctlExecutorGui());\n\tex->create_running_dialog(this);\n\n\tauto test_status = current_test_->force_stop(ex);\n\tif (!test_status) {\n\t\t/// Translators: %1 is test name\n\t\tgui_show_error_dialog(Glib::ustring::compose(_(\"Cannot stop %1\"),\n\t\t\t\tSelfTest::get_test_displayable_name(current_test_->get_test_type())), test_status.error().message(), this);\n\t\treturn;\n\t}\n\n\t// nothing else to do - the cleanup is performed by the idle callback.\n}\n\n\n\n\n// Callback attached to StorageDevice.\n// We don't refresh automatically (that would make it impossible to do\n// several same-drive info window comparisons side by side).\n// But we need to look for testing status change, to avoid aborting it.\nvoid GscInfoWindow::on_drive_changed([[maybe_unused]] StorageDevice* pdrive)\n{\n\tif (!drive_)\n\t\treturn;\n\tconst bool test_active = drive_->get_test_is_active();\n\n\t// disable refresh button if test is active or if it's a virtual drive\n\tif (auto* refresh_info_button = lookup_widget<Gtk::Button*>(\"refresh_info_button\"))\n\t\trefresh_info_button->set_sensitive(!test_active && !drive_->get_is_virtual());\n\n\t// disallow close. usually modal dialogs are used for this, but we can't have\n\t// per-drive modal dialogs.\n\tif (auto* close_window_button = lookup_widget<Gtk::Button*>(\"close_window_button\"))\n\t\tclose_window_button->set_sensitive(!test_active);\n\n\t// test_active is also checked in delete_event handler, because this call may not\n\t// succeed - the window manager may refuse to do it.\n\tthis->set_deletable(!test_active);\n}\n\n\n\nbool GscInfoWindow::on_treeview_button_press_event(GdkEventButton* button_event, Gtk::Menu* menu, Gtk::TreeView* treeview)\n{\n\tif (button_event->type == GDK_BUTTON_PRESS && button_event->button == 3) {\n\t\tconst bool selection_empty = treeview->get_selection()->get_selected_rows().empty();\n\t\tstd::vector<Widget*> children = menu->get_children();\n\t\tfor (auto& child : children) {\n\t\t\tchild->set_sensitive(!selection_empty);\n\t\t}\n\t\tmenu->popup(button_event->button, button_event->time);\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n\n\nvoid GscInfoWindow::on_treeview_menu_copy_clicked(Gtk::TreeView* treeview)\n{\n\tstd::string text;\n\n\tconst int num_cols = static_cast<int>(treeview->get_n_columns());\n\tstd::vector<std::string> col_texts;\n\tfor (int i = 0; i < num_cols; ++i) {\n\t\tGtk::TreeViewColumn* tcol = treeview->get_column(i);\n\t\tcol_texts.push_back(\"\\\"\" + hz::string_replace_copy(tcol->get_title(), \"\\\"\", \"\\\"\\\"\") + \"\\\"\");\n\t}\n\ttext += hz::string_join(col_texts, ',') + \"\\n\";\n\n\tauto selection = treeview->get_selection()->get_selected_rows();\n\tauto list_store = Glib::RefPtr<Gtk::ListStore>::cast_dynamic(treeview->get_model());\n\tfor (const auto& path : selection) {\n\t\tstd::vector<std::string> cell_texts;\n\t\tGtk::TreeRow row = *(list_store->get_iter(path));\n\n\t\tfor (int j = 0; j < num_cols; ++j) {  // gather data only from tree columns, not model columns (like tooltips and helper data)\n\t\t\tconst GType type = list_store->get_column_type(j);\n\t\t\tif (type == G_TYPE_INT) {\n\t\t\t\tint32_t value = 0;\n\t\t\t\trow.get_value(j, value);\n\t\t\t\tcell_texts.push_back(hz::number_to_string_nolocale(value));  // no locale for export\n\t\t\t} else if (type == G_TYPE_STRING) {\n\t\t\t\tstd::string value;\n\t\t\t\trow.get_value(j, value);\n\t\t\t\tcell_texts.push_back(\"\\\"\" + hz::string_replace_copy(value, \"\\\"\", \"\\\"\\\"\") + \"\\\"\");\n\t\t\t}\n\t\t}\n\t\ttext += hz::string_join(cell_texts, ',') + \"\\n\";\n\t}\n\n\tif (auto clipboard = Gtk::Clipboard::get()) {\n\t\tclipboard->set_text(text);\n\t}\n}\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_info_window.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#ifndef GSC_INFO_WINDOW_H\n#define GSC_INFO_WINDOW_H\n\n#include <gtkmm.h>\n#include <map>\n#include <memory>\n\n#include \"applib/app_builder_widget.h\"\n#include \"applib/storage_device.h\"\n#include \"applib/selftest.h\"\n\n\n\n/// Columns of treeviews inside GscInfoWindow\nstruct GscInfoWindowColumns {\n\n\t/// ATA Attributes table model columns\n\tstruct {\n\t\tGtk::TreeModelColumn<int32_t> id;\n\t\tGtk::TreeModelColumn<Glib::ustring> displayable_name;\n\t\tGtk::TreeModelColumn<Glib::ustring> when_failed;\n\t\tGtk::TreeModelColumn<std::string> normalized_value;\n\t\tGtk::TreeModelColumn<std::string> worst;\n\t\tGtk::TreeModelColumn<std::string> threshold;\n\t\tGtk::TreeModelColumn<std::string> raw;\n\t\tGtk::TreeModelColumn<Glib::ustring> type;\n\t\t// Gtk::TreeModelColumn<Glib::ustring> updated;\n\t\tGtk::TreeModelColumn<std::string> flag_value;\n\t\tGtk::TreeModelColumn<Glib::ustring> tooltip;\n\t\tGtk::TreeModelColumn<const StorageProperty*> storage_property;\n\t} ata_attribute_table_columns;\n\n\t/// NVMe attributes table model columns\n\tstruct {\n\t\tGtk::TreeModelColumn<Glib::ustring> displayable_name;\n\t\tGtk::TreeModelColumn<std::string> value;\n\t\tGtk::TreeModelColumn<Glib::ustring> tooltip;\n\t\tGtk::TreeModelColumn<const StorageProperty*> storage_property;\n\t} nvme_attribute_table_columns;\n\n\t/// Statistics table model columns\n\tstruct {\n\t\tGtk::TreeModelColumn<Glib::ustring> displayable_name;\n\t\tGtk::TreeModelColumn<std::string> value;\n\t\tGtk::TreeModelColumn<std::string> flags;\n\t\tGtk::TreeModelColumn<std::string> page_offset;\n\t\tGtk::TreeModelColumn<Glib::ustring> tooltip;\n\t\tGtk::TreeModelColumn<const StorageProperty*> storage_property;\n\t} statistics_table_columns;\n\n\t/// Self-test log table model columns\n\tstruct {\n\t\tGtk::TreeModelColumn<uint32_t> log_entry_index;\n\t\tGtk::TreeModelColumn<std::string> type;\n\t\tGtk::TreeModelColumn<std::string> status;\n\t\tGtk::TreeModelColumn<std::string> percent;\n\t\tGtk::TreeModelColumn<std::string> hours;\n\t\tGtk::TreeModelColumn<std::string> lba;\n\t\tGtk::TreeModelColumn<Glib::ustring> tooltip;\n\t\tGtk::TreeModelColumn<const StorageProperty*> storage_property;\n\t} self_test_log_table_columns;\n\n\t/// Error log table model columns\n\tstruct {\n\t\tGtk::TreeModelColumn<uint32_t> log_entry_index;\n\t\tGtk::TreeModelColumn<std::string> hours;\n\t\tGtk::TreeModelColumn<std::string> state;\n\t\tGtk::TreeModelColumn<std::string> lba;\n\t\tGtk::TreeModelColumn<std::string> details;\n\t\tGtk::TreeModelColumn<Glib::ustring> tooltip;\n\t\tGtk::TreeModelColumn<const StorageProperty*> storage_property;\n\t\tGtk::TreeModelColumn<Glib::ustring> mark_name;\n\t} error_log_table_columns;\n\n\t/// Capabilities table model columns\n\tstruct {\n\t\tGtk::TreeModelColumn<int> entry_index;\n\t\tGtk::TreeModelColumn<Glib::ustring> name;\n\t\tGtk::TreeModelColumn<std::string> flag_value;  // Text ATA\n\t\tGtk::TreeModelColumn<Glib::ustring> str_values;  // Text ATA\n\t\tGtk::TreeModelColumn<Glib::ustring> value;  // JSON ATA\n\t\tGtk::TreeModelColumn<Glib::ustring> tooltip;\n\t\tGtk::TreeModelColumn<const StorageProperty*> storage_property;\n\t} capabilities_table_columns;\n\n};\n\n\n\n\n/// The \"Drive Information\" window.\n/// Use create() / destroy() with this class instead of new / delete!\nclass GscInfoWindow : public AppBuilderWidget<GscInfoWindow, true> {\n\tpublic:\n\n\t\t// name of ui file (without .ui extension) for AppBuilderWidget\n\t\tstatic inline const std::string_view ui_name = \"gsc_info_window\";\n\n\n\t\t/// Constructor, GtkBuilder needs this.\n\t\tGscInfoWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui);\n\n\t\t/// Virtual destructor\n\t\t~GscInfoWindow() override;\n\n\n\t\t/// Set the drive to show\n\t\tvoid set_drive(StorageDevicePtr d);\n\n\t\t/// Fill the dialog with info from the drive\n\t\tvoid fill_ui_with_info(bool scan = true, bool clear_ui = true, bool clear_tests = true);\n\n\t\t/// Clear all info in UI\n\t\tvoid clear_ui_info(bool clear_tests_too = true);\n\n\t\t/// Refresh the drive information in UI\n\t\tvoid refresh_info(bool clear_tests_too = true);\n\n\n\t\t// Show the Tests tab. Called by main window.\n\t\tvoid show_tests();\n\n\n\tprotected:\n\n\t\t/// fill_ui_with_info() helper\n\t\tvoid fill_ui_general(const StoragePropertyRepository& property_repo);\n\n\t\t/// fill_ui_with_info() helper\n\t\tvoid fill_ui_ata_attributes(const StoragePropertyRepository& property_repo);\n\n\t\t/// fill_ui_with_info() helper\n\t\tvoid fill_ui_nvme_attributes(const StoragePropertyRepository& property_repo);\n\n\t\t/// fill_ui_with_info() helper\n\t\tvoid fill_ui_statistics(const StoragePropertyRepository& property_repo);\n\n\t\t/// fill_ui_with_info() helper\n\t\tvoid fill_ui_self_test_info();\n\n\t\t/// fill_ui_with_info() helper\n\t\tvoid fill_ui_self_test_log(const StoragePropertyRepository& property_repo);\n\n\t\t/// fill_ui_with_info() helper\n\t\tvoid fill_ui_ata_error_log(const StoragePropertyRepository& property_repo);\n\n\t\t/// fill_ui_with_info() helper\n\t\tvoid fill_ui_nvme_error_log(const StoragePropertyRepository& property_repo);\n\n\t\t/// fill_ui_with_info() helper\n\t\tvoid fill_ui_temperature_log(const StoragePropertyRepository& property_repo);\n\n\t\t/// fill_ui_with_info() helper\n\t\tWarningLevel fill_ui_capabilities(const StoragePropertyRepository& property_repo);\n\n\t\t/// fill_ui_with_info() helper\n\t\tWarningLevel fill_ui_error_recovery(const StoragePropertyRepository& property_repo);\n\n\t\t/// fill_ui_with_info() helper\n\t\tWarningLevel fill_ui_selective_self_test_log(const StoragePropertyRepository& property_repo);\n\n\t\t/// fill_ui_with_info() helper\n\t\tWarningLevel fill_ui_physical(const StoragePropertyRepository& property_repo);\n\n\t\t/// fill_ui_with_info() helper\n\t\tWarningLevel fill_ui_directory(const StoragePropertyRepository& property_repo);\n\n\n\t\t// ---------- Helpers\n\n\t\t/// Cell renderer function for a table\n\t\tvoid cell_renderer_for_ata_attributes(Gtk::CellRenderer* cr, const Gtk::TreeModel::iterator& iter,\n\t\t\t\tint column_index) const;\n\n\t\t/// Cell renderer function for a table\n\t\tvoid cell_renderer_for_nvme_attributes(Gtk::CellRenderer* cr, const Gtk::TreeModel::iterator& iter,\n\t\t\t\tint column_index) const;\n\n\t\t/// Cell renderer function for a table\n\t\tvoid cell_renderer_for_statistics(Gtk::CellRenderer* cr, const Gtk::TreeModel::iterator& iter,\n\t\t\t\tint column_index) const;\n\n\t\t/// Cell renderer function for a table\n\t\tvoid cell_renderer_for_self_test_log(Gtk::CellRenderer* cr, const Gtk::TreeModel::iterator& iter,\n\t\t\t\tint column_index) const;\n\n\t\t/// Cell renderer function for a table\n\t\tvoid cell_renderer_for_error_log(Gtk::CellRenderer* cr, const Gtk::TreeModel::iterator& iter,\n\t\t\t\tint column_index) const;\n\n\t\t/// Cell renderer function for a table\n\t\tvoid cell_renderer_for_capabilities(Gtk::CellRenderer* cr, const Gtk::TreeModel::iterator& iter,\n\t\t\t\tint column_index) const;\n\n\n\t\t// ---------- Callbacks\n\n\t\t/// An idle callback to update the status while the test is running.\n\t\tstatic gboolean test_idle_callback(void* data);\n\n\n\t\t// ---------- Overridden virtual methods\n\n\t\t/// Destroy this object on delete event (by default it calls hide()).\n\t\t/// If a test is running, show a question dialog first.\n\t\t/// Reimplemented from Gtk::Window.\n\t\tbool on_delete_event(GdkEventAny* e) override;\n\n\n\t\t// ---------- Other callbacks\n\n\t\t/// Button click callback\n\t\tvoid on_refresh_info_button_clicked();\n\n\t\t/// Button click callback\n\t\tvoid on_view_output_button_clicked();\n\n\t\t/// Button click callback\n\t\tvoid on_save_info_button_clicked();\n\n\t\t/// Button click callback\n\t\tvoid on_close_window_button_clicked();\n\n\n\t\t/// Callback\n\t\tvoid on_test_type_combo_changed();\n\n\t\t/// Button click callback\n\t\tvoid on_test_execute_button_clicked();\n\n\t\t/// Button click callback\n\t\tvoid on_test_stop_button_clicked();\n\n\n\t\t/// Callback attached to StorageDevice change signal.\n\t\tvoid on_drive_changed(StorageDevice* pdrive);\n\n\t\t/// Callback\n\t\tbool on_treeview_button_press_event(GdkEventButton* button_event, Gtk::Menu* menu, Gtk::TreeView* treeview);\n\n\t\t/// Callback\n\t\tvoid on_treeview_menu_copy_clicked(Gtk::TreeView* treeview);\n\n\n\tprivate:\n\n\t\t// ---------- Connections\n\n\t\tsigc::connection error_log_row_selected_conn_;  ///< Callback connection\n\n\t\tsigc::connection test_type_combo_changed_conn_;  ///< Callback connection\n\n\t\tsigc::connection drive_changed_connection_;  // Callback connection of drive's signal_changed callback\n\n\n\t\t// ---------- Data members\n\n\t\tstd::map<std::string, Gtk::Menu*> treeview_menus_;  ///< Context menus\n\n\t\t/// tab header names, to perform their coloration\n\t\tstruct {\n\t\t\tGlib::ustring identity;  ///< Tab header name\n\t\t\tGlib::ustring ata_attributes;  ///< Tab header name\n\t\t\tGlib::ustring nvme_attributes;  ///< Tab header name\n\t\t\tGlib::ustring statistics;  ///< Tab header name\n\t\t\tGlib::ustring test;  ///< Tab header name\n\t\t\tGlib::ustring ata_error_log;  ///< Tab header name\n\t\t\tGlib::ustring nvme_error_log;  ///< Tab header name\n\t\t\tGlib::ustring temperature;  ///< Tab header name\n\t\t\tGlib::ustring advanced;  ///< Tab header name\n\n\t\t\tGlib::ustring capabilities;  ///< Tab header name\n\t\t\tGlib::ustring erc;  ///< Tab header name\n\t\t\tGlib::ustring selective_selftest;  ///< Tab header name\n\t\t\tGlib::ustring phy;  ///< Tab header name\n\t\t\tGlib::ustring directory;  ///< Tab header name\n\t\t} tab_names_;\n\n\t\tGtk::Label* device_name_label_ = nullptr;  ///< Top label\n\n\t\tStorageDevicePtr drive_;  ///< The drive we're showing\n\n\t\tstd::shared_ptr<SelfTest> current_test_;  ///< Currently running test, or 0.\n\n\t\t// Test idle callback temporaries\n\t\tstd::string test_error_msg_;  ///< Our errors\n\t\tGlib::Timer test_timer_poll_;  ///< Timer for testing phase\n\t\tGlib::Timer test_timer_bar_;  ///< Timer for testing phase\n\t\tbool test_force_bar_update_ = false;  ///< Helper for testing callback\n\n\t\t// \"Test type\" combobox columns\n\t\tstruct {\n\t\t\tGtk::TreeModelColumn<Glib::ustring> name;  ///< Combobox model column\n\t\t\tGtk::TreeModelColumn<Glib::ustring> description;  ///< Combobox model column\n\t\t\tGtk::TreeModelColumn<std::shared_ptr<SelfTest>> self_test;  ///< Combobox model column\n\t\t} test_combo_columns_;\n\n\t\tGlib::RefPtr<Gtk::ListStore> test_combo_model_;  ///< Combobox model\n\n\t\t/// Columns of treeviews inside GscInfoWindow\n\t\tstd::unique_ptr<GscInfoWindowColumns> columns_;\n\n\t\tint book_selftest_page_no_ = -1;  ///< The page number of the self-test log in the notebook\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_init.cpp",
    "content": "/*\n *****************************************************************************\n License: GNU General Public License v3.0 only\n Copyright:\n \t(C) 2008 - 2025 Alexander Shaduri <ashaduri@gmail.com>\n *****************************************************************************\n */\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#include <glibmm.h>\n#include <gtkmm.h>\n#include <glib.h>  // g_, G*\n\n#include <string>\n// #include <locale.h>  // _configthreadlocale (win32)\n#include <stdexcept>  // std::runtime_error\n#include <vector>\n#include <sstream>\n#include <limits>\n#include <memory>\n#include <cmath>\n#include <iostream>\n\n\n\n#ifdef _WIN32\n\t#include <windows.h>\n\t#include <versionhelpers.h>\n#endif\n\n#include \"libdebug/libdebug.h\"  // include full libdebug here (to add domains, etc.)\n#include \"rconfig/rconfig.h\"\n#include \"rconfig/loadsave.h\"\n#include \"rconfig/autosave.h\"\n#include \"hz/data_file.h\"  // data_file_add_search_directory\n#include \"hz/fs.h\"\n#include \"hz/locale_tools.h\"  // locale_c*\n#include \"hz/string_algo.h\"  // string_join()\n#include \"hz/win32_tools.h\"  // win32_get_registry_value_string()\n#include \"hz/env_tools.h\"\n#include \"hz/string_num.h\"\n#include \"build_config.h\"  // BuildEnv\n\n#include \"applib/window_instance_manager.h\"\n#include \"applib/gsc_settings.h\"\n#include \"gsc_main_window.h\"\n#include \"gsc_executor_log_window.h\"\n#include \"gsc_init.h\"\n#include \"gsc_startup_settings.h\"\n\n\n\nnamespace {\n\n\t/// Config file in user's HOME\n\t[[nodiscard]] inline const hz::fs::path& get_home_config_file()\n\t{\n\t\tstatic hz::fs::path home_config_file = hz::fs_get_user_config_dir() / \"gsmartcontrol\" / \"gsmartcontrol2.conf\";\n\t\treturn home_config_file;\n\t}\n\n\n\n\t/// Libdebug channel buffer stream\n\t[[nodiscard]] inline std::ostringstream& get_debug_buf_channel_stream()\n\t{\n\t\tstatic std::ostringstream stream;\n\t\treturn stream;\n\t}\n\n\n\t/// Get libdebug buffer channel (create new one if unavailable).\n\t[[nodiscard]] inline DebugChannelBasePtr get_debug_buf_channel()\n\t{\n\t\tstatic DebugChannelBasePtr channel = std::make_shared<DebugChannelOStream>(get_debug_buf_channel_stream());\n\t\treturn channel;\n\t}\n\n}\n\n\n\nstd::string app_get_debug_buffer_str()\n{\n\treturn get_debug_buf_channel_stream().str();\n}\n\n\n\n\n\nnamespace {\n\n\n\t/// Find the configuration files and load them.\n\tinline bool app_init_config()\n\t{\n\t\t// Default system-wide settings. This file is empty by default.\n\t\thz::fs::path global_config_file;\n\t\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\t\tglobal_config_file = hz::fs_path_from_string(\"gsmartcontrol2.conf\");  // CWD, installation dir by default.\n\t\t} else {\n\t\t\tglobal_config_file = hz::fs_path_from_string(BuildEnv::package_sysconf_dir()) / \"gsmartcontrol2.conf\";\n\t\t}\n\n\t\tdebug_out_dump(\"app\", DBG_FUNC_MSG << \"Global config file: \\\"\" << hz::fs_path_to_string(global_config_file) << \"\\\"\\n\");\n\t\tdebug_out_dump(\"app\",\n\t\t\t\tDBG_FUNC_MSG << \"Local config file: \\\"\" << hz::fs_path_to_string(get_home_config_file()) << \"\\\"\\n\");\n\n\t\t// load global first\n\t\tstd::error_code ec;\n\t\tif (hz::fs::exists(global_config_file, ec) && hz::fs_path_is_readable(global_config_file, ec)) {\n\t\t\trconfig::load_from_file(global_config_file);\n\t\t}\n\n\t\t// load local\n\t\tif (hz::fs::exists(get_home_config_file(), ec) && hz::fs_path_is_readable(get_home_config_file(), ec)) {\n\t\t\trconfig::load_from_file(get_home_config_file());\n\n\t\t} else {\n\t\t\t// create the parent directories of the config file\n\t\t\tconst hz::fs::path config_loc = get_home_config_file().parent_path();\n\t\t\tif (!hz::fs::exists(config_loc, ec)) {\n\t\t\t\thz::fs::create_directories(config_loc, ec);\n\t\t\t\thz::fs::permissions(config_loc, hz::fs::perms::owner_all, ec);\n\t\t\t}\n\t\t}\n\n\t\tinit_default_settings();  // initialize /default\n\n\t\trconfig::dump_config();\n\n\t\trconfig::autosave_set_config_file(get_home_config_file());\n\t\tconst int autosave_timeout_sec = rconfig::get_data<int>(\"system/config_autosave_timeout_sec\");\n\t\tif (autosave_timeout_sec > 0) {\n\t\t\trconfig::autosave_start(std::chrono::seconds(autosave_timeout_sec));\n\t\t}\n\n\t\treturn true;\n\t}\n\n\n/*\n\t/// If it's the first time the application was started by this user, show a message.\n\tinline void app_show_first_boot_message(Gtk::Window* parent)\n\t{\n\t\tbool first_boot = rconfig::get_data<bool>(\"system/first_boot\");\n\n\t\tif (first_boot) {\n\t// \t\tGlib::ustring msg = \"First boot\";\n\t// \t\tGtk::MessageDialog(*parent, msg, false, Gtk::MESSAGE_INFO, Gtk::BUTTONS_OK, true).run();\n\t\t}\n\n\t// \trconfig::set_data(\"system/first_boot\", false);  // don't show it again\n\t}\n*/\n\n}  // anon. ns\n\n\n\nextern \"C\" {\n\t/// Glib message -> libdebug message convertor\n\tstatic void glib_message_handler([[maybe_unused]] const gchar* log_domain,\n\t\t\t[[maybe_unused]] GLogLevelFlags log_level,\n\t\t\tconst gchar* message,\n\t\t\t[[maybe_unused]] gpointer user_data)\n\t{\n\t\t// log_domain is already printed as part of message.\n\t\tswitch(log_level) {\n\t\t\tcase G_LOG_FLAG_RECURSION:\n\t\t\tcase G_LOG_FLAG_FATAL:\n\t\t\tcase G_LOG_LEVEL_ERROR:  // fatal\n\t\t\tcase G_LOG_LEVEL_CRITICAL:\n\t\t\t\tdebug_out_error(\"gtk\", message << std::endl);\n\t\t\t\tbreak;\n\t\t\tcase G_LOG_LEVEL_WARNING:\n\t\t\t\tdebug_out_warn(\"gtk\", message << std::endl);\n\t\t\t\tbreak;\n\t\t\tcase G_LOG_LEVEL_MESSAGE:\n\t\t\tcase G_LOG_LEVEL_INFO:\n\t\t\t\tdebug_out_info(\"gtk\", message << std::endl);\n\t\t\t\tbreak;\n\t\t\tcase G_LOG_LEVEL_DEBUG:\n\t\t\t\tdebug_out_dump(\"gtk\", message << std::endl);\n\t\t\t\tbreak;\n\t\t\tcase G_LOG_LEVEL_MASK:\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\n\n\n\nnamespace {\n\n\n\t/// Command-line argument values\n\tstruct CmdArgs {\n\t\t// Note: Use GLib types here:\n\t\tgboolean arg_locale = TRUE;  ///< if false, disable using system locale\n\t\tgboolean arg_version = FALSE;  ///< if true, show version and exit\n\t\tgboolean arg_scan = TRUE;  ///< if false, don't scan the system for drives on startup\n\t\tgboolean arg_forget_manual_devices = FALSE;  ///< if true, forget all manually added devices\n\t\tgchar** arg_add_virtual = nullptr;  ///< load smartctl data from these files as virtual drives\n\t\tgchar** arg_add_device = nullptr;  ///< add these device files manually\n\t\tdouble arg_gdk_scale = std::numeric_limits<double>::quiet_NaN();  ///< The value of GDK_SCALE environment variable\n\t\tdouble arg_gdk_dpi_scale = std::numeric_limits<double>::quiet_NaN();  ///< The value of GDK_DPI_SCALE environment variable\n\t};\n\n\n\n\t/// Parse command-line arguments (fills \\c args)\n\tinline bool parse_cmdline_args(CmdArgs& args, int& argc, char**& argv)\n\t{\n\t\tstatic const std::vector<GOptionEntry> arg_entries = {\n\t\t\t{ \"no-locale\", 'l', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &(args.arg_locale),\n\t\t\t\t\tN_(\"Don't use system locale\"), nullptr },\n\t\t\t{ \"version\", 'V', 0, G_OPTION_ARG_NONE, &(args.arg_version),\n\t\t\t\t\tN_(\"Display version information\"), nullptr },\n\t\t\t{ \"no-scan\", '\\0', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &(args.arg_scan),\n\t\t\t\t\tN_(\"Don't scan devices on startup\"), nullptr },\n\t\t\t{ \"add-virtual\", '\\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &(args.arg_add_virtual),\n\t\t\t\t\tN_(\"Load smartctl data from file, creating a virtual drive. You can specify this option multiple times.\"), nullptr },\n\t\t\t{ \"add-device\", '\\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &(args.arg_add_device),\n\t\t\t\t\tN_(\"Add this device to device list. The format of the device is \\\"<device>::<type>::<extra_args>\\\", where type and extra_args are optional.\"\n\t\t\t\t\t\" This option is useful with --no-scan to list certain drives only. You can specify this option multiple times.\"\n\t\t\t\t\t\" Example: --add-device /dev/sda --add-device /dev/twa0::3ware,2 --add-device '/dev/sdb::::-T permissive'\"), nullptr },\n\t\t\t{ \"forget-devices\", '\\0', 0, G_OPTION_ARG_NONE, &(args.arg_forget_manual_devices),\n\t\t\t\t\tN_(\"Forget all previously manually added devices.\"), nullptr },\n#ifndef _WIN32\n\t\t\t// X11-specific\n\t\t\t{ \"gdk-scale\", 'l', 0, G_OPTION_ARG_DOUBLE, &(args.arg_gdk_scale),\n\t\t\t\t\tN_(\"The value of GDK_SCALE environment variable (useful when executing with pkexec)\"), nullptr },\n\t\t\t{ \"gdk-dpi-scale\", 'l', 0, G_OPTION_ARG_DOUBLE, &(args.arg_gdk_dpi_scale),\n\t\t\t\t\tN_(\"The value of GDK_DPI_SCALE environment variable (useful when executing with pkexec)\"), nullptr },\n#endif\n\t\t\t{ nullptr, '\\0', 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr }\n\t\t};\n\n\t\tGError* error = nullptr;\n\t\tGOptionContext* context = g_option_context_new(\"- A GTK+ GUI for smartmontools\");\n\n\t\t// our options\n\t\tg_option_context_add_main_entries(context, arg_entries.data(), nullptr);\n\n\t\t// gtk options\n\t\tg_option_context_add_group(context, gtk_get_option_group(FALSE));\n\n\t\t// libdebug options; this will also automatically apply them\n\t\tg_option_context_add_group(context, debug_get_option_group());\n\n\t\t// The command-line parser stops at the first unknown option. Since this\n\t\t// is kind of inconsistent, we abort altogether.\n\t\tconst bool parsed = static_cast<bool>(g_option_context_parse(context, &argc, &argv, &error));\n\n\t\tif (error) {\n\t\t\tstd::string error_text = \"\\n\" + Glib::ustring::compose(_(\"Error parsing command-line options: %1\"), (error->message ? error->message : \"invalid error\"));\n\t\t\terror_text += \"\\n\\n\";\n\t\t\tg_error_free(error);\n\n\t\t\tgchar* help_text = g_option_context_get_help(context, TRUE, nullptr);\n\t\t\tif (help_text) {\n\t\t\t\terror_text += help_text;\n\t\t\t\tg_free(help_text);\n\t\t\t}\n\n\t\t\tstd::cerr << error_text;\n\t\t}\n\t\tg_option_context_free(context);\n\n\t\treturn parsed;\n\t}\n\n\n\n\t/// Print application version information\n\tinline void app_print_version_info()\n\t{\n\t\tconst std::string versiontext = \"\\n\" + Glib::ustring::compose(_(\"GSmartControl version %1\"), BuildEnv::package_version()) + \"\\n\";\n\n\t\tstd::string warningtext = std::string(\"\\n\") + _(\"Warning: GSmartControl comes with ABSOLUTELY NO WARRANTY.\\n\"\n\t\t\t\t\"See LICENSE.txt file for details.\") + \"\\n\\n\";\n\t\t/// %1 is years, %2 is email address\n\t\twarningtext += Glib::ustring::compose(_(\"Copyright (C) %1 Alexander Shaduri %2\"), \"2008 - 2025\",\n\t\t\t\t\"<ashaduri@gmail.com>\") + \"\\n\\n\";\n\n\t\tstd::cout << versiontext << warningtext;\n\t}\n\n}\n\n\n\n\nbool app_init_and_loop(int& argc, char**& argv)\n{\n\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\tstd::string csd_value;\n\t\tif (!hz::env_get_value(\"GTK_CSD\", csd_value)) {  // if not set\n\t\t\t// Disable client-side decorations (enable native windows decorations) under Windows.\n\t\t\thz::env_set_value(\"GTK_CSD\", \"0\");\n\t\t}\n\t}\n\n\t// Set up gettext. This has to be before gtk is initialized.\n\tbindtextdomain(BuildEnv::package_name(), BuildEnv::package_locale_dir());\n\tbind_textdomain_codeset(BuildEnv::package_name(), \"UTF-8\");\n\ttextdomain(BuildEnv::package_name());\n\n\t// Glib needs the C locale set to system locale for command line args.\n\t// We will reset it later if needed.\n\thz::locale_c_set(\"\");  // set the current locale to system locale\n\n\t// Parse command line args.\n\t// Due to gtk_get_option_group()/g_option_context_parse() calls, this\n\t// will also initialize GTK and set the C locale to system locale (as well\n\t// as do some locale-specific gdk initialization).\n\tCmdArgs args;\n\tif (! parse_cmdline_args(args, argc, argv)) {\n\t\treturn true;\n\t}\n\n\t// If locale setting is explicitly disabled, revert to the original classic C locale.\n\t// Note that changing GTK locale after it's inited isn't really supported by GTK,\n\t// but we have no other choice - glib needs system locale when parsing the\n\t// arguments, and gtk is inited while the parsing is performed.\n\tif (args.arg_locale == FALSE) {\n\t\thz::locale_c_set(\"C\");\n\t} else {\n\t\t// change the C++ locale to match the C one.\n\t\thz::locale_cpp_set(\"\");  // this may fail on some systems\n\t}\n\n\n\tif (args.arg_version == TRUE) {\n\t\t// show version information and exit\n\t\tapp_print_version_info();\n\t\treturn true;\n\t}\n\n\n\t// register libdebug domains\n\tdebug_register_domain(\"gtk\");\n\tdebug_register_domain(\"app\");\n\tdebug_register_domain(\"hz\");\n\tdebug_register_domain(\"rconfig\");\n\n\n\t// Add special debug channel to collect all libdebug output into a buffer.\n\tdebug_add_channel(\"all\", debug_level::get_all_flags(), get_debug_buf_channel());\n\n\n\n\tstd::vector<std::string> load_virtuals;\n\tif (args.arg_add_virtual) {\n\t\tconst gchar* entry = nullptr;\n\t\twhile ( (entry = *(args.arg_add_virtual)++) != nullptr ) {\n\t\t\tload_virtuals.emplace_back(entry);\n\t\t}\n\t}\n\tconst std::string load_virtuals_str = hz::string_join(load_virtuals, \", \");  // for display purposes only\n\n\tstd::vector<std::string> load_devices;\n\tif (args.arg_add_device) {\n\t\tconst gchar* entry = nullptr;\n\t\twhile ( (entry = *(args.arg_add_device)++) != nullptr ) {\n\t\t\tload_devices.emplace_back(entry);\n\t\t}\n\t}\n\tconst std::string load_devices_str = hz::string_join(load_devices, \"; \");  // for display purposes only\n\n\n\t// it's here because earlier there are no domains\n\tdebug_out_dump(\"app\", \"Application options:\\n\"\n\t\t<< \"\\tlocale: \" << args.arg_locale << \"\\n\"\n\t\t<< \"\\tversion: \" << args.arg_version << \"\\n\"\n\t\t<< \"\\tscan: \" << args.arg_scan << \"\\n\"\n\t\t<< \"\\targ_add_virtual: \" << (load_virtuals_str.empty() ? \"[empty]\" : load_virtuals_str) << \"\\n\"\n\t\t<< \"\\targ_add_device: \" << (load_devices_str.empty() ? \"[empty]\" : load_devices_str) << \"\\n\"\n\t\t<< \"\\targ_gdk_scale: \" << args.arg_gdk_scale << \"\\n\"\n\t\t<< \"\\targ_gdk_dpi_scale: \" << args.arg_gdk_dpi_scale << \"\\n\");\n\n\tdebug_out_dump(\"app\", \"LibDebug options:\\n\" << debug_get_cmd_args_dump());\n\n\tif constexpr(!BuildEnv::is_kernel_family_windows()) {  // X11\n\t\tif (!std::isnan(args.arg_gdk_scale)) {\n\t\t\thz::env_set_value(\"GDK_SCALE\", hz::number_to_string_nolocale(args.arg_gdk_scale));\n\t\t}\n\t\tif (!std::isnan(args.arg_gdk_dpi_scale)) {\n\t\t\thz::env_set_value(\"GDK_DPI_SCALE\", hz::number_to_string_nolocale(args.arg_gdk_dpi_scale));\n\t\t}\n\t}\n\n\n\t// Load config files\n\tapp_init_config();\n\n\n\t// Redirect all GTK+/Glib and related messages to libdebug.\n\t// Do this before GTK+ init, to capture its possible warnings as well.\n\tconst std::vector<const char*> gtkdomains = {\n\t\t\t// no atk or cairo, they don't log. libgnomevfs may be loaded by gtk file chooser.\n\t\t\t\"GLib\", \"GModule\", \"GLib-GObject\", \"GLib-GRegex\", \"GLib-GIO\", \"GThread\",\n\t\t\t\"Pango\", \"Gtk\", \"Gdk\", \"GdkPixbuf\", \"libgnomevfs\",\n\t\t\t\"glibmm\", \"giomm\", \"atkmm\", \"pangomm\", \"gdkmm\", \"gtkmm\"\n\t};\n\n\tfor (const auto* domain : gtkdomains) {\n\t\tg_log_set_handler(domain, GLogLevelFlags(G_LOG_LEVEL_MASK | G_LOG_FLAG_FATAL\n\t\t\t\t| G_LOG_FLAG_RECURSION), glib_message_handler, nullptr);\n\t}\n\n\n\t// Save the locale\n\tconst std::locale final_loc_cpp = hz::locale_cpp_get<std::locale>();\n\n\t// Initialize GTK+ (it's already initialized by command-line parser,\n\t// so this doesn't do much).\n\t// Newer gtkmm will try to set the C++ locale here.\n\t// Note: passing false (as use_locale) as the third parameter here\n\t// will generate a gtk_disable_setlocale() warning (due to gtk being\n\t// already initialized), so manually save / restore the C++ locale\n\t// (C locale won't be touched).\n\t// Nothing is affected in gtkmm itself by C++ locale, so it's ok to do it.\n\tGtk::Main m(argc, argv);\n\n\t// Restore the locale\n\thz::locale_cpp_set(final_loc_cpp);\n\n\n\tdebug_out_info(\"app\", \"Current C locale: \" << hz::locale_c_get() << \"\\n\");\n\tdebug_out_info(\"app\", \"Current C++ locale: \" << hz::locale_cpp_get<std::string>() << \"\\n\");\n\n\tdebug_out_info(\"app\", \"Current working directory: \" << Glib::get_current_dir() << \"\\n\");\n\n\n#ifdef _WIN32\n\t// Now that all program-specific locale setup has been performed,\n\t// make sure the future locale changes affect only current thread.\n\t// Not available in mingw, so disable for now.\n// \t_configthreadlocale(_ENABLE_PER_THREAD_LOCALE);\n#endif\n\n\n\t// This shows up in About dialog gtk.\n\tGlib::set_application_name(_(\"GSmartControl\"));\n\n\t// Check whether we're running from build directory, or it's a packaged program.\n\tauto application_dir = hz::fs_get_application_dir();\n\tdebug_out_info(\"app\", \"Application directory: \" << application_dir << \"\\n\");\n\n\tconst bool is_from_source = !application_dir.empty() && hz::fs::exists((application_dir / \"src\"));  // this covers standard cmake builds, but not VS.\n\n\t// Add data file search paths\n\tif (is_from_source) {\n\t\tif constexpr(BuildEnv::debug_build()) {\n\t\t\thz::data_file_add_search_directory(\"icons\", hz::fs_path_from_string(BuildEnv::package_top_source_dir()) / \"data\" / \"icons\");\n\t\t\thz::data_file_add_search_directory(\"ui\", hz::fs_path_from_string(BuildEnv::package_top_source_dir()) / \"src\" / \"gui\" / \"ui\");\n\t\t\thz::data_file_add_search_directory(\"doc\", hz::fs_path_from_string(BuildEnv::package_top_source_dir()) / \"doc\");\n\t\t} else {\n\t\t\t// Assume the source is the parent directory (standard cmake build with the build directory as a subdirectory of source directory,\n\t\t\t// and the executables placed directly in the build directory).\n\t\t\thz::data_file_add_search_directory(\"icons\", application_dir.parent_path() / \"data\" / \"icons\");\n\t\t\thz::data_file_add_search_directory(\"ui\", application_dir.parent_path() / \"src\" / \"gui\" / \"ui\");\n\t\t\thz::data_file_add_search_directory(\"doc\", application_dir.parent_path() / \"doc\");\n\t\t}\n\t} else {\n\t\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\t\thz::data_file_add_search_directory(\"icons\", application_dir / \"icons\");\n\t\t\thz::data_file_add_search_directory(\"ui\", application_dir / \"ui\");\n\t\t\thz::data_file_add_search_directory(\"doc\", application_dir / \"doc\");\n\t\t} else {\n\t\t\thz::data_file_add_search_directory(\"icons\", hz::fs_path_from_string(BuildEnv::package_pkgdata_dir()) / BuildEnv::package_name() / \"icons\");  // /usr/share/program_name/icons\n\t\t\thz::data_file_add_search_directory(\"ui\", hz::fs_path_from_string(BuildEnv::package_pkgdata_dir()) / BuildEnv::package_name() / \"ui\");  // /usr/share/program_name/ui\n\t\t\thz::data_file_add_search_directory(\"doc\", hz::fs_path_from_string(BuildEnv::package_doc_dir()));  // /usr/share/doc/[packages/]gsmartcontrol\n\t\t}\n\t}\n\n\t// GTK+3's \"win32\" theme is broken when Windows \"Classic\" theme is used.\n\t// Make sure we fall back to Adwaita (which works, but looks non-native)\n\t// for platforms which support \"Classic\" theme - Windows Server and Windows Vista / 7.\n\t// Windows 8 / 10 don't support \"Classic\" so native look is preferred.\n\t// Note: Win32 theme is also incompatible with fractional scaling.\n\t// Note: Win32 theme is disabled for now, and the default built-in Adwaita is used.\n/*\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\tGlib::RefPtr<Gtk::Settings> gtk_settings = Gtk::Settings::get_default();\n\t\tif (gtk_settings) {\n\t\t\tconst Glib::ustring theme_name = gtk_settings->property_gtk_theme_name().get_value();\n\t\t\tdebug_out_dump(\"app\", \"Current GTK theme: \" << theme_name << \"\\n\");\n\t\t\tbool windows_is_using_classic_theme = false;\n\t\t#ifdef _WIN32\n\t\t\twindows_is_using_classic_theme = IsWindowsServer() || !IsWindows8OrGreater();\n\t\t#endif\n\t\t\tif (windows_is_using_classic_theme && theme_name == \"win32\") {\n\t\t\t\tdebug_out_dump(\"app\", \"Windows with Classic theme support detected, switching to Adwaita theme.\\n\");\n\t\t\t\tgtk_settings->property_gtk_theme_name().set_value(\"Adwaita\");\n\t\t\t}\n\t\t}\n\t}\n*/\n\n\t// Detect Windows dark mode and set GTK theme preference accordingly\n\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\tGlib::RefPtr<Gtk::Settings> gtk_settings = Gtk::Settings::get_default();\n\t\tif (gtk_settings) {\n\t\t\tbool use_dark_theme = false;\n\t\t#ifdef _WIN32\n\t\t\t// Check Windows registry for dark mode preference\n\t\t\t// HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize\n\t\t\t// AppsUseLightTheme = 0 means dark mode, 1 means light mode\n\t\t\tDWORD apps_use_light_theme = 1;  // Default to light mode\n\t\t\tif (hz::win32_get_registry_value_dword(HKEY_CURRENT_USER,\n\t\t\t\t\tR\"(Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize)\",\n\t\t\t\t\t\"AppsUseLightTheme\", apps_use_light_theme)) {\n\t\t\t\tuse_dark_theme = (apps_use_light_theme == 0);\n\t\t\t\tdebug_out_dump(\"app\", \"Windows theme detected: \" << (use_dark_theme ? \"dark\" : \"light\") << \"\\n\");\n\t\t\t} else {\n\t\t\t\tdebug_out_dump(\"app\", \"Could not read Windows theme preference, defaulting to light mode.\\n\");\n\t\t\t}\n\t\t#endif\n\t\t\t// Apply the dark theme preference to GTK\n\t\t\tgtk_settings->property_gtk_application_prefer_dark_theme().set_value(use_dark_theme);\n\t\t\tdebug_out_dump(\"app\", \"GTK dark theme preference set to: \" << (use_dark_theme ? \"dark\" : \"light\") << \"\\n\");\n\t\t}\n\t}\n\n\n\t// The application is dpi-aware in Windows.\n\t// However, Gtk3 does not support fractional scaling, so at 250% scaling in system settings, the UI will use 200%.\n\t//\n\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\tdouble h_ppi = 0;\n\t#ifdef _WIN32\n\t\t// Get system DPI (we don't support per-monitor dpi)\n\t\tHDC screen = GetDC(nullptr);\n\t\th_ppi = GetDeviceCaps(screen, LOGPIXELSX);\n\t\tReleaseDC(nullptr, screen);\n\t#endif\n\t\tif (h_ppi > 0) {\n\t\t\tconst double scale = h_ppi / 96.0;\n\t\t\tdebug_out_info(\"app\", \"Windows system DPI: \" << h_ppi << \", scale: \" << scale << \"\\n\");\n\t\t\tconst int fraction_percent = static_cast<int>(std::round(scale * 100)) % 100;\n\t\t\tif (fraction_percent != 0) {  // fractional scaling\n\t\t\t\t// Increase the font size by fraction, but round down the size to match the Windows behavior (?)\n\t\t\t\tdebug_out_dump(\"app\", \"Fractional scaling detected, increasing font size by \" << fraction_percent << \"%.\\n\");\n\t\t\t\tGtk::Settings::get_default()->property_gtk_font_name()\n\t\t\t\t\t\t.set_value(\"Segoe UI \" + hz::number_to_string_nolocale(static_cast<int>(9 * (1. + fraction_percent/100.))));\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set default icon for all windows.\n\t// Win32 version has its icon compiled-in, so no need to set it there.\n\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\t// we load it via icontheme to provide multi-size version.\n\n\t\t// application-installed, /usr/share/icons/<theme_name>/apps/<size>\n\t\tif (Gtk::IconTheme::get_default()->has_icon(\"gsmartcontrol\")) {\n\t\t\tGtk::Window::set_default_icon_name(\"gsmartcontrol\");\n\n\t\t// try the gnome icon, it's higher quality / resolution\n\t\t} else if (Gtk::IconTheme::get_default()->has_icon(\"gnome-dev-harddisk\")) {\n\t\t\tGtk::Window::set_default_icon_name(\"gnome-dev-harddisk\");\n\n\t\t// gtk built-in, always available\n\t\t} else {\n\t\t\tGtk::Window::set_default_icon_name(\"gtk-harddisk\");\n\t\t}\n\t}\n\n\n\t// Export some command line arguments to rconfig\n\n\t// obey the command line option for no-scan on startup\n\tget_startup_settings().no_scan = !bool(args.arg_scan);\n\n\t// load virtual drives on startup if specified.\n\tget_startup_settings().load_virtuals = load_virtuals;\n\n\t// add devices to the list on startup if specified.\n\tget_startup_settings().add_devices = load_devices;\n\n\t// forget all manually added devices on startup if specified.\n\tget_startup_settings().forget_manual_devices = bool(args.arg_forget_manual_devices);\n\n\n\t// Create executor log window, but don't show it.\n\t// It will track all command executor outputs.\n\t// The window is destroyed by the instance manager.\n\tGscExecutorLogWindow::create();\n\n\n\t// Open the main window.\n\t// The window is destroyed by the instance manager.\n\t{\n\t\tauto main_window = GscMainWindow::create();\n\t\tif (!main_window) {\n\t\t\tdebug_out_fatal(\"app\", \"Cannot create the main window. Exiting.\\n\");\n\t\t\treturn false;  // cannot create main window\n\t\t}\n\n\t\t// first-boot message\n\t\t// app_show_first_boot_message(win);\n\n\t\t// The Main Loop\n\t\tdebug_out_info(\"app\", \"Entering main loop.\\n\");\n\t\tGtk::Main::run();\n\t\tdebug_out_info(\"app\", \"Main loop exited.\\n\");\n\t}\n\n\t// Destroy all windows manually, to avoid surprises\n\tWindowInstanceManagerStorage::destroy_all_instances();\n\n\t// std::cerr << app_get_debug_buffer_str();  // this will output everything that went through libdebug.\n\n\treturn true;\n}\n\n\n\n\nvoid app_quit()\n{\n\tdebug_out_info(\"app\", \"Saving config before exit...\\n\");\n\n\t// save the config\n\trconfig::autosave_force_now();\n\n\t// exit the main loop\n\tdebug_out_info(\"app\", \"Trying to exit the main loop...\\n\");\n\n\tGtk::Main::quit();\n\n\t// don't destroy main window here - we may be in one of its callbacks\n}\n\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_init.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#ifndef GSC_INIT_H\n#define GSC_INIT_H\n\n#include <string>\n\n\n/// Initialize the application and run the main loop\nbool app_init_and_loop(int& argc, char**& argv);\n\n\n/// Quit the application (exit the main loop)\nvoid app_quit();\n\n\n/// Return everything that went through libdebug's channels.\n/// Useful for showing logs.\nstd::string app_get_debug_buffer_str();\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_main.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#include <cstdlib>  // EXIT_*\n\n#include \"hz/win32_tools.h\"  // hz::win32_*\n#include \"hz/main_tools.h\"\n\n#include \"gsc_init.h\"  // app_init_and_loop()\n\n\n\n/// Application main function\nint main(int argc, char** argv)\n{\n\treturn hz::main_exception_wrapper([&argc, &argv]()\n\t{\n\t\t// disable \"Send to MS...\" dialog box in non-debug builds\n\t#if defined _WIN32 && !(defined DEBUG_BUILD && DEBUG_BUILD)\n\t\tSetErrorMode(SEM_FAILCRITICALERRORS);\n\t#endif\n\n\t\t// debug builds already have a console, no need to create one.\n\t#if defined _WIN32 && !(defined DEBUG_BUILD && DEBUG_BUILD)\n\t\t// if the console is not open, or unsupported (win2k), use files.\n\t\tif (!hz::win32_redirect_stdio_to_console()) {  // redirect stdout/stderr to console (if open and supported)\n\t\t\thz::win32_redirect_stdio_to_files();  // redirect stdout/stderr to output files\n\t\t}\n\t#endif\n\n\t\t// initialize stuff and enter the main loop\n\t\tif (!app_init_and_loop(argc, argv)) {\n\t\t\treturn EXIT_FAILURE;\n\t\t}\n\n\t\treturn EXIT_SUCCESS;\n\t});\n}\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_main_window.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#include <glibmm.h>\n#include <gtkmm.h>\n#include <system_error>\n#include <vector>\n#include <memory>\n#include <algorithm>\n\n#include \"hz/string_algo.h\"  // string_split\n#include \"hz/string_num.h\"\n#include \"hz/debug.h\"\n#include \"hz/launch_url.h\"\n#include \"hz/fs.h\"\n#include \"rconfig/rconfig.h\"\n#include \"applib/storage_detector.h\"\n#include \"applib/gui_utils.h\"  // gui_show_error_dialog\n#include \"applib/smartctl_executor.h\"  // get_smartctl_binary()\n#include \"applib/smartctl_executor_gui.h\"\n#include \"applib/app_gtkmm_tools.h\"  // app_gtkmm_*\n#include \"applib/warning_colors.h\"  // app_property_get_label_highlight_color\n#include \"applib/app_regex.h\"\n#include \"applib/smartctl_version_parser.h\"\n\n#include \"gsc_init.h\"  // app_quit()\n#include \"gsc_about_dialog.h\"\n#include \"gsc_info_window.h\"\n#include \"gsc_preferences_window.h\"\n#include \"gsc_executor_log_window.h\"\n#include \"gsc_executor_error_dialog.h\"  // gsc_executor_error_dialog_show\n\n#include \"gsc_main_window_iconview.h\"\n#include \"gsc_main_window.h\"\n#include \"gsc_add_device_window.h\"\n#include \"applib/command_executor_factory.h\"\n#include \"gsc_startup_settings.h\"\n#include \"build_config.h\"\n#include \"applib/storage_settings.h\"\n\n\nusing namespace std::literals;\n\n\nGscMainWindow::GscMainWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui)\n\t\t: AppBuilderWidget<GscMainWindow, false>(gtkcobj, std::move(ui))\n{\n\t// iconview, gtkuimanager stuff (menus), custom labels\n\tcreate_widgets();\n\n\t// Size\n\t{\n\t\tconst int def_size_w = rconfig::get_data<int>(\"gui/main_window/default_size_w\");\n\t\tconst int def_size_h = rconfig::get_data<int>(\"gui/main_window/default_size_h\");\n\t\tif (def_size_w > 0 && def_size_h > 0) {\n\t\t\tset_default_size(def_size_w, def_size_h);\n\t\t}\n\t}\n\n\t// show the window first, scan later\n\tshow();\n\n\t// Position (after the window has been shown)\n\t{\n\t\tconst int pos_x = rconfig::get_data<int>(\"gui/main_window/default_pos_x\");\n\t\tconst int pos_y = rconfig::get_data<int>(\"gui/main_window/default_pos_y\");\n\t\tif (pos_x > 0 && pos_y > 0) {  // to avoid situations where positions are not supported\n\t\t\tthis->move(pos_x, pos_y);\n\t\t}\n\t}\n\n\twhile (Gtk::Main::events_pending())  // allow the window to show\n\t\tGtk::Main::iteration();\n\n\n\t// Check if smartctl is executable\n\tbool smartctl_valid = check_smartctl_version_and_set_format();\n\n\t// Scan\n\tpopulate_iconview_on_startup(smartctl_valid);\n}\n\n\n\nGscMainWindow::~GscMainWindow()\n{\n\t// This is needed because for some reason, if any icon is selected,\n\t// on_iconview_selection_changed() is called even after the window is deleted,\n\t// causing crash on exit.\n\t// iconview_->clear_all();\n\tdelete iconview_;\n}\n\n\n\nvoid GscMainWindow::populate_iconview_on_startup(bool smartctl_valid)\n{\n\tif (!smartctl_valid) {\n\t\ticonview_->set_empty_view_message(GscMainWindowIconView::Message::NoSmartctl);\n\t\ticonview_->clear_all();  // the message won't be shown without invalidating the region.\n\t\twhile (Gtk::Main::events_pending())  // give expose event the time it needs\n\t\t\tGtk::Main::iteration();\n\n\t} else if (rconfig::get_data<bool>(\"gui/scan_on_startup\")  // config option\n\t\t\t&& !get_startup_settings().no_scan) {  // command-line option\n\t\trescan_devices(true);  // scan for devices and fill the iconview\n\n\t} else {\n\t\ticonview_->set_empty_view_message(GscMainWindowIconView::Message::ScanDisabled);\n\t\ticonview_->clear_all();  // the message won't be shown without invalidating the region.\n\t\twhile (Gtk::Main::events_pending())  // give expose event the time it needs\n\t\t\tGtk::Main::iteration();\n\t}\n\n\t// Add command-line-requested devices and virtual drives.\n\n\tif (smartctl_valid) {\n\t\tfor (auto&& dev_with_type : get_startup_settings().add_devices) {\n\t\t\tif (!dev_with_type.empty()) {\n\t\t\t\tstd::vector<std::string> parts;\n\t\t\t\thz::string_split(dev_with_type, \"::\", parts, false);\n\t\t\t\tconst std::string file = (!parts.empty() ? parts.at(0) : std::string());\n\t\t\t\tconst std::string type_arg = (parts.size() > 1 ? parts.at(1) : std::string());\n\n\t\t\t\tconst std::string extra_args_str = (parts.size() > 2 ? parts.at(2) : std::string());\n\t\t\t\tstd::vector<std::string> extra_args;\n\t\t\t\tif (!extra_args_str.empty()) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\textra_args = Glib::shell_parse_argv(extra_args_str);\n\t\t\t\t\t}\n\t\t\t\t\tcatch(Glib::ShellError& e)\n\t\t\t\t\t{\n\t\t\t\t\t\t// TODO Report\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (!file.empty()) {\n\t\t\t\t\t// Report any smartctl-related issues\n\t\t\t\t\tadd_device_interactive(file, type_arg, extra_args);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add auto-add devices stored in configuration\n\t\tif (get_startup_settings().forget_manual_devices) {\n\t\t\tapp_set_startup_manual_devices({});  // clear the list\n\t\t} else {\n\t\t\tadd_startup_manual_devices();\n\t\t}\n\t}\n\n\tfor (auto&& virt : get_startup_settings().load_virtuals) {\n\t\tif (!virt.empty()) {\n\t\t\tadd_virtual_drive(virt);\n\t\t}\n\t}\n\n\t// update the menus (group sensitiveness, etc.)\n\ticonview_->update_menu_actions();\n\tthis->update_status_widgets();\n}\n\n\n\n\n// pass enum elements here\n#define APP_ACTION_NAME(a) #a\n\n\nbool GscMainWindow::create_widgets()\n{\n\t// --------------------------------- Icon View\n\n\tget_ui()->get_widget_derived(\"drive_iconview\", iconview_);  // fill our iconview and do the rest\n\tDBG_ASSERT_RETURN(iconview_, false);\n\n\ticonview_->set_main_window(this);\n\n\t// --------------------------------- Action widgets\n\n\tstatic const Glib::ustring ui_info =\n\t\"<menubar name='main_menubar'>\"\n\n\t\"\t<menu action='file_menu'>\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_quit) \"' />\"\n\t\"\t</menu>\"\n\n\t\"\t<menu action='device_menu'>\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_view_details) \"' />\"\n\t\"\t\t<separator />\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_enable_smart) \"' />\"\n\t\"\t\t<separator />\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_reread_device_data) \"' />\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_perform_tests) \"' />\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_remove_device) \"' />\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_remove_virtual_device) \"' />\"\n\n\t\"\t\t<separator />\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_add_device) \"' />\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_load_virtual) \"' />\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_rescan_devices) \"' />\"\n\t\"\t</menu>\"\n\n\t\"\t<menu action='options_menu'>\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_executor_log) \"' />\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_update_drivedb) \"' />\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_preferences) \"' />\"\n\t\"\t</menu>\"\n\n\t\"\t<menu action='help_menu'>\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_online_documentation) \"' />\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_support) \"' />\"\n\t\"\t\t<menuitem action='\" APP_ACTION_NAME(action_about) \"' />\"\n\t\"\t</menu>\"\n\n\t\"</menubar>\"\n\n\t\"<popup name='device_popup'>\"\n\t\"\t<menuitem action='\" APP_ACTION_NAME(action_view_details) \"' />\"\n\t\"\t<separator />\"\n\t\"\t<menuitem action='\" APP_ACTION_NAME(action_enable_smart) \"' />\"\n\t\"\t<separator />\"\n\t\"\t<menuitem action='\" APP_ACTION_NAME(action_reread_device_data) \"' />\"\n\t\"\t<menuitem action='\" APP_ACTION_NAME(action_perform_tests) \"' />\"\n\t\"\t<menuitem action='\" APP_ACTION_NAME(action_remove_device) \"' />\"\n\t\"\t<menuitem action='\" APP_ACTION_NAME(action_remove_virtual_device) \"' />\"\n\t\"</popup>\"\n\n\t\"<popup name='empty_area_popup'>\"\n\t\"\t<menuitem action='\" APP_ACTION_NAME(action_add_device) \"' />\"\n\t\"\t<menuitem action='\" APP_ACTION_NAME(action_load_virtual) \"' />\"\n\t\"\t<menuitem action='\" APP_ACTION_NAME(action_rescan_devices) \"' />\"\n\t\"</popup>\";\n\n\n\t// Action groups\n\tactiongroup_main_ = Gtk::ActionGroup::create(\"main_actions\");\n\tactiongroup_device_ = Gtk::ActionGroup::create(\"device_actions\");\n\n\tGlib::RefPtr<Gtk::Action> action;\n\n\n\t// Add actions\n\tactiongroup_main_->add(Gtk::Action::create(\"file_menu\", _(\"_File\")));\n\n\t\taction = Gtk::Action::create(APP_ACTION_NAME(action_quit), Gtk::Stock::QUIT);\n\t\tactiongroup_main_->add((action_map_[action_quit] = action), Gtk::AccelKey(\"<control>Q\"),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_quit));\n\n\tactiongroup_main_->add(Gtk::Action::create(\"device_menu\", _(\"_Device\")));\n\n\t\taction = Gtk::Action::create(APP_ACTION_NAME(action_view_details), Gtk::Stock::INFO, _(\"_View details\"),\n\t\t\t\t_(\"View detailed information\"));\n\t\tactiongroup_device_->add((action_map_[action_view_details] = action), Gtk::AccelKey(\"<control>V\"),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_view_details));\n\n\t\taction = Gtk::ToggleAction::create(APP_ACTION_NAME(action_enable_smart), _(\"Enable SMART\"),\n\t\t\t\t_(\"Toggle SMART status. The status will be preserved at least until reboot (unless you toggle it again).\"));\n\t\tlookup_widget<Gtk::CheckButton*>(\"status_smart_enabled_check\")->set_related_action(action);\n\t\tactiongroup_device_->add((action_map_[action_enable_smart] = action), Gtk::AccelKey(\"<control>M\"),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_enable_smart));\n\n\t\taction = Gtk::Action::create(APP_ACTION_NAME(action_reread_device_data), Gtk::Stock::REFRESH, _(\"Re-read Data\"),\n\t\t\t\t_(\"Re-read basic SMART data\"));\n\t\tactiongroup_device_->add((action_map_[action_reread_device_data] = action), Gtk::AccelKey(\"<control>E\"),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_reread_device_data));\n\n\t\taction = Gtk::Action::create(APP_ACTION_NAME(action_perform_tests), _(\"Perform _Tests...\"),\n\t\t\t\t_(\"Perform various self-tests on the drive\"));\n\t\tactiongroup_device_->add((action_map_[action_perform_tests] = action), Gtk::AccelKey(\"<control>T\"),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_perform_tests));\n\n\t\taction = Gtk::Action::create(APP_ACTION_NAME(action_remove_device), Gtk::Stock::REMOVE, _(\"Re_move Added Device\"),\n\t\t\t\t_(\"Remove previously added device\"));\n\t\tactiongroup_device_->add((action_map_[action_remove_device] = action), Gtk::AccelKey(\"<control>W\"),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_remove_device));\n\n\t\taction = Gtk::Action::create(APP_ACTION_NAME(action_remove_virtual_device), Gtk::Stock::REMOVE, _(\"Re_move Virtual Device\"),\n\t\t\t\t_(\"Remove previously loaded virtual device\"));\n\t\tactiongroup_device_->add((action_map_[action_remove_virtual_device] = action), Gtk::AccelKey(\"Delete\"),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_remove_virtual_device));\n\n\t\t// ---\n\t\taction = Gtk::Action::create(APP_ACTION_NAME(action_add_device), Gtk::Stock::OPEN, _(\"_Add Device...\"),\n\t\t\t\t_(\"Manually add device to device list\"));\n\t\tactiongroup_main_->add((action_map_[action_add_device] = action), Gtk::AccelKey(\"<control>D\"),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_add_device));\n\n\t\taction = Gtk::Action::create(APP_ACTION_NAME(action_load_virtual), Gtk::Stock::OPEN, _(\"_Load Smartctl Output as Virtual Device...\"),\n\t\t\t\t_(\"Load smartctl output from a text file as a read-only virtual device\"));\n\t\tactiongroup_main_->add((action_map_[action_load_virtual] = action), Gtk::AccelKey(\"<control>O\"),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_load_virtual));\n\n\t\taction = Gtk::Action::create(APP_ACTION_NAME(action_rescan_devices), Gtk::Stock::REFRESH, _(\"_Re-scan Device List\"),\n\t\t\t\t_(\"Re-scan device list\"));\n\t\tactiongroup_main_->add((action_map_[action_rescan_devices] = action), Gtk::AccelKey(\"<control>R\"),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_rescan_devices));\n\n\tactiongroup_main_->add(Gtk::Action::create(\"options_menu\", _(\"_Options\")));\n\n\t\taction = Gtk::Action::create(APP_ACTION_NAME(action_executor_log), _(\"View Execution Log\"));\n\t\tactiongroup_main_->add((action_map_[action_executor_log] = action),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_executor_log));\n\n\t\taction = Gtk::Action::create(APP_ACTION_NAME(action_update_drivedb), _(\"Update Drive Database\"));\n\t\tactiongroup_main_->add((action_map_[action_update_drivedb] = action),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_update_drivedb));\n\n\t\taction = Gtk::Action::create(APP_ACTION_NAME(action_preferences), Gtk::Stock::PREFERENCES);\n\t\tactiongroup_main_->add((action_map_[action_preferences] = action), Gtk::AccelKey(\"<alt>P\"),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_preferences));\n\n\tactiongroup_main_->add(Gtk::Action::create(\"help_menu\", _(\"_Help\")));\n\n\t\taction = Gtk::Action::create(APP_ACTION_NAME(action_online_documentation), Gtk::Stock::HELP);\n\t\tactiongroup_main_->add((action_map_[action_online_documentation] = action), Gtk::AccelKey(\"F1\"),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_online_documentation));\n\n\t\taction = Gtk::Action::create(APP_ACTION_NAME(action_support), _(\"Support\"));\n\t\tactiongroup_main_->add((action_map_[action_support] = action),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_support));\n\n\t\taction = Gtk::Action::create(APP_ACTION_NAME(action_about), Gtk::Stock::ABOUT);\n\t\tactiongroup_main_->add((action_map_[action_about] = action),\n\t\t\t\tsigc::bind(sigc::mem_fun(*this, &GscMainWindow::on_action_activated), action_about));\n\n\n\n\t// create uimanager\n\tui_manager_ = Gtk::UIManager::create();\n\tui_manager_->insert_action_group(actiongroup_main_);\n\tui_manager_->insert_action_group(actiongroup_device_);\n\n\t// add accelerator group to our window so that they work\n\tadd_accel_group(ui_manager_->get_accel_group());\n\n\n\ttry {\n\t\tui_manager_->add_ui_from_string(ui_info);\n\t}\n\tcatch(Glib::Error& ex)\n\t{\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"UI creation failed: \" << ex.what() << \"\\n\");\n\t\treturn false;\n\t}\n\n\n\t// add some more accelerators (in addition to existing ones)\n\tGtk::Widget* rescan_item = ui_manager_->get_widget(\"/main_menubar/device_menu/\" APP_ACTION_NAME(action_rescan_devices));\n\tif (rescan_item)\n\t\trescan_item->add_accelerator(\"activate\", get_accel_group(), GDK_KEY_F5, Gdk::ModifierType(0), Gtk::AccelFlags(0));\n\n\n\t// look after the created widgets\n\tauto* menubar_vbox = lookup_widget<Gtk::Box*>(\"menubar_vbox\");\n\tGtk::Widget* menubar = ui_manager_->get_widget(\"/main_menubar\");\n\tif (menubar_vbox && menubar) {\n\t\tmenubar_vbox->pack_start(*menubar, Gtk::PACK_EXPAND_WIDGET);\n\t\tmenubar->set_hexpand(true);\n// \t\tmenubar->set_halign(Gtk::ALIGN_START);\n\t}\n\n\n\t// Set tooltips on menu items - gtk does that only on toolbar items.\n\tGlib::ustring tooltip_text;\n\tstd::vector<Glib::RefPtr<Gtk::ActionGroup> > groups = ui_manager_->get_action_groups();\n\tfor (auto& group : groups) {\n\t\tstd::vector<Glib::RefPtr<Gtk::Action> > actions = group->get_actions();\n\t\tfor (auto& group_action : actions) {\n\t\t\tstd::vector<Gtk::Widget*> widgets = group_action->get_proxies();\n\t\t\tif (!(tooltip_text = group_action->property_tooltip()).empty()) {\n\t\t\t\tfor (auto& widget : widgets) {\n\t\t\t\t\tapp_gtkmm_set_widget_tooltip(*widget, tooltip_text, true);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\n\t// ----------------------------------------- Labels\n\n\t// create and add labels\n\tauto* name_label_box = lookup_widget<Gtk::Box*>(\"status_name_label_hbox\");\n\tname_label_ = Gtk::manage(new Gtk::Label(_(\"No drive selected\"), Gtk::ALIGN_START));\n\tname_label_->set_line_wrap(true);\n\tname_label_->set_selectable(true);\n\tname_label_->show();\n\tname_label_box->pack_start(*name_label_, true, true);\n\n\tauto* health_label_box = lookup_widget<Gtk::Box*>(\"status_health_label_hbox\");\n\thealth_label_ = Gtk::manage(new Gtk::Label(_(\"No drive selected\"), Gtk::ALIGN_START));\n\thealth_label_->set_line_wrap(true);\n\thealth_label_->set_selectable(true);\n\thealth_label_->show();\n\thealth_label_box->pack_start(*health_label_, true, true);\n\n\tauto* family_label_box = lookup_widget<Gtk::Box*>(\"status_family_label_hbox\");\n\tfamily_label_ = Gtk::manage(new Gtk::Label(_(\"No drive selected\"), Gtk::ALIGN_START));\n\tfamily_label_->set_line_wrap(true);\n\tfamily_label_->set_selectable(true);\n\tfamily_label_->show();\n\tfamily_label_box->pack_start(*family_label_, true, true);\n\n\treturn true;\n}\n\n\n\nnamespace {\n\n\t/// Return true if the user agrees to quit\n\tinline bool ask_about_quit_on_test(Gtk::Window& parent)\n\t{\n\t\tint status = 0;\n\t\t{\n\t\t\tGtk::MessageDialog dialog(parent,\n\t\t\t\t\t\"\\n\"s + _(\"One of the drives is performing a test. Do you really want to quit?\")\n\t\t\t\t\t+ \"\\n\\n<small>\" + _(\"The test will continue to run in the background, but you won't be\"\n\t\t\t\t\t\" able to monitor it using GSmartControl.\") + \"</small>\",\n\t\t\t\t\ttrue, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO, true);\n\t\t\tstatus = dialog.run();\n\t\t}\n\t\treturn (status == Gtk::RESPONSE_YES);\n\t}\n\t\n}\n\n\n\n\n// NOTE: Do NOT bind the Glib::RefPtr<Gtk::Action> parameter to this function.\n// Doing so causes valgrind errors on window destroy (and Send Report dialogs on win32).\n// Probably a gtkmm bug. Use action maps or Gtk::Action* instead.\nvoid GscMainWindow::on_action_activated(GscMainWindow::action_t action_type)\n{\n\tif (!this->action_handling_enabled_)  // check if we should do something\n\t\treturn;\n\n\tif (action_map_.find(action_type) == action_map_.end()) {\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Invalid action activated: \" << static_cast<int>(action_type) << \".\\n\");\n\t\treturn;\n\t}\n\n\tGlib::RefPtr<Gtk::Action> action = action_map_[action_type];\n\tif (!action) {\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Action is NULL for action type \" << static_cast<int>(action_type) << \".\\n\");\n\t\treturn;\n\t}\n\n\tconst std::string action_name = action->get_name();\n\n\t// Do NOT output action->get_name() directly, it dies with unhandled conversion error\n\t// exception if used with operator <<.\n\tdebug_out_info(\"app\", DBG_FUNC_MSG << \"Action activated: \\\"\" + action_name << \"\\\"\\n\");\n\n\n\tswitch (action_type) {\n\t\tcase action_quit:\n\t\t\tquit_requested();\n\t\t\tbreak;\n\n\t\tcase action_view_details:\n\t\t\tif (iconview_)\n\t\t\t\tthis->show_device_info_window(iconview_->get_selected_drive());\n\t\t\tbreak;\n\n\t\tcase action_enable_smart:  // this may be invoked on menu manipulation\n\t\t\ton_action_enable_smart_toggled(dynamic_cast<Gtk::ToggleAction*>(\n\t\t\t\t\tactiongroup_device_->get_action(APP_ACTION_NAME(action_enable_smart)).operator->()));\n\t\t\tbreak;\n\n\t\tcase action_reread_device_data:\n\t\t\ton_action_reread_device_data();\n\t\t\tbreak;\n\n\t\tcase action_perform_tests:\n\t\t\tif (iconview_) {\n\t\t\t\tauto win = this->show_device_info_window(iconview_->get_selected_drive());\n\t\t\t\tif (win)  // won't be created if test is already running\n\t\t\t\t\twin->show_tests();\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase action_remove_device:\n\t\t\tif (iconview_) {\n\t\t\t\tStorageDevicePtr drive = iconview_->get_selected_drive();\n\t\t\t\tif (drive && drive->get_is_manually_added() && !drive->get_test_is_active()) {\n\t\t\t\t\ticonview_->remove_selected_drive();\n\n\t\t\t\t\tauto devices = app_get_startup_manual_devices();\n\t\t\t\t\tstd::erase_if(devices,\n\t\t\t\t\t\t\t[drive](const AppAddDeviceOption& dev) {\n\t\t\t\t\t\t\t\treturn (dev.device == drive->get_device() && dev.type == drive->get_type_argument());\n\t\t\t\t\t\t\t});\n\t\t\t\t\tapp_set_startup_manual_devices(devices);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase action_remove_virtual_device:\n\t\t\tif (iconview_) {\n\t\t\t\tStorageDevicePtr drive = iconview_->get_selected_drive();\n\t\t\t\tif (drive && drive->get_is_virtual())\n\t\t\t\t\ticonview_->remove_selected_drive();\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase action_add_device:\n\t\t\tthis->show_add_device_chooser();\n\t\t\tbreak;\n\n\n\t\tcase action_load_virtual:\n\t\t\tthis->show_load_virtual_file_chooser();\n\t\t\tbreak;\n\n\t\tcase action_rescan_devices:\n\t\t\trescan_devices(false);\n\t\t\tadd_startup_manual_devices();\n\t\t\tbreak;\n\n\t\tcase action_executor_log:\n\t\t{\n\t\t\t// this one will only hide on close.\n\t\t\tauto win = GscExecutorLogWindow::create();  // probably already created\n\t\t\t// win->set_transient_for(*this);  // don't do this - it will make it always-on-top of this.\n\t\t\twin->show_last();  // show the window and select last entry\n\t\t\tbreak;\n\t\t}\n\n\t\tcase action_update_drivedb:\n\t\t{\n\t\t\trun_update_drivedb();\n\t\t\tbreak;\n\t\t}\n\n\t\tcase action_preferences:\n\t\t{\n\t\t\tauto win = GscPreferencesWindow::create();  // destroyed on close\n\t\t\twin->set_transient_for(*this);  // for \"destroy with parent\", always-on-top\n\t\t\twin->set_main_window(this);\n\t\t\twin->set_modal(true);\n\t\t\twin->show();\n\t\t\tbreak;\n\t\t}\n\n\t\tcase action_online_documentation:\n\t\t{\n\t\t\thz::launch_url(gobj(), \"https://gsmartcontrol.shaduri.dev/\");\n\t\t\tbreak;\n\t\t}\n\n\t\tcase action_support:\n\t\t{\n\t\t\thz::launch_url(gobj(), \"https://gsmartcontrol.shaduri.dev/support\");\n\t\t\tbreak;\n\t\t}\n\n\t\tcase action_about:\n\t\t{\n\t\t\tauto dialog = GscAboutDialog::create();  // destroyed on close\n\t\t\tdialog->set_transient_for(*this);  // for \"destroy with parent\"\n\t\t\tdialog->show();\n\t\t\tbreak;\n\t\t}\n\n//\t\tdefault:\n//\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Unknown action: \\\"\" << action_name << \"\\\"\\n\");\n//\t\t\tbreak;\n\t}\n}\n\n\n\nvoid GscMainWindow::on_action_enable_smart_toggled(Gtk::ToggleAction* action)\n{\n\tif (!action || !iconview_)\n\t\treturn;\n\tif (!action->get_sensitive())  // it's insensitive, nothing to do (this shouldn't happen).\n\t\treturn;\n\n\tStorageDevicePtr drive = iconview_->get_selected_drive();\n\n\t// we should be protected from these by disabled actions, but still...\n\tif (!drive || drive->get_is_virtual() || drive->get_test_is_active())\n\t\treturn;\n\n\tif (!drive->get_smart_switch_supported())\n\t\treturn;\n\n\tStorageDevice::SmartStatus status = drive->get_smart_status();\n\tif (status == StorageDevice::SmartStatus::Unsupported)  // this shouldn't happen\n\t\treturn;\n\n\tconst bool toggle_active = action->get_active();\n\n\tif ( (toggle_active && status == StorageDevice::SmartStatus::Disabled)\n\t\t\t|| (!toggle_active && status == StorageDevice::SmartStatus::Enabled) ) {\n\n\t\tstd::shared_ptr<SmartctlExecutorGui> ex(new SmartctlExecutorGui());\n\t\tex->create_running_dialog(this);\n\n\t\tauto command_status = drive->set_smart_enabled(toggle_active, ex);  // run it with GUI support\n\n\t\tif (!command_status) {\n\t\t\tconst std::string error_header = (toggle_active ? _(\"Cannot enable SMART\") : _(\"Cannot disable SMART\"));\n\t\t\tgsc_executor_error_dialog_show(error_header, command_status.error().message(), this);\n\t\t}\n\n\t\ton_action_reread_device_data();  // reread if changed\n\t}\n}\n\n\n\nvoid GscMainWindow::on_action_reread_device_data()\n{\n\tif (!iconview_)\n\t\treturn;\n\n\tStorageDevicePtr drive = iconview_->get_selected_drive();\n\n\tif (!drive->get_is_virtual() && !drive->get_test_is_active()) {  // disallow on virtual and testing\n\t\tstd::shared_ptr<SmartctlExecutorGui> ex(new SmartctlExecutorGui());\n\t\tex->create_running_dialog(this);\n\n\t\t// note: this will clear the non-basic properties!\n\t\tauto fetch_status = drive->fetch_basic_data_and_parse(ex);  // run it with GUI support\n\n\t\t// the icon will be updated through drive's signal_changed callback.\n\t\tif (!fetch_status) {\n\t\t\tgsc_executor_error_dialog_show(_(\"Cannot retrieve SMART data\"), fetch_status.error().message(), this);\n\t\t}\n\t}\n}\n\n\n\nGtk::Menu* GscMainWindow::get_popup_menu(const StorageDevicePtr& drive)\n{\n\tif (!ui_manager_)\n\t\treturn nullptr;\n\tif (drive) {\n\t\treturn dynamic_cast<Gtk::Menu*>(ui_manager_->get_widget(\"/device_popup\"));\n\t}\n\treturn dynamic_cast<Gtk::Menu*>(ui_manager_->get_widget(\"/empty_area_popup\"));\n}\n\n\n\nvoid GscMainWindow::set_drive_menu_status(const StorageDevicePtr& drive)\n{\n\t// disable any action handling until we're out of here, else we'll get some\n\t// bogus toggle actions, etc.\n\tthis->action_handling_enabled_ = false;\n\n\tdo {  // for quick skipping\n\n\t\t// if no drive is selected or if a test is being run on selected drive, disallow.\n\t\tif (!drive || drive->get_test_is_active()) {\n\t\t\tactiongroup_device_->set_sensitive(false);\n\t\t\tbreak;  // nothing else to do here\n\t\t}\n\n\t\t// make everything sensitive, then disable one by one\n\t\tactiongroup_device_->set_sensitive(true);\n\n\t\tconst bool is_virtual = (drive && drive->get_is_virtual());\n\n\t\tStorageDevice::SmartStatus smart_status = StorageDevice::SmartStatus::Unsupported;\n\n\t\tif (drive && !is_virtual) {\n\t\t\tsmart_status = drive->get_smart_status();\n\t\t}\n\n\n\t\t// Sensitivity and visibility manipulation.\n\t\t// Do this first, then do the enable / disable stuff.\n\t\t{\n\t\t\tGlib::RefPtr<Gtk::Action> action;\n\n\t\t\tif ((action = actiongroup_device_->get_action(APP_ACTION_NAME(action_perform_tests)))) {\n\t\t\t\tauto status = drive->get_self_test_support_status();\n\t\t\t\taction->set_sensitive(status != StorageDevice::SelfTestSupportStatus::Unsupported);\n\t\t\t}\n\t\t\tif ((action = actiongroup_device_->get_action(APP_ACTION_NAME(action_reread_device_data)))) {\n\t\t\t\taction->set_visible(drive && !is_virtual);\n\t\t\t}\n\t\t\tif ((action = actiongroup_device_->get_action(APP_ACTION_NAME(action_remove_device)))) {\n\t\t\t\taction->set_visible(drive && drive->get_is_manually_added());\n\t\t\t\t// action->set_sensitive(drive && drive->get_is_manually_added());\n\t\t\t}\n\t\t\tif ((action = actiongroup_device_->get_action(APP_ACTION_NAME(action_remove_virtual_device)))) {\n\t\t\t\taction->set_visible(drive && is_virtual);\n\t\t\t}\n\t\t\tif ((action = actiongroup_device_->get_action(APP_ACTION_NAME(action_enable_smart)))) {\n\t\t\t\taction->set_sensitive(drive && drive->get_smart_switch_supported());\n\t\t\t}\n\t\t}\n\n\n\t\t// smart toggle status\n\t\t{\n\t\t\tGtk::ToggleAction* action = dynamic_cast<Gtk::ToggleAction*>(\n\t\t\t\t\tactiongroup_device_->get_action(APP_ACTION_NAME(action_enable_smart)).operator->());\n\t\t\tif (action) {\n\t\t\t\taction->set_active(smart_status == StorageDevice::SmartStatus::Enabled);\n\t\t\t}\n\t\t}\n\n\t} while (false);\n\n\n\t// re-enable action handling\n\tthis->action_handling_enabled_ = true;\n}\n\n\n\n// update statusbar with selected drive info\nvoid GscMainWindow::update_status_widgets()\n{\n\tif (!iconview_)\n\t\treturn;\n\n// \tGtk::Label* name_label = this->lookup_widget<Gtk::Label*>(\"status_name_label\");\n// \tGtk::Label* health_label = this->lookup_widget<Gtk::Label*>(\"status_health_label\");\n// \tGtk::Label* family_label = this->lookup_widget<Gtk::Label*>(\"status_family_label\");\n// \tGtk::Statusbar* statusbar = this->lookup_widget<Gtk::Statusbar*>(\"window_statusbar\");\n\n\tconst StorageDevicePtr drive = iconview_->get_selected_drive();\n\tif (!drive) {\n\t\tif (name_label_)\n\t\t\tname_label_->set_text(_(\"No drive selected\"));\n\t\tif (health_label_)\n\t\t\thealth_label_->set_text(_(\"No drive selected\"));\n\t\tif (family_label_)\n\t\t\tfamily_label_->set_text(_(\"No drive selected\"));\n// \t\tif (statusbar)\n// \t\t\tstatusbar->pop();\n\t\treturn;\n\t}\n\n\t/// Translators: %1 is filename\n\tconst std::string device = Glib::Markup::escape_text(drive->get_is_virtual()\n\t\t\t? Glib::ustring::compose(_(\"Virtual: %1\"), drive->get_virtual_filename()) : Glib::ustring(drive->get_device_with_type()));\n\tconst std::string size = Glib::Markup::escape_text(drive->get_device_size_str());\n\tconst std::string model = Glib::Markup::escape_text(drive->get_model_name().empty()\n\t\t\t? std::string(_(\"Unknown model\")) : drive->get_model_name());\n\tconst std::string family = Glib::Markup::escape_text(drive->get_family_name().empty()\n\t\t\t? C_(\"model_family\", \"Unknown\") : drive->get_family_name());\n\tconst std::string family_fallback = Glib::Markup::escape_text(drive->get_family_name().empty() ? model : drive->get_family_name());\n\tconst std::string drive_letters_str = Glib::Markup::escape_text(drive->format_drive_letters(false));\n\n\tconst std::string info_str = device\n\t\t\t+ (drive_letters_str.empty() ? \"\" : (\" (<b>\" + drive_letters_str + \"</b>)\"))\n\t\t\t+ (size.empty() ? \"\" : (\", \" + size))\n\t\t\t+ (model.empty() ? \"\" : (\", \" + model));\n\tif (name_label_) {\n\t\tname_label_->set_markup(info_str);\n\t\tapp_gtkmm_set_widget_tooltip(*name_label_, info_str, false);  // in case it doesn't fit\n\t}\n\n\tconst StorageProperty health_prop = drive->get_health_property();\n\n\tif (health_label_) {\n\t\tif (health_prop.generic_name == \"smart_status/passed\") {\n\t\t\thealth_label_->set_text(health_prop.format_value());\n\t\t\tstd::string fg;\n\t\t\tif (app_property_get_label_highlight_color(gui_is_dark_theme_active(), health_prop.warning_level, fg)) {\n\t\t\t\thealth_label_->set_markup(\"<span color=\\\"\" + fg + \"\\\">\"+ Glib::Markup::escape_text(health_label_->get_text()) + \"</span>\");\n\t\t\t}\n\t\t\t// don't set description tooltip - we already have the basic one.\n\t\t\t// unless it's failing.\n\t\t\t// app_gtkmm_set_widget_tooltip(*health_label, health_prop.get_description(), true);\n\n\t\t\tif (health_prop.warning_level != WarningLevel::None) {\n\t\t\t\tconst std::string tooltip_str = storage_property_get_warning_reason(health_prop)\n\t\t\t\t\t\t+ \"\\n\\n\" + _(\"View details for more information.\");\n\t\t\t\tapp_gtkmm_set_widget_tooltip(*health_label_, tooltip_str, true);\n\t\t\t}\n\n\t\t} else {\n\t\t\thealth_label_->set_text(C_(\"health_status\", \"Unknown\"));\n\t\t}\n\t}\n\n\tif (family_label_) {\n\t\tfamily_label_->set_text(family);\n\t\tapp_gtkmm_set_widget_tooltip(*family_label_, family, false);  // in case it doesn't fit\n\t}\n\n// \tstd::string status_str = \" \" + device + (size.empty() ? \"\" : (\", \" + size)) + (family_fallback.empty() ? \"\" : (\", \" + family_fallback));\n// \tif (statusbar) {\n// \t\tstatusbar->pop();\n// \t\tstatusbar->push(status_str);\n// \t}\n}\n\n\n\nvoid GscMainWindow::rescan_devices(bool startup)\n{\n\t// ignore double-scan (may happen because we use gtk loop iterations here).\n\tif (this->scanning_)\n\t\treturn;\n\n\t// If we're not in startup, smartctl version may have changed (by specifying a different binary in Preferences)\n\t// so we need to re-validate the output format:\n\tif (!startup) {\n\t\t// This shows an error dialog on error\n\t\tif (!check_smartctl_version_and_set_format()) {\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// don't manipulate window sensitiveness here - it breaks things\n\t// (cursors, gtk errors pop out, etc.)\n\n\t// if at least one drive is having a test performed, disallow.\n\tif (this->testing_active()) {\n\t\tint status = 0;\n\t\t{\n\t\t\tGtk::MessageDialog dialog(*this,\n\t\t\t\t\t\"\\n\"s + _(\"This operation may abort any running tests. Do you wish to continue?\"),\n\t\t\t\t\ttrue, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO, true);\n\t\t\tstatus = dialog.run();\n\t\t}\n\t\tif (status != Gtk::RESPONSE_YES) {\n\t\t\treturn;\n\t\t}\n\t}\n\n\tthis->scanning_ = true;\n\n// \tstd::string match_str = rconfig::get_data<std::string>(\"system/device_match_patterns\");\n\tauto blacklist_str = rconfig::get_data<std::string>(\"system/device_blacklist_patterns\");\n\n// \tstd::vector<std::string> match_patterns;\n\tstd::vector<std::string> blacklist_patterns;\n// \thz::string_split(match_str, ';', match_patterns, true);\n\thz::string_split(blacklist_str, ';', blacklist_patterns, true);\n\n\ticonview_->set_empty_view_message(GscMainWindowIconView::Message::Scanning);\n\n\ticonview_->clear_all();  // clear previous icons, invalidate region to update the message.\n\twhile (Gtk::Main::events_pending())  // give expose event the time it needs\n\t\tGtk::Main::iteration();\n\n\tthis->drives_.clear();\n\n\t// populate the icon area with drive icons\n\tStorageDetector sd;\n// \tsd.add_match_patterns(match_patterns);\n\tsd.add_blacklist_patterns(blacklist_patterns);\n\n\n\tauto ex_factory = std::make_shared<CommandExecutorFactory>(true, this);  // run it with GUI support\n\n\tauto fetch_status = sd.detect_and_fetch_basic_data(drives_, ex_factory);\n\n\tbool error = false;\n\n\t// Catch permission errors.\n\t// executor errors and outputs, not reported through error_message.\n\tconst std::vector<std::string> fetch_outputs = sd.get_fetch_data_error_outputs();\n\tfor (const auto& fetch_output : fetch_outputs) {\n\t\t// debug_out_error(\"app\", DBG_FUNC_MSG << fetch_outputs[i] << \"\\n\");\n\t\tif (app_regex_partial_match(\"/Smartctl open device.+Permission denied/mi\", fetch_output)) {\n\t\t\tgsc_executor_error_dialog_show(_(\"An error occurred while scanning the system\"),\n\t\t\t\t\t_(\"It seems that smartctl doesn't have enough permissions to access devices.\\n\"\n\t\t\t\t\t\"<small>See \\\"Permission Problems\\\" section of the documentation, accessible through the Help menu.</small>\"), this, true, true);\n\t\t\terror = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!error && !fetch_status) {  // generic scan error. smartctl errors are not reported during scan at all.\n\t\t// we don't show output button here\n\t\tgsc_executor_error_dialog_show(_(\"An error occurred while scanning the system\"),\n\t\t\t\tfetch_status.error().message(), this, false, false);\n\t\t// error = true;\n\n\t// add them anyway, in case the error was only on one drive.\n\t} else { // if (!error) {\n\t\t// add them to iconview\n\t\tfor (auto& drive : drives_) {\n\t\t\tif (rconfig::get_data<bool>(\"gui/show_smart_capable_only\")) {\n\t\t\t\tif (drive->get_smart_status() != StorageDevice::SmartStatus::Unsupported)\n\t\t\t\t\ticonview_->add_entry(drive);\n\t\t\t} else {\n\t\t\t\ticonview_->add_entry(drive);\n\t\t\t}\n\t\t}\n\t}\n\n\t// in case there are no drives in the system.\n\tif (iconview_->get_num_icons() == 0)\n\t\ticonview_->set_empty_view_message(GscMainWindowIconView::Message::NoDrivesFound);\n\n\tthis->scanning_ = false;\n}\n\n\n\nvoid GscMainWindow::add_startup_manual_devices()\n{\n\tauto auto_add_devices = app_get_startup_manual_devices();\n\tfor (const auto& dev : auto_add_devices) {\n\t\tif (!dev.device.empty()) {\n\t\t\tstd::vector<std::string> params;\n\t\t\tif (!dev.options.empty()) {\n\t\t\t\ttry {\n\t\t\t\t\tparams = Glib::shell_parse_argv(dev.options);\n\t\t\t\t}\n\t\t\t\tcatch(Glib::ShellError& e) {\n\t\t\t\t\t// Skip this entry if we can't parse params\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Ignore errors here so that the devices can be removed manually\n\t\t\tadd_device_silent(dev.device, dev.type, params);\n\t\t}\n\t}\n}\n\n\n\nvoid GscMainWindow::run_update_drivedb()\n{\n\tauto smartctl_binary = get_smartctl_binary();\n\n\tif (smartctl_binary.empty()) {\n\t\tgui_show_error_dialog(_(\"Error Updating Drive Database\"), _(\"Smartctl binary is not specified in configuration.\"), this);\n\t\treturn;\n\t}\n\n\tstd::vector<std::string> argv;\n\n\tif constexpr (BuildEnv::is_kernel_family_windows()) {\n\t\thz::fs::path update_binary_path = hz::fs_path_from_string(\"update-smart-drivedb.ps1\");\n\t\tif (smartctl_binary.is_absolute()) {\n\t\t\tupdate_binary_path = smartctl_binary.parent_path() / update_binary_path;\n\t\t}\n\t\targv = {\"powershell.exe\", \"-ExecutionPolicy\", \"Bypass\", \"-File\", hz::fs_path_to_string(update_binary_path)};\n\n\t} else {  // X11\n\t\t// TODO Wayland\n\t\thz::fs::path update_binary_path = hz::fs_path_from_string(\"update-smart-drivedb\");\n\t\tif (smartctl_binary.is_absolute()) {\n\t\t\tupdate_binary_path = smartctl_binary.parent_path() / update_binary_path;\n\t\t}\n\t\targv = {\"xterm\", \"-hold\", \"-e\", hz::fs_path_to_string(update_binary_path)};\n\t}\n\n\ttry {\n\t\tGlib::spawn_async(Glib::get_current_dir(), argv, Glib::SPAWN_SEARCH_PATH);\n\t}\n\tcatch(Glib::Error& e) {\n\t\tgui_show_error_dialog(_(\"Error Updating Drive Database\"), e.what(), this);\n\t}\n}\n\n\n\nbool GscMainWindow::add_device_interactive(const std::string& file, const std::string& type_arg, const std::vector<std::string>& extra_args)\n{\n\t// win32 doesn't have device files, so skip the check in Windows.\n\tif constexpr(!BuildEnv::is_kernel_family_windows()) {\n\t\tstd::error_code ec;\n\t\tif (!hz::fs::exists(hz::fs_path_from_string(file), ec)) {\n\t\t\tgui_show_error_dialog(_(\"Cannot add device\"),\n\t\t\t\t\t(!ec ? Glib::ustring::compose(_(\"Device \\\"%1\\\" doesn't exist.\"), file).raw() : ec.message()), this);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn add_device(false, file, type_arg, extra_args);\n}\n\n\n\nbool GscMainWindow::add_device_silent(const std::string& file, const std::string& type_arg, const std::vector<std::string>& extra_args)\n{\n\t// We want the manual add to always succeed even if there are errors,\n\t// so that it can be removed as well.\n\treturn add_device(true, file, type_arg, extra_args);\n}\n\n\n\nbool GscMainWindow::add_device(bool silent, const std::string& file, const std::string& type_arg, const std::vector<std::string>& extra_args)\n{\n\tauto drive = std::make_shared<StorageDevice>(file);\n\tdrive->set_type_argument(type_arg);\n\tdrive->set_extra_arguments(extra_args);\n\tdrive->set_is_manually_added(true);\n\n\tauto ex_factory = std::make_shared<CommandExecutorFactory>(true, this);  // pass this as dialog parent\n\n\tstd::vector<StorageDevicePtr> tmp_drives;\n\ttmp_drives.push_back(drive);\n\n\t// Don't report errors here, just add the drive to the list.\n\tStorageDetector sd;\n\tauto fetch_error = sd.fetch_basic_data(tmp_drives, ex_factory, true);  // return its first error\n\tif (!silent && !fetch_error) {\n\t\tgsc_executor_error_dialog_show(_(\"An error occurred while adding the device\"), fetch_error.error().message(), this);\n\n\t} else {\n\t\tthis->drives_.push_back(drive);\n\t\tthis->iconview_->add_entry(drive, true);  // add it, scroll and select it.\n\t}\n\n\treturn true;\n}\n\n\n\nbool GscMainWindow::add_virtual_drive(const std::string& file)\n{\n\tstd::string output;\n\tconst int max_size = 10*1024*1024;  // 10M\n\tauto ec = hz::fs_file_get_contents(hz::fs_path_from_string(file), output, max_size);\n\tif (ec) {\n\t\tdebug_out_warn(\"app\", \"Cannot open virtual drive file \\\"\" << file << \"\\\": \" << ec.message() << \"\\n\");\n\t\tgui_show_error_dialog(_(\"Cannot load data file\"), ec.message(), this);\n\t\treturn false;\n\t}\n\n\t// we have to use smart pointers here, because a pointer may be invalidated\n\t// on vector reallocation\n\tauto drive = std::make_shared<StorageDevice>(file, true);\n\n\tdrive->set_full_output(output);\n\tdrive->set_info_output(output);  // info can be parsed from full output string too.\n\n\t// this will set the type and add the properties\n\tauto parse_error = drive->parse_any_data_for_virtual();\n\tif (!parse_error) {\n\t\tgui_show_error_dialog(_(\"Cannot interpret SMART data\"), parse_error.error().message(), this);\n\t\treturn false;\n\t}\n\n\tthis->drives_.push_back(drive);\n\n\tthis->iconview_->add_entry(drives_.back(), true);  // add it, scroll and select it.\n\n\treturn true;\n}\n\n\n\n\nbool GscMainWindow::testing_active() const\n{\n\treturn std::any_of(drives_.cbegin(), drives_.cend(),\n\t[](const auto& drive)\n\t{\n\t\treturn drive && drive->get_test_is_active();\n\t});\n}\n\n\n\nstd::shared_ptr<GscInfoWindow> GscMainWindow::show_device_info_window(const StorageDevicePtr& drive)\n{\n\tif (!drive) {\n\t\treturn nullptr;\n\t}\n\n\t// if a test is being run on it, disallow.\n\tif (drive->get_test_is_active()) {\n\t\tgui_show_warn_dialog(_(\"Please wait until the test is finished on this drive.\"), this);\n\t\treturn nullptr;\n\t}\n\n\t// ask to enable SMART if it's supported but disabled\n\tif (!drive->get_is_virtual() && (drive->get_smart_status() == StorageDevice::SmartStatus::Disabled)) {\n\n\t\tint status = 0;\n\n\t\t// scope hides the dialog, without it two dialogs may be shown (this and)\n\t\t// the error one, and we don't want that.\n\t\t{\n\t\t\tGtk::MessageDialog dialog(*this,\n\t\t\t\t\t\"\\n\"s + _(\"This drive has SMART disabled. Do you want to enable it?\") + \"\\n\\n\"\n\t\t\t\t\t+ \"<small>\" + _(\"SMART will stay enabled at least until you reboot your computer.\") + \"\\n\"\n\t\t\t\t\t+ _(\"See \\\"Enable SMART Permanently\\\" section of the documentation, accessible through the Help menu.\") + \"</small>\",\n\t\t\t\t\ttrue, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO, true);\n\n\t\t\tstatus = dialog.run();\n\t\t}\n\n\t\tif (status == Gtk::RESPONSE_YES) {\n\t\t\tstd::shared_ptr<SmartctlExecutorGui> ex(new SmartctlExecutorGui());\n\t\t\tex->create_running_dialog(this, Glib::ustring::compose(_(\"Running {command} on %1...\"), drive->get_device_with_type()));\n\t\t\tauto command_status = drive->set_smart_enabled(true, ex);  // run it with GUI support\n\n\t\t\tif (!command_status) {\n\t\t\t\tgsc_executor_error_dialog_show(_(\"Cannot enable SMART\"), command_status.error().message(), this);\n\t\t\t}\n\t\t}\n\t}\n\n\n\t// Virtual drives are parsed at load time.\n\t// Parse non-virtual, smart-supporting drives here.\n\tif (!drive->get_is_virtual() && drive->get_smart_status() != StorageDevice::SmartStatus::Unsupported) {\n\t\tstd::shared_ptr<SmartctlExecutorGui> ex(new SmartctlExecutorGui());\n\t\tex->create_running_dialog(this, Glib::ustring::compose(_(\"Running {command} on %1...\"), drive->get_device_with_type()));\n\t\tauto command_status = drive->fetch_full_data_and_parse(ex);  // run it with GUI support\n\n\t\tif (!command_status) {\n\t\t\tgsc_executor_error_dialog_show(_(\"Cannot retrieve SMART data\"), command_status.error().message(), this);\n\t\t\treturn nullptr;\n\t\t}\n\t}\n\n\n\t// If the drive output wasn't fully parsed (happens with e.g. scsi and\n\t// usb devices), only very basic info is available and there's no point\n\t// in showing this window. - for both virtual and non-virtual.\n\tif (drive->get_parse_status() == StorageDevice::ParseStatus::None) {\n\t\tgsc_no_info_dialog_show(_(\"No additional information is available for this drive.\"),\n\t\t\t\t\"\", this, false, drive->get_basic_output(), _(\"Smartctl Output\"), drive->get_save_filename());\n\t\treturn nullptr;\n\t}\n\n\n\tauto win = GscInfoWindow::create();  // self-destroyed\n\n\twin->set_drive(drive);\n\twin->fill_ui_with_info(false);  // already scanned. \"refresh\" will scan it again in the info window.\n\n\t// win->set_transient_for(*this);  // for \"destroy with parent\", always-on-top\n\n\twin->show();\n\n\treturn win;\n}\n\n\n\nvoid GscMainWindow::show_prefs_updated_message()\n{\n\ticonview_->set_empty_view_message(GscMainWindowIconView::Message::PleaseRescan);\n\ticonview_->clear_all();  // the message won't be shown without invalidating the region.\n\twhile (Gtk::Main::events_pending())  // give expose event the time it needs\n\t\tGtk::Main::iteration();\n}\n\n\n\n\nvoid GscMainWindow::show_add_device_chooser()\n{\n\tauto window = GscAddDeviceWindow::create();\n\twindow->set_main_window(this);\n\twindow->set_transient_for(*this);\n\twindow->show();\n}\n\n\n\nbool GscMainWindow::check_smartctl_version_and_set_format()\n{\n\tstd::string error_msg;\n\tbool show_output_button = true;\n\n\tdo {\n\t\tconst std::string smartctl_binary = hz::fs_path_to_string(get_smartctl_binary());\n\n\t\t// Don't use default options here - they are used when invoked\n\t\t// with a device option.\n// \t\tstd::string smartctl_def_options = rconfig::get_data<std::string>(\"system/smartctl_options\");\n\n\t\tif (smartctl_binary.empty()) {\n\t\t\terror_msg = _(\"Smartctl binary is not specified in configuration.\");\n\t\t\tshow_output_button = false;\n\t\t\tbreak;\n\t\t}\n\n// \t\tif (!smartctl_def_options.empty())\n// \t\t\tsmartctl_def_options += \" \";\n\n\t\tSmartctlExecutorGui ex;\n\t\tex.create_running_dialog(this);\n\t\tex.set_running_msg(_(\"Checking if smartctl is executable...\"));\n\n\t\tex.set_command(smartctl_binary, {\"-V\"});  // --version\n\n\t\tif (!ex.execute() || !ex.get_error_msg().empty()) {\n\t\t\terror_msg = ex.get_error_msg();\n\t\t\tbreak;\n\t\t}\n\n\t\tconst std::string output = ex.get_stdout_str();\n\t\tif (output.empty()) {\n\t\t\terror_msg = _(\"Smartctl returned an empty output.\");\n\t\t\tbreak;\n\t\t}\n\n\t\tstd::string version, version_full;\n\t\tif (!SmartctlVersionParser::parse_version_text(output, version, version_full)) {\n\t\t\terror_msg = _(\"Smartctl returned invalid output.\");\n\t\t\tbreak;\n\t\t}\n\n\t\t// Check smartctl runtime version\n\t\tif (double version_double = 0; hz::string_is_numeric_nolocale<double>(version, version_double, false)) {\n\t\t\tif (version_double < SmartctlVersionParser::minimum_req_runtime_version) {\n\t\t\t\terror_msg = Glib::ustring::compose(_(\"Smartctl version %1 found, %2 required.\"),\n\t\t\t\t\t\tversion,\n\t\t\t\t\t\thz::number_to_string_nolocale(SmartctlVersionParser::minimum_req_runtime_version));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (SmartctlVersionParser::check_format_supported(SmartctlOutputFormat::Json, version)) {\n\t\t\tdebug_out_info(\"app\", \"Smartctl JSON output format supported.\\n\");\n\t\t\tSmartctlVersionParser::set_default_format(SmartctlOutputFormat::Json);\n\t\t} else {\n\t\t\tdebug_out_warn(\"app\", \"Smartctl JSON output format not supported, falling back to Text output format.\\n\");\n\t\t\tSmartctlVersionParser::set_default_format(SmartctlOutputFormat::Text);\n\t\t}\n\n\t} while (false);\n\n\tconst bool smartctl_valid = error_msg.empty();\n\tif (!smartctl_valid) {\n\t\tgsc_executor_error_dialog_show(_(\"There was an error while executing smartctl\"),\n\t\t\t\terror_msg + \"\\n\\n<i>\" + _(\"Please specify the correct smartctl binary in Preferences.\") + \"</i>\",\n\t\t\t\tthis, true, show_output_button);\n\t}\n\n\treturn smartctl_valid;\n}\n\n\n\nvoid GscMainWindow::show_load_virtual_file_chooser()\n{\n\tstatic std::string last_dir;\n\tif (last_dir.empty()) {\n\t\tlast_dir = rconfig::get_data<std::string>(\"gui/drive_data_open_save_dir\");\n\t}\n\tint result = 0;\n\n\tGlib::RefPtr<Gtk::FileFilter> specific_filter = Gtk::FileFilter::create();\n\tspecific_filter->set_name(_(\"JSON and Text Files\"));\n\tspecific_filter->add_pattern(\"*.json\");\n\tspecific_filter->add_pattern(\"*.txt\");\n\n\tGlib::RefPtr<Gtk::FileFilter> all_filter = Gtk::FileFilter::create();\n\tall_filter->set_name(_(\"All Files\"));\n\tall_filter->add_pattern(\"*\");\n\n#if GTK_CHECK_VERSION(3, 20, 0)\n\tstd::unique_ptr<GtkFileChooserNative, decltype(&g_object_unref)> dialog(gtk_file_chooser_native_new(\n\t\t\t_(\"Load Data From...\"), this->gobj(), GTK_FILE_CHOOSER_ACTION_OPEN, nullptr, nullptr),\n\t\t\t&g_object_unref);\n\n\tgtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), specific_filter->gobj());\n\tgtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), all_filter->gobj());\n\n\tgtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog.get()), TRUE);\n\n\tif (!last_dir.empty()) {\n\t\tgtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog.get()), last_dir.c_str());\n\t}\n\n\tresult = gtk_native_dialog_run(GTK_NATIVE_DIALOG(dialog.get()));\n\n#else\n\tGtk::FileChooserDialog dialog(*this, _(\"Load Data From...\"),\n\t\t\tGtk::FILE_CHOOSER_ACTION_OPEN);\n\n\t// Add response buttons the the dialog\n\tdialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);\n\tdialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_ACCEPT);\n\n\tdialog.add_filter(specific_filter);\n\tdialog.add_filter(all_filter);\n\n\tdialog.set_select_multiple(true);\n\n\tif (!last_dir.empty())\n\t\tdialog.set_current_folder(last_dir);\n\n\t// Show the dialog and wait for a user response\n\tresult = dialog.run();  // the main cycle blocks here\n#endif\n\n\t// Handle the response\n\tswitch (result) {\n\t\tcase Gtk::RESPONSE_ACCEPT:\n\t\t{\n\t\t\tstd::vector<std::string> files;\n\n#if GTK_CHECK_VERSION(3, 20, 0)\n\t\t\tstd::unique_ptr<GSList, decltype(&g_slist_free)> file_slist(gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog.get())),\n\t\t\t\t\t&g_slist_free);\n\t\t\tGSList* iterator = file_slist.get();\n\t\t\twhile(iterator) {\n\t\t\t\tfiles.push_back(app_ustring_from_gchar((gchar*)iterator->data));\n\t\t\t\titerator = g_slist_next(iterator);\n\t\t\t}\n#else\n\t\t\tfiles = dialog.get_filenames();  // in fs encoding\n#endif\n\t\t\tif (!files.empty()) {\n\t\t\t\tlast_dir = hz::fs_path_to_string(hz::fs_path_from_string(files.front()).parent_path());\n\t\t\t}\n\t\t\trconfig::set_data(\"gui/drive_data_open_save_dir\", last_dir);\n\t\t\tfor (const auto& file : files) {\n\t\t\t\tstd::error_code ec;\n\t\t\t\tif (!hz::fs::is_directory(hz::fs_path_from_string(file), ec)) {  // file chooser returns selected directories as well, ignore them.\n\t\t\t\t\tthis->add_virtual_drive(file);\n\t\t\t\t}\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\n\t\tcase Gtk::RESPONSE_CANCEL: case Gtk::RESPONSE_DELETE_EVENT:\n\t\t\t// nothing, the dialog is closed already\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Unknown dialog response code: \" << result << \".\\n\");\n\t\t\tbreak;\n\t}\n}\n\n\n\nvoid GscMainWindow::quit_requested()\n{\n\t// if at least one drive is having a test performed, disallow.\n\tif (this->testing_active()) {\n\t\tif (!ask_about_quit_on_test(*this)) {\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// window size / pos\n\t{\n\t\tint window_w = 0, window_h = 0;\n\t\tget_size(window_w, window_h);\n\t\trconfig::set_data(\"gui/main_window/default_size_w\", window_w);\n\t\trconfig::set_data(\"gui/main_window/default_size_h\", window_h);\n\n\t\tint pos_x = 0, pos_y = 0;\n\t\tget_position(pos_x, pos_y);\n\t\trconfig::set_data(\"gui/main_window/default_pos_x\", pos_x);\n\t\trconfig::set_data(\"gui/main_window/default_pos_y\", pos_y);\n\t}\n\n\tapp_quit();  // ends the main loop\n}\n\n\n\n// by default, delete_event calls hide().\nbool GscMainWindow::on_delete_event([[maybe_unused]] GdkEventAny* e)\n{\n\tquit_requested();\n\treturn true;  // event handled\n}\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_main_window.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#ifndef GSC_MAIN_WINDOW_H\n#define GSC_MAIN_WINDOW_H\n\n#include <map>\n#include <gtkmm.h>\n\n#include \"applib/app_builder_widget.h\"\n#include \"applib/storage_device.h\"\n\n\n\nclass GscMainWindowIconView;  // defined in gsc_main_window_iconview.h\n\nclass GscInfoWindow;  // declared in gsc_info_window.h\n\n\n\n/// The main window.\n/// Use create() / destroy() with this class instead of new / delete!\nclass GscMainWindow : public AppBuilderWidget<GscMainWindow, false> {\n\tpublic:\n\n\t\tfriend class GscMainWindowIconView;  // It needs our privates\n\n\t\t// name of ui file (without .ui extension) for AppBuilderWidget\n\t\tstatic inline const std::string_view ui_name = \"gsc_main_window\";\n\n\n\t\t/// Constructor, GtkBuilder needs this.\n\t\tGscMainWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui);\n\n\t\t/// Destructor.\n\t\tvirtual ~GscMainWindow();\n\n\n\t\t/// Scan for devices and fill the iconview\n\t\tvoid rescan_devices(bool startup);\n\n\n\t\t/// Add manually added devices to the icon view.\n\t\tvoid add_startup_manual_devices();\n\n\n\t\t/// Execute update-smart-drivedb\n\t\tvoid run_update_drivedb();\n\n\n\t\t/// Add device file to icon list, interactively showing errors if any.\n\t\tbool add_device_interactive(const std::string& file, const std::string& type_arg, const std::vector<std::string>& extra_args);\n\n\t\t/// Add device file to icon list silently, ignoring errors.\n\t\tbool add_device_silent(const std::string& file, const std::string& type_arg, const std::vector<std::string>& extra_args);\n\n\n\tprivate:\n\n\t\t/// Add device file to icon list\n\t\tbool add_device(bool silent, const std::string& file, const std::string& type_arg, const std::vector<std::string>& extra_args);\n\n\n\tpublic:\n\n\t\t/// Read smartctl data from file, add it as a virtual drive to icon list\n\t\tbool add_virtual_drive(const std::string& file);\n\n\n\t\t/// If at least one drive is having a test performed, return true.\n\t\tbool testing_active() const;\n\n\n\t\t/// Show the info window for the drive\n\t\tstd::shared_ptr<GscInfoWindow> show_device_info_window(const StorageDevicePtr& drive);\n\n\t\t/// Show \"Preferences updated, please rescan\" message\n\t\tvoid show_prefs_updated_message();\n\n\n\tprotected:\n\n\t\t/// Action type\n\t\tenum action_t {\n\t\t\taction_quit,\n\n\t\t\taction_view_details,\n\t\t\taction_enable_smart,\n\t\t\taction_reread_device_data,\n\t\t\taction_perform_tests,\n\t\t\taction_remove_device,\n\t\t\taction_remove_virtual_device,\n\t\t\taction_add_device,\n\t\t\taction_load_virtual,\n\t\t\taction_rescan_devices,\n\n\t\t\taction_executor_log,\n\t\t\taction_update_drivedb,\n\t\t\taction_preferences,\n\n\t\t\taction_online_documentation,\n\t\t\taction_support,\n\t\t\taction_about\n\t\t};\n\n\n\t\t/// Enable/disable items in Drive menu, set toggles in menu items\n\t\tvoid set_drive_menu_status(const StorageDevicePtr& drive);\n\n\t\t/// Get popup menu for a drive\n\t\t[[nodiscard]] Gtk::Menu* get_popup_menu(const StorageDevicePtr& drive);\n\n\t\t/// Update status widgets (status area, etc.)\n\t\tvoid update_status_widgets();\n\n\n\t\t/// Create the widgets - iconview, gtkuimanager stuff (menus), custom labels\n\t\tbool create_widgets();\n\n\t\t/// scan and populate iconview widget with drive icons\n\t\tvoid populate_iconview_on_startup(bool smartctl_valid);\n\n\t\t/// Show \"Add Device\" window\n\t\tvoid show_add_device_chooser();\n\n\t\t/// Show \"Load Virtual File\" dialog\n\t\tvoid show_load_virtual_file_chooser();\n\n\n\t\t/// Check smartctl version and set default parser format accordingly.\n\t\t/// An error dialog is shown if there is an error with smartctl.\n\t\tbool check_smartctl_version_and_set_format();\n\n\n\t\t/// Called when quit has been requested (by delete event or Quit action)\n\t\tvoid quit_requested();\n\n\n\t\t// -------------------- callbacks\n\n\t\t// ---------- overriden virtual methods\n\n\t\t/// Quit the application on delete event (by default it calls hide()).\n\t\t/// If some test is running, show a question dialog first.\n\t\t/// Reimplemented from Gtk::Window.\n\t\tbool on_delete_event(GdkEventAny* e) override;\n\n\n\t\t// ---------- other callbacks\n\n\n// \t\tvoid on_action_activated(Glib::RefPtr<Gtk::Action> action, action_t action_type);\n\n\t\t/// Action activate callback\n\t\tvoid on_action_activated(action_t action_type);\n\n\t\t/// Action callback\n\t\tvoid on_action_enable_smart_toggled(Gtk::ToggleAction* action);\n\n\t\t/// Action callback\n\t\tvoid on_action_reread_device_data();\n\n\n\tprivate:\n\n\t\tGscMainWindowIconView* iconview_ = nullptr;  ///< The main icon view\n\t\tstd::vector<StorageDevicePtr> drives_;  ///< Scanned drives\n\n\t\tGlib::RefPtr<Gtk::UIManager> ui_manager_;  ///< UI manager\n\t\tGlib::RefPtr<Gtk::ActionGroup> actiongroup_main_;  ///< Action group\n\t\tGlib::RefPtr<Gtk::ActionGroup> actiongroup_device_;  ///< Action group\n\t\tbool action_handling_enabled_ = true;  ///< Whether action handling is enabled or not\n\t\tstd::map<action_t, Glib::RefPtr<Gtk::Action> > action_map_;  ///< Used by on_action_activated().\n\n\t\tGtk::Label* name_label_ = nullptr;  ///< A UI label\n\t\tGtk::Label* health_label_ = nullptr;  ///< A UI label\n\t\tGtk::Label* family_label_ = nullptr;  ///< A UI label\n\n\t\tbool scanning_ = false;  ///< If the scanning is in process or not\n\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_main_window_iconview.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#include <cstddef>\n#include <cstdint>\n#include <glibmm.h>\n#include <gtkmm.h>\n#include <vector>\n#include <cmath>  // std::floor\n#include <unordered_map>\n#include <cairomm/cairomm.h>\n\n#include \"applib/warning_level.h\"\n#include \"hz/string_algo.h\"  // string_join\n#include \"hz/debug.h\"\n#include \"hz/data_file.h\"  // data_file_find\n#include \"applib/app_gtkmm_tools.h\"\n#include \"applib/warning_colors.h\"\n\n#include \"gsc_main_window.h\"\n#include \"rconfig/rconfig.h\"\n#include \"build_config.h\"\n\n#include \"gsc_main_window_iconview.h\"\n\n\n\n\nstd::string GscMainWindowIconView::get_message_string(Message type)\n{\n\tstatic const std::unordered_map<Message, std::string> m {\n\t\t\t{Message::None,          _(\"[error - invalid message]\")},\n\t\t\t{Message::ScanDisabled,  _(\"Automatic scanning is disabled.\\nPress Ctrl+R to scan manually.\")},\n\t\t\t{Message::Scanning,      _(\"Scanning system, please wait...\")},\n\t\t\t{Message::NoDrivesFound, _(\"No drives found.\")},\n\t\t\t{Message::NoSmartctl,    _(\"Please specify the correct smartctl binary in\\nPreferences and press Ctrl-R to re-scan.\")},\n\t\t\t{Message::PleaseRescan,  _(\"Preferences changed.\\nPress Ctrl-R to re-scan.\")},\n\t};\n\tif (auto iter = m.find(type); iter != m.end()) {\n\t\treturn iter->second;\n\t}\n\treturn \"[internal_error]\";\n}\n\n\n\nGscMainWindowIconView::GscMainWindowIconView(BaseObjectType* gtkcobj, [[maybe_unused]] const Glib::RefPtr<Gtk::Builder>& ref_ui)\n\t\t: Gtk::IconView(gtkcobj)\n{\n\tcolumns_.add(col_name_);  // we can use the col_name variable by value after this.\n\tthis->set_markup_column(col_name_);\n\n\tcolumns_.add(col_description_);\n\n\tcolumns_.add(col_pixbuf_);\n\n\t// For high quality rendering with GDK_SCALE=2\n\tthis->pack_start(cell_renderer_pixbuf_, false);\n\tthis->set_cell_data_func(cell_renderer_pixbuf_,\n\t\t\tsigc::mem_fun(this, &GscMainWindowIconView::on_cell_data_render));\n\n\tcolumns_.add(col_drive_ptr_);\n\n\tcolumns_.add(col_populated_);\n\n\t// create a Tree Model\n\tref_list_model_ = Gtk::ListStore::create(columns_);\n// \t\t\tref_list_model->set_sort_column(col_name, Gtk::SORT_ASCENDING);\n\tthis->set_model(ref_list_model_);\n\n\t// we add it here because list model must be created already\n\tset_tooltip_column(col_description_.index());\n\n\tthis->load_icon_pixbufs();\n\n\tthis->signal_item_activated().connect(sigc::mem_fun(*this,\n\t\t\t&GscMainWindowIconView::on_iconview_item_activated) );\n\n\tthis->signal_selection_changed().connect(sigc::mem_fun(*this,\n\t\t\t&GscMainWindowIconView::on_iconview_selection_changed) );\n\n\tthis->signal_button_press_event().connect(sigc::mem_fun(*this,\n\t\t\t&GscMainWindowIconView::on_iconview_button_press_event) );\n}\n\n\n\nvoid GscMainWindowIconView::set_main_window(GscMainWindow* w)\n{\n\tmain_window_ = w;\n}\n\n\n\nvoid GscMainWindowIconView::set_empty_view_message(Message message)\n{\n\tempty_view_message_ = message;\n}\n\n\n\nint GscMainWindowIconView::get_num_icons() const\n{\n\treturn num_icons_;\n}\n\n\n\nbool GscMainWindowIconView::on_draw(const Cairo::RefPtr<Cairo::Context>& cr)\n{\n\tif (in_destruction()) {\n\t\treturn true;\n\t}\n\tif (empty_view_message_ != Message::None && this->num_icons_ == 0) {  // no icons\n\t\tconst Glib::RefPtr<Pango::Layout> layout = this->create_pango_layout(\"\");\n\t\tlayout->set_alignment(Pango::ALIGN_CENTER);\n\t\tlayout->set_markup(get_message_string(empty_view_message_));\n\n\t\tint layout_w = 0, layout_h = 0;\n\t\tlayout->get_pixel_size(layout_w, layout_h);\n\n\t\tconst int pos_x = (get_allocation().get_width() - layout_w) / 2;\n\t\tconst int pos_y = (get_allocation().get_height() - layout_h) / 2;\n\t\tcr->move_to(pos_x, pos_y);\n\n\t\t// Use the foreground color from the widget's style context so\n\t\t// the text is visible in both light and dark themes.\n\t\tconst auto style_context = get_style_context();\n\t\tconst Gdk::RGBA fg_color = style_context->get_color(style_context->get_state());\n\t\tcr->set_source_rgba(fg_color.get_red(), fg_color.get_green(), fg_color.get_blue(), fg_color.get_alpha());\n\n\t\tlayout->show_in_cairo_context(cr);\n\n\t\treturn true;\n\t}\n\n\treturn Gtk::IconView::on_draw(cr);\n}\n\n\n\nvoid GscMainWindowIconView::on_cell_data_render(const Gtk::TreeModel::const_iterator& iter)\n{\n\tGtk::TreeRow row = *iter;\n\tGlib::RefPtr<Gdk::Pixbuf> pixbuf = row[col_pixbuf_];\n\tif (!pixbuf) {\n\t\treturn;\n\t}\n\n\t// Gtkmm property_surface() doesn't work, so use plain C.\n\t// https://bugzilla.gnome.org/show_bug.cgi?id=788513\n\t// Also, Gtkmm doesn't have gdk_cairo_surface_create_from_pixbuf() wrapper.\n\t// https://bugzilla.gnome.org/show_bug.cgi?id=788533\n\n// \t\t\tCairo::Format format = Cairo::FORMAT_ARGB32;\n// \t\t\tif (pixbuf->get_n_channels() == 3) {\n// \t\t\t\tformat = Cairo::FORMAT_RGB24;\n// \t\t\t}\n// \t\t\tCairo::RefPtr<Cairo::Surface> surface = get_window()->create_similar_image_surface(\n// \t\t\t\t\tformat, pixbuf->get_width(), pixbuf->get_height(), get_scale_factor());\n// \t\t\tcell_renderer_pixbuf.property_surface().set_value(surface);\n\n\t// gdk_cairo_surface_create_from_pixbuf() (and create_similar_image_surface()) from gtk 3.10.\n\tcairo_surface_t* surface = gdk_cairo_surface_create_from_pixbuf(pixbuf->gobj(), get_scale_factor(), get_window()->gobj());\n\tg_object_set(G_OBJECT(cell_renderer_pixbuf_.gobj()), \"surface\", surface, nullptr);\n\tcairo_surface_destroy(surface);\n}\n\n\n\nvoid GscMainWindowIconView::add_entry(StorageDevicePtr drive, bool scroll_to_it)\n{\n\tif (!drive)\n\t\treturn;\n\n\tGtk::TreeModel::Row row = *(ref_list_model_->append());\n\trow[col_drive_ptr_] = drive;\n\n\tthis->decorate_entry(row);\n\n\trow[col_populated_] = true;  // triggers rendering\n\n\tdrive->signal_changed().connect(sigc::mem_fun(this, &GscMainWindowIconView::on_drive_changed));\n\n\tif (scroll_to_it) {\n\t\tconst Gtk::TreeModel::Path tpath(row);\n\t\t// scroll_to_path() and set/get_cursor() are since gtkmm 2.8.\n\n\t\tthis->scroll_to_path(tpath, true, 0.5, 0.5);\n\t\t// select it (keyboard & selection)\n\t\tGtk::CellRenderer* cell = nullptr;\n\t\tif (this->get_cursor(cell) && cell) {\n\t\t\tthis->set_cursor(tpath, *cell, false);\n\t\t}\n\t\tthis->select_path(tpath);  // highlight it\n\t}\n\n\t++num_icons_;\n}\n\n\n\nvoid GscMainWindowIconView::decorate_entry(const Gtk::TreePath& model_path)\n{\n\tif (model_path.empty())\n\t\treturn;\n\n\tGtk::TreeModel::Row row = *(ref_list_model_->get_iter(model_path));\n\tthis->decorate_entry(row);\n}\n\n\n\nvoid GscMainWindowIconView::decorate_entry(Gtk::TreeModel::Row& row)\n{\n\tStorageDevicePtr drive = row[col_drive_ptr_];\n\tif (!drive) {\n\t\treturn;\n\t}\n\n\t// it needs this space to be symmetric (why?);\n\tstd::string name;  // = \"<big>\" + drive->get_device_with_type() + \" </big>\\n\";\n\tGlib::ustring drive_letters = Glib::Markup::escape_text(drive->format_drive_letters(false));\n\tif (drive_letters.empty()) {\n\t\tdrive_letters = C_(\"media\", \"not mounted\");\n\t}\n\tGlib::ustring drive_letters_with_volname = Glib::Markup::escape_text(drive->format_drive_letters(true));\n\tif (drive_letters_with_volname.empty()) {\n\t\tdrive_letters_with_volname = C_(\"media\", \"not mounted\");\n\t}\n\n\t// note: if this wraps, it becomes left-aligned in gtk <= 2.10.\n\tname += (drive->get_model_name().empty() ? Glib::ustring(\"Unknown model\") : Glib::Markup::escape_text(drive->get_model_name()));\n\tif (rconfig::get_data<bool>(\"gui/icons_show_device_name\")) {\n\t\tif (!drive->get_is_virtual()) {\n\t\t\tconst std::string dev = Glib::Markup::escape_text(drive->get_device_with_type());\n\t\t\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\t\t\tname += \"\\n\" + Glib::ustring::compose(_(\"%1 (%2)\"), dev, drive_letters);\n\t\t\t} else {\n\t\t\t\tname += \"\\n\" + dev;\n\t\t\t}\n\t\t} else if (!drive->get_virtual_filename().empty()) {\n\t\t\tname += \"\\n\" + Glib::Markup::escape_text(drive->get_virtual_filename());\n\t\t}\n\t}\n\tif (rconfig::get_data<bool>(\"gui/icons_show_serial_number\") && !drive->get_serial_number().empty()) {\n\t\tname += \"\\n\" + Glib::Markup::escape_text(drive->get_serial_number());\n\t}\n\tStorageProperty scan_time_prop;\n\tif (drive->get_is_virtual()) {\n\t\tscan_time_prop = drive->get_property_repository().lookup_property(\"local_time/asctime\");\n\t\tif (!scan_time_prop.empty() && !scan_time_prop.get_value<std::string>().empty()) {\n\t\t\tname += \"\\n\" + Glib::Markup::escape_text(scan_time_prop.get_value<std::string>());\n\t\t}\n\t}\n\n\tstd::vector<std::string> tooltip_strs;\n\n\tif (drive->get_is_virtual()) {\n\t\tconst std::string vfile = drive->get_virtual_filename();\n\t\ttooltip_strs.push_back(Glib::ustring::compose(_(\"Loaded from: %1\"), (vfile.empty() ? (Glib::ustring(\"[\") + C_(\"name\", \"empty\") + \"]\") : Glib::Markup::escape_text(vfile))));\n\t\tif (!scan_time_prop.empty() && !scan_time_prop.get_value<std::string>().empty()) {\n\t\t\ttooltip_strs.push_back(Glib::ustring::compose(_(\"Scanned on: \"), Glib::Markup::escape_text(scan_time_prop.get_value<std::string>())));\n\t\t}\n\t} else {\n\t\ttooltip_strs.push_back(Glib::ustring::compose(_(\"Device: %1\"), \"<b>\" + Glib::Markup::escape_text(drive->get_device_with_type()) + \"</b>\"));\n\t}\n\n\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\ttooltip_strs.push_back(Glib::ustring::compose(_(\"Drive letters: %1\"), \"<b>\" + drive_letters_with_volname + \"</b>\"));\n\t}\n\n\tif (!drive->get_serial_number().empty()) {\n\t\ttooltip_strs.push_back(Glib::ustring::compose(_(\"Serial number: %1\"), \"<b>\" + Glib::Markup::escape_text(drive->get_serial_number()) + \"</b>\"));\n\t}\n\ttooltip_strs.push_back(Glib::ustring::compose(_(\"SMART status: %1\"),\n\t\t\t\"<b>\" + Glib::Markup::escape_text(StorageDevice::get_status_displayable_name(drive->get_smart_status())) + \"</b>\"));\n\n\tstd::string tooltip_str = hz::string_join(tooltip_strs, '\\n');\n\n\n\tGlib::RefPtr<Gdk::Pixbuf> icon;\n\tif (icon_pixbufs_.contains(drive->get_detected_type())) {\n\t\ticon = icon_pixbufs_[drive->get_detected_type()];\n\t} else {\n\t\ticon = default_icon_;\n\t}\n\n\tconst StorageProperty health_prop = drive->get_health_property();\n\tif (health_prop.warning_level != WarningLevel::None && health_prop.generic_name == \"smart_status/passed\") {\n\t\tif (icon) {\n\t\t\ticon = icon->copy();  // work on a copy\n\t\t\tif (icon->get_colorspace() == Gdk::COLORSPACE_RGB && icon->get_bits_per_sample() == 8) {\n\t\t\t\tconst std::ptrdiff_t n_channels = icon->get_n_channels();\n\t\t\t\tconst std::ptrdiff_t icon_width = icon->get_width();\n\t\t\t\tconst std::ptrdiff_t icon_height = icon->get_height();\n\t\t\t\tconst std::ptrdiff_t rowstride = icon->get_rowstride();\n\t\t\t\tguint8* pixels = icon->get_pixels();\n\n\t\t\t\tfor (std::ptrdiff_t y = 0; y < icon_height; ++y) {\n\t\t\t\t\tfor (std::ptrdiff_t x = 0; x < icon_width; ++x) {\n\t\t\t\t\t\tguint8* p = pixels + y * rowstride + x * n_channels;\n\t\t\t\t\t\tauto avg = static_cast<uint8_t>(std::floor((p[0] * 0.30) + (p[1] * 0.59) + (p[2] * 0.11) + 0.001 + 0.5));\n\t\t\t\t\t\tp[0] = avg;  // R\n\t\t\t\t\t\tp[1] = 0;  // G\n\t\t\t\t\t\tp[2] = 0;  // B\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttooltip_str += \"\\n\\n\" + storage_property_get_warning_reason(health_prop)\n\t\t\t\t+ \"\\n\\n\" + _(\"View details for more information.\");\n\t}\n\n\n\t// we use all these if-s because changing the data (without actually changing it)\n\t// sometimes leads to screwed up icons in iconview (blame gtk).\n\n\tif (row.get_value(col_name_) != name) {\n\t\trow[col_name_] = name;  // markup\n\t}\n\tif (row.get_value(col_description_) != tooltip_str) {\n\t\trow[col_description_] = tooltip_str;  // markup\n\t}\n\n\tif (row.get_value(col_pixbuf_) != icon) {\n\t\trow[col_pixbuf_] = icon;\n\t}\n}\n\n\n\nvoid GscMainWindowIconView::remove_entry(const Gtk::TreePath& model_path)\n{\n\tconst Gtk::TreeModel::Row row = *(ref_list_model_->get_iter(model_path));\n\tref_list_model_->erase(row);\n}\n\n\n\nvoid GscMainWindowIconView::remove_selected_drive()\n{\n\tconst auto& selected_items = this->get_selected_items();\n\tif (!selected_items.empty()) {\n\t\tconst Gtk::TreePath model_path = *(selected_items.begin());\n\t\tthis->remove_entry(model_path);\n\t}\n}\n\n\n\nvoid GscMainWindowIconView::clear_all()\n{\n\tnum_icons_ = 0;\n\tref_list_model_->clear();\n\n\t// this is needed to update the label from \"disabled\" to \"scanning\"\n\tif (this->get_realized()) {\n\t\tconst Gdk::Rectangle rect = this->get_allocation();\n\t\tGlib::RefPtr<Gdk::Window> win = this->get_window();\n\t\twin->invalidate_rect(rect, true);  // force expose event\n\t\twin->process_updates(false);  // update immediately\n\t}\n}\n\n\n\nStorageDevicePtr GscMainWindowIconView::get_selected_drive()\n{\n\tStorageDevicePtr drive;\n\tconst auto& selected_items = this->get_selected_items();\n\tif (!selected_items.empty()) {\n\t\tconst Gtk::TreePath model_path = *(selected_items.begin());\n\t\tconst Gtk::TreeModel::Row row = *(ref_list_model_->get_iter(model_path));\n\t\tdrive = row[col_drive_ptr_];\n\t}\n\treturn drive;\n}\n\n\n\nGtk::TreePath GscMainWindowIconView::get_path_by_drive(StorageDevice* drive)\n{\n\tconst Gtk::TreeNodeChildren children = ref_list_model_->children();\n\tfor (const auto& row : children) {\n\t\t// convert iter to row (iter is row's base, but can we cast it?)\n\t\tif (drive == row.get_value(col_drive_ptr_).get())\n\t\t\treturn ref_list_model_->get_path(row);\n\t}\n\treturn {};  // check with .empty()\n}\n\n\n\nvoid GscMainWindowIconView::update_menu_actions()\n{\n\t// if there's nothing selected, disable items from \"Drives\" menu\n\tif (this->get_selected_items().empty()) {\n\t\tmain_window_->set_drive_menu_status(nullptr);\n\n\t} else {  // enable drives menu, set proper smart toggles\n\t\tconst Gtk::TreePath model_path = *(this->get_selected_items().begin());\n\t\tconst Gtk::TreeModel::Row row = *(ref_list_model_->get_iter(model_path));\n\t\tif (!row[col_populated_]) {  // protect against using incomplete model entry\n\t\t\treturn;\n\t\t}\n\n\t\tStorageDevicePtr drive = row[col_drive_ptr_];\n\t\tmain_window_->set_drive_menu_status(drive);\n\t}\n}\n\n\nvoid GscMainWindowIconView::on_iconview_item_activated(const Gtk::TreePath& model_path)\n{\n\tdebug_out_info(\"app\", DBG_FUNC << \"\\n\");\n\tif (!main_window_)\n\t\treturn;\n\n\tconst Gtk::TreeModel::Row row = *(ref_list_model_->get_iter(model_path));\n\tif (!row[col_populated_]) {  // protect against using incomplete model entry\n\t\treturn;\n\t}\n\n\tStorageDevicePtr drive = row[col_drive_ptr_];\n\tmain_window_->show_device_info_window(drive);\n}\n\n\n\nvoid GscMainWindowIconView::on_iconview_selection_changed()\n{\n\t// Must do it here - if done during menu activation, the actions won't work\n\t// properly before that.\n\tthis->update_menu_actions();\n\n\tmain_window_->update_status_widgets();  // status area, etc.\n}\n\n\n\nbool GscMainWindowIconView::on_iconview_button_press_event(GdkEventButton* event_button)\n{\n\t// select and show popup menu on right-click\n\tif (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) {\n\t\tStorageDevicePtr drive;  // clicked drive (if any)\n\n\t\t// don't use get_item_at_pos() - it's not available in gtkmm < 2.8.\n\t\tGtk::TreePath tpath = this->get_path_at_pos(static_cast<int>(event_button->x),\n\t\t\t\tstatic_cast<int>(event_button->y));\n\t\t// if (this->get_item_at_pos(static_cast<int>(event_button->x), static_cast<int>(event_button->y), model_path)) {\n\n\t\tif (tpath.gobj() && !tpath.empty()) {  // without gobj() check gtkmm 2.6 (but not 2.12) prints lots of errors\n\t\t\t// move keyboard focus to the icon (just as left-click does)\n\n\t\t\tGtk::CellRenderer* cell = nullptr;\n\t\t\tif (this->get_cursor(cell) && cell) {\n\t\t\t\t// gtkmm's set_cursor() is undefined (but declared) in 2.8, so use gtk variant.\n\t\t\t\tgtk_icon_view_set_cursor(GTK_ICON_VIEW(this->gobj()), tpath.gobj(), cell->gobj(), FALSE);\n\t\t\t}\n\n\t\t\t// select the icon\n\t\t\tthis->select_path(tpath);\n\n\t\t\tconst Gtk::TreeModel::Row row = *(ref_list_model_->get_iter(tpath));\n\t\t\tdrive = row[col_drive_ptr_];\n\n\t\t} else {\n\t\t\tthis->unselect_all();  // unselect on empty area right-click\n\t\t}\n\n\t\tGtk::Menu* menu = main_window_->get_popup_menu(drive);\n\t\tif (menu)\n\t\t\tmenu->popup(event_button->button, event_button->time);\n\n\t\treturn true;  // stop handling\n\t}\n\n\t// left click and everything else - continue handling.\n\t// left click selects the icon by default, and allows \"activated\" signal on double-click.\n\treturn false;\n}\n\n\n\nvoid GscMainWindowIconView::on_drive_changed(StorageDevice* drive)\n{\n\tconst Gtk::TreePath model_path = this->get_path_by_drive(drive);\n\tthis->decorate_entry(model_path);\n\tthis->update_menu_actions();\n\tmain_window_->update_status_widgets();\n}\n\n\n\nvoid GscMainWindowIconView::load_icon_pixbufs()\n{\n\tGlib::RefPtr<Gtk::IconTheme> default_icon_theme;\n\ttry {\n\t\tdefault_icon_theme = Gtk::IconTheme::get_default();\n\t} catch (...) {\n\t\t// nothing\n\t}\n\n\tdefault_icon_ = load_icon_pixbuf(default_icon_theme, \"drive-harddisk\", \"drive-harddisk.png\");\n\n\tfor (auto type : StorageDeviceDetectedTypeExt::get_all_values()) {\n\t\tGlib::RefPtr<Gdk::Pixbuf> type_icon;\n\t\tswitch(type) {\n\t\t\tcase StorageDeviceDetectedType::Unknown:\n\t\t\tcase StorageDeviceDetectedType::NeedsExplicitType:\n\t\t\t\tbreak;\n\t\t\tcase StorageDeviceDetectedType::AtaAny:\n\t\t\tcase StorageDeviceDetectedType::AtaHdd:\n\t\t\t\ttype_icon = load_icon_pixbuf(default_icon_theme, \"drive-harddisk\", \"drive-harddisk.png\");\n\t\t\t\tbreak;\n\t\t\tcase StorageDeviceDetectedType::AtaSsd:\n\t\t\tcase StorageDeviceDetectedType::Nvme:\n\t\t\t\t// Not in XDG, but available in some icon themes\n\t\t\t\ttype_icon = load_icon_pixbuf(default_icon_theme, \"drive-harddisk-solidstate\", \"\");\n\t\t\t\tbreak;\n\t\t\tcase StorageDeviceDetectedType::BasicScsi:\n\t\t\t\t// Most likely to be a flash drive\n\t\t\t\ttype_icon = load_icon_pixbuf(default_icon_theme, \"drive-removable-media\", \"drive-removable-media-usb.png\");\n\t\t\t\tbreak;\n\t\t\tcase StorageDeviceDetectedType::CdDvd:\n\t\t\t\ttype_icon = load_icon_pixbuf(default_icon_theme, \"drive-optical\", \"drive-optical.png\");\n\t\t\t\tbreak;\n\t\t\tcase StorageDeviceDetectedType::UnsupportedRaid:\n\t\t\t\t// Not in XDG, but available in some icon themes\n\t\t\t\ttype_icon = load_icon_pixbuf(default_icon_theme, \"drive-multidisk\", \"\");\n\t\t\t\tbreak;\n\t\t}\n\t\tif (!type_icon) {\n\t\t\ttype_icon = default_icon_;\n\t\t}\n\t\ticon_pixbufs_[type] = type_icon;\n\t}\n}\n\n\n\nGlib::RefPtr<Gdk::Pixbuf> GscMainWindowIconView::load_icon_pixbuf(Glib::RefPtr<Gtk::IconTheme> default_icon_theme,\n\t\tconst std::string& xdg_icon_name, const std::string& bundled_icon_filename)\n{\n\tGlib::RefPtr<Gdk::Pixbuf> icon;\n\n\t// Try XDG version first\n\ttry {\n\t\tif (default_icon_theme && !xdg_icon_name.empty()) {\n\t\t\ticon = default_icon_theme->load_icon(xdg_icon_name, icon_size_, get_scale_factor(), Gtk::IconLookupFlags(0));\n\t\t}\n\t} catch (...) { }  // ignore exceptions\n\n\tif (!icon && !bundled_icon_filename.empty()) {\n\t\t// Still no luck, use bundled ones.\n\t\tif (auto icon_file = hz::data_file_find(\"icons\", bundled_icon_filename); !icon_file.empty()) {\n\t\t\ttry {\n\t\t\t\ticon = Gdk::Pixbuf::create_from_file(hz::fs_path_to_string(icon_file));\n\t\t\t} catch (...) { }  // ignore exceptions\n\t\t}\n\t}\n\n\treturn icon;\n}\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_main_window_iconview.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#ifndef GSC_MAIN_WINDOW_ICONVIEW_H\n#define GSC_MAIN_WINDOW_ICONVIEW_H\n\n#include <glibmm.h>\n#include <gtkmm.h>\n#include <cairomm/cairomm.h>\n#include <string>\n\n#include \"gsc_main_window.h\"\n#include \"applib/storage_device.h\"\n\n\n\n/// The icon view of the main window (shows a drive list).\n/// Note: The IconView must have a fixed icon width set (e.g. in .ui file).\n/// Otherwise, it doesn't re-compute it when clearing and adding new icons.\nclass GscMainWindowIconView : public Gtk::IconView {\n\tpublic:\n\n\t\t/// Message type to show\n\t\tenum class Message {\n\t\t\tNone,  ///< No message\n\t\t\tScanDisabled,  ///< Scanning is disabled\n\t\t\tScanning,  ///< Scanning drives...\n\t\t\tNoDrivesFound,  ///< No drives found\n\t\t\tNoSmartctl,  ///< No smartctl installed\n\t\t\tPleaseRescan,  ///< Re-scan to see the drives\n\t\t};\n\n\n\t\t/// Get message string\n\t\t[[nodiscard]] static std::string get_message_string(Message type);\n\n\n\t\t/// Constructor, GtkBuilder needs this.\n\t\t[[maybe_unused]] GscMainWindowIconView(BaseObjectType* gtkcobj, [[maybe_unused]] const Glib::RefPtr<Gtk::Builder>& ref_ui);\n\n\n\t\t/// Set the parent window\n\t\tvoid set_main_window(GscMainWindow* w);\n\n\n\t\t/// Set the message type to display when there are no icons to show\n\t\tvoid set_empty_view_message(Message message);\n\n\n\t\t/// Get the number of icons currently displayed\n\t\t[[nodiscard]] int get_num_icons() const;\n\n\n\t\t// Overridden from Gtk::Widget\n\t\tbool on_draw(const Cairo::RefPtr<Cairo::Context>& cr) override;\n\n\n\t\t/// Cell data renderer (needed for high quality icons in GDK_SCALE=2).\n\t\t/// We have to use Cairo surfaces, because pixbufs are scaled by GtkIconView.\n\t\tvoid on_cell_data_render(const Gtk::TreeModel::const_iterator& iter);\n\n\n\t\t/// Add a drive entry to the icon view\n\t\tvoid add_entry(StorageDevicePtr drive, bool scroll_to_it = false);\n\n\n\t\t/// Decorate a drive entry (colorize it if it has errors, etc.).\n\t\t/// This should be called to update the icon of already refreshed drive.\n\t\tvoid decorate_entry(const Gtk::TreePath& model_path);\n\n\n\t\t/// Decorate a drive entry (colorize it if it has errors, etc.).\n\t\t/// This should be called to update the icon of already refreshed drive.\n\t\tvoid decorate_entry(Gtk::TreeModel::Row& row);\n\n\n\t\t/// Remove drive entry\n\t\tvoid remove_entry(const Gtk::TreePath& model_path);\n\n\n\t\t/// Remove selected drive entry\n\t\tvoid remove_selected_drive();\n\n\n\t\t/// Remove all entries\n\t\tvoid clear_all();\n\n\n\t\t/// Get selected drive\n\t\t[[nodiscard]] StorageDevicePtr get_selected_drive();\n\n\n\t\t/// Get tree path by a drive\n\t\t[[nodiscard]] Gtk::TreePath get_path_by_drive(StorageDevice* drive);\n\n\n\t\t/// Update menu actions in the Drives menu\n\t\tvoid update_menu_actions();\n\n\n\tprotected:\n\n\t\t/// Callback\n\t\tvoid on_iconview_item_activated(const Gtk::TreePath& model_path);\n\n\n\t\t/// Callback\n\t\tvoid on_iconview_selection_changed();\n\n\n\t\t/// Callback\n\t\tbool on_iconview_button_press_event(GdkEventButton* event_button);\n\n\n\t\t/// Callback attached to StorageDevice, updates its view.\n\t\tvoid on_drive_changed(StorageDevice* drive);\n\n\n\t\t/// Load drive-type-specific icons\n\t\tvoid load_icon_pixbufs();\n\n\n\t\t/// Load drive-type-specific icons\n\t\t[[nodiscard]] Glib::RefPtr<Gdk::Pixbuf> load_icon_pixbuf(Glib::RefPtr<Gtk::IconTheme> default_icon_theme,\n\t\t\t\tconst std::string& xdg_icon_name, const std::string& bundled_icon_filename);\n\n\n\tprivate:\n\n\t\tGtk::TreeModel::ColumnRecord columns_;  ///< Model columns\n\t\tGtk::CellRendererPixbuf cell_renderer_pixbuf_;  ///< Cell renderer for icons.\n\n\t\tGtk::TreeModelColumn<std::string> col_name_;  ///< Model column\n\t\tGtk::TreeModelColumn<Glib::ustring> col_description_;  ///< Model column\n\t\tGtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf> > col_pixbuf_;  ///< Model column\n\t\tGtk::TreeModelColumn<StorageDevicePtr> col_drive_ptr_;  ///< Model column\n\t\tGtk::TreeModelColumn<bool> col_populated_;  ///< Model column, indicates whether the model entry has been fully populated.\n\n\t\tGlib::RefPtr<Gtk::ListStore> ref_list_model_;  ///< The icon view model\n\t\tint num_icons_ = 0;  ///< Track the number of icons, because liststore makes it difficult to count them.\n\n\t\t/// Adwaita's drive-harddisk icons are tiny at 48, so 64 is better.\n\t\t/// Plus, 64 scales well to 128 and 256 (if using GDK_SCALE).\n\t\tconst int icon_size_ = 64;\n\n\t\tGlib::RefPtr<Gdk::Pixbuf> default_icon_;  ///< Icon pixbuf, used when type-specific icon is missing\n\t\tstd::unordered_map<StorageDeviceDetectedType, Glib::RefPtr<Gdk::Pixbuf>> icon_pixbufs_;  ///< Icons for different drive types\n\n\t\tGscMainWindow* main_window_ = nullptr;  ///< The main window, our parent\n\n\t\tMessage empty_view_message_ = Message::None;  ///< Message type to display when not showing any icons\n\n};\n\n\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_preferences_window.cpp",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#include <glibmm.h>\n#include <gtkmm.h>\n#include <gdk/gdk.h>  // GDK_KEY_Escape\n#include <map>\n#include <type_traits>  // std::decay_t\n#include <memory>\n\n#include \"build_config.h\"\n#include \"hz/fs_ns.h\"\n#include \"hz/string_sprintf.h\"\n#include \"rconfig/rconfig.h\"\n#include \"applib/storage_settings.h\"\n#include \"applib/app_gtkmm_tools.h\"\n#include \"gsc_main_window.h\"\n\n#include \"gsc_preferences_window.h\"\n\n\n\nusing namespace std::literals;\n\n\n/// Device Options tree view of the Preferences window\nclass GscPreferencesDeviceOptionsTreeView : public Gtk::TreeView {\n\tpublic:\n\n\t\t/// Constructor, GtkBuilder needs this.\n\t\tGscPreferencesDeviceOptionsTreeView(BaseObjectType* gtkcobj, [[maybe_unused]] const Glib::RefPtr<Gtk::Builder>& ref_ui)\n\t\t\t\t: Gtk::TreeView(gtkcobj)\n\t\t{\n\t\t\tGtk::TreeModelColumnRecord model_columns;\n\n\t\t\t// Device, Type, [Parameters], [Device Real], [Type Real].\n\t\t\t// Device may hold \"<empty>\", while Device Real is \"\".\n\t\t\t// Type may hold \"<all>\", while Type Real is \"\".\n\n\t\t\tmodel_columns.add(col_device);\n\t\t\tthis->append_column(_(\"Device\"), col_device);\n\t\t\tthis->set_search_column(col_device.index());\n\n\t\t\tmodel_columns.add(col_type);\n\t\t\tthis->append_column(_(\"Type\"), col_type);\n\n\t\t\tmodel_columns.add(col_parameters);\n\t\t\tmodel_columns.add(col_device_real);\n\t\t\tmodel_columns.add(col_type_real);\n\n\n\t\t\t// create a TreeModel (ListStore)\n\t\t\tmodel = Gtk::ListStore::create(model_columns);\n\t\t\tmodel->set_sort_column(col_device, Gtk::SORT_ASCENDING);  // default sort\n\t\t\tthis->set_model(model);\n\n\t\t\tGlib::RefPtr<Gtk::TreeSelection> selection = this->get_selection();\n\n\t\t\tselection->signal_changed().connect(sigc::mem_fun(*this,\n\t\t\t\t\t&GscPreferencesDeviceOptionsTreeView::on_selection_changed) );\n\t\t}\n\n\n\t\t/// Set the parent window\n\t\tvoid set_preferences_window(GscPreferencesWindow* w)\n\t\t{\n\t\t\tpreferences_window = w;\n\t\t}\n\n\n\t\t/// Remove selected row\n\t\tvoid remove_selected_row()\n\t\t{\n\t\t\tif (this->get_selection()->count_selected_rows() > 0) {\n\t\t\t\tGtk::TreeIter iter = this->get_selection()->get_selected();\n\t\t\t\tmodel->erase(iter);\n\t\t\t}\n\t\t}\n\n\n\t\t/// Add a new row (for a new device)\n\t\tvoid add_new_row(const std::string& device, const std::string& type, const std::string& params, bool select = true)\n\t\t{\n\t\t\tGtk::TreeRow row = *(model->append());\n\t\t\trow[col_device] = (device.empty() ? \"<\"s + C_(\"name\", \"empty\") + \">\" : device);\n\t\t\trow[col_type] = (type.empty() ? \"<\"s + C_(\"types\", \"all\") + \">\" : type);\n\t\t\trow[col_parameters] = params;\n\t\t\trow[col_device_real] = device;\n\t\t\trow[col_type_real] = type;\n\n\t\t\tif (select)\n\t\t\t\tthis->get_selection()->select(row);\n\t\t}\n\n\n\t\t/// Update selected row device entry\n\t\tvoid update_selected_row_device(const std::string& device)\n\t\t{\n\t\t\tif (this->get_selection()->count_selected_rows() > 0) {\n\t\t\t\tGtk::TreeRow row = *(this->get_selection()->get_selected());\n\t\t\t\trow[col_device] = (device.empty() ? \"<\"s + C_(\"name\", \"empty\") + \">\" : device);\n\t\t\t\trow[col_device_real] = device;\n\t\t\t}\n\t\t}\n\n\n\t\t/// Update selected row type entry\n\t\tvoid update_selected_row_type(const std::string& type)\n\t\t{\n\t\t\tif (this->get_selection()->count_selected_rows() > 0) {\n\t\t\t\tGtk::TreeRow row = *(this->get_selection()->get_selected());\n\t\t\t\trow[col_type] = (type.empty() ? \"<\"s + C_(\"types\", \"all\") + \">\" : type);\n\t\t\t\trow[col_type_real] = type;\n\t\t\t}\n\t\t}\n\n\n\t\t/// Update selected row parameters entry\n\t\tvoid update_selected_row_params(const std::string& params)\n\t\t{\n\t\t\tif (this->get_selection()->count_selected_rows() > 0) {\n\t\t\t\tGtk::TreeRow row = *(this->get_selection()->get_selected());\n\t\t\t\trow[col_parameters] = params;\n\t\t\t}\n\t\t}\n\n\n\t\t/// Remove all rows\n\t\tvoid clear_all()\n\t\t{\n\t\t\tmodel->clear();\n\t\t}\n\n\n\t\t/// Check whether there is a row selected\n\t\tbool has_selected_row()\n\t\t{\n\t\t\treturn this->get_selection()->count_selected_rows() > 0;\n\t\t}\n\n\n\t\t/// Set the device map (as loaded from config)\n\t\tvoid set_device_map(const AppDeviceOptionMap& devmap)\n\t\t{\n\t\t\tclear_all();\n\t\t\tfor (const auto& value : devmap.value) {\n\t\t\t\tstd::string dev = value.first.first;\n\t\t\t\tstd::string type = value.first.second;\n\t\t\t\tstd::string params = value.second;\n\t\t\t\tthis->add_new_row(dev, type, params, false);\n\t\t\t}\n\t\t}\n\n\n\t\t/// Get the device map (to be saved to config)\n\t\tAppDeviceOptionMap get_device_map()\n\t\t{\n\t\t\tAppDeviceOptionMap devmap;\n\n\t\t\tGtk::TreeNodeChildren children = model->children();\n\t\t\tfor (const auto& row : children) {\n\t\t\t\tstd::string dev = row.get_value(col_device_real);\n\t\t\t\tif (!dev.empty()) {\n\t\t\t\t\tstd::string type = row.get_value(col_type_real);\n\t\t\t\t\tstd::string params = row.get_value(col_parameters);\n\t\t\t\t\tdevmap.value[std::pair(dev, type)] = params;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn devmap;\n\t\t}\n\n\n\n\t\t/// Selection change callback\n\t\tvoid on_selection_changed()\n\t\t{\n\t\t\tstd::string dev, type, par;\n\t\t\tif (this->get_selection()->count_selected_rows() > 0) {\n\t\t\t\tGtk::TreeRow row = *(this->get_selection()->get_selected());\n\t\t\t\tdev = row[col_device_real];\n\t\t\t\ttype = row[col_type_real];\n\t\t\t\tpar = row[col_parameters];\n\t\t\t\tpreferences_window->device_widget_set_remove_possible(true);\n\n\t\t\t} else {\n\t\t\t\tpreferences_window->device_widget_set_remove_possible(false);\n\t\t\t}\n\n\t\t\tpreferences_window->update_device_widgets(dev, type, par);\n\t\t}\n\n\n\tprivate:\n\n\t\tGlib::RefPtr<Gtk::ListStore> model;  ///< The list model\n\n\t\tGtk::TreeModelColumn<Glib::ustring> col_device;  ///< Model column\n\t\tGtk::TreeModelColumn<Glib::ustring> col_type;  ///< Model column\n\t\tGtk::TreeModelColumn<std::string> col_parameters;  ///< Model column\n\t\tGtk::TreeModelColumn<std::string> col_device_real;  ///< Model column\n\t\tGtk::TreeModelColumn<std::string> col_type_real;  ///< Model column\n\n\t\tGscPreferencesWindow* preferences_window = nullptr;  ///< The parent window\n\n};\n\n\n\n\n\n\n\nGscPreferencesWindow::GscPreferencesWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui)\n\t\t: AppBuilderWidget<GscPreferencesWindow, true>(gtkcobj, std::move(ui))\n{\n\t// Connect callbacks\n\n\tGtk::Button* window_cancel_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(window_cancel_button, clicked);\n\n\tGtk::Button* window_ok_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(window_ok_button, clicked);\n\n\tGtk::Button* window_reset_all_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(window_reset_all_button, clicked);\n\n\n\tGlib::ustring smartctl_binary_tooltip = _(\"A path to smartctl binary. If the path is not absolute, the binary will be looked for in user's PATH.\");\n\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\tsmartctl_binary_tooltip += Glib::ustring(\"\\n\") + _(\"Note: smartctl.exe shows a console during execution, while smartctl-nc.exe (default) doesn't (nc means no-console).\");\n\t}\n\tif (auto* smartctl_binary_label = lookup_widget<Gtk::Label*>(\"smartctl_binary_label\")) {\n\t\tapp_gtkmm_set_widget_tooltip(*smartctl_binary_label, smartctl_binary_tooltip);\n\t}\n\tif (auto* smartctl_binary_entry = lookup_widget<Gtk::Entry*>(\"smartctl_binary_entry\")) {\n\t\tapp_gtkmm_set_widget_tooltip(*smartctl_binary_entry, smartctl_binary_tooltip);\n\t}\n\n\tGtk::Button* smartctl_binary_browse_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(smartctl_binary_browse_button, clicked);\n\n\n\tGtk::Button* device_options_add_device_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(device_options_add_device_button, clicked);\n\n\tGtk::Button* device_options_remove_device_button = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(device_options_remove_device_button, clicked);\n\n\n\tGtk::Entry* device_options_device_entry = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(device_options_device_entry, changed);\n\n\tGlib::ustring device_options_tooltip = _(\"A device name to match\");\n\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\tdevice_options_tooltip = _(\"A device name to match (for example, use \\\"pd0\\\" for the first physical drive)\");\n\t} else if constexpr(BuildEnv::is_kernel_linux()) {\n\t\tdevice_options_tooltip = _(\"A device name to match (for example, /dev/sda or /dev/twa0)\");\n\t}\n\tif (auto* device_options_device_label = lookup_widget<Gtk::Label*>(\"device_options_device_label\")) {\n\t\tapp_gtkmm_set_widget_tooltip(*device_options_device_label, device_options_tooltip);\n\t}\n\tif (device_options_device_entry) {\n\t\tapp_gtkmm_set_widget_tooltip(*device_options_device_entry, device_options_tooltip);\n\t}\n\n\n\tGtk::Entry* device_options_type_entry = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(device_options_type_entry, changed);\n\n\tGtk::Entry* device_options_parameter_entry = nullptr;\n\tAPP_BUILDER_AUTO_CONNECT(device_options_parameter_entry, changed);\n\n\n\t// Accelerators\n\n\tGlib::RefPtr<Gtk::AccelGroup> accel_group = this->get_accel_group();\n\tif (window_cancel_button) {\n\t\twindow_cancel_button->add_accelerator(\"clicked\", accel_group, GDK_KEY_Escape,\n\t\t\t\tGdk::ModifierType(0), Gtk::AccelFlags(0));\n\t}\n\n\n\t// create Device Options treeview\n\tget_ui()->get_widget_derived(\"device_options_treeview\", device_options_treeview_);\n\tif (device_options_treeview_)\n\t\tdevice_options_treeview_->set_preferences_window(this);\n\n\t// we can't do this in treeview's constructor, it doesn't know about this window yet.\n\tthis->device_widget_set_remove_possible(false);  // initial state\n\n\t// hide win32-only options for non-win32.\n\tif constexpr(!BuildEnv::is_kernel_family_windows()) {\n\t\tif (auto* smartctl_search_check = this->lookup_widget<Gtk::CheckButton*>(\"search_in_smartmontools_first_check\")) {\n\t\t\tsmartctl_search_check->hide();\n\t\t}\n\t}\n\n\timport_config();\n\n\t// show();\n}\n\n\n\nvoid GscPreferencesWindow::set_main_window(GscMainWindow* window)\n{\n\tmain_window_ = window;\n}\n\n\n\n\nvoid GscPreferencesWindow::update_device_widgets(const std::string& device, const std::string& type, const std::string& params)\n{\n\tif (auto* entry = this->lookup_widget<Gtk::Entry*>(\"device_options_device_entry\"))\n\t\tentry->set_text(device);\n\n\tif (auto* entry = this->lookup_widget<Gtk::Entry*>(\"device_options_type_entry\"))\n\t\tentry->set_text(type);\n\n\tif (auto* entry = this->lookup_widget<Gtk::Entry*>(\"device_options_parameter_entry\"))\n\t\tentry->set_text(params);\n}\n\n\n\nvoid GscPreferencesWindow::device_widget_set_remove_possible(bool b)\n{\n\tif (auto* button = this->lookup_widget<Gtk::Button*>(\"device_options_remove_device_button\"))\n\t\tbutton->set_sensitive(b);\n}\n\n\n\nnamespace {\n\n\t/// Set configuration in a smart way - don't set the defaults.\n\ttemplate<typename T>\n\tvoid prefs_config_set(const std::string& path, T&& data)\n\t{\n\t\tusing data_type = std::decay_t<T>;\n\t\tauto def_value = rconfig::get_default_data<data_type>(path);\n\t\tif (def_value != data) {\n\t\t\trconfig::set_data(path, data);\n\t\t} else {\n\t\t\trconfig::unset_data(path);\n\t\t}\n\t}\n\n}\n\n\n\nvoid GscPreferencesWindow::import_config()\n{\n\t// Clear and fill the entries.\n\n\t// ------- General tab\n\n\tbool scan_on_startup = rconfig::get_data<bool>(\"gui/scan_on_startup\");\n\tif (auto* check = this->lookup_widget<Gtk::CheckButton*>(\"scan_on_startup_check\"))\n\t\tcheck->set_active(scan_on_startup);\n\n\tbool show_smart_capable_only = rconfig::get_data<bool>(\"gui/show_smart_capable_only\");\n\tif (auto* check = this->lookup_widget<Gtk::CheckButton*>(\"show_smart_capable_only_check\"))\n\t\tcheck->set_active(show_smart_capable_only);\n\n\tbool icons_show_device_name = rconfig::get_data<bool>(\"gui/icons_show_device_name\");\n\tif (auto* check = this->lookup_widget<Gtk::CheckButton*>(\"show_device_name_under_icon_check\"))\n\t\tcheck->set_active(icons_show_device_name);\n\n\tbool icons_show_serial_number = rconfig::get_data<bool>(\"gui/icons_show_serial_number\");\n\tif (auto* check = this->lookup_widget<Gtk::CheckButton*>(\"show_serial_number_under_icon_check\"))\n\t\tcheck->set_active(icons_show_serial_number);\n\n\tbool win32_search_smartctl_in_smartmontools = rconfig::get_data<bool>(\"system/win32_search_smartctl_in_smartmontools\");\n\tif (auto* check = this->lookup_widget<Gtk::CheckButton*>(\"search_in_smartmontools_first_check\"))\n\t\tcheck->set_active(win32_search_smartctl_in_smartmontools);\n\n\tauto smartctl_binary = rconfig::get_data<std::string>(\"system/smartctl_binary\");\n\tif (auto* entry = this->lookup_widget<Gtk::Entry*>(\"smartctl_binary_entry\"))\n\t\tentry->set_text(smartctl_binary);\n\n\tauto smartctl_options = rconfig::get_data<std::string>(\"system/smartctl_options\");\n\tif (auto* entry = this->lookup_widget<Gtk::Entry*>(\"smartctl_options_entry\"))\n\t\tentry->set_text(smartctl_options);\n\n\n\t// ------- Drives tab\n\n\tauto device_blacklist_patterns = rconfig::get_data<std::string>(\"system/device_blacklist_patterns\");\n\tif (auto* entry = this->lookup_widget<Gtk::Entry*>(\"device_blacklist_patterns_entry\"))\n\t\tentry->set_text(device_blacklist_patterns);\n\n\tif (device_options_treeview_) {\n\t\tdevice_options_treeview_->set_device_map(app_config_get_device_option_map());\n\t}\n\n}\n\n\n\nvoid GscPreferencesWindow::export_config()\n{\n\t// we don't clear config here, it might contain non-dialog options too.\n\n\t// ------- General tab\n\n\tif (auto* check = this->lookup_widget<Gtk::CheckButton*>(\"scan_on_startup_check\"))\n\t\tprefs_config_set(\"gui/scan_on_startup\", bool(check->get_active()));\n\n\tif (auto* check = this->lookup_widget<Gtk::CheckButton*>(\"show_smart_capable_only_check\"))\n\t\tprefs_config_set(\"gui/show_smart_capable_only\", bool(check->get_active()));\n\n\tif (auto* check = this->lookup_widget<Gtk::CheckButton*>(\"show_device_name_under_icon_check\"))\n\t\tprefs_config_set(\"gui/icons_show_device_name\", bool(check->get_active()));\n\n\tif (auto* check = this->lookup_widget<Gtk::CheckButton*>(\"show_serial_number_under_icon_check\"))\n\t\tprefs_config_set(\"gui/icons_show_serial_number\", bool(check->get_active()));\n\n\tif (auto* check = this->lookup_widget<Gtk::CheckButton*>(\"search_in_smartmontools_first_check\"))\n\t\tprefs_config_set(\"system/win32_search_smartctl_in_smartmontools\", bool(check->get_active()));\n\n\tif (auto* entry = this->lookup_widget<Gtk::Entry*>(\"smartctl_binary_entry\"))\n\t\tprefs_config_set(\"system/smartctl_binary\", std::string(entry->get_text()));\n\n\tif (auto* entry = this->lookup_widget<Gtk::Entry*>(\"smartctl_options_entry\"))\n\t\tprefs_config_set(\"system/smartctl_options\", std::string(entry->get_text()));\n\n\n\t// ------- Drives tab\n\n\tif (auto* entry = this->lookup_widget<Gtk::Entry*>(\"device_blacklist_patterns_entry\"))\n\t\tprefs_config_set(\"system/device_blacklist_patterns\", std::string(entry->get_text()));\n\n\tauto devmap = device_options_treeview_->get_device_map();\n\tprefs_config_set(\"system/smartctl_device_options\", devmap);\n}\n\n\n\nbool GscPreferencesWindow::on_delete_event([[maybe_unused]] GdkEventAny* e)\n{\n\ton_window_cancel_button_clicked();\n\treturn true;  // event handled\n}\n\n\n\nvoid GscPreferencesWindow::on_window_cancel_button_clicked()\n{\n\tdestroy_instance();\n}\n\n\n\nvoid GscPreferencesWindow::on_window_ok_button_clicked()\n{\n\t// Check if device map contains drives with empty device names or parameters.\n\tauto devmap = device_options_treeview_->get_device_map();\n\tbool contains_empty = false;\n\tfor (const auto& iter : devmap.value) {\n\t\tif (iter.first.first.empty() || iter.second.empty()) {\n\t\t\tcontains_empty = true;\n\t\t}\n\t}\n\n\tif (contains_empty) {\n\t\tGtk::MessageDialog dialog(*this,\n\t\t\t\t_(\"You have specified an empty Parameters field for one or more entries\"\n\t\t\t\t\" in Per-Drive Smartctl Parameters section. Such entries will be discarded.\\n\"\n\t\t\t\t\"\\nDo you want to continue?\"),\n\t\t\t\ttrue, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO, true);\n\t\tif (dialog.run() != Gtk::RESPONSE_YES) {\n\t\t\treturn;\n\t\t}\n\t}\n\n\texport_config();\n\n\tif (main_window_) {\n\t\tmain_window_->show_prefs_updated_message();\n\t}\n\n\tdestroy_instance();\n}\n\n\n\nvoid GscPreferencesWindow::on_window_reset_all_button_clicked()\n{\n\tGtk::MessageDialog dialog(*this,\n\t\t\t\"\\n\"s + _(\"Are you sure you want to reset all program settings to their defaults?\") + \"\\n\",\n\t\t\ttrue, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO, true);\n\n\tif (dialog.run() == Gtk::RESPONSE_YES) {\n\t\trconfig::clear_config();\n\t\timport_config();\n\t\t// close the window, because the user might get the impression that \"Cancel\" will revert.\n\t\tdestroy_instance();\n\t}\n}\n\n\n\nvoid GscPreferencesWindow::on_smartctl_binary_browse_button_clicked()\n{\n\tauto* entry = this->lookup_widget<Gtk::Entry*>(\"smartctl_binary_entry\");\n\tauto path = hz::fs_path_from_string(std::string(entry->get_text()));\n\n\tint result = 0;\n\n\tGlib::RefPtr<Gtk::FileFilter> specific_filter, all_filter;\n\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\tspecific_filter = Gtk::FileFilter::create();\n\t\tspecific_filter->set_name(_(\"Executable Files\"));\n\t\tspecific_filter->add_pattern(\"*.exe\");\n\n\t\tall_filter = Gtk::FileFilter::create();\n\t\tall_filter->set_name(_(\"All Files\"));\n\t\tall_filter->add_pattern(\"*\");\n\t}\n\n#if GTK_CHECK_VERSION(3, 20, 0)\n\tstd::unique_ptr<GtkFileChooserNative, decltype(&g_object_unref)> dialog(gtk_file_chooser_native_new(\n\t\t\t_(\"Choose Smartctl Binary...\"), this->gobj(), GTK_FILE_CHOOSER_ACTION_OPEN, nullptr, nullptr),\n\t\t\t&g_object_unref);\n\n\tif (path.is_absolute())\n\t\tgtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dialog.get()), hz::fs_path_to_string(path).c_str());\n\n\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\tgtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), specific_filter->gobj());\n\t\tgtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), all_filter->gobj());\n\t}\n\n\tresult = gtk_native_dialog_run(GTK_NATIVE_DIALOG(dialog.get()));\n\n#else\n\tGtk::FileChooserDialog dialog(*this, _(\"Choose Smartctl Binary...\"),\n\t\t\tGtk::FILE_CHOOSER_ACTION_OPEN);\n\n\t// Add response buttons the the dialog\n\tdialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);\n\tdialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_ACCEPT);\n\n\tif constexpr(BuildEnv::is_kernel_family_windows()) {\n\t\tdialog.add_filter(specific_filter);\n\t\tdialog.add_filter(all_filter);\n\t}\n\n\t// Note: This works on absolute paths only (otherwise it's gtk warning).\n\tif (path.is_absolute())\n\t\tdialog.set_filename(hz::fs_path_to_string(path));  // change to its dir and select it if exists.\n\n\t// Show the dialog and wait for a user response\n\tresult = dialog.run();  // the main cycle blocks here\n#endif\n\n\t// Handle the response\n\tswitch (result) {\n\t\tcase Gtk::RESPONSE_ACCEPT:\n\t\t{\n\t\t\tGlib::ustring file;\n#if GTK_CHECK_VERSION(3, 20, 0)\n\t\t\tfile = app_ustring_from_gchar(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog.get())));\n#else\n\t\t\tfile = dialog.get_filename();  // in fs encoding\n#endif\n\t\t\tentry->set_text(file);\n\t\t\tbreak;\n\t\t}\n\n\t\tcase Gtk::RESPONSE_CANCEL: case Gtk::RESPONSE_DELETE_EVENT:\n\t\t\t// nothing, the dialog is closed already\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Unknown dialog response code: \" << result << \".\\n\");\n\t\t\tbreak;\n\t}\n\n}\n\n\n\n\nvoid GscPreferencesWindow::on_device_options_remove_device_button_clicked()\n{\n\tdevice_options_treeview_->remove_selected_row();\n}\n\n\n\nvoid GscPreferencesWindow::on_device_options_add_device_button_clicked()\n{\n\tstd::string dev, type, par;\n\n\tif (auto* entry = this->lookup_widget<Gtk::Entry*>(\"device_options_device_entry\"))\n\t\tdev = entry->get_text();\n\n\tif (auto* entry = this->lookup_widget<Gtk::Entry*>(\"device_options_type_entry\"))\n\t\ttype = entry->get_text();\n\n\tif (auto* entry = this->lookup_widget<Gtk::Entry*>(\"device_options_parameter_entry\"))\n\t\tpar = entry->get_text();\n\n\tif (device_options_treeview_->has_selected_row()) {\n\t\tdevice_options_treeview_->add_new_row(\"\", \"\", \"\");  // without this it would clone the existing.\n\t} else {\n\t\tdevice_options_treeview_->add_new_row(dev, type, par);\n\t}\n}\n\n\n\nvoid GscPreferencesWindow::on_device_options_device_entry_changed()\n{\n\tif (auto* entry = this->lookup_widget<Gtk::Entry*>(\"device_options_device_entry\"))\n\t\tdevice_options_treeview_->update_selected_row_device(entry->get_text());\n}\n\n\n\nvoid GscPreferencesWindow::on_device_options_type_entry_changed()\n{\n\tif (auto* entry = this->lookup_widget<Gtk::Entry*>(\"device_options_type_entry\"))\n\t\tdevice_options_treeview_->update_selected_row_type(entry->get_text());\n}\n\n\n\nvoid GscPreferencesWindow::on_device_options_parameter_entry_changed()\n{\n\tif (auto* entry = this->lookup_widget<Gtk::Entry*>(\"device_options_parameter_entry\"))\n\t\tdevice_options_treeview_->update_selected_row_params(entry->get_text());\n}\n\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_preferences_window.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#ifndef GSC_PREFERENCES_WINDOW_H\n#define GSC_PREFERENCES_WINDOW_H\n\n#include <gtkmm.h>\n\n#include \"applib/app_builder_widget.h\"\n\n\n\nclass GscPreferencesDeviceOptionsTreeView;  // defined in cpp file\n\nclass GscMainWindow;  // declared in gsc_main_window.h\n\n\n\n/// The Preferences window.\n/// Use create() / destroy() with this class instead of new / delete!\nclass GscPreferencesWindow : public AppBuilderWidget<GscPreferencesWindow, true> {\n\tpublic:\n\n\t\t// name of ui file (without .ui extension) for AppBuilderWidget\n\t\tstatic inline const std::string_view ui_name = \"gsc_preferences_window\";\n\n\n\t\t/// Constructor, GtkBuilder needs this.\n\t\tGscPreferencesWindow(BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui);\n\n\n\t\t/// Set main window so that we can manipulate it\n\t\tvoid set_main_window(GscMainWindow* window);\n\n\n\t\t/// Update the device widgets (per-device parameters), used by GscPreferencesDeviceOptionsTreeView.\n\t\tvoid update_device_widgets(const std::string& device, const std::string& type, const std::string& params);\n\n\t\t/// Set whether to allow removing device entries, used by GscPreferencesDeviceOptionsTreeView.\n\t\tvoid device_widget_set_remove_possible(bool b);\n\n\n\tprotected:\n\n\t\t/// Import the configuration into UI\n\t\tvoid import_config();\n\n\t\t/// Export the configuration from UI\n\t\tvoid export_config();\n\n\n\t\t// -------------------- callbacks\n\n\t\t// ---------- overriden virtual methods\n\n\t\t/// Destroy this object on delete event (by default it calls hide()).\n\t\t/// Reimplemented from Gtk::Window.\n\t\tbool on_delete_event(GdkEventAny* e) override;\n\n\n\t\t// ---------- other callbacks\n\n\t\t/// Button click callback\n\t\tvoid on_window_cancel_button_clicked();\n\n\t\t/// Button click callback\n\t\tvoid on_window_ok_button_clicked();\n\n\t\t/// Button click callback\n\t\tvoid on_window_reset_all_button_clicked();\n\n\n\t\t/// Button click callback\n\t\tvoid on_smartctl_binary_browse_button_clicked();\n\n\t\t/// Button click callback\n\t\tvoid on_device_options_remove_device_button_clicked();\n\n\t\t/// Button click callback\n\t\tvoid on_device_options_add_device_button_clicked();\n\n\t\t/// Callback\n\t\tvoid on_device_options_device_entry_changed();\n\n\t\t/// Callback\n\t\tvoid on_device_options_type_entry_changed();\n\n\t\t/// Callback\n\t\tvoid on_device_options_parameter_entry_changed();\n\n\n\tprivate:\n\n\t\tGscMainWindow* main_window_ = nullptr;  ///< Main window that called us.\n\n\t\tGscPreferencesDeviceOptionsTreeView* device_options_treeview_ = nullptr;  ///< Device options tree view\n\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_startup_settings.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#ifndef GSC_STARTUP_SETTINGS_H\n#define GSC_STARTUP_SETTINGS_H\n\n\n\n/// Settings passed using command-line options\nstruct GscStartupSettings {\n\n\tbool no_scan = false;  ///< No scanning on startup\n\tstd::vector<std::string> load_virtuals;  ///< Virtual files to load\n\tstd::vector<std::string> add_devices;  ///< Devices to add (with options)\n\tbool forget_manual_devices = false;  ///< Forget all manually added devices\n\n};\n\n\n\n/// Get startup settings\n[[nodiscard]] inline GscStartupSettings& get_startup_settings()\n{\n\tstatic GscStartupSettings startup_settings;\n\treturn startup_settings;\n}\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_text_window.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup gsc\n/// \\weakgroup gsc\n/// @{\n\n#ifndef GSC_TEXT_WINDOW_H\n#define GSC_TEXT_WINDOW_H\n\n#include <glibmm.h>\n#include <gtkmm.h>\n#include <gdk/gdk.h>  // GDK_KEY_Escape\n#include <memory>\n#include <variant>\n\n#include \"hz/debug.h\"\n#include \"hz/fs.h\"\n#include \"rconfig/rconfig.h\"\n\n#include \"applib/app_builder_widget.h\"\n#include \"applib/app_gtkmm_tools.h\"\n\n\n\n/// GscTextWindow InstanceSwitch parameter for smartctl output instance.\nstruct SmartctlOutputInstance {\n\tstatic constexpr bool multi_instance = true;\n};\n\n\n\n/// A generic text-displaying window.\n/// Use create() / destroy() with this class instead of new / delete!\n/// \\tparam InstanceSwitch e.g. supply 3 different classes to use 3 different single-instance windows.\ntemplate<class InstanceSwitch>\nclass GscTextWindow : public AppBuilderWidget<GscTextWindow<InstanceSwitch>, InstanceSwitch::multi_instance> {\n\tpublic:\n\n\t\t// name of ui file (without .ui extension) for AppBuilderWidget\n\t\tstatic inline const std::string_view ui_name = \"gsc_text_window\";\n\n\n\t\t/// Constructor, GtkBuilder needs this.\n\t\tGscTextWindow(typename Gtk::Window::BaseObjectType* gtkcobj, Glib::RefPtr<Gtk::Builder> ui)\n\t\t\t\t: AppBuilderWidget<GscTextWindow<InstanceSwitch>, InstanceSwitch::multi_instance>(gtkcobj, std::move(ui))\n\t\t{\n\t\t\t// Connect callbacks\n\n\t\t\tGtk::Button* save_as_button = nullptr;\n\t\t\tAPP_BUILDER_AUTO_CONNECT(save_as_button, clicked);\n\n\t\t\tGtk::Button* close_window_button = nullptr;\n\t\t\tAPP_BUILDER_AUTO_CONNECT(close_window_button, clicked);\n\n\n\t\t\t// Accelerators\n\n\t\t\tGlib::RefPtr<Gtk::AccelGroup> accel_group = this->get_accel_group();\n\t\t\tif (close_window_button) {\n\t\t\t\tclose_window_button->add_accelerator(\"clicked\", accel_group, GDK_KEY_Escape,\n\t\t\t\t\t\tGdk::ModifierType(0), Gtk::AccelFlags(0));\n\t\t\t}\n\n\t\t\t// show();  // better show later, after set_text().\n\n\t\t\tdefault_title_ = this->get_title();\n\t\t}\n\n\n\t\tvoid set_text_from_command(const Glib::ustring& title, const std::string& contents)\n\t\t{\n\t\t\tthis->contents_ = contents;  // we save it to prevent its mangling through the widget\n\t\t\tthis->set_text_helper(title, true, true);\n\t\t}\n\n\n\t\tvoid set_text(const Glib::ustring& title, const Glib::ustring& contents,\n\t\t\t\tbool save_visible = false, bool use_monospace = false)\n\t\t{\n\t\t\tthis->contents_ = contents;\n\t\t\tthis->set_text_helper(title, save_visible, use_monospace);\n\t\t}\n\n\n\t\t/// Set the text to display\n\t\tvoid set_text_helper(const Glib::ustring& title,\n\t\t\t\tbool save_visible = false, bool use_monospace = false)\n\t\t{\n\t\t\tthis->set_title(title + \" - \" + default_title_);  // something - gsmartcontrol\n\n\t\t\tGtk::TextView* textview = this->template lookup_widget<Gtk::TextView*>(\"main_textview\");\n\t\t\tif (textview) {\n\t\t\t\tGlib::RefPtr<Gtk::TextBuffer> buffer = textview->get_buffer();\n\n\t\t\t\tif (std::holds_alternative<std::string>(contents_)) {\n\t\t\t\t\tbuffer->set_text(app_make_valid_utf8_from_command_output(std::get<std::string>(contents_)));\n\t\t\t\t} else {\n\t\t\t\t\tbuffer->set_text(std::get<Glib::ustring>(contents_));\n\t\t\t\t}\n\n\t\t\t\tif (use_monospace) {\n\t\t\t\t\tGlib::RefPtr<Gtk::TextTag> tag = buffer->create_tag();\n\t\t\t\t\ttag->property_family() = \"Monospace\";\n\t\t\t\t\tbuffer->apply_tag(tag, buffer->begin(), buffer->end());\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tGtk::Button* save_as_button = this->template lookup_widget<Gtk::Button*>(\"save_as_button\");\n\t\t\tif (save_as_button) {\n\t\t\t\tif (save_visible) {\n\t\t\t\t\tsave_as_button->set_sensitive(true);\n\t\t\t\t\tsave_as_button->show();\n\t\t\t\t} else {\n\t\t\t\t\tsave_as_button->hide();\n\t\t\t\t\tsave_as_button->set_sensitive(false);  // disable accelerators\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\n\t\t/// Set the default file name to be shown on Save As\n\t\tvoid set_save_filename(const std::string& filename)\n\t\t{\n\t\t\tsave_filename_ = filename;\n\t\t}\n\n\n\tprotected:\n\n\n\t\t// -------------------- callbacks\n\n\n\t\t// ---------- overriden virtual methods\n\n\t\t/// Destroy this object on delete event (by default it calls hide()).\n\t\t/// Reimplemented from Gtk::Window.\n\t\tbool on_delete_event([[maybe_unused]] GdkEventAny* e) override\n\t\t{\n\t\t\ton_close_window_button_clicked();\n\t\t\treturn true;  // event handled\n\t\t}\n\n\n\t\t// ---------- other callbacks\n\n\t\t/// Button click callback\n\t\tvoid on_save_as_button_clicked()\n\t\t{\n\t\t\tstatic std::string last_dir;\n\t\t\tif (last_dir.empty()) {\n\t\t\t\tlast_dir = rconfig::get_data<std::string>(\"gui/drive_data_open_save_dir\");\n\t\t\t}\n\t\t\tint result = 0;\n\n\t\t\tGlib::RefPtr<Gtk::FileFilter> specific_filter = Gtk::FileFilter::create();\n\t\t\tspecific_filter->set_name(_(\"JSON and Text Files\"));\n\t\t\tspecific_filter->add_pattern(\"*.json\");\n\t\t\tspecific_filter->add_pattern(\"*.txt\");\n\n\t\t\tGlib::RefPtr<Gtk::FileFilter> all_filter = Gtk::FileFilter::create();\n\t\t\tall_filter->set_name(_(\"All Files\"));\n\t\t\tall_filter->add_pattern(\"*\");\n\n#if GTK_CHECK_VERSION(3, 20, 0)\n\t\t\tstd::unique_ptr<GtkFileChooserNative, decltype(&g_object_unref)> dialog(gtk_file_chooser_native_new(\n\t\t\t\t\t_(\"Save Data As...\"), this->gobj(), GTK_FILE_CHOOSER_ACTION_SAVE, nullptr, nullptr),\n\t\t\t\t\t&g_object_unref);\n\n\t\t\tgtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog.get()), TRUE);\n\n\t\t\tgtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), specific_filter->gobj());\n\t\t\tgtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog.get()), all_filter->gobj());\n\n\t\t\tif (!last_dir.empty())\n\t\t\t\tgtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog.get()), last_dir.c_str());\n\n\t\t\tif (!save_filename_.empty())\n\t\t\t\tgtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog.get()), save_filename_.c_str());\n\n\t\t\tresult = gtk_native_dialog_run(GTK_NATIVE_DIALOG(dialog.get()));\n\n#else\n\t\t\tGtk::FileChooserDialog dialog(*this, _(\"Save Data As...\"),\n\t\t\t\t\tGtk::FILE_CHOOSER_ACTION_SAVE);\n\n\t\t\t// Add response buttons the the dialog\n\t\t\tdialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);\n\t\t\tdialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_ACCEPT);\n\n\t\t\tdialog.set_do_overwrite_confirmation(true);\n\n\t\t\tdialog.add_filter(specific_filter);\n\t\t\tdialog.add_filter(all_filter);\n\n\t\t\tif (!last_dir.empty())\n\t\t\t\tdialog.set_current_folder(last_dir);\n\n\t\t\tif (!save_filename_.empty())\n\t\t\t\tdialog.set_current_name(save_filename_);\n\n\t\t\t// Show the dialog and wait for a user response\n\t\t\tresult = dialog.run();  // the main cycle blocks here\n#endif\n\n\t\t\t// Handle the response\n\t\t\tswitch (result) {\n\t\t\t\tcase Gtk::RESPONSE_ACCEPT:\n\t\t\t\t{\n\t\t\t\t\thz::fs::path file;\n#if GTK_CHECK_VERSION(3, 20, 0)\n\t\t\t\t\tfile = hz::fs_path_from_string(app_string_from_gchar(gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog.get()))));\n\t\t\t\t\tlast_dir = hz::fs_path_to_string(file.parent_path());\n#else\n\t\t\t\t\tfile = hz::fs_path_from_string(dialog.get_filename());  // in fs encoding\n\t\t\t\t\tlast_dir = dialog.get_current_folder();  // save for the future\n#endif\n\t\t\t\t\trconfig::set_data(\"gui/drive_data_open_save_dir\", last_dir);\n\n\t\t\t\t\tif (file.extension() != \".json\" && file.extension() != \".txt\") {\n\t\t\t\t\t\tfile += \".json\";\n\t\t\t\t\t}\n\n\t\t\t\t\tstd::string text;\n\t\t\t\t\tif (std::holds_alternative<std::string>(contents_)) {\n\t\t\t\t\t\ttext = std::get<std::string>(contents_);\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttext = std::get<Glib::ustring>(contents_);\n\t\t\t\t\t}\n\t\t\t\t\tauto ec = hz::fs_file_put_contents(file, text);\n\t\t\t\t\tif (ec) {\n\t\t\t\t\t\tgui_show_error_dialog(_(\"Cannot save data to file\"), ec.message(), this);\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\n\t\t\t\tcase Gtk::RESPONSE_CANCEL: case Gtk::RESPONSE_DELETE_EVENT:\n\t\t\t\t\t// nothing, the dialog is closed already\n\t\t\t\t\tbreak;\n\n\t\t\t\tdefault:\n\t\t\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG << \"Unknown dialog response code: \" << result << \".\\n\");\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\n\t\t/// Button click callback\n\t\tvoid on_close_window_button_clicked()\n\t\t{\n\t\t\tthis->destroy_instance();\n\t\t}\n\n\n\tprivate:\n\n\t\tGlib::ustring default_title_;  ///< Window title\n\n\t\t/// The text to display\n\t\tstd::variant<\n\t\t\tstd::string,  // command output\n\t\t\tGlib::ustring  // utf-8 text\n\t\t> contents_;\n\n\t\tstd::string save_filename_;  ///< Default filename for Save As\n\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/gui/gsc_winres.in.rc",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2008 - 2025 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n\ngsmartcontrol ICON \"gsmartcontrol.ico\"\n\n// Embed manifest in exe\n1 24 \"gsmartcontrol.exe.manifest\"\n\n1 VERSIONINFO\n    FILEVERSION 0,0,0,0\n    PRODUCTVERSION 0,0,0,0\n//  FILEOS VOS__WINDOWS32\n//  FILETYPE VFT_FONT\nBEGIN\n    BLOCK \"StringFileInfo\"\n    BEGIN\n        BLOCK \"04090025\"\n        BEGIN\n            VALUE \"FileDescription\", \"GSmartControl - Hard disk drive and SSD health inspection tool\"\n            VALUE \"FileVersion\", \"@CMAKE_PROJECT_VERSION@\"\n            VALUE \"InternalName\", \"gsmartcontrol.exe\"\n            VALUE \"LegalCopyright\", \"Copyright (C) 2008 - 2025 Alexander Shaduri\"\n            VALUE \"OriginalFilename\", \"gsmartcontrol.exe\"\n            VALUE \"ProductName\", \"GSmartControl\"\n            VALUE \"ProductVersion\", \"@CMAKE_PROJECT_VERSION@\"\n        END\n    END\n    BLOCK \"VarFileInfo\"\n    BEGIN\n        VALUE \"Translation\", 1033, 37\n    END\nEND\n\n"
  },
  {
    "path": "src/gui/gsmartcontrol.exe.in.manifest",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\" xmlns:asmv3=\"urn:schemas-microsoft-com:asm.v3\">\n\n<!--\nLicense: BSD Zero Clause License file\nCopyright:\n\t(C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n-->\n\n\t<assemblyIdentity version=\"0.0.0.0\"\n\t\tname=\"gsmartcontrol.exe\"\n\t\tprocessorArchitecture=\"@WINDOWS_ARCH@\"\n\t\ttype=\"win32\" />\n\n\t<!-- This is displayed in UAC dialogs -->\n\t<description>GSmartControl - Hard disk drive and SSD health inspection tool</description>\n\n\t<!-- Privilege requirements -->\n\t<ms_asmv2:trustInfo xmlns:ms_asmv2=\"urn:schemas-microsoft-com:asm.v2\">\n\t\t<ms_asmv2:security>\n\t\t\t<ms_asmv2:requestedPrivileges>\n\t\t\t\t<ms_asmv2:requestedExecutionLevel level=\"requireAdministrator\" uiAccess=\"false\" />\n\t\t\t</ms_asmv2:requestedPrivileges>\n\t\t</ms_asmv2:security>\n\t</ms_asmv2:trustInfo>\n\n\t<!-- GTK3 applications on Windows do not support fractional scaling\n\t\t(e.g. 250%), making them too small on such displays.\n\t\tWe try to compensate for this by increasing the font size in the application.-->\n\t<asmv3:application>\n\t\t<asmv3:windowsSettings xmlns=\"http://schemas.microsoft.com/SMI/2005/WindowsSettings\">\n\t\t\t<dpiAware>true</dpiAware>\n\t\t</asmv3:windowsSettings>\n\t</asmv3:application>\n\n</assembly>\n"
  },
  {
    "path": "src/gui/ui/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nset(UI_FILES\n\tgsc_about_dialog.glade\n\tgsc_add_device_window.glade\n\tgsc_executor_log_window.glade\n\tgsc_info_window.glade\n\tgsc_main_window.glade\n\tgsc_preferences_window.glade\n\tgsc_text_window.glade\n)\n\nif (WIN32)\n\tinstall(FILES ${UI_FILES}\n\t\tDESTINATION \"ui/\")\nelse()\n\tinstall(FILES ${UI_FILES}\n\t\tDESTINATION \"${CMAKE_INSTALL_DATADIR}/gsmartcontrol/ui/\")\nendif()\n"
  },
  {
    "path": "src/gui/ui/gsc_about_dialog.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.22.1 \n\nCopyright (C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n\nThis file is part of GSmartControl.\n\nGSmartControl is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nGSmartControl is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with GSmartControl.  If not, see <http://www.gnu.org/licenses/>.\n\n-->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.20\"/>\n  <!-- interface-license-type gplv3 -->\n  <!-- interface-name GSmartControl -->\n  <!-- interface-copyright 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com> -->\n  <object class=\"GtkAboutDialog\" id=\"gsc_about_dialog\">\n    <property name=\"can_focus\">False</property>\n    <property name=\"events\">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>\n    <property name=\"border_width\">6</property>\n    <property name=\"resizable\">False</property>\n    <property name=\"window_position\">center-on-parent</property>\n    <property name=\"destroy_with_parent\">True</property>\n    <property name=\"type_hint\">dialog</property>\n    <property name=\"logo_icon_name\"/>\n    <property name=\"license_type\">gpl-3-0-only</property>\n    <child>\n      <placeholder/>\n    </child>\n    <child internal-child=\"vbox\">\n      <object class=\"GtkBox\" id=\"dialog-vbox1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"events\">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>\n        <property name=\"orientation\">vertical</property>\n        <property name=\"spacing\">6</property>\n        <child internal-child=\"action_area\">\n          <object class=\"GtkButtonBox\" id=\"dialog-action_area1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"events\">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>\n            <property name=\"layout_style\">end</property>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"pack_type\">end</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <placeholder/>\n        </child>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "src/gui/ui/gsc_add_device_window.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.40.0 \n\nCopyright (C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n\nThis file is part of GSmartControl.\n\nGSmartControl is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nGSmartControl is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with GSmartControl.  If not, see <http://www.gnu.org/licenses/>.\n\n-->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.20\"/>\n  <!-- interface-license-type gplv3 -->\n  <!-- interface-name GSmartControl -->\n  <!-- interface-copyright 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com> -->\n  <object class=\"GtkWindow\" id=\"gsc_add_device_window\">\n    <property name=\"can-focus\">False</property>\n    <property name=\"title\" translatable=\"yes\">Add Device - GSmartControl</property>\n    <property name=\"modal\">True</property>\n    <child>\n      <object class=\"GtkBox\" id=\"vbox1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can-focus\">False</property>\n        <property name=\"border-width\">12</property>\n        <property name=\"orientation\">vertical</property>\n        <property name=\"spacing\">12</property>\n        <child>\n          <object class=\"GtkBox\" id=\"vbox2\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">False</property>\n            <property name=\"orientation\">vertical</property>\n            <child>\n              <object class=\"GtkLabel\" id=\"top_info_link_label\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"halign\">start</property>\n                <property name=\"label\" translatable=\"yes\">The &lt;a href=\"%1\"&gt;smartctl man page&lt;/a&gt; contains information on what you can enter here.</property>\n                <property name=\"use-markup\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"padding\">6</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <!-- n-columns=3 n-rows=3 -->\n              <object class=\"GtkGrid\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"row-spacing\">6</property>\n                <property name=\"column-spacing\">12</property>\n                <child>\n                  <object class=\"GtkBox\" id=\"hbox1\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"spacing\">6</property>\n                    <child>\n                      <object class=\"GtkEntry\" id=\"device_name_entry\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"invisible-char\">●</property>\n                        <property name=\"activates-default\">True</property>\n                        <property name=\"primary-icon-activatable\">False</property>\n                        <property name=\"secondary-icon-activatable\">False</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"device_name_browse_button\">\n                        <property name=\"label\" translatable=\"yes\">Browse...</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"receives-default\">True</property>\n                        <property name=\"use-underline\">True</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">1</property>\n                    <property name=\"top-attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"device_type_label\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"halign\">start</property>\n                    <property name=\"label\" translatable=\"yes\">Device type:</property>\n                    <property name=\"use-underline\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"label3\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"tooltip-text\" translatable=\"yes\">Additional smartctl parameters</property>\n                    <property name=\"halign\">start</property>\n                    <property name=\"label\" translatable=\"yes\">Smartctl parameters:</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkComboBoxText\" id=\"device_type_combo\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"has-entry\">True</property>\n                    <child internal-child=\"entry\">\n                      <object class=\"GtkEntry\">\n                        <property name=\"can-focus\">True</property>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">1</property>\n                    <property name=\"top-attach\">1</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkEntry\" id=\"smartctl_params_entry\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"tooltip-text\" translatable=\"yes\">Additional smartctl parameters</property>\n                    <property name=\"invisible-char\">●</property>\n                    <property name=\"activates-default\">True</property>\n                    <property name=\"primary-icon-activatable\">False</property>\n                    <property name=\"secondary-icon-activatable\">False</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">1</property>\n                    <property name=\"top-attach\">2</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkLabel\" id=\"device_name_label\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"halign\">start</property>\n                    <property name=\"label\" translatable=\"yes\">Device name:</property>\n                    <property name=\"use-underline\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"left-attach\">0</property>\n                    <property name=\"top-attach\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n                <child>\n                  <placeholder/>\n                </child>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">True</property>\n                <property name=\"padding\">6</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkCheckButton\" id=\"auto_add_device_check\">\n                <property name=\"label\" translatable=\"yes\">Automatically add this device on startup</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"receives-default\">False</property>\n                <property name=\"halign\">start</property>\n                <property name=\"active\">True</property>\n                <property name=\"draw-indicator\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label2\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"halign\">start</property>\n                <property name=\"wrap\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"padding\">6</property>\n                <property name=\"position\">3</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkButtonBox\" id=\"hbuttonbox1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">False</property>\n            <property name=\"spacing\">6</property>\n            <property name=\"layout-style\">end</property>\n            <child>\n              <object class=\"GtkButton\" id=\"window_cancel_button\">\n                <property name=\"label\">gtk-cancel</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"receives-default\">True</property>\n                <property name=\"use-stock\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">False</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"window_ok_button\">\n                <property name=\"label\">gtk-ok</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"can-default\">True</property>\n                <property name=\"has-default\">True</property>\n                <property name=\"receives-default\">True</property>\n                <property name=\"use-stock\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">False</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"pack-type\">end</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "src/gui/ui/gsc_executor_log_window.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.22.1 \n\nCopyright (C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n\nThis file is part of GSmartControl.\n\nGSmartControl is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nGSmartControl is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with GSmartControl.  If not, see <http://www.gnu.org/licenses/>.\n\n-->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.20\"/>\n  <!-- interface-license-type gplv3 -->\n  <!-- interface-name GSmartControl -->\n  <!-- interface-copyright 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com> -->\n  <object class=\"GtkWindow\" id=\"gsc_executor_log_window\">\n    <property name=\"can_focus\">False</property>\n    <property name=\"title\" translatable=\"yes\">Execution Log - GSmartControl</property>\n    <property name=\"default_width\">900</property>\n    <property name=\"default_height\">650</property>\n    <property name=\"destroy_with_parent\">True</property>\n    <child>\n      <placeholder/>\n    </child>\n    <child>\n      <object class=\"GtkBox\" id=\"vbox1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"border_width\">12</property>\n        <property name=\"orientation\">vertical</property>\n        <property name=\"spacing\">12</property>\n        <child>\n          <object class=\"GtkPaned\" id=\"hpaned1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">True</property>\n            <property name=\"position\">180</property>\n            <child>\n              <object class=\"GtkBox\" id=\"vbox3\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"orientation\">vertical</property>\n                <property name=\"spacing\">6</property>\n                <child>\n                  <object class=\"GtkScrolledWindow\" id=\"scrolledwindow1\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"shadow_type\">in</property>\n                    <child>\n                      <object class=\"GtkTreeView\" id=\"command_list_treeview\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"tooltip_text\" translatable=\"yes\">Commands executed</property>\n                        <property name=\"reorderable\">True</property>\n                        <child internal-child=\"selection\">\n                          <object class=\"GtkTreeSelection\"/>\n                        </child>\n                      </object>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">True</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">0</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"hbox3\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"homogeneous\">True</property>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label5\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkButton\" id=\"clear_command_list_button\">\n                        <property name=\"label\">gtk-clear</property>\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"receives_default\">True</property>\n                        <property name=\"tooltip_text\" translatable=\"yes\">Clear command list</property>\n                        <property name=\"use_stock\">True</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"expand\">False</property>\n                    <property name=\"fill\">True</property>\n                    <property name=\"position\">1</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"resize\">False</property>\n                <property name=\"shrink\">True</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkAlignment\" id=\"alignment1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n                <property name=\"left_padding\">2</property>\n                <child>\n                  <object class=\"GtkBox\" id=\"vbox2\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">False</property>\n                    <property name=\"orientation\">vertical</property>\n                    <property name=\"spacing\">6</property>\n                    <child>\n                      <object class=\"GtkBox\" id=\"command_hbox\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"spacing\">6</property>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"label3\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">False</property>\n                            <property name=\"halign\">start</property>\n                            <property name=\"label\" translatable=\"yes\">Command:</property>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">False</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">0</property>\n                          </packing>\n                        </child>\n                        <child>\n                          <object class=\"GtkEntry\" id=\"command_entry\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">True</property>\n                            <property name=\"tooltip_text\" translatable=\"yes\">Command with parameters</property>\n                            <property name=\"editable\">False</property>\n                            <property name=\"activates_default\">True</property>\n                            <property name=\"truncate_multiline\">True</property>\n                            <property name=\"primary_icon_activatable\">False</property>\n                            <property name=\"secondary_icon_activatable\">False</property>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">True</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">1</property>\n                          </packing>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"padding\">3</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkLabel\" id=\"label4\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">False</property>\n                        <property name=\"halign\">start</property>\n                        <property name=\"label\" translatable=\"yes\">Output:</property>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScrolledWindow\" id=\"scrolledwindow2\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can_focus\">True</property>\n                        <property name=\"shadow_type\">in</property>\n                        <child>\n                          <object class=\"GtkTextView\" id=\"output_textview\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can_focus\">True</property>\n                            <property name=\"tooltip_text\" translatable=\"yes\">Complete output of the command</property>\n                            <property name=\"editable\">False</property>\n                            <property name=\"left_margin\">5</property>\n                            <property name=\"right_margin\">5</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">2</property>\n                      </packing>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"resize\">True</property>\n                <property name=\"shrink\">True</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">True</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"hbox1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"spacing\">6</property>\n            <property name=\"homogeneous\">True</property>\n            <child>\n              <object class=\"GtkButton\" id=\"window_save_current_button\">\n                <property name=\"label\" translatable=\"yes\">_Save Current</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"tooltip_text\" translatable=\"yes\">Save currently shown output</property>\n                <property name=\"use_underline\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"window_save_all_button\">\n                <property name=\"label\" translatable=\"yes\">Save _All</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"tooltip_text\" translatable=\"yes\">Save complete log of command execution (useful when reporting bugs)</property>\n                <property name=\"use_underline\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label2\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"window_close_button\">\n                <property name=\"label\">gtk-close</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"tooltip_text\" translatable=\"yes\">Close this window</property>\n                <property name=\"use_stock\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">3</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "src/gui/ui/gsc_info_window.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.40.0 \n\nCopyright (C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n\nThis file is part of GSmartControl.\n\nGSmartControl is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nGSmartControl is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with GSmartControl.  If not, see <http://www.gnu.org/licenses/>.\n\n-->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.20\"/>\n  <!-- interface-license-type gplv3 -->\n  <!-- interface-name GSmartControl -->\n  <!-- interface-copyright 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com> -->\n  <object class=\"GtkListStore\" id=\"model1\">\n    <columns>\n      <!-- column-name gchararray -->\n      <column type=\"gchararray\"/>\n    </columns>\n    <data>\n      <row>\n        <col id=\"0\" translatable=\"yes\">These are dummy entries</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Immediate Offline</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Short</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Extended</col>\n      </row>\n      <row>\n        <col id=\"0\" translatable=\"yes\">Conveyance</col>\n      </row>\n    </data>\n  </object>\n  <object class=\"GtkWindow\" id=\"gsc_info_window\">\n    <property name=\"can-focus\">False</property>\n    <property name=\"title\" translatable=\"yes\">Device Information - GSmartControl</property>\n    <property name=\"window-position\">center-on-parent</property>\n    <property name=\"default-width\">850</property>\n    <property name=\"default-height\">600</property>\n    <property name=\"destroy-with-parent\">True</property>\n    <child>\n      <object class=\"GtkBox\" id=\"vbox1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can-focus\">False</property>\n        <property name=\"border-width\">12</property>\n        <property name=\"orientation\">vertical</property>\n        <property name=\"spacing\">12</property>\n        <child>\n          <object class=\"GtkBox\" id=\"vbox2\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">False</property>\n            <property name=\"orientation\">vertical</property>\n            <property name=\"spacing\">6</property>\n            <child>\n              <object class=\"GtkAlignment\" id=\"alignment1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"left-padding\">6</property>\n                <property name=\"right-padding\">6</property>\n                <child>\n                  <object class=\"GtkBox\" id=\"device_name_label_hbox\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <child>\n                      <placeholder/>\n                    </child>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"expand\">False</property>\n                <property name=\"fill\">False</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkNotebook\" id=\"main_notebook\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"has-focus\">True</property>\n                <child>\n                  <object class=\"GtkBox\" id=\"general_tab_vbox\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"orientation\">vertical</property>\n                    <child>\n                      <object class=\"GtkScrolledWindow\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"shadow-type\">in</property>\n                        <child>\n                          <object class=\"GtkViewport\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"border-width\">6</property>\n                            <child>\n                              <!-- n-columns=3 n-rows=3 -->\n                              <object class=\"GtkGrid\" id=\"identity_table\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">False</property>\n                                <property name=\"halign\">center</property>\n                                <property name=\"valign\">center</property>\n                                <property name=\"hexpand\">True</property>\n                                <property name=\"border-width\">12</property>\n                                <property name=\"row-spacing\">6</property>\n                                <property name=\"column-spacing\">12</property>\n                                <child>\n                                  <placeholder/>\n                                </child>\n                                <child>\n                                  <placeholder/>\n                                </child>\n                                <child>\n                                  <placeholder/>\n                                </child>\n                                <child>\n                                  <placeholder/>\n                                </child>\n                                <child>\n                                  <placeholder/>\n                                </child>\n                                <child>\n                                  <placeholder/>\n                                </child>\n                                <child>\n                                  <placeholder/>\n                                </child>\n                                <child>\n                                  <placeholder/>\n                                </child>\n                                <child>\n                                  <placeholder/>\n                                </child>\n                              </object>\n                            </child>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                  </object>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"general_tab_label\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"has-focus\">True</property>\n                    <property name=\"is-focus\">True</property>\n                    <property name=\"tooltip-text\" translatable=\"yes\">Drive identity and general health information</property>\n                    <property name=\"margin-top\">3</property>\n                    <property name=\"margin-bottom\">3</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">_General</property>\n                    <property name=\"use-markup\">True</property>\n                    <property name=\"use-underline\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"tab-fill\">False</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"attributes_tab_vbox\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"border-width\">6</property>\n                    <property name=\"orientation\">vertical</property>\n                    <property name=\"spacing\">6</property>\n                    <child>\n                      <object class=\"GtkBox\" id=\"attributes_label_vbox\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"orientation\">vertical</property>\n                        <property name=\"spacing\">3</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScrolledWindow\" id=\"scrolledwindow4\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"shadow-type\">in</property>\n                        <child>\n                          <object class=\"GtkTreeView\" id=\"attributes_treeview\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">True</property>\n                            <property name=\"rubber-banding\">True</property>\n                            <property name=\"enable-grid-lines\">both</property>\n                            <child internal-child=\"selection\">\n                              <object class=\"GtkTreeSelection\">\n                                <property name=\"mode\">multiple</property>\n                              </object>\n                            </child>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"position\">1</property>\n                  </packing>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"attributes_tab_label\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"tooltip-text\" translatable=\"yes\">SMART attributes</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">Attributes</property>\n                    <property name=\"use-markup\">True</property>\n                    <property name=\"use-underline\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"position\">1</property>\n                    <property name=\"tab-fill\">False</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"nvme_attributes_tab_vbox\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"border-width\">6</property>\n                    <property name=\"orientation\">vertical</property>\n                    <property name=\"spacing\">6</property>\n                    <child>\n                      <object class=\"GtkBox\" id=\"nvme_attributes_label_vbox\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"orientation\">vertical</property>\n                        <property name=\"spacing\">3</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScrolledWindow\" id=\"scrolledwindow1\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"shadow-type\">in</property>\n                        <child>\n                          <object class=\"GtkTreeView\" id=\"nvme_attributes_treeview\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">True</property>\n                            <property name=\"rubber-banding\">True</property>\n                            <property name=\"enable-grid-lines\">both</property>\n                            <child internal-child=\"selection\">\n                              <object class=\"GtkTreeSelection\">\n                                <property name=\"mode\">multiple</property>\n                              </object>\n                            </child>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"position\">2</property>\n                  </packing>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"nvme_attributes_tab_label\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"tooltip-text\" translatable=\"yes\">NVMe attributes</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">NVMe Attributes</property>\n                    <property name=\"use-markup\">True</property>\n                    <property name=\"use-underline\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"position\">2</property>\n                    <property name=\"tab-fill\">False</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"statistics_tab_vbox\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"border-width\">6</property>\n                    <property name=\"orientation\">vertical</property>\n                    <property name=\"spacing\">6</property>\n                    <child>\n                      <object class=\"GtkBox\" id=\"statistics_label_vbox\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"orientation\">vertical</property>\n                        <property name=\"spacing\">3</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScrolledWindow\" id=\"scrolledwindow10\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"shadow-type\">in</property>\n                        <child>\n                          <object class=\"GtkTreeView\" id=\"statistics_treeview\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">True</property>\n                            <property name=\"headers-clickable\">False</property>\n                            <property name=\"rubber-banding\">True</property>\n                            <property name=\"enable-grid-lines\">both</property>\n                            <child internal-child=\"selection\">\n                              <object class=\"GtkTreeSelection\">\n                                <property name=\"mode\">multiple</property>\n                              </object>\n                            </child>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"position\">3</property>\n                  </packing>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"statistics_tab_label\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"tooltip-text\" translatable=\"yes\">Drive statistics</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">Statistics</property>\n                    <property name=\"use-markup\">True</property>\n                    <property name=\"use-underline\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"position\">3</property>\n                    <property name=\"tab-fill\">False</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"test_tab_vbox\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"border-width\">12</property>\n                    <property name=\"orientation\">vertical</property>\n                    <property name=\"spacing\">6</property>\n                    <child>\n                      <object class=\"GtkBox\" id=\"vbox4\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"orientation\">vertical</property>\n                        <property name=\"spacing\">15</property>\n                        <child>\n                          <object class=\"GtkLabel\" id=\"label1\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"halign\">start</property>\n                            <property name=\"label\" translatable=\"yes\">Self-tests are built-in tests within the drive designed to recognize drive fault conditions. All self-tests are safe to user data. The tests can be performed during normal system operation, but will take longer to complete if the drive is not idle.</property>\n                            <property name=\"wrap\">True</property>\n                            <property name=\"width-chars\">70</property>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">False</property>\n                            <property name=\"fill\">False</property>\n                            <property name=\"position\">0</property>\n                          </packing>\n                        </child>\n                        <child>\n                          <!-- n-columns=3 n-rows=4 -->\n                          <object class=\"GtkGrid\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"row-spacing\">6</property>\n                            <property name=\"column-spacing\">6</property>\n                            <child>\n                              <object class=\"GtkBox\" id=\"test_result_hbox\">\n                                <property name=\"can-focus\">False</property>\n                                <property name=\"spacing\">6</property>\n                                <child>\n                                  <object class=\"GtkImage\" id=\"test_result_image\">\n                                    <property name=\"visible\">True</property>\n                                    <property name=\"can-focus\">False</property>\n                                    <property name=\"icon-name\">dialog-information</property>\n                                    <property name=\"icon_size\">3</property>\n                                  </object>\n                                  <packing>\n                                    <property name=\"expand\">False</property>\n                                    <property name=\"fill\">True</property>\n                                    <property name=\"position\">0</property>\n                                  </packing>\n                                </child>\n                                <child>\n                                  <object class=\"GtkLabel\" id=\"test_result_label\">\n                                    <property name=\"visible\">True</property>\n                                    <property name=\"can-focus\">False</property>\n                                    <property name=\"label\">[Test result text placeholder]</property>\n                                    <property name=\"use-markup\">True</property>\n                                    <property name=\"justify\">center</property>\n                                    <property name=\"width-chars\">70</property>\n                                  </object>\n                                  <packing>\n                                    <property name=\"expand\">True</property>\n                                    <property name=\"fill\">True</property>\n                                    <property name=\"position\">1</property>\n                                  </packing>\n                                </child>\n                              </object>\n                              <packing>\n                                <property name=\"left-attach\">0</property>\n                                <property name=\"top-attach\">3</property>\n                                <property name=\"width\">2</property>\n                              </packing>\n                            </child>\n                            <child>\n                              <object class=\"GtkProgressBar\" id=\"test_completion_progressbar\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">False</property>\n                                <property name=\"tooltip-text\" translatable=\"yes\">Test progress</property>\n                                <property name=\"hexpand\">True</property>\n                                <property name=\"text\">[test completion % and ETA placeholder text]</property>\n                                <property name=\"show-text\">True</property>\n                                <property name=\"ellipsize\">end</property>\n                              </object>\n                              <packing>\n                                <property name=\"left-attach\">0</property>\n                                <property name=\"top-attach\">2</property>\n                              </packing>\n                            </child>\n                            <child>\n                              <object class=\"GtkButton\" id=\"test_stop_button\">\n                                <property name=\"label\">gtk-stop</property>\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">True</property>\n                                <property name=\"receives-default\">True</property>\n                                <property name=\"tooltip-text\" translatable=\"yes\">Abort the test</property>\n                                <property name=\"use-stock\">True</property>\n                              </object>\n                              <packing>\n                                <property name=\"left-attach\">1</property>\n                                <property name=\"top-attach\">2</property>\n                              </packing>\n                            </child>\n                            <child>\n                              <object class=\"GtkScrolledWindow\" id=\"scrolledwindow5\">\n                                <property name=\"height-request\">85</property>\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">True</property>\n                                <property name=\"shadow-type\">in</property>\n                                <property name=\"min-content-height\">80</property>\n                                <child>\n                                  <object class=\"GtkTextView\" id=\"test_description_textview\">\n                                    <property name=\"visible\">True</property>\n                                    <property name=\"can-focus\">True</property>\n                                    <property name=\"tooltip-text\" translatable=\"yes\">Test description</property>\n                                    <property name=\"editable\">False</property>\n                                    <property name=\"wrap-mode\">word</property>\n                                    <property name=\"left-margin\">5</property>\n                                    <property name=\"right-margin\">5</property>\n                                    <property name=\"cursor-visible\">False</property>\n                                  </object>\n                                </child>\n                              </object>\n                              <packing>\n                                <property name=\"left-attach\">0</property>\n                                <property name=\"top-attach\">1</property>\n                                <property name=\"width\">2</property>\n                              </packing>\n                            </child>\n                            <child>\n                              <object class=\"GtkButton\" id=\"test_execute_button\">\n                                <property name=\"label\">gtk-execute</property>\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">True</property>\n                                <property name=\"receives-default\">True</property>\n                                <property name=\"tooltip-text\" translatable=\"yes\">Perform selected test</property>\n                                <property name=\"use-stock\">True</property>\n                              </object>\n                              <packing>\n                                <property name=\"left-attach\">1</property>\n                                <property name=\"top-attach\">0</property>\n                              </packing>\n                            </child>\n                            <child>\n                              <object class=\"GtkBox\" id=\"test_execution_hbox\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">False</property>\n                                <property name=\"hexpand\">True</property>\n                                <property name=\"spacing\">6</property>\n                                <child>\n                                  <object class=\"GtkBox\" id=\"hbox4\">\n                                    <property name=\"visible\">True</property>\n                                    <property name=\"can-focus\">False</property>\n                                    <property name=\"spacing\">6</property>\n                                    <child>\n                                      <object class=\"GtkLabel\" id=\"label2\">\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">False</property>\n                                        <property name=\"halign\">start</property>\n                                        <property name=\"label\" translatable=\"yes\">Test type:</property>\n                                        <attributes>\n                                          <attribute name=\"weight\" value=\"bold\"/>\n                                        </attributes>\n                                      </object>\n                                      <packing>\n                                        <property name=\"expand\">False</property>\n                                        <property name=\"fill\">True</property>\n                                        <property name=\"position\">0</property>\n                                      </packing>\n                                    </child>\n                                    <child>\n                                      <object class=\"GtkComboBox\" id=\"test_type_combo\">\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">False</property>\n                                        <property name=\"tooltip-text\" translatable=\"yes\">Choose a test type</property>\n                                        <property name=\"model\">model1</property>\n                                      </object>\n                                      <packing>\n                                        <property name=\"expand\">False</property>\n                                        <property name=\"fill\">True</property>\n                                        <property name=\"position\">1</property>\n                                      </packing>\n                                    </child>\n                                  </object>\n                                  <packing>\n                                    <property name=\"expand\">False</property>\n                                    <property name=\"fill\">True</property>\n                                    <property name=\"position\">0</property>\n                                  </packing>\n                                </child>\n                                <child>\n                                  <object class=\"GtkBox\" id=\"hbox3\">\n                                    <property name=\"visible\">True</property>\n                                    <property name=\"can-focus\">False</property>\n                                    <property name=\"spacing\">6</property>\n                                    <child>\n                                      <object class=\"GtkLabel\" id=\"label5\">\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">False</property>\n                                        <property name=\"tooltip-text\" translatable=\"yes\">Estimated test duration on idle drive</property>\n                                        <property name=\"label\" translatable=\"yes\">Estimated duration:</property>\n                                        <attributes>\n                                          <attribute name=\"weight\" value=\"bold\"/>\n                                        </attributes>\n                                      </object>\n                                      <packing>\n                                        <property name=\"expand\">False</property>\n                                        <property name=\"fill\">True</property>\n                                        <property name=\"position\">0</property>\n                                      </packing>\n                                    </child>\n                                    <child>\n                                      <object class=\"GtkLabel\" id=\"min_duration_label\">\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">False</property>\n                                        <property name=\"tooltip-text\" translatable=\"yes\">Minimum test duration on idle drive</property>\n                                        <property name=\"label\" translatable=\"yes\">N/A</property>\n                                      </object>\n                                      <packing>\n                                        <property name=\"expand\">False</property>\n                                        <property name=\"fill\">True</property>\n                                        <property name=\"position\">1</property>\n                                      </packing>\n                                    </child>\n                                  </object>\n                                  <packing>\n                                    <property name=\"expand\">True</property>\n                                    <property name=\"fill\">True</property>\n                                    <property name=\"padding\">10</property>\n                                    <property name=\"position\">1</property>\n                                  </packing>\n                                </child>\n                              </object>\n                              <packing>\n                                <property name=\"left-attach\">0</property>\n                                <property name=\"top-attach\">0</property>\n                              </packing>\n                            </child>\n                            <child>\n                              <placeholder/>\n                            </child>\n                            <child>\n                              <placeholder/>\n                            </child>\n                            <child>\n                              <placeholder/>\n                            </child>\n                            <child>\n                              <placeholder/>\n                            </child>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">True</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">1</property>\n                          </packing>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkFrame\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"margin-top\">6</property>\n                        <property name=\"label-xalign\">0</property>\n                        <property name=\"shadow-type\">none</property>\n                        <child>\n                          <object class=\"GtkAlignment\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <child>\n                              <object class=\"GtkBox\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">False</property>\n                                <property name=\"orientation\">vertical</property>\n                                <child>\n                                  <object class=\"GtkBox\" id=\"selftest_log_label_vbox\">\n                                    <property name=\"visible\">True</property>\n                                    <property name=\"can-focus\">False</property>\n                                    <property name=\"orientation\">vertical</property>\n                                    <property name=\"spacing\">3</property>\n                                    <child>\n                                      <placeholder/>\n                                    </child>\n                                  </object>\n                                  <packing>\n                                    <property name=\"expand\">False</property>\n                                    <property name=\"fill\">True</property>\n                                    <property name=\"padding\">4</property>\n                                    <property name=\"position\">0</property>\n                                  </packing>\n                                </child>\n                                <child>\n                                  <object class=\"GtkScrolledWindow\" id=\"scrolledwindow3\">\n                                    <property name=\"visible\">True</property>\n                                    <property name=\"can-focus\">True</property>\n                                    <property name=\"shadow-type\">in</property>\n                                    <child>\n                                      <object class=\"GtkTreeView\" id=\"selftest_log_treeview\">\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">True</property>\n                                        <property name=\"rubber-banding\">True</property>\n                                        <property name=\"enable-grid-lines\">both</property>\n                                        <child internal-child=\"selection\">\n                                          <object class=\"GtkTreeSelection\">\n                                            <property name=\"mode\">multiple</property>\n                                          </object>\n                                        </child>\n                                      </object>\n                                    </child>\n                                  </object>\n                                  <packing>\n                                    <property name=\"expand\">True</property>\n                                    <property name=\"fill\">True</property>\n                                    <property name=\"position\">1</property>\n                                  </packing>\n                                </child>\n                              </object>\n                            </child>\n                          </object>\n                        </child>\n                        <child type=\"label\">\n                          <object class=\"GtkLabel\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Self-Test Log</property>\n                            <attributes>\n                              <attribute name=\"weight\" value=\"bold\"/>\n                            </attributes>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"position\">4</property>\n                  </packing>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"test_tab_label\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"tooltip-text\" translatable=\"yes\">Perform self-tests on the hard drive.\nSelf-test log contains information about the most recent manually performed SMART self-tests.</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">Self-Tests</property>\n                    <property name=\"use-markup\">True</property>\n                    <property name=\"use-underline\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"position\">4</property>\n                    <property name=\"tab-fill\">False</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"error_log_tab_vbox\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"border-width\">6</property>\n                    <property name=\"orientation\">vertical</property>\n                    <child>\n                      <object class=\"GtkBox\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"orientation\">vertical</property>\n                        <property name=\"spacing\">6</property>\n                        <child>\n                          <object class=\"GtkBox\" id=\"error_log_label_vbox\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"orientation\">vertical</property>\n                            <property name=\"spacing\">3</property>\n                            <child>\n                              <placeholder/>\n                            </child>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">False</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">0</property>\n                          </packing>\n                        </child>\n                        <child>\n                          <object class=\"GtkBox\" id=\"vbox5\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"orientation\">vertical</property>\n                            <child>\n                              <object class=\"GtkPaned\" id=\"vpaned1\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">True</property>\n                                <property name=\"orientation\">vertical</property>\n                                <property name=\"position\">170</property>\n                                <property name=\"position-set\">True</property>\n                                <property name=\"wide-handle\">True</property>\n                                <child>\n                                  <object class=\"GtkScrolledWindow\" id=\"error_log_scrolledwindow\">\n                                    <property name=\"visible\">True</property>\n                                    <property name=\"can-focus\">True</property>\n                                    <property name=\"shadow-type\">in</property>\n                                    <child>\n                                      <object class=\"GtkTreeView\" id=\"error_log_treeview\">\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">True</property>\n                                        <property name=\"enable-grid-lines\">both</property>\n                                        <child internal-child=\"selection\">\n                                          <object class=\"GtkTreeSelection\"/>\n                                        </child>\n                                      </object>\n                                    </child>\n                                  </object>\n                                  <packing>\n                                    <property name=\"resize\">False</property>\n                                    <property name=\"shrink\">True</property>\n                                  </packing>\n                                </child>\n                                <child>\n                                  <object class=\"GtkScrolledWindow\" id=\"error_log_details_scrolledwindow\">\n                                    <property name=\"visible\">True</property>\n                                    <property name=\"can-focus\">True</property>\n                                    <property name=\"shadow-type\">in</property>\n                                    <child>\n                                      <object class=\"GtkTextView\" id=\"error_log_textview\">\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">True</property>\n                                        <property name=\"tooltip-text\" translatable=\"yes\">Complete error log information</property>\n                                        <property name=\"editable\">False</property>\n                                        <property name=\"left-margin\">5</property>\n                                        <property name=\"right-margin\">5</property>\n                                        <property name=\"monospace\">True</property>\n                                      </object>\n                                    </child>\n                                  </object>\n                                  <packing>\n                                    <property name=\"resize\">True</property>\n                                    <property name=\"shrink\">True</property>\n                                  </packing>\n                                </child>\n                              </object>\n                              <packing>\n                                <property name=\"expand\">True</property>\n                                <property name=\"fill\">True</property>\n                                <property name=\"position\">0</property>\n                              </packing>\n                            </child>\n                          </object>\n                          <packing>\n                            <property name=\"expand\">True</property>\n                            <property name=\"fill\">True</property>\n                            <property name=\"position\">1</property>\n                          </packing>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"position\">5</property>\n                  </packing>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"error_log_tab_label\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"tooltip-text\" translatable=\"yes\">SMART error log contains most recent non-trivial errors the hard drive has encountered</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">Error Log</property>\n                    <property name=\"use-markup\">True</property>\n                    <property name=\"use-underline\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"position\">5</property>\n                    <property name=\"tab-fill\">False</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"nvme_error_log_tab_vbox\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"border-width\">6</property>\n                    <property name=\"orientation\">vertical</property>\n                    <property name=\"spacing\">6</property>\n                    <child>\n                      <object class=\"GtkBox\" id=\"nvme_error_log_label_vbox\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"orientation\">vertical</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScrolledWindow\" id=\"scrolledwindow2\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"shadow-type\">in</property>\n                        <child>\n                          <object class=\"GtkTextView\" id=\"nvme_error_log_textview\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">True</property>\n                            <property name=\"editable\">False</property>\n                            <property name=\"left-margin\">5</property>\n                            <property name=\"right-margin\">5</property>\n                            <property name=\"monospace\">True</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"position\">6</property>\n                  </packing>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"nvme_error_log_tab_label\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"tooltip-text\" translatable=\"yes\">NVMe error information log contains recent errors encountered by the drive.\n\nNote: The log is not preserved across power cycles or controller resets.</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">NVMe Error Log</property>\n                  </object>\n                  <packing>\n                    <property name=\"position\">6</property>\n                    <property name=\"tab-fill\">False</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"temperature_log_tab_vbox\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"border-width\">6</property>\n                    <property name=\"orientation\">vertical</property>\n                    <property name=\"spacing\">6</property>\n                    <child>\n                      <object class=\"GtkBox\" id=\"temperature_log_label_vbox\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"orientation\">vertical</property>\n                        <child>\n                          <placeholder/>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkScrolledWindow\" id=\"scrolledwindow6\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"shadow-type\">in</property>\n                        <child>\n                          <object class=\"GtkTextView\" id=\"temperature_log_textview\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">True</property>\n                            <property name=\"editable\">False</property>\n                            <property name=\"left-margin\">5</property>\n                            <property name=\"right-margin\">5</property>\n                            <property name=\"monospace\">True</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"position\">7</property>\n                  </packing>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"temperature_log_tab_label\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"tooltip-text\" translatable=\"yes\">Current temperature and history</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">Temperature Log</property>\n                  </object>\n                  <packing>\n                    <property name=\"position\">7</property>\n                    <property name=\"tab-fill\">False</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"advanced_tab_vbox\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"orientation\">vertical</property>\n                    <property name=\"spacing\">6</property>\n                    <child>\n                      <object class=\"GtkNotebook\" id=\"advanced_notebook\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">True</property>\n                        <property name=\"border-width\">6</property>\n                        <child>\n                          <object class=\"GtkScrolledWindow\" id=\"capabilities_scrolledwindow\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">True</property>\n                            <property name=\"shadow-type\">in</property>\n                            <child>\n                              <object class=\"GtkTreeView\" id=\"capabilities_treeview\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">True</property>\n                                <property name=\"enable-grid-lines\">both</property>\n                                <child internal-child=\"selection\">\n                                  <object class=\"GtkTreeSelection\"/>\n                                </child>\n                              </object>\n                            </child>\n                          </object>\n                        </child>\n                        <child type=\"tab\">\n                          <object class=\"GtkLabel\" id=\"capabilities_tab_label\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"margin-left\">6</property>\n                            <property name=\"margin-right\">6</property>\n                            <property name=\"margin-top\">3</property>\n                            <property name=\"margin-bottom\">3</property>\n                            <property name=\"label\" translatable=\"yes\">Capabilities</property>\n                          </object>\n                          <packing>\n                            <property name=\"tab-fill\">False</property>\n                          </packing>\n                        </child>\n                        <child>\n                          <object class=\"GtkScrolledWindow\" id=\"erc_scrolledwindow\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">True</property>\n                            <property name=\"shadow-type\">in</property>\n                            <child>\n                              <object class=\"GtkTextView\" id=\"erc_log_textview\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">True</property>\n                                <property name=\"editable\">False</property>\n                                <property name=\"left-margin\">5</property>\n                                <property name=\"right-margin\">5</property>\n                                <property name=\"monospace\">True</property>\n                              </object>\n                            </child>\n                          </object>\n                          <packing>\n                            <property name=\"position\">1</property>\n                          </packing>\n                        </child>\n                        <child type=\"tab\">\n                          <object class=\"GtkLabel\" id=\"erc_tab_label\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"margin-left\">6</property>\n                            <property name=\"margin-right\">6</property>\n                            <property name=\"label\" translatable=\"yes\">Error Recovery</property>\n                          </object>\n                          <packing>\n                            <property name=\"position\">1</property>\n                            <property name=\"tab-fill\">False</property>\n                          </packing>\n                        </child>\n                        <child>\n                          <object class=\"GtkScrolledWindow\" id=\"selective_selftest_scrolledwindow\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">True</property>\n                            <property name=\"shadow-type\">in</property>\n                            <child>\n                              <object class=\"GtkTextView\" id=\"selective_selftest_log_textview\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">True</property>\n                                <property name=\"editable\">False</property>\n                                <property name=\"left-margin\">5</property>\n                                <property name=\"right-margin\">5</property>\n                                <property name=\"monospace\">True</property>\n                              </object>\n                            </child>\n                          </object>\n                          <packing>\n                            <property name=\"position\">2</property>\n                          </packing>\n                        </child>\n                        <child type=\"tab\">\n                          <object class=\"GtkLabel\" id=\"selective_selftest_tab_label\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"margin-left\">6</property>\n                            <property name=\"margin-right\">6</property>\n                            <property name=\"label\" translatable=\"yes\">Selective Self-Test Log</property>\n                          </object>\n                          <packing>\n                            <property name=\"position\">2</property>\n                            <property name=\"tab-fill\">False</property>\n                          </packing>\n                        </child>\n                        <child>\n                          <object class=\"GtkScrolledWindow\" id=\"phy_scrolledwindow\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">True</property>\n                            <property name=\"shadow-type\">in</property>\n                            <child>\n                              <object class=\"GtkTextView\" id=\"phy_log_textview\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">True</property>\n                                <property name=\"editable\">False</property>\n                                <property name=\"left-margin\">5</property>\n                                <property name=\"right-margin\">5</property>\n                                <property name=\"monospace\">True</property>\n                              </object>\n                            </child>\n                          </object>\n                          <packing>\n                            <property name=\"position\">3</property>\n                          </packing>\n                        </child>\n                        <child type=\"tab\">\n                          <object class=\"GtkLabel\" id=\"phy_tab_label\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"margin-left\">6</property>\n                            <property name=\"margin-right\">6</property>\n                            <property name=\"label\" translatable=\"yes\">Physical</property>\n                          </object>\n                          <packing>\n                            <property name=\"position\">3</property>\n                            <property name=\"tab-fill\">False</property>\n                          </packing>\n                        </child>\n                        <child>\n                          <object class=\"GtkScrolledWindow\" id=\"directory_scrolledwindow\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">True</property>\n                            <property name=\"shadow-type\">in</property>\n                            <child>\n                              <object class=\"GtkTextView\" id=\"directory_log_textview\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">True</property>\n                                <property name=\"editable\">False</property>\n                                <property name=\"left-margin\">5</property>\n                                <property name=\"right-margin\">5</property>\n                                <property name=\"monospace\">True</property>\n                              </object>\n                            </child>\n                          </object>\n                          <packing>\n                            <property name=\"position\">4</property>\n                          </packing>\n                        </child>\n                        <child type=\"tab\">\n                          <object class=\"GtkLabel\" id=\"directory_tab_label\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"margin-left\">6</property>\n                            <property name=\"margin-right\">6</property>\n                            <property name=\"label\" translatable=\"yes\">Directory</property>\n                          </object>\n                          <packing>\n                            <property name=\"position\">4</property>\n                            <property name=\"tab-fill\">False</property>\n                          </packing>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">True</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"position\">8</property>\n                    <property name=\"tab-fill\">False</property>\n                  </packing>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"advanced_tab_label\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">True</property>\n                    <property name=\"tooltip-text\" translatable=\"yes\">Advanced parameters and logs</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">Advanced</property>\n                    <property name=\"use-markup\">True</property>\n                    <property name=\"use-underline\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"position\">8</property>\n                    <property name=\"tab-fill\">False</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">True</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"hbox1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">False</property>\n            <property name=\"spacing\">6</property>\n            <property name=\"homogeneous\">True</property>\n            <child>\n              <object class=\"GtkButton\" id=\"refresh_info_button\">\n                <property name=\"label\">gtk-refresh</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"receives-default\">True</property>\n                <property name=\"tooltip-text\" translatable=\"yes\">Re-read all the information</property>\n                <property name=\"use-stock\">True</property>\n                <accelerator key=\"F5\" signal=\"clicked\"/>\n                <accelerator key=\"R\" signal=\"clicked\" modifiers=\"GDK_CONTROL_MASK\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"view_output_button\">\n                <property name=\"label\" translatable=\"yes\">_View Output</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"receives-default\">True</property>\n                <property name=\"tooltip-text\" translatable=\"yes\">View smartctl output</property>\n                <property name=\"use-underline\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"save_info_button\">\n                <property name=\"label\">gtk-save-as</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"receives-default\">True</property>\n                <property name=\"tooltip-text\" translatable=\"yes\">Save the SMART information (smartctl output) to a text file</property>\n                <property name=\"use-stock\">True</property>\n                <accelerator key=\"S\" signal=\"clicked\" modifiers=\"GDK_CONTROL_MASK\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label8\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">3</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"close_window_button\">\n                <property name=\"label\">gtk-close</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"receives-default\">True</property>\n                <property name=\"tooltip-text\" translatable=\"yes\">Close this window</property>\n                <property name=\"use-stock\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">4</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">False</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "src/gui/ui/gsc_main_window.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.38.2 \n\nCopyright (C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n\nThis file is part of GSmartControl.\n\nGSmartControl is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nGSmartControl is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with GSmartControl.  If not, see <http://www.gnu.org/licenses/>.\n\n-->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.20\"/>\n  <!-- interface-license-type gplv3 -->\n  <!-- interface-name GSmartControl -->\n  <!-- interface-copyright 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com> -->\n  <object class=\"GtkWindow\" id=\"gsc_main_window\">\n    <property name=\"can-focus\">False</property>\n    <property name=\"title\" translatable=\"yes\">GSmartControl</property>\n    <property name=\"default-width\">560</property>\n    <property name=\"default-height\">450</property>\n    <child>\n      <object class=\"GtkBox\" id=\"vbox1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can-focus\">False</property>\n        <property name=\"orientation\">vertical</property>\n        <child>\n          <object class=\"GtkBox\" id=\"menubar_vbox\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">False</property>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">False</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <!-- n-columns=3 n-rows=3 -->\n          <object class=\"GtkGrid\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">False</property>\n            <property name=\"margin-left\">6</property>\n            <property name=\"margin-right\">6</property>\n            <property name=\"margin-top\">6</property>\n            <property name=\"margin-bottom\">6</property>\n            <property name=\"row-spacing\">6</property>\n            <property name=\"column-spacing\">6</property>\n            <child>\n              <object class=\"GtkBox\" id=\"status_name_label_hbox\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <child>\n                  <placeholder/>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left-attach\">1</property>\n                <property name=\"top-attach\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkBox\" id=\"status_health_label_hbox\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <child>\n                  <placeholder/>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left-attach\">1</property>\n                <property name=\"top-attach\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"status_name_left_label\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"halign\">start</property>\n                <property name=\"label\" translatable=\"yes\">Drive information:</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left-attach\">0</property>\n                <property name=\"top-attach\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"halign\">start</property>\n                <property name=\"label\" translatable=\"yes\">Basic health check:</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left-attach\">0</property>\n                <property name=\"top-attach\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label3\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <property name=\"halign\">start</property>\n                <property name=\"label\" translatable=\"yes\">Model family:</property>\n                <property name=\"selectable\">True</property>\n                <attributes>\n                  <attribute name=\"weight\" value=\"bold\"/>\n                </attributes>\n              </object>\n              <packing>\n                <property name=\"left-attach\">0</property>\n                <property name=\"top-attach\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkBox\" id=\"status_family_label_hbox\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n                <child>\n                  <placeholder/>\n                </child>\n              </object>\n              <packing>\n                <property name=\"left-attach\">1</property>\n                <property name=\"top-attach\">2</property>\n              </packing>\n            </child>\n            <child>\n              <placeholder/>\n            </child>\n            <child>\n              <placeholder/>\n            </child>\n            <child>\n              <placeholder/>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkScrolledWindow\" id=\"scrolledwindow1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">True</property>\n            <property name=\"events\">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>\n            <property name=\"shadow-type\">in</property>\n            <child>\n              <object class=\"GtkIconView\" id=\"drive_iconview\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"has-focus\">True</property>\n                <property name=\"is-focus\">True</property>\n                <property name=\"events\">GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>\n                <property name=\"margin\">10</property>\n                <property name=\"item-width\">250</property>\n                <property name=\"spacing\">3</property>\n                <property name=\"row-spacing\">20</property>\n                <property name=\"column-spacing\">5</property>\n              </object>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">True</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">2</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"vbox3\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">False</property>\n            <property name=\"margin-left\">6</property>\n            <property name=\"margin-right\">6</property>\n            <property name=\"margin-top\">6</property>\n            <property name=\"margin-bottom\">6</property>\n            <property name=\"orientation\">vertical</property>\n            <property name=\"spacing\">6</property>\n            <child>\n              <object class=\"GtkCheckButton\" id=\"status_smart_enabled_check\">\n                <property name=\"label\">enable smart (action-replaced text)</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"receives-default\">False</property>\n                <property name=\"draw-indicator\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">3</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "src/gui/ui/gsc_preferences_window.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.40.0 \n\nCopyright (C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n\nThis file is part of GSmartControl.\n\nGSmartControl is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nGSmartControl is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with GSmartControl.  If not, see <http://www.gnu.org/licenses/>.\n\n-->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.20\"/>\n  <!-- interface-license-type gplv3 -->\n  <!-- interface-name GSmartControl -->\n  <!-- interface-copyright 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com> -->\n  <object class=\"GtkWindow\" id=\"gsc_preferences_window\">\n    <property name=\"can-focus\">False</property>\n    <child>\n      <object class=\"GtkBox\" id=\"vbox1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can-focus\">False</property>\n        <property name=\"border-width\">12</property>\n        <property name=\"orientation\">vertical</property>\n        <property name=\"spacing\">12</property>\n        <child>\n          <object class=\"GtkBox\" id=\"vbox2\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">False</property>\n            <property name=\"orientation\">vertical</property>\n            <child>\n              <object class=\"GtkNotebook\" id=\"notebook1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"has-focus\">True</property>\n                <child>\n                  <object class=\"GtkBox\" id=\"vbox3\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"border-width\">5</property>\n                    <property name=\"orientation\">vertical</property>\n                    <property name=\"spacing\">6</property>\n                    <child>\n                      <object class=\"GtkFrame\" id=\"frame1\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label-xalign\">0</property>\n                        <property name=\"shadow-type\">none</property>\n                        <child>\n                          <object class=\"GtkAlignment\" id=\"alignment1\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"left-padding\">12</property>\n                            <child>\n                              <object class=\"GtkBox\" id=\"vbox5\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">False</property>\n                                <property name=\"orientation\">vertical</property>\n                                <child>\n                                  <object class=\"GtkBox\" id=\"vbox6\">\n                                    <property name=\"visible\">True</property>\n                                    <property name=\"can-focus\">False</property>\n                                    <property name=\"orientation\">vertical</property>\n                                    <property name=\"spacing\">6</property>\n                                    <child>\n                                      <object class=\"GtkCheckButton\" id=\"scan_on_startup_check\">\n                                        <property name=\"label\" translatable=\"yes\">Scan system for drives on startup</property>\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">True</property>\n                                        <property name=\"receives-default\">False</property>\n                                        <property name=\"halign\">start</property>\n                                        <property name=\"use-underline\">True</property>\n                                        <property name=\"draw-indicator\">True</property>\n                                      </object>\n                                      <packing>\n                                        <property name=\"expand\">True</property>\n                                        <property name=\"fill\">True</property>\n                                        <property name=\"position\">0</property>\n                                      </packing>\n                                    </child>\n                                    <child>\n                                      <object class=\"GtkCheckButton\" id=\"show_smart_capable_only_check\">\n                                        <property name=\"label\" translatable=\"yes\">Show SMART-capable drives only</property>\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">True</property>\n                                        <property name=\"receives-default\">False</property>\n                                        <property name=\"halign\">start</property>\n                                        <property name=\"use-underline\">True</property>\n                                        <property name=\"draw-indicator\">True</property>\n                                      </object>\n                                      <packing>\n                                        <property name=\"expand\">True</property>\n                                        <property name=\"fill\">True</property>\n                                        <property name=\"position\">1</property>\n                                      </packing>\n                                    </child>\n                                    <child>\n                                      <object class=\"GtkCheckButton\" id=\"show_device_name_under_icon_check\">\n                                        <property name=\"label\" translatable=\"yes\">Show device name under drive icon</property>\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">True</property>\n                                        <property name=\"receives-default\">False</property>\n                                        <property name=\"halign\">start</property>\n                                        <property name=\"use-underline\">True</property>\n                                        <property name=\"draw-indicator\">True</property>\n                                      </object>\n                                      <packing>\n                                        <property name=\"expand\">True</property>\n                                        <property name=\"fill\">True</property>\n                                        <property name=\"position\">2</property>\n                                      </packing>\n                                    </child>\n                                    <child>\n                                      <object class=\"GtkCheckButton\" id=\"show_serial_number_under_icon_check\">\n                                        <property name=\"label\" translatable=\"yes\">Show serial number under drive icon</property>\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">True</property>\n                                        <property name=\"receives-default\">False</property>\n                                        <property name=\"halign\">start</property>\n                                        <property name=\"use-underline\">True</property>\n                                        <property name=\"draw-indicator\">True</property>\n                                      </object>\n                                      <packing>\n                                        <property name=\"expand\">True</property>\n                                        <property name=\"fill\">True</property>\n                                        <property name=\"position\">3</property>\n                                      </packing>\n                                    </child>\n                                  </object>\n                                  <packing>\n                                    <property name=\"expand\">True</property>\n                                    <property name=\"fill\">True</property>\n                                    <property name=\"padding\">10</property>\n                                    <property name=\"position\">0</property>\n                                  </packing>\n                                </child>\n                              </object>\n                            </child>\n                          </object>\n                        </child>\n                        <child type=\"label\">\n                          <object class=\"GtkLabel\" id=\"label1\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">&lt;b&gt;Program Settings&lt;/b&gt;</property>\n                            <property name=\"use-markup\">True</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"padding\">5</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkFrame\" id=\"frame2\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label-xalign\">0</property>\n                        <property name=\"shadow-type\">none</property>\n                        <child>\n                          <object class=\"GtkAlignment\" id=\"alignment2\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"left-padding\">12</property>\n                            <child>\n                              <object class=\"GtkBox\" id=\"vbox7\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">False</property>\n                                <property name=\"orientation\">vertical</property>\n                                <child>\n                                  <object class=\"GtkBox\" id=\"vbox12\">\n                                    <property name=\"visible\">True</property>\n                                    <property name=\"can-focus\">False</property>\n                                    <property name=\"orientation\">vertical</property>\n                                    <property name=\"spacing\">7</property>\n                                    <child>\n                                      <object class=\"GtkCheckButton\" id=\"search_in_smartmontools_first_check\">\n                                        <property name=\"label\" translatable=\"yes\">Look for smartctl in smartmontools installation directory first</property>\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">True</property>\n                                        <property name=\"receives-default\">False</property>\n                                        <property name=\"tooltip-text\" translatable=\"yes\">If smartmontools is installed, use its smartctl by default</property>\n                                        <property name=\"halign\">start</property>\n                                        <property name=\"use-underline\">True</property>\n                                        <property name=\"draw-indicator\">True</property>\n                                      </object>\n                                      <packing>\n                                        <property name=\"expand\">True</property>\n                                        <property name=\"fill\">True</property>\n                                        <property name=\"position\">0</property>\n                                      </packing>\n                                    </child>\n                                    <child>\n                                      <!-- n-columns=3 n-rows=3 -->\n                                      <object class=\"GtkGrid\">\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">False</property>\n                                        <property name=\"row-spacing\">6</property>\n                                        <property name=\"column-spacing\">12</property>\n                                        <child>\n                                          <object class=\"GtkBox\" id=\"hbox4\">\n                                            <property name=\"visible\">True</property>\n                                            <property name=\"can-focus\">False</property>\n                                            <property name=\"spacing\">6</property>\n                                            <child>\n                                              <object class=\"GtkEntry\" id=\"smartctl_binary_entry\">\n                                                <property name=\"visible\">True</property>\n                                                <property name=\"can-focus\">True</property>\n                                                <property name=\"activates-default\">True</property>\n                                                <property name=\"primary-icon-activatable\">False</property>\n                                                <property name=\"secondary-icon-activatable\">False</property>\n                                              </object>\n                                              <packing>\n                                                <property name=\"expand\">True</property>\n                                                <property name=\"fill\">True</property>\n                                                <property name=\"position\">0</property>\n                                              </packing>\n                                            </child>\n                                            <child>\n                                              <object class=\"GtkButton\" id=\"smartctl_binary_browse_button\">\n                                                <property name=\"label\" translatable=\"yes\">Browse...</property>\n                                                <property name=\"visible\">True</property>\n                                                <property name=\"can-focus\">True</property>\n                                                <property name=\"receives-default\">True</property>\n                                                <property name=\"use-underline\">True</property>\n                                              </object>\n                                              <packing>\n                                                <property name=\"expand\">True</property>\n                                                <property name=\"fill\">True</property>\n                                                <property name=\"position\">1</property>\n                                              </packing>\n                                            </child>\n                                          </object>\n                                          <packing>\n                                            <property name=\"left-attach\">1</property>\n                                            <property name=\"top-attach\">0</property>\n                                          </packing>\n                                        </child>\n                                        <child>\n                                          <object class=\"GtkEntry\" id=\"smartctl_options_entry\">\n                                            <property name=\"visible\">True</property>\n                                            <property name=\"can-focus\">True</property>\n                                            <property name=\"tooltip-text\" translatable=\"yes\">Global parameters for smartctl. These parameters will be used every time the program invokes smartctl. Must be shell-escaped.</property>\n                                            <property name=\"activates-default\">True</property>\n                                            <property name=\"primary-icon-activatable\">False</property>\n                                            <property name=\"secondary-icon-activatable\">False</property>\n                                          </object>\n                                          <packing>\n                                            <property name=\"left-attach\">1</property>\n                                            <property name=\"top-attach\">1</property>\n                                          </packing>\n                                        </child>\n                                        <child>\n                                          <object class=\"GtkLabel\" id=\"label8\">\n                                            <property name=\"visible\">True</property>\n                                            <property name=\"can-focus\">False</property>\n                                            <property name=\"tooltip-text\" translatable=\"yes\">Global parameters for smartctl. These parameters will be used every time the program invokes smartctl. Must be shell-escaped.</property>\n                                            <property name=\"halign\">start</property>\n                                            <property name=\"label\" translatable=\"yes\">Smartctl parameters:</property>\n                                            <property name=\"use-underline\">True</property>\n                                          </object>\n                                          <packing>\n                                            <property name=\"left-attach\">0</property>\n                                            <property name=\"top-attach\">1</property>\n                                          </packing>\n                                        </child>\n                                        <child>\n                                          <object class=\"GtkLabel\" id=\"smartctl_binary_label\">\n                                            <property name=\"visible\">True</property>\n                                            <property name=\"can-focus\">False</property>\n                                            <property name=\"halign\">start</property>\n                                            <property name=\"label\" translatable=\"yes\">Smartctl binary:</property>\n                                            <property name=\"use-underline\">True</property>\n                                          </object>\n                                          <packing>\n                                            <property name=\"left-attach\">0</property>\n                                            <property name=\"top-attach\">0</property>\n                                          </packing>\n                                        </child>\n                                        <child>\n                                          <placeholder/>\n                                        </child>\n                                        <child>\n                                          <placeholder/>\n                                        </child>\n                                        <child>\n                                          <placeholder/>\n                                        </child>\n                                        <child>\n                                          <placeholder/>\n                                        </child>\n                                        <child>\n                                          <placeholder/>\n                                        </child>\n                                      </object>\n                                      <packing>\n                                        <property name=\"expand\">True</property>\n                                        <property name=\"fill\">True</property>\n                                        <property name=\"position\">1</property>\n                                      </packing>\n                                    </child>\n                                  </object>\n                                  <packing>\n                                    <property name=\"expand\">True</property>\n                                    <property name=\"fill\">True</property>\n                                    <property name=\"padding\">10</property>\n                                    <property name=\"position\">0</property>\n                                  </packing>\n                                </child>\n                              </object>\n                            </child>\n                          </object>\n                        </child>\n                        <child type=\"label\">\n                          <object class=\"GtkLabel\" id=\"label5\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">&lt;b&gt;Smartctl Invocation&lt;/b&gt;</property>\n                            <property name=\"use-markup\">True</property>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"padding\">5</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                  </object>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"label3\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-top\">3</property>\n                    <property name=\"margin-bottom\">3</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">_General</property>\n                    <property name=\"use-underline\">True</property>\n                  </object>\n                  <packing>\n                    <property name=\"tab-fill\">False</property>\n                  </packing>\n                </child>\n                <child>\n                  <object class=\"GtkBox\" id=\"vbox4\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"border-width\">5</property>\n                    <property name=\"orientation\">vertical</property>\n                    <property name=\"spacing\">6</property>\n                    <child>\n                      <object class=\"GtkFrame\" id=\"frame3\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label-xalign\">0</property>\n                        <property name=\"shadow-type\">none</property>\n                        <child>\n                          <object class=\"GtkAlignment\" id=\"alignment3\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"left-padding\">12</property>\n                            <child>\n                              <object class=\"GtkBox\" id=\"vbox9\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">False</property>\n                                <property name=\"orientation\">vertical</property>\n                                <child>\n                                  <object class=\"GtkBox\" id=\"hbox6\">\n                                    <property name=\"visible\">True</property>\n                                    <property name=\"can-focus\">False</property>\n                                    <property name=\"spacing\">6</property>\n                                    <child>\n                                      <object class=\"GtkLabel\" id=\"label6\">\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">False</property>\n                                        <property name=\"tooltip-text\" translatable=\"yes\">Semicolon-separated blacklist regular expression patterns for device search. For example, to blacklist all \"/dev/sd*\" devices, use \"sd.{1}$\". Simply listing them as \"sda;sdb\" will also work.</property>\n                                        <property name=\"halign\">start</property>\n                                        <property name=\"label\" translatable=\"yes\">Device blacklist patterns:</property>\n                                        <property name=\"use-underline\">True</property>\n                                      </object>\n                                      <packing>\n                                        <property name=\"expand\">False</property>\n                                        <property name=\"fill\">True</property>\n                                        <property name=\"position\">0</property>\n                                      </packing>\n                                    </child>\n                                    <child>\n                                      <object class=\"GtkEntry\" id=\"device_blacklist_patterns_entry\">\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">True</property>\n                                        <property name=\"tooltip-text\" translatable=\"yes\">Semicolon-separated blacklist regular expression patterns for device search. For example, to blacklist all \"/dev/sd*\" devices, use \"sd.$\". Simply listing them as \"sda;sdb\" or \"sd[ab]\" will also work.</property>\n                                        <property name=\"activates-default\">True</property>\n                                        <property name=\"primary-icon-activatable\">False</property>\n                                        <property name=\"secondary-icon-activatable\">False</property>\n                                      </object>\n                                      <packing>\n                                        <property name=\"expand\">True</property>\n                                        <property name=\"fill\">True</property>\n                                        <property name=\"position\">1</property>\n                                      </packing>\n                                    </child>\n                                  </object>\n                                  <packing>\n                                    <property name=\"expand\">True</property>\n                                    <property name=\"fill\">True</property>\n                                    <property name=\"padding\">12</property>\n                                    <property name=\"position\">0</property>\n                                  </packing>\n                                </child>\n                              </object>\n                            </child>\n                          </object>\n                        </child>\n                        <child type=\"label\">\n                          <object class=\"GtkLabel\" id=\"label10\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Drive Search</property>\n                            <attributes>\n                              <attribute name=\"weight\" value=\"bold\"/>\n                            </attributes>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"padding\">6</property>\n                        <property name=\"position\">0</property>\n                      </packing>\n                    </child>\n                    <child>\n                      <object class=\"GtkFrame\" id=\"frame4\">\n                        <property name=\"visible\">True</property>\n                        <property name=\"can-focus\">False</property>\n                        <property name=\"label-xalign\">0</property>\n                        <property name=\"shadow-type\">none</property>\n                        <child>\n                          <object class=\"GtkAlignment\" id=\"alignment4\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"left-padding\">12</property>\n                            <child>\n                              <object class=\"GtkBox\" id=\"vbox8\">\n                                <property name=\"visible\">True</property>\n                                <property name=\"can-focus\">False</property>\n                                <property name=\"orientation\">vertical</property>\n                                <child>\n                                  <object class=\"GtkAlignment\" id=\"alignment5\">\n                                    <property name=\"visible\">True</property>\n                                    <property name=\"can-focus\">False</property>\n                                    <property name=\"top-padding\">12</property>\n                                    <child>\n                                      <object class=\"GtkLabel\" id=\"label7\">\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">False</property>\n                                        <property name=\"halign\">start</property>\n                                        <property name=\"label\" translatable=\"yes\">Adding a drive here will make it use the specified parameters.</property>\n                                      </object>\n                                    </child>\n                                  </object>\n                                  <packing>\n                                    <property name=\"expand\">True</property>\n                                    <property name=\"fill\">True</property>\n                                    <property name=\"padding\">6</property>\n                                    <property name=\"position\">0</property>\n                                  </packing>\n                                </child>\n                                <child>\n                                  <object class=\"GtkBox\" id=\"hbox2\">\n                                    <property name=\"visible\">True</property>\n                                    <property name=\"can-focus\">False</property>\n                                    <property name=\"spacing\">12</property>\n                                    <child>\n                                      <object class=\"GtkBox\" id=\"vbox10\">\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">False</property>\n                                        <property name=\"orientation\">vertical</property>\n                                        <property name=\"spacing\">6</property>\n                                        <child>\n                                          <object class=\"GtkScrolledWindow\" id=\"scrolledwindow1\">\n                                            <property name=\"visible\">True</property>\n                                            <property name=\"can-focus\">True</property>\n                                            <property name=\"shadow-type\">in</property>\n                                            <child>\n                                              <object class=\"GtkTreeView\" id=\"device_options_treeview\">\n                                                <property name=\"visible\">True</property>\n                                                <property name=\"can-focus\">True</property>\n                                                <property name=\"tooltip-text\" translatable=\"yes\">Device list</property>\n                                                <property name=\"headers-clickable\">False</property>\n                                                <child internal-child=\"selection\">\n                                                  <object class=\"GtkTreeSelection\"/>\n                                                </child>\n                                              </object>\n                                            </child>\n                                          </object>\n                                          <packing>\n                                            <property name=\"expand\">True</property>\n                                            <property name=\"fill\">True</property>\n                                            <property name=\"position\">0</property>\n                                          </packing>\n                                        </child>\n                                        <child>\n                                          <object class=\"GtkBox\" id=\"hbox3\">\n                                            <property name=\"visible\">True</property>\n                                            <property name=\"can-focus\">False</property>\n                                            <property name=\"spacing\">6</property>\n                                            <property name=\"homogeneous\">True</property>\n                                            <child>\n                                              <object class=\"GtkButton\" id=\"device_options_remove_device_button\">\n                                                <property name=\"label\">gtk-remove</property>\n                                                <property name=\"visible\">True</property>\n                                                <property name=\"can-focus\">True</property>\n                                                <property name=\"receives-default\">True</property>\n                                                <property name=\"tooltip-text\" translatable=\"yes\">Remove selected entry from device list</property>\n                                                <property name=\"use-stock\">True</property>\n                                              </object>\n                                              <packing>\n                                                <property name=\"expand\">True</property>\n                                                <property name=\"fill\">True</property>\n                                                <property name=\"position\">0</property>\n                                              </packing>\n                                            </child>\n                                            <child>\n                                              <object class=\"GtkButton\" id=\"device_options_add_device_button\">\n                                                <property name=\"label\">gtk-add</property>\n                                                <property name=\"visible\">True</property>\n                                                <property name=\"can-focus\">True</property>\n                                                <property name=\"receives-default\">True</property>\n                                                <property name=\"tooltip-text\" translatable=\"yes\">Add a new entry to device list</property>\n                                                <property name=\"use-stock\">True</property>\n                                              </object>\n                                              <packing>\n                                                <property name=\"expand\">True</property>\n                                                <property name=\"fill\">True</property>\n                                                <property name=\"position\">1</property>\n                                              </packing>\n                                            </child>\n                                          </object>\n                                          <packing>\n                                            <property name=\"expand\">False</property>\n                                            <property name=\"fill\">True</property>\n                                            <property name=\"position\">1</property>\n                                          </packing>\n                                        </child>\n                                      </object>\n                                      <packing>\n                                        <property name=\"expand\">True</property>\n                                        <property name=\"fill\">True</property>\n                                        <property name=\"position\">0</property>\n                                      </packing>\n                                    </child>\n                                    <child>\n                                      <object class=\"GtkBox\" id=\"vbox11\">\n                                        <property name=\"visible\">True</property>\n                                        <property name=\"can-focus\">False</property>\n                                        <property name=\"orientation\">vertical</property>\n                                        <child>\n                                          <!-- n-columns=3 n-rows=4 -->\n                                          <object class=\"GtkGrid\">\n                                            <property name=\"visible\">True</property>\n                                            <property name=\"can-focus\">False</property>\n                                            <property name=\"row-spacing\">6</property>\n                                            <property name=\"column-spacing\">12</property>\n                                            <child>\n                                              <object class=\"GtkLabel\" id=\"label67\">\n                                                <property name=\"visible\">True</property>\n                                                <property name=\"can-focus\">False</property>\n                                                <property name=\"tooltip-text\" translatable=\"yes\">Smartctl parameters to add (for example, \"-T permissive\" or \"-d usbsunplus\")</property>\n                                                <property name=\"halign\">start</property>\n                                                <property name=\"label\" translatable=\"yes\">Add parameters:</property>\n                                                <property name=\"use-underline\">True</property>\n                                              </object>\n                                              <packing>\n                                                <property name=\"left-attach\">0</property>\n                                                <property name=\"top-attach\">3</property>\n                                              </packing>\n                                            </child>\n                                            <child>\n                                              <object class=\"GtkLabel\" id=\"device_options_type_label\">\n                                                <property name=\"visible\">True</property>\n                                                <property name=\"can-focus\">False</property>\n                                                <property name=\"tooltip-text\" translatable=\"yes\">Match only this type of device (as used by the -d smartctl parameter). Leave empty for all types. This can be used to match a drive behind a RAID device, e.g. \"areca,2\".</property>\n                                                <property name=\"halign\">start</property>\n                                                <property name=\"label\" translatable=\"yes\">Match type:</property>\n                                                <property name=\"use-underline\">True</property>\n                                              </object>\n                                              <packing>\n                                                <property name=\"left-attach\">0</property>\n                                                <property name=\"top-attach\">2</property>\n                                              </packing>\n                                            </child>\n                                            <child>\n                                              <object class=\"GtkLabel\" id=\"device_options_device_label\">\n                                                <property name=\"visible\">True</property>\n                                                <property name=\"can-focus\">False</property>\n                                                <property name=\"halign\">start</property>\n                                                <property name=\"label\" translatable=\"yes\">Match device:</property>\n                                                <property name=\"use-underline\">True</property>\n                                              </object>\n                                              <packing>\n                                                <property name=\"left-attach\">0</property>\n                                                <property name=\"top-attach\">1</property>\n                                              </packing>\n                                            </child>\n                                            <child>\n                                              <object class=\"GtkEntry\" id=\"device_options_parameter_entry\">\n                                                <property name=\"visible\">True</property>\n                                                <property name=\"can-focus\">True</property>\n                                                <property name=\"tooltip-text\" translatable=\"yes\">Smartctl parameters to add (for example, \"-T permissive\" or \"-d usbsunplus\")</property>\n                                                <property name=\"primary-icon-activatable\">False</property>\n                                                <property name=\"secondary-icon-activatable\">False</property>\n                                              </object>\n                                              <packing>\n                                                <property name=\"left-attach\">1</property>\n                                                <property name=\"top-attach\">3</property>\n                                              </packing>\n                                            </child>\n                                            <child>\n                                              <object class=\"GtkEntry\" id=\"device_options_type_entry\">\n                                                <property name=\"visible\">True</property>\n                                                <property name=\"can-focus\">True</property>\n                                                <property name=\"tooltip-text\" translatable=\"yes\">Match only this type of device (as used by the -d smartctl parameter). Leave empty for all types. This can be used to match a drive behind a RAID device, e.g. \"areca,2\".</property>\n                                                <property name=\"primary-icon-activatable\">False</property>\n                                                <property name=\"secondary-icon-activatable\">False</property>\n                                              </object>\n                                              <packing>\n                                                <property name=\"left-attach\">1</property>\n                                                <property name=\"top-attach\">2</property>\n                                              </packing>\n                                            </child>\n                                            <child>\n                                              <object class=\"GtkEntry\" id=\"device_options_device_entry\">\n                                                <property name=\"visible\">True</property>\n                                                <property name=\"can-focus\">True</property>\n                                                <property name=\"primary-icon-activatable\">False</property>\n                                                <property name=\"secondary-icon-activatable\">False</property>\n                                              </object>\n                                              <packing>\n                                                <property name=\"left-attach\">1</property>\n                                                <property name=\"top-attach\">1</property>\n                                              </packing>\n                                            </child>\n                                            <child>\n                                              <object class=\"GtkLabel\" id=\"label9\">\n                                                <property name=\"visible\">True</property>\n                                                <property name=\"can-focus\">False</property>\n                                                <property name=\"halign\">start</property>\n                                                <property name=\"label\" translatable=\"yes\">Drive Properties:</property>\n                                                <attributes>\n                                                  <attribute name=\"weight\" value=\"bold\"/>\n                                                </attributes>\n                                              </object>\n                                              <packing>\n                                                <property name=\"left-attach\">0</property>\n                                                <property name=\"top-attach\">0</property>\n                                                <property name=\"width\">2</property>\n                                              </packing>\n                                            </child>\n                                            <child>\n                                              <placeholder/>\n                                            </child>\n                                            <child>\n                                              <placeholder/>\n                                            </child>\n                                            <child>\n                                              <placeholder/>\n                                            </child>\n                                            <child>\n                                              <placeholder/>\n                                            </child>\n                                          </object>\n                                          <packing>\n                                            <property name=\"expand\">False</property>\n                                            <property name=\"fill\">True</property>\n                                            <property name=\"position\">0</property>\n                                          </packing>\n                                        </child>\n                                      </object>\n                                      <packing>\n                                        <property name=\"expand\">True</property>\n                                        <property name=\"fill\">True</property>\n                                        <property name=\"position\">1</property>\n                                      </packing>\n                                    </child>\n                                  </object>\n                                  <packing>\n                                    <property name=\"expand\">True</property>\n                                    <property name=\"fill\">True</property>\n                                    <property name=\"padding\">6</property>\n                                    <property name=\"position\">1</property>\n                                  </packing>\n                                </child>\n                              </object>\n                            </child>\n                          </object>\n                        </child>\n                        <child type=\"label\">\n                          <object class=\"GtkLabel\" id=\"label11\">\n                            <property name=\"visible\">True</property>\n                            <property name=\"can-focus\">False</property>\n                            <property name=\"label\" translatable=\"yes\">Per-Drive Smartctl Parameters</property>\n                            <attributes>\n                              <attribute name=\"weight\" value=\"bold\"/>\n                            </attributes>\n                          </object>\n                        </child>\n                      </object>\n                      <packing>\n                        <property name=\"expand\">False</property>\n                        <property name=\"fill\">True</property>\n                        <property name=\"padding\">6</property>\n                        <property name=\"position\">1</property>\n                      </packing>\n                    </child>\n                  </object>\n                  <packing>\n                    <property name=\"position\">1</property>\n                  </packing>\n                </child>\n                <child type=\"tab\">\n                  <object class=\"GtkLabel\" id=\"label4\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can-focus\">False</property>\n                    <property name=\"margin-left\">6</property>\n                    <property name=\"margin-right\">6</property>\n                    <property name=\"hexpand\">True</property>\n                    <property name=\"label\" translatable=\"yes\">_Drives</property>\n                    <property name=\"use-underline\">True</property>\n                    <property name=\"width-chars\">10</property>\n                  </object>\n                  <packing>\n                    <property name=\"position\">1</property>\n                    <property name=\"tab-fill\">False</property>\n                  </packing>\n                </child>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">True</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"hbox1\">\n            <property name=\"visible\">True</property>\n            <property name=\"can-focus\">False</property>\n            <property name=\"spacing\">6</property>\n            <property name=\"homogeneous\">True</property>\n            <child>\n              <object class=\"GtkButton\" id=\"window_reset_all_button\">\n                <property name=\"label\" translatable=\"yes\">_Reset all</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"receives-default\">True</property>\n                <property name=\"tooltip-text\" translatable=\"yes\">Reset all program settings to their defaults</property>\n                <property name=\"use-underline\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label2\">\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">False</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"window_cancel_button\">\n                <property name=\"label\">gtk-cancel</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"receives-default\">True</property>\n                <property name=\"use-stock\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"window_ok_button\">\n                <property name=\"label\">gtk-ok</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can-focus\">True</property>\n                <property name=\"is-focus\">True</property>\n                <property name=\"can-default\">True</property>\n                <property name=\"has-default\">True</property>\n                <property name=\"receives-default\">True</property>\n                <property name=\"use-stock\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">3</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "src/gui/ui/gsc_text_window.glade",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!-- Generated with glade 3.22.1 \n\nCopyright (C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n\nThis file is part of GSmartControl.\n\nGSmartControl is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nGSmartControl is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with GSmartControl.  If not, see <http://www.gnu.org/licenses/>.\n\n-->\n<interface>\n  <requires lib=\"gtk+\" version=\"3.20\"/>\n  <!-- interface-license-type gplv3 -->\n  <!-- interface-name GSmartControl -->\n  <!-- interface-copyright 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com> -->\n  <object class=\"GtkWindow\" id=\"gsc_text_window\">\n    <property name=\"can_focus\">False</property>\n    <property name=\"title\" translatable=\"yes\">GSmartControl</property>\n    <property name=\"default_width\">800</property>\n    <property name=\"default_height\">600</property>\n    <property name=\"destroy_with_parent\">True</property>\n    <child>\n      <placeholder/>\n    </child>\n    <child>\n      <object class=\"GtkBox\" id=\"vbox1\">\n        <property name=\"visible\">True</property>\n        <property name=\"can_focus\">False</property>\n        <property name=\"border_width\">12</property>\n        <property name=\"orientation\">vertical</property>\n        <property name=\"spacing\">12</property>\n        <child>\n          <object class=\"GtkBox\" id=\"vbox2\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"orientation\">vertical</property>\n            <child>\n              <object class=\"GtkScrolledWindow\" id=\"scrolledwindow1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"shadow_type\">in</property>\n                <child>\n                  <object class=\"GtkTextView\" id=\"main_textview\">\n                    <property name=\"visible\">True</property>\n                    <property name=\"can_focus\">True</property>\n                    <property name=\"has_focus\">True</property>\n                    <property name=\"is_focus\">True</property>\n                    <property name=\"editable\">False</property>\n                    <property name=\"left_margin\">5</property>\n                    <property name=\"right_margin\">5</property>\n                    <property name=\"cursor_visible\">False</property>\n                  </object>\n                </child>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">True</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">0</property>\n          </packing>\n        </child>\n        <child>\n          <object class=\"GtkBox\" id=\"hbox2\">\n            <property name=\"visible\">True</property>\n            <property name=\"can_focus\">False</property>\n            <property name=\"spacing\">6</property>\n            <property name=\"homogeneous\">True</property>\n            <child>\n              <object class=\"GtkButton\" id=\"save_as_button\">\n                <property name=\"label\">gtk-save-as</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"tooltip_text\" translatable=\"yes\">Saved displayed text as...</property>\n                <property name=\"use_stock\">True</property>\n                <accelerator key=\"S\" signal=\"activate\" modifiers=\"GDK_CONTROL_MASK\"/>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">0</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label1\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">1</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkLabel\" id=\"label2\">\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">False</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">2</property>\n              </packing>\n            </child>\n            <child>\n              <object class=\"GtkButton\" id=\"close_window_button\">\n                <property name=\"label\">gtk-close</property>\n                <property name=\"visible\">True</property>\n                <property name=\"can_focus\">True</property>\n                <property name=\"can_default\">True</property>\n                <property name=\"has_default\">True</property>\n                <property name=\"receives_default\">True</property>\n                <property name=\"tooltip_text\" translatable=\"yes\">Close this window</property>\n                <property name=\"use_stock\">True</property>\n              </object>\n              <packing>\n                <property name=\"expand\">True</property>\n                <property name=\"fill\">True</property>\n                <property name=\"position\">3</property>\n              </packing>\n            </child>\n          </object>\n          <packing>\n            <property name=\"expand\">False</property>\n            <property name=\"fill\">True</property>\n            <property name=\"position\">1</property>\n          </packing>\n        </child>\n      </object>\n    </child>\n  </object>\n</interface>\n"
  },
  {
    "path": "src/hz/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nadd_library(hz INTERFACE)\n\n# Relative sources are allowed only since cmake 3.13.\ntarget_sources(hz INTERFACE\n\t${CMAKE_CURRENT_SOURCE_DIR}/bad_cast_exception.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/data_file.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/debug.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/enum_helper.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/env_tools.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/error_container.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/error_holder.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/format_unit.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/fs.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/fs_ns.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/launch_url.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/locale_tools.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/main_tools.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/process_signal.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/stream_cast.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/string_algo.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/string_num.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/string_sprintf.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/system_specific.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/win32_tools.h\n)\n\ntarget_link_libraries(hz\n\tINTERFACE\n#\t\tlibdebug\n\t\tapp_gtkmm_interface  # ENABLE_* macros\n\t\tapp_gettext_interface  # format_unit.h uses this\n\t\tlibdebug  # debug.h\n\t\twhereami  # whereami.h\n\t\ttl_expected  # error_container.h\n#\t\tstd::filesystem  # fs_ns.h, linking to stdc++fs / c++fs if needed\n)\n\n# Support <experimental/filesystem> if needed\n#if (\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"GNU\")\n#\ttarget_link_libraries(hz\n#\t\tINTERFACE\n#\t\t\tstdc++fs\n#\t)\n#endif()\n\ntarget_include_directories(hz\n\tINTERFACE\n\t\t\"${CMAKE_SOURCE_DIR}/src\"\n)\n\ntarget_compile_definitions(hz\n\tINTERFACE\n\t\tHZ_USE_LIBDEBUG=1\n)\n\n\nadd_subdirectory(tests)\n\n"
  },
  {
    "path": "src/hz/bad_cast_exception.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_BAD_CAST_EXCEPTION_H\n#define HZ_BAD_CAST_EXCEPTION_H\n\n#include <exception>  // std::exception\n#include <string>\n#include <typeinfo>  // std::type_info\n\n#include \"system_specific.h\"  // type_name_demangle\n#include \"string_sprintf.h\"  // hz::string_sprintf\n\n\n\nnamespace hz {\n\n\n\n/// Children of this class are thrown from various casting functions\nclass bad_cast_except : public std::exception {  // from <exception>\n\tpublic:\n\n\t\t/// Constructor\n\t\t/// \\param src source type\n\t\t/// \\param dest destination type\n\t\t/// \\param self_name child class name\n\t\t/// \\param error_msg error message\n\t\tbad_cast_except(const std::type_info& src, const std::type_info& dest,\n\t\t\t\tconst char* self_name = nullptr, const char* error_msg = nullptr)\n\t\t\t: src_type_(src), dest_type_(dest),\n\t\t\tself_name_(self_name ? self_name : \"bad_cast_except\"),\n\t\t\terror_msg_(error_msg ? error_msg : \"Type cast failed from \\\"%s\\\" to \\\"%s\\\".\")  // still need %s here for correct arg count for printf\n\t\t{ }\n\n\n\t\t/// Reimplemented from std::exception\n\t\tconst char* what() const noexcept override\n\t\t{\n\t\t\t// Note: we do quite a lot of construction here, but since it's not\n\t\t\t// an out-of-memory exception, what the heck.\n\t\t\tconst std::string who = (self_name_.empty() ? \"[unknown]\" : self_name_);\n\n\t\t\tstd::string from = (src_type_ == typeid(void) ? \"[unknown]\" : hz::type_name_demangle(src_type_.name()));\n\t\t\tif (from.empty())\n\t\t\t\tfrom = src_type_.name();\n\n\t\t\tstd::string to = (dest_type_ == typeid(void) ? \"[unknown]\" : hz::type_name_demangle(dest_type_.name()));\n\t\t\tif (to.empty())\n\t\t\t\tto = dest_type_.name();\n\n\t\t\treturn (msg_ = hz::string_sprintf((who + \": \" + error_msg_).c_str(), from.c_str(), to.c_str())).c_str();\n\t\t}\n\n\n\t\t/// Get source type\n\t\tconst std::type_info& src_type() const\n\t\t{\n\t\t\treturn src_type_;\n\t\t}\n\n\n\t\t/// Get destination type\n\t\tconst std::type_info& dest_type() const\n\t\t{\n\t\t\treturn dest_type_;\n\t\t}\n\n\n\tprivate:\n\n\t\tconst std::type_info& src_type_;  ///< Cast source type info. Can be a reference since type_info objects are guaranteed to live forever.\n\t\tconst std::type_info& dest_type_;  ///< Cast destination type info\n\n\t\tmutable std::string msg_;  ///< This must be a member to avoid its destruction on function call return. use what().\n\n\t\tstd::string self_name_;  ///< The exception class name\n\t\tstd::string error_msg_;  ///< Error message\n};\n\n\n}  // ns\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/data_file.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_DATA_FILE_H\n#define HZ_DATA_FILE_H\n\n#include <string>\n#include <vector>\n#include <unordered_map>\n\n#include \"fs.h\"\n#include \"debug.h\"\n\n\n\nnamespace hz {\n\n\n/// Static variable holder\nstruct DataFileStaticHolder {\n\t///< Search directories for data files\n\tstatic inline std::unordered_map<std::string, std::vector<fs::path>> search_directories;\n};\n\n\n\n//------------------------------------------------------------------------\n\n\n/// Add a directory to a search path\ninline void data_file_add_search_directory(const std::string& domain, fs::path path)\n{\n\tif (!path.empty())\n\t\tDataFileStaticHolder::search_directories[domain].push_back(std::move(path));\n}\n\n\n\n/// Get currently registered search directories (a copy is returned)\ninline std::vector<fs::path> data_file_get_search_directories(const std::string& domain)\n{\n\tif (DataFileStaticHolder::search_directories.contains(domain)) {\n\t\treturn DataFileStaticHolder::search_directories.at(domain);\n\t}\n\treturn {};\n}\n\n\n\n/// Set a directory list for a search path\ninline void data_file_set_search_directories(const std::string& domain, std::vector<fs::path> dirs)\n{\n\tDataFileStaticHolder::search_directories.insert_or_assign(domain, std::move(dirs));\n}\n\n\n\n/// Find a data file (using a file name) in a search directory list.\n/// \\return A full path to filename, or empty string on \"not found\".\ninline fs::path data_file_find(const std::string& domain, const std::string& filename, bool allow_to_be_directory = false)\n{\n\tif (filename.empty())\n\t\treturn {};\n\n\tif (fs_path_from_string(filename).is_absolute()) {  // shouldn't happen\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG\n\t\t\t\t<< \"Data file \\\"\" << filename << \"\\\" must be relative.\\n\");\n\t\treturn {};\n\t}\n\n\tauto dirs = data_file_get_search_directories(domain);\n\tif (dirs.empty()) {  // shouldn't happen\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG\n\t\t\t\t<< \"No search directories registered for domain \\\"\" << domain << \"\\\".\\n\");\n\t\treturn {};\n\t}\n\n\tfor (const auto& dir : dirs) {\n\t\tauto file_path = dir / fs_path_from_string(filename);\n\n\t\tstd::error_code ec;\n\t\tif (fs::exists(file_path, ec)) {\n\t\t\tif (!allow_to_be_directory && fs::is_directory(file_path, ec)) {\n\t\t\t\tdebug_out_error(\"app\", DBG_FUNC_MSG\n\t\t\t\t\t\t<< \"Data file \\\"[\" << domain << \":]\" << file_path << \"\\\" file found at \\\"\" << dir << \"\\\", but it is a directory.\\n\");\n\t\t\t\treturn {};\n\t\t\t}\n\t\t\tdebug_out_info(\"app\", DBG_FUNC_MSG\n\t\t\t\t\t<< \"Data file \\\"[\" << domain << \":]\" << file_path << \"\\\" file found at \\\"\" << dir << \"\\\".\\n\");\n\t\t\treturn file_path;\n\t\t}\n\t}\n\n\tdebug_out_error(\"app\", DBG_FUNC_MSG\n\t\t\t<< \"Data file \\\"[\" << domain << \":]\" << filename << \"\\\" not found.\\n\");\n\treturn {};\n}\n\n\n\n/// Get data file contents.\ninline std::string data_file_get_contents(const std::string& domain, const std::string& filename, std::uintmax_t max_size)\n{\n\tauto file = data_file_find(domain, filename);\n\tif (!file.empty()) {\n\t\tstd::string contents;\n\t\tauto ec = hz::fs_file_get_contents(file, contents, max_size);\n\t\tif (!ec) {\n\t\t\treturn contents;\n\t\t}\n\t\tdebug_out_error(\"app\", DBG_FUNC_MSG\n\t\t\t\t<< \"Data file \\\"[\" << domain << \":]\" << filename << \"\\\" cannot be loaded: \" << ec.message() << \".\\n\");\n\t}\n\treturn {};\n}\n\n\n\n\n} // ns\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/debug.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_DEBUG_H\n#define HZ_DEBUG_H\n\n#include <cstdio>  // std::fprintf(), std::vfprintf()\n\n\n/*\n#include <cassert>\n#ifndef ASSERT  // assert() is undefined if NDEBUG is defined.\n#\tdefine ASSERT(a) assert(a)\n#endif\n*/\n\n/**\n\\file\nThis file is a link between libhz (and its users) and libdebug.\nIt provides a way to write in libdebug-like API without actually\nusing libdebug.\n\nNote that it provides only output functions. The setup functions\ncannot be emulated (but you probably won't need them in libraries\nanyway).\n*/\n\n\n// Use libdebug as is\n#if defined(HZ_USE_LIBDEBUG) && (HZ_USE_LIBDEBUG)\n\n\t// only output functions\n\t#include \"libdebug/libdebug_mini.h\"\n\n\n#else\n\n\t// undef them in case libdebug was included\n\t#ifdef debug_out_dump\n\t\t#undef debug_out_dump\n\t#endif\n\t#ifdef debug_out_info\n\t\t#undef debug_out_info\n\t#endif\n\t#ifdef debug_out_warn\n\t\t#undef debug_out_warn\n\t#endif\n\t#ifdef debug_out_error\n\t\t#undef debug_out_error\n\t#endif\n\t#ifdef debug_out_fatal\n\t\t#undef debug_out_fatal\n\t#endif\n\n\t#ifdef DBG_FILE\n\t\t#undef DBG_FILE\n\t#endif\n\t#ifdef DBG_LINE\n\t\t#undef DBG_LINE\n\t#endif\n\t#ifdef DBG_FUNC_NAME\n\t\t#undef DBG_FUNC_NAME\n\t#endif\n\t#ifdef DBG_FUNC_PRNAME\n\t\t#undef DBG_FUNC_PRNAME\n\t#endif\n\t#ifdef DBG_FUNC\n\t\t#undef DBG_FUNC\n\t#endif\n\t#ifdef DBG_FUNC_MSG\n\t\t#undef DBG_FUNC_MSG\n\t#endif\n\n\t#ifdef DBG_POS\n\t\t#undef DBG_POS\n\t#endif\n\n\t#ifdef DBG_TRACE_POINT_MSG\n\t\t#undef DBG_TRACE_POINT_MSG\n\t#endif\n\t#ifdef DBG_TRACE_POINT_AUTO\n\t\t#undef DBG_TRACE_POINT_AUTO\n\t#endif\n\n\t#ifdef DBG_FUNCTION_ENTER_MSG\n\t\t#undef DBG_FUNCTION_ENTER_MSG\n\t#endif\n\t#ifdef DBG_FUNCTION_EXIT_MSG\n\t\t#undef DBG_FUNCTION_EXIT_MSG\n\t#endif\n\n\t#ifdef DBG_ASSERT_MSG\n\t\t#undef DBG_ASSERT_MSG\n\t#endif\n\t#ifdef DBG_ASSERT\n\t\t#undef DBG_ASSERT\n\t#endif\n\n\n\t// emulate libdebug API through std::cerr\n\t#if defined(HZ_EMULATE_LIBDEBUG) && HZ_EMULATE_LIBDEBUG\n\n\t\t#include <iostream>\n\t\t#include <cstdio>\n\t\t#include <string>\n\n\t\t// cerr / stderr have no buffering, so no need to sync them with each other.\n\n\n\t\t#define debug_out_dump(domain, output) \\\n\t\t\tstd::cerr << \"<dump>  [\" << (domain) << \"] \" << output\n\n\t\t#define debug_out_info(domain, output) \\\n\t\t\tstd::cerr << \"<info>  [\" << (domain) << \"] \" << output\n\n\t\t#define debug_out_warn(domain, output) \\\n\t\t\tstd::cerr << \"<warn>  [\" << (domain) << \"] \" << output\n\n\t\t#define debug_out_error(domain, output) \\\n\t\t\tstd::cerr << \"<error> [\" << (domain) << \"] \" << output\n\n\t\t#define debug_out_fatal(domain, output) \\\n\t\t\tstd::cerr << \"<fatal> [\" << (domain) << \"] \" << output\n\n\n\t\t#define DBG_FILE __FILE__\n\t\t#define DBG_LINE __LINE__\n\t\t#define DBG_FUNC_NAME __func__\n\n\t\t#ifdef __GNUC__\n\t\t\t#define DBG_FUNC_PRNAME __PRETTY_FUNCTION__\n\t\t#else\n\t\t\t#define DBG_FUNC_PRNAME DBG_FUNC_NAME\n\t\t#endif\n\n\n\t\t#include <string>\n\n\t\tnamespace hz {\n\t\t\tnamespace internal {\n\t\t\t\tinline std::string format_function_msg(const std::string& func, bool add_suffix)\n\t\t\t\t{\n\t\t\t\t\t// if it's \"bool<unnamed>::A::func(int)\" or \"bool test::A::func(int)\",\n\t\t\t\t\t// remove the return type and parameters.\n\t\t\t\t\tstd::string::size_type endpos = func.find('(');\n\t\t\t\t\tif (endpos == std::string::npos)\n\t\t\t\t\t\tendpos = func.size();\n\n\t\t\t\t\t// search for first space (after the parameter), or \"<unnamed>\".\n\t\t\t\t\tstd::string::size_type pos = func.find_first_of(\" >\");\n\t\t\t\t\tif (pos != std::string::npos) {\n\t\t\t\t\t\tif (func[pos] == '>')\n\t\t\t\t\t\t\tpos += 2;  // skip ::\n\t\t\t\t\t\t++pos;  // skip whatever character we're over\n\t\t\t\t\t\t// debug_out_info(\"default\", \"pos: \" << pos << \", endpos: \" << endpos << \"\\n\");\n\t\t\t\t\t\treturn func.substr(pos >= endpos ? 0 : pos, endpos - pos) + (add_suffix ? \"(): \" : \"()\");\n\t\t\t\t\t}\n\t\t\t\t\treturn func.substr(0, endpos) + (add_suffix ? \"(): \" : \"()\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t#define DBG_FUNC (hz::internal::format_function_msg(DBG_FUNC_PRNAME, false).c_str())\n\n\t\t#define DBG_FUNC_MSG (hz::internal::format_function_msg(DBG_FUNC_PRNAME, true).c_str())\n\n\n\t\t// Note: DBG_POS is not an object if emulated or disabled! Only valid for outputting to streams.\n\t\t#define DBG_POS \"(function: \" << DBG_FUNC_NAME << \"(), file: \" << DBG_FILE \\\n\t\t\t\t<< \", line: \" << DBG_LINE << \")\"\n\n\n\t\t#define DBG_TRACE_POINT_MSG(a) debug_out_dump(\"default\", \"Trace point \\\"\" << #a << \"\\\" reached at \" << DBG_POS << \".\\n\")\n\t\t#define DBG_TRACE_POINT_AUTO debug_out_dump(\"default\", \"Trace point reached at \" << DBG_POS << \".\\n\")\n\n\t\t#define DBG_FUNCTION_ENTER_MSG debug_out_dump(\"default\", \"ENTER: \\\"\" << DBG_FUNC << \"\\\"\\n\")\n\t\t#define DBG_FUNCTION_EXIT_MSG debug_out_dump(\"default\", \"EXIT:  \\\"\" << DBG_FUNC << \"\\\"\\n\")\n\n\n\t\t#define DBG_ASSERT_MSG(cond, msg) \\\n\t\tif (true) { \\\n\t\t\tif (!(cond)) \\\n\t\t\t\tdebug_out_error(\"default\", (msg) << \"\\n\"); \\\n\t\t} else (void)0\n\n\t\t#define DBG_ASSERT(cond) \\\n\t\tif (true) { \\\n\t\t\tif (!(cond)) \\\n\t\t\t\tdebug_out_error(\"default\", \"ASSERTION FAILED: \" << #cond << \" at \" << DBG_POS << \"\\n\"); \\\n\t\t} else (void)0\n\n\n\t// No output at all\n\t#else\n\n\t\t// do/while block is needed to:\n\t\t// 1. make \" if(a) debug_out_info(); f(); \" work correctly.\n\t\t// 2. require terminating semicolon.\n\t\t#define debug_out_dump(domain, output) if(true){}else(void)0\n\t\t#define debug_out_info(domain, output) if(true){}else(void)0\n\t\t#define debug_out_warn(domain, output) if(true){}else(void)0\n\t\t#define debug_out_error(domain, output) if(true){}else(void)0\n\t\t#define debug_out_fatal(domain, output) if(true){}else(void)0\n\n\t\t#define DBG_FILE \"\"\n\t\t#define DBG_LINE 0\n\n\t\t#define DBG_FUNC_NAME \"\"\n\t\t#define DBG_FUNC_PRNAME \"\"\n\t\t#define DBG_FUNC \"\"\n\t\t#define DBG_FUNC_MSG \"\"\n\n\t\t// Note: DBG_POS is not an object if emulated or disabled!\n\t\t#define DBG_POS \"\"\n\n\t\t#define DBG_TRACE_POINT_MSG(a) if(true){}else(void)0\n\t\t#define DBG_TRACE_POINT_AUTO if(true){}else(void)0\n\n\t\t#define DBG_FUNCTION_ENTER_MSG if(true){}else(void)0\n\t\t#define DBG_FUNCTION_EXIT_MSG if(true){}else(void)0\n\n\t\t#define DBG_ASSERT_MSG(cond, msg) if(true){}else(void)0\n\t\t#define DBG_ASSERT(cond) if(true){}else(void)0\n\n\t#endif\n\n\n\t// other stuff, emulated or not\n\n// \t#ifdef debug_begin\n// \t\t#undef debug_begin\n// \t#endif\n\t#define debug_begin() if(true){}else(void)0\n\n// \t#ifdef debug_end\n// \t\t#undef debug_end\n// \t#endif\n\t#define debug_end() if(true){}else(void)0\n\n\n\t#define debug_indent_inc(...) if(true){}else(void)0\n\t#define debug_indent_dec(...) if(true){}else(void)0\n\t#define debug_indent_reset() if(true){}else(void)0\n\n\t#define debug_indent \"\"\n\t#define debug_unindent \"\"\n\t#define debug_resindent \"\"\n\n\n\n#endif\n\n\n\n\n\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/enum_helper.h",
    "content": "/******************************************************************************\nLicense: GNU General Public License v3.0 only\nCopyright:\n\t(C) 2022 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef ENUM_HELPER_H\n#define ENUM_HELPER_H\n\n#include <string>\n#include <vector>\n#include <utility>\n#include <unordered_map>\n#include <algorithm>\n\n\n\nnamespace hz {\n\n\n/// Helper class for defining enum-related functions.\n/// In EnumExtClass it expects the following (accessible) members:\n/// - static inline EnumType default_value = ...;\n/// - static const std::unordered_map<EnumType, std::pair<std::string, DisplayableStringType>>& get_enum_static_map();\ntemplate <typename Enum, typename EnumExtClass, typename DisplayableStringType>\nclass EnumHelper {\n\tpublic:\n\n\t\tusing EnumType = Enum;\n\t\tusing EnumMapType = std::unordered_map<EnumType, std::pair<std::string, DisplayableStringType>>;\n\n\n\t\t/// Return storable name of an enum member\n\t\t[[nodiscard]] static std::string get_storable_name(EnumType enum_value)\n\t\t{\n\t\t\tconst auto& m = get_enum_static_map();\n\t\t\t// Iterator: enum -> pair{storable, displayable}\n\t\t\tauto iter = m.find(enum_value);\n\t\t\treturn iter != m.end() ? std::string(iter->second.first) : std::string();\n\t\t}\n\n\n\t\t/// Return an enum member by its storable name\n\t\t[[nodiscard]] static EnumType get_by_storable_name(const std::string& storable_name,\n\t\t\t\tEnumType default_value = EnumExtClass::default_value)\n\t\t{\n\t\t\tconst auto& m = get_storable_enum_static_map();\n\t\t\t// Iterator: storable_name -> enum\n\t\t\tauto iter = m.find(storable_name);\n\t\t\treturn iter != m.end() ? iter->second : default_value;\n\t\t}\n\n\n\t\t/// Return displayable name of an enum member\n\t\t[[nodiscard]] static DisplayableStringType get_displayable_name(EnumType enum_value)\n\t\t{\n\t\t\tconst auto& m = get_enum_static_map();\n\t\t\t// Iterator: enum -> pair{storable, displayable}\n\t\t\tauto iter = m.find(enum_value);\n\t\t\treturn iter != m.end() ? DisplayableStringType(iter->second.second) : DisplayableStringType();\n\t\t}\n\n\n\t\t/// Return all possible members of an enum\n\t\t[[nodiscard]] static std::vector<EnumType> get_all_values()\n\t\t{\n\t\t\tstatic const auto v = build_enum_value_list();\n\t\t\treturn v;\n\t\t}\n\n\n\tprivate:\n\n\t\t/// Get a static map of storable names to enum values.\n\t\t[[nodiscard]] static const EnumMapType& get_enum_static_map()\n\t\t{\n\t\t\tstatic const auto m = EnumExtClass::build_enum_map();\n\t\t\treturn m;\n\t\t}\n\n\n\t\t/// Get a static map of storable names to enum values.\n\t\t[[nodiscard]] static const std::unordered_map<std::string, EnumType>& get_storable_enum_static_map()\n\t\t{\n\t\t\tstatic const auto m = build_storable_enum_map();\n\t\t\treturn m;\n\t\t}\n\n\n\t\t/// Build a map of storable names to enum values.\n\t\tstatic std::unordered_map<std::string, EnumType> build_storable_enum_map()\n\t\t{\n\t\t\tstd::unordered_map<std::string, EnumType> m;\n\t\t\tfor (const auto& [enum_value, data] : EnumExtClass::get_enum_static_map()) {\n\t\t\t\tm.emplace(data.first, enum_value);\n\t\t\t}\n\t\t\treturn m;\n\t\t}\n\n\n\t\t/// Build a list of enum values from get_enum_static_map()\n\t\tstatic std::vector<EnumType> build_enum_value_list()\n\t\t{\n\t\t\tconst auto& m = EnumExtClass::get_enum_static_map();\n\t\t\tstd::vector<EnumType> v;\n\t\t\tv.reserve(m.size());\n\t\t\tfor (const auto& p : m) {\n\t\t\t\tv.push_back(p.first);\n\t\t\t}\n\t\t\tstd::sort(v.begin(), v.end());\n\t\t\treturn v;\n\t\t}\n\n};\n\n\n\n}\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/env_tools.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_ENV_TOOLS_H\n#define HZ_ENV_TOOLS_H\n\n#include <string>\n#include <memory>\n#include <utility>\n\n\n/// \\def HAVE_SETENV\n/// Defined to 0 or 1. If 1, the compiler has setenv() and unsetenv().\n#ifndef HAVE_SETENV\n// setenv(), unsetenv() glibc feature test macros:\n// _BSD_SOURCE || _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600.\n// However, since they are available almost anywhere except windows,\n// we just test for that.\n\t#if defined _WIN32\n\t\t#define HAVE_SETENV 0\n\t#else\n\t\t#define HAVE_SETENV 1\n\t#endif\n#endif\n\n\n#if defined _WIN32\n\t#include <windows.h>  // winapi stuff\n\t#include \"win32_tools.h\"  // win32_*\n#elif defined ENABLE_GLIB && ENABLE_GLIB\n\t#include <glib.h>\n#else\n\t#include <cstdlib>  // for stdlib.h, std::getenv\n\t#include <cstring>  // std::malloc\n\t#include <stdlib.h>  // setenv, unsetenv, putenv\n#endif\n\n\n/**\n\\file\nEnvironment manipulation functions.\nOn windows, these always work with utf-8 strings.\n*/\n\n\nnamespace hz {\n\n\n/// Get environment variable value.\n/// \\return false if error or no such variable.\ninline bool env_get_value(const std::string& name, std::string& value)\n{\n\tif (name.empty())\n\t\treturn false;\n\n#if defined _WIN32\n\n\t// getenv and _wgetenv are not thread-safe under windows, so use winapi.\n\n\tstd::wstring wname = hz::win32_utf8_to_utf16(name);\n\tif (wname.empty())  // conversion error\n\t\treturn false;\n\n\twchar_t dummy[2];\n\t// Determine the size of needed buffer by supplying an undersized one.\n\t// If it fits, returns number of chars not including 0. If not, returns the number\n\t// of chars needed for full data, including 0.\n\tDWORD len = GetEnvironmentVariableW(wname.c_str(), dummy, 2);\n\n\tif (len == 0) {  // failure\n\t\treturn false;\n\t}\n\tif (len == 1) {  // whaddayaknow, it fit\n\t\tlen = 2;\n\t}\n\n\tauto wvalue = std::make_unique<wchar_t[]>(len);\n\tif (GetEnvironmentVariableW(wname.c_str(), wvalue.get(), len) != len - 1) {  // failure\n\t\treturn false;\n\t}\n\n\tif (wcschr(wvalue.get(), L'%') != 0) {  // check for embedded variables\n\n\t\t// This always includes 0 in the returned length. Go figure.\n\t\tlen = ExpandEnvironmentStringsW(wvalue.get(), dummy, 2);  // get the needed length\n\n\t\tif (len > 0) {  // not failure\n\t\t\tauto wvalue_exp = std::make_unique<wchar_t[]>(len);\n\t\t\tif (ExpandEnvironmentStringsW(wvalue.get(), wvalue_exp.get(), len) != len)  // failure\n\t\t\t\treturn false;\n\n\t\t\tvalue = hz::win32_utf16_to_utf8(wvalue_exp.get());\n\t\t\treturn true;\n\t\t}\n\t}\n\n\tvalue = hz::win32_utf16_to_utf8(wvalue.get());\n\treturn true;\n\n\n#elif defined ENABLE_GLIB && ENABLE_GLIB\n\t// glib version may be thread-unsafe on win32, so don't use it there.\n\tconst char* v = g_getenv(name.c_str());\n\tif (!v)\n\t\treturn false;\n\tvalue = v;\n\treturn true;\n\n#else\n\t// Since C++11, getenv has to be thread-safe (unless some other thread is\n\t// modifying the environment).\n\tconst char* v = std::getenv(name.c_str());\n\tif (!v)\n\t\treturn false;\n\tvalue = v;\n\treturn true;\n\n#endif\n}\n\n\n\n/// Set environment variable. If \\c overwrite is false, the value won't\n/// be overwritten if it already exists.\n/// NOTE: This function is not thread-safe in GLibc and should only be invoked at the start of the program.\n/// \\return true if the value was written successfully.\ninline bool env_set_value(const std::string& name, const std::string& value, bool overwrite = true)\n{\n\tif (name.empty() || name.find('=') != std::string::npos)\n\t\treturn false;\n\n#if defined _WIN32\n\tstd::string oldvalue;\n\tif (!overwrite && env_get_value(name, oldvalue))\n\t\treturn true;\n\n\tstd::wstring wname = hz::win32_utf8_to_utf16(name);\n\tstd::wstring wvalue = hz::win32_utf8_to_utf16(value);\n\n\tif (wname.empty() || wvalue.empty())  // conversion error\n\t\treturn false;\n\n\t// Since using main() instead of wmain() causes environment tables to be\n\t// desynchronized, calling _wputenv() could correct the _wgetenv() table.\n\t// However, both of them are not thread-safe (their secure variants too),\n\t// so we avoid them.\n\n\treturn (SetEnvironmentVariableW(wname.c_str(), wvalue.c_str()) != 0);\n\n\t// glib version may be thread-unsafe on win32, so don't use it there.\n#elif defined ENABLE_GLIB && ENABLE_GLIB\n\treturn g_setenv(name.c_str(), value.c_str(), static_cast<gboolean>(overwrite)) != 0;  // may be thread-unsafe\n\n\n#elif defined HAVE_SETENV && HAVE_SETENV\n\t// setenv returns -1 if there's insufficient space in environment\n\t// or it's an invalid name (EINVAL errno is set).\n\treturn setenv(name.c_str(), value.c_str(), overwrite) == 0;\n\n#else\n\tstd::string oldvalue;\n\tif (!overwrite && env_get_value(name, oldvalue)) {\n\t\treturn true;\n\t}\n\tstd::string putenv_arg = name + \"=\" + value;\n\tchar* buf = (char*)std::malloc(putenv_arg.size() + 1);\n\tstd::memcpy(buf, putenv_arg.c_str(), putenv_arg.size() + 1);\n\treturn putenv(buf) == 0;  // returns 0 on success. Potentially leaks, but it is the way putenv() behaves.\n#endif\n\n}\n\n\n\n/// Unset an environment variable.\n/// NOTE: This function is not thread-safe in GLibc and should only be invoked at the start of the program.\ninline bool env_unset_value(const std::string& name)\n{\n\tif (name.empty() || name.find('=') != std::string::npos)\n\t\treturn false;\n\n#if defined _WIN32\n\tstd::wstring wname = hz::win32_utf8_to_utf16(name);\n\n\tif (!wname.empty()) {\n\t\t// we could do _wputenv(\"name=\") or _wputenv_s(...) too, but they're not thread-safe.\n\t\treturn SetEnvironmentVariableW(wname.c_str(), 0);\n\t}\n\treturn false;\n\n#elif defined ENABLE_GLIB && ENABLE_GLIB\n\tg_unsetenv(name.c_str());  // returns void. may not be thread-safe!\n\treturn true;\n\n#elif defined HAVE_SETENV && HAVE_SETENV\n\t// unsetenv returns -1 on error with errno EINVAL set (invalid char in name).\n\treturn unsetenv(name.c_str()) == 0;\n\n#else\n\tstd::string putenv_arg = name + \"=\";\n\tchar* buf = (char*)std::malloc(putenv_arg.size() + 1);\n\tstd::memcpy(buf, putenv_arg.c_str(), putenv_arg.size() + 1);\n\treturn putenv(buf) == 0;  // returns 0 on success. Potentially leaks, but it is the way putenv() behaves.\n#endif\n\n}\n\n\n\n\n\n}  // ns\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/error_container.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2024 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_ERROR_H\n#define HZ_ERROR_H\n\n#include <source_location>\n#include <string>\n#include <tl/expected.hpp>\n#include <utility>\n// #include <stacktrace>\n\n\n\nnamespace hz {\n\n\n\n/// ErrorContainer is a generic class that can carry any type of error data.\n/// Based on:\n/// Exceptionally Bad: The Misuse of Exceptions in C++ & How to Do Better - Peter Muldoon - CppCon 2023\ntemplate<typename ErrorData>\nclass ErrorContainer {\n\tpublic:\n\n\t\t/// Constructor\n\t\tErrorContainer(ErrorData data, std::string error_message,\n\t\t\t\tconst std::source_location& loc = std::source_location::current()\n\t\t\t\t// std::stacktrace trace = std::stacktrace::current()\n\t\t\t)\n\t\t\t: error_message_(std::move(error_message)),\n\t\t\tdata_(std::move(data)),\n\t\t\tlocation_(loc)\n\t\t\t// backtrace_(trace)\n\t\t{ }\n\n\n\t\t/// Get the error data\n\t\t[[nodiscard]] const ErrorData& data() const noexcept\n\t\t{\n\t\t\treturn data_;\n\t\t}\n\n\n\t\t/// Get the error data reference\n\t\t// [[nodiscard]] ErrorData& get_data_ref()\n\t\t// {\n\t\t// \treturn data_;\n\t\t// }\n\n\n\t\t/// Get the error message\n\t\t[[nodiscard]] const std::string& message() const noexcept\n\t\t{\n\t\t\treturn error_message_;\n\t\t}\n\n\n\t\t/// Get the error message reference\n\t\t// [[nodiscard]] std::string& get_message_ref()\n\t\t// {\n\t\t// \treturn error_message_;\n\t\t// }\n\n\n\t\t/// Get the error source location\n\t\t[[nodiscard]] const std::source_location& where() const\n\t\t{\n\t\t\treturn location_;\n\t\t}\n\n\n\t\t/// Get the error stack trace\n\t\t// [[nodiscard]] const std::stacktrace& stack() const\n\t\t// {\n\t\t// \treturn backtrace_;\n\t\t// }\n\n\n\tprivate:\n\n\t\tstd::string error_message_;\n\t\tErrorData data_;\n\t\tstd::source_location location_;\n\t\t// std::stacktrace backtrace_;\n};\n\n\n\n/// ExpectedValue is a wrapper around tl::expected that uses ErrorContainer as the error type.\ntemplate<typename ValueType, typename ErrorType>\nusing ExpectedValue = tl::expected<ValueType, ErrorContainer<ErrorType>>;\n\n\n/// ExpectedVoid is a wrapper around tl::expected that uses ErrorContainer as the error type and void as value.\ntemplate<typename ErrorType>\nusing ExpectedVoid = tl::expected<void, ErrorContainer<ErrorType>>;\n\n\n/// Unexpected creates an unexpected value with an ErrorContainer as error.\ntemplate<typename ErrorContainerWithData>\nauto UnexpectedFromContainer(const ErrorContainerWithData& container)\n{\n\treturn tl::unexpected(container);\n}\n\n\n/// Unexpected creates an unexpected value with an ErrorContainer.\ntemplate<typename ErrorData>\nauto Unexpected(ErrorData&& data, std::string error_message,\n\t\tconst std::source_location& loc = std::source_location::current()\n\t\t// std::stacktrace trace = std::stacktrace::current()\n\t)\n{\n\treturn tl::unexpected(ErrorContainer<ErrorData>(std::forward<ErrorData>(data),\n\t\t\tstd::move(error_message),\n\t\t\tloc\n\t\t\t// trace\n\t));\n}\n\n\n/// UnexpectedFrom creates an unexpected value from ExpectedValue or ExpectedVoid\n/// containing an error.\ntemplate<typename ExpectedValueT>\nauto UnexpectedFrom(ExpectedValueT unexpected_value)\n{\n\treturn tl::unexpected(unexpected_value.error());\n}\n\n\n\n/*\nstd::ostream& operator<< (std::ostream& os, const std::source_location& location)\n{\n\treturn os << location.file_name() << \"(\"\n\t\t<< location.line() << \":\"\n\t\t<< location.column() << \"), function `\"\n\t\t<< location.function_name() << \"`\";\n}\n\n\nstd::ostream& operator<< (std::ostream& os, const std::stacktrace& backtrace)\n{\n\tif (backtrace.size() <= 3>) {\n\t\tos << \"No stack trace available.\\n\";\n\t\treturn os;\n\t}\n\tif (backtrace.size() > 3) {\n\t\tos << \"Stack trace (most recent call last):\\n\";\n\t\tfor (auto iter = backtrace.begin(); iter != (backtrace.end() - 3); ++iter) {\n\t\t\tos << iter->source_file() << \"(\" << iter->source_line()\n\t\t\t<< \") : \" << iter->description() << \"\\n\";\n\t\t}\n\t}\n\treturn os;\n}\n*/\n\n\n\n\n}  // ns\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/error_holder.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_ERROR_HOLDER_H\n#define HZ_ERROR_HOLDER_H\n\n#include <vector>\n#include <memory>\n#include <string>\n#include <exception>  // for std::exception specialization\n#include <typeinfo>  // std::type_info\n#include <system_error>\n#include <utility>\n\n#include \"debug.h\"  // DBG_ASSERT\n#include \"process_signal.h\"  // hz::signal_to_string\n#include \"bad_cast_exception.h\"\n\n\n\nnamespace hz {\n\n\n\n/**\n\\file\nPredefined error types are: \"errno\", \"signal\" (child exited with signal).\n*/\n\n\n/// Error level (severity)\nenum class ErrorLevel {\n\tNone = 0,  ///< No error\n\tDump = 1 << 0,  ///< Dump\n\tInfo = 1 << 1,  ///< Informational (default)\n\tWarn = 1 << 2,  ///< Warning\n\tError = 1 << 3,  ///< Error\n\tFatal = 1 << 4  ///< Fatal\n};\n\n\n\ntemplate<typename CodeType>\nclass Error;\n\n\n\n/// Base class for Error<T>\nclass ErrorBase {\n\tpublic:\n\n\t\t// This is thrown in case of type conversion error\n\t\tclass type_mismatch : public hz::bad_cast_except {\n\t\t\tpublic:\n\t\t\t\ttype_mismatch(const std::type_info& src, const std::type_info& dest)\n\t\t\t\t\t: hz::bad_cast_except(src, dest, \"type_mismatch\",\n\t\t\t\t\t\t\"Error: type mismatch. Original type: \\\"%s\\\", requested type: \\\"%s\\\".\")\n\t\t\t\t{ }\n\t\t};\n\n\n\t\t/// Constructor\n\t\tErrorBase(std::string type, ErrorLevel level, std::string message)\n\t\t\t\t: type_(std::move(type)), level_(level), message_(std::move(message))\n\t\t{ }\n\n\t\t/// Constructor\n\t\tErrorBase(std::string type, ErrorLevel level)\n\t\t\t\t: type_(std::move(type)), level_(level)\n\t\t{ }\n\n\t\t/// Defaulted\n\t\tErrorBase(const ErrorBase& other) = default;\n\n\t\t/// Defaulted\n\t\tErrorBase(ErrorBase&& other) = default;\n\n\t\t/// Defaulted\n\t\tErrorBase& operator=(const ErrorBase&) = default;\n\n\t\t/// Defaulted\n\t\tErrorBase& operator=(ErrorBase&&) = default;\n\n\n\t\t/// Virtual destructor\n\t\tvirtual ~ErrorBase() = default;\n\n\n\t\t/// Clone this object\n\t\t[[nodiscard]] virtual ErrorBase* clone() = 0;  // needed for copying by base pointers\n\n\n\t\t/// Get std::type_info for the error code type.\n\t\t[[nodiscard]] virtual const std::type_info& get_code_type_info() const = 0;\n\n\n\t\t/// Get error code of type \\c CodeMemberType\n\t\ttemplate<class CodeMemberType>\n\t\t[[nodiscard]] CodeMemberType get_code() const  // this may throw on bad cast!\n\t\t{\n\t\t\tif (get_code_type_info() != typeid(CodeMemberType))\n\t\t\t\tthrow type_mismatch(get_code_type_info(), typeid(CodeMemberType));\n\t\t\treturn static_cast<const Error<CodeMemberType>*>(this)->code;\n\t\t}\n\n\t\t/// Get error code of type \\c CodeMemberType\n\t\ttemplate<class CodeMemberType>\n\t\t[[nodiscard]] bool get_code(CodeMemberType& put_it_here) const  // this doesn't throw\n\t\t{\n\t\t\tif (get_code_type_info() != typeid(CodeMemberType))\n\t\t\t\treturn false;\n\t\t\tput_it_here = static_cast<const Error<CodeMemberType>*>(this)->get_code_member();\n\t\t\treturn true;\n\t\t}\n\n\n\t\t/// Increase the level (severity) of the error\n\t\tErrorLevel level_inc()\n\t\t{\n\t\t\tif (level_ == ErrorLevel::Fatal)\n\t\t\t\treturn level_;\n\t\t\treturn (level_ = static_cast<ErrorLevel>(static_cast<int>(level_) << 1));\n\t\t}\n\n\t\t/// Decrease the level (severity) of the error\n\t\tErrorLevel level_dec()\n\t\t{\n\t\t\tif (level_ == ErrorLevel::None)\n\t\t\t\treturn level_;\n\t\t\treturn (level_ = static_cast<ErrorLevel>(static_cast<int>(level_) >> 1));\n\t\t}\n\n\t\t/// Get error level (severity)\n\t\t[[nodiscard]] ErrorLevel get_level() const\n\t\t{\n\t\t\treturn level_;\n\t\t}\n\n\n\t\t/// Get error type\n\t\t[[nodiscard]] std::string get_type() const\n\t\t{\n\t\t\treturn type_;\n\t\t}\n\n\t\t/// Get error message\n\t\t[[nodiscard]] std::string get_message() const\n\t\t{\n\t\t\treturn message_;\n\t\t}\n\n\n\tprotected:\n\n\t\t/// Set error type\n\t\tvoid set_type(std::string type)\n\t\t{\n\t\t\ttype_ = std::move(type);\n\t\t}\n\n\n\t\t/// Set error level\n\t\tvoid set_level(ErrorLevel level)\n\t\t{\n\t\t\tlevel_ = level;\n\t\t}\n\n\n\t\t/// Set error message\n\t\tvoid set_message(std::string message)\n\t\t{\n\t\t\tmessage_ = std::move(message);\n\t\t}\n\n\n\tprivate:\n\n\t\tstd::string type_;  ///< Error type\n\t\tErrorLevel level_ = ErrorLevel::None;  ///< Error severity\n\t\tstd::string message_;  ///< Error message\n\n};\n\n\n\n\n\n// Provides some common stuff for Error to ease template specializations.\ntemplate<typename CodeType>\nclass ErrorCodeHolder : public ErrorBase {\n\tprotected:\n\n\t\t/// Constructor\n\t\tErrorCodeHolder(const std::string& type, ErrorLevel level, const CodeType& code,\n\t\t\t\tconst std::string& msg)\n\t\t\t: ErrorBase(type, level, msg), code_(code)\n\t\t{ }\n\n\t\t/// Constructor\n\t\tErrorCodeHolder(const std::string& type, ErrorLevel level, const CodeType& code)\n\t\t\t: ErrorBase(type, level), code_(code)\n\t\t{ }\n\n\tpublic:\n\n\t\t// Reimplemented from ErrorBase\n\t\t[[nodiscard]] const std::type_info& get_code_type_info() const override\n\t\t{\n\t\t\treturn typeid(CodeType);\n\t\t}\n\n\t\t// Reimplemented from ErrorBase\n\t\t[[nodiscard]] const CodeType& get_code_member() const\n\t\t{\n\t\t\treturn code_;\n\t\t}\n\n\tprivate:\n\n\t\tCodeType code_ = CodeType();  ///< Error code. We have a class specialization for references too\n\n};\n\n\n\n// Specialization for void, helpful for custom messages\ntemplate<>\nclass ErrorCodeHolder<void> : public ErrorBase {\n\tprotected:\n\n\t\t/// Constructor\n\t\tErrorCodeHolder(const std::string& type, ErrorLevel level, const std::string& msg)\n\t\t\t: ErrorBase(type, level, msg)\n\t\t{ }\n\n\tpublic:\n\n\t\t// Reimplemented from ErrorBase\n\t\t[[nodiscard]] const std::type_info& get_code_type_info() const override\n\t\t{\n\t\t\treturn typeid(void);\n\t\t}\n\n};\n\n\n\n\n\n\n/// Error class. Stores an error code of type \\c CodeType.\n/// Instantiate this in user code.\ntemplate<typename CodeType>\nclass Error : public ErrorCodeHolder<CodeType> {\n\tpublic:\n\n\t\t/// Constructor\n\t\tError(const std::string& type, ErrorLevel level, const CodeType& code,\n\t\t\t\tconst std::string& msg)\n\t\t\t: ErrorCodeHolder<CodeType>(type, level, code, msg)\n\t\t{ }\n\n\t\t// Reimplemented from ErrorBase\n\t\tErrorBase* clone() override\n\t\t{\n\t\t\treturn new Error(ErrorCodeHolder<CodeType>::get_type(), ErrorCodeHolder<CodeType>::get_level(),\n\t\t\t\t\tErrorCodeHolder<CodeType>::get_code_member(), ErrorCodeHolder<CodeType>::get_message());\n\t\t}\n};\n\n\n\n\n/// Error class specialization for void (helpful for custom messages).\ntemplate<>\nclass Error<void> : public ErrorCodeHolder<void> {\n\tpublic:\n\n\t\tError(const std::string& type, ErrorLevel level, const std::string& msg)\n\t\t\t: ErrorCodeHolder<void>(type, level, msg)\n\t\t{ }\n\n\t\t// Reimplemented from ErrorBase\n\t\tErrorBase* clone() override\n\t\t{\n\t\t\treturn new Error(ErrorCodeHolder<void>::get_type(), ErrorCodeHolder<void>::get_level(),\n\t\t\t\t\tErrorCodeHolder<void>::get_message());\n\t\t}\n};\n\n\n\n\n/// Error class specialization for int (can be used for signals, errno).\n/// Message is automatically constructed if not provided.\ntemplate<>\nclass Error<int> : public ErrorCodeHolder<int> {\n\tpublic:\n\n\t\t/// Constructor\n\t\tError(const std::string& type, ErrorLevel level, int code, const std::string& msg)\n\t\t\t: ErrorCodeHolder<int>(type, level, code, msg)\n\t\t{ }\n\n\t\t/// Constructor\n\t\tError(const std::string& type, ErrorLevel level, int code)\n\t\t\t: ErrorCodeHolder<int>(type, level, code)\n\t\t{\n\t\t\tif (type == \"errno\") {\n\t\t\t\tthis->set_message(std::error_code(code, std::system_category()).message());\n\n\t\t\t} else if (type == \"signal\") {\n\t\t\t\t// hz::signal_string should be translated already\n\t\t\t\tthis->set_message(\"Child exited with signal: \" + hz::signal_to_string(code));\n\n\t\t\t} else {  // nothing else supported here. use constructor with a message.\n\t\t\t\tDBG_ASSERT(0);\n\t\t\t}\n\t\t}\n\n\t\t// Reimplemented from ErrorBase\n\t\tErrorBase* clone() override\n\t\t{\n\t\t\treturn new Error(ErrorCodeHolder<int>::get_type(), ErrorCodeHolder<int>::get_level(),\n\t\t\t\t\tErrorCodeHolder<int>::get_code_member(), ErrorCodeHolder<int>::get_message());\n\t\t}\n};\n\n\n\n\n/*\nnamespace {\n\n\tError<int> e1(\"type1\", ErrorLevel::info, 6);\n\tError<std::string> e2(\"type2\", ErrorLevel::fatal, \"asdasd\", \"aaa\");\n\n\tstd::bad_alloc ex3;\n// \tError<std::exception&> e3(\"type3\", ErrorBase::info, static_cast<std::exception&>(ex3));\n\tError<std::exception&> e3(\"type3\", ErrorLevel::info, ex3);\n\n\n\tGlib::IOChannelError ex4(Glib::IOChannelError::FILE_TOO_BIG, \"message4\");\n\tError<Glib::Error&> e4(\"type4\", ErrorLevel::info, ex4);\n\n}\n*/\n\n\n\n/// A class wishing to implement Error holding storage should inherit this.\nclass ErrorHolder {\n\tpublic:\n\n\t\tusing error_list_t = std::vector<std::shared_ptr<ErrorBase>>;  ///< A list of ErrorBase* pointers\n\n\t\t/// Defaulted\n\t\tErrorHolder() = default;\n\n\t\t/// Deleted\n\t\tErrorHolder(const ErrorHolder& other) = delete;\n\n\t\t/// Deleted\n\t\tErrorHolder(ErrorHolder&& other) = delete;\n\n\t\t/// Deleted\n\t\tErrorHolder& operator=(const ErrorHolder&) = delete;\n\n\t\t/// Deleted\n\t\tErrorHolder& operator=(ErrorHolder&&) = delete;\n\n\t\t/// Virtual destructor\n\t\tvirtual ~ErrorHolder() = default;\n\n\n\t\t/// Add an error to the error list\n\t\ttemplate<class E>\n\t\tvoid push_error(const E& e)\n\t\t{\n\t\t\tauto cloned = std::make_shared<E>(e);\n\t\t\terrors_.push_back(cloned);\n\t\t\terror_warn(cloned.get());\n\t\t}\n\n\n\t\t/// Check if there are any errors in this class.\n\t\t[[nodiscard]] bool has_errors() const\n\t\t{\n\t\t\treturn !errors_.empty();\n\t\t}\n\n\n\t\t/// Get a list of errors.\n\t\t[[nodiscard]] error_list_t get_errors() const\n\t\t{\n\t\t\treturn errors_;\n\t\t}\n\n\n\t\t/// Clear the error list\n\t\tvoid clear_errors()\n\t\t{\n\t\t\terrors_.clear();\n\t\t}\n\n\n\t\t/// This function is called every time push_error() is invoked.\n\t\t/// The default implementation prints the message using libdebug.\n\t\t/// Override in children if needed.\n\t\tvirtual void error_warn(ErrorBase* e)\n\t\t{\n\t\t\tconst std::string msg = e->get_type() + \": \" + e->get_message() + \"\\n\";\n\t\t\tconst ErrorLevel level = e->get_level();\n\n\t\t\t// use debug macros, not functions (to allow complete removal through preprocessor).\n\t\t\tswitch (level) {\n\t\t\t\tcase ErrorLevel::None: break;\n\t\t\t\tcase ErrorLevel::Dump: debug_out_dump(\"hz\", msg); break;\n\t\t\t\tcase ErrorLevel::Info: debug_out_info(\"hz\", msg); break;\n\t\t\t\tcase ErrorLevel::Warn: debug_out_warn(\"hz\", \"Warning: \" << msg); break;\n\t\t\t\tcase ErrorLevel::Error: debug_out_error(\"hz\", \"Error: \" << msg); break;\n\t\t\t\tcase ErrorLevel::Fatal: debug_out_fatal(\"hz\", \"Fatal: \" << msg); break;\n\t\t\t}\n\t\t}\n\n\n\tprivate:\n\n\t\terror_list_t errors_;  ///< Error list. The newest errors at the end.\n\n};\n\n\n\n\n}  // ns\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/format_unit.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_FORMAT_UNIT_H\n#define HZ_FORMAT_UNIT_H\n\n#include <string>\n#include <cstddef>  // std::size_t\n#include <ctime>  // for time.h, std::time, std::localtime, ...\n#include <time.h>  // localtime_r, localtime_s\n#include <iomanip>  // std::put_time\n#include <cstdint>\n#include <sstream>\n#include <chrono>\n#include <vector>\n\n#if defined __MINGW32__\n\t#include <_mingw.h>  // MINGW_HAS_SECURE_API\n#endif\n\n#ifdef ENABLE_GLIB\n\t#include <glibmm.h>\n\t#include <glibmm/i18n.h>\n#else\n\t#define C_(Context,String) (String)\n#endif\n\n#include \"string_num.h\"  // hz::number_to_string_locale\n#include \"string_algo.h\"\n\n\n/// \\def HAVE_REENTRANT_LOCALTIME\n/// Defined to 0 or 1. If 1, localtime() is reentrant.\n#ifndef HAVE_REENTRANT_LOCALTIME\n// win32 and solaris localtime() is reentrant\n\t#if defined _WIN32 || defined sun || defined __sun\n\t\t#define HAVE_REENTRANT_LOCALTIME 1\n\t#else\n\t\t#define HAVE_REENTRANT_LOCALTIME 0\n\t#endif\n#endif\n\n\n\n\nnamespace hz {\n\n\n\n\n/// Format byte or bit size in human-readable way, e.g. KB, mb, etc.\n/// Note that kilobit always means 1000 bits, there's no confusion with that (as opposed to kilobyte).\n/// This function honors the SI rules, e.g. GiB for binary, GB for decimal (as defined by SI).\ninline std::string format_size(uint64_t size, bool use_decimal = false, bool size_is_bits = false)\n{\n\tconst uint64_t multiplier = (use_decimal ? 1000 : 1024);\n\tconst uint64_t kb_size = multiplier;  // kilo\n\tconst uint64_t mb_size = kb_size * multiplier;  // mega\n\tconst uint64_t gb_size = mb_size * multiplier;  // giga\n\tconst uint64_t tb_size = gb_size * multiplier;  // tera\n\tconst uint64_t pb_size = tb_size * multiplier;  // peta\n\tconst uint64_t eb_size = pb_size * multiplier;  // exa\n\n\t// these are beyond the size of uint64_t.\n// \tconst uint64_t zb_size = eb_size * multiplier;  // zetta\n// \tconst uint64_t yb_size = zb_size * multiplier;  // yotta\n\n\t// Note: This won't work with runtime language change.\n\tstatic const std::vector<std::string> names = {\n\t\tC_(\"file_size\", \"%s B\"),  // bytes decimal\n\t\tC_(\"file_size\", \"%s B\"),  // bytes binary\n\t\tC_(\"file_size\", \"%s bit\"),  // bits decimal\n\t\tC_(\"file_size\", \"%s bit\"),  // bits binary. note: 2 bit, not 2 bits.\n\n\t\tC_(\"file_size\", \"%s KB\"),\n\t\tC_(\"file_size\", \"%s KiB\"),\n\t\tC_(\"file_size\", \"%s Kbit\"),\n\t\tC_(\"file_size\", \"%s Kibit\"),\n\n\t\tC_(\"file_size\", \"%s MB\"),\n\t\tC_(\"file_size\", \"%s MiB\"),\n\t\tC_(\"file_size\", \"%s Mbit\"),\n\t\tC_(\"file_size\", \"%s Mibit\"),\n\n\t\tC_(\"file_size\", \"%s GB\"),\n\t\tC_(\"file_size\", \"%s GiB\"),\n\t\tC_(\"file_size\", \"%s Gbit\"),\n\t\tC_(\"file_size\", \"%s Gibit\"),\n\n\t\tC_(\"file_size\", \"%s TB\"),\n\t\tC_(\"file_size\", \"%s TiB\"),\n\t\tC_(\"file_size\", \"%s Tbit\"),\n\t\tC_(\"file_size\", \"%s Tibit\"),\n\n\t\tC_(\"file_size\", \"%s PB\"),\n\t\tC_(\"file_size\", \"%s PiB\"),\n\t\tC_(\"file_size\", \"%s Pbit\"),\n\t\tC_(\"file_size\", \"%s Pibit\"),\n\n\t\tC_(\"file_size\", \"%s EB\"),\n\t\tC_(\"file_size\", \"%s EiB\"),\n\t\tC_(\"file_size\", \"%s Ebit\"),\n\t\tC_(\"file_size\", \"%s Eibit\")\n\t};\n\n\tconst std::size_t addn = static_cast<std::size_t>(!use_decimal) + (static_cast<std::size_t>(size_is_bits) * 2);\n\n\tif (size >= eb_size) {  // exa\n\t\treturn hz::string_replace_copy(names[(6 * 4) + addn], \"%s\",\n\t\t\t\thz::number_to_string_locale(static_cast<long double>(size) / static_cast<long double>(eb_size), 2, true), 1);\n\t}\n\n\tif (size >= pb_size) {  // peta\n\t\treturn hz::string_replace_copy(names[(5 * 4) + addn], \"%s\",\n\t\t\t\thz::number_to_string_locale(static_cast<long double>(size) / static_cast<long double>(pb_size), 2, true), 1);\n\t}\n\n\tif (size >= tb_size) {  // tera\n\t\treturn hz::string_replace_copy(names[(4 * 4) + addn], \"%s\",\n\t\t\t\thz::number_to_string_locale(static_cast<long double>(size) / static_cast<long double>(tb_size), 2, true), 1);\n\t}\n\n\tif (size >= gb_size) {  // giga\n\t\treturn hz::string_replace_copy(names[(3 * 4) + addn], \"%s\",\n\t\t\t\thz::number_to_string_locale(static_cast<long double>(size) / static_cast<long double>(gb_size), 2, true), 1);\n\t}\n\n\tif (size >= mb_size) {  // mega\n\t\treturn hz::string_replace_copy(names[(2 * 4) + addn], \"%s\",\n\t\t\t\thz::number_to_string_locale(static_cast<long double>(size) / static_cast<long double>(mb_size), 2, true), 1);\n\t}\n\n\tif (size >= kb_size) {  // kilo\n\t\treturn hz::string_replace_copy(names[(1 * 4) + addn], \"%s\",\n\t\t\t\thz::number_to_string_locale(static_cast<long double>(size) / static_cast<long double>(kb_size), 2, true), 1);\n\t}\n\n\treturn hz::string_replace_copy(names[(0 * 4) + addn], \"%s\", std::to_string(size));\n}\n\n\n\n/// Format time length (e.g. 330 seconds) in a human-readable manner\n/// (e.g. \"5 min 30 sec\").\ninline std::string format_time_length(std::chrono::seconds secs)\n{\n\tusing namespace std::literals;\n\tusing day_unit = std::chrono::duration\n\t\t\t<int, std::ratio_multiply<std::ratio<24>, std::chrono::hours::period>>;\n\n\t// don't use uints here - they bring bugs.\n\tconst int64_t min_size = 60;\n\tconst int64_t hour_size = min_size * 60;\n\tconst int64_t day_size = hour_size * 24;\n\n\tif (secs >= 100h) {\n\t\tday_unit days = std::chrono::round<day_unit>(secs);\n\t\tconst std::chrono::seconds sec_diff = secs - days;  // difference between days and actual time (may be positive or negative)\n\n\t\tif (days.count() < 10) {  // if less than 10 days, display hours too\n\n\t\t\t// if there's more than half an hour missing from complete day, add a day.\n\t\t\t// then add half an hour and convert to hours.\n\t\t\tconst int64_t hours = ((sec_diff.count() < (-hour_size / 2) ? sec_diff.count() + day_size : sec_diff.count()) + hour_size / 2) / hour_size;\n\t\t    if (hours > 0 && sec_diff.count() < (-hour_size / 2))\n\t\t       days--;\n\n\t\t\treturn hz::string_replace_array_copy(C_(\"time\", \"{days} d {hours} h\"),\n\t\t\t\t\tstd::vector<std::string>{\"{days}\", \"{hours}\"},\n\t\t\t\t\tstd::vector<std::string>{std::to_string(days.count()), std::to_string(hours)});\n\n\t\t}\n\t\t// display days only\n\t\treturn hz::string_replace_copy(C_(\"time\", \"{days} d\"),\n\t\t\t\"{days}\", std::to_string(days.count()));\n\t}\n\n\tif (secs >= 100min) {\n\t\tauto hours = std::chrono::round<std::chrono::hours>(secs);\n\t\tconst std::chrono::seconds sec_diff = secs - hours;\n\n\t\tif (hours.count() < 10) {  // if less than 10 hours, display minutes too\n\t\t\tconst int64_t minutes = ((sec_diff.count() < (-min_size / 2) ? sec_diff.count() + hour_size : sec_diff.count()) + min_size / 2) / min_size;\n\t\t\tif (minutes > 0 && sec_diff.count() < (-min_size / 2))\n\t\t\t\thours--;\n\n\t\t\treturn hz::string_replace_array_copy(C_(\"time\", \"{hours} h {minutes} min\"),\n\t\t\t\t\tstd::vector<std::string>{\"{hours}\", \"{minutes}\"},\n\t\t\t\t\tstd::vector<std::string>{std::to_string(hours.count()), std::to_string(minutes)});\n\n\t\t}\n\t\t// display hours only\n\t\treturn std::to_string(hours.count()) + \" \" + \"h\";\n\t}\n\n\tif (secs >= 100s) {\n\t\tauto minutes = std::chrono::round<std::chrono::minutes>(secs);\n\t\treturn hz::string_replace_copy(C_(\"time\", \"{minutes} min\"),\n\t\t\t\t\"{minutes}\", std::to_string(minutes.count()));\n\t}\n\n\treturn hz::string_replace_copy(C_(\"time\", \"{seconds} sec\"),\n\t\t\t\"{seconds}\", std::to_string(secs.count()));\n}\n\n\n\n\n\n\n\n\n/// Format a date specified by \\c ltmp (pointer to tm structure).\n/// See std::put_time() documentation for format details. To print ISO datetime\n/// use \"%Y-%m-%d %H:%M:%S\" (sometimes 'T' is used instead of space).\ninline std::string format_date(const std::string& format, const struct std::tm* ltmp, bool use_classic_locale)\n{\n\tif (!ltmp || format.empty())\n\t\treturn {};\n\n\tstd::ostringstream ss;\n\tif (!use_classic_locale) {\n\t\tss.imbue(std::locale::classic());\n\t}\n\tss << std::put_time(ltmp, format.c_str());\n\treturn ss.str();\n}\n\n\n\n/// Format a date specified by \\c timet (seconds since Epoch).\n/// See strftime() documentation for format details.\ninline std::string format_date(const std::string& format, std::time_t timet, bool use_classic_locale)\n{\n#if defined MINGW_HAS_SECURE_API || defined _MSC_VER\n\tstruct std::tm ltm;\n\tif (localtime_s(&ltm, &timet) != 0)  // shut up msvc (it thinks std::localtime() is unsafe)\n\t\treturn std::string();\n\tconst struct std::tm* ltmp = &ltm;\n\n#elif defined HAVE_REENTRANT_LOCALTIME && HAVE_REENTRANT_LOCALTIME\n\tconst struct std::tm* ltmp = std::localtime(&timet);\n\n#else\n\tstruct std::tm ltm = {};\n\tif (!localtime_r(&timet, &ltm))  // use reentrant localtime_r (posix/bsd and related)\n\t\treturn {};\n\tconst struct std::tm* ltmp = &ltm;\n#endif\n\n\treturn format_date(format, ltmp, use_classic_locale);\n}\n\n\n\n/// Format current date.\n/// See strftime() documentation for format details.\ninline std::string format_date(const std::string& format, bool use_classic_locale)\n{\n\tconst std::time_t timet = std::time(nullptr);\n\tif (timet == static_cast<std::time_t>(-1))\n\t\treturn {};\n\n\treturn format_date(format, timet, use_classic_locale);\n}\n\n\n\n\n\n\n} // ns\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/fs.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_FS_H\n#define HZ_FS_H\n\n#include <string>\n#include <string_view>\n#include <cerrno>\n#include <system_error>\n#include <cstdio>  // std::FILE, std::fopen() and friends\n#include <stdio.h>  // off_t, fileno(), _fileno(), _wfopen()\n#include <limits>\n#include <array>\n#include <vector>\n\n#ifdef _WIN32\n\t#include <io.h>  // _waccess*()\n#else\n\t#include <cstddef>  // std::size_t\n\t#include <unistd.h>  // access()\n#endif\n\n#if defined __MINGW32__\n\t#include <_mingw.h>  // MINGW_HAS_SECURE_API\n#endif\n\n#include \"env_tools.h\"  // hz::env_get_value\n\n#ifdef _WIN32\n\t#include \"win32_tools.h\"  // win32_* stuff\n#endif\n\n#include \"fs_ns.h\"\n#include \"whereami.h\"\n#include \"string_algo.h\"\n\n\n/// \\def HAVE_POSIX_OFF_T_FUNCS\n/// Defined to 0 or 1. If 1, compiler supports fseeko/ftello. See fs_file.h for details.\n#ifndef HAVE_POSIX_OFF_T_FUNCS\n\t// It's quite hard to detect, so we just enable for any non-win32.\n\t#ifndef _WIN32\n\t\t#define HAVE_POSIX_OFF_T_FUNCS 1\n\t#endif\n#endif\n\n/// \\def HAVE_WIN_LFS_FUNCS\n/// Defined to 0 or 1. If 1, compiler supports LFS (large file support) functions on win32.\n/// It seems that LFS functions are always defined in mingw-w64 and msvc >= 2005.\n#ifndef HAVE_WIN_LFS_FUNCS\n\t#ifdef _WIN32\n\t\t#define HAVE_WIN_LFS_FUNCS 1\n\t#endif\n#endif\n\n\n/**\n\\file\nFilesystem utilities\n*/\n\n\nnamespace hz {\n\n\n\n#ifdef _WIN32\n\t// Unlike std::filesystem::path::preferred_separator, this is always char.\n\tconstexpr char fs_preferred_separator = '\\\\';\n#else\n\tconstexpr char fs_preferred_separator = '/';\n#endif\n\n\n\n/// \\typedef platform_file_size_t\n/// Offset & size type. may be uint32_t or uint64_t, depending on system and compilation flags.\n/// Note: It is usually discouraged to use this type in headers, because the library may be\n/// compiled with one size and the application with another. However, this problem is rather\n/// limited if using header-only approach, like we do.\n\n#if defined HAVE_POSIX_OFF_T_FUNCS && HAVE_POSIX_OFF_T_FUNCS\n\tusing platform_file_size_t = off_t;  // off_t is in stdio.h, available in all self-respecting unix systems.\n#elif defined HAVE_WIN_LFS_FUNCS && HAVE_WIN_LFS_FUNCS\n\tusing platform_file_size_t = long long;  // ms stuff, _fseeki64() and friends use it.\n#else\n\tusing platform_file_size_t = long;  // fseek() and friends use long. win32 doesn't have off_t.\n#endif\n\n\n\n/// Same as std::filesystem::path::u8string(), but returns std::string with UTF-8 data.\n/// This is needed because since C++20, std::filesystem::path::u8string() returns std::u8string\n/// instead of std::string, which is what is often desired for compatibility.\ninline std::string fs_path_to_string(const fs::path& p)\n{\n\treturn u8string_to_string(p.u8string());\n}\n\n\n\n/// Same as std::filesystem::u8path(std::string), which is deprecated since C++20.\ninline fs::path fs_path_from_string(std::string_view u8str)\n{\n\treturn {u8string_from_string(u8str)};\n}\n\n\n\n\n/// Platform-dependent fopen(). Sets errno on error.\ninline std::FILE* fs_platform_fopen(const fs::path& file, const char* open_mode)\n{\n\t// Don't validate parameters, they will be validated by the called functions.\n#if defined MINGW_HAS_SECURE_API || defined _MSC_VER\n\tstd::FILE* f = nullptr;\n\terrno = _wfopen_s(&f, file.c_str(), hz::win32_utf8_to_utf16(open_mode).c_str());\n\treturn f;\n#elif defined _WIN32\n\treturn _wfopen(file.c_str(), hz::win32_utf8_to_utf16(open_mode).c_str());\n#else\n\treturn std::fopen(file.c_str(), open_mode);\n#endif\n}\n\n\n\n/// Platform-dependent fseek(). Sets errno on error.\ninline int fs_platform_fseek(FILE* stream, std::uintmax_t offset, int whence)\n{\n\t// Don't validate parameters, they will be validated by the called functions.\n#if defined HAVE_POSIX_OFF_T_FUNCS && HAVE_POSIX_OFF_T_FUNCS\n\treturn fseeko(stream, (off_t)offset, whence);  // POSIX\n#elif defined HAVE_WIN_LFS_FUNCS && HAVE_WIN_LFS_FUNCS\n\treturn _fseeki64(stream, (long long)offset, whence);\n#else\n\treturn std::fseek(stream, (long)offset, whence);\n#endif\n}\n\n\n\n/// Platform-dependent ftell(). Sets errno on error.\ninline std::uintmax_t fs_platform_ftell(FILE* stream)\n{\n\tplatform_file_size_t pos = 0;\n#if defined HAVE_POSIX_OFF_T_FUNCS && HAVE_POSIX_OFF_T_FUNCS\n\tpos = ftello(stream);  // POSIX\n#elif defined HAVE_WIN_LFS_FUNCS && HAVE_WIN_LFS_FUNCS\n\tpos = _ftelli64(stream);\n#else\n\tpos = std::ftell(stream);\n#endif\n\tif (pos == static_cast<platform_file_size_t>(-1)) {\n\t\treturn static_cast<std::uintmax_t>(-1);\n\t}\n\treturn static_cast<std::uintmax_t>(pos);\n}\n\n\n\n/// Same as other versions of get_contents(), but puts data into an already\n/// allocated buffer of size \\c buf_size.\n/// If the size is insufficient, false is returned and buffer is left untouched.\n/// If any other error occurs, the buffer is left in unspecified state.\n/// Internal usage only: If buf_size is -1, the buffer will be automatically allocated.\n/// TODO: Support files which are not seekable and don't have a size attribute (e.g. /proc/*).\n/// \\return Empty (zero) error code on success.\ninline std::error_code fs_file_get_contents_noalloc(const fs::path& file, unsigned char*& put_data_here, std::uintmax_t buf_size,\n\t\tstd::uintmax_t& put_size_here, std::uintmax_t max_size)\n{\n\tif (file.empty()) {\n\t\treturn std::make_error_code(std::errc::invalid_argument);\n\t}\n\n\tstd::FILE* f = fs_platform_fopen(file, \"rb\");\n\tif (!f) {\n\t\treturn {errno, std::system_category()};\n\t}\n\n\tstd::error_code ec;\n\n\tdo {  // goto emulation\n\n\t\tif (fs_platform_fseek(f, 0, SEEK_END) != 0) {\n\t\t\tec = std::error_code(errno, std::system_category());\n\t\t\tbreak;  // goto cleanup\n\t\t}\n\n\t\t// We can't use fs::file_size() here since it will introduce a race condition.\n\t\tconst std::uintmax_t size = fs_platform_ftell(f);\n\n\t\tif (size == static_cast<std::uintmax_t>(-1)) {  // size may be unsigned, it's the way it works\n\t\t\tec = std::error_code(errno, std::system_category());\n\t\t\tbreak;  // goto cleanup\n\t\t}\n\n\t\tif (size > max_size) {\n\t\t\tec = std::make_error_code(std::errc::file_too_large);\n\t\t\tbreak;  // goto cleanup\n\t\t}\n\n\t\t// automatically allocate the buffer if buf_size is -1.\n\t\tconst bool auto_alloc = (buf_size == static_cast<std::uintmax_t>(-1));\n\t\tif (!auto_alloc && buf_size < size) {\n\t\t\tec = std::make_error_code(std::errc::no_buffer_space);\n\t\t\tbreak;  // goto cleanup\n\t\t}\n\n\t\tstd::rewind(f);  // returns void\n\n\t\tunsigned char* buf = put_data_here;\n\t\tif (auto_alloc)\n\t\t\tbuf = new unsigned char[static_cast<std::size_t>(size)];  // this may throw!\n\n\t\t// We don't need large file support here because we read into memory,\n\t\t// which is limited at 31 bits anyway (on 32-bit systems).\n\t\t// I really hope this is not a byte-by-byte operation\n\t\tconst std::size_t read_bytes = std::fread(buf, 1, static_cast<std::size_t>(size), f);\n\t\tif (size != static_cast<std::uintmax_t>(read_bytes)) {\n\t\t\t// Unexpected number of bytes read. Not sure if this is reflected in errno or not.\n\t\t\tec = std::error_code(errno, std::system_category());\n\t\t\tif (auto_alloc)\n\t\t\t\tdelete[] buf;\n\t\t\tbreak;  // goto cleanup\n\t\t}\n\n\t\t// All OK\n\t\tput_size_here = size;\n\t\tif (auto_alloc)\n\t\t\tput_data_here = buf;\n\n\t} while (false);\n\n\t// cleanup:\n\tif (std::fclose(f) != 0) {\n\t\tif (!ec) {  // don't overwrite the previous error\n\t\t\tec = std::error_code(errno, std::system_category());\n\t\t}\n\t}\n\n\treturn ec;\n}\n\n\n\n/// Get file contents. \\c put_data_here will be allocated to whatever\n/// size is needed to contain all the data. The size is written to \\c put_size_here.\n/// You must call \"delete[] put_data_here\" afterwards.\n/// If the file is larger than \\c max_size (100M by default), the function refuses to load it.\n/// Note: No additional trailing 0 is written to data!\n/// \\return Empty (zero) error code on success.\ninline std::error_code fs_file_get_contents(const fs::path& file, unsigned char*& put_data_here,\n\t\tstd::uintmax_t& put_size_here, std::uintmax_t max_size)\n{\n\treturn fs_file_get_contents_noalloc(file, put_data_here, static_cast<std::uintmax_t>(-1), put_size_here, max_size);\n}\n\n\n\n/// Same as other versions of get_contents(), but for std::string\n/// (no terminating 0 is needed inside the file, the string is 0-terminated anyway).\n/// \\return Empty (zero) error code on success.\ninline std::error_code fs_file_get_contents(const fs::path& file, std::string& put_data_here, std::uintmax_t max_size)\n{\n\tstd::uintmax_t size = 0;\n\tunsigned char* buf = nullptr;\n\tconst std::error_code ec = fs_file_get_contents(file, buf, size, max_size);\n\tif (ec)\n\t\treturn ec;\n\n\t// Note: No need for appending 0, it's all automatic in string.\n\tput_data_here.reserve(static_cast<std::string::size_type>(size));  // string takes size without trailing 0.\n\tput_data_here.append(reinterpret_cast<char*>(buf), static_cast<std::string::size_type>(size));\n\n\tdelete[] buf;\n\treturn ec;\n}\n\n\n\n/// Procfs files don't support SEEK_END or ftello(). They can't\n/// be read using fs_file_get_contents, so use this function instead.\ninline std::error_code fs_file_get_contents_unseekable(const hz::fs::path& file, std::string& put_data_here)\n{\n\tstd::FILE* fp = fs_platform_fopen(file, \"rb\");\n\tif (!fp) {\n\t\treturn {errno, std::system_category()};\n\t}\n\tput_data_here.clear();\n\n\tstd::array<char, 1024> line = {};\n\twhile (std::fgets(line.data(), static_cast<int>(line.size()), fp) != nullptr) {\n\t\tif (line[0] != '\\0')\n\t\t\tput_data_here += line.data();  // line contains the terminating newline as well\n\t}\n\n\tstd::fclose(fp);\n\n\treturn {};\n}\n\n\n\n/// Write data to file, creating or truncating it beforehand.\n/// \\c data may or may not be 0-terminated (it's irrelevant).\n/// \\return Empty (zero) error code on success.\ninline std::error_code fs_file_put_contents(const fs::path& file, const unsigned char* data, std::uintmax_t data_size)\n{\n\tif (file.empty()) {\n\t\treturn std::make_error_code(std::errc::invalid_argument);\n\t}\n\n\tstd::FILE* f = fs_platform_fopen(file, \"wb\");\n\tif (!f) {\n\t\treturn {errno, std::system_category()};\n\t}\n\n\t// We write in chunks to support large files.\n\tconst std::size_t chunk_size = 32*1024;  // 32K block devices will be happy.\n\tstd::uintmax_t left_to_write = data_size;\n\n\tbool write_error = false;\n\twhile (left_to_write >= static_cast<std::uintmax_t>(chunk_size)) {\n\t\t// better loop than specify all in one, this way we can support _really_ large files.\n\t\tif (std::fwrite(data + data_size - left_to_write, chunk_size, 1, f) != 1) {\n\t\t\twrite_error = true;\n\t\t\tbreak;\n\t\t}\n\t\tleft_to_write -= static_cast<std::uintmax_t>(chunk_size);\n\t}\n\n\t// write the remainder\n\tif (!write_error && left_to_write > 0 && std::fwrite(data + data_size - left_to_write, static_cast<std::size_t>(left_to_write), 1, f) != 1)\n\t\twrite_error = true;\n\n\tif (write_error) {\n\t\t// Number of written bytes doesn't match the data size. Not sure if this is reflected in errno or not.\n\t\tauto ec = std::error_code(errno, std::system_category());\n\t\tstd::fclose(f);  // don't check anything, it's too late\n\t\treturn ec;\n\t}\n\n\tif (std::fclose(f) != 0)\n\t\treturn {errno, std::system_category()};\n\n\treturn {};\n}\n\n\n\n/// Same as the other version of put_contents(), but writes data from std::string.\n/// No terminating 0 is written to the file.\n/// \\return Empty (zero) error code on success.\ninline std::error_code fs_file_put_contents(const fs::path& file, const std::string_view& data)\n{\n\treturn fs_file_put_contents(file, reinterpret_cast<const unsigned char*>(data.data()), static_cast<std::uintmax_t>(data.size()));\n}\n\n\n\n\n\n/// Get the current user's home directory.\n/// This function always returns something, but\n/// note that the directory may not actually exist at all.\ninline fs::path fs_get_home_dir()\n{\n\t// Do NOT use g_get_home_dir, it doesn't work consistently in win32\n\t// between glib versions.\n\n#ifdef _WIN32\n\t// For windows we usually get \"C:\\documents and settings\\username\".\n\t// Try $USERPROFILE, then CSIDL_PROFILE, then Windows directory\n\t// (glib uses it, not sure why though).\n\n\tstd::string dir;\n\thz::env_get_value(\"USERPROFILE\", dir);  // in utf-8\n\n\tif (dir.empty()) {\n\t\tdir = win32_get_special_folder(CSIDL_PROFILE);\n\t}\n\tif (dir.empty()) {\n\t\tdir = win32_get_windows_directory();  // always returns something.\n\t}\n\n\treturn fs_path_from_string(dir);\n\n#else  // linux, etc.\n\t// We use $HOME to allow the user to override it.\n\t// Other solutions involve getpwuid_r() to read from passwd.\n\tstd::string dir;\n\tif (!hz::env_get_value(\"HOME\", dir)) {  // works well enough\n\t\t// HOME may be empty in some situations (limited shells\n\t\t// and rescue logins).\n\t\t// We could use /tmp/<username>, but obtaining the username\n\t\t// is too complicated and unportable.\n\n\t\tstd::error_code ec;\n\t\treturn fs::temp_directory_path(ec);\n\t}\n\n\treturn {dir};  // native encoding\n#endif\n}\n\n\n\n/// Get the current user's configuration file directory (in native fs encoding\n/// for UNIX, utf-8 for windows). E.g. \"$HOME/.config\" in UNIX.\ninline fs::path fs_get_user_config_dir()\n{\n\tfs::path path;\n\n#ifdef _WIN32\n\t// that's \"C:\\documents and settings\\username\\application data\".\n\tpath = fs_path_from_string(win32_get_special_folder(CSIDL_APPDATA));\n\tif (path.empty()) {\n\t\tpath = fs_get_home_dir();  // fallback, always non-empty.\n\t}\n#else\n\tstd::string dir;\n\tif (hz::env_get_value(\"XDG_CONFIG_HOME\", dir)) {\n\t\tpath = dir;  // native encoding.\n\t} else {\n\t\t// default to $HOME/.config\n\t\tpath = fs_get_home_dir() / \".config\";\n\t}\n#endif\n\treturn path;\n}\n\n\n\n/// Get absolute path of the current executable\ninline fs::path fs_get_application_dir()\n{\n\tauto path_len = static_cast<std::size_t>(wai_getExecutablePath(nullptr, 0, nullptr));\n\tif (path_len == 0) {\n\t\treturn {};\n\t}\n\tstd::vector<char> vpath(path_len + 1);\n\n\tint dirname_length = 0;\n\n\t// In Windows the path is in utf-8.\n\twai_getExecutablePath(vpath.data(), int(path_len), &dirname_length);\n\n\tfs::path app_dir = {vpath.begin(), vpath.begin() + dirname_length};\n\n\t// On Windows, the binary may be in the \"bin\" subdirectory, so remove that.\n#ifdef _WIN32\n\tif (app_dir.filename() == \"bin\") {\n\t\tapp_dir = app_dir.parent_path();\n\t}\n#endif\n\n\treturn app_dir;\n}\n\n\n\n/// Check if the existing file can be fopen'ed with \"rb\", or the directory has read perms.\n/// Note: This function should be use only as an utility function (e.g. for GUI notification);\n/// other uses are not logically concurrent-safe (and therefore, insecure).\n/// ec is set to error code in case of failure.\ninline bool fs_path_is_readable(const fs::path& path, std::error_code& ec)\n{\n\tif (path.empty()) {\n\t\tec = std::make_error_code(std::errc::invalid_argument);\n\t\treturn false;\n\t}\n\n#if defined MINGW_HAS_SECURE_API || defined _MSC_VER\n\tif (_waccess_s(path.c_str(), 04))  // msvc uses integers instead (R_OK == 04 anyway).\n#elif defined _WIN32\n\tif (_waccess(path.c_str(), 04) == -1)\n#else\n\tif (access(path.c_str(), R_OK) == -1)  // from unistd.h\n#endif\n\t{\n\t\tec = std::error_code(errno, std::system_category());\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n\n\n/// Check if the existing or soon to be created file is writable, or if files can be created in this dir.\n/// Note: The same security considerations apply to this function as to is_readable().\ninline bool fs_path_is_writable(const fs::path& path, std::error_code& ec)\n{\n\tif (path.empty()) {\n\t\tec = std::make_error_code(std::errc::invalid_argument);\n\t\treturn false;\n\t}\n\n\tstd::error_code ignored_ec;  // ignore this error\n\tconst bool is_directory = fs::is_directory(path, ignored_ec);\n\tbool path_exists = fs::exists(path, ec);\n\tif (ec) {\n\t\treturn false;\n\t}\n\n\tconst fs::path dirname = (is_directory ? path : path.parent_path());\n\n#ifdef _WIN32  // win32 doesn't get access() (it just doesn't work with writing)\n\n\t// If it doesn't exist, try to create it.\n\t// If it exists and is a file, try to open it for writing.\n\t// If it exists and is a directory, try to create a test file in it.\n\t// Note: This method is possibly non-suitable for symlink-capable filesystems.\n\n\tfs::path path_to_check = path;\n\tif (path_exists && is_directory) {\n\t\tpath_to_check /= \"__test.txt\";\n\t\tpath_exists = fs::exists(path_to_check, ignored_ec);\n\t}\n\n\t// path_to_check either doesn't exist, or it's a file. try to open it.\n\tstd::FILE* f = fs_platform_fopen(path_to_check, \"ab\");  // this creates a 0 size file if it doesn't exist!\n\tif (!f) {\n\t\tec = std::error_code(errno, std::system_category());\n\t\treturn false;\n\t}\n\n\tif (std::fclose(f) != 0) {\n\t\tec = std::error_code(errno, std::system_category());\n\t\treturn false;\n\t}\n\n\t// remove the created file\n\tif (path_exists && _wunlink(path_to_check.c_str()) == -1) {\n\t\tec = std::error_code(errno, std::system_category());\n\t\treturn false;\n\t}\n\n\t// All OK\n\n#else\n\n\tif (path_exists && is_directory) {\n\t\tif (access(dirname.c_str(), W_OK) == -1) {\n\t\t\tec = std::error_code(errno, std::system_category());\n\t\t\treturn false;\n\t\t}\n\n\t} else {  // no such path or it's a file\n\t\tif (path_exists) {  // checking an existing file\n\t\t\tif (access(path.c_str(), W_OK) == -1) {\n\t\t\t\tec = std::error_code(errno, std::system_category());\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t} else {  // no such path, check parent dir's access mode\n\t\t\tif (access(dirname.c_str(), W_OK) == -1) {\n\t\t\t\tec = std::error_code(errno, std::system_category());\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t}\n\n#endif\n\n\treturn true;\n}\n\n\n\n\n/// Change the supplied filename so that it's safe to create it\n/// (remove any potentially harmful characters from it).\ninline std::string fs_filename_make_safe(const std::string_view& filename)\n{\n\tstd::string s(filename);\n\tstd::string::size_type pos = 0;\n\twhile ((pos = s.find_first_not_of(\n\t\t\t\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890._-\",\n\t\t\tpos)) != std::string::npos) {\n\t\ts[pos] = '_';\n\t\t++pos;\n\t}\n\t// win32 kernel (heh) has trouble with space and dot-ending files\n\tif (!s.empty() && (s[s.size() - 1] == '.' || s[s.size() - 1] == ' ')) {\n\t\ts[s.size() - 1] = '_';\n\t}\n\treturn s;\n}\n\n\n\n\n}  // ns hz\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/fs_ns.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2018 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_FS_NS_H\n#define HZ_FS_NS_H\n\n#if __has_include(<filesystem>)\n\t#include <filesystem>\n#else\n\t#include <experimental/filesystem>\n#endif\n\n\n/**\n\\file\nFilesystem utilities\n*/\n\n\nnamespace hz {\n\n\n#ifdef __cpp_lib_filesystem\n\n\tnamespace fs = std::filesystem;\n\n#else  // __cpp_lib_experimental_filesystem\n\n\t// Note: This requires -lstdc++fs with gcc's libstdc++, -lc++experimental with clang's libc++.\n\tnamespace fs = std::experimental::filesystem;\n\n#endif\n\n\n}  // ns hz\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/launch_url.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_LAUNCH_URL_H\n#define HZ_LAUNCH_URL_H\n\n#include <string>\n#include <gtk/gtk.h>\n\n#ifdef _WIN32\n\t#include <windows.h>  // seems to be needed by shellapi.h\n\t#include <shellapi.h>  // ShellExecuteW()\n\t#include \"win32_tools.h\"  // hz::win32_utf8_to_utf16\n#else\n\t#include <memory>\n#endif\n\n\n\n\nnamespace hz {\n\n\n\n/// Open URL in browser or mailto: link in mail client.\n/// Return error message on error, empty string otherwise.\n/// The link is in utf-8 in windows.\ninline std::string launch_url([[maybe_unused]] GtkWindow* window, const std::string& link)\n{\n\t// For some reason, gtk_show_uri() crashes on windows, so use our manual implementation.\n#ifdef _WIN32\n\tstd::wstring wlink = hz::win32_utf8_to_utf16(link);\n\tif (wlink.empty())\n\t\treturn \"Error while executing a command: The specified URI contains non-UTF-8 characters.\";\n\n\tHINSTANCE inst = ShellExecuteW(nullptr, L\"open\", wlink.c_str(), nullptr, nullptr, SW_SHOWNORMAL);\n\tif (inst > reinterpret_cast<HINSTANCE>(32)) {\n\t\treturn std::string();\n\t}\n\treturn \"Error while executing a command: Internal error.\";\n\n#else\n\n\tGError* error = nullptr;\n#if GTK_CHECK_VERSION(3, 22, 0)\n\tbool status = static_cast<bool>(gtk_show_uri_on_window(window, link.c_str(), GDK_CURRENT_TIME, &error));\n#else\n\tGdkScreen* screen = (window ? gtk_window_get_screen(window) : nullptr);\n\tbool status = static_cast<bool>(gtk_show_uri(screen, link.c_str(), GDK_CURRENT_TIME, &error));\n#endif\n\tstd::unique_ptr<GError, decltype(&g_error_free)> uerror(error, &g_error_free);\n\n\tif (!status) {\n\t\treturn std::string(\"Cannot open URL: \")\n\t\t\t\t+ ((error && error->message) ? (std::string(\": \") + error->message) : \".\");\n\t}\n\treturn {};\n#endif\n}\n\n\n\n\n}  // ns\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/locale_tools.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_LOCALE_TOOLS_H\n#define HZ_LOCALE_TOOLS_H\n\n#include <string>\n#include <locale>\n#include <clocale>  // std::setlocale\n#include <stdexcept>  // std::runtime_error\n\n\n/**\n\\file\nLocale manipulation facilities\n\nNote: The POSIX man page for setlocale (which libstdc++ is based on)\nstates: \"The locale state is common to all threads within a process.\"\nThis may have rather serious implications for thread-safety.\nHowever, for glibc, libstdc++ uses the \"uselocale\" extension, which\nsets locale on per-thread basis.\n*/\n\n\n// Remove setlocale macro from mingw's libintl.h\n// TODO mingw's gettext implementation should use libintl_setlocale():\n// extern char* libintl_setlocale(int, const char*);\n#ifdef setlocale\n\t#undef setlocale\n#endif\n\n\n\nnamespace hz {\n\n\n/// Set the C standard library locale, storing the previous one into \\c old_locale.\n/// \\return false on failure\ninline bool locale_c_set(const std::string& loc, std::string& old_locale)\n{\n\t// even though undocumented, glibc returns 0 when the locale string is invalid.\n\tchar* old = std::setlocale(LC_ALL, loc.c_str());\n\tif (old) {\n\t\told_locale = old;\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n\n\n/// Set the C standard library locale.\n/// \\return false on failure\ninline bool locale_c_set(const std::string& loc)\n{\n\treturn (bool)std::setlocale(LC_ALL, loc.c_str());  // returns 0 if invalid\n}\n\n\n\n/// Get current C standard library locale.\ninline std::string locale_c_get()\n{\n\treturn std::setlocale(LC_ALL, nullptr);\n}\n\n\n\n/// Set the C++ standard library locale (may also set the C locale depending\n/// on implementation), storing the previous one into \\c old_locale.\n/// \\return false on failure (no exception is thrown)\ninline bool locale_cpp_set(const std::locale& loc, std::locale& old_locale)\n{\n\ttry {\n\t\t// under FreeBSD (at least 6.x) and OSX, this may throw on anything\n\t\t// except \"C\" and \"POSIX\" with message:\n\t\t// \"locale::facet::_S_create_c_locale name not valid\".\n\t\t// Blame inadequate implementations.\n\t\told_locale = std::locale::global(loc);\n\t}\n\tcatch (const std::runtime_error& e) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n\n\n/// Set the C++ standard library locale (may also set the C locale depending\n/// on implementation), storing the previous one's name into \\c old_locale.\n/// \\return false on failure (no exception is thrown)\ninline bool locale_cpp_set(const std::locale& loc, std::string& old_locale)\n{\n\ttry {\n\t\told_locale = std::locale::global(loc).name();  // not sure, but name() may not be unique (?)\n\t}\n\tcatch (const std::runtime_error& e) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n\n\n/// Set the C++ standard library locale (may also set the C locale depending\n/// on implementation).\n/// \\return false on failure (no exception is thrown)\ninline bool locale_cpp_set(const std::locale& loc)\n{\n\ttry {\n\t\tstd::locale::global(loc);\n\t}\n\tcatch (const std::runtime_error& e) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n\n\n/// Set the C++ standard library locale name (may also set the C locale depending\n/// on implementation), storing the previous one into \\c old_locale.\n/// \\return false on failure (no exception is thrown)\ninline bool locale_cpp_set(const std::string& loc, std::locale& old_locale)\n{\n\ttry {\n\t\told_locale = std::locale::global(std::locale(loc.c_str()));\n\t}\n\tcatch (const std::runtime_error& e) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n\n/// Set the C++ standard library locale name (may also set the C locale depending\n/// on implementation), storing the previous one's name into \\c old_locale.\n/// \\return false on failure (no exception is thrown)\ninline bool locale_cpp_set(const std::string& loc, std::string& old_locale)\n{\n\ttry {\n\t\told_locale = std::locale::global(std::locale(loc.c_str())).name();  // not sure, but name() may not be unique (?)\n\t}\n\tcatch (const std::runtime_error& e) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n\n/// Set the C++ standard library locale name (may also set the C locale depending\n/// on implementation).\n/// \\return false on failure (no exception is thrown)\ninline bool locale_cpp_set(const std::string& loc)\n{\n\ttry {\n\t\tstd::locale::global(std::locale(loc.c_str()));\n\t}\n\tcatch (const std::runtime_error& e) {\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n\n\n/// Get current C++ standard library locale. This has no definition,\n/// use one of the specializations.\ntemplate<typename ReturnType>\nReturnType locale_cpp_get();\n\n\n/// Get current C++ standard library locale as a string.\n/// May return \"*\" if it's not representable as a string.\ntemplate<> inline\nstd::string locale_cpp_get<std::string>()\n{\n\treturn std::locale().name();\n}\n\n\n/// Get current C++ standard library locale std::locale object.\ntemplate<> inline\nstd::locale locale_cpp_get<std::locale>()\n{\n\treturn {};\n}\n\n\n\n\n\n/// Temporarily change the C standard library locale as long\n/// as this object lives.\nclass ScopedCLocale {\n\tpublic:\n\n\t\t/// Change to classic locale (aka \"C\")\n\t\texplicit ScopedCLocale(bool do_change = true) : do_change_(do_change)\n\t\t{\n\t\t\tif (do_change_) {  // avoid unnecessary const char* -> string conversion overhead\n\t\t\t\tbad_ = locale_c_set(\"C\", old_locale_);\n\t\t\t}\n\t\t}\n\n\t\t/// Change to user-specified locale loc.\n\t\texplicit ScopedCLocale(const std::string& loc, bool do_change = true) : do_change_(do_change)\n\t\t{\n\t\t\tif (do_change_) {\n\t\t\t\tbad_ = locale_c_set(loc, old_locale_);\n\t\t\t}\n\t\t}\n\n\t\t/// Deleted\n\t\tScopedCLocale(const ScopedCLocale& other) = delete;\n\n\t\t/// Deleted\n\t\tScopedCLocale(ScopedCLocale&& other) = delete;\n\n\t\t/// Deleted\n\t\tScopedCLocale& operator=(const ScopedCLocale&) = delete;\n\n\t\t/// Deleted\n\t\tScopedCLocale& operator=(ScopedCLocale&&) = delete;\n\n\n\t\t/// Change back the locale\n\t\t~ScopedCLocale()\n\t\t{\n\t\t\tthis->restore();\n\t\t}\n\n\t\t/// Get the old locale\n\t\t[[nodiscard]] std::string old() const\n\t\t{\n\t\t\treturn old_locale_;\n\t\t}\n\n\t\t/// Return true if locale setting was unsuccessful\n\t\t[[nodiscard]] bool bad() const\n\t\t{\n\t\t\treturn bad_;\n\t\t}\n\n\t\t/// Restore the locale. Invoked by destructor.\n\t\tbool restore()\n\t\t{\n\t\t\tif (do_change_ && !bad_) {\n\t\t\t\tbad_ = locale_c_set(old_locale_);\n\t\t\t\tdo_change_ = false;  // avoid invoking again\n\t\t\t}\n\t\t\treturn bad_;\n\t\t}\n\n\n\tprivate:\n\n\t\tstd::string old_locale_;  ///< Old locale\n\t\tbool do_change_ = true;  ///< Whether we changed something or not\n\t\tbool bad_ = false;  ///< If true, there was some error\n\n};\n\n\n\n/// Temporarily change the C++ standard library locale, as long as this\n/// object lives. This may also set the C locale depending on implementation.\nclass ScopedCppLocale {\n\tpublic:\n\n\t\t/// Change to classic locale (aka \"C\")\n\t\texplicit ScopedCppLocale(bool do_change = true) : do_change_(do_change)\n\t\t{\n\t\t\tif (do_change) {\n\t\t\t\tbad_ = locale_cpp_set(std::locale::classic(), old_locale_);\n\t\t\t}\n\t\t}\n\n\t\t/// Change to user-specified locale loc.\n\t\texplicit ScopedCppLocale(const std::string& loc, bool do_change = true) : do_change_(do_change)\n\t\t{\n\t\t\tif (do_change_) {\n\t\t\t\tbad_ = locale_cpp_set(loc, old_locale_);\n\t\t\t}\n\t\t}\n\n\t\t/// Change to user-specified locale loc.\n\t\texplicit ScopedCppLocale(const std::locale& loc, bool do_change = true) : do_change_(do_change)\n\t\t{\n\t\t\tif (do_change_) {\n\t\t\t\tbad_ = locale_cpp_set(loc, old_locale_);\n\t\t\t}\n\t\t}\n\n\t\t/// Deleted\n\t\tScopedCppLocale(const ScopedCppLocale& other) = delete;\n\n\t\t/// Deleted\n\t\tScopedCppLocale(ScopedCppLocale&& other) = delete;\n\n\t\t/// Deleted\n\t\tScopedCppLocale& operator=(const ScopedCppLocale&) = delete;\n\n\t\t/// Deleted\n\t\tScopedCppLocale& operator=(ScopedCppLocale&&) = delete;\n\n\n\t\t/// Change the locale back to the old one\n\t\t~ScopedCppLocale()\n\t\t{\n\t\t\tthis->restore();\n\t\t}\n\n\n\t\t/// Get the old locale\n\t\t[[nodiscard]] std::locale old() const\n\t\t{\n\t\t\treturn old_locale_;\n\t\t}\n\n\t\t/// Return true if locale setting was unsuccessful\n\t\t[[nodiscard]] bool bad() const\n\t\t{\n\t\t\treturn bad_;\n\t\t}\n\n\t\t/// Restore the locale. Invoked by destructor.\n\t\tbool restore()\n\t\t{\n\t\t\tif (do_change_ && !bad_) {\n\t\t\t\tbad_ = locale_cpp_set(old_locale_);\n\t\t\t\tdo_change_ = false;  // avoid invoking again\n\t\t\t}\n\t\t\treturn bad_;\n\t\t}\n\n\n\tprivate:\n\n\t\tstd::locale old_locale_;  ///< The old locale\n\t\tbool do_change_ = true;  ///< Whether we changed the locale or not\n\t\tbool bad_ = false;  ///< If true, there was an error setting the locale\n\n};\n\n\n\n\n\n}  // ns\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/main_tools.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2013 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_MAIN_TOOLS_H\n#define HZ_MAIN_TOOLS_H\n\n#include <cstdlib>  // EXIT_*\n#include <iostream>  // std::cerr\n#include <exception>  // std::exception, std::set_terminate()\n#include <string>\n\n#include \"system_specific.h\"  // type_name_demangle\n\n\nnamespace hz {\n\n\n/// This function calls `main_impl()` but wraps it in verbose exception handlers.\n/// \\tparam MainImplFunc function with signature `int main_impl()`, returning exit status.\ntemplate <typename MainImplFunc>\nint main_exception_wrapper(MainImplFunc main_impl) noexcept\n{\n\t// We don't use __gnu_cxx::__verbose_terminate_handler(), we can do the same in noexcept way\n\t// with C++ABI library (without clang-tidy warnings about not handling exceptions in main()).\n\ttry {\n\t\treturn main_impl();\n\t}\n\tcatch(std::exception& e) {\n\t\t// don't use anything other than cerr here, it's the safest option.\n\t\tstd::cerr << \"main(): Unhandled exception: \" << e.what() << std::endl;\n\t\tif (const auto* ex_type = get_current_exception_type()) {\n\t\t\tstd::cerr << \"Type of exception: \" << type_name_demangle(ex_type->name()) << std::endl;\n\t\t}\n\t}\n\tcatch(...) {  // this guarantees proper unwinding in case of unhandled exceptions (win32 I think)\n\t\tstd::cerr << \"main(): Unhandled unknown exception.\" << std::endl;\n\t\tif (const auto* ex_type = get_current_exception_type()) {\n\t\t\tstd::cerr << \"Type of exception: \" << type_name_demangle(ex_type->name()) << std::endl;\n\t\t}\n\t}\n\treturn EXIT_FAILURE;\n}\n\n\n\n}\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/process_signal.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_PROCESS_SIGNAL_H\n#define HZ_PROCESS_SIGNAL_H\n\n#include <string>\n#include <cstdio>  // std::snprintf\n\n#if defined ENABLE_GLIB && ENABLE_GLIB\n\t#include <glib.h>  // g_strsignal()\n#else\n\t#include <cstring>  // for string.h\n\t#include <string.h>  // strsignal()\n#endif\n\n#ifdef _WIN32\n\t// Note: #define WINVER 0x0501 (aka winxp) before this!\n\t// This is needed for GetProcessId() (winxp or later).\n\t// Just writing a prototype doesn't work - we get undefined symbols.\n\t#include <windows.h>  // all that winapi stuff\n\t#include <cerrno>  // errno\n#else\n\t#include <sys/types.h>  // pid_t\n\t#include <csignal>  // for signal.h\n\t#include <signal.h>  // kill()\n#endif\n\n\n\n/**\n\\file\nCompilation options:\n- Define ENABLE_GLIB to 1 to enable glib-related code (portable signal messages).\n*/\n\n\n// win32 doesn't have signal W* macros, define them (this is _very_ rough,\n// but win32 doesn't provide any better way AFAIK).\n// checking one macro is enough.\n#if defined _WIN32 && !defined WIFEXITED\n\t#define WIFEXITED(w) (((w) & 0XFFFFFF00) == 0)\n\t#define WIFSIGNALED(w) (!WIFEXITED(w))\n\t#define WEXITSTATUS(w) (w)\n\t#define WTERMSIG(w) (w)\n#endif\n\n\n\n\nnamespace hz {\n\n\n\n/// Portable strsignal() version for std::string.\n/// Note: This function may return messages in native language,\n/// possibly using LC_MESSAGES to select the language.\n/// If Glib is enabled, it returns messages in UTF-8 format.\ninline std::string signal_string(int signal_value);\n\n\n/// \\typedef process_id_t\n/// Process handle\n\n/// \\enum Signal\n/// Sendable signals\n\n/// \\var Signal Signal::None\n/// Verify that the process exists\n\n/// \\var Signal Signal::Term\n/// Ask the process to terminate itself\n\n/// \\var Signal Signal::Kill\n/// Nuke the process\n\n#ifdef _WIN32\n\tusing process_id_t = HANDLE;  // process handle, not process id\n\n\tenum class Signal {\n\t\tNone,\n\t\tTerminate,\n\t\tKill,\n\t};\n\n#else\n\n\tusing process_id_t = pid_t;\n\n\tenum class Signal {\n\t\tNone = 0,\n\t\tTerminate = SIGTERM,\n\t\tKill = SIGKILL\n\t};\n\n#endif\n\n\n/// Check if process ID handle is a valid ID or pointer.\n/// Invalid ID is 0 in Unix and nullptr in Windows.\ninline bool process_id_valid(process_id_t process_handle);\n\n\n/// Portable kill(). Works with Signal signals only.\n/// Process groups are not supported under win32.\ninline int process_signal_send(process_id_t process_handle, Signal sig);\n\n\n\n\n// ------------------------------------------ Implementation\n\n\n\n\ninline std::string signal_to_string(int signal_value)\n{\n\tstd::string msg;\n\n#if defined ENABLE_GLIB && ENABLE_GLIB\n\tmsg = g_strsignal(signal_value);  // no need to free. won't return 0. message is in utf8.\n\n// mingw doesn't have strsignal()!\n#elif defined _WIN32\n\n\tchar buf[64] = {0};\n\tstd::snprintf(buf, 64, \"Unknown signal: %d.\", signal_value);\n\tmsg = buf;\n\n#else  // no glib and not win32\n\n\tconst char* m = strsignal(signal_value);  // this may return 0, but not on Linux.\n\tif (m) {\n\t\tmsg = m;\n\t} else {\n\t\tchar buf[64] = {0};\n\t\tstd::snprintf(buf, 64, \"Unknown signal: %d.\", signal_value);\n\t\tmsg = buf;\n\t}\n#endif\n\n\treturn msg;\n}\n\n\n\n\nbool process_id_valid(process_id_t process_handle)\n{\n#ifdef _WIN32\n\treturn process_handle != nullptr;\n#else\n\treturn process_handle != 0;\n#endif\n}\n\n\n\n\n#ifdef _WIN32\n\nnamespace internal {\n\n\t// structure used to pass parameters to process_signal_find_by_pid.\n\tstruct process_signal_find_by_pid_arg {\n\t\texplicit process_signal_find_by_pid_arg(DWORD pid_) : pid(pid_), hwnd(nullptr)\n\t\t{ }\n\t\tDWORD pid;  // pid we're looking from\n\t\tHWND hwnd;  // hwnd used to return the result\n\t};\n\n\t// signal_send() helper\n\tinline BOOL CALLBACK process_signal_find_by_pid(HWND hwnd, LPARAM cb_arg)\n\t{\n\t\tDWORD pid = 0;\n\t\tGetWindowThreadProcessId(hwnd, &pid);\n\n\t\tauto* arg = reinterpret_cast<process_signal_find_by_pid_arg*>(cb_arg);\n\t\tif (pid == arg->pid) {\n\t\t\targ->hwnd = hwnd;\n\t\t\treturn FALSE;  // stop the caller\n\t\t}\n\n\t\treturn TRUE;  // continue searching\n\t}\n\n}\n\n#endif\n\n\n\n#ifndef _WIN32\n\n\tinline int process_signal_send(process_id_t process_handle, Signal sig)\n\t{\n\t\treturn kill(process_handle, static_cast<int>(sig));  // aah, the beauty of simplicity...\n\t}\n\n\n#else\n\n\tinline int process_signal_send(process_id_t process_handle, Signal sig)\n\t{\n\t\tif (!process_id_valid(process_handle)) {\n\t\t\terrno = ESRCH;  // The pid or process group does not exist.\n\t\t\treturn -1;\n\t\t}\n\n\t\t// just check if the process exists\n\t\tif (sig == Signal::None) {\n\t\t\t// Warning: GetProcessId() requires winxp or higher.\n\t\t\t// Without it we can't do anything meaningful.\n\t\t#if defined(WINVER) && WINVER >= 0x0501\n\t\t\tif (GetProcessId(process_handle) == 0) {\n\t\t\t\t// this may indicate many things, but let's not be picky.\n\t\t\t\terrno = ESRCH;  // The pid or process group does not exist.\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t#endif\n\t\t\treturn 0;  // everything ok\n\t\t}\n\n\t\t// unconditionall kill, no cleanups\n\t\tif (sig == Signal::Kill) {\n\n\t\t\t// This is an ugly way of murder, but such is the life of processes in win32...\n\t\t\t// GetExitCodeProcess() will return UINT(-1) as exit code.\n\t\t\tif (TerminateProcess(process_handle, static_cast<UINT>(-1)) == 0) {\n\t\t\t\terrno = ESRCH;  // The pid or process group does not exist.\n\t\t\t\treturn -1;\n\t\t\t}\n\n\n\t\t// try euthanasia\n\t\t} else if (sig == Signal::Terminate) {\n\n\t\t\t// Warning: GetProcessId() requires winxp or higher.\n\t\t\t// Without it we can't do anything meaningful.\n\t\t#if defined(WINVER) && WINVER >= 0x0501\n\t\t\tinternal::process_signal_find_by_pid_arg arg(GetProcessId(process_handle));\n\n\t\t\tif (EnumWindows(&internal::process_signal_find_by_pid, reinterpret_cast<LPARAM>(&arg)) != 0) {\n\t\t\t\tif (arg.hwnd) {  // we found something\n\t\t\t\t\t// tell it to close.\n\t\t\t\t\t// not sure what's the difference between ANSI/UNICODE here.\n\t\t\t\t\tPostMessageA(arg.hwnd, WM_QUIT, 0, 0);  // check the status later\n\n\t\t\t\t} else {  // error, not found\n\t\t\t\t\terrno = EPERM;  // no permission\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\n\t\t\t} else {  // no windows for this pid, can't kill without them...\n\t\t\t\terrno = EPERM;  // no permission\n\t\t\t\treturn -1;\n\t\t\t}\n\n\t\t#else\n\t\t\terrno = EPERM;  // no permission\n\t\t\treturn -1;\n\t\t#endif\n\n\t\t// huh? some bad enum / int screwup happened\n\t\t} else {\n\t\t\terrno = EINVAL;  // invalid signal\n\t\t\treturn -1;\n\t\t}\n\n\n\t\t// The signal was sent, wait for confirmation or something.\n\t\tDWORD exit_code = STILL_ACTIVE;\n\n\t\t// wait for its status to change for 500 msec.\n\t\tif (WaitForSingleObject(process_handle, 500) == WAIT_OBJECT_0) {\n\t\t\t// condition reached.\n\t\t\t// this puts STILL_ACTIVE into exit_code if it's not terminated yet.\n\t\t\tif (GetExitCodeProcess(process_handle, &exit_code) != 0) {\n\t\t\t\terrno = EPERM;  // no permission\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\n\t\tif (exit_code == STILL_ACTIVE) {\n\t\t\terrno = EPERM;  // no permission\n\t\t\treturn -1;\n\t\t}\n\n\t\treturn 0;\n\t}\n\n\n\n#endif\n\n\n\n\n}  // ns\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/stream_cast.h",
    "content": "/******************************************************************************\nLicense: Boost Software License 1.0\nCopyright:\n\t(C) 2000 - 2010 Kevlin Henney\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n#ifndef HZ_LEXICAL_CAST_H\n#define HZ_LEXICAL_CAST_H\n\n#include <cstddef>  // std::size_t\n#include <string>\n#include <sstream>\n#include <limits>  // std::numeric_limits\n#include <type_traits>\n\n#include \"bad_cast_exception.h\"\n\n\n/**\n\\file\nstream_cast library, based on boost::lexical_cast.\n\nConvert any type supporting \"operator<<\" to any other type\nsupporting \"operator>>\".\n\nOriginal notes and copyright info follow:\n\n// what:  lexical_cast custom keyword cast\n// who:   contributed by Kevlin Henney,\n//        enhanced with contributions from Terje Slettebø,\n//        with additional fixes and suggestions from Gennaro Prota,\n//        Beman Dawes, Dave Abrahams, Daryle Walker, Peter Dimov,\n//        and other Boosters\n// when:  November 2000, March 2003, June 2005\n\n// Copyright Kevlin Henney, 2000-2005. All rights reserved.\n//\n// Distributed under the Boost Software License, Version 1.0. (See\n// accompanying file LICENSE_1_0.txt or copy at\n// http://www.boost.org/LICENSE_1_0.txt)\n*/\n\n\n\nnamespace hz {\n\n\n\nnamespace internal {\n\n\n\t/// stream_cast helper stream\n\ttemplate<typename Target, typename Source>\n\tclass lexical_stream {\n\t\tpublic:\n\n\t\t\t// Constructor\n\t\t\tlexical_stream()\n\t\t\t{\n\t\t\t\tstream.unsetf(std::ios::skipws);  // disable whitespace skipping\n\n\t\t\t\tif (std::numeric_limits<Target>::is_specialized) {\n\t\t\t\t\tstream.precision(std::numeric_limits<Target>::digits10 + 1);\n\n\t\t\t\t} else if (std::numeric_limits<Source>::is_specialized) {\n\t\t\t\t\tstream.precision(std::numeric_limits<Source>::digits10 + 1);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/// Output operator\n\t\t\tbool operator<<(const Source &input)\n\t\t\t{\n\t\t\t\treturn !(stream << input).fail();\n\t\t\t}\n\n\t\t\t/// Input operator\n\t\t\ttemplate<typename InputStreamable>\n\t\t\tbool operator>>(InputStreamable &output)\n\t\t\t{\n\t\t\t\treturn !std::is_pointer<InputStreamable>::value &&\n\t\t\t\t\tstream >> output &&\n\t\t\t\t\tstream.get() == std::char_traits<std::stringstream::char_type>::eof();\n\t\t\t}\n\n\t\t\t/// Input operator\n\t\t\tbool operator>>(std::string &output)\n\t\t\t{\n\t\t\t\toutput = stream.str();\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\tprivate:\n\t\t\tstd::stringstream stream;  ///< The actual string stream\n\n\t};\n\n\n}  // ns internal\n\n\n\n\n/// This is thrown in case of cast failure\nclass bad_stream_cast : public hz::bad_cast_except {\n\tpublic:\n\t\tbad_stream_cast(const std::type_info& src, const std::type_info& dest)\n\t\t\t\t: hz::bad_cast_except(src, dest, \"bad_stream_cast\",\n\t\t\t\tR\"(Failed stream_cast from \"%s\" to \"%s\".)\")\n\t\t{ }\n};\n\n\n\n/// Stream cast - cast Source to Target using stream operators.\ntemplate<typename Target, typename Source>\nTarget stream_cast(const Source& arg)\n{\n\tusing NewSource = std::decay_t<Source>;\n\n\tinternal::lexical_stream<Target, NewSource> interpreter;\n\tTarget result;\n\n\tif (!(interpreter << arg && interpreter >> result)) {\n\t\tthrow bad_stream_cast(typeid(NewSource), typeid(Target));\n\t}\n\n\treturn result;\n}\n\n\n\n\n}  // ns\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/string_algo.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_STRING_ALGO_H\n#define HZ_STRING_ALGO_H\n\n#include <string>\n#include <cctype>  // std::tolower, std::toupper\n#include <string_view>\n\n\n\nnamespace hz {\n\n\n\n// --------------------------------------------- UTF8\n\n/// Since C++20, std::filesystem::path::u8string() returns std::u8string instead of std::string.\n/// This introduces incompatibility. We always assume that std::string contains utf-8 data,\n/// So these conversion functions assume the same.\n\n\n/// Convert std::u8string_view to UTF-8-containing std::string\ninline std::string u8string_to_string(std::u8string_view u8str)\n{\n\treturn std::string(reinterpret_cast<const char*>(u8str.data()), u8str.size());\n}\n\n\n/// Convert UTF-8-containing std::string to std::u8string\ninline std::u8string u8string_from_string(const std::string_view& str)\n{\n\treturn std::u8string(reinterpret_cast<const char8_t*>(str.data()), str.size());\n}\n\n\n\n\n\n// --------------------------------------------- Split\n\n\n/// Split a string into components by character (delimiter), appending the\n/// components (without delimiters) to container \"append_here\".\n/// If skip_empty is true, then empty components will be omitted.\n/// If \"limit\" is more than 0, only put a maximum of \"limit\" number of\n/// elements into vector, with the last one containing the rest of the string.\ntemplate<class Container>\nvoid string_split(std::string_view str, char delimiter,\n\t\tContainer& append_here, bool skip_empty = false, typename Container::difference_type limit = 0)\n{\n\tstd::string::size_type curr = 0, last = 0;\n\tconst std::string::size_type end = str.size();\n\ttypename Container::difference_type num = 0;  // number inserted\n\n\twhile (true) {\n\t\tif (last >= end) {  // last is past the end\n\t\t\tif (!skip_empty)  // no need to check num here\n\t\t\t\tappend_here.push_back(std::string());\n\t\t\tbreak;\n\t\t}\n\n\t\tcurr = str.find(delimiter, last);\n\n\t\tif (!skip_empty || (curr != last)) {\n\t\t\tif (++num == limit) {\n\t\t\t\tappend_here.emplace_back(str.substr(last, std::string::npos));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tappend_here.emplace_back(str.substr(last, (curr == std::string::npos ? curr : (curr - last))));\n\t\t}\n\n\t\tif (curr == std::string::npos)\n\t\t\tbreak;\n\n\t\tlast = curr + 1;\n\t}\n}\n\n\n\n/// Split a string into components by another string (delimiter), appending the\n/// components (without delimiters) to container \"append_here\".\n/// If skip_empty is true, then empty components will be omitted.\n/// If \"limit\" is more than 0, only put a maximum of \"limit\" number of\n/// elements into vector, with the last one containing the rest of the string.\ntemplate<class Container>\nvoid string_split(std::string_view str, const std::string& delimiter,\n\t\tContainer& append_here, bool skip_empty = false, typename Container::difference_type limit = 0)\n{\n\tconst std::string::size_type skip_size = delimiter.size();\n\tif (str.size() < skip_size) {\n\t\tappend_here.emplace_back(str);\n\t\treturn;\n\t}\n\n\tstd::string::size_type curr = 0, last = 0;\n\tconst std::string::size_type end = str.size();\n\ttypename Container::difference_type num = 0;  // number inserted\n\n\twhile (true) {\n\t\tif (last >= end) {  // last is past the end\n\t\t\tif (!skip_empty)  // no need to check num here\n\t\t\t\tappend_here.push_back(std::string());\n\t\t\tbreak;\n\t\t}\n\n\t\tcurr = str.find(delimiter, last);\n\t\tstd::string_view component = str.substr(last, (curr == std::string::npos ? curr : (curr - last)));\n\n\t\tif (!skip_empty || !component.empty()) {\n\t\t\tif (++num == limit) {\n\t\t\t\tappend_here.emplace_back(str.substr(last, std::string::npos));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tappend_here.emplace_back(component);\n\t\t}\n\n\t\tif (curr == std::string::npos)\n\t\t\tbreak;\n\n\t\tlast = curr + skip_size;\n\t}\n}\n\n\n/// Split a string into components by any of the characters (delimiters), appending the\n/// components (without delimiters) to container \"append_here\".\n/// If skip_empty is true, then empty components will be omitted.\n/// If \"limit\" is more than 0, only put a maximum of \"limit\" number of\n/// elements into vector, with the last one containing the rest of the string.\ntemplate<class Container>\nvoid string_split_by_chars(std::string_view str, std::string_view delimiter_chars,\n\t\tContainer& append_here, bool skip_empty = false, typename Container::difference_type limit = 0)\n{\n\tstd::string::size_type curr = 0, last = 0;\n\tconst std::string::size_type end = str.size();\n\ttypename Container::difference_type num = 0;  // number inserted\n\n\twhile (true) {\n\t\tif (last >= end) {  // last is past the end\n\t\t\tif (!skip_empty)  // no need to check num here\n\t\t\t\tappend_here.emplace_back({});\n\t\t\tbreak;\n\t\t}\n\n\t\tcurr = str.find_first_of(delimiter_chars, last);\n\n\t\tif (!skip_empty || (curr != last)) {\n\t\t\tif (++num == limit) {\n\t\t\t\tappend_here.push_back(str.substr(last, std::string::npos));\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tappend_here.push_back(str.substr(last, (curr == std::string::npos ? curr : (curr - last))));\n\t\t}\n\n\t\tif (curr == std::string::npos)\n\t\t\tbreak;\n\n\t\tlast = curr + 1;\n\t}\n}\n\n\n\n\n// --------------------------------------------- Join\n\n\n/// Join elements of container v into a string, using glue between them.\ntemplate<class Container>\nstd::string string_join(const Container& v, char glue)\n{\n\tstd::string ret;\n\tfor (auto begin = v.cbegin(), iter = begin; iter != v.cend(); ++iter) {\n\t\tif (iter != begin)\n\t\t\tret += glue;\n\t\tret += (*iter);\n\t}\n\treturn ret;\n}\n\n\n\n/// Join elements of container v into a string, using glue between them.\ntemplate<class Container>\nstd::string string_join(const Container& v, std::string_view glue)\n{\n\tstd::string ret;\n\tfor (auto begin = v.cbegin(), iter = begin; iter != v.cend(); ++iter) {\n\t\tif (iter != begin)\n\t\t\tret += glue;\n\t\tret += (*iter);\n\t}\n\treturn ret;\n}\n\n\n\n\n// --------------------------------------------- Trim\n\n\n/// Trim a string s from both sides (modifying s). Return true if s was modified.\n/// Trimming removes all trim_chars that occur on either side of the string s.\ninline bool string_trim(std::string& s, const std::string& trim_chars = \" \\t\\r\\n\")\n{\n\tif (trim_chars.empty())\n\t\treturn false;\n\n\tconst auto s_size = s.size();\n\n\tstd::string::size_type index = s.find_last_not_of(trim_chars);\n\tif (index != std::string::npos)\n\t\ts.erase(index + 1);  // from index+1 to the end\n\n\tindex = s.find_first_not_of(trim_chars);\n\tif (index != std::string::npos) {\n\t\ts.erase(0, index);\n\t} else {\n\t\ts.clear();\n\t}\n\n\treturn s_size != s.size();  // true if s was modified\n}\n\n\n/// Trim a string s from both sides (not modifying s), returning the changed string.\n/// Trimming removes all trim_chars that occur on either side of the string s.\ninline std::string string_trim_copy(std::string_view s, const std::string& trim_chars = \" \\t\\r\\n\")\n{\n\tstd::string ret(s);\n\tstring_trim(ret, trim_chars);\n\treturn ret;\n}\n\n\n\n\n/// Trim a string s from the left (modifying s). Return true if s was modified.\n/// Trimming removes all trim_chars that occur on the left side of the string s.\ninline bool string_trim_left(std::string& s, const std::string& trim_chars = \" \\t\\r\\n\")\n{\n\tif (trim_chars.empty())\n\t\treturn false;\n\n\tconst auto s_size = s.size();\n\n\tstd::string::size_type index = s.find_first_not_of(trim_chars);\n\tif (index != std::string::npos) {\n\t\ts.erase(0, index);\n\t} else {\n\t\ts.clear();\n\t}\n\n\treturn s_size != s.size();  // true if s was modified\n}\n\n\n/// Trim a string s from the left (not modifying s), returning the changed string.\n/// Trimming removes all trim_chars that occur on the left side of the string s.\ninline std::string string_trim_left_copy(std::string_view s, const std::string& trim_chars = \" \\t\\r\\n\")\n{\n\tstd::string ret(s);\n\tstring_trim_left(ret, trim_chars);\n\treturn ret;\n}\n\n\n\n\n/// Trim a string s from the right (modifying s). Return true if s was modified.\n/// Trimming removes all trim_chars that occur on the right side of the string s.\ninline bool string_trim_right(std::string& s, const std::string& trim_chars = \" \\t\\r\\n\")\n{\n\tif (trim_chars.empty())\n\t\treturn false;\n\n\tconst std::string::size_type s_size = s.size();\n\n\tstd::string::size_type index = s.find_last_not_of(trim_chars);\n\tif (index != std::string::npos)\n\t\ts.erase(index + 1);  // from index+1 to the end\n\n\treturn s_size != s.size();  // true if s was modified\n}\n\n\n/// Trim a string s from the right (not modifying s), returning the changed string.\n/// Trimming removes all trim_chars that occur on the right side of the string s.\ninline std::string string_trim_right_copy(std::string_view s, const std::string& trim_chars = \" \\t\\r\\n\")\n{\n\tstd::string ret(s);\n\tstring_trim_right(ret, trim_chars);\n\treturn ret;\n}\n\n\n\n// --------------------------------------------- Erase\n\n\n/// Erase the left side of string s if it contains substring_to_erase,\n/// modifying s. Returns true if s was modified.\ninline bool string_erase_left(std::string& s, const std::string& substring_to_erase)\n{\n\tif (substring_to_erase.empty())\n\t\treturn false;\n\n\tstd::string::size_type sub_size = substring_to_erase.size();\n\tif (s.compare(0, sub_size, substring_to_erase) == 0) {\n\t\ts.erase(0, sub_size);\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n\n/// Erase the left side of string s if it contains substring_to_erase,\n/// not modifying s, returning the changed string.\ninline std::string string_erase_left_copy(const std::string& s, const std::string& substring_to_erase)\n{\n\tstd::string ret(s);\n\tstring_erase_left(ret, substring_to_erase);\n\treturn ret;\n}\n\n\n\n\n/// Erase the right side of string s if it contains substring_to_erase,\n/// modifying s. Returns true if s was modified.\ninline bool string_erase_right(std::string& s, const std::string& substring_to_erase)\n{\n\tstd::string::size_type sub_size = substring_to_erase.size();\n\tif (sub_size == 0)\n\t\treturn false;\n\n\tstd::string::size_type s_size = s.size();\n\tif (sub_size > s_size)\n\t\treturn false;\n\n\tif (s.compare(s_size - sub_size, sub_size, substring_to_erase) == 0) {\n\t\ts.erase(s_size - sub_size, sub_size);\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n\n/// Erase the right side of string s if it contains substring_to_erase,\n/// not modifying s, returning the changed string.\ninline std::string string_erase_right_copy(const std::string& s, const std::string& substring_to_erase)\n{\n\tstd::string ret(s);\n\tstring_erase_right(ret, substring_to_erase);\n\treturn ret;\n}\n\n\n\n// --------------------------------------------- Misc. Transformations\n\n\n/// remove adjacent duplicate chars inside s (modifying s).\n/// returns true if s was modified.\n/// useful for e.g. removing extra spaces inside the string.\ninline bool string_remove_adjacent_duplicates(std::string& s, char c, std::size_t max_out_adjacent = 1)\n{\n\tif (s.size() <= max_out_adjacent)\n\t\treturn false;\n\n\tbool changed = false;\n\tstd::string::size_type pos1 = 0, pos2 = 0;\n\n\twhile ((pos1 = s.find(c, pos1)) != std::string::npos) {\n\t\tpos2 = s.find_first_not_of(c, pos1);\n\t\tif (pos2 == std::string::npos)\n\t\t\tpos2 = s.size();  // just past the last char\n\t\tif (pos2 - pos1 > max_out_adjacent) {\n\t\t\ts.erase(pos1 + max_out_adjacent, pos2 - pos1 - max_out_adjacent);\n\t\t\tchanged = true;\n\t\t}\n\t\tpos1 += max_out_adjacent;\n\t}\n\n\treturn changed;\n}\n\n\n/// remove adjacent duplicate chars inside s, not modifying s, returning the changed string.\ninline std::string string_remove_adjacent_duplicates_copy(const std::string& s, char c, std::size_t max_out_adjacent = 1)\n{\n\tstd::string ret(s);\n\tstring_remove_adjacent_duplicates(ret, c, max_out_adjacent);\n\treturn ret;\n}\n\n\n\n\n// --------------------------------------------- Replace\n\n\n// TODO: Add string_replace_linear(), where multiple strings are replaced into the\n// original string (as opposed to previous result).\n\n\n/// Replace from with to inside s (modifying s). Return number of replacements made.\ninline std::string::size_type string_replace(std::string& s,\n\t\tconst std::string_view& from, const std::string_view& to, int max_replacements = -1)\n{\n\tif (from.empty())\n\t\treturn std::string::npos;\n\tif (max_replacements == 0 || from == to)\n\t\treturn 0;\n\n\tconst std::string::size_type from_len(from.size());\n\tconst std::string::size_type to_len(to.size());\n\n\tstd::string::size_type cnt = 0;\n\tstd::string::size_type pos = 0;\n\n\twhile ((pos = s.find(from, pos)) != std::string::npos) {\n\t\ts.replace(pos, from_len, to);\n\t\tpos += to_len;\n\t\tif (static_cast<int>(++cnt) >= max_replacements && max_replacements != -1)\n\t\t\tbreak;\n\t}\n\n\treturn cnt;\n}\n\n\n/// Replace from with to inside s, not modifying s, returning the changed string.\ninline std::string string_replace_copy(const std::string& s,\n\t\tconst std::string_view& from, const std::string_view& to, int max_replacements = -1)\n{\n\tstd::string ret(s);\n\tstring_replace(ret, from, to, max_replacements);\n\treturn ret;\n}\n\n\n\n\n/// Replace from with to inside s (modifying s). char version.\ninline std::string::size_type string_replace(std::string& s,\n\t\tchar from, char to, int max_replacements = -1)\n{\n\tif (max_replacements == 0 || from == to)\n\t\treturn 0;\n\n\tstd::string::size_type cnt = 0, pos = 0;\n\n\twhile ((pos = s.find(from, pos)) != std::string::npos) {\n\t\ts[pos] = to;\n\t\t++pos;\n\t\tif (static_cast<int>(++cnt) >= max_replacements && max_replacements != -1)\n\t\t\tbreak;\n\t}\n\n\treturn cnt;\n}\n\n\n/// Replace from with to inside s, not modifying s, returning the changed string. char version.\ninline std::string string_replace_copy(const std::string& s,\n\t\tchar from, char to, int max_replacements = -1)\n{\n\tstd::string ret(s);\n\tstring_replace(ret, from, to, max_replacements);\n\treturn ret;\n}\n\n\n\n\n\n/// Replace from_chars[0] with to_chars[0], from_chars[1] with to_chars[1], etc. in s (modifying s).\n/// from_chars.size() must be equal to to_chars.size().\n/// Note: This is a multi-pass algorithm (there are from_chars.size() iterations).\ninline std::string::size_type string_replace_chars(std::string& s,\n\t\tconst std::string_view& from_chars, const std::string_view& to_chars, int max_replacements = -1)\n{\n\tconst std::string::size_type from_size = from_chars.size();\n\tif (from_size != to_chars.size())\n\t\treturn std::string::npos;\n\tif (max_replacements == 0 || from_chars == to_chars)\n\t\treturn 0;\n\n\tstd::string::size_type cnt = 0, pos = 0;\n\n\tfor(std::string::size_type i = 0; i < from_size; ++i) {\n\t\tpos = 0;\n\t\tchar from = from_chars[i], to = to_chars[i];\n\t\twhile ((pos = s.find(from, pos)) != std::string::npos) {\n\t\t\ts[pos] = to;\n\t\t\t++pos;\n\t\t\tif (static_cast<int>(++cnt) >= max_replacements && max_replacements != -1)\n\t\t\t\tbreak;\n\t\t}\n\t\tif (max_replacements != -1 && static_cast<int>(cnt) >= max_replacements)\n\t\t\tbreak;\n\t}\n\n\treturn cnt;\n}\n\n\n/// Replace from_chars[0] with to_chars[0], from_chars[1] with to_chars[1], etc. in s,\n/// not modifying s, returning the changed string.\n/// from_chars.size() must be equal to to_chars.size().\n/// Note: This is a multi-pass algorithm (there are from_chars.size() iterations).\ninline std::string string_replace_chars_copy(const std::string& s,\n\t\tconst std::string_view& from_chars, const std::string_view& to_chars, int max_replacements = -1)\n{\n\tstd::string ret(s);\n\tstring_replace_chars(ret, from_chars, to_chars, max_replacements);\n\treturn ret;\n}\n\n\n\n\n\n/// Replace all chars from from_chars with to_char (modifying s).\ninline std::string::size_type string_replace_chars(std::string& s,\n\t\tconst std::string_view& from_chars, char to_char, int max_replacements = -1)\n{\n\tif (from_chars.empty())\n\t\treturn std::string::npos;\n\tif (max_replacements == 0)\n\t\treturn 0;\n\n\tstd::string::size_type cnt = 0, pos = 0;\n\twhile ((pos = s.find_first_of(from_chars, pos)) != std::string::npos) {\n\t\ts[pos] = to_char;\n\t\t++pos;\n\t\tif (static_cast<int>(++cnt) >= max_replacements && max_replacements != -1)\n\t\t\tbreak;\n\t}\n\n\treturn cnt;\n}\n\n\n/// Replace all chars from from_chars with to_char, not modifying s, returning the changed string.\ninline std::string string_replace_chars_copy(const std::string& s,\n\t\tconst std::string_view& from_chars, char to_char, int max_replacements = -1)\n{\n\tstd::string ret(s);\n\tstring_replace_chars(ret, from_chars, to_char, max_replacements);\n\treturn ret;\n}\n\n\n\n\n\n/// Replace from_strings[0] with to_strings[0], from_strings[1] with to_strings[1], etc.\n/// in s (modifying s). Returns total number of replacements performed.\n/// from_strings.size() must be equal to to_strings.size().\n/// Note: This is a multi-pass algorithm (there are from_strings.size() iterations).\n\n/// Implementation note: We cannot use \"template<template<class> C1>\", because\n/// it appears that it was a gcc extension, removed in 4.1 (C1 cannot bind to std::vector,\n/// which has 2 (or more, implementation dependent) template parameters. gcc 4.1\n/// allowed this because vector's other parameters have defaults).\ntemplate<class Container1, class Container2>\nstd::string::size_type string_replace_array(std::string& s,\n\t\tconst Container1& from_strings, const Container2& to_strings, int max_replacements = -1)\n{\n\tconst std::string::size_type from_array_size = from_strings.size();\n\n\tif (from_array_size != to_strings.size())\n\t\treturn std::string::npos;\n\tif (max_replacements == 0)\n\t\treturn 0;\n\n\tstd::string::size_type cnt = 0;\n\n\tfor(std::string::size_type i = 0; i < from_array_size; ++i) {\n\t\tstd::string::size_type pos = 0;\n\t\twhile ((pos = s.find(from_strings[i], pos)) != std::string::npos) {\n\t\t\ts.replace(pos, from_strings[i].size(), to_strings[i]);\n\t\t\tpos += to_strings[i].size();\n\t\t\tif (static_cast<int>(++cnt) >= max_replacements && max_replacements != -1)\n\t\t\t\tbreak;\n\t\t}\n\t\tif (max_replacements != -1 && static_cast<int>(cnt) > max_replacements)\n\t\t\tbreak;\n\t}\n\n\treturn cnt;\n}\n\n\n/// Eeplace from_strings[0] with to_strings[0], from_strings[1] with to_strings[1], etc. in s,\n/// not modifying s, returning the changed string.\n/// from_strings.size() must be equal to to_strings.size().\n/// Note: This is a multi-pass algorithm (there are from_strings.size() iterations).\ntemplate<class Container1, class Container2> inline\nstd::string string_replace_array_copy(const std::string& s,\n\t\tconst Container1& from_strings, const Container2& to_strings, int max_replacements = -1)\n{\n\tstd::string ret(s);\n\tstring_replace_array(ret, from_strings, to_strings, max_replacements);\n\treturn ret;\n}\n\n\n/// A version with a hash\ntemplate<class AssociativeContainer>\nstd::string::size_type string_replace_array(std::string& s,\n\t\tconst AssociativeContainer& replacement_map, int max_replacements = -1)\n{\n\tif (max_replacements == 0)\n\t\treturn 0;\n\n\tstd::string::size_type cnt = 0;\n\n\tfor(const auto& iter : replacement_map) {\n\t\tstd::string::size_type pos = 0;\n\t\twhile ((pos = s.find(iter.first, pos)) != std::string::npos) {\n\t\t\ts.replace(pos, iter.first.size(), iter.second);\n\t\t\tpos += iter.second.size();\n\t\t\tif (static_cast<int>(++cnt) >= max_replacements && max_replacements != -1)\n\t\t\t\tbreak;\n\t\t}\n\t\tif (max_replacements != -1 && static_cast<int>(cnt) > max_replacements)\n\t\t\tbreak;\n\t}\n\n\treturn cnt;\n}\n\n\n/// A version with a hash\ntemplate<class AssociativeContainer>\nstd::string string_replace_array_copy(const std::string& s,\n\t\tconst AssociativeContainer& replacement_map, int max_replacements = -1)\n{\n\tstd::string ret(s);\n\tstring_replace_array(ret, replacement_map, max_replacements);\n\treturn ret;\n}\n\n\n\n\n\n\n\n/// Replace all strings in from_strings with to_string in s (modifying s).\n/// Returns total number of replacements performed.\n/// Note: This is a one-pass algorithm.\ntemplate<class Container> inline\nstd::string::size_type string_replace_array(std::string& s,\n\t\tconst Container& from_strings, const std::string_view& to_string, int max_replacements = -1)\n{\n\tconst std::string::size_type from_array_size = from_strings.size();\n\tconst std::string::size_type to_str_size = to_string.size();\n\n\tif (from_array_size == 0)\n\t\treturn std::string::npos;\n\tif (max_replacements == 0)\n\t\treturn 0;\n\n\tstd::string::size_type cnt = 0, pos = 0;\n\tfor(std::string::size_type i = 0; i < from_array_size; ++i) {\n\t\tpos = 0;\n\t\twhile ((pos = s.find(from_strings[i], pos)) != std::string::npos) {\n\t\t\ts.replace(pos, from_strings[i].size(), to_string);\n\t\t\tpos += to_str_size;\n\t\t\tif (static_cast<int>(++cnt) >= max_replacements && max_replacements != -1)\n\t\t\t\tbreak;\n\t\t}\n\t\tif (max_replacements != -1 && static_cast<int>(cnt) > max_replacements)\n\t\t\tbreak;\n\t}\n\n\treturn cnt;\n}\n\n\n/// Replace all strings in from_strings with to_string in s, not modifying s, returning the changed string.\n/// Note: This is a one-pass algorithm.\ntemplate<class Container> inline\nstd::string string_replace_array_copy(const std::string& s,\n\t\tconst Container& from_strings, const std::string_view& to_string, int max_replacements = -1)\n{\n\tstd::string ret(s);\n\tstring_replace_array(ret, from_strings, to_string, max_replacements);\n\treturn ret;\n}\n\n\n\n\n/// Same as the other overloads, but needed to avoid conflict with all-template version\ntemplate<class Container> inline\nstd::string::size_type string_replace_array(std::string& s,\n\t\tconst Container& from_strings, const std::string& to_string, int max_replacements = -1)\n{\n\treturn string_replace_array<Container>(s, from_strings, std::string_view(to_string), max_replacements);\n}\n\n\n// Same as the other overloads, but needed to avoid conflict with all-template version\ntemplate<class Container> inline\nstd::string string_replace_array_copy(const std::string& s,\n\t\tconst Container& from_strings, const std::string& to_string, int max_replacements = -1)\n{\n\treturn string_replace_array_copy<Container>(s, from_strings, std::string_view(to_string), max_replacements);\n}\n\n\n\n\n/// Same as the other overloads, but needed to avoid conflict with all-template version\ntemplate<class Container> inline\nstd::string::size_type string_replace_array(std::string& s,\n\t\tconst Container& from_strings, const char* to_string, int max_replacements = -1)\n{\n\treturn string_replace_array<Container>(s, from_strings, std::string_view(to_string), max_replacements);\n}\n\n\n// Same as the other overloads, but needed to avoid conflict with all-template version\ntemplate<class Container> inline\nstd::string string_replace_array_copy(const std::string& s,\n\t\tconst Container& from_strings, const char* to_string, int max_replacements = -1)\n{\n\treturn string_replace_array_copy<Container>(s, from_strings, std::string_view(to_string), max_replacements);\n}\n\n\n\n\n// --------------------------------------------- Matching\n\n\n// FIXME These should be in C++20.\n\n\n/// Check whether a string begins with another string\ninline bool string_begins_with(std::string_view str, const std::string& substr)\n{\n\tif (str.length() >= substr.length()) {\n\t\treturn (str.compare(0, substr.length(), substr) == 0);\n\t}\n\treturn false;\n}\n\n\n\n/// Check whether a string begins with a character\ninline bool string_begins_with(std::string_view str, char ch)\n{\n\treturn !str.empty() && str[0] == ch;\n}\n\n\n\n/// Check whether a string ends with another string\ninline bool string_ends_with(std::string_view str, const std::string& substr)\n{\n\tif (str.length() >= substr.length()) {\n\t\treturn (str.compare(str.length() - substr.length(), substr.length(), substr) == 0);\n\t}\n\treturn false;\n}\n\n\n\n/// Check whether a string ends with a character\ninline bool string_ends_with(std::string_view str, char ch)\n{\n\treturn !str.empty() && str[str.size() - 1] == ch;\n}\n\n\n\n\n// --------------------------------------------- Utility\n\n\n/// Auto-detect and convert mac/dos/unix newline formats in s (modifying s) to unix format.\n/// Returns true if \\c s was changed.\ninline bool string_any_to_unix(std::string& s)\n{\n\tstd::string::size_type n = hz::string_replace(s, \"\\r\\n\", \"\\n\");  // dos\n\tn += hz::string_replace(s, '\\r', '\\n');  // mac\n\treturn bool(n);\n}\n\n\n\n/// Auto-detect and convert mac/dos/unix newline formats in s to unix format.\n/// Returns the result string.\ninline std::string string_any_to_unix_copy(std::string_view s)\n{\n\tstd::string ret(s);\n\tstring_any_to_unix(ret);\n\treturn ret;\n}\n\n\n/// Auto-detect and convert mac/dos/unix newline formats in s (modifying s) to dos format.\n/// Returns true if \\c s was changed.\ninline bool string_any_to_dos(std::string& s)\n{\n\tbool changed = string_any_to_unix(s);\n\tstd::string::size_type n = hz::string_replace(s, \"\\n\", \"\\r\\n\");  // dos\n\treturn bool(n) || changed;  // may not really work\n}\n\n\n\n/// Auto-detect and convert mac/dos/unix newline formats in s to dos format.\n/// Returns the result string.\ninline std::string string_any_to_dos_copy(std::string_view s)\n{\n\tstd::string ret(s);\n\tstring_any_to_dos(ret);\n\treturn ret;\n}\n\n\n\n/// Convert s to lowercase (modifying s). Return size of the string.\ninline std::string::size_type string_to_lower(std::string& s)\n{\n\tconst std::string::size_type len = s.size();\n\tfor(std::string::size_type i = 0; i != len; ++i) {\n\t\ts[i] = static_cast<char>(std::tolower(s[i]));\n\t}\n\treturn len;\n}\n\n\n\n/// Convert s to lowercase, not modifying s, returning the changed string.\ninline std::string string_to_lower_copy(std::string_view s)\n{\n\tstd::string ret(s);\n\tstring_to_lower(ret);\n\treturn ret;\n}\n\n\n\n/// Convert s to uppercase (modifying s). Return size of the string.\ninline std::string::size_type string_to_upper(std::string& s)\n{\n\tconst std::string::size_type len = s.size();\n\tfor(std::string::size_type i = 0; i != len; ++i) {\n\t\ts[i] = static_cast<char>(std::toupper(s[i]));\n\t}\n\treturn len;\n}\n\n\n\n/// Convert s to uppercase, not modifying s, returning the changed string.\ninline std::string string_to_upper_copy(std::string_view s)\n{\n\tstd::string ret(s);\n\tstring_to_upper(ret);\n\treturn ret;\n}\n\n\n\n// --------------------------------------------- Natural Sort\n\n\n/// Compare two strings using natural (alphanumeric) sort order.\n/// Natural sort order treats consecutive digits as numbers, so \"file2.txt\" comes before \"file10.txt\".\n/// This is useful for sorting filenames, device names, etc.\n/// Returns: < 0 if a < b, 0 if a == b, > 0 if a > b\ninline int string_natural_compare(std::string_view a, std::string_view b)\n{\n\tstd::size_t i = 0;\n\tstd::size_t j = 0;\n\tconst std::size_t a_size = a.size();\n\tconst std::size_t b_size = b.size();\n\n\twhile (i < a_size && j < b_size) {\n\t\tconst bool a_is_digit = std::isdigit(static_cast<unsigned char>(a[i]));\n\t\tconst bool b_is_digit = std::isdigit(static_cast<unsigned char>(b[j]));\n\n\t\tif (a_is_digit && b_is_digit) {\n\t\t\t// Both are digits, compare as numbers\n\t\t\t// Skip leading zeros\n\t\t\twhile (i < a_size && a[i] == '0') {\n\t\t\t\t++i;\n\t\t\t}\n\t\t\twhile (j < b_size && b[j] == '0') {\n\t\t\t\t++j;\n\t\t\t}\n\n\t\t\t// Count the number of digits\n\t\t\tstd::size_t a_digit_start = i;\n\t\t\tstd::size_t b_digit_start = j;\n\t\t\twhile (i < a_size && std::isdigit(static_cast<unsigned char>(a[i]))) {\n\t\t\t\t++i;\n\t\t\t}\n\t\t\twhile (j < b_size && std::isdigit(static_cast<unsigned char>(b[j]))) {\n\t\t\t\t++j;\n\t\t\t}\n\n\t\t\tconst std::size_t a_digit_count = i - a_digit_start;\n\t\t\tconst std::size_t b_digit_count = j - b_digit_start;\n\n\t\t\t// Compare by length first (longer number is greater)\n\t\t\tif (a_digit_count != b_digit_count) {\n\t\t\t\treturn static_cast<int>(a_digit_count) - static_cast<int>(b_digit_count);\n\t\t\t}\n\n\t\t\t// Same length, compare digit by digit\n\t\t\tfor (std::size_t k = 0; k < a_digit_count; ++k) {\n\t\t\t\tif (a[a_digit_start + k] != b[b_digit_start + k]) {\n\t\t\t\t\treturn static_cast<int>(a[a_digit_start + k]) - static_cast<int>(b[b_digit_start + k]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t} else if (a_is_digit != b_is_digit) {\n\t\t\t// One is digit, one is not - digit comes before non-digit\n\t\t\treturn a_is_digit ? -1 : 1;\n\n\t\t} else {\n\t\t\t// Both are non-digits, compare as characters\n\t\t\tif (a[i] != b[j]) {\n\t\t\t\treturn static_cast<int>(static_cast<unsigned char>(a[i])) - static_cast<int>(static_cast<unsigned char>(b[j]));\n\t\t\t}\n\t\t\t++i;\n\t\t\t++j;\n\t\t}\n\t}\n\n\t// If one string is a prefix of the other, the shorter one comes first\n\treturn static_cast<int>(a_size) - static_cast<int>(b_size);\n}\n\n\n}  // ns\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/string_num.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_STRING_NUM_H\n#define HZ_STRING_NUM_H\n\n#include <string>\n#include <sstream>\n#include <iomanip>  // setbase, setprecision, setw\n#include <ios>  // std::fixed, std::internal\n#include <locale>  // std::locale\n#include <limits>  // std::numeric_limits\n#include <cstring>  // std::strncmp\n#include <exception>\n#include <type_traits>\n#include <algorithm>\n\n#include \"locale_tools.h\"\n\n\n/**\n\\file\nString to number and number to string conversions, with or without locale.\n*/\n\n\n\nnamespace hz {\n\n\n\t/// Check whether a string represents a numeric value.\n\t/// strict == true indicates that the whole string must represent a number exactly.\n\t/// strict == false allows the string to contain the number only in its beginning\n\t/// (ignores any leading spaces and trailing garbage).\n\t/// base has an effect only for integral types. For bool it specifies whether to\n\t/// accept \"true\" and \"false\" (as opposed to 1 and 0). For others, base should\n\t/// be between 2 and 36 inclusive.\n\t/// This function has no definition, only specializations.\n\t/// A version that operates in global locale.\n\ttemplate<typename T>\n\tbool string_is_numeric_locale(const std::string& s, T& number, bool strict, int base_or_boolalpha);\n\n\t/// A version that operates in global locale.\n\ttemplate<typename T>\n\tbool string_is_numeric_locale(const std::string& s, T& number, bool strict = true);\n\n\n\t/// A version that operates in classic locale.\n\ttemplate<typename T>\n\tbool string_is_numeric_nolocale(const std::string& s, T& number, bool strict, int base_or_boolalpha);\n\n\t/// A version that operates in classic locale.\n\ttemplate<typename T>\n\tbool string_is_numeric_nolocale(const std::string& s, T& number, bool strict = true);\n\n\n\t/// A convenience string_is_numeric wrapper.\n\t/// Note that in strict mode, T() is returned for invalid values.\n\t/// A version that operates in global locale.\n\ttemplate<typename T>\n\tT string_to_number_locale(const std::string& s, bool strict, int base_or_boolalpha);\n\n\t/// Short version with default base. (Needed because default base is different for bool and int).\n\t/// A version that operates in global locale.\n\ttemplate<typename T>\n\tT string_to_number_locale(const std::string& s, bool strict = true);\n\n\n\t/// A version that operates in classic locale.\n\ttemplate<typename T>\n\tT string_to_number_nolocale(const std::string& s, bool strict, int base_or_boolalpha);\n\n\t/// A version that operates in classic locale.\n\ttemplate<typename T>\n\tT string_to_number_nolocale(const std::string& s, bool strict = true);\n\n\n\n\t/// Convert numeric value to string. alpha_or_base_or_precision means:\n\t/// for bool, 0 means 1/0, 1 means true/false;\n\t/// for int family (including char), it's the base to format in (8, 10, 16 are definitely supported);\n\t/// for float family, it controls the number of digits after comma.\n\t/// A version that operates in global locale.\n\ttemplate<typename T>\n\tstd::string number_to_string_locale(T number, int alpha_or_base_or_precision, bool fixed_prec = false);\n\n\t/// Short version with default base / precision.\n\t/// A version that operates in global locale.\n\ttemplate<typename T>\n\tstd::string number_to_string_locale(T number);\n\n\n\t/// A version that operates in classic locale.\n\ttemplate<typename T>\n\tstd::string number_to_string_nolocale(T number, int alpha_or_base_or_precision, bool fixed_prec = false);\n\n\t/// A version that operates in classic locale.\n\ttemplate<typename T>\n\tstd::string number_to_string_nolocale(T number);\n\n\n}\n\n\n\n\n// ------------------------------------------- Implementation\n\n\n\nnamespace hz {\n\n\n\nnamespace internal {\n\n\n\t/// If a string starts with '-', stoul() and stoull() use unsigned wraparound rules\n\t/// for the return value, not returning \"out of range\". Detect this condition.\n\tinline bool string_starts_with_minus(const std::string& s, const std::locale& loc)\n\t{\n\t\tauto first_symbol = std::find_if(s.begin(), s.end(), [&](char c) {\n\t\t\treturn !std::isspace(c, loc);\n\t\t});\n\t\treturn first_symbol != s.end() && *first_symbol == '-';\n\t}\n\n\n\n\t// Version for integral / floating point types\n\ttemplate<typename T>\n\tbool string_is_numeric_impl_global_locale(const std::string& s, T& number, bool strict, [[maybe_unused]] int base, const std::locale& loc = std::locale())\n\t{\n\t\tstatic_assert(std::is_arithmetic_v<T>, \"Type T not convertible to a number\");\n\n\t\tif (s.empty() || (strict && std::isspace(s[0], loc)))  // sto* functions skip leading space\n\t\t\treturn false;\n\n\t\tstd::size_t num_read = 0;\n\t\tT value = T();\n\n\t\ttry {\n\t\t\tif constexpr(std::is_same_v<T, char>\n\t\t\t        || std::is_same_v<T, unsigned char>\n\t\t\t        || std::is_same_v<T, signed char>\n\t\t\t\t\t|| std::is_same_v<T, wchar_t>\n\t\t\t\t\t|| std::is_same_v<T, char16_t>\n\t\t\t\t\t|| std::is_same_v<T, char32_t>\n\t\t\t\t\t|| std::is_same_v<T, short>\n\t\t\t\t\t|| std::is_same_v<T, int>) {\n\t\t\t\tint tmp = std::stoi(s, &num_read, base);\n\t\t\t\tif (tmp != static_cast<T>(tmp)) {\n\t\t\t\t\treturn false;  // out of range\n\t\t\t\t}\n\t\t\t\tvalue = static_cast<T>(tmp);\n\t\t\t} else if constexpr(std::is_same_v<T, long>) {\n\t\t\t\tvalue = std::stol(s, &num_read, base);\n\t\t\t} else if constexpr(std::is_same_v<T, long long>) {\n\t\t\t\tvalue = std::stoll(s, &num_read, base);\n\t\t\t} else if constexpr(std::is_same_v<T, unsigned short>\n\t\t\t        || std::is_same_v<T, unsigned int>\n\t\t\t        || std::is_same_v<T, unsigned long>) {\n\t\t\t\tif (string_starts_with_minus(s, loc)) {\n\t\t\t\t\treturn false;  // \"out of range\" for unsigned\n\t\t\t\t}\n\t\t\t\tunsigned long tmp = std::stoul(s, &num_read, base);\n\t\t\t\tif (tmp != static_cast<T>(tmp)) {\n\t\t\t\t\treturn false;  // out of range for smaller type\n\t\t\t\t}\n\t\t\t\tvalue = static_cast<T>(tmp);\n\t\t\t} else if constexpr(std::is_same_v<T, unsigned long long>) {\n\t\t\t\tif (string_starts_with_minus(s, loc)) {\n\t\t\t\t\treturn false;  // \"out of range\" for unsigned\n\t\t\t\t}\n\t\t\t\tvalue = std::stoull(s, &num_read, base);\n\t\t\t} else if constexpr(std::is_same_v<T, float>) {\n\t\t\t\tvalue = std::stof(s, &num_read);\n\t\t\t} else if constexpr(std::is_same_v<T, double>) {\n\t\t\t\tvalue = std::stod(s, &num_read);\n\t\t\t} else if constexpr(std::is_same_v<T, long double>) {\n\t\t\t\tvalue = std::stold(s, &num_read);\n\t\t\t}\n\t\t}\n\t\tcatch (std::exception&) {  // std::invalid_argument, std::out_of_range\n\t\t\treturn false;\n\t\t}\n\n\t\t// If strict, check that everything was parsed. If not strict, check that\n\t\t// at least one character was parsed (not sure if this includes whitespace).\n\t\tif (strict ? (num_read == s.size()) : (num_read != 0)) {\n\t\t\tnumber = value;\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\n\n\t// Version for integral / floating point types\n\ttemplate<typename T>\n\tbool string_is_numeric_impl_classic_locale(const std::string& s, T& number, bool strict,  [[maybe_unused]] int base)\n\t{\n\t\t// TODO port to std::from_chars().\n\t\tScopedCLocale loc;\n\t\treturn string_is_numeric_impl_global_locale(s, number, strict, base, std::locale::classic());\n\t}\n\n\n\n\t// A version for bool\n\tinline bool string_is_numeric_impl_bool(const std::string& s, bool& number, bool strict, int boolalpha_enabled, bool use_classic_locale)\n\t{\n\t\tstd::locale loc;\n\t\tif (use_classic_locale) {\n\t\t\tloc = std::locale::classic();\n\t\t}\n\t\tif (s.empty() || (strict && std::isspace(s[0], loc)))  // ascii_strtoi() skips the leading spaces\n\t\t\treturn false;\n\t\tconst char* str = s.c_str();\n\n\t\tif (boolalpha_enabled != 0) {\n\t\t\t// skip spaces. won't do anything in strict mode (we already ruled out spaces there)\n\t\t\twhile (std::isspace(*str, loc)) {\n\t\t\t\t++str;\n\t\t\t}\n\t\t\t// contains \"true\" at start, or equals to \"true\" if strict.\n\t\t\tif (std::strncmp(str, \"true\", 4) == 0 && (!strict || str[4] == '\\0')) {  // str is 0-terminated, so no violation here\n\t\t\t\tnumber = true;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\t// same for \"false\"\n\t\t\tif (std::strncmp(str, \"false\", 5) == 0 && (!strict || str[5] == '\\0')) {\n\t\t\t\tnumber = false;\n\t\t\t\treturn true;\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\n\t\tint value = 0;\n\t\tbool status = false;\n\t\tif (use_classic_locale) {\n\t\t\tstatus = string_is_numeric_impl_classic_locale(s, value, strict, 10);\n\t\t} else {\n\t\t\tstatus = string_is_numeric_impl_global_locale(s, value, strict, 10);\n\t\t}\n\t\tif (!status) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (strict && value != 0 && value != 1) {\n\t\t\treturn false;\n\t\t}\n\n\t\tnumber = static_cast<bool>(value);\n\t\treturn true;\n\t}\n\n\n}\n\n\n\ntemplate<typename T>\nbool string_is_numeric_locale(const std::string& s, T& number, bool strict, int base_or_boolalpha)\n{\n\tif constexpr(std::is_same_v<T, bool>) {\n\t\treturn internal::string_is_numeric_impl_bool(s, number, strict, base_or_boolalpha, false);\n\t} else {\n\t\treturn internal::string_is_numeric_impl_global_locale(s, number, strict, base_or_boolalpha);\n\t}\n}\n\n\n\ntemplate<typename T>\nbool string_is_numeric_locale(const std::string& s, T& number, bool strict)\n{\n\tif constexpr(std::is_same_v<T, bool>) {\n\t\treturn internal::string_is_numeric_impl_bool(s, number, strict, 1, false);  // use boolalpha\n\t} else {\n\t\treturn internal::string_is_numeric_impl_global_locale(s, number, strict, 0);  // auto-detect base\n\t}\n}\n\n\n\ntemplate<typename T>\nbool string_is_numeric_nolocale(const std::string& s, T& number, bool strict, int base_or_boolalpha)\n{\n\tif constexpr(std::is_same_v<T, bool>) {\n\t\treturn internal::string_is_numeric_impl_bool(s, number, strict, base_or_boolalpha, true);\n\t} else {\n\t\treturn internal::string_is_numeric_impl_classic_locale(s, number, strict, base_or_boolalpha);\n\t}\n}\n\n\ntemplate<typename T>\nbool string_is_numeric_nolocale(const std::string& s, T& number, bool strict)\n{\n\tif constexpr(std::is_same_v<T, bool>) {\n\t\treturn internal::string_is_numeric_impl_bool(s, number, strict, 1, true);  // use boolalpha\n\t} else {\n\t\treturn internal::string_is_numeric_impl_classic_locale(s, number, strict, 0);  // auto-detect base\n\t}\n}\n\n\n\n\n// ------------------------------- string_to_number\n\n\ntemplate<typename T>\nT string_to_number_locale(const std::string& s, bool strict, int base_or_boolalpha)\n{\n\tT value = T();\n\tstring_is_numeric_locale(s, value, strict, base_or_boolalpha);\n\treturn value;\n}\n\n\n\ntemplate<typename T>\nT string_to_number_locale(const std::string& s, bool strict)\n{\n\tT value = T();\n\tstring_is_numeric_locale(s, value, strict);\n\treturn value;\n}\n\n\n\n\ntemplate<typename T>\nT string_to_number_nolocale(const std::string& s, bool strict, int base_or_boolalpha)\n{\n\tT value = T();\n\tstring_is_numeric_nolocale(s, value, strict, base_or_boolalpha);\n\treturn value;\n}\n\n\n\ntemplate<typename T>\nT string_to_number_nolocale(const std::string& s, bool strict)\n{\n\tT value = T();\n\tstring_is_numeric_nolocale(s, value, strict);\n\treturn value;\n}\n\n\n\n\n// ------------------------------- number_to_string\n\n\n\nnamespace internal {\n\n\tinline std::string number_to_string_impl_bool(bool number, int boolalpha_enabled)\n\t{\n\t\tif (boolalpha_enabled != 0)\n\t\t\treturn (number ? \"true\" : \"false\");\n\t\treturn (number ? \"1\" : \"0\");\n\t}\n\n\n\ttemplate<typename T>\n\tstd::string number_to_string_impl_integral(T number, int base, bool use_classic_locale)\n\t{\n\t\tif (number == 0) {\n\t\t\tif (base == 16) {\n\t\t\t\treturn \"0x\" + std::string(sizeof(T) * 2, '0');  // 0 doesn't print as 0x0000, but as 000000. fix that.\n\t\t\t}\n\t\t\tif (base == 8) {  // same here, 0 prints as 0.\n\t\t\t\treturn \"00\";  // better than simply 0 (at least it's clearly octal).\n\t\t\t}\n\t\t\t// base 10 can possibly have some funny formatting, so continue...\n\t\t}\n\n\t\tstd::ostringstream ss;\n\t\tif (use_classic_locale) {\n\t\t\tss.imbue(std::locale::classic());\n\t\t}\n\n\t\tif (base == 16) {\n\t\t\t// setfill & internal: leading 0s between 0x and XXXX.\n\t\t\t// setw: e.g. for int32, we need 4*2 (size * 2 chars for byte) + 2 (0x) width.\n\t\t\tss << std::setfill('0') << std::internal << std::setw(static_cast<int>((sizeof(T) * 2) + 2));\n\t\t}\n\n\t\tss << std::showbase << std::setbase(base);\n\n\t\tif constexpr(std::is_same_v<char, T> || std::is_same_v<signed char, T> || std::is_same_v<unsigned char, T>\n\t\t\t\t|| std::is_same_v<char16_t, T> || std::is_same_v<char32_t, T> || std::is_same_v<wchar_t, T>) {\n\t\t\tss << static_cast<int>(number);  // avoid printing them as characters\n\t\t} else {\n\t\t\tss << number;\n\t\t}\n\t\treturn ss.str();\n\t}\n\n\n\ttemplate<typename T>\n\tstd::string number_to_string_impl_floating(T number, int precision, bool fixed_prec, bool use_classic_locale)\n\t{\n\t\tstd::ostringstream ss;\n\t\tif (use_classic_locale) {\n\t\t\tss.imbue(std::locale::classic());\n\t\t}\n\n\t\t// without std::fixed, precision is counted as all digits, as opposed to only after comma.\n\t\tif (fixed_prec)\n\t\t\tss << std::fixed;\n\n\t\tss << std::setprecision(precision) << number;\n\t\treturn ss.str();\n\t}\n\n\n\n\ttemplate<typename T>\n\tstd::string number_to_string_impl(T number, int boolalpha_or_base_or_precision,\n\t\t\t[[maybe_unused]] bool fixed_prec,  [[maybe_unused]] bool use_classic_locale)\n\t{\n\t\tstatic_assert(std::is_arithmetic_v<T>, \"Type T not convertible to a number\");\n\n\t\tif constexpr(std::is_same_v<bool, T>) {\n\t\t\treturn internal::number_to_string_impl_bool(number, boolalpha_or_base_or_precision);\n\t\t}\n\t\tif constexpr(std::is_integral_v<T>) {\n\t\t\treturn internal::number_to_string_impl_integral(number, boolalpha_or_base_or_precision, use_classic_locale);\n\t\t}\n\t\tif constexpr(std::is_floating_point_v<T>) {\n\t\t\treturn internal::number_to_string_impl_floating(number, boolalpha_or_base_or_precision, fixed_prec, use_classic_locale);\n\t\t}\n\t\t// unreachable\n\t\treturn {};\n\t}\n\n\n}  // ns internal\n\n\n\n\ntemplate<typename T>\nstd::string number_to_string_locale(T number, int boolalpha_or_base_or_precision, bool fixed_prec)\n{\n\treturn internal::number_to_string_impl(number, boolalpha_or_base_or_precision, fixed_prec, false);\n}\n\n\ntemplate<typename T>\nstd::string number_to_string_locale(T number)\n{\n\tint base = 0;\n\tif constexpr(std::is_same_v<bool, T>) {\n\t\tbase = 1;  // alpha (true / false), as opposed to 1 / 0.\n\t} else if (std::is_integral_v<T>) {\n\t\tbase = 10;  // default base - 10\n\t} else if (std::is_floating_point_v<T>) {\n\t\tbase = std::numeric_limits<T>::digits10 + 1;  // precision. 1 is for sign\n\t}\n\t// don't use fixed prec here, digits10 is for the whole number\n\treturn number_to_string_locale(number, base, false);\n}\n\n\ntemplate<typename T>\nstd::string number_to_string_nolocale(T number, int boolalpha_or_base_or_precision, bool fixed_prec)\n{\n\treturn internal::number_to_string_impl(number, boolalpha_or_base_or_precision, fixed_prec, true);\n}\n\n\ntemplate<typename T>\nstd::string number_to_string_nolocale(T number)\n{\n\tint base = 0;\n\tif constexpr(std::is_same_v<bool, T>) {\n\t\tbase = 1;  // alpha (true / false), as opposed to 1 / 0.\n\t} else if constexpr(std::is_integral_v<T>) {\n\t\tbase = 10;  // default base - 10\n\t} else if constexpr(std::is_floating_point_v<T>) {\n\t\tbase = std::numeric_limits<T>::digits10 + 1;  // precision. 1 is for sign\n\t}\n\t// don't use fixed prec here, digits10 is for the whole number\n\treturn number_to_string_nolocale(number, base, false);\n}\n\n\n\n\n\n}  // ns\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/string_sprintf.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_STRING_SPRINTF_H\n#define HZ_STRING_SPRINTF_H\n\n\n// Define ENABLE_GLIB to 1 to enable glib string functions (more portable?).\n\n\n/// \\def HAVE_VASPRINTF\n/// Defined to 0 or 1. If 1, the compiler has vasprintf() (GNU/glibc extension).\n#ifndef HAVE_VASPRINTF\n#if defined _GNU_SOURCE\n\t\t#define HAVE_VASPRINTF 1\n\t#else\n\t\t#define HAVE_VASPRINTF 0\n\t#endif\n#endif\n\n\n#include <string>\n#include <cstdarg>  // for va_start, std::va_list and friends.\n#include <cstdio>\n#include <cstdlib>  // std::free\n\n#if defined ENABLE_GLIB && ENABLE_GLIB\n\t#include <glib.h>  // g_strdup_vprintf(), g_printf_string_upper_bound()\n#elif defined HAVE_VASPRINTF && HAVE_VASPRINTF\n\t#include <stdio.h>  // vasprintf()\n#endif\n\n#include \"system_specific.h\"\n\n\n/**\n\\file\n\\brief sprintf()-like formatting to std::string with automatic allocation.\n\n\\note These functions use system *printf family of functions.\n\n\\note If using mingw runtime >= 3.15 and __USE_MINGW_ANSI_STDIO,\nmingw supports both C99/POSIX and msvcrt format specifiers.\nThis includes proper printing of long double, %lld and %llu, etc.\nThis does _not_ affect _snprintf() and similar non-standard functions.\nNote that you may still get warnings from gcc regarding non-MS\nformat specifiers (see HZ_FUNC_PRINTF_CHECK).\n\n\n\\note There types are non-portable (the first one is MS variant, the second one is standard):\n%I64d, %lld (long long int),\n%I64u, %llu (unsigned long long int),\n%f, %Lf (long double; needs casting to double under non-ANSI mingw if using %f).\n*/\n\n\n\nnamespace hz {\n\n\n\n/// Get the buffer size required to safely allocate a buffer (including terminating 0) and call vsnprintf().\ninline int string_vsprintf_get_buffer_size(const char* format, std::va_list ap) HZ_FUNC_PRINTF_ISO_CHECK(1, 0);\n\n\n/// Same as string_vsprintf_get_buffer_size(), but using varargs.\ninline int string_sprintf_get_buffer_size(const char* format, ...) HZ_FUNC_PRINTF_ISO_CHECK(1, 2);\n\n\n/// Auto-allocating std::string-returning portable vsprintf\ninline std::string string_vsprintf(const char* format, std::va_list ap) HZ_FUNC_PRINTF_ISO_CHECK(1, 0);\n\n\n/// Same as string_vsprintf(), but using varargs\ninline std::string string_sprintf(const char* format, ...) HZ_FUNC_PRINTF_ISO_CHECK(1, 2);\n\n\n\n\n\n// ------------------------------------------- Implementation\n\n\n\ninline int string_vsprintf_get_buffer_size(const char* format, std::va_list ap)\n{\n#if defined ENABLE_GLIB && ENABLE_GLIB\n\t// the glib version returns gsize\n\treturn static_cast<int>(g_printf_string_upper_bound(format, ap));\n\n#else\n\t// In C++11, vsnprintf() returns the number of bytes it could have written\n\t// (without \\0) if buffer was large enough.\n\t// This includes mingw/ISO.\n\tconst int size = std::vsnprintf(nullptr, 0, format, ap);  // C99 and others\n\tif (size < 0)\n\t\treturn -1;\n\treturn size + 1;  // that +1 is for \\0.\n#endif\n}\n\n\n\ninline int string_sprintf_get_buffer_size(const char* format, ...)\n{\n\tstd::va_list ap;\n\tva_start(ap, format);\n\tint ret = string_vsprintf_get_buffer_size(format, ap);\n\tva_end(ap);\n\treturn ret;\n}\n\n\n\n\ninline std::string string_vsprintf(const char* format, std::va_list ap)\n{\n#if defined ENABLE_GLIB && ENABLE_GLIB\n\t// there's also g_vasprintf(), but only since glib 2.4.\n\tgchar* buf = g_strdup_vprintf(format, ap);\n\tstd::string ret = (buf ? buf : \"\");\n\tif (buf)\n\t\tg_free(buf);\n\n#elif defined HAVE_VASPRINTF && HAVE_VASPRINTF\n\tstd::string ret;\n\tchar* str = 0;\n\tif (vasprintf(&str, format, ap) > 0)  // GNU extension\n\t\tret = (str ? str : \"\");\n\tstd::free(str);\n\n#else\n\n\tstd::string ret;\n\tint size = string_vsprintf_get_buffer_size(format, ap);\n\tif (size > 0) {\n\t\tchar* buf = new char[size];\n\t\tint written = 0;\n\n\t\twritten = std::vsnprintf(buf, size, format, ap);  // writes size chars, including 0.\n\n\t\tif (written > 0)\n\t\t\tret = buf;\n\t\tdelete[] buf;\n\t}\n\n#endif\n\treturn ret;\n}\n\n\n\ninline std::string string_sprintf(const char* format, ...)\n{\n\tstd::va_list ap;\n\tva_start(ap, format);\n\tstd::string ret = string_vsprintf(format, ap);\n\tva_end(ap);\n\treturn ret;\n}\n\n\n\n\n\n\n}  // ns\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/system_specific.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_SYSTEM_SPECIFIC_H\n#define HZ_SYSTEM_SPECIFIC_H\n\n/**\n\\file\nSystem/compiler-specific functionality.\n*/\n\n\n/// \\def HAVE_GCC_CXX_ABI\n/// Defined to 0 or 1. If 1, compiler supports C++ ABI library.\n\n\n// Check for C++ abi library availability (gcc / clang / intel+linux / apple-clang)\n#if __has_include(<cxxabi.h>)\n\t#define HAVE_GCC_CXX_ABI 1\n#endif\n\n\n/// \\def HZ_GCC_CHECK_VERSION(major, minor, micro)\n/// returns true if gcc version is greater or equal to specified.\n#ifndef HZ_GCC_CHECK_VERSION\n\t#define HZ_GCC_CHECK_VERSION(major, minor, micro) \\\n\t\t\t( defined (__GNUC__) && ( \\\n\t\t\t\t( (__GNUC__) > (major) ) \\\n\t\t\t\t|| ( ((__GNUC__) == (major)) && ((__GNUC_MINOR__) > (minor)) ) \\\n\t\t\t\t|| ( ((__GNUC__) == (major)) && ((__GNUC_MINOR__) == (minor)) && ((__GNUC_PATCHLEVEL__) >= (micro)) ) \\\n\t\t\t) )\n#endif\n\n\n\n/// \\def HZ_GCC_ATTR(a)\n/// Wrap gcc's attributes. Evaluates to nothing in non-gcc-compatible compilers.\n#ifndef HZ_GCC_ATTR\n\t#ifdef __GNUC__\n\t\t#define HZ_GCC_ATTR(a) __attribute__((a))\n\t#else\n\t\t#define HZ_GCC_ATTR(a)\n\t#endif\n#endif\n\n\n/// \\def HZ_FUNC_PRINTF_ISO_CHECK(format_idx, check_idx)\n/// Easy compile-time printf format checks.\n/// See http://gcc.gnu.org/onlinedocs/gcc-4.4.1/gcc/Function-Attributes.html\n\n/// \\def HZ_FUNC_PRINTF_MS_CHECK(format_idx, check_idx)\n/// Easy compile-time printf format checks (windows version of format specifiers).\n/// ms_printf is available since gcc 4.4.\n/// Note: When using __USE_MINGW_ANSI_STDIO, mingw uses\n/// its own *printf() implementation (rather than msvcrt), which accepts\n/// both MS-style and standard format specifiers.\n/// ms_printf gives warnings on standard specifiers, but we still keep\n/// it so that the code will be portable to other win32 environments.\n/// TODO: Check if simply specifying \"printf\" selects the correct\n/// version for mingw.\n\n\n#ifndef HZ_FUNC_PRINTF_ISO_CHECK\n\t#define HZ_FUNC_PRINTF_ISO_CHECK(format_idx, check_idx) HZ_GCC_ATTR(format(printf, format_idx, check_idx))\n#endif\n\n\n\n#include <string>\n\n/**\n\\fn std::string hz::type_name_demangle(const std::string& name)\nDemangle a C/C++ type name, as returned by std::type_info.name().\nSimilar to c++filt command. Supported under gcc only for now.\n*/\n\n/**\n\\fn std::type_info* hz::get_current_exception_type()\nReturns the type_info for the currently handled exception, or null if there is none or unsupported.\n*/\n\n\n#if defined HAVE_GCC_CXX_ABI\n\t#include <string>\n\t#include <cstdlib>  // std::free().\n\t#include <cxxabi.h>  // ::abi::*\n\n\nnamespace hz {\n\n\tinline std::string type_name_demangle(const std::string& name)\n\t{\n\t\tint status = 0;\n\t\tchar* demangled = ::abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status);\n\t\tstd::string ret;\n\t\tif (demangled) {\n\t\t\tif (status == 0)\n\t\t\t\tret = demangled;\n\t\t\tstd::free(demangled);\n\t\t}\n\t\treturn ret;\n\t}\n\n\n\t[[nodiscard]] inline std::type_info* get_current_exception_type()\n\t{\n\t\treturn ::abi::__cxa_current_exception_type();\n\t}\n\n\n}  // ns hz\n\n\n#else  // non-gcc-compatible\n\nnamespace hz {\n\n\tinline std::string type_name_demangle(const std::string& name)\n\t{\n\t\treturn name;  // can't do anything here\n\t}\n\n\n\t[[nodiscard]] inline std::type_info* get_current_exception_type()\n\t{\n\t\treturn nullptr;\n\t}\n\n\n}  // ns hz\n\n\n#endif\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/hz/tests/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2022 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nif (NOT APP_BUILD_TESTS)\n    set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL true)\nelse()\n    set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL false)\nendif()\n\n\n# Use Object libraries to allow runtime test discovery\nadd_library(hz_tests OBJECT)\ntarget_sources(hz_tests PRIVATE\n\ttest_format_unit.cpp\n\ttest_string_algo.cpp\n\ttest_string_num.cpp\n)\ntarget_link_libraries(hz_tests PRIVATE\n\thz\n\tCatch2\n)\n\n"
  },
  {
    "path": "src/hz/tests/test_format_unit.cpp",
    "content": "/******************************************************************************\nLicense: BSD Zero Clause License\nCopyright:\n\t(C) 2008 - 2022 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz_tests\n/// \\weakgroup hz_tests\n/// @{\n\n#include \"catch2/catch.hpp\"\n\n// disable libdebug, we don't link to it\n#undef HZ_USE_LIBDEBUG\n#define HZ_USE_LIBDEBUG 0\n// enable libdebug emulation via std::cerr\n#undef HZ_EMULATE_LIBDEBUG\n#define HZ_EMULATE_LIBDEBUG 1\n\n// The first header should be then one we're testing, to avoid missing\n// header pitfalls.\n#include \"hz/format_unit.h\"\n\n#include <cstdint>\n\n\n\nTEST_CASE(\"FormatUnitSize\", \"[hz][formatting]\")\n{\n\tREQUIRE(hz::format_size(3LL * 1024 * 1024) == \"3.00 MiB\");\n\tREQUIRE(hz::format_size(4LL * 1000 * 1000, true, true) == \"4.00 Mbit\");\n\tREQUIRE(hz::format_size(100LL * 1000 * 1000 * 1000) == \"93.13 GiB\");  // aka how the hard disk manufactures screw you\n\tREQUIRE(hz::format_size(100LL * 1024 * 1024, true) == \"104.86 MB\");  // 100 MiB in decimal MB\n\n\tREQUIRE(hz::format_size(5) == \"5 B\");\n\tREQUIRE(hz::format_size(6, true, true) == \"6 bit\");\n\tREQUIRE(hz::format_size(uint64_t(2.5 * 1024)) == \"2.50 KiB\");\n\tREQUIRE(hz::format_size(uint64_t(2.5 * 1024 * 1024)) == \"2.50 MiB\");\n\tREQUIRE(hz::format_size(uint64_t(2.5 * 1024 * 1024 * 1024)) == \"2.50 GiB\");\n\tREQUIRE(hz::format_size(uint64_t(2.5 * 1024 * 1024 * 1024 * 1024)) == \"2.50 TiB\");\n\tREQUIRE(hz::format_size(uint64_t(2.5 * 1024 * 1024 * 1024 * 1024 * 1024)) == \"2.50 PiB\");\n\tREQUIRE(hz::format_size(uint64_t(2.5 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024)) == \"2.50 EiB\");\n\n\t// Common size of 1 TiB hdd in decimal\n\tREQUIRE(hz::format_size(uint64_t(1000204886016ULL), true) == \"1.00 TB\");\n}\n\n\n\nTEST_CASE(\"FormatUnitTimeLength\", \"[hz][formatting]\")\n{\n\tusing namespace std::literals;\n\tusing days = std::chrono::duration<int, std::ratio_multiply<std::ratio<24>, std::chrono::hours::period>>;\n\n\tREQUIRE(hz::format_time_length(5s) == \"5 sec\");\n\tREQUIRE(hz::format_time_length(90s) == \"90 sec\");\n\tREQUIRE(hz::format_time_length(5min + 30s) == \"6 min\");  // rounded to the nearest minute\n\tREQUIRE(hz::format_time_length(130min) == \"2 h 10 min\");\n\tREQUIRE(hz::format_time_length(5h + 30min) == \"5 h 30 min\");\n\tREQUIRE(hz::format_time_length(10h + 40min) == \"11 h\");  // rounded to nearest hour\n\tREQUIRE(hz::format_time_length(24h + 20min) == \"24 h\");  // rounded to nearest hour\n\tREQUIRE(hz::format_time_length(130h + 30min) == \"5 d 11 h\");\n\tREQUIRE(hz::format_time_length(days(5) + 15h + 30min) == \"5 d 16 h\");  // rounded to nearest hour\n\tREQUIRE(hz::format_time_length(days(20) - 8h) == \"20 d\");  // rounded to nearest day\n}\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/hz/tests/test_string_algo.cpp",
    "content": "/******************************************************************************\nLicense: BSD Zero Clause License\nCopyright:\n\t(C) 2008 - 2022 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz_tests\n/// \\weakgroup hz_tests\n/// @{\n\n#include \"catch2/catch.hpp\"\n\n// disable libdebug, we don't link to it\n#undef HZ_USE_LIBDEBUG\n#define HZ_USE_LIBDEBUG 0\n// enable libdebug emulation through std::cerr\n#undef HZ_EMULATE_LIBDEBUG\n#define HZ_EMULATE_LIBDEBUG 1\n\n// The first header should be then one we're testing, to avoid missing\n// header pitfalls.\n#include \"hz/string_algo.h\"\n\n#include <vector>\n\n\n\n/// Main function for the test\nTEST_CASE(\"StringAlgorithms\", \"[hz][string]\")\n{\n\tusing namespace hz;\n\n\tSECTION(\"string_split, single-character\") {\n\t\tstd::string s = \"/aa/bbb/ccccc//dsada//\";\n\t\tstd::vector<std::string> result;\n\t\tstring_split(s, '/', result, false);\n\n\t\tstd::vector<std::string> expected = {\n\t\t\t\"\",\n\t\t\t\"aa\",\n\t\t\t\"bbb\",\n\t\t\t\"ccccc\",\n\t\t\t\"\",\n\t\t\t\"dsada\",\n\t\t\t\"\",\n\t\t\t\"\"\n\t\t};\n\t\tREQUIRE(result == expected);\n\t}\n\n\tSECTION(\"string_split, single-character, skip empty\") {\n\t\tstd::string s = \"/aa/bbb/ccccc//dsada//\";\n\t\tstd::vector<std::string> result;\n\t\tstring_split(s, '/', result, true);\n\n\t\tREQUIRE(result == std::vector<std::string> {\n\t\t\t\"aa\",\n\t\t\t\"bbb\",\n\t\t\t\"ccccc\",\n\t\t\t\"dsada\",\n\t\t});\n\t}\n\n\tSECTION(\"string_split, multi-character\") {\n\t\tstd::string s = \"//aa////bbb/ccccc//dsada////\";\n\t\tstd::vector<std::string> result;\n\t\tstring_split(s, \"//\", result, false);\n\n\t\tREQUIRE(result == std::vector<std::string> {\n\t\t\t\"\",\n\t\t\t\"aa\",\n\t\t\t\"\",\n\t\t\t\"bbb/ccccc\",\n\t\t\t\"dsada\",\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t});\n\t}\n\n\tSECTION(\"string_remove_adjacent_duplicates\") {\n\t\tstd::string s = \"  a b bb  c     d   \";\n\t\tREQUIRE(string_remove_adjacent_duplicates_copy(s, ' ') == \" a b bb c d \");\n\t\tREQUIRE(string_remove_adjacent_duplicates_copy(s, ' ', 2) == \"  a b bb  c  d  \");\n\t}\n\n\tSECTION(\"string_replace single-character\") {\n\t\tstd::string s = \"/a/b/c/dd//e/\";\n\t\tstring_replace(s, '/', ':');\n\t\tREQUIRE(s == \":a:b:c:dd::e:\");\n\t}\n\n\tSECTION(\"string_replace multi-character\") {\n\t\tstd::string s = \"112/2123412\";\n\t\tstring_replace(s, \"12\", \"AB\");\n\t\tREQUIRE(s == \"1AB/2AB34AB\");\n\t}\n\n\tSECTION(\"string_replace_array multi -> multi\") {\n\t\tstd::vector<std::string> from, to;\n\n\t\tfrom.emplace_back(\"12\");\n\t\tfrom.emplace_back(\"abc\");\n\n\t\tto.emplace_back(\"345\");\n\t\tto.emplace_back(\"de\");\n\n\t\tstd::string s = \"12345678abcdefg abc ab\";\n\t\tstring_replace_array(s, from, to);\n\t\tREQUIRE(s == \"345345678dedefg de ab\");\n\t}\n\n\tSECTION(\"string_replace_array multi -> single\") {\n\t\tstd::vector<std::string> from, to;\n\n\t\tfrom.emplace_back(\"12\");\n\t\tfrom.emplace_back(\"abc\");\n\n\t\tstd::string s = \"12345678abcdefg abc ab\";\n\t\tstring_replace_array(s, from, \":\");\n\t\tREQUIRE(s == \":345678:defg : ab\");\n\t}\n\n\tSECTION(\"string_natural_compare\") {\n\t\tusing namespace hz;\n\n\t\t// Test basic number comparison\n\t\tREQUIRE(string_natural_compare(\"file1.txt\", \"file2.txt\") < 0);\n\t\tREQUIRE(string_natural_compare(\"file2.txt\", \"file10.txt\") < 0);\n\t\tREQUIRE(string_natural_compare(\"file10.txt\", \"file2.txt\") > 0);\n\t\tREQUIRE(string_natural_compare(\"file9.txt\", \"file10.txt\") < 0);\n\n\t\t// Test device names (the actual use case)\n\t\tREQUIRE(string_natural_compare(\"pd0\", \"pd1\") < 0);\n\t\tREQUIRE(string_natural_compare(\"pd1\", \"pd2\") < 0);\n\t\tREQUIRE(string_natural_compare(\"pd2\", \"pd10\") < 0);\n\t\tREQUIRE(string_natural_compare(\"pd9\", \"pd10\") < 0);\n\t\tREQUIRE(string_natural_compare(\"pd10\", \"pd11\") < 0);\n\t\tREQUIRE(string_natural_compare(\"pd10\", \"pd9\") > 0);\n\n\t\t// Test equality\n\t\tREQUIRE(string_natural_compare(\"pd5\", \"pd5\") == 0);\n\t\tREQUIRE(string_natural_compare(\"test\", \"test\") == 0);\n\n\t\t// Test prefix\n\t\tREQUIRE(string_natural_compare(\"pd\", \"pd1\") < 0);\n\t\tREQUIRE(string_natural_compare(\"pd1\", \"pd\") > 0);\n\n\t\t// Test mixed content\n\t\tREQUIRE(string_natural_compare(\"a1b2c3\", \"a1b2c10\") < 0);\n\t\tREQUIRE(string_natural_compare(\"a10b2\", \"a2b10\") > 0);\n\n\t\t// Test non-numeric strings\n\t\tREQUIRE(string_natural_compare(\"abc\", \"def\") < 0);\n\t\tREQUIRE(string_natural_compare(\"xyz\", \"abc\") > 0);\n\n\t\t// Test numbers vs letters (digits come before non-digits)\n\t\tREQUIRE(string_natural_compare(\"1test\", \"atest\") < 0);\n\t\tREQUIRE(string_natural_compare(\"test1\", \"testa\") < 0);\n\n\t\t// Test long digit sequences (well beyond a few digits)\n\t\tREQUIRE(string_natural_compare(\"file12345678901234567890.txt\", \"file12345678901234567891.txt\") < 0);\n\t\tREQUIRE(string_natural_compare(\"file12345678901234567891.txt\", \"file12345678901234567890.txt\") > 0);\n\t\tREQUIRE(string_natural_compare(\"file12345678901234567890.txt\", \"file12345678901234567890.txt\") == 0);\n\n\t\t// Compare short vs very long numeric substrings\n\t\tREQUIRE(string_natural_compare(\"file1.txt\", \"file12345678901234567890.txt\") < 0);\n\t\tREQUIRE(string_natural_compare(\"file12345678901234567890.txt\", \"file99999999999.txt\") > 0);\n\n\t\t// Mixed with long numbers inside other text\n\t\tREQUIRE(string_natural_compare(\"a1b2c123456789012345\", \"a1b2c123456789012346\") < 0);\n\t\tREQUIRE(string_natural_compare(\"a1b2c123456789012345\", \"a1b2c123456789012345\") == 0);\n\t}\n\n}\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/hz/tests/test_string_num.cpp",
    "content": "/******************************************************************************\nLicense: BSD Zero Clause License\nCopyright:\n\t(C) 2008 - 2022 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz_tests\n/// \\weakgroup hz_tests\n/// @{\n\n#include \"catch2/catch.hpp\"\n\n// disable libdebug, we don't link to it\n#undef HZ_USE_LIBDEBUG\n#define HZ_USE_LIBDEBUG 0\n// enable libdebug emulation through std::cerr\n#undef HZ_EMULATE_LIBDEBUG\n#define HZ_EMULATE_LIBDEBUG 1\n\n// The first header should be then one we're testing, to avoid missing\n// header pitfalls.\n#include \"hz/string_num.h\"\n\n#include <limits>\n#include <cstdint>\n\n#include \"hz/main_tools.h\"\n\n\n\nTEST_CASE(\"NumericStrings\", \"[hz][parser]\")\n{\n\tusing namespace hz;\n\n\t{\n\t\tint i = 10;\n\t\tREQUIRE(string_is_numeric_nolocale<int>(\"-1\", i) == true);\n\t\tREQUIRE(i == -1);\n\t}\n\t{\n\t\tint i = 10;\n\t\tREQUIRE(string_is_numeric_nolocale<int>(\"-1.3\", i) == false);\n\t\tREQUIRE(i == 10);\n\t}\n\t{\n\t\tint16_t int16_ = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"32768\", int16_) == false);  // overflow, false\n\t\tREQUIRE(int16_ == 10);\n\n\t\tuint16_t uint16_ = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"65535\", uint16_) == true);\n\t\tREQUIRE(uint16_ == 65535);\n\n\t\tint32_t int32_ = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\" 1.33\", int32_) == false);  // strict, false\n\t\tREQUIRE(int32_ == 10);\n\n\t\tint32_t int32_2 = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"1.33\", int32_2) == false);  // strict, false\n\t\tREQUIRE(int32_2 == 10);\n\n\t\tuint32_t uint32_ = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"-1\", uint32_) == false);  // underflow, false\n\t\tREQUIRE(uint32_ == 10);\n\n\t\tint64_t int64_ = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"-1\", int64_) == true);\n\t\tREQUIRE(int64_ == -1);\n\n\t\tuint64_t uint64_ = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"1\", uint64_) == true);\n\t\tREQUIRE(uint64_ == 1);\n\n\t\tuint64_t uint64_2 = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"-1\", uint64_2) == false);  // underflow\n\t\tREQUIRE(uint64_2 == 10);\n\n\t\tuint64_t uint64_3 = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\" 1\", uint64_3) == false);  // strict\n\t\tREQUIRE(uint64_3 == 10);\n\n\t\tuint64_t uint64_4 = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\" 1\", uint64_4, false) == true);  // ignore space\n\t\tREQUIRE(uint64_4 == 1);\n\n\t\tint int_ = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"-1\", int_) == true);\n\t\tREQUIRE(int_ == -1);\n\n\t\tunsigned int uint_ = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"-1\", uint_) == false);  // underflow, false\n\t\tREQUIRE(uint_ == 10);\n\n\t\tlong long_int_ = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"-1\", long_int_) == true);\n\t\tREQUIRE(long_int_ == -1);\n\n\t\tunsigned long long_uint_ = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"-1\", long_uint_) == false);  // underflow, false\n\t\tREQUIRE(long_uint_ == 10);\n\n\t\tchar char_ = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"4\", char_) == true);\n\t\tREQUIRE(char_ == 4);\n\n\t\tunsigned char uchar_ = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"315\", uchar_) == false);  // overflow, false\n\t\tREQUIRE(uchar_ == 10);\n\n\t\tunsigned char uchar_2 = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"128\", uchar_2) == true);\n\t\tREQUIRE(uchar_2 == 128);\n\n\t\tunsigned char uchar_3 = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"-2\", uchar_3) == false);  // underflow\n\t\tREQUIRE(uchar_3 == 10);\n\n\t\tsigned char schar_2 = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"128\", schar_2) == false);  // overflow, false\n\t\tREQUIRE(schar_2 == 10);\n\n\t\tsigned char schar_3 = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"-128\", schar_3) == true);\n\t\tREQUIRE(schar_3 == -128);\n\n\t\tbool b = false;\n\t\tREQUIRE(string_is_numeric_nolocale(\"true\", b) == true);\n\t\tREQUIRE(b == true);\n\t}\n\t{\n\t\tconst double eps = std::numeric_limits<double>::epsilon();  // it will do\n\n\t\tdouble d = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"-1.3\", d) == true);\n\t\tREQUIRE(std::abs(-1.3 - d) <= eps);\n\n\t\td = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\" inf\", d) == false);\n\t\tREQUIRE(std::abs(10. - d) <= eps);\n\n\t\td = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\" inf\", d, false) == true);  // non-strict\n\t\tREQUIRE(std::isinf(d));\n\n\t\td = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"infinity\", d) == true);\n\t\tREQUIRE(std::isinf(d));\n\n\t\td = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"NAn\", d) == true);\n\t\tREQUIRE(std::isnan(d));\n\n\t\td = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"3.e+4\", d) == true);\n\t\tREQUIRE(std::abs(3e4 - d) <= eps);\n\n\t\td = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"-3E4\", d) == true);\n\t\tREQUIRE(std::abs(-3e4 - d) <= eps);\n\n\t\td = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"123 \", d) == false);  // strict\n\t\tREQUIRE(std::abs(10 - d) <= eps);\n\n\t\td = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"123 \", d, false) == true);  // non-strict\n\t\tREQUIRE(std::abs(123 - d) <= eps);\n\n\t\td = 10;\n\t\tREQUIRE(string_is_numeric_nolocale(\"e+3\", d) == false);\n\t\tREQUIRE(std::abs(10 - d) <= eps);\n\t}\n\t{\n\n\t\t// Note: suffixes are case-insensitive.\n\t\t// Long int and long double are differentiated by '.' in constant.\n\n\t\tREQUIRE(number_to_string_nolocale(true) == \"true\");  // bool\n\t\tREQUIRE(number_to_string_nolocale('a') == \"97\");  // char\n\t\tREQUIRE(number_to_string_nolocale(1.L) == \"1\");  // long double\n\t\tREQUIRE(number_to_string_nolocale(2L) == \"2\");  // long int\n\t\tREQUIRE(number_to_string_nolocale(6) == \"6\");  // int\n\n\t\tREQUIRE(number_to_string_nolocale(3LL) == \"3\");  // long long int\n\t\tREQUIRE(number_to_string_nolocale(4ULL) == \"4\");  // unsigned long long int\n\n\t\tREQUIRE(number_to_string_nolocale(5.F) == \"5\");  // float\n\t\tREQUIRE(number_to_string_nolocale(1.33) == \"1.33\");  // double\n\t\tREQUIRE(number_to_string_nolocale(2.123456789123456789123456789L).starts_with(\"2.12345678912345\") == true);\n\n\t\tREQUIRE(number_to_string_nolocale(std::numeric_limits<double>::quiet_NaN()) == \"nan\");\n\t\tREQUIRE(number_to_string_nolocale(std::numeric_limits<float>::signaling_NaN()) == \"nan\");\n\t\tREQUIRE(number_to_string_nolocale(std::numeric_limits<long double>::infinity()) == \"inf\");\n\t\tREQUIRE(number_to_string_nolocale(-std::numeric_limits<long double>::infinity()) == \"-inf\");\n\n\t\tREQUIRE(number_to_string_nolocale(uint16_t(0x1133), 16) == \"0x1133\");\n\t\tREQUIRE(number_to_string_nolocale(uint32_t(0x00001234), 16) == \"0x00001234\");\n\t\tREQUIRE(number_to_string_nolocale(uint64_t(0x00001234), 16) == \"0x0000000000001234\");\n\t\tREQUIRE(number_to_string_nolocale(uint16_t(0), 16) == \"0x0000\");\n\n\t\tREQUIRE(number_to_string_nolocale(uint16_t(0xffff), 8) == \"0177777\");\n\t\tREQUIRE(number_to_string_nolocale(uint16_t(0), 8) == \"00\");\n\n\t\tREQUIRE(number_to_string_nolocale(uint16_t(0xffff), 2) == \"65535\");\n\t\tREQUIRE(number_to_string_nolocale(uint16_t(0), 2) == \"0\");\n\n\t}\n}\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/hz/win32_tools.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup hz\n/// \\weakgroup hz\n/// @{\n\n#ifndef HZ_WIN32_TOOLS_H\n#define HZ_WIN32_TOOLS_H\n\n#ifdef _WIN32  // Guards this header completely\n\n#include <windows.h>  // lots of stuff\n#include <shlobj.h>  // SH*\n#include <io.h>  // _open_osfhandle, _waccess\n#include <fcntl.h>  // _O_TEXT\n\n#include <string>\n#include <vector>\n#include <array>\n#include <cctype>  // for ctype.h\n#include <ctype.h>  // towupper\n#include <cstdio>  // for stdio.h, std::f*, std::setvbuf, std::fprintf, std::FILE\n#include <stdio.h>  // _fdopen, _wfopen, _wremove\n#include <cstdlib>  // std::atexit\n#include <cstring>  // std::strlen\n#include <ios>  // std::ios::sync_with_stdio\n#include <filesystem>\n\n#if defined __MINGW32__\n\t#include <_mingw.h>  // MINGW_HAS_SECURE_API\n#endif\n\n\n\n/**\n\\file\n\\brief Win32-specific API functions.\n\\note The public API works with UTF-8 strings, unless noted otherwise.\n*/\n\n\n\nnamespace hz {\n\n\n/// Get a list of drives available for the user. The drives are in\n/// \"C:\\\" format (uppercase, utf-8).\ntemplate<class Container> inline\nbool win32_get_drive_list(Container& put_here);\n\n\n/// Get windows \"special\" folder by CSIDL (CSIDL_APPDATA, for example).\n/// See http://msdn.microsoft.com/en-us/library/bb762494(VS.85).aspx\n/// for details.\ninline std::string win32_get_special_folder(int csidl, bool auto_create = true);\n\n\n/// Usually C:\\\\Windows or something like that.\ninline std::string win32_get_windows_directory();\n\n\n/// Get registry value as a string.\n/// Base may be e.g. HKEY_CURRENT_USER.\n/// Note that this works only with REG_SZ (string) types,\n/// returning their value as utf-8. False is returned for all other types.\ninline bool win32_get_registry_value_string(HKEY base,\n\t\tconst std::string& keydir, const std::string& key, std::string& put_here);\n\n\n/// Set registry value as a string.\n/// Note that this sets the type as REG_SZ (string). The accepted utf-8\n/// value is converted to utf-16 for storage.\ninline bool win32_set_registry_value_string(HKEY base,\n\t\tconst std::string& keydir, const std::string& key, const std::string& value);\n\n\n/// Get registry value as a DWORD.\n/// Base may be e.g. HKEY_CURRENT_USER.\n/// Note that this works only with REG_DWORD types.\n/// False is returned for all other types.\ninline bool win32_get_registry_value_dword(HKEY base,\n\t\tconst std::string& keydir, const std::string& key, DWORD& put_here);\n\n\n/// Redirect stdout and stderr to console window (if open). Requires winxp (at compile-time).\n/// \\param create_if_none if true, create a new console if none was found and attach to it.\n/// \\return false if failed or unsupported.\ninline bool win32_redirect_stdio_to_console(bool create_if_none = false);\n\n\n/// Redirect stdout and stderr to console window (if open). Requires winxp (at compile-time).\n/// \\param create_if_none if true, create a new console if none was found and attach to it.\n/// \\param console_created Set to true if a new console was created due to \\c create_if_none.\n/// \\return false if failed or unsupported.\ninline bool win32_redirect_stdio_to_console(bool create_if_none, bool& console_created);\n\n\n/// Redirect stdout to stdout.txt and stderr to stderr.txt.\n/// Call once.\n/// \\return true on success, false on failure.\ninline bool win32_redirect_stdio_to_files(const std::string& stdout_file = \"\", const std::string& stderr_file = \"\");\n\n\n\n\n/// Convert a string in user-specified encoding to utf-16 string (00-terminated array of wchar_t).\n/// wchar_t is 2 bytes on windows, thus holding utf-16 code unit.\ninline std::wstring win32_user_to_utf16(UINT from_cp, std::string_view from_str, bool* ok = nullptr);\n\n\n/// Convert utf-16 string (represented as 0-terminated array of wchar_t, where\n/// wchar_t is 2 bytes (on windows)) to a string in user-specified encoding.\ninline std::string win32_utf16_to_user(UINT to_cp, std::wstring_view utf16_str, bool* ok = nullptr);\n\n\n/// Convert utf-8 string to utf-16 string. See win32_user_to_utf16() for details.\ninline std::wstring win32_utf8_to_utf16(std::string_view utf8_str, bool* ok = nullptr);\n\n\n\n/// Convert utf-16 string to utf-8 string. See win32_utf16_to_user() for details.\ninline std::string win32_utf16_to_utf8(std::wstring_view utf16_str, bool* ok = nullptr);\n\n/// Convert utf-16 string to utf-8 string. See win32_utf16_to_user() for details.\ntemplate<std::size_t ArraySize>\nstd::string win32_utf16_to_utf8(const std::array<wchar_t, ArraySize>& utf16_str, bool* ok = nullptr);\n\n\n\n/// Convert current locale-encoded string to utf-16 string. See win32_user_to_utf16() for details.\n/// Use CP_ACP is system default windows ANSI codepage.\n/// Use CP_THREAD_ACP is for current thread. Think before you choose. :)\ninline std::wstring win32_locale_to_utf16(std::string_view loc_str, bool use_thread_locale = false, bool* ok = nullptr);\n\n\n/// Convert utf-16 string to locale-encoded string. See win32_utf16_to_user() for details.\ninline std::string win32_utf16_to_locale(std::wstring_view utf16_str, bool use_thread_locale = false, bool* ok = nullptr);\n\n\n/// Convert current locale-encoded string to utf-8 string.\ninline std::string win32_locale_to_utf8(std::string_view loc_str, bool use_thread_locale = false, bool* ok = nullptr);\n\n\n/// Convert utf-8 string to locale-encoded string. See win32_utf8_to_user() for details.\ninline std::string win32_utf8_to_locale(std::string_view utf8_str, bool use_thread_locale = false, bool* ok = nullptr);\n\n\n\n\n\n\n// ------------------------------------------- Implementation\n\n\n\n\n// Get a list of drives available for the user. The drives are in\n// \"C:\\\" format (uppercase, utf-8).\ntemplate<class Container> inline\nbool win32_get_drive_list(Container& put_here)\n{\n\tDWORD buf_size = GetLogicalDriveStringsW(0, nullptr);  // find out the required buffer size\n\tif (buf_size == 0)  // error\n\t\treturn false;\n\n\tstd::vector<wchar_t> buf(buf_size);\n\n\t// Returns a consecutive array of 00-terminated strings (each 00-terminated).\n\tif (!GetLogicalDriveStringsW(buf_size, buf.data())) {\n\t\treturn false;\n\t}\n\n\twchar_t* text_ptr = buf.data();\n\n\t// add the drives to list\n\twhile (*text_ptr != 0) {\n\t\ttext_ptr[0] = static_cast<wchar_t>(towupper(text_ptr[0]));\n\t\tstd::string drive = win32_utf16_to_utf8(text_ptr);\n\t\tif (!drive.empty()) {\n\t\t\tput_here.push_back(drive + \":\\\\\");\n\t\t}\n\t\twhile (*text_ptr != 0)\n\t\t\t++text_ptr;\n\t\t++text_ptr;  // set to position after 0\n\t}\n\n\treturn true;\n}\n\n\n\n// Get windows \"special\" folder by CSIDL (CSIDL_APPDATA, for example).\n// See http://msdn.microsoft.com/en-us/library/bb762494(VS.85).aspx\n// for details.\ninline std::string win32_get_special_folder(int csidl, bool auto_create)\n{\n\tstd::array<wchar_t, MAX_PATH> buf{};\n\n\t// SHGetSpecialFolderPath() is since ie4\n#if (defined _WIN32_IE) && (_WIN32_IE >= 0x0400)\n\tif (SHGetSpecialFolderPathW(nullptr, buf.data(), csidl, static_cast<BOOL>(auto_create)) == TRUE) {\n\t\treturn win32_utf16_to_utf8(buf);\n\t}\n\n#else\n\tLPITEMIDLIST pidl = nullptr;\n\n\tif (SHGetSpecialFolderLocation(nullptr, csidl, &pidl) == S_OK) {\n\t\tif (SHGetPathFromIDListW(pidl, buf.data())== TRUE) {\n\t\t\terrno = 0;\n\t\t\t// 00 is F_OK (for msvc). ENOENT is \"no such file or directory\".\n\t\t\t// We can't really do anything if errors happen here, so ignore them.\n\t\t\tif (auto_create && _waccess(buf.data(), 00) == -1 && errno == ENOENT) {\n\t\t\t\t_wmkdir(buf.data());\n\t\t\t}\n\t\t\treturn win32_utf16_to_utf8(buf);\n\t\t}\n\n\t\t// We must free pidl. This can be done in two ways:\n\n\t\t// CoTaskMemFree() requires linking to ole32.\n\t\t// CoTaskMemFree(pidl);\n\n\t\t// SHGetMalloc() which is deprecated and may be removed in the future.\n\t\tIMalloc* allocator = nullptr;\n\t\tif (SHGetMalloc(&allocator) == S_OK && allocator) {\n\t\t\tallocator->Free(pidl);\n\t\t\tallocator->Release();\n\t\t}\n\t}\n\n#endif\n\n\treturn std::string();\n}\n\n\n\n// Usually C:\\Windows or something like that.\ninline std::string win32_get_windows_directory()\n{\n\tstd::array<wchar_t, MAX_PATH> buf{};\n\tUINT status = GetWindowsDirectoryW(buf.data(), MAX_PATH);\n\tif (status != 0 && buf[0] != 0) {\n\t\twin32_utf16_to_utf8(buf);\n\t}\n\treturn \"C:\\\\\";\n}\n\n\n\n// base may be e.g. HKEY_CURRENT_USER.\n// Note that this works only with REG_SZ (string) types,\n// returning their value as utf-8. False is returned for all other types.\ninline bool win32_get_registry_value_string(HKEY base,\n\t\tconst std::string& keydir, const std::string& key, std::string& put_here)\n{\n\tstd::wstring wkeydir = win32_utf8_to_utf16(keydir);\n\tif (wkeydir.empty())\n\t\treturn false;\n\n\tHKEY reg_key = nullptr;\n\tbool open_status = (RegOpenKeyExW(base, wkeydir.c_str(), 0, KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS);\n\n\tif (!open_status)\n\t\treturn false;\n\n\tbool ok = false;\n\tstd::wstring wkey = win32_utf8_to_utf16(key, &ok);\n\tif (!ok) {  // conversion error. Note that an empty string is not an error.\n\t\tif (reg_key)\n\t\t\tRegCloseKey(reg_key);\n\t\treturn false;\n\t}\n\n\tDWORD type = 0;\n\tDWORD nbytes = 0;\n\t// This returns the size, including the 0 character only if the data was stored with it.\n\tbool status = (RegQueryValueExW(reg_key, wkey.c_str(), nullptr, &type, nullptr, &nbytes) == ERROR_SUCCESS);\n\n\tif (status) {\n\t\tDWORD buf_len = ((nbytes % 2) == 1 ? (nbytes+1) : nbytes);  // 00-terminate on 2-byte boundary, in case the value isn't.\n\t\tstd::vector<BYTE> result(buf_len);\n\n\t\tstatus = (RegQueryValueExW(reg_key, wkey.c_str(), nullptr, &type, result.data(), &nbytes) == ERROR_SUCCESS);\n\n\t\tif (status) {\n\t\t\tstd::string utf8 = win32_utf16_to_utf8(reinterpret_cast<wchar_t*>(result.data()), &ok);\n\t\t\tif (ok) {\n\t\t\t\tput_here = utf8;\n\t\t\t} else {\n\t\t\t\tstatus = false;\n\t\t\t}\n\t\t}\n\t}\n\n\tif (reg_key)\n\t\tRegCloseKey(reg_key);\n\n\treturn status;\n}\n\n\n\n\n// Note that this sets the type as REG_SZ (string). The accepted utf-8\n// value is converted to utf-16 for storage.\ninline bool win32_set_registry_value_string(HKEY base,\n\t\tconst std::string& keydir, const std::string& key, const std::string& value)\n{\n\tstd::wstring wkeydir = win32_utf8_to_utf16(keydir);\n\tif (wkeydir.empty())\n\t\treturn false;\n\n\tHKEY reg_key = nullptr;\n\tif (RegOpenKeyExW(base, wkeydir.c_str(), 0, KEY_QUERY_VALUE, &reg_key) != ERROR_SUCCESS)\n\t\treturn false;\n\n\tbool status = true;\n\n\tbool ok = false;\n\tstd::wstring wvalue = win32_utf8_to_utf16(value, &ok);\n\tstd::wstring wkey;\n\tif (ok) {\n\t\twkey = win32_utf8_to_utf16(key, &ok);\n\t}\n\tif (!ok) {\n\t\tstatus = false;\n\t}\n\n\tif (status) {\n\t\tstatus = (RegSetValueExW(reg_key, wkey.c_str(), 0, REG_SZ,\n\t\t\t\t\treinterpret_cast<const BYTE*>(wvalue.data()),\n\t\t\t\t\tDWORD(wvalue.size() + 1*sizeof(wchar_t))\n\t\t\t\t) == ERROR_SUCCESS);\n\t}\n\n\tif (reg_key)\n\t\tRegCloseKey(reg_key);\n\n\treturn status;\n}\n\n\n\n// Get registry value as a DWORD.\n// Note that this works only with REG_DWORD types.\ninline bool win32_get_registry_value_dword(HKEY base,\n\t\tconst std::string& keydir, const std::string& key, DWORD& put_here)\n{\n\tstd::wstring wkeydir = win32_utf8_to_utf16(keydir);\n\tif (wkeydir.empty())\n\t\treturn false;\n\n\tHKEY reg_key = nullptr;\n\tbool open_status = (RegOpenKeyExW(base, wkeydir.c_str(), 0, KEY_QUERY_VALUE, &reg_key) == ERROR_SUCCESS);\n\n\tif (!open_status)\n\t\treturn false;\n\n\tbool ok = false;\n\tstd::wstring wkey = win32_utf8_to_utf16(key, &ok);\n\tif (!ok) {  // conversion error. Note that an empty string is not an error.\n\t\tif (reg_key)\n\t\t\tRegCloseKey(reg_key);\n\t\treturn false;\n\t}\n\n\tDWORD type = 0;\n\tDWORD value = 0;\n\tDWORD nbytes = sizeof(DWORD);\n\tbool status = (RegQueryValueExW(reg_key, wkey.c_str(), nullptr, &type,\n\t\t\treinterpret_cast<BYTE*>(&value), &nbytes) == ERROR_SUCCESS);\n\n\tif (status && type == REG_DWORD) {\n\t\tput_here = value;\n\t} else {\n\t\tstatus = false;\n\t}\n\n\tif (reg_key)\n\t\tRegCloseKey(reg_key);\n\n\treturn status;\n}\n\n\n\n// Redirect stdout and stderr to console window (if open).\ninline bool win32_redirect_stdio_to_console(bool create_if_none)\n{\n\tbool console_created = false;\n\treturn win32_redirect_stdio_to_console(create_if_none, console_created);\n}\n\n\n\n// Redirect stdout and stderr to console window (if open).\ninline bool win32_redirect_stdio_to_console(bool create_if_none, bool& console_created)\n{\n\tconsole_created = false;\n\n\t// AttachConsole is since winxp (note that mingw and MS headers say win2k,\n\t// which is incorrect; msdn is correct).\n#if defined WINVER && WINVER >= 0x0501\n\tif (AttachConsole(ATTACH_PARENT_PROCESS) == FALSE) {\n\t\tif (create_if_none) {\n\t\t\t// Even though msdn says that stdio is redirected to the new console,\n\t\t\t// it isn't so and we must do it manually.\n\t\t\tif (AllocConsole() == FALSE) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconsole_created = true;\n\t\t} else {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// redirect unbuffered STDOUT to the console\n\tHANDLE h_out = GetStdHandle(STD_OUTPUT_HANDLE);\n\tstd::FILE* fp_out = _fdopen(_open_osfhandle((intptr_t)(h_out), _O_TEXT), \"w\");\n\t*stdout = *fp_out;\n\tstd::setvbuf(stdout, nullptr, _IONBF, 0);\n\n\t// redirect unbuffered STDERR to the console\n\tHANDLE h_err = GetStdHandle(STD_ERROR_HANDLE);\n\tstd::FILE* fp_err = _fdopen(_open_osfhandle((intptr_t)(h_err), _O_TEXT), \"w\");\n\t*stderr = *fp_err;\n\tstd::setvbuf(stderr, nullptr, _IONBF, 0);\n\n\tstd::ios::sync_with_stdio();\n\n\tstd::fprintf(stderr, \"\\n\");  // so that we start with an empty line\n\n\treturn true;\n\n#else  // unsupported\n\treturn false;\n#endif\n}\n\n\n\n\nnamespace internal {\n\n\n\tstruct Win32RedirectHolder {\n\t\tstatic inline std::wstring stdout_file;\n\t\tstatic inline std::wstring stderr_file;\n\t};\n\n\n\n\t/// Returns full path (in utf-16) to the output filename (in utf-8)\n\tinline std::wstring win32_get_std_output_file(std::wstring_view base)\n\t{\n\t\tstd::array<wchar_t, MAX_PATH> name{};\n\n\t\t// This function doesn't write terminating 0 if the buffer is insufficient,\n\t\t// so we reserve some space for it.\n\t\tif (GetModuleFileNameW(nullptr, name.data(), MAX_PATH - 1) == TRUE) {\n\t\t\tstd::wstring wname(name.data());\n\t\t\tstd::wstring::size_type pos = wname.find_last_of(L'.');\n\t\t\twname = wname.substr(0, pos);  // full path, including the executable without extension.\n\t\t\tif (!wname.empty()) {\n\t\t\t\treturn wname + L\"-\" + std::wstring(base);\n\t\t\t}\n\t\t}\n\n\t\treturn std::wstring(base);\n\t}\n\n\n\n\t/// Remove the output files if there was no output written.\n\t/// This is an atexit() callback.\n\textern \"C\" inline void win32_redirect_stdio_to_files_cleanup()\n\t{\n\t\t// Flush the output in case anything is queued\n\t\tstd::fclose(stdout);\n\t\tstd::fclose(stderr);\n\n\t\t// See if the files have any output in them. If not, remove the empty files.\n\t\tif (!Win32RedirectHolder::stdout_file.empty()) {\n\t\t\tstd::filesystem::path path(Win32RedirectHolder::stdout_file);\n\t\t\tstd::error_code ignored_ec;\n\t\t\tif (std::filesystem::exists(path, ignored_ec) && std::filesystem::is_empty(path, ignored_ec)) {\n\t\t\t\tstd::filesystem::remove(path, ignored_ec);\n\t\t\t}\n\t\t\tWin32RedirectHolder::stdout_file.clear();\n\t\t}\n\t\tif (!Win32RedirectHolder::stderr_file.empty()) {\n\t\t\tstd::filesystem::path path(Win32RedirectHolder::stderr_file);\n\t\t\tstd::error_code ignored_ec;\n\t\t\tif (std::filesystem::exists(path, ignored_ec) && std::filesystem::is_empty(path, ignored_ec)) {\n\t\t\t\tstd::filesystem::remove(path, ignored_ec);\n\t\t\t}\n\t\t\tWin32RedirectHolder::stderr_file.clear();\n\t\t}\n\t}\n\n\n}  // ns\n\n\n\n\n// Redirect stdout to stdout.txt and stderr to stderr.txt.\n// Call once.\ninline bool win32_redirect_stdio_to_files(const std::string& stdout_file, const std::string& stderr_file)\n{\n\tstd::wstring wstdout_file;\n\tif (stdout_file.empty()) {\n\t\twstdout_file = internal::win32_get_std_output_file(L\"stdout.txt\");\n\t} else {\n\t\twstdout_file = win32_utf8_to_utf16(stdout_file);\n\t}\n\tstd::FILE* fp_out = nullptr;\n\tif (!wstdout_file.empty()) {\n#if defined MINGW_HAS_SECURE_API || defined _MSC_VER\n\t\terrno = _wfopen_s(&fp_out, wstdout_file.c_str(), L\"wb\");\n#else\n\t\tfp_out = _wfopen(wstdout_file.c_str(), L\"wb\");\n#endif\n\t}\n\tif (!wstdout_file.empty() && fp_out) {\n\t\t*stdout = *fp_out;\n\t\tstd::setvbuf(stdout, nullptr, _IOLBF, BUFSIZ);  // Line buffered\n\t} else {\n\t\twstdout_file.clear();\n\t}\n\tinternal::Win32RedirectHolder::stdout_file = wstdout_file;\n\n\tstd::wstring wstderr_file;  // freed on cleanup\n\tif (stderr_file.empty()) {\n\t\twstderr_file = internal::win32_get_std_output_file(L\"stderr.txt\");\n\t} else {\n\t\twstderr_file = win32_utf8_to_utf16(stderr_file);\n\t}\n\tstd::FILE* fp_err = nullptr;\n\tif (!wstderr_file.empty()) {\n#if defined MINGW_HAS_SECURE_API || defined _MSC_VER\n\t\terrno = _wfopen_s(&fp_err, wstderr_file.c_str(), L\"wb\");\n#else\n\t\tfp_err = _wfopen(wstderr_file.c_str(), L\"wb\");\n#endif\n\t}\n\tif (!wstderr_file.empty() && fp_out) {\n\t\t*stderr = *fp_err;\n\t\tstd::setvbuf(stderr, nullptr, _IONBF, BUFSIZ);  // No buffering\n\t} else {\n\t\twstderr_file.clear();\n\t}\n\tinternal::Win32RedirectHolder::stderr_file = wstderr_file;  // so that it's freed properly\n\n\tif (fp_out || fp_err) {\n\t\t// make sure cout, wcout, cin, wcin, wcerr, cerr, wclog and clog\n\t\t// write there as well.\n\t\tstd::ios::sync_with_stdio();\n\n\t\tstd::atexit(internal::win32_redirect_stdio_to_files_cleanup);\n\n\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n\n\n\n\n\ninline std::wstring win32_user_to_utf16(UINT from_cp, std::string_view from_str, bool* ok)\n{\n\tif (from_str.empty()) {\n\t\tif (ok) {\n\t\t\t*ok = true;\n\t\t}\n\t\treturn std::wstring();\n\t}\n\n\t// excluding terminating 0\n\tint wide_bufsize = MultiByteToWideChar(from_cp, 0, from_str.data(), int(from_str.size()), nullptr, 0);\n\tif (wide_bufsize == ERROR_NO_UNICODE_TRANSLATION) {\n\t\tif (ok) {\n\t\t\t*ok = false;\n\t\t}\n\t\treturn std::wstring();  // invalid sequence\n\t}\n\tif (wide_bufsize == 0) {\n\t\tif (ok) {\n\t\t\t*ok = false;\n\t\t}\n\t\treturn std::wstring();  // error in conversion\n\t}\n\n\tstd::vector<wchar_t> res(std::size_t(wide_bufsize + 1));\n\tMultiByteToWideChar(from_cp, 0, from_str.data(), int(from_str.size()), res.data(), wide_bufsize + 1);\n\tres.back() = L'\\0';  // just in case\n\n\tif (ok) {\n\t\t*ok = true;\n\t}\n\treturn std::wstring(res.data());\n}\n\n\n\n// Convert utf-16 string (represented as 0-terminated array of wchar_t, where\n// wchar_t is 2 bytes (on windows)) to a string in user-specified encoding.\n// Caller must delete[] the returned value.\n// The size of the returned buffer is std::strlen(buf) + 1.\n// returned_buf_size (if not 0) will be set to the size of returned buffer\n// (in char; including terminating 0).\ninline std::string win32_utf16_to_user(UINT to_cp, std::wstring_view utf16_str, bool* ok)\n{\n\tif (utf16_str.empty()) {\n\t\tif (ok) {\n\t\t\t*ok = true;\n\t\t}\n\t\treturn std::string();\n\t}\n\n\tint buf_size = WideCharToMultiByte(to_cp, 0, utf16_str.data(), int(utf16_str.size()), nullptr, 0, nullptr, nullptr);\n\tif (buf_size == 0) {\n\t\tif (ok) {\n\t\t\t*ok = false;\n\t\t}\n\t\treturn std::string();\n\t}\n\n\tstd::vector<char> res(std::size_t(buf_size + 1));\n\tWideCharToMultiByte(to_cp, 0,\n\t\t\tutf16_str.data(), int(utf16_str.size()),\n\t\t\tres.data(), int(res.size()),\n\t\t\tnullptr, nullptr);\n\tres.back() = '\\0';  // just in case\n\n\tif (ok) {\n\t\t*ok = true;\n\t}\n\treturn std::string(res.data());\n}\n\n\n\n// Convert utf-8 string to utf-16 string. See win32_user_to_utf16() for details.\ninline std::wstring win32_utf8_to_utf16(std::string_view utf8_str, bool* ok)\n{\n\treturn win32_user_to_utf16(CP_UTF8, utf8_str, ok);\n}\n\n\n// Convert utf-16 string to utf-8 string. See win32_utf16_to_user() for details.\ninline std::string win32_utf16_to_utf8(std::wstring_view utf16_str, bool* ok)\n{\n\treturn win32_utf16_to_user(CP_UTF8, utf16_str, ok);\n}\n\n\n\n// Convert utf-16 string to utf-8 string. See win32_utf16_to_user() for details.\ntemplate<std::size_t ArraySize>\nstd::string win32_utf16_to_utf8(const std::array<wchar_t, ArraySize>& utf16_str, bool* ok)\n{\n\treturn win32_utf16_to_user(CP_UTF8, {utf16_str.data()}, ok);\n}\n\n\n\n// Convert current locale-encoded string to utf-16 string. See win32_user_to_utf16() for details.\n// Use CP_ACP is system default windows ANSI codepage.\n// Use CP_THREAD_ACP is for current thread. Think before you choose. :)\ninline std::wstring win32_locale_to_utf16(std::string_view loc_str, bool use_thread_locale, bool* ok)\n{\n\treturn win32_user_to_utf16(use_thread_locale ? CP_THREAD_ACP : CP_ACP, loc_str, ok);\n}\n\n\n// Convert utf-16 string to locale-encoded string. See win32_utf16_to_user() for details.\ninline std::string win32_utf16_to_locale(std::wstring_view utf16_str, bool use_thread_locale, bool* ok)\n{\n\treturn win32_utf16_to_user(use_thread_locale ? CP_THREAD_ACP : CP_ACP, utf16_str, ok);\n}\n\n\n\n// Convert current locale-encoded string to utf-8 string.\ninline std::string win32_locale_to_utf8(std::string_view loc_str, bool use_thread_locale, bool* ok)\n{\n\tstd::wstring utf16_str = win32_locale_to_utf16(loc_str, use_thread_locale, ok);\n\tif (ok && !(*ok)) {\n\t\treturn std::string();\n\t}\n\treturn win32_utf16_to_utf8(utf16_str, ok);\n}\n\n\n// Convert utf-8 string to locale-encoded string. See win32_utf8_to_user() for details.\ninline std::string win32_utf8_to_locale(std::string_view utf8_str, bool use_thread_locale, bool* ok)\n{\n\tstd::wstring utf16_str = win32_utf8_to_utf16(utf8_str, ok);\n\tif (ok && !(*ok)) {\n\t\treturn std::string();\n\t}\n\treturn win32_utf16_to_locale(utf16_str, use_thread_locale, ok);\n}\n\n\n\n\n\n\n\n\n\n\n}  // ns\n\n\n\n#endif  // win32\n\n#endif  // hg\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nadd_library(libdebug STATIC)\n\ntarget_sources(libdebug PRIVATE\n\tdchannel.cpp\n\tdchannel.h\n\tdcmdarg.cpp\n\tdcmdarg.h\n\tdexcept.h\n\tdflags.cpp\n\tdflags.h\n\tdout.cpp\n\tdout.h\n\tdstate.cpp\n\tdstate.h\n\tdstate_pub.h\n\tdstream.cpp\n\tdstream.h\n\tlibdebug.h\n\tlibdebug_mini.h\n)\n\ntarget_include_directories(libdebug INTERFACE \"${CMAKE_SOURCE_DIR}/src\")\n\ntarget_link_libraries(libdebug\n    PRIVATE\n\t\thz\n\t\tapp_gtkmm_interface  # .cpp only\n)\n\n\nadd_subdirectory(examples)\n\n"
  },
  {
    "path": "src/libdebug/dchannel.cpp",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#include \"hz/format_unit.h\"  // format_date()\n\n#include \"dchannel.h\"\n// #include <iostream>  // tmp\n\n\n\nstd::string debug_format_message(debug_level::flag level, const std::string& domain,\n\t\t\t\tdebug_format::flags& format_flags, int indent_level, bool is_first_line, const std::string& msg)\n{\n\tif (msg.empty())\n\t\treturn msg;\n\n\tstd::string ret;\n\tret.reserve(msg.size() + 40);  // indentation + domain/level\n\n\t// allow prefix only for first line and others when first_line_only is disabled\n\tif (is_first_line || !format_flags.test(debug_format::first_line_only)) {\n\n\t\tif (format_flags.test(debug_format::datetime)) {  // print time\n\t\t\tret += hz::format_date(\"%Y-%m-%d %H:%M:%S: \", true);\n\t\t}\n\n\t\tif (format_flags.test(debug_format::level)) {  // print level name\n\t\t\tconst bool use_color = format_flags.test(debug_format::color);\n\t\t\tif (use_color)\n\t\t\t\tret += debug_level::get_color_start(level);\n\n\t\t\tconst std::string level_name = std::string(\"<\") + debug_level::get_name(level) + \">\";\n\t\t\tret += level_name + std::string(8 - level_name.size(), ' ');\n\n\t\t\tif (use_color)\n\t\t\t\tret += debug_level::get_color_stop(level);\n\t\t}\n\n\t\tif (format_flags.test(debug_format::domain)) {  // print domain name\n\t\t\tret += std::string(\"[\") + domain + \"] \";\n\t\t}\n\n\t}\n\n\n\tif (format_flags.test(debug_format::indent)) {\n\t\tconst std::string spaces(static_cast<std::size_t>(indent_level * 4), ' ');  // indentation spaces\n\n\t\t// replace all newlines with \\n(indent-spaces) except for the last one.\n\t\tstd::string::size_type oldpos = 0, pos = 0;\n\t\twhile(pos != std::string::npos) {\n\t\t\tif (pos == 0) {\n\t\t\t\tret.insert(0, spaces);\n\t\t\t} else {\n\t\t\t\tret.append(spaces);\n\t\t\t}\n\t\t\tpos = msg.find('\\n', oldpos);\n\t\t\tif (pos != std::string::npos)\n\t\t\t\tpos++;\n\t\t\tif (pos >= msg.size())\n\t\t\t\tpos = std::string::npos;\n\n\t\t\tret.append(msg, oldpos, pos - oldpos);\n\t\t\toldpos = pos;\n\t\t}\n\n\t} else {\n\t\tret += msg;\n\t}\n\n\treturn ret;\n}\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/dchannel.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#ifndef LIBDEBUG_DCHANNEL_H\n#define LIBDEBUG_DCHANNEL_H\n\n#include <string>\n#include <ostream>  // std::ostream (iosfwd is not enough for win32 and suncc)\n#include <memory>\n\n#include \"dflags.h\"\n\n\nclass DebugChannelBase;\n\n/// Strong reference-holding pointer\nusing DebugChannelBasePtr = std::shared_ptr<DebugChannelBase>;\n\n\n\n// TODO Per-channel flags (instead of per-debug-stream flags).\n\n\n/// All channels must inherit this.\nclass DebugChannelBase {\n\tpublic:\n\n\t\t/// Defaulted\n\t\tDebugChannelBase() = default;\n\n\t\t/// Deleted\n\t\tDebugChannelBase(const DebugChannelBase& other) = delete;\n\n\t\t/// Deleted\n\t\tDebugChannelBase(DebugChannelBase&& other) = delete;\n\n\t\t/// Deleted\n\t\tDebugChannelBase& operator=(const DebugChannelBase&) = delete;\n\n\t\t/// Deleted\n\t\tDebugChannelBase& operator=(DebugChannelBase&&) = delete;\n\n\t\t/// Virtual destructor\n\t\tvirtual ~DebugChannelBase() = default;\n\n\t\t/// Send a message to channel\n\t\tvirtual void send(debug_level::flag level, const std::string& domain,\n\t\t\t\tdebug_format::flags& format_flags, int indent_level, bool is_first_line, const std::string& msg) = 0;\n};\n\n\n\n\n/// Helper function for DebugChannel objects, formats a message.\nstd::string debug_format_message(debug_level::flag level, const std::string& domain,\n\t\t\t\tdebug_format::flags& format_flags, int indent_level, bool is_first_line, const std::string& msg);\n\n\n\n/// std::ostream wrapper as a DebugChannel.\nclass DebugChannelOStream : public DebugChannelBase {\n\tpublic:\n\n\t\t/// Constructor\n\t\texplicit DebugChannelOStream(std::ostream& os) : os_(os)\n\t\t{ }\n\n\t\t/// Reimplemented from DebugChannelBase.\n\t\tvoid send(debug_level::flag level, const std::string& domain,\n\t\t\t\tdebug_format::flags& format_flags, int indent_level, bool is_first_line, const std::string& msg) override\n\t\t{\n\t\t\tos_ << debug_format_message(level, domain, format_flags, indent_level, is_first_line, msg);\n\t\t}\n\n\n\t\t// Non-debug-API members:\n\n\t\t/// Get the ostream.\n\t\t[[nodiscard]] std::ostream& get_ostream()\n\t\t{\n\t\t\treturn os_;\n\t\t}\n\n\n\tprivate:\n\n\t\tstd::ostream& os_;  ///< Wrapped ostream\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/dcmdarg.cpp",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#if defined ENABLE_GLIB && ENABLE_GLIB\n\n#include <string>\n#include <vector>\n#include <map>\n#include <glib.h>\n#include <sstream>\n#include <ios>  // std::boolalpha\n#include <algorithm>  // std::find\n\n#include \"hz/string_algo.h\"  // string_split()\n\n#include \"dcmdarg.h\"\n#include \"dflags.h\"\n#include \"dstate.h\"\n\n\n\n\nnamespace debug_internal {\n\n\n\t/// Internal class, holds values of command line args.\n\tstruct DebugCmdArgs {\n\t\t/// Constructor\n\t\tDebugCmdArgs()\n\t\t{\n\t\t#ifdef _WIN32\n\t\t\tverbose = TRUE;\n\t\t\tdebug_colorize = FALSE;\n\t\t#endif\n\t\t#ifdef DEBUG_BUILD\n\t\t\tverbosity_level = 5;  // all\n\t\t#endif\n\t\t}\n\n\t\t// use glib types here\n\t\tgboolean verbose = FALSE;  ///< Verbose output (enables higher verbosity level)\n\t\tgboolean quiet = FALSE;  ///< Less verbose output (enables lower verbosity level)\n\t\tgint verbosity_level = 3;  ///< Verbosity level override - warn, error, fatal\n\t\tstd::vector<std::string> debug_levels;  ///< Comma-separated names of levels to enable\n\t\tgboolean debug_colorize = TRUE;  ///< Colorize the output or not\n\n\t\tdebug_level::flags levels_enabled;  ///< Final vector - not actually an argument, but filled after the parsing.\n\t};\n\n\n\n\t/// Get libdebug command-line arguments\n\t[[nodiscard]] inline DebugCmdArgs* get_debug_get_args_holder()\n\t{\n\t\tstatic DebugCmdArgs args;\n\t\treturn &args;\n\t}\n\n\n} // ns debug_internal\n\n\n\n\nextern \"C\" {\n\n\t/// Glib callback for parsing libdebug levels argument\n\tstatic gboolean debug_internal_parse_levels(const gchar* option_name,\n\t\t\tconst gchar* value, gpointer data, GError** error);\n\n\t/// Glib callback for performing post-parse phase (parse hook)\n\tstatic gboolean debug_internal_post_parse_func(GOptionContext* context,\n\t\t\tGOptionGroup *group, gpointer data, GError** error);\n\n}\n\n\n\n// GLib callback\nstatic gboolean debug_internal_parse_levels([[maybe_unused]] const gchar* option_name,\n\t\tconst gchar* value, gpointer data, [[maybe_unused]] GError** error)\n{\n\tif (!value)\n\t\treturn FALSE;\n\tauto* args = static_cast<debug_internal::DebugCmdArgs*>(data);\n\tconst std::string levels = value;\n\thz::string_split(levels, ',', args->debug_levels, true);\n\t// will filter out invalid ones later\n\treturn TRUE;\n}\n\n\n\n// GLib callback\nstatic gboolean debug_internal_post_parse_func([[maybe_unused]] GOptionContext* context,\n\t\t[[maybe_unused]] GOptionGroup *group, gpointer data, [[maybe_unused]] GError** error)\n{\n\tauto* args = static_cast<debug_internal::DebugCmdArgs*>(data);\n\n\tif (!args->debug_levels.empty()) {  // no string levels on command line given\n\t\targs->levels_enabled.reset();  // reset\n\t\tif (std::find(args->debug_levels.cbegin(), args->debug_levels.cend(), \"dump\") != args->debug_levels.cend()) {\n\t\t\targs->levels_enabled.set(debug_level::dump);\n\t\t}\n\t\tif (std::find(args->debug_levels.cbegin(), args->debug_levels.cend(), \"info\") != args->debug_levels.cend()) {\n\t\t\targs->levels_enabled.set(debug_level::info);\n\t\t}\n\t\tif (std::find(args->debug_levels.cbegin(), args->debug_levels.cend(), \"warn\") != args->debug_levels.cend()) {\n\t\t\targs->levels_enabled.set(debug_level::warn);\n\t\t}\n\t\tif (std::find(args->debug_levels.cbegin(), args->debug_levels.cend(), \"error\") != args->debug_levels.cend()) {\n\t\t\targs->levels_enabled.set(debug_level::error);\n\t\t}\n\t\tif (std::find(args->debug_levels.cbegin(), args->debug_levels.cend(), \"fatal\") != args->debug_levels.cend()) {\n\t\t\targs->levels_enabled.set(debug_level::fatal);\n\t\t}\n\n\t} else if (args->quiet == TRUE) {\n\t\targs->levels_enabled.reset();\n\n\t} else if (args->verbose == TRUE) {\n\t\targs->levels_enabled.reset();\n\t\targs->levels_enabled.set(debug_level::dump);\n\t\targs->levels_enabled.set(debug_level::info);\n\t\targs->levels_enabled.set(debug_level::warn);\n\t\targs->levels_enabled.set(debug_level::error);\n\t\targs->levels_enabled.set(debug_level::fatal);\n\n\t} else {\n\t\targs->levels_enabled.reset();\n\t\tif (args->verbosity_level > 0) args->levels_enabled.set(debug_level::fatal);\n\t\tif (args->verbosity_level > 1) args->levels_enabled.set(debug_level::error);\n\t\tif (args->verbosity_level > 2) args->levels_enabled.set(debug_level::warn);\n\t\tif (args->verbosity_level > 3) args->levels_enabled.set(debug_level::info);\n\t\tif (args->verbosity_level > 4) args->levels_enabled.set(debug_level::dump);\n\t}\n\n\tconst bool color_enabled = static_cast<bool>(args->debug_colorize);\n\n\n\tdebug_internal::DebugState::DomainMap& domain_map = debug_internal::get_debug_state_ref().get_domain_map_ref();\n\n\tfor (auto& [domain_name, levels_streams] : domain_map) {\n\t\tfor (auto& [level, stream] : levels_streams) {\n\t\t\tstream->set_enabled(bool(args->levels_enabled.test(level)));\n\n\t\t\tdebug_format::flags format = stream->get_format();\n\t\t\tformat.set(debug_format::color, color_enabled);\n\t\t\tstream->set_format(format);\n\t\t}\n\t}\n\n\treturn TRUE;\n}\n\n\n\n\n\nstd::string debug_get_cmd_args_dump()\n{\n\tdebug_internal::DebugCmdArgs* args = debug_internal::get_debug_get_args_holder();\n\tstd::ostringstream ss;\n\n// \tss << \"\\tverbose: \" << std::boolalpha << static_cast<bool>(args->verbose) << \"\\n\";\n// \tss << \"\\tquiet: \" << std::boolalpha << static_cast<bool>(args->quiet) << \"\\n\";\n// \tss << \"\\tverbosity_level: \" << args->verbosity_level << \"\\n\";\n// \tss << \"\\tdebug_levels: \" << args->debug_levels << \"\\n\";\n\tss << \"\\tlevels_enabled: \" << args->levels_enabled << \"\\n\";\n\tss << \"\\tdebug_colorize: \" << std::boolalpha << static_cast<bool>(args->debug_colorize) << \"\\n\";\n\n\treturn ss.str();\n}\n\n\n\n\n// If adding the returned value to context (which you must do),\n// no need to free the result.\nGOptionGroup* debug_get_option_group()\n{\n\tdebug_internal::DebugCmdArgs* args = debug_internal::get_debug_get_args_holder();\n\n\tGOptionGroup* group = g_option_group_new(\"debug\",\n\t\t\t\"Libdebug Logging Options\", \"Show libdebug options\",\n\t\t\targs, nullptr);\n\n\tstatic const std::vector<GOptionEntry> entries = {\n\t\t{ \"verbose\", 'v', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE,\n\t\t\t\t&(args->verbose), \"Enable verbose logging; same as --verbosity-level 5\", nullptr },\n\t\t{ \"quiet\", 'q', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE,\n\t\t\t\t&(args->quiet), \"Disable logging; same as --verbosity-level 0\", nullptr },\n\t\t{ \"verbosity-level\", 'b', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_INT,\n\t\t\t\t&(args->verbosity_level), \"Set verbosity level [0-5]\", nullptr },\n\t\t{ \"debug-levels\", '\\0', 0, G_OPTION_ARG_CALLBACK,\n\t\t\t\t(gpointer)(&debug_internal_parse_levels),  // reinterpret_cast<> doesn't work here\n\t\t\t\t\"Enable only these logging levels; the argument is a comma-separated list of (dump, info, warn, error, fatal)\", nullptr },\n\t\t{ \"debug-colorize\", '\\0', 0, G_OPTION_ARG_NONE,\n\t\t\t\t&(args->debug_colorize), \"Enable colored output\", nullptr },\n\t\t{ \"debug-no-colorize\", '\\0', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE,\n\t\t\t\t&(args->debug_colorize), \"Disable colored output\", nullptr },\n\t\t{ nullptr, '\\0', 0, G_OPTION_ARG_NONE, nullptr, nullptr, nullptr }\n\t};\n\n\tg_option_group_add_entries(group, entries.data());\n\n\tg_option_group_set_parse_hooks(group, nullptr, &debug_internal_post_parse_func);\n\n\treturn group;\n}\n\n\n\n\n\n#endif  // glib\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/dcmdarg.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#ifndef LIBDEBUG_DCMDARG_H\n#define LIBDEBUG_DCMDARG_H\n\n/**\n\\file\nGlib option parsing support - public interface\n*/\n\n#if defined ENABLE_GLIB && ENABLE_GLIB\n\n#include <glib.h>\n\n\n/// Get Glib option group to use for handling libdebug command-line\n/// arguments using Glib. You must not delete the returned value if\n/// adding it to context (which is usually the case).\n/// This function will also automatically apply the passed (and default)\n/// options to all domains.\nGOptionGroup* debug_get_option_group();\n\n/// Return a string-dump of all libdebug options (use to debug\n/// command-line option issues).\nstd::string debug_get_cmd_args_dump();\n\n\n\n#endif  // glib\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/dexcept.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#ifndef LIBDEBUG_DEXCEPT_H\n#define LIBDEBUG_DEXCEPT_H\n\n#include <stdexcept>  // std::runtime_error\n\n\n\n/// Exception thrown on internal libdebug errors\nclass debug_internal_error : public std::runtime_error {\n\tpublic:\n\t\tusing runtime_error::runtime_error;\n};\n\n\n\n\n/// Exception thrown on libdebug API usage errors\nclass debug_usage_error : virtual public std::runtime_error {\n\tpublic:\n\t\tusing runtime_error::runtime_error;\n};\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/dflags.cpp",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#include \"dflags.h\"\n\n#include <map>\n\n\nnamespace debug_level {\n\n\nconst flags& get_all_flags()\n{\n\tstatic const flags all_flags = []() {\n\t\tflags f;\n\t\tf.set(debug_level::fatal);\n\t\tf.set(debug_level::error);\n\t\tf.set(debug_level::warn);\n\t\tf.set(debug_level::info);\n\t\tf.set(debug_level::dump);\n\t\treturn f;\n\t}();\n\treturn all_flags;\n}\n\n\n\n/// Get debug level name\nconst char* get_name(flag level)\n{\n\tstatic const std::map<debug_level::flag, const char*> level_names = {\n\t\t{debug_level::fatal, \"fatal\"},\n\t\t{debug_level::error, \"error\"},\n\t\t{debug_level::warn, \"warn\"},\n\t\t{debug_level::info, \"info\"},\n\t\t{debug_level::dump, \"dump\"}\n\t};\n\treturn level_names.at(level);\n}\n\n\n\n/// Get debug level color start sequence\nconst char* get_color_start(flag level)\n{\n\tstatic const std::map<debug_level::flag, const char*> level_colors = {\n\t\t{debug_level::fatal, \"\\033[1;4;31m\"},  // red underlined\n\t\t{debug_level::error, \"\\033[1;4;31m\"},  // red\n\t\t{debug_level::warn, \"\\033[1;35m\"},  // magenta\n\t\t{debug_level::info, \"\\033[1;36m\"},  // cyan\n\t\t{debug_level::dump, \"\\033[1;32m\"}  // green\n\t};\n\treturn level_colors.at(level);\n}\n\n\n\n/// Get debug level color stop sequence\nconst char* get_color_stop([[maybe_unused]] flag level)\n{\n\treturn \"\\033[0m\";\n}\n\n\n}\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/dflags.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#ifndef LIBDEBUG_DFLAGS_H\n#define LIBDEBUG_DFLAGS_H\n\n#include <bitset>\n\n\n/// Debug level enum and related functions\nnamespace debug_level {\n\tenum flag : std::size_t {  ///< Debug level (seriousness).\n\t\tdump,  ///< Dump level (structure dumps, additional verbosity, etc.)\n\t\tinfo,  ///< Information level (what the application is doing)\n\t\twarn,  ///< Warning level (simple warnings)\n\t\terror,  ///< Error level (recoverable errors)\n\t\tfatal,  ///< Fatal level (non-recoverable errors)\n\t\tbits,  ///< Number of bits for bitset\n\t};\n\n\tusing flags = std::bitset<bits>;  ///< Combination of debug level flags\n\n\t/// Get bitset with all flags enabled\n\t[[nodiscard]] const flags& get_all_flags();\n\n\t/// Get debug level name\n\t[[nodiscard]] const char* get_name(flag level);\n\n\t/// Get color start sequence for debug level (for colorizing the output)\n\t[[nodiscard]] const char* get_color_start(flag level);\n\n\t/// Get color stop sequence for debug level (for colorizing the output)\n\t[[nodiscard]] const char* get_color_stop(flag level);\n\n\n\t/// Convert ORed flags into a vector of flags\n\ttemplate<class Container>\n\tvoid get_matched_levels_array(const flags& levels, Container& put_here)\n\t{\n\t\tfor (auto level : {\n\t\t\t\tdebug_level::dump,\n\t\t\t\tdebug_level::info,\n\t\t\t\tdebug_level::warn,\n\t\t\t\tdebug_level::error,\n\t\t\t\tdebug_level::fatal }) {\n\t\t\tif (levels.test(level)) {\n\t\t\t\tput_here.push_back(level);\n\t\t\t}\n\t\t}\n\t}\n}\n\n\n\n/// Debug formatting option (how to format the message) enum and related functions\nnamespace debug_format {\n\tenum flag : std::size_t {  ///< Format flag. Some of these flags can be ORed for some functions.\n\t\tdatetime,  ///< Show datetime\n\t\tlevel,  ///< Show debug level name\n\t\tdomain,  ///< Show domain name\n\t\tcolor,  ///< Colorize output. Note: Colorization works only for supported shells / terminals (e.g. bash / xterm).\n\t\tindent,  ///< Enable indentation\n\t\tfirst_line_only,  ///< Internal flag, prefix first line only.\n\t\tbits,  ///< Number of bits for bitset\n\t};\n\n\t using flags = std::bitset<bits>;  ///< Combination of format flags\n}\n\n\n\n/// Debug position output flags (how to format the current source line information)\nnamespace debug_pos {\n\tenum flag : std::size_t {  ///< Position output flags\n\t\tfunc_name,  ///< Print function name (only)\n\t\tfunc,  ///< Print function name with namespaces, etc. (off by default).\n\t\tline,  ///< Print source code line\n\t\tfile,  ///< Print file path and name\n\t\t// def = func_name | line | file,  ///< Default flags\n\t\tbits,  ///< Number of bits for bitset\n\t};\n\n\tusing flags = std::bitset<bits>;  ///< Combination of flags\n}\n\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/dout.cpp",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#include <string>\n#include <iosfwd>  // std::ostream definition\n#include <sstream>\n\n#include \"dout.h\"\n#include \"dflags.h\"\n#include \"dstate.h\"\n#include \"dexcept.h\"\n\n\n\n\n// This may throw for invalid domain or level.\nstd::ostream& debug_out(debug_level::flag level, const std::string& domain)\n{\n\tauto& dm = debug_internal::get_debug_state_ref().get_domain_map_ref();\n\n\tauto level_map = dm.find(domain);\n\tif (level_map == dm.end()) {  // no such domain\n\t\t// this is an internal error\n\t\tconst std::string msg = \"debug_out(): Debug state doesn't contain the requested domain: \\\"\" + domain + \"\\\".\";\n\t\tthrow debug_internal_error(msg.c_str());\n\t}\n\n\tauto os = level_map->second.find(level);\n\tif (level_map == dm.end()) {\n\t\tconst std::string msg = std::string(\"debug_out(): Debug state doesn't contain the requested level \") +\n\t\t\t\tdebug_level::get_name(level) + \" in domain: \\\"\" + domain + \"\\\".\";\n\n\t\t// this is an internal error\n\t\tthrow debug_internal_error(msg.c_str());\n\t}\n\n\treturn *(os->second);\n}\n\n\n\n\n// Start / stop prefix printing. Useful for large dumps\n\nvoid debug_begin()\n{\n\tdebug_internal::get_debug_state_ref().push_inside_begin();\n}\n\n\nvoid debug_end()\n{\n\tdebug_internal::get_debug_state_ref().pop_inside_begin();\n\t// this is needed because else the contents won't be written until next write.\n\tdebug_internal::get_debug_state_ref().force_output();\n}\n\n\n\n\n\nnamespace debug_internal {\n\n\n\tstd::string DebugSourcePos::str() const\n\t{\n\t\tstd::ostringstream os;\n\t\tos << \"(\";\n\n\t\tif (enabled_types.test(debug_pos::func_name)) {\n\t\t\tos << \"function: \" << func_name;\n\n\t\t} else if (enabled_types.test(debug_pos::func)) {\n\t\t\tos << \"function: \" << func << \"()\";\n\t\t}\n\n\t\tif (enabled_types.test(debug_pos::file)) {\n\t\t\tif (os.str() != \"(\")\n\t\t\t\tos << \", \";\n\t\t\tos << \"file: \" << file;\n\t\t}\n\n\t\tif (enabled_types.test(debug_pos::line)) {\n\t\t\tif (os.str() != \"(\")\n\t\t\t\tos << \", \";\n\t\t\tos << \"line: \" << line;\n\t\t}\n\n\t\tos << \")\";\n\n\t\treturn os.str();\n\t}\n\n\n}\n\n\n\n// ------------------ Indentation and manipulators\n\n\n// increase indentation level for all debug levels\nvoid debug_indent_inc(int by)\n{\n\tconst int curr = debug_internal::get_debug_state_ref().get_indent_level();\n\tdebug_internal::get_debug_state_ref().set_indent_level(curr + by);\n}\n\n\nvoid debug_indent_dec(int by)\n{\n\tint curr = debug_internal::get_debug_state_ref().get_indent_level();\n\tcurr -= by;\n\tif (curr < 0)\n\t\tcurr = 0;\n\tdebug_internal::get_debug_state_ref().set_indent_level(curr);\n}\n\n\nvoid debug_indent_reset()\n{\n\tdebug_internal::get_debug_state_ref().set_indent_level(0);\n}\n\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/dout.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#ifndef LIBDEBUG_DOUT_H\n#define LIBDEBUG_DOUT_H\n\n#include <string>\n// Note: Sun compiler refuses to compile without <ostream> (iosfwd is not enough).\n// Since every useful operator << is defined in ostream, we include it here anyway.\n#include <ostream>  // std::ostream\n#include <utility>\n\n#include \"hz/system_specific.h\"  // HZ_FUNC_PRINTF_ISO_CHECK\n\n#include \"dflags.h\"\n\n\n\n/// Get a libdebug-handled stream for \\c level and \\c domain.\n/// \\throw debug_usage_error if invalid domain or level.\nstd::ostream& debug_out(debug_level::flag level, const std::string& domain);\n\n\n\n// These are macros to be able to easily compile-out per-level output.\n\n/// Send an output to debug stream. For example:\n/// \\code\n/// debug_out_error(\"app\", DBG_FUNC_MSG << \"Error in structure consistency.\\n\");\n/// debug_out_dump(\"app\", \"Error value: \" << value << \".\\n\");\n/// \\endcode\n#define debug_out_dump(domain, output) \\\n\tdebug_out(debug_level::dump, domain) << output\n\n/// Send an output to debug stream. \\see debug_out_dump().\n#define debug_out_info(domain, output) \\\n\tdebug_out(debug_level::info, domain) << output\n\n/// Send an output to debug stream. \\see debug_out_dump().\n#define debug_out_warn(domain, output) \\\n\tdebug_out(debug_level::warn, domain) << output\n\n/// Send an output to debug stream. \\see debug_out_dump().\n#define debug_out_error(domain, output) \\\n\tdebug_out(debug_level::error, domain) << output\n\n/// Send an output to debug stream. \\see debug_out_dump().\n#define debug_out_fatal(domain, output) \\\n\tdebug_out(debug_level::fatal, domain) << output\n\n\n\n/// Start prefix printing. Useful for large dumps where you don't want prefixes to\n/// be printed on each debug_* call.\nvoid debug_begin();\n\n/// Stop prefix printing.\nvoid debug_end();\n\n\n\n\n// Source position tracking\n\n\nnamespace debug_internal {\n\n\t/// Source position object for sending to libdebug streams, prints source position.\n\tstruct DebugSourcePos {\n\n\t\t/// Constructor\n\t\tinline DebugSourcePos(std::string par_file, int line_, std::string par_func_name, std::string par_func)\n\t\t\t\t: func_name(std::move(par_func_name)), func(std::move(par_func)), line(line_), file(std::move(par_file))\n\t\t{\n\t\t\tenabled_types.set(debug_pos::func_name);\n\t\t\tenabled_types.set(debug_pos::line);\n\t\t\tenabled_types.set(debug_pos::file);\n\t\t}\n\n\t\t/// Formatted output string\n\t\t[[nodiscard]] std::string str() const;\n\n\n\t\tstd::string func_name;  ///< Function name only\n\t\tstd::string func;  ///< Function name with namespaces and classes\n\t\tint line = 0;  ///< Source line\n\t\tstd::string file;  ///< Source file\n\n\t\tdebug_pos::flags enabled_types;  ///< Enabled formatting types\n\t};\n\n\n\t/// Output operator\n\tinline std::ostream& operator<< (std::ostream& os, const DebugSourcePos& pos)\n\t{\n\t\treturn os << pos.str();\n\t}\n\n\n\t/// Format the function name\n\tinline std::string format_function_msg(const std::string& func, bool add_suffix)\n\t{\n\t\t// if it's \"bool<unnamed>::A::func(int)\" or \"bool test::A::func(int)\",\n\t\t// remove the return type and parameters.\n\t\tstd::string::size_type endpos = func.find('(');\n\t\tif (endpos == std::string::npos)\n\t\t\tendpos = func.size();\n\n\t\t// search for first space (after the parameter), or \"<unnamed>\".\n\t\tstd::string::size_type pos = func.find_first_of(\" >\");\n\t\tif (pos != std::string::npos) {\n\t\t\tif (func[pos] == '>')\n\t\t\t\tpos += 2;  // skip ::\n\t\t\t++pos;  // skip whatever character we're over\n\t\t\t// debug_out_info(\"default\", \"pos: \" << pos << \", endpos: \" << endpos << \"\\n\");\n\t\t\treturn func.substr(pos >= endpos ? 0 : pos, endpos - pos) + (add_suffix ? \"(): \" : \"()\");\n\t\t}\n\t\treturn func.substr(0, endpos) + (add_suffix ? \"(): \" : \"()\");\n\t}\n\n}\n\n\n\n\n// __BASE_FILE__, __FILE__, __LINE__, __func__, __FUNCTION__, __PRETTY_FUNCTION__\n\n// These two may seem pointless, but they actually help to implement\n// zero-overhead (if you define them to something else when libdebug is disabled).\n\n/// Current file as const char*.\n#define DBG_FILE __FILE__\n\n/// Current line as integer constant.\n#define DBG_LINE __LINE__\n\n\n/// \\def DBG_FUNC_NAME\n/// Function name (without classes / namespaces) only, e.g. \"main\", as const char*.\n#define DBG_FUNC_NAME __func__\n\n\n/// \\def DBG_FUNC_PRNAME\n/// Function pretty name is the whole function prototype,\n/// including return type and classes / namespaces, as const char*.\n#ifdef __GNUC__\n\t#define DBG_FUNC_PRNAME __PRETTY_FUNCTION__\n#else\n\t#define DBG_FUNC_PRNAME DBG_FUNC_NAME\n#endif\n\n\n/// \"class::function()\", as const char*.\n/// static_cast is needed to avoid clang-tidy errors about converting array to pointer.\n#define DBG_FUNC (debug_internal::format_function_msg(static_cast<const char*>(DBG_FUNC_PRNAME), false).c_str())\n\n/// \"class::function(): \", as const char*.\n/// static_cast is needed to avoid clang-tidy errors about converting array to pointer.\n#define DBG_FUNC_MSG (debug_internal::format_function_msg(static_cast<const char*>(DBG_FUNC_PRNAME), true).c_str())\n\n\n/// When sent into std::ostream, this object prints current source position.\n#define DBG_POS debug_internal::DebugSourcePos(DBG_FILE, DBG_LINE, static_cast<const char*>(DBG_FUNC_NAME), DBG_FUNC)\n\n\n/// A standalone function-like macro, prints \"Trace point \"a\" reached at (source position)\"\n/// (\\c a is the macro parameter).\n#define DBG_TRACE_POINT_MSG(a) debug_out_dump(\"default\", \"TRACE point \\\"\" << #a << \"\\\" reached at \" << DBG_POS << \".\\n\")\n\n/// A standalone function-like macro, prints \"Trace point reached at (source position)\".\n#define DBG_TRACE_POINT_AUTO debug_out_dump(\"default\", \"TRACE point reached at \" << DBG_POS << \".\\n\")\n\n\n/// A standalone function-like macro, prints \"ENTER: \"function_name\"\".\n#define DBG_FUNCTION_ENTER_MSG debug_out_dump(\"default\", \"ENTER: \\\"\" << DBG_FUNC << \"\\\"\\n\")\n\n/// A standalone function-like macro, prints \"EXIT:  \"function_name\"\".\n#define DBG_FUNCTION_EXIT_MSG debug_out_dump(\"default\", \"EXIT:  \\\"\" << DBG_FUNC << \"\\\"\\n\")\n\n\n/// A standalone function-like macro, prints \\c msg if \\c cond evaluates to false.\n#define DBG_ASSERT_MSG(cond, msg) \\\n\tif (true) { \\\n\t\tif (!(cond)) \\\n\t\t\tdebug_out_error(\"default\", (msg) << \"\\n\"); \\\n\t} else (void)0\n\n\n/// A standalone function-like macro, prints generic message if \\c cond evaluates to false.\n#define DBG_ASSERT(cond) \\\n\tif (true) { \\\n\t\tif (!(cond)) \\\n\t\t\tdebug_out_error(\"default\", \"ASSERTION FAILED: \" << #cond << \" at \" << DBG_POS << \"\\n\"); \\\n\t} else (void)0\n\n\n/// Prints generic message to error-level if assertion fails. Don't need to send into stream, it prints by itself.\n/// Returns from the function on assertion failure.\n#define DBG_ASSERT_RETURN(cond, return_value) \\\n\tdo { \\\n\t\tif (!(cond)) { \\\n\t\t\tdebug_out_error(\"default\", \"ASSERTION FAILED: \" << #cond << \" at \" << DBG_POS << \"\\n\"); \\\n\t\t\treturn (return_value); \\\n\t\t} \\\n\t} while(false)\n\n\n/// Prints generic message to error-level if assertion fails. Don't need to send into stream, it prints by itself.\n/// Returns from the function on assertion failure.\n#define DBG_ASSERT_RETURN_NONE(cond) \\\n\tdo { \\\n\t\tif (!(cond)) { \\\n\t\t\tdebug_out_error(\"default\", \"ASSERTION FAILED: \" << #cond << \" at \" << DBG_POS << \"\\n\"); \\\n\t\t\treturn; \\\n\t\t} \\\n\t} while(false)\n\n\n\n\n// ------------------ Indentation and manipulators\n\n\n\n/// Increase indentation level for all debug levels\nvoid debug_indent_inc(int by = 1);\n\n/// Decrease indentation level for all debug levels\nvoid debug_indent_dec(int by = 1);\n\n/// Reset indentation level to 0 for all debug levels\nvoid debug_indent_reset();\n\n\n\n\n\nnamespace debug_internal {\n\n\tstruct DebugIndent;\n\tstruct DebugUnindent;\n\tstruct DebugResetIndent;\n\n\n\t/// A stream manipulator that increases the indentation level\n\tstruct DebugIndent {\n\t\t/// Constructor\n\t\tconstexpr explicit DebugIndent(int indent_level = 1) : by(indent_level)\n\t\t{ }\n\n\t\t/// Constructs a new DebugIndent object\n\t\tDebugIndent operator() (int indent_level = 1)\n\t\t{\n\t\t\treturn DebugIndent(indent_level);\n\t\t}\n\n\t\tint by = 1;  ///< Number of indentation levels to increase with (may be negative)\n\t};\n\n\n\t/// A stream manipulator that decreases the indentation level\n\tstruct DebugUnindent {\n\t\t/// Constructor\n\t\tconstexpr explicit DebugUnindent(int unindent_level = 1) : by(unindent_level)\n\t\t{ }\n\n\t\t/// Constructs a new DebugUnindent object\n\t\tDebugUnindent operator() (int unindent_level = 1)\n\t\t{\n\t\t\treturn DebugUnindent(unindent_level);\n\t\t}\n\n\t\tint by = 1;  ///< Number of indentation levels to decrease with (may be negative)\n\t};\n\n\n\t/// A stream manipulator that resets the indentation level to 0\n\tstruct DebugResetIndent {\n\n\t\t/// Constructs a new DebugResetIndent object\n\t\tDebugResetIndent operator() ()  // just for consistency\n\t\t{\n\t\t\treturn *this;\n\t\t}\n\t};\n\n\n\n\t// These operators need to be inside the same namespace that their\n\t// operands are for ADL to work inside _other_ namespaces.\n\n\t/// A stream manipulator operator\n\tinline std::ostream& operator<< (std::ostream& os, const debug_internal::DebugIndent& m)\n\t{\n\t\tdebug_indent_inc(m.by);\n\t\treturn os;\n\t}\n\n\n\t/// A stream manipulator operator\n\tinline std::ostream& operator<< (std::ostream& os, const debug_internal::DebugUnindent& m)\n\t{\n\t\tdebug_indent_dec(m.by);\n\t\treturn os;\n\t}\n\n\n\t/// A stream manipulator operator\n\tinline std::ostream& operator<< (std::ostream& os,  [[maybe_unused]] const debug_internal::DebugResetIndent& m)\n\t{\n\t\tdebug_indent_reset();\n\t\treturn os;\n\t}\n\n\n}  // ns\n\n\n\n/// Manupulator object - send this to libdebug-backed stream to increase the indentation level by 1.\nconstexpr inline debug_internal::DebugIndent debug_indent;\n\n/// Manupulator object - send this to libdebug-backed stream to decrease the indentation level by 1.\nconstexpr inline debug_internal::DebugUnindent debug_unindent;\n\n/// Manupulator object - send this to libdebug-backed stream to reset the indentation level to 0.\nconstexpr inline debug_internal::DebugResetIndent debug_resindent;\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/dstate.cpp",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#include <iostream>  // std::cerr, the default output stream\n#include <map>\n\n#include \"dstate.h\"\n#include \"dflags.h\"\n#include \"dchannel.h\"\n#include \"dstream.h\"\n\n\n\nnamespace debug_internal {\n\n\n\n\tvoid DebugState::setup_default_state() noexcept\n\t{\n\t\t// defaults:\n\t\tdebug_level::flags levels_enabled;\n\t\tlevels_enabled.set(debug_level::warn);\n\t\tlevels_enabled.set(debug_level::error);\n\t\tlevels_enabled.set(debug_level::fatal);\n#ifdef DEBUG_BUILD\n\t\tlevels_enabled.set(debug_level::dump);\n\t\tlevels_enabled.set(debug_level::info);\n#endif\n\n\t\t// default format\n\t\tdebug_format::flags format_flags;\n\t\tformat_flags.set(debug_format::level);\n\t\tformat_flags.set(debug_format::domain);\n\t\tformat_flags.set(debug_format::indent);\n#ifndef _WIN32\n\t\tformat_flags.set(debug_format::color);\n#endif\n\n\t\tconst std::map<debug_level::flag, bool> levels = {\n\t\t\t\t{debug_level::dump, levels_enabled.test(debug_level::dump) },\n\t\t\t\t{debug_level::info, levels_enabled.test(debug_level::info) },\n\t\t\t\t{debug_level::warn, levels_enabled.test(debug_level::warn) },\n\t\t\t\t{debug_level::error, levels_enabled.test(debug_level::error) },\n\t\t\t\t{debug_level::fatal, levels_enabled.test(debug_level::fatal) },\n\t\t};\n\n\t\tDomainMap& dm = get_domain_map_ref();\n\n\t\tdm[\"default\"] = {};\n\n\t\tLevelMap& level_map = dm.find(\"default\")->second;\n\n\t\t// we add the same copy to save memory and to ensure proper std::cerr locking.\n\t\tauto channel = std::make_shared<DebugChannelOStream>(std::cerr);\n\n\t\tfor (const auto& [level, enabled] : levels) {\n\t\t\tlevel_map[level] = std::make_shared<DebugOutStream>(level, \"default\", format_flags);\n\n\t\t\tlevel_map[level]->add_channel(channel);  // add by smartpointer\n\t\t\tlevel_map[level]->set_enabled(enabled);\n\t\t}\n\t}\n\n\n\n\t/// Global libdebug state.\n\t/// This will initialize the default domain and channels automatically.\n\tDebugState& get_debug_state_ref()\n\t{\n\t\tstatic DebugState state;\n\t\treturn state;\n\t}\n\n\n}\n\n\n\n\n\nbool debug_register_domain(const std::string& domain)\n{\n\tusing namespace debug_internal;\n\n\tDebugState::DomainMap& dm = get_debug_state_ref().get_domain_map_ref();\n\n\tif (dm.find(domain) != dm.end())  // already exists\n\t\treturn false;\n\n\t// copy the \"default\" domain - use it as a template\n\tauto def_iter = dm.find(\"default\");\n\tif (def_iter == dm.end()) {\n\t\tthrow debug_internal_error((\"debug_register_domain(\\\"\" + domain\n\t\t\t+ \"\\\"): Domain \\\"default\\\" doesn't exist.\").c_str());\n\t}\n\n\tconst DebugState::LevelMap& def_level_map = def_iter->second;\n\n\tdm[domain] = DebugState::LevelMap();\n\tDebugState::LevelMap& level_map = dm.find(domain)->second;\n\n\tfor (const auto& iter : def_level_map) {\n\t\tlevel_map[iter.first] = std::make_shared<DebugOutStream>(*(iter.second), domain);\n\t}\n\n\treturn true;\n}\n\n\n\nbool debug_unregister_domain(const std::string& domain)\n{\n\tusing namespace debug_internal;\n\tDebugState::DomainMap& dm = get_debug_state_ref().get_domain_map_ref();\n\n\tauto found = dm.find(domain);\n\tif (found == dm.end())  // doesn't exists\n\t\treturn false;\n\n\tdm.erase(found);  // this should clear everything - it's all smartpointers\n\treturn true;\n}\n\n\n\nstd::vector<std::string> debug_get_registered_domains()\n{\n\tusing namespace debug_internal;\n\tDebugState::DomainMap& dm = get_debug_state_ref().get_domain_map_ref();\n\n\tstd::vector<std::string> domains;\n\tdomains.reserve(dm.size());\n\tfor (const auto& iter : dm)\n\t\tdomains.push_back(iter.first);\n\n\treturn domains;\n}\n\n\n\n\n\nbool debug_set_enabled(const std::string& domain, const debug_level::flags& levels, bool enabled)\n{\n\tusing namespace debug_internal;\n\tDebugState::DomainMap& dm = get_debug_state_ref().get_domain_map_ref();\n\n\tif (domain == \"all\") {\n\t\tbool status = true;\n\t\tfor (auto& iter : dm)\n\t\t\tstatus = status && debug_set_enabled(iter.first, levels, enabled);\n\t\treturn status;\n\t}\n\n\tauto found = dm.find(domain);\n\tif (found == dm.end())  // doesn't exists\n\t\treturn false;\n\n\tstd::vector<debug_level::flag> matched_levels;\n\tdebug_level::get_matched_levels_array(levels, matched_levels);\n\tfor (auto matched_level : matched_levels) {\n\t\tfound->second[matched_level]->set_enabled(enabled);\n\t}\n\n\treturn true;\n}\n\n\n\ndebug_level::flags debug_get_enabled(const std::string& domain)\n{\n\tusing namespace debug_internal;\n\tDebugState::DomainMap& dm = get_debug_state_ref().get_domain_map_ref();\n\n\tdebug_level::flags levels;\n\n\tauto found = dm.find(domain);\n\tif (found == dm.end())  // doesn't exist\n\t\treturn levels;\n\n\tDebugState::LevelMap& level_map = found->second;\n\n\tfor (const auto& [level, stream] : level_map) {\n\t\tif (stream->get_enabled())\n\t\t\tlevels.set(level);\n\t}\n\n\treturn levels;\n}\n\n\n\n\nbool debug_set_format(const std::string& domain, const debug_level::flags& levels, const debug_format::flags& format)\n{\n\tusing namespace debug_internal;\n\tDebugState::DomainMap& dm = get_debug_state_ref().get_domain_map_ref();\n\n\tif (domain == \"all\") {\n\t\tbool status = true;\n\t\tfor (const auto& iter : dm)\n\t\t\tstatus = status && debug_set_format(iter.first, levels, format);\n\t\treturn status;\n\t}\n\n\tauto found = dm.find(domain);\n\tif (found == dm.end())  // doesn't exists\n\t\treturn false;\n\n\tstd::vector<debug_level::flag> matched_levels;\n\tdebug_level::get_matched_levels_array(levels, matched_levels);\n\tfor (auto matched_level : matched_levels) {\n\t\tfound->second[matched_level]->set_format(format);\n\t}\n\n\treturn true;\n}\n\n\n\nstd::map<debug_level::flag, debug_format::flags> debug_get_formats(const std::string& domain)\n{\n\tusing namespace debug_internal;\n\tDebugState::DomainMap& dm = get_debug_state_ref().get_domain_map_ref();\n\n\tstd::map<debug_level::flag, debug_format::flags> formats;\n\n\tauto found = dm.find(domain);\n\tif (found == dm.end())  // doesn't exists\n\t\treturn formats;\n\n\tfor (const auto& iter : found->second)\n\t\tformats[iter.first] = iter.second->get_format();\n\n\treturn formats;\n}\n\n\n\n\nbool debug_add_channel(const std::string& domain, const debug_level::flags& levels, const DebugChannelBasePtr& channel)\n{\n\tusing namespace debug_internal;\n\tDebugState::DomainMap& dm = get_debug_state_ref().get_domain_map_ref();\n\n\tif (domain == \"all\") {\n\t\tbool status = true;\n\t\tfor (const auto& iter : dm)\n\t\t\tstatus = status && debug_add_channel(iter.first, levels, channel);\n\t\treturn status;\n\t}\n\n\tauto found = dm.find(domain);\n\tif (found == dm.end())  // doesn't exists\n\t\treturn false;\n\n\tstd::vector<debug_level::flag> matched_levels;\n\tdebug_level::get_matched_levels_array(levels, matched_levels);\n\tfor (auto matched_level : matched_levels) {\n\t\tfound->second[matched_level]->add_channel(channel);\n\t}\n\n\treturn true;\n}\n\n\n\n\nbool debug_clear_channels(const std::string& domain, const debug_level::flags& levels)\n{\n\tusing namespace debug_internal;\n\tDebugState::DomainMap& dm = get_debug_state_ref().get_domain_map_ref();\n\n\tif (domain == \"all\") {\n\t\tbool status = true;\n\t\tfor (const auto& iter : dm)\n\t\t\tstatus = status && debug_clear_channels(iter.first, levels);\n\t\treturn status;\n\t}\n\n\tauto found = dm.find(domain);\n\tif (found == dm.end())  // doesn't exists\n\t\treturn false;\n\n\tstd::vector<debug_level::flag> matched_levels;\n\tdebug_level::get_matched_levels_array(levels, matched_levels);\n\tfor (auto matched_level : matched_levels) {\n\t\tfound->second[matched_level]->set_channels(std::vector<DebugChannelBasePtr>());\n\t}\n\n\treturn true;\n}\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/dstate.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#ifndef LIBDEBUG_DSTATE_H\n#define LIBDEBUG_DSTATE_H\n\n#include <string>\n#include <map>\n#include <stack>\n#include <memory>\n\n#include \"dflags.h\"\n#include \"dstream.h\"\n#include \"dexcept.h\"\n\n#include \"dstate_pub.h\"  // public interface part of this header\n\n\n\nnamespace debug_internal {\n\n\n\t// domain name \"default\" - template for all new domains.\n\t// domain name \"all\" - used for manipulating all domains.\n\n\n\t/// Libdebug global state\n\tclass DebugState {\n\t\tpublic:\n\n\t\t\t/// A mapping of debug levels to respective streams\n\t\t\tusing LevelMap = std::map<debug_level::flag, std::shared_ptr<DebugOutStream>>;\n\n\t\t\t/// A mapping of domains to debug level maps with streams\n\t\t\tusing DomainMap = std::map<std::string, LevelMap>;\n\n\n\t\t\t/// Constructor (statically called), calls setup_default_state().\n\t\t\tDebugState() noexcept\n\t\t\t{\n\t\t\t\tsetup_default_state();\n\t\t\t}\n\n\n\t\t\t/// Initialize the \"default\" template domain, set the default enabled levels / format flags.\n\t\t\t/// Automatically called by constructor.\n\t\t\tvoid setup_default_state() noexcept;\n\n\n\t\t\t/// Get the domain/level mapping.\n\t\t\t[[nodiscard]] DomainMap& get_domain_map_ref()\n\t\t\t{\n\t\t\t\treturn domain_map;\n\t\t\t}\n\n\n\t\t\t/// Get current indentation level.\n\t\t\t[[nodiscard]] int get_indent_level() const\n\t\t\t{\n\t\t\t\treturn indent_level_;\n\t\t\t}\n\n\t\t\t/// Set current indentation level.\n\t\t\tvoid set_indent_level(int indent_level)\n\t\t\t{\n\t\t\t\tindent_level_ = indent_level;\n\t\t\t}\n\n\t\t\t/// Open a debug_begin() context.\n\t\t\tvoid push_inside_begin(bool value = true)\n\t\t\t{\n\t\t\t\tinside_begin_.push(value);\n\t\t\t}\n\n\t\t\t/// Close a debug_begin() context.\n\t\t\tbool pop_inside_begin()\n\t\t\t{\n\t\t\t\tif (inside_begin_.empty())\n\t\t\t\t\tthrow debug_usage_error(\"DebugState::pop_inside_begin(): Begin / End stack underflow! Mismatched begin()/end()?\");\n\t\t\t\tconst bool val = inside_begin_.top();\n\t\t\t\tinside_begin_.pop();\n\t\t\t\treturn val;\n\t\t\t}\n\n\t\t\t/// Check if we're inside a debug_begin() context.\n\t\t\t[[nodiscard]] bool get_inside_begin() const\n\t\t\t{\n\t\t\t\tif (inside_begin_.empty())\n\t\t\t\t\treturn false;\n\t\t\t\treturn inside_begin_.top();\n\t\t\t}\n\n\n\t\t\t/// Flush all the stream buffers. This will write prefixes too.\n\t\t\tvoid force_output()\n\t\t\t{\n\t\t\t\tfor (auto& iter : domain_map) {\n\t\t\t\t\tfor (auto& iter2 : iter.second)\n\t\t\t\t\t\titer2.second->force_output();\n\t\t\t\t}\n\t\t\t}\n\n\n\t\tprivate:\n\n\t\t\tint indent_level_ = 0;  ///< Current indentation level\n\t\t\tstd::stack<bool> inside_begin_;  ///< True if inside debug_begin() / debug_end() block\n\n\t\t\tDomainMap domain_map;  ///< Domain / debug level mapping.\n\n\t};\n\n\n\n\t/// Get global libdebug state\n\t[[nodiscard]] DebugState& get_debug_state_ref();\n\n\n\n}  // ns\n\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/dstate_pub.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#ifndef LIBDEBUG_DSTATE_PUB_H\n#define LIBDEBUG_DSTATE_PUB_H\n\n#include <string>\n#include <map>\n#include <vector>\n\n#include \"dflags.h\"\n#include \"dchannel.h\"\n\n/**\n\\file\nPublic interface of dstate.h\n*/\n\n\n/// Register a libdebug output domain. Domains are like components\n/// of an application, and libdebug flags for each one can be manipulated\n/// separately. This will use the \"default\" domain as a template.\n/// \\return false if the domain is registered already.\nbool debug_register_domain(const std::string& domain);\n\n/// Unregister a previously registered domain.\n/// \\return false if no such domain\nbool debug_unregister_domain(const std::string& domain);\n\n/// Get a list of registered domain\nstd::vector<std::string> debug_get_registered_domains();\n\n\n/// Enable/disable output streams. Set the domain to \"all\" for all domains.\n/// Multiple levels may be passed (OR'ed), as well as debug_level::all.\nbool debug_set_enabled(const std::string& domain, const debug_level::flags& levels, bool enabled);\n\n/// See which levels are enabled for domain.\ndebug_level::flags debug_get_enabled(const std::string& domain);\n\n\n/// Set format flags for domain / level. Set the domain to \"all\" for all domains.\n/// Multiple levels may be passed (OR'ed), as well as debug_level::all.\nbool debug_set_format(const std::string& domain, const debug_level::flags& levels, const debug_format::flags& format);\n\n/// Get all enabled format flags for each level in a domain\nstd::map<debug_level::flag, debug_format::flags> debug_get_formats(const std::string& domain);\n\n\n/// Add a new output channel to domain. Set the domain to \"all\" for all domains.\n/// Multiple levels may be passed (OR'ed), as well as debug_level::all.\nbool debug_add_channel(const std::string& domain, const debug_level::flags& levels, const DebugChannelBasePtr& channel);\n\n/// Remove all output channels from domain. Set the domain to \"all\" for all domains.\n/// Multiple levels may be passed (OR'ed), as well as debug_level::all.\nbool debug_clear_channels(const std::string& domain, const debug_level::flags& levels);\n\n\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/dstream.cpp",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#include <ostream>  // std::ostream definition\n\n#include \"dstream.h\"\n#include \"dstate.h\"\n#include \"dchannel.h\"\n\n// #include <iostream>  // tmp\n\n\n\nnamespace debug_internal {\n\n\t/// Null stream buf - discards anything that is sent to it.\n\tclass NullStreamBuf : public std::streambuf {\n\n\t\tprotected:\n\t\t\tint overflow([[maybe_unused]] int unused) override { return 0; }\n\t\t\tint sync() override { return 0; }\n\t};\n\n\n\n\t/// Get null streambuf, see s_null_streambuf.\n\tstd::streambuf& get_null_streambuf()\n\t{\n\t\tstatic NullStreamBuf s_null_streambuf;\n\t\treturn s_null_streambuf;\n\t}\n\n\t/// Null ostream - discards anything that is sent to it.\n\tstd::ostream& get_null_stream()\n\t{\n\t\tstatic std::ostream s_null_stream(&get_null_streambuf());\n\t\treturn s_null_stream;\n\t}\n\n\n\n\tvoid DebugStreamBuf::flush_to_channel()\n\t{\n\t\tdebug_format::flags flags = dos_->format_;\n\t\tbool is_first_line = false;\n\t\tif (get_debug_state_ref().get_inside_begin()) {\n\t\t\tflags.set(debug_format::first_line_only);\n\t\t\tif (dos_->get_is_first_line()) {\n\t\t\t\tdos_->set_is_first_line(false);  // tls\n\t\t\t\tis_first_line = true;\n\t\t\t}\n\t\t} else {\n\t\t\tdos_->set_is_first_line(true);\n\t\t\tis_first_line = true;\n\t\t}\n\n\t\tfor (auto& channel : dos_->channels_) {\n\t\t\t// send() locks the channel if needed\n\t\t\tchannel->send(dos_->level_, dos_->domain_, flags,\n\t\t\t\t\tget_debug_state_ref().get_indent_level(), is_first_line, oss_.str());\n\t\t}\n\t\toss_.str(\"\");  // clear the buffer\n\t\toss_.clear();  // clear the flags\n\t}\n\n\n\n}\n\n\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/dstream.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#ifndef LIBDEBUG_DSTREAM_H\n#define LIBDEBUG_DSTREAM_H\n\n#include <ostream>  // std::ostream definition\n#include <streambuf>  // std::streambuf definition\n#include <cstdio>\n#include <string>\n#include <sstream>\n#include <utility>\n#include <vector>\n\n#include \"dflags.h\"\n#include \"dchannel.h\"\n\n\n\nnamespace debug_internal {\n\n\n\t/// Get null streambuf - a streambuf which does nothing.\n\t[[nodiscard]] std::streambuf& get_null_streambuf();\n\n\t/// Get null ostream - an ostream which does nothing.\n\t[[nodiscard]] std::ostream& get_null_stream();\n\n\n\t// state.h includes us, so we need these forward declarations\n// \tclass DebugState;\n// \t[[nodiscard]] DebugState& get_debug_state();\n\n\n\n\tclass DebugOutStream;\n\n\n\t/// Streambuf for libdebug, used in DebugOutStream implementation.\n\tclass DebugStreamBuf : public std::streambuf {\n\t\tpublic:\n\n\t\t\t/// Constructor\n\t\t\texplicit DebugStreamBuf(DebugOutStream* dos) : dos_(dos)\n\t\t\t{\n\t\t\t\t// in case of overflow for output, overflow() will be called to _output_ the data.\n\n\t\t\t\t// no buffers here - we process it char-by-char.\n\t\t\t\t// std::size_t buf_size = 0;\n\t\t\t\t// if (buf_size) {\n\t\t\t\t// \tchar* buf = new char[buf_size];  // further accessible through pbase().\n\t\t\t\t// \tsetp(buf, buf + buf_size);  // Set output sequence pointers, aka output buffer\n\t\t\t\t// } else {\n\t\t\t\t\tsetp(nullptr, nullptr);\n\t\t\t\t// }\n\n\t\t\t\tsetg(nullptr, nullptr, nullptr);  // Set input sequence pointers; not relevant in this class.\n\t\t\t}\n\n\n\t\t\t/// Deleted\n\t\t\tDebugStreamBuf(const DebugStreamBuf& other) = delete;\n\n\t\t\t/// Deleted\n\t\t\tDebugStreamBuf(DebugStreamBuf&& other) = delete;\n\n\t\t\t/// Deleted\n\t\t\tDebugStreamBuf& operator=(const DebugStreamBuf&) = delete;\n\n\t\t\t/// Deleted\n\t\t\tDebugStreamBuf& operator=(DebugStreamBuf&&) = delete;\n\n\n\t\t\t/// Virtual destructor\n\t\t\t~DebugStreamBuf() override\n\t\t\t{\n\t\t\t\tsync();\n\t\t\t\tdelete[] pbase();  // delete the buffer\n\t\t\t}\n\n\n\t\t\t/// Force output of the stringstream's contents to the channels.\n\t\t\tvoid force_output()\n\t\t\t{\n\t\t\t\tflush_to_channel();\n\t\t\t}\n\n\n\t\tprotected:\n\n\t\t\t/// Overflow happens when a new character is to be written at the put\n\t\t\t/// pointer pptr position, but this has reached the end pointer epptr.\n\t\t\t/// Reimplemented.\n\t\t\tint overflow(int c) override\n\t\t\t{\n\t\t\t\tsync();  // write the buffer contents if available\n\t\t\t\tif (c != traits_type::eof()) {\n\t\t\t\t\tif (pbase() == epptr()) {  // no buffer, write it char-by-char (epptr() - buffer end pointer)\n\t// \t\t\t\t\tstd::string tmp;\n\t// \t\t\t\t\ttmp += char(c);\n\t// \t\t\t\t\twrite_out(tmp);\n\t\t\t\t\t\twrite_char(char(c));\n\t\t\t\t\t} else {  // we have a buffer\n\t\t\t\t\t\t// put c into buffer (the overflowed char); the rest is written in sync() earlier.\n\t\t\t\t\t\tsputc(static_cast<char>(c));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t\t}\n\n\n\t\t\t/// Sort-of flush the buffer. Only makes sense if there is a buffer.\n\t\t\t/// Reimplemented.\n\t\t\tint sync() final\n\t\t\t{\n\t\t\t\tif (pbase() != pptr()) {  // pptr() - current position; condition is true only if there is something in the buffer.\n\t// \t\t\t\twrite_out(std::string(pbase(), pptr() - pbase()));\n\t\t\t\t\tfor (char* pos = pbase(); pos != pptr(); ++pos)\n\t\t\t\t\t\twrite_char(*pos);\n\t\t\t\t\tsetp(pbase(), epptr());  // reset the buffer's current pointer (?)\n\t\t\t\t}\n\t\t\t\treturn 0;\n\t\t\t}\n\n\n\t\t\t/// Write contents if necessary.\n\t\t\tvoid write_char(char c)\n\t\t\t{\n\t\t\t\toss_ << c;\n\t\t\t\tif (c == '\\n')  // send to channels on newline\n\t\t\t\t\tflush_to_channel();\n\t\t\t}\n\n\n\t\t\t/// Flush contents to debug channel.\n\t\t\tvoid flush_to_channel();\n\n\n\t\tprivate:\n\n\t\t\tDebugOutStream* dos_ = nullptr;  ///< Debug output stream\n\n\t\t\tstd::ostringstream oss_;  ///< A buffer for output storage.\n\n\t};\n\n\n\n\n\t/// Debug output stream (inherits std::ostream).\n\t/// This is returned by debug_out().\n\tclass DebugOutStream : public std::ostream {\n\t\tpublic:\n\n\t\t\tfriend class DebugStreamBuf;\n\n\t\t\t/// Constructor\n\t\t\tDebugOutStream(debug_level::flag level, std::string domain, const debug_format::flags& format_flags)\n\t\t\t\t\t: std::ostream(nullptr), level_(level), domain_(std::move(domain)), format_(format_flags), buf_(this)\n\t\t\t{\n\t\t\t\tset_enabled(true);  // sets ostream's rdbuf\n\t\t\t}\n\n// \t\t\tDebugOutStream() : std::ostream(nullptr), buf_(this)\n// \t\t\t{\n// \t\t\t\tset_enabled(false);\n// \t\t\t}\n\n\t\t\t/// Construct with settings from another DebugOutStream.\n\t\t\tDebugOutStream(const DebugOutStream& other, std::string domain)\n\t\t\t\t\t: std::ostream(nullptr), level_(other.level_), domain_(std::move(domain)), format_(other.format_), buf_(this)\n\t\t\t{\n\t\t\t\tset_enabled(other.get_enabled());  // sets ostream's rdbuf\n\t\t\t\tfor (const auto& channel : other.channels_) {\n\t\t\t\t\tchannels_.push_back(channel);\n\t\t\t\t}\n\t\t\t}\n\n/*\n\t\t\tvoid set_level(debug_level::flag level)\n\t\t\t{\n\t\t\t\tlevel_ = level;\n\t\t\t}\n\n\t\t\tvoid set_domain(const std::string& domain)\n\t\t\t{\n\t\t\t\tdomain_ = domain;\n\t\t\t}\n*/\n\n\t\t\t/// Set format flags\n\t\t\tvoid set_format(const debug_format::flags& format_flags)\n\t\t\t{\n\t\t\t\tformat_ = format_flags;\n\t\t\t}\n\n\t\t\t/// Get format flags\n\t\t\t[[nodiscard]] debug_format::flags get_format() const\n\t\t\t{\n\t\t\t\treturn format_;\n\t\t\t}\n\n\n\t\t\t/// Enable or disable output. If disabled, any data sent to this\n\t\t\t/// stream is discarded.\n\t\t\tvoid set_enabled(bool enabled)\n\t\t\t{\n\t\t\t\tif (enabled) {\n\t\t\t\t\trdbuf(&buf_);\n\t\t\t\t} else {\n\t\t\t\t\trdbuf(&get_null_streambuf());\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t/// Check whether the stream is enabled or not.\n\t\t\t[[nodiscard]] bool get_enabled() const\n\t\t\t{\n\t\t\t\treturn (rdbuf() == &buf_);\n\t\t\t}\n\n\n\t\t\t/// Set channel list to send the data to.\n\t\t\tvoid set_channels(std::vector<DebugChannelBasePtr> channels)\n\t\t\t{\n\t\t\t\tchannels_ = std::move(channels);\n\t\t\t}\n\n\t\t\t/// Get channel list\n\t\t\t[[nodiscard]] std::vector<DebugChannelBasePtr>& get_channels()\n\t\t\t{\n\t\t\t\treturn channels_;\n\t\t\t}\n\n\t\t\t/// Add a channel to channel list.\n\t\t\t/// This will claim the ownership of the passed parameter.\n\t\t\tvoid add_channel(DebugChannelBasePtr channel)\n\t\t\t{\n\t\t\t\tchannels_.push_back(channel);\n\t\t\t}\n\n\n\t\t\t/// Check if the last sent output is still on the same line\n\t\t\t/// as the first one.\n\t\t\t[[nodiscard]] bool get_is_first_line() const\n\t\t\t{\n\t\t\t\treturn is_first_line_;\n\t\t\t}\n\n\t\t\t/// Set whether we're on the first line of the output or not.\n\t\t\tvoid set_is_first_line(bool b)\n\t\t\t{\n\t\t\t\tis_first_line_ = b;\n\t\t\t}\n\n\n\t\t\t/// Force output of buf_'s contents to the channels.\n\t\t\t/// This also outputs a prefix if needed.\n\t\t\tstd::ostream& force_output()\n\t\t\t{\n\t\t\t\tbuf_.force_output();\n\t\t\t\treturn *this;\n\t\t\t}\n\n\n\t\tprivate:\n\n\t\t\tdebug_level::flag level_ = debug_level::dump;  ///< Debug level of this stream\n\t\t\tstd::string domain_;  ///< Domain of this stream\n\t\t\tdebug_format::flags format_;  ///< Format flags\n\n\t\t\tbool is_first_line_ = true;  ///< Whether it's the first line of output or not\n\n\t\t\tstd::vector<DebugChannelBasePtr> channels_;  ///< Channels that the output is sent to\n\n\t\t\tDebugStreamBuf buf_;  /// Streambuf for implementation.\n\t};\n\n\n\n\n\n}  // ns debug_internal\n\n\n\n\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/examples/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nif (NOT APP_BUILD_EXAMPLES)\n    set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL true)\nelse()\n    set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL false)\nendif()\n\n\nadd_executable(example_libdebug)\ntarget_sources(example_libdebug PRIVATE\n\texample_libdebug.cpp\n)\ntarget_link_libraries(example_libdebug PRIVATE\n\tlibdebug\n)\n\n"
  },
  {
    "path": "src/libdebug/examples/example_libdebug.cpp",
    "content": "/******************************************************************************\nLicense: BSD Zero Clause License\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug_examples\n/// \\weakgroup libdebug_examples\n/// @{\n\n// The first header should be then one we're testing, to avoid missing\n// header pitfalls.\n#include \"libdebug/libdebug.h\"\n\n#include <iostream>\n\n#include \"hz/main_tools.h\"\n\n\n\n/// libdebug namespace for tests\nnamespace libdebug_example {\n\n\t/// Libdebug test class\n\tstruct TestClassA {\n\t\tbool func([[maybe_unused]] int a = 2)\n\t\t{\n\t\t\t// this prints \"func\"\n\t\t\tdebug_out_info(\"default\", DBG_FUNC_NAME << \"\\n\");\n\n\t\t\t// this prints \"bool<unnamed>::TestClassA::func()\" if in anonymous namespace,\n\t\t\t// else \"bool test::TestClass::func()\".\n\t\t\tdebug_out_info(\"default\", DBG_FUNC_PRNAME << \"\\n\");\n\n\t\t\t// prints \"test::TestClassA::func(): function called.\"\n\t\t\tdebug_out_info(\"default\", DBG_FUNC_MSG << \"function called.\\n\");\n\n\t\t\treturn true;\n\t\t}\n\t};\n\n}\n\n\nnamespace {\n\n\t/// Libdebug test struct\n\ttemplate<typename U>\n\tstruct TestClassB {\n\t\ttemplate<typename V>\n\t\tU func2([[maybe_unused]] V v, [[maybe_unused]] int i)\n\t\t{\n\t\t\tdebug_out_info(\"default\", DBG_FUNC_PRNAME << \"\\n\");\n\t\t\tdebug_out_info(\"default\", DBG_FUNC_MSG << \"function called.\\n\");\n\t\t\treturn 0;\n\t\t}\n\t};\n\n\t/// Libdebug test struct\n\ttemplate<typename T>\n\tstruct TestClassC { };\n\n}\n\n\n\n/// A separate function is needed to avoid clang-tidy complaining about capturing __function__ from lambda.\nint main_impl()\n{\n\tdebug_register_domain(\"dom\");\n\n\tdebug_set_enabled(\"dom\", debug_level::dump, false);\n\tdebug_set_format(\"dom\", debug_level::info,\n\t\t\t(debug_get_formats(\"dom\")[debug_level::info]\n\t\t\t.reset(debug_format::color)\n\t\t\t.set(debug_format::datetime)\n\t));\n\n\n\tconst std::string something = \"some thing\";\n\tconst char* obj = \"obj\";\n\tconst int op = 5;\n\n\n\tdebug_out_dump(\"dom\", \"Dumping something: \" << something << std::endl);\n\tdebug_out_info(\"dom\", \"Doing something: \" << something << std::endl);\n\tdebug_out_error(\"dom\", \"Error while doing something\\n\");\n\n\tdebug_out_info(\"dom\", \"Doing something with \" << obj << \" object\\n\");\n\tdebug_out_fatal(\"dom\", \"Fatal error while performing operation \" << op << \"\\n\");\n\n\n\tDBG_ASSERT_MSG(1 == 0, \"One does not equal 0\");\n\tDBG_ASSERT(1 == 0);\n\n\tdebug_out_dump(\"default\", DBG_POS << \"\\n\");\n\tdebug_out_dump(\"default\", DBG_POS.func << \"\\n\");\n\n\n\tDBG_TRACE_POINT_MSG(1);\n\tDBG_TRACE_POINT_MSG(666 a);\n\n\tDBG_TRACE_POINT_AUTO;\n\tDBG_TRACE_POINT_AUTO;\n\n// \tStr s;\n// \ts.somefunc(1, 1);\n\n\n\n\t// these begin()/end() turn off prefix printing\n\tdebug_begin();  // don't use different levels inside, or they might get merged (the order will be different).\n\t\tdebug_out_info(\"default\", \"The following lines should have no prefixes\\n\");\n\t\tdebug_out_info(\"default\", \"1st line\\n\" << \"2nd line\\n\");\n\t\tdebug_out_error(\"default\", \"3rd line, error, prefixed\\n\");\n\t\tdebug_out_info(\"default\", debug_indent << \"4th line, not prefixed\\n\");\n\t\tdebug_out_warn(\"default\", \"5th line, warning, prefixed\\n\");\n\t\tdebug_out_warn(\"default\", \"6th line, warning, not prefixed\\n\");\n\t\tdebug_indent_dec();  // or use << debug_unindent\n\tdebug_end();\n\n\tdebug_out_info(\"default\", \"prefixed\\n\");\n\n\n\n\tstd::ostream& os = debug_out_dump(\"default\", \"\");  // get the ostream\n\tos << \"\";\n\n\n\tlibdebug_example::TestClassA().func();\n\n\tTestClassB<unsigned int>().func2(TestClassC<char*>(), 0);\n\n\t// \tdebug_out_warn(\"default\", DBG_FUNC_MSG << \"Doing something.\\n\");\n\t// \tdebug_out_warn(\"default\", DBG_FUNC_MSG << \"Doing something.\\n\");\n\n\treturn EXIT_SUCCESS;\n}\n\n\n\n/// Main function for the test\nint main()\n{\n\treturn hz::main_exception_wrapper([]()\n\t{\n\t\treturn main_impl();\n\t});\n}\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/libdebug.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#ifndef LIBDEBUG_LIBDEBUG_H\n#define LIBDEBUG_LIBDEBUG_H\n\n/**\n\\file\nMain libdebug include file, includes complete libdebug functionality.\n\\see libdebug_mini.h\n*/\n\n// These macros may be used to control how libdebug is built:\n\n/// \\def ENABLE_GLIB\n/// Define to 1 to enable Glib option parsing support.\n\n/// \\def DEBUG_BUILD\n/// Define to 1 to enable all levels by default.\n\n\n/// \\namespace debug_internal\n/// Libdebug internal implementation details\n\n\n\n// all libdebug headers:\n\n#include \"dchannel.h\"\n#include \"dcmdarg.h\"\n#include \"dexcept.h\"\n#include \"dflags.h\"\n#include \"dout.h\"\n#include \"dstate_pub.h\"\n// #include \"dstream.h\"  // no dstream - it's internal only\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/libdebug/libdebug_mini.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup libdebug\n/// \\weakgroup libdebug\n/// @{\n\n#ifndef LIBDEBUG_LIBDEBUG_MINI_H\n#define LIBDEBUG_LIBDEBUG_MINI_H\n\n\n/**\n\\file\nYou may include this instead of libdebug.h if you only need the output\nfunctions and not control functions. This may speed up compilation\ntimes because it avoids many dependencies.\n\\see libdebug.h\n*/\n\n\n\n// output-related files only\n#include \"dflags.h\"\n#include \"dout.h\"\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/rconfig/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nadd_library(rconfig INTERFACE)\n\n# Relative sources are allowed only since cmake 3.13.\ntarget_sources(rconfig INTERFACE\n\t${CMAKE_CURRENT_SOURCE_DIR}/autosave.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/loadsave.h\n\t${CMAKE_CURRENT_SOURCE_DIR}/rconfig.h\n)\n\ntarget_include_directories(rconfig INTERFACE \"${CMAKE_SOURCE_DIR}/src\")\n\ntarget_link_libraries(rconfig\n\tINTERFACE\n\t\thz\n\t\tnlohmann_json\n\t\tapp_gtkmm_interface\n)\n\n\nadd_subdirectory(examples)\n\n"
  },
  {
    "path": "src/rconfig/autosave.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup rconfig\n/// \\weakgroup rconfig\n/// @{\n\n#ifndef RCONFIG_AUTOSAVE_H\n#define RCONFIG_AUTOSAVE_H\n\n// Autosave functions are only available if GLib is enabled.\n#if !defined ENABLE_GLIB || !(ENABLE_GLIB)\n\t#error \"Glib support must be enabled.\"\n#endif\n\n#include <string>\n#include <chrono>\n#include <glib.h>\n\n#include \"hz/debug.h\"\n#include \"hz/fs.h\"\n\n#include \"loadsave.h\"\n\n\n// TODO gtkmm4: Port this to Glib::SignalTimeout\n\n\n\nnamespace rconfig {\n\n\nnamespace impl {\n\t\n\tinline hz::fs::path autosave_config_file;  ///< Config file to autosave to.\n\tinline bool autosave_enabled = false;  ///< Autosave enabled or not. This acts as a stopper flag for autosave callback.\n\n}\n\n\n\nextern \"C\" {\n\n\t/// Data for autosave_timeout_callback() function\n\tstruct AutosaveCallbackData {\n\t\tbool force = true;\n\t};\n\n\n\n\t/// Autosave timeout callback for Glib. internal.\n\tinline gboolean autosave_timeout_callback(void* data)\n\t{\n\t\tbool force = false;\n\t\tif (data) {\n\t\t\tforce = reinterpret_cast<AutosaveCallbackData*>(data)->force;\n\t\t}\n\n\t\tif (!force && !impl::autosave_enabled)  // no more autosaves\n\t\t\treturn FALSE;  // remove timeout, disable autosave for real.\n\n\t\tauto file = impl::autosave_config_file;\n\t\tdebug_out_info(\"rconfig\", \"Autosaving config to \\\"\" << file << \"\\\".\" << std::endl);\n\n\t\tstd::error_code ec;\n\t\tif ((hz::fs::exists(file, ec) && !hz::fs::is_regular_file(file, ec)) || !hz::fs_path_is_writable(file, ec)) {\n\t\t\tdebug_out_error(\"rconfig\", \"Autosave failed: Cannot write to file:  \" << ec.message() << std::endl);\n\t\t\treturn static_cast<gboolean>(force);  // if manual, return failure. else, don't stop the timeout.\n\t\t}\n\n\t\tconst bool status = rconfig::save_to_file(impl::autosave_config_file);\n\t\tif (force)\n\t\t\treturn static_cast<gboolean>(status);  // return status to caller\n\n\t\treturn TRUE;  // continue timeouts\n\t}\n\n}\n\n\n\n\n/// Set config file to autosave to.\ninline bool autosave_set_config_file(const hz::fs::path& file)\n{\n\tif (file.empty()) {\n\t\tdebug_out_error(\"rconfig\", DBG_FUNC_MSG << \"Error: Filename is empty.\" << std::endl);\n\t\treturn false;\n\t}\n\n\timpl::autosave_config_file = file;\n\n\tdebug_out_info(\"rconfig\", \"Setting autosave config file to \\\"\" << file << \"\\\".\" << std::endl);\n\treturn true;\n}\n\n\n\n/// Enable autosave every \\c sec_interval seconds.\ninline bool autosave_start(std::chrono::seconds sec_interval)\n{\n\tif (impl::autosave_enabled) {  // already autosaving, you should stop it first.\n\t\tdebug_out_warn(\"rconfig\", \"Error while starting config autosave: Autosave is active already.\" << std::endl);\n\t\treturn false;\n\t}\n\n\timpl::autosave_enabled = true;\n\tdebug_out_info(\"rconfig\", \"Starting config autosave with \" << sec_interval.count() << \" sec. interval.\" << std::endl);\n\n\tg_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, guint(std::chrono::milliseconds(sec_interval).count()),\n\t\t\t&autosave_timeout_callback, nullptr, nullptr);\n\n\treturn true;\n}\n\n\n\n/// Disable autosave\ninline void autosave_stop()\n{\n\tdebug_out_info(\"rconfig\", \"Stopping config autosave.\" << std::endl);\n\n\t// set the stop flag. it will make autosave stop on next timeout callback call.\n\timpl::autosave_enabled = false;\n}\n\n\n\n/// Forcibly save the config now.\ninline bool autosave_force_now()\n{\n\tAutosaveCallbackData data;\n\treturn static_cast<bool>(autosave_timeout_callback(&data));\n}\n\n\n\n\n\n}  // ns\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/rconfig/examples/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nif (NOT APP_BUILD_EXAMPLES)\n    set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL true)\nelse()\n    set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL false)\nendif()\n\n\nadd_executable(example_rconfig)\ntarget_sources(example_rconfig PRIVATE\n\texample_rconfig.cpp\n)\ntarget_link_libraries(example_rconfig PRIVATE\n\trconfig\n)\n\n"
  },
  {
    "path": "src/rconfig/examples/example_rconfig.cpp",
    "content": "/******************************************************************************\nLicense: BSD Zero Clause License\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup rconfig_examples\n/// \\weakgroup rconfig_examples\n/// @{\n\n// disable libdebug, we don't link to it\n#undef HZ_USE_LIBDEBUG\n#define HZ_USE_LIBDEBUG 0\n// enable libdebug emulation through std::cerr\n#undef HZ_EMULATE_LIBDEBUG\n#define HZ_EMULATE_LIBDEBUG 1\n\n#include \"rconfig/loadsave.h\"\n\n#if defined ENABLE_GLIB && ENABLE_GLIB\n\t#include \"rconfig/autosave.h\"\n#endif\n#include \"hz/main_tools.h\"\n\n#include <iostream>\n#include <cstdint>\n#include <chrono>\n\n\n\n/// Main function for the test\nint main()\n{\n\treturn hz::main_exception_wrapper([]()\n\t{\n\t\tusing namespace std::literals;\n\n\t\trconfig::load_from_file(\"test.config\");\n\n\t\t// populate /default:\n\t\trconfig::set_default_data(\"app/use_stuff\", true);  // bool\n\t\trconfig::set_default_data(\"app/some_string1\", std::string(\"some_string1_data\"));\n\t\trconfig::set_default_data(\"app/some_string2\", \"some_string2_data\");  // this will store it as std::string\n\t\trconfig::set_default_data(\"app/int_value\", 5);  // stored as int64_t\n\t\trconfig::set_default_data(\"app/int64_value\", int64_t(5));  // explicitly\n\t\trconfig::set_default_data(\"app/double_value\", 6.7);  // double\n\n\t\trconfig::set_data(\"app/int_var\", 11);  // override default.\n\n\t\tconst int int_var = rconfig::get_data<int>(\"app/int_value\");\n\t\tstd::cerr << \"app/int_value: \" << int_var << \"\\n\";\n\n\t\tstd::cerr << \"app/some_string2: \" << rconfig::get_data<std::string>(\"app/some_string2\") << \"\\n\";\n\n\t\trconfig::dump_config();\n\t\trconfig::save_to_file(\"test.config\");\n\n\t#if defined ENABLE_GLIB && ENABLE_GLIB\n\t\trconfig::autosave_set_config_file(\"test2.config\");\n\t\trconfig::autosave_start(2s);  // every 2 seconds\n\t\twhile(true) {  // FIXME This is undefined behavior\n\t\t\t// without this the timeout function won't be called.\n\t\t\tg_main_context_iteration(nullptr, FALSE);\n\t\t}\n\t#endif\n\n\t\treturn EXIT_SUCCESS;\n\t});\n}\n\n\n\n\n\n\n\n/// @}\n"
  },
  {
    "path": "src/rconfig/loadsave.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup rconfig\n/// \\weakgroup rconfig\n/// @{\n\n#ifndef RCONFIG_LOADSAVE_H\n#define RCONFIG_LOADSAVE_H\n\n#include <string>\n\n#include \"hz/debug.h\"\n#include \"hz/fs.h\"\n\n#include \"rconfig.h\"\n\n\n\nnamespace rconfig {\n\n\n\n/// Load the config branch from file.\ninline bool load_from_file(const hz::fs::path& file)\n{\n\tstd::string json_str;\n\tauto ec = hz::fs_file_get_contents(file, json_str, 10*1024*1024);  // 10M\n\tif (ec) {\n\t\tdebug_out_error(\"rconfig\", \"load_from_file(): Unable to read from file \\\"\"\n\t\t\t\t<< file << \"\\\": \" << ec.message() << std::endl);\n\t\treturn false;\n\t}\n\n\ttry {\n\t\tget_config_branch() = json::parse(json_str);\n\t}\n\tcatch (json::parse_error& e) {\n\t\tdebug_out_warn(\"rconfig\", \"Cannot load config file \\\"\"\n\t\t\t\t<< file << \"\\\": \" << e.what() << std::endl);\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n\n\n/// Save the config branch to a file.\ninline bool save_to_file(const hz::fs::path& file)\n{\n\tconst std::string json_str = get_config_branch().dump(4);\n\n\tauto ec = hz::fs_file_put_contents(file, json_str);\n\tif (ec) {\n\t\tdebug_out_error(\"rconfig\", DBG_FUNC_MSG\n\t\t\t\t<< \"Unable to write to file \\\"\" << file << \"\\\": \" << ec.message() << \".\" << std::endl);\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n\n\n\n\n}  // ns\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/rconfig/rconfig.h",
    "content": "/******************************************************************************\nLicense: Zlib\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup rconfig\n/// \\weakgroup rconfig\n/// @{\n\n#ifndef RCONFIG_CONFIG_H\n#define RCONFIG_CONFIG_H\n\n#include \"nlohmann/json.hpp\"\n\n#include <string>\n#include <memory>\n#include <vector>\n#include <stdexcept>  // std::runtime_error\n\n#include \"hz/debug.h\"\n#include \"hz/string_algo.h\"\n\n\n\nnamespace rconfig {\n\n\nusing json = nlohmann::json;\nusing namespace std::string_literals;\n\n\nnamespace impl {\n\n\tinline std::unique_ptr<json> config_node;  ///< Node for serializable branch\n\tinline std::unique_ptr<json> default_node;  ///< Node for default branch\n\n\n\ttemplate<typename T>\n\tinline void set_node_data(json& root, const std::string& path, T&& value)\n\t{\n\t\tstd::vector<std::string> components;\n\t\thz::string_split(path, '/', components, true);\n\n\t\tjson* curr = &root;\n\t\tfor (std::size_t comp_index = 0; comp_index < components.size(); ++comp_index) {\n\t\t\tconst std::string& comp_name = components[comp_index];\n\n\t\t\t// we can't have non-object values in the middle of a path\n\t\t\tif (!curr->is_object()) {\n\t\t\t\tthrow std::runtime_error(\"Cannot set node data \\\"\"s + path + \"\\\", component \\\"\" + comp_name + \"\\\" is not an object.\");\n\t\t\t}\n\t\t\tif (auto iter = curr->find(comp_name); iter != curr->end()) {  // path component exists\n\t\t\t\tjson& jval = iter.value();\n\t\t\t\tif (comp_index + 1 == components.size()) {  // it's the \"value\" component\n\t\t\t\t\tjval = json(std::forward<T>(value));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// continue to the next component\n\t\t\t\tcurr = &jval;\n\n\t\t\t} else {  // path component doesn't exist\n\t\t\t\tif (comp_index + 1 == components.size()) {  // it's the \"value\" component\n\t\t\t\t\t(*curr)[comp_name] = json(std::forward<T>(value));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcurr = &((*curr)[comp_name] = json::object());\n\t\t\t}\n\t\t}\n\t}\n\n\n\n\ttemplate<typename T>\n\tbool get_node_data(json& root, const std::string& path, T& value)\n\t{\n\t\tstd::vector<std::string> components;\n\t\thz::string_split(path, '/', components, true);\n\n\t\tjson* curr = &root;\n\t\tfor (std::size_t comp_index = 0; comp_index < components.size(); ++comp_index) {\n\t\t\tconst std::string& comp_name = components[comp_index];\n\n\t\t\tif (!curr->is_object()) {  // we can't have non-object values in the middle of a path\n\t\t\t\tthrow std::runtime_error(\"Cannot get node data \\\"\"s + path + \"\\\", component \\\"\" + comp_name + \"\\\" is not an object.\");\n\t\t\t}\n\t\t\tif (auto iter = curr->find(comp_name); iter != curr->end()) {  // path component exists\n\t\t\t\tjson& jval = iter.value();\n\t\t\t\tif (comp_index + 1 == components.size()) {  // it's the \"value\" component\n\t\t\t\t\tvalue = jval.get<T>();  // may throw json::type_error\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t\t// continue to the next component\n\t\t\t\tcurr = &jval;\n\n\t\t\t} else {  // path component doesn't exist\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\n\tinline void unset_node_data(json& root, const std::string& path)\n\t{\n\t\tstd::vector<std::string> components;\n\t\thz::string_split(path, '/', components, true);\n\n\t\tjson* curr = &root;\n\t\tfor (std::size_t comp_index = 0; comp_index < components.size(); ++comp_index) {\n\t\t\tconst std::string& comp_name = components[comp_index];\n\n\t\t\tif (auto iter = curr->find(comp_name); iter != curr->end()) {  // path component exists\n\t\t\t\tjson& jval = iter.value();\n\t\t\t\tif (comp_index + 1 == components.size()) {  // it's the \"value\" component\n\t\t\t\t\tcurr->erase(comp_name);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (!jval.is_object()) {  // we can't have non-object values in the middle of a path\n\t\t\t\t\tdebug_out_error(\"rconfig\", \"Component \\\"\"s + comp_name + \"\\\" in path \\\"\" + path + \"\\\" is not an object, removing it.\");\n\t\t\t\t\tcurr->erase(comp_name);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\t// continue to the next component\n\t\t\t\tcurr = &jval;\n\n\t\t\t} else {  // path component doesn't exist\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\n}\n\n\n\n/// Clear user config\ninline void clear_config()\n{\n\timpl::config_node = std::make_unique<json>(json::object());\n}\n\n\n/// Clear defaults\ninline void clear_defaults()\n{\n\timpl::default_node = std::make_unique<json>(json::object());\n}\n\n\n/// Initialize the root node. This is called automatically.\ninline bool init_root()\n{\n\tif (impl::config_node)  // already inited\n\t\treturn false;\n\n\tclear_config();\n\tclear_defaults();\n\n\treturn true;\n}\n\n\n\n/// Get the config branch node\n[[nodiscard]] inline json& get_config_branch()\n{\n\tinit_root();\n\treturn *impl::config_node;\n}\n\n\n\n/// Get the default branch node\n[[nodiscard]] inline json& get_default_branch()\n{\n\tinit_root();\n\treturn *impl::default_node;\n}\n\n\n\n/// Set the data in path\ntemplate<typename T>\nbool set_data(const std::string& path, T data)\n{\n\t// Since config is loaded from a user file, it may contain some invalid\n\t// nodes. Don't abort, print warnings.\n\ttry {\n\t\timpl::set_node_data(get_config_branch(), path, std::move(data));\n\t}\n\tcatch (std::exception& e) {\n\t\tdebug_out_error(\"rconfig\", e.what());\n\t\treturn false;\n\t}\n\treturn true;\n}\n\n\n\n/// Set the default data in path\ntemplate<typename T>\nvoid set_default_data(const std::string& path, T data)\n{\n\t// Default data branch must always be valid, so abort if anything is wrong.\n\timpl::set_node_data(get_default_branch(), path, std::move(data));\n}\n\n\n\n/// Get the data from config. If no such node exists, look it up in defaults.\ntemplate<typename T>\n[[nodiscard]] T get_data(const std::string& path)\n{\n\tT data = {};\n\tbool found = false;\n\ttry {\n\t\t// This can possibly throw because the user config file is incorrect\n\t\tfound = impl::get_node_data(get_config_branch(), path, data);\n\t}\n\tcatch(std::exception& e) {\n\t\tdebug_out_error(\"rconfig\", e.what());\n\t}\n\n\t// This can throw only for errors within the program.\n\tif (!found && !impl::get_node_data(get_default_branch(), path, data)) {\n\t\tthrow std::runtime_error(\"No such node: \"s + path);\n\t}\n\treturn data;\n}\n\n\n\n/// Get the data from defaults.\ntemplate<typename T>\n[[nodiscard]] T get_default_data(const std::string& path)\n{\n\tT data = {};\n\t// This can throw only for errors within the program.\n\tif (!impl::get_node_data(get_default_branch(), path, data)) {\n\t\tthrow std::runtime_error(\"No such node: \"s + path);\n\t}\n\treturn data;\n}\n\n\n\n/// Unset data in config path\ninline void unset_data(const std::string& path)\n{\n\timpl::unset_node_data(get_config_branch(), path);\n}\n\n\n\n/// Dump config to debug output\ninline void dump_config(bool print_defaults = false)\n{\n\tdebug_begin();\n\tdebug_out_dump(\"rconfig\", \"Config:\\n\" + get_config_branch().dump(4) + \"\\n\");\n\tdebug_end();\n\n\tif (print_defaults) {\n\t\tdebug_begin();\n\t\tdebug_out_dump(\"rconfig\", \"Defaults:\\n\" + get_default_branch().dump(4) + \"\\n\");\n\t\tdebug_end();\n\t}\n}\n\n\n\n\n}  // ns\n\n\n\n#endif\n\n/// @}\n"
  },
  {
    "path": "src/test_all/CMakeLists.txt",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\nif (NOT APP_BUILD_TESTS)\n    set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL true)\nelse()\n    set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL false)\nendif()\n\n# Make sure include() works with our modules.\nset(CMAKE_MODULE_PATH \"${CMAKE_SOURCE_DIR}/dependencies/catch2/Catch2/contrib\")\ninclude(ParseAndAddCatchTests)\n\nadd_executable(test_all)\ntarget_sources(test_all PRIVATE\n\ttest_all.cpp\n)\ntarget_link_libraries(test_all PRIVATE\n\tlibdebug\n\tapplib_tests\n\thz_tests\n\tCatch2\n)\n\nif (NOT CMAKE_CROSSCOMPILING)\n\tcatch_discover_tests(test_all)\nendif()\n"
  },
  {
    "path": "src/test_all/test_all.cpp",
    "content": "/******************************************************************************\nLicense: BSD Zero Clause License\nCopyright:\n\t(C) 2008 - 2021 Alexander Shaduri <ashaduri@gmail.com>\n******************************************************************************/\n/// \\file\n/// \\author Alexander Shaduri\n/// \\ingroup test_all\n/// \\weakgroup test_all\n/// @{\n\n\n#define CATCH_CONFIG_RUNNER\n#include \"catch2/catch.hpp\"\n\n\n#include \"libdebug/libdebug.h\"\n\n\n\nint main(int argc, char* argv[])\n{\n\tdebug_register_domain(\"gtk\");\n\tdebug_register_domain(\"app\");\n\tdebug_register_domain(\"hz\");\n\tdebug_register_domain(\"rconfig\");\n\n\tconst int result = Catch::Session().run( argc, argv );\n\treturn result;\n}\n\n\n\n/// @}\n"
  },
  {
    "path": "toolchains/linux-dev-with-tidy.cmake",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n\n# Developer options\nset(APP_COMPILER_ENABLE_WARNINGS ON)\nset(APP_BUILD_EXAMPLES ON)\nset(APP_BUILD_TESTS ON)\n\n\n# Enable clang-tidy by default, reading from <project_root>/.clang-tidy file\nset(CMAKE_CXX_CLANG_TIDY \"clang-tidy\")\n\n"
  },
  {
    "path": "toolchains/linux-dev.cmake",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n\n# Developer options\nset(APP_COMPILER_ENABLE_WARNINGS ON)\nset(APP_BUILD_EXAMPLES ON)\nset(APP_BUILD_TESTS ON)\n\n"
  },
  {
    "path": "toolchains/win32-mingw-cross.cmake",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n# Windows 32-bit production version with mingw/gcc.\n# Use with:\n# cmake -DCMAKE_TOOLCHAIN_FILE=...\n\n# Target OS\nset(CMAKE_SYSTEM_NAME Windows)\n\n# Specify the compiler\nset(CMAKE_C_COMPILER i686-w64-mingw32-gcc)\nset(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)\n#set(CMAKE_RC_COMPILER i686-w64-mingw32-windres)\n\n# The target environment\nset(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32/sys-root/mingw)\n\n# Adjust the default behaviour of the FIND_XXX() commands:\n# search headers and libraries in the target environment, search\n# programs in the host environment\nset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\nset(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\nset(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\nset(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n\nIF (CMAKE_CROSSCOMPILING)\n\tset(ENV{PKG_CONFIG_LIBDIR} \"${CMAKE_FIND_ROOT_PATH}/lib/pkgconfig\")\nendif()\n\n#set(APP_NSIS_PATH /usr/bin/makensis)\n\n\n# Enable LTO for Release build (does not work with mingw)\n#set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)\n\n# Increase optimizations of Release builds\nadd_compile_options($<$<CONFIG:RELEASE>:-g0>)\nadd_compile_options($<$<CONFIG:RELEASE>:-O3>)\nadd_compile_options($<$<CONFIG:RELEASE>:-s>)\n\n# Enable common CPU optimizations\nadd_compile_options(-march=i686 -mtune=generic)\n\n\n# Developer options\nset(APP_COMPILER_ENABLE_WARNINGS ON)\nset(APP_BUILD_EXAMPLES ON)\nset(APP_BUILD_TESTS ON)\n\n\n"
  },
  {
    "path": "toolchains/win32-mingw-msys2.cmake",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2024 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n# Windows 32-bit production version with mingw/msys2.\n# Use with:\n# cmake -DCMAKE_TOOLCHAIN_FILE=...\n\n# Target OS\nset(CMAKE_SYSTEM_NAME Windows)\n\n# Specify the compiler\nset(CMAKE_C_COMPILER i686-w64-mingw32-gcc)\nset(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)\n#set(CMAKE_RC_COMPILER i686-w64-mingw32-windres)\n\n# The target environment. This is a path which is valid for msys2.\nset(CMAKE_FIND_ROOT_PATH \"$ENV{MINGW_PREFIX}\")\n\n# Enable LTO for Release build (does not work with mingw)\n#set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)\n\n# Increase optimizations of Release builds\nadd_compile_options($<$<CONFIG:RELEASE>:-g0>)\nadd_compile_options($<$<CONFIG:RELEASE>:-O3>)\nadd_compile_options($<$<CONFIG:RELEASE>:-s>)\n\n# Enable common CPU optimizations\nadd_compile_options(-march=i686 -mtune=generic)\n\n\n# Developer options\nset(APP_COMPILER_ENABLE_WARNINGS ON)\nset(APP_BUILD_EXAMPLES ON)\nset(APP_BUILD_TESTS ON)\n\n\n"
  },
  {
    "path": "toolchains/win64-mingw-cross.cmake",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2021 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n# Windows 32-bit production version with mingw/gcc.\n# Use with:\n# cmake -DCMAKE_TOOLCHAIN_FILE=...\n\n# Target OS\nset(CMAKE_SYSTEM_NAME Windows)\n\n# Specify the compiler\nset(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)\nset(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)\n#set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)\n\n# The target environment\nset(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32/sys-root/mingw)\n\n# Adjust the default behaviour of the FIND_XXX() commands:\n# search headers and libraries in the target environment, search\n# programs in the host environment\nset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\nset(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\nset(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\nset(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)\n\nIF (CMAKE_CROSSCOMPILING)\n\tset(ENV{PKG_CONFIG_LIBDIR} \"${CMAKE_FIND_ROOT_PATH}/lib/pkgconfig\")\nendif()\n\n#set(APP_NSIS_PATH /usr/bin/makensis)\n\n\n# Enable LTO for Release build (does not work with mingw)\n#set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)\n\n# Increase optimizations of Release builds\nadd_compile_options($<$<CONFIG:RELEASE>:-g0>)\nadd_compile_options($<$<CONFIG:RELEASE>:-O3>)\nadd_compile_options($<$<CONFIG:RELEASE>:-s>)\n\n# Enable common CPU optimizations\nadd_compile_options(-mtune=generic)\n\n\n# Developer options\nset(APP_COMPILER_ENABLE_WARNINGS ON)\nset(APP_BUILD_EXAMPLES ON)\nset(APP_BUILD_TESTS ON)\n\n\n"
  },
  {
    "path": "toolchains/win64-mingw-msys2.cmake",
    "content": "###############################################################################\n# License: BSD Zero Clause License file\n# Copyright:\n#   (C) 2024 Alexander Shaduri <ashaduri@gmail.com>\n###############################################################################\n\n# Windows 64-bit production version with mingw/msys2.\n# Use with:\n# cmake -DCMAKE_TOOLCHAIN_FILE=...\n\n# Target OS\nset(CMAKE_SYSTEM_NAME Windows)\n\n# Specify the compiler\nset(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)\nset(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)\n#set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)\n\n# The target environment. This is a path which is valid for msys2.\nset(CMAKE_FIND_ROOT_PATH \"$ENV{MINGW_PREFIX}\")\n\n# Enable LTO for Release build (does not work with mingw)\n#set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)\n\n# Increase optimizations of Release builds\nadd_compile_options($<$<CONFIG:RELEASE>:-g0>)\nadd_compile_options($<$<CONFIG:RELEASE>:-O3>)\nadd_compile_options($<$<CONFIG:RELEASE>:-s>)\n\n# Enable common CPU optimizations\nadd_compile_options(-mtune=generic)\n\n\n# Developer options\nset(APP_COMPILER_ENABLE_WARNINGS ON)\nset(APP_BUILD_EXAMPLES ON)\nset(APP_BUILD_TESTS ON)\n\n\n"
  },
  {
    "path": "version.txt",
    "content": "\n# This file is included from CMakeLists.txt\n\nset(CMAKE_PROJECT_VERSION \"2.0.2\")\n\n"
  }
]