[
  {
    "path": ".github/workflows/CICD.yml",
    "content": "name: CICD\n\nenv:\n  CICD_INTERMEDIATES_DIR: \"_cicd-intermediates\"\n  MSRV_FEATURES: \"\"\n\non:\n  workflow_dispatch:\n  pull_request:\n  push:\n    branches:\n      - master\n    tags:\n      - '*'\n\njobs:\n  crate_metadata:\n    name: Extract crate metadata\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v3\n    - name: Extract crate information\n      id: crate_metadata\n      run: |\n        cargo metadata --no-deps --format-version 1 | jq -r '\"name=\" + .packages[0].name' | tee -a $GITHUB_OUTPUT\n        cargo metadata --no-deps --format-version 1 | jq -r '\"version=\" + .packages[0].version' | tee -a $GITHUB_OUTPUT\n        cargo metadata --no-deps --format-version 1 | jq -r '\"maintainer=\" + .packages[0].authors[0]' | tee -a $GITHUB_OUTPUT\n        cargo metadata --no-deps --format-version 1 | jq -r '\"homepage=\" + .packages[0].homepage' | tee -a $GITHUB_OUTPUT\n        cargo metadata --no-deps --format-version 1 | jq -r '\"msrv=\" + .packages[0].rust_version' | tee -a $GITHUB_OUTPUT\n    outputs:\n      name: ${{ steps.crate_metadata.outputs.name }}\n      version: ${{ steps.crate_metadata.outputs.version }}\n      maintainer: ${{ steps.crate_metadata.outputs.maintainer }}\n      homepage: ${{ steps.crate_metadata.outputs.homepage }}\n      msrv: ${{ steps.crate_metadata.outputs.msrv }}\n\n  ensure_cargo_fmt:\n    name: Ensure 'cargo fmt' has been run\n    runs-on: ubuntu-24.04\n    steps:\n    - uses: dtolnay/rust-toolchain@stable\n      with:\n        components: rustfmt\n    - uses: actions/checkout@v3\n    - run: cargo fmt -- --check\n\n  min_version:\n    name: Minimum supported rust version\n    runs-on: ubuntu-24.04\n    needs: crate_metadata\n    steps:\n    - name: Checkout source code\n      uses: actions/checkout@v3\n\n    - name: Install rust toolchain (v${{ needs.crate_metadata.outputs.msrv }})\n      uses: dtolnay/rust-toolchain@master\n      with:\n        toolchain: ${{ needs.crate_metadata.outputs.msrv }}\n        components: clippy\n    - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix)\n      run: cargo clippy --locked --all-targets ${{ env.MSRV_FEATURES }}\n    - name: Run tests\n      run: cargo test --locked ${{ env.MSRV_FEATURES }}\n\n  build:\n    name: ${{ matrix.job.target }} (${{ matrix.job.os }})\n    runs-on: ${{ matrix.job.os }}\n    needs: crate_metadata\n    strategy:\n      fail-fast: false\n      matrix:\n        job:\n          - { target: aarch64-unknown-linux-gnu   , os: ubuntu-24.04, use-cross: true }\n          - { target: arm-unknown-linux-gnueabihf , os: ubuntu-24.04, use-cross: true }\n          - { target: arm-unknown-linux-musleabihf, os: ubuntu-24.04, use-cross: true }\n          - { target: i686-pc-windows-msvc        , os: windows-2025                  }\n          - { target: i686-unknown-linux-gnu      , os: ubuntu-24.04, use-cross: true }\n          - { target: i686-unknown-linux-musl     , os: ubuntu-24.04, use-cross: true }\n          - { target: x86_64-apple-darwin         , os: macos-15-intel                }\n          - { target: aarch64-apple-darwin        , os: macos-15                      }\n          # Was causing CI failures unrelated to app logic\n          # - { target: x86_64-pc-windows-gnu       , os: windows-2019                  }\n          - { target: x86_64-pc-windows-msvc      , os: windows-2025                  }\n          - { target: x86_64-unknown-linux-gnu    , os: ubuntu-24.04, use-cross: true }\n          - { target: x86_64-unknown-linux-musl   , os: ubuntu-24.04, use-cross: true }\n    env:\n      BUILD_CMD: cargo\n    steps:\n    - name: Checkout source code\n      uses: actions/checkout@v3\n\n    - name: Install prerequisites\n      shell: bash\n      run: |\n        case ${{ matrix.job.target }} in\n          arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;\n          aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;\n        esac\n\n    - name: Install Rust toolchain\n      uses: dtolnay/rust-toolchain@stable\n      with:\n        targets: ${{ matrix.job.target }}\n\n    - name: Install cross\n      if: matrix.job.use-cross\n      uses: taiki-e/install-action@v2\n      with:\n        tool: cross\n\n    - name: Overwrite build command env variable\n      if: matrix.job.use-cross\n      shell: bash\n      run: echo \"BUILD_CMD=cross\" >> $GITHUB_ENV\n\n    - name: Show version information (Rust, cargo, GCC)\n      shell: bash\n      run: |\n        gcc --version || true\n        rustup -V\n        rustup toolchain list\n        rustup default\n        cargo -V\n        rustc -V\n\n    - name: Build\n      shell: bash\n      run: $BUILD_CMD build --locked --release --target=${{ matrix.job.target }}\n\n    - name: Run example\n      if: ${{ !matrix.job.use-cross }}\n      shell: bash\n      run: $BUILD_CMD run --release --target=${{ matrix.job.target }} --example=simple\n\n    - name: Set binary name & path\n      id: bin\n      shell: bash\n      run: |\n        # Figure out suffix of binary\n        EXE_suffix=\"\"\n        case ${{ matrix.job.target }} in\n          *-pc-windows-*) EXE_suffix=\".exe\" ;;\n        esac;\n\n        # Setup paths\n        BIN_NAME=\"${{ needs.crate_metadata.outputs.name }}${EXE_suffix}\"\n        BIN_PATH=\"target/${{ matrix.job.target }}/release/${BIN_NAME}\"\n\n        # Let subsequent steps know where to find the binary\n        echo \"BIN_PATH=${BIN_PATH}\" >> $GITHUB_OUTPUT\n        echo \"BIN_NAME=${BIN_NAME}\" >> $GITHUB_OUTPUT\n\n    - name: Set testing options\n      id: test-options\n      shell: bash\n      run: |\n        # test only library unit tests and binary for arm-type targets\n        unset CARGO_TEST_OPTIONS\n        case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS=\"--lib --bin ${{ needs.crate_metadata.outputs.name }}\" ;; esac;\n        echo \"CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}\" >> $GITHUB_OUTPUT\n\n    - name: Run tests\n      shell: bash\n      run: $BUILD_CMD test --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}\n\n    - name: Setup Pandoc\n      uses: r-lib/actions/setup-pandoc@v2\n\n    - name: Generate man page\n      run: pandoc -s -f markdown -t man -o \"doc/${{ needs.crate_metadata.outputs.name }}.1\" \"doc/${{ needs.crate_metadata.outputs.name }}.1.md\"\n\n    - name: Create tarball\n      id: package\n      shell: bash\n      run: |\n        PKG_suffix=\".tar.gz\" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=\".zip\" ;; esac;\n        PKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-v${{ needs.crate_metadata.outputs.version }}-${{ matrix.job.target }}\n        PKG_NAME=${PKG_BASENAME}${PKG_suffix}\n        echo \"PKG_NAME=${PKG_NAME}\" >> $GITHUB_OUTPUT\n\n        PKG_STAGING=\"${{ env.CICD_INTERMEDIATES_DIR }}/package\"\n        ARCHIVE_DIR=\"${PKG_STAGING}/${PKG_BASENAME}/\"\n        mkdir -p \"${ARCHIVE_DIR}\"\n\n        # Binary\n        cp \"${{ steps.bin.outputs.BIN_PATH }}\" \"$ARCHIVE_DIR\"\n\n        # README, LICENSE and CHANGELOG files\n        cp \"README.md\" \"LICENSE-MIT\" \"LICENSE-APACHE\" \"CHANGELOG.md\" \"$ARCHIVE_DIR\"\n\n        # Man page\n        cp \"doc/${{ needs.crate_metadata.outputs.name }}.1\" \"$ARCHIVE_DIR\"\n\n        # base compressed package\n        pushd \"${PKG_STAGING}/\" >/dev/null\n        case ${{ matrix.job.target }} in\n          *-pc-windows-*) 7z -y a \"${PKG_NAME}\" \"${PKG_BASENAME}\"/* | tail -2 ;;\n          *) tar czf \"${PKG_NAME}\" \"${PKG_BASENAME}\"/* ;;\n        esac;\n        popd >/dev/null\n\n        # Let subsequent steps know where to find the compressed package\n        echo \"PKG_PATH=${PKG_STAGING}/${PKG_NAME}\" >> $GITHUB_OUTPUT\n\n    - name: Create Debian package\n      id: debian-package\n      shell: bash\n      if: startsWith(matrix.job.os, 'ubuntu')\n      run: |\n        COPYRIGHT_YEARS=\"2018 - \"$(date \"+%Y\")\n        DPKG_STAGING=\"${{ env.CICD_INTERMEDIATES_DIR }}/debian-package\"\n        DPKG_DIR=\"${DPKG_STAGING}/dpkg\"\n        mkdir -p \"${DPKG_DIR}\"\n\n        DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}\n        DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }}-musl\n        case ${{ matrix.job.target }} in *-musl*) DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-musl ; DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }} ;; esac;\n        DPKG_VERSION=${{ needs.crate_metadata.outputs.version }}\n\n        unset DPKG_ARCH\n        case ${{ matrix.job.target }} in\n          aarch64-*-linux-*) DPKG_ARCH=arm64 ;;\n          arm-*-linux-*hf) DPKG_ARCH=armhf ;;\n          i686-*-linux-*) DPKG_ARCH=i686 ;;\n          x86_64-*-linux-*) DPKG_ARCH=amd64 ;;\n          *) DPKG_ARCH=notset ;;\n        esac;\n\n        DPKG_NAME=\"${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb\"\n        echo \"DPKG_NAME=${DPKG_NAME}\" >> $GITHUB_OUTPUT\n\n        # Binary\n        install -Dm755 \"${{ steps.bin.outputs.BIN_PATH }}\" \"${DPKG_DIR}/usr/bin/${{ steps.bin.outputs.BIN_NAME }}\"\n\n        # Man page\n        install -Dm644 'doc/${{ needs.crate_metadata.outputs.name }}.1' \"${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1\"\n        gzip -n --best \"${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1\"\n\n        # README and LICENSE\n        install -Dm644 \"README.md\" \"${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md\"\n        install -Dm644 \"LICENSE-MIT\" \"${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-MIT\"\n        install -Dm644 \"LICENSE-APACHE\" \"${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-APACHE\"\n        install -Dm644 \"CHANGELOG.md\" \"${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog\"\n        gzip -n --best \"${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog\"\n\n        cat > \"${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright\" <<EOF\n        Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n        Upstream-Name: ${{ needs.crate_metadata.outputs.name }}\n        Source: ${{ needs.crate_metadata.outputs.homepage }}\n\n        Files: *\n        Copyright: ${{ needs.crate_metadata.outputs.maintainer }}\n        Copyright: $COPYRIGHT_YEARS ${{ needs.crate_metadata.outputs.maintainer }}\n        License: Apache-2.0 or MIT\n\n        License: Apache-2.0\n          On Debian systems, the complete text of the Apache-2.0 can be found in the\n          file /usr/share/common-licenses/Apache-2.0.\n\n        License: MIT\n          Permission is hereby granted, free of charge, to any\n          person obtaining a copy of this software and associated\n          documentation files (the \"Software\"), to deal in the\n          Software without restriction, including without\n          limitation the rights to use, copy, modify, merge,\n          publish, distribute, sublicense, and/or sell copies of\n          the Software, and to permit persons to whom the Software\n          is furnished to do so, subject to the following\n          conditions:\n          .\n          The above copyright notice and this permission notice\n          shall be included in all copies or substantial portions\n          of the Software.\n          .\n          THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\n          ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\n          TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n          PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\n          SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n          CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n          OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\n          IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n          DEALINGS IN THE SOFTWARE.\n        EOF\n          chmod 644 \"${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright\"\n\n          # control file\n          mkdir -p \"${DPKG_DIR}/DEBIAN\"\n          cat > \"${DPKG_DIR}/DEBIAN/control\" <<EOF\n        Package: ${DPKG_BASENAME}\n        Version: ${DPKG_VERSION}\n        Section: utils\n        Priority: optional\n        Maintainer: ${{ needs.crate_metadata.outputs.maintainer }}\n        Homepage: ${{ needs.crate_metadata.outputs.homepage }}\n        Architecture: ${DPKG_ARCH}\n        Provides: ${{ needs.crate_metadata.outputs.name }}\n        Conflicts: ${DPKG_CONFLICTS}\n        Description: A command-line benchmarking tool\n        EOF\n\n        DPKG_PATH=\"${DPKG_STAGING}/${DPKG_NAME}\"\n        echo \"DPKG_PATH=${DPKG_PATH}\" >> $GITHUB_OUTPUT\n\n        # build dpkg\n        fakeroot dpkg-deb --build \"${DPKG_DIR}\" \"${DPKG_PATH}\"\n\n    - name: \"Artifact upload: tarball\"\n      uses: actions/upload-artifact@main\n      with:\n        name: ${{ steps.package.outputs.PKG_NAME }}\n        path: ${{ steps.package.outputs.PKG_PATH }}\n\n    - name: \"Artifact upload: Debian package\"\n      uses: actions/upload-artifact@main\n      if: steps.debian-package.outputs.DPKG_NAME\n      with:\n        name: ${{ steps.debian-package.outputs.DPKG_NAME }}\n        path: ${{ steps.debian-package.outputs.DPKG_PATH }}\n\n    - name: Check for release\n      id: is-release\n      shell: bash\n      run: |\n        unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/v[0-9].* ]]; then IS_RELEASE='true' ; fi\n        echo \"IS_RELEASE=${IS_RELEASE}\" >> $GITHUB_OUTPUT\n\n    - name: Publish archives and packages\n      uses: softprops/action-gh-release@v1\n      if: steps.is-release.outputs.IS_RELEASE\n      with:\n        files: |\n          ${{ steps.package.outputs.PKG_PATH }}\n          ${{ steps.debian-package.outputs.DPKG_PATH }}\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\n**/*.rs.bk\n\n# Generated files\nhexyl.1\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# v0.17.0\n\n## Features\n\n- Add option to output result in C include file style, see #242 (@wpcwzy)\n- Add `--color-scheme` option, see #247 (@aticu)\n- Add `braille` character table, see #247 (@aticu)\n- Add command line argument to generate shell completion, see #155 (@friedz)\n- Add colors to `--help`/`-h`, see #253 (@starsep)\n\n## Bugfixes\n\n- Fix memory allocation bug when terminal width is less than 10, see #244 (@selfup)\n\n\n# v0.16.0\n\n## Features\n\n* New `--print-color-table` option, see #229 (@sahinfalcon)\n\n## Bugfixes\n\n- Throw an error when try to view a directory, see #234 (@Integral-Tech)\n\n\n# v0.15.0\n\n## Features\n\n- Add codepage 1047 for EBCDIC, see #226 (@v1gnesh)\n\n## Other\n\n- Rewrite CLI using the derive API, see #225 (@sorairolake)\n\n\n# v0.14.0\n\n## Features\n\n* New `--character-table` option, with the ability to use [codepage 437](https://www.azabani.com/2020/11/15/xd.html), see #194 and #195 (@sharifhsn)\n* New `--character-table=ascii` option for a ASCII-only character table, see #212 and #36 (@sharkdp)\n\n## Bugfixes\n\n* Show output when doing `hexyl /dev/zero`, see #211 (@sharifhsn)\n* Respect NO_COLOR environment variable, see #210 (@sharkdp)\n\n\n# v0.13.1\n\n## Bugfixes\n\n- Correctly handle discontinuous input (stdin), see #196 and #197 (@sharifhsn)\n\n# v0.13.0\n\n## Features\n\n- Support both little and big Endian dumps using `--endianness={little,big}`, see #189 and #104 (@RinHizakura)\n\n## Changes\n\n- **Breaking**: Changed the meaning of the short flag `-C` to be consistent with `hexdump -C`. Previously, this would *hide* the character panel, but now `-C` *shows* the character panel, in case it has been previously (e.g. in an `alias`) disabled with `--no-characters`, see #187 (@sharkdp)\n\n## `hexyl` as a library\n\n- New `endianness` method for `PrinterBuilder`\n\n\n# v0.12.0\n\n## Features\n\n- Only show one panel by default if the terminal width is not wide enough for two panels, see #182 (@sharkdp)\n- Respect the `NO_COLOR` environment variable, see #179 (@sharifhsn)\n\n## Bugfixes\n\n- Do not fail with an error if `--panels=auto` is used and the output is piped, see #184 (@sharkdp)\n\n## Changes\n\n- Breaking: For `xxd`-compatibility reasons, `--group_bytes` has been renamed to `--group-size` (with an `--groupsize` alias), see #121 (@sharkdp)\n\n## `hexyl` as a library\n\n- Breaking: `num_group_bytes` has been renamed to `group_size`.\n\n\n# v0.11.0\n\n## Features\n\n- Significantly improved performance, see #173 and #176 (@sharifhsn)\n- Added variable panels through the `--panels` and `--terminal-width` flags, see [#13](https://github.com/sharkdp/hexyl/issues/13) and [#164](https://github.com/sharkdp/hexyl/pull/164) (@sharifhsn)\n- Added new `--group-bytes`/`-g` option, see #104 and #170 (@RinHizakura)\n- Added new `--base B` option (where `B` can be `binary`, `octal`, `decimal` or `hexadecimal`), see #147 and #178 (@sharifhsn)\n- Show actual zero bytes as `⋄` in the character panel (previously: `0`), in order not to confuse them with ASCII\n  `0` bytes if colors are deactivated. Closes #166 (@sharkdp)\n\n## `hexyl` as a library\n\n- Breaking change: `Printer::new` is deprecated as a part of the public API. Alternatively, you can now construct a `Printer` using the `PrinterBuilder` builder API, see [#168](https://github.com/sharkdp/hexyl/pull/168). (@sharifhsn)\n\n## Other\n\n- More tests for the squeezing feature, see #177 (@mkatychev)\n\n## Thank you\n\nSpecial thanks go to @sharifhsn, not just for the new features,\nbugfixes and performance improvements. But also for many internal\nimprovements of the code base and other maintenance tasks.\n\n\n# v0.10.0\n\n## Features\n\n- Added new `--plain`, `--no-characters`, and `--no-position` flags, see #154 (@mkatychev)\n- Allow hex numbers and units for `--block-size` argument, see #111 and #144 (@merkrafter)\n\n## Other\n\n- Added a man page, see #151 (@sorairolake)\n- Mention ability to specify length in hex, see #143 (@merkrafter)\n- `--length` and `--bytes` are now marked as conflicting command-line options, see #152 (@sorairolake)\n\n\n# v0.9.0\n\n## Changes\n\n- Breaking change (binary): setting the `-o/--display-offset` flag no longer overrides the value set by `--skip` [#115](https://github.com/sharkdp/hexyl/issues/115). The first displayed address is now the sum of the two values - this matches the behaviour of `xxd`.\n\n## Features\n\n- Allow relative and negative byte offsets (e.g. `hexyl --skip=-1block`), see #99 (@ErichDonGubler)\n- Added `-l` as another alias for '-n/--length' (`xxd` compatibility), see #121 and #135 (@TheDoctor314)\n\n## Bugfixes\n\n- Argument `--length` silently takes precedence over `--bytes`, see #105\n- Print warning on empty content, see #107 and #108\n- Disallow block sizes of zero, see #110\n- Fix newline appearing in `--version` output, see #131 and #133 (@scimas)\n\n## Other\n\n- Better diagnostic messages, see #98 (@ErichDonGubler)\n\n## Packaging\n\n- `hexyl` is now available on snapstore, see #116 (@purveshpatel511)\n\n\n# v0.8.0\n\n## Features\n\n- A new `--skip <N>` / `-s <N>` option can be used to skip the first `N` bytes of the input, see #16, #88 (@Tarnadas, @MaxJohansen, @ErichDonGubler)\n- The `--length`/`--bytes`/`--skip`/`--display-offset` options can now take units for their value argument, for example:\n  ``` bash\n  hexyl /dev/random --length=1KiB\n  hexyl $(which hexyl) --skip=1MiB --length=10KiB\n  ```\n  Both decimal SI prefixes (kB, MB, …) as well as binary IEC prefixes (KiB, MiB, …) are supported.\n  In addition, there is a new `--block-size <SIZE>` option that can be used to control the size of the `block`\n  unit:\n  ``` bash\n  hexyl /dev/random --block-size=4kB --length=2block\n  ```\n  See: #44 (@ErichDonGubler and @aswild)\n\n## Other\n\n- Various improvements throughout the code base by @ErichDonGubler\n\n## Packaging\n\n- `hexyl` is now available on Void Linux, see #91 (@notramo)\n\n\n# v0.7.0\n\n## Bugfixes\n\n- hexyl can now be closed with `Ctrl-C` when reading input from STDIN, see #84\n\n## Changes\n\n- Breaking change (library): [`Printer::print_all`](https://docs.rs/hexyl/latest/hexyl/struct.Printer.html#method.print_all) does not take a second argument anymore.\n- Added an example on how to use `hexyl` as a library: https://github.com/sharkdp/hexyl/blob/v0.7.0/examples/simple.rs\n\n\n# v0.6.0\n\n## Features\n\n- `hexyl` can now be used as a library, see #67 (@tommilligan)\n\n- Added a new `-o`/`--display-offset` option to add a certain offset to the\n  reported file positions, see #57 (@tommilligan)\n\n## Bugfixes\n\n- Remove additional space on short input, see #69 (@nalshihabi)\n\n## Other\n\n- Performance improvements, see #73 and #66\n\n\n# v0.5.1\n\n## Bugfixes\n\n- A bug in the squeezing logic caused a wrong hexdump, see #62 (@awidegreen)\n- Some colors are printed even if they're disabled, see #64 (@awidegreen)\n- Fixed build failure on OpenBSD 6.5, see #61\n\n\n# v0.5.0\n\n## Features\n\n- Added support for squeezing where reoccurring lines are squashed together and visualized with an asterisk. A new `-v`/`--no-squeezing` option can be used to disable the feature. For details, see #59 (@awidegreen)\n- Added a new `--border` option with support for various styles (Unicode, ASCII, None), see #54 (@dmke)\n- The `--length`/`-n` argument can be passed as a hexadecimal number (`hexyl -n 0xff /dev/urandom`), see #45 (@Qyriad)\n- Added `--bytes`/`-c` as an alias for `--length`/`-n`, see #48 (@selfup)\n\n## Changes\n\n- Print header immediately before the first line, see #51 (@mziter)\n\n\n# v0.4.0\n\n## Features\n\n- Added a new `--color=always/auto/never` option which can be used\n  to control `hexyl`s color output, see #30 (@bennetthardwick)\n- Use 16 colors instead of 256, see #38\n\n## Changes\n\n- Various speed improvements, see #33 (@kballard)\n\n## Bugfixes\n\n- Proper Ctrl-C handling, see #35\n- Proper handling of broken pipes (`hexyl … | head`)\n\n\n# v0.3.1\n\n- Various (huge) performance improvements, see #23 and #24 (@kballard)\n- Replaced 24-bit truecolor ANSI codes by 8-bit codes to support\n  more terminal emulators, fixes #9\n\n\n# v0.3.0\n\nWindows support\n\n\n# v0.2.0\n\nInitial release\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nThank you for considering to contribute to `hexyl`!\n\n\n\n## Add an entry to the changelog\n\nIf your contribution changes the behavior of `hexyl` (as opposed to a typo-fix\nin the documentation), please update the [`CHANGELOG.md`](CHANGELOG.md) file\nand describe your changes. This makes the release process much easier and\ntherefore helps to get your changes into a new `hexyl` release faster.\n\nThe top of the `CHANGELOG` contains a *\"unreleased\"* section with a few\nsubsections (Features, Bugfixes, …). Please add your entry to the subsection\nthat best describes your change\n\nEntries follow this format:\n```\n- Short description of what has been changed, see #123 (@user)\n```\nHere, `#123` is the number of the original issue and/or your pull request.\nPlease replace `@user` by your GitHub username.\n\n\n## Adding a new feature\n\nPlease consider opening a [ticket](https://github.com/sharkdp/hexyl/issues/new)\nfirst in order to give us a chance to discuss the feature first.\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nauthors = [\"David Peter <mail@david-peter.de>\"]\ncategories = [\"command-line-utilities\"]\nkeywords = [\"hex\", \"viewer\"]\ndescription = \"A command-line hex viewer\"\nhomepage = \"https://github.com/sharkdp/hexyl\"\nlicense = \"MIT/Apache-2.0\"\nname = \"hexyl\"\nreadme = \"README.md\"\nrepository = \"https://github.com/sharkdp/hexyl\"\nversion = \"0.17.0\"\nedition = \"2021\"\nrust-version = \"1.88\"\n\n[dependencies]\nanyhow = \"1.0\"\nconst_format = \"0.2\"\nlibc = \"0.2\"\nowo-colors = \"4\"\nsupports-color = \"3\"\nthiserror = \"1.0\"\nterminal_size = \"0.4\"\nclap_complete = \"4\"\n\n[dependencies.clap]\nversion = \"4\"\nfeatures = [\"derive\", \"wrap_help\"]\n\n[dev-dependencies]\nassert_cmd = \"2.1\"\npredicates = \"3.0\"\npretty_assertions = \"1.4.0\"\n\n[profile.release]\nlto = true\ncodegen-units = 1\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "LICENSE-MIT",
    "content": "Permission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "![](doc/logo.svg)\n\n[![CICD](https://github.com/sharkdp/hexyl/actions/workflows/CICD.yml/badge.svg)](https://github.com/sharkdp/hexyl/actions/workflows/CICD.yml)\n[![](https://img.shields.io/crates/l/hexyl.svg?colorB=22ba4c)](https://crates.io/crates/hexyl)\n![](https://img.shields.io/crates/v/hexyl.svg?colorB=00aa88)\n\n`hexyl` is a hex viewer for the terminal. It uses a colored output to distinguish different categories\nof bytes (NULL bytes, printable ASCII characters, ASCII whitespace characters, other ASCII characters and non-ASCII).\n\n### Sponsors\n\nA special *thank you* goes to our biggest <a href=\"doc/sponsors.md\">sponsor</a>:<br>\n\n<a href=\"https://www.warp.dev/hexyl\">\n  <img src=\"doc/sponsors/warp-logo.png\" width=\"200\" alt=\"Warp\">\n  <br>\n  <strong>Warp, the intelligent terminal</strong>\n  <br>\n  <sub>Available on MacOS, Linux, Windows</sub>\n</a>\n\n## Preview\n\n![](https://i.imgur.com/MWO9uSL.png)\n\n![](https://i.imgur.com/Dp7Wncz.png)\n\n![](https://i.imgur.com/ln3TniI.png)\n\n![](https://i.imgur.com/f8nm8g6.png)\n\n\n## Installation\n\n### On Ubuntu\n\n*... and other Debian-based Linux distributions.*\n\nIf you run Ubuntu 19.10 (Eoan Ermine) or newer, you can install the [officially maintained package](https://packages.ubuntu.com/search?keywords=hexyl):\n```bash\nsudo apt install hexyl\n```\nIf you use an older version of Ubuntu, you can download\nthe latest `.deb` package from the release page and install it via:\n\n``` bash\nsudo dpkg -i hexyl_0.15.0_amd64.deb  # adapt version number and architecture\n```\n\n### On Debian\n\nIf you run Debian Buster or newer, you can install the [officially maintained Debian package](https://packages.debian.org/search?searchon=names&keywords=hexyl):\n```bash\nsudo apt-get install hexyl\n```\n\nIf you run an older version of Debian, see above for instructions on how to\nmanually install `hexyl`.\n\n### On Fedora\n\nIf you run Fedora 35 or newer, you can install the [officially maintained Fedora package](https://packages.fedoraproject.org/pkgs/rust-hexyl/hexyl):\n\n```bash\nsudo dnf install hexyl\n```\n\n### On Arch Linux\n\nYou can install `hexyl` from [the official package repository](https://archlinux.org/packages/extra/x86_64/hexyl/):\n\n```\npacman -S hexyl\n```\n\n### On Void Linux\n\n```\nxbps-install hexyl\n```\n\n### On Gentoo Linux\n\nAvailable in [dm9pZCAq overlay](https://github.com/gentoo-mirror/dm9pZCAq)\n\n```\nsudo eselect repository enable dm9pZCAq\nsudo emerge --sync dm9pZCAq\nsudo emerge sys-apps/hexyl::dm9pZCAq\n```\n\n### On macOS\n\nVia [Homebrew](https://brew.sh):\n\n```\nbrew install hexyl\n```\n\n...or via [MacPorts](https://www.macports.org):\n\n```\nsudo port install hexyl\n```\n\n### On FreeBSD\n\n```\npkg install hexyl\n```\n\n### On NetBSD\n\n```\npkgin install hexyl\n```\n\n### On OpenBSD\n\n```\ndoas pkg_add hexyl\n```\n\n### on Termux\n```\npkg install hexyl\n```\nor\n```\napt install hexyl\n```\n\n### Via Nix\n\n```\nnix-env -i hexyl\n```\n\n### Via Guix\n\n```\nguix package -i hexyl\n```\n\nOr add the `hexyl` package in the list of packages to be installed in your system configuration (e.g., `/etc/config.scm`).\n\n### On other distributions\n\nCheck out the [release page](https://github.com/sharkdp/hexyl/releases) for binary builds.\n\n### On Windows\n\nCheck out the [release page](https://github.com/sharkdp/hexyl/releases) for binary builds.\nAlternatively, install from source via `cargo`, `snap` or `scoop` (see below).\nMake sure that you use a terminal that supports ANSI escape sequences (like ConHost v2 since Windows 10 1703\nor Windows Terminal since Windows 10 1903).\n\n### Via cargo\n\nIf you have Rust 1.56 or higher, you can install `hexyl` from source via `cargo`:\n```\ncargo install hexyl\n```\n\nAlternatively, you can install `hexyl` directly from the repository by using:\n```\ngit clone https://github.com/sharkdp/hexyl\ncargo install --path ./hexyl\n```\n\nNote: To convert the man page, you will need [Pandoc](https://pandoc.org/).\n\nYou can convert from Markdown by using (in the project root):\n```\npandoc -s -f markdown -t man -o ./doc/hexyl.1 ./doc/hexyl.1.md\n```\n\n### Via snap package\n\n```\nsudo snap install hexyl\n```\n[Get it from the Snap Store](https://snapcraft.io/hexyl)\n\n\n### Via [Scoop](https://scoop.sh)\n```\nscoop install hexyl\n```\n\n### Via [X-CMD](https://x-cmd.com)\n```\nx env use hexyl\n```\n\n## Configuration\n\n`hexyl` colors can be configured via environment variables. The variables used are as follows:\n\n * `HEXYL_COLOR_ASCII_PRINTABLE`: Any non-whitespace printable ASCII character\n * `HEXYL_COLOR_ASCII_WHITESPACE`: Whitespace such as space or newline (only visible in middle panel with byte values)\n * `HEXYL_COLOR_ASCII_OTHER`: Any other ASCII character (< `0x80`) besides null\n * `HEXYL_COLOR_NULL`: The null byte (`0x00`)\n * `HEXYL_COLOR_NONASCII`: Any non-ASCII byte (> `0x7F`)\n * `HEXYL_COLOR_OFFSET`: The lefthand file offset\n\nThe colors can be any of the 8 standard terminal colors: `black`, `blue`, `cyan`, `green`, `magenta`, `red`,\n`yellow` and `white`. The \"bright\" variants are also supported (e.g., `bright blue`). Additionally, you can use\nthe RGB hex format, `#abcdef`. For example, `HEXYL_COLOR_ASCII_PRINTABLE=blue HEXYL_COLOR_ASCII_WHITESPACE=\"bright green\"\nHEXYL_COLOR_ASCII_OTHER=\"#ff7f99\"`.\n\n## License\n\nLicensed under either of\n\n * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0)\n * MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT)\n\nat your option.\n"
  },
  {
    "path": "doc/hexyl.1.md",
    "content": "% HEXYL(1) hexyl 0.12.0 | General Commands Manual\n%\n% 2022-12-05\n\n# NAME\n\nhexyl - a command-line hex viewer\n\n# SYNOPSIS\n\n**hexyl** [_OPTIONS_] [_FILE_]\n\n# DESCRIPTION\n\n**hexyl** is a simple hex viewer for the terminal.\nIt uses a colored output to distinguish different categories of bytes (NULL\nbytes, printable ASCII characters, ASCII whitespace characters, other ASCII\ncharacters and non-ASCII).\n\n# POSITIONAL ARGUMENTS\n\n_FILE_\n:   The file to display.\n    If no _FILE_ argument is given, read from STDIN.\n\n# OPTIONS\n\n**-n**, **\\--length** _N_\n:   Only read _N_ bytes from the input.\n    The _N_ argument can also include a unit with a decimal prefix (kB, MB, ..)\n    or binary prefix (kiB, MiB, ..), or can be specified using a hex number.\n\n    Examples:\n\n    :   \n\n        Read the first 64 bytes:\n        :   $ **hexyl \\--length=64**\n\n        Read the first 4 kibibytes:\n        :   $ **hexyl \\--length=4KiB**\n\n        Read the first 255 bytes (specified using a hex number):\n        :   $ **hexyl \\--length=0xff**\n\n**-c**, **\\--bytes** _N_\n:   An alias for **-n**/**\\--length**.\n\n**-l** _N_\n:   Yet another alias for **-n**/**\\--length**.\n\n**-s**, **\\--skip** _N_\n:   Skip the first _N_ bytes of the input.\n    The _N_ argument can also include a unit (see **\\--length** for details).\n    A negative value is valid and will seek from the end of the file.\n\n**\\--block-size** _SIZE_\n:   Sets the size of the block unit to _SIZE_ (default is 512).\n\n    Examples:\n\n    :   \n\n        Sets the block size to 1024 bytes:\n        :   $ **hexyl \\--block-size=1024 \\--length=5block**\n\n        Sets the block size to 4 kilobytes:\n        :   $ **hexyl \\--block-size=4kB \\--length=2block**\n\n**-v**, **\\--no-squeezing**\n:   Displays all input data.\n    Otherwise any number of groups of output lines which would be identical to\n    the preceding group of lines, are replaced with a line comprised of a\n    single asterisk.\n\n**\\--color** _WHEN_\n:   When to use colors.\n    The auto-mode only displays colors if the output goes to an interactive\n    terminal.\n\n    Possible values:\n\n    :   - **always** (default)\n        - **auto**\n        - **never**\n\n**\\--border** _STYLE_\n:   Whether to draw a border with Unicode characters, ASCII characters, or none\n    at all.\n\n    Possible values:\n\n    :   - **unicode** (default)\n        - **ascii**\n        - **none**\n\n**-o**, **\\--display-offset** _N_\n:   Add _N_ bytes to the displayed file position.\n    The _N_ argument can also include a unit (see **\\--length** for details).\n    A negative value is valid and calculates an offset relative to the end of\n    the file.\n\n**-h**, **\\--help**\n:   Prints help information.\n\n**-V**, **\\--version**\n:   Prints version information.\n\n# ENVIRONMENT VARIABLES\n\n**hexyl** colors can be configured via environment variables. The variables used are as follows:\n\n:   - **HEXYL_COLOR_ASCII_PRINTABLE**: Any non-whitespace printable ASCII character\n    - **HEXYL_COLOR_ASCII_WHITESPACE**: Whitespace such as space or newline (only visible in middle panel with byte values)\n    - **HEXYL_COLOR_ASCII_OTHER**: Any other ASCII character (< **0x80**) besides null\n    - **HEXYL_COLOR_NULL**: The null byte (**0x00**)\n    - **HEXYL_COLOR_NONASCII**: Any non-ASCII byte (> **0x7F**)\n    - **HEXYL_COLOR_OFFSET**: The lefthand file offset\n\nThe colors can be any of the 8 standard terminal colors: **black**, **blue**, **cyan**, **green**, **magenta**, **red**,\n**yellow** and **white**. The \"bright\" variants are also supported (e.g., **bright blue**). Additionally, you can use\nthe RGB hex format, **#abcdef**. For example, **HEXYL_COLOR_ASCII_PRINTABLE=blue HEXYL_COLOR_ASCII_WHITESPACE=\"bright green\"\nHEXYL_COLOR_ASCII_OTHER=\"#ff7f99\"**.\n\n# NOTES\n\nSource repository:\n:   <https://github.com/sharkdp/hexyl>\n\n# EXAMPLES\n\nPrint a given file:\n:   $ **hexyl small.png**\n\nPrint and view a given file in the terminal pager:\n:   $ **hexyl big.png | less -r**\n\nPrint the first 256 bytes of a given special file:\n:   $ **hexyl -n 256 /dev/urandom**\n\n# AUTHORS\n\n**hexyl** was written by David Peter <mail@david-peter.de>.\n\n# REPORTING BUGS\n\nBugs can be reported on GitHub at:\n:   <https://github.com/sharkdp/hexyl/issues>\n\n# COPYRIGHT\n\n**hexyl** is dual-licensed under:\n\n:   - Apache License 2.0 (<https://www.apache.org/licenses/LICENSE-2.0>)\n    - MIT License (<https://opensource.org/licenses/MIT>)\n\n# SEE ALSO\n\n**hexdump**(1), **xxd**(1)\n"
  },
  {
    "path": "doc/sponsors.md",
    "content": "## Sponsors\n\n`hexyl` development is sponsored by many individuals and companies. Thank you very much!\n\nPlease note, that being sponsored does not affect the individuality of the `hexyl`\nproject or affect the maintainers' actions in any way.\nWe remain impartial and continue to assess pull requests solely on merit - the\nfeatures added, bugs solved, and effect on the overall complexity of the code.\nNo issue will have a different priority based on sponsorship status of the\nreporter.\n\nContributions from anybody are most welcomed.\n\nIf you want to see our biggest sponsors, check the top of [`README.md`](../README.md#sponsors).\n"
  },
  {
    "path": "examples/simple.rs",
    "content": "use std::io;\n\nuse hexyl::{BorderStyle, PrinterBuilder};\n\nfn main() {\n    let input = [\n        0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44,\n        0x52, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x44, 0x08, 0x02, 0x00, 0x00, 0x00,\n    ];\n\n    let stdout = io::stdout();\n    let mut handle = stdout.lock();\n\n    let mut printer = PrinterBuilder::new(&mut handle)\n        .show_color(true)\n        .show_char_panel(true)\n        .show_position_panel(true)\n        .with_border_style(BorderStyle::Unicode)\n        .enable_squeezing(false)\n        .num_panels(2)\n        .group_size(1)\n        .build();\n    printer.print_all(&input[..]).unwrap();\n}\n"
  },
  {
    "path": "src/colors.rs",
    "content": "use owo_colors::{colors, AnsiColors, Color, DynColors, OwoColorize};\nuse std::str::FromStr;\nuse std::sync::LazyLock;\n\npub static COLOR_NULL: LazyLock<String> =\n    LazyLock::new(|| init_color(\"NULL\", AnsiColors::BrightBlack));\npub static COLOR_OFFSET: LazyLock<String> =\n    LazyLock::new(|| init_color(\"OFFSET\", AnsiColors::BrightBlack));\npub static COLOR_ASCII_PRINTABLE: LazyLock<String> =\n    LazyLock::new(|| init_color(\"ASCII_PRINTABLE\", AnsiColors::Cyan));\npub static COLOR_ASCII_WHITESPACE: LazyLock<String> =\n    LazyLock::new(|| init_color(\"ASCII_WHITESPACE\", AnsiColors::Green));\npub static COLOR_ASCII_OTHER: LazyLock<String> =\n    LazyLock::new(|| init_color(\"ASCII_OTHER\", AnsiColors::Green));\npub static COLOR_NONASCII: LazyLock<String> =\n    LazyLock::new(|| init_color(\"NONASCII\", AnsiColors::Yellow));\npub const COLOR_RESET: &str = colors::Default::ANSI_FG;\n\nfn init_color(name: &str, default_ansi: AnsiColors) -> String {\n    let default = DynColors::Ansi(default_ansi);\n    let env_var = format!(\"HEXYL_COLOR_{name}\");\n    let color = match std::env::var(env_var).as_deref() {\n        Ok(color) => match DynColors::from_str(color) {\n            Ok(color) => color,\n            _ => default,\n        },\n        _ => default,\n    };\n    // owo_colors' API isn't designed to get the terminal codes directly for\n    // dynamic colors, so we use this hack to get them from the LHS of some text.\n    format!(\"{}\", \"|\".color(color))\n        .split_once(\"|\")\n        .unwrap()\n        .0\n        .to_owned()\n}\n\npub const COLOR_NULL_RGB: &[u8] = &rgb_bytes(100, 100, 100);\n\npub const COLOR_DEL: &[u8] = &rgb_bytes(64, 128, 0);\n\npub const COLOR_GRADIENT_NONASCII: [[u8; 19]; 128] =\n    generate_color_gradient(&[(255, 0, 0, 0.0), (255, 255, 0, 0.66), (255, 255, 255, 1.0)]);\n\npub const COLOR_GRADIENT_ASCII_NONPRINTABLE: [[u8; 19]; 31] =\n    generate_color_gradient(&[(255, 0, 255, 0.0), (128, 0, 255, 1.0)]);\n\npub const COLOR_GRADIENT_ASCII_PRINTABLE: [[u8; 19]; 95] =\n    generate_color_gradient(&[(0, 128, 255, 0.0), (0, 255, 128, 1.0)]);\n\nconst fn as_dec(byte: u8) -> [u8; 3] {\n    [\n        b'0' + (byte / 100),\n        b'0' + ((byte % 100) / 10),\n        b'0' + (byte % 10),\n    ]\n}\n\nconst fn rgb_bytes(r: u8, g: u8, b: u8) -> [u8; 19] {\n    let mut buf = *b\"\\x1b[38;2;rrr;ggg;bbbm\";\n\n    // r 7\n    buf[7] = as_dec(r)[0];\n    buf[8] = as_dec(r)[1];\n    buf[9] = as_dec(r)[2];\n\n    // g 11\n    buf[11] = as_dec(g)[0];\n    buf[12] = as_dec(g)[1];\n    buf[13] = as_dec(g)[2];\n\n    // b 15\n    buf[15] = as_dec(b)[0];\n    buf[16] = as_dec(b)[1];\n    buf[17] = as_dec(b)[2];\n\n    buf\n}\n\nconst fn generate_color_gradient<const N: usize>(stops: &[(u8, u8, u8, f64)]) -> [[u8; 19]; N] {\n    let mut out = [rgb_bytes(0, 0, 0); N];\n\n    assert!(stops.len() >= 2, \"need at least two stops for the gradient\");\n\n    let mut byte = 0;\n    while byte < N {\n        let relative_byte = byte as f64 / N as f64;\n\n        let mut i = 1;\n        while i < stops.len() && stops[i].3 < relative_byte {\n            i += 1;\n        }\n        if i >= stops.len() {\n            i = stops.len() - 1;\n        }\n        let prev_stop = stops[i - 1];\n        let stop = stops[i];\n        let diff = stop.3 - prev_stop.3;\n        let t = (relative_byte - prev_stop.3) / diff;\n\n        let r = (prev_stop.0 as f64 + (t * (stop.0 as f64 - prev_stop.0 as f64))) as u8;\n        let g = (prev_stop.1 as f64 + (t * (stop.1 as f64 - prev_stop.1 as f64))) as u8;\n        let b = (prev_stop.2 as f64 + (t * (stop.2 as f64 - prev_stop.2 as f64))) as u8;\n\n        out[byte] = rgb_bytes(r, g, b);\n\n        byte += 1;\n    }\n\n    out\n}\n\n#[rustfmt::skip]\npub const CP437: [char; 256] = [\n    // Copyright (c) 2016, Delan Azabani <delan@azabani.com>\n    //\n    // Permission to use, copy, modify, and/or distribute this software for any\n    // purpose with or without fee is hereby granted, provided that the above\n    // copyright notice and this permission notice appear in all copies.\n    //\n    // THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n    // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n    // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n    // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n    // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n    // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n    // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n    //\n    // modified to use the ⋄ character instead of ␀\n\n    // use https://en.wikipedia.org/w/index.php?title=Code_page_437&oldid=978947122\n    // not ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP437.TXT\n    // because we want the graphic versions of 01h–1Fh + 7Fh\n    '⋄','☺','☻','♥','♦','♣','♠','•','◘','○','◙','♂','♀','♪','♫','☼',\n    '►','◄','↕','‼','¶','§','▬','↨','↑','↓','→','←','∟','↔','▲','▼',\n    ' ','!','\"','#','$','%','&','\\'','(',')','*','+',',','-','.','/',\n    '0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?',\n    '@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',\n    'P','Q','R','S','T','U','V','W','X','Y','Z','[','\\\\',']','^','_',\n    '`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o',\n    'p','q','r','s','t','u','v','w','x','y','z','{','|','}','~','⌂',\n    'Ç','ü','é','â','ä','à','å','ç','ê','ë','è','ï','î','ì','Ä','Å',\n    'É','æ','Æ','ô','ö','ò','û','ù','ÿ','Ö','Ü','¢','£','¥','₧','ƒ',\n    'á','í','ó','ú','ñ','Ñ','ª','º','¿','⌐','¬','½','¼','¡','«','»',\n    '░','▒','▓','│','┤','╡','╢','╖','╕','╣','║','╗','╝','╜','╛','┐',\n    '└','┴','┬','├','─','┼','╞','╟','╚','╔','╩','╦','╠','═','╬','╧',\n    '╨','╤','╥','╙','╘','╒','╓','╫','╪','┘','┌','█','▄','▌','▐','▀',\n    'α','ß','Γ','π','Σ','σ','µ','τ','Φ','Θ','Ω','δ','∞','φ','ε','∩',\n    '≡','±','≥','≤','⌠','⌡','÷','≈','°','∙','·','√','ⁿ','²','■','ﬀ',\n];\n\n#[rustfmt::skip]\npub const CP1047: [char; 256] = [\n     //\n     //  Copyright (c) 2016,2024 IBM Corporation and other Contributors.\n     //\n     //  All rights reserved. This program and the accompanying materials\n     //  are made available under the terms of the Eclipse Public License v1.0\n     //  which accompanies this distribution, and is available at\n     //  http://www.eclipse.org/legal/epl-v10.html\n     //\n     //  Contributors:\n     //    Mark Taylor - Initial Contribution\n     //\n\n     // ref1 https://github.com/ibm-messaging/mq-smf-csv/blob/master/src/smfConv.c\n    //  ref2 https://web.archive.org/web/20150607033635/http://www-01.ibm.com/software/globalization/cp/cp01047.html\n    '.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.',\n    '.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.',\n    '.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.',\n    '.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.',\n    ' ','.','.','.','.','.','.','.','.','.','$','.','<','(','+','|',\n    '&','.','.','.','.','.','.','.','.','.','!','$','*',')',';','.',\n    '-','/','.','.','.','.','.','.','.','.','.',',','%','_','>','?',\n    '.','.','.','.','.','.','.','.','.','.',':','#','@','\\'','=','.',\n    '.','a','b','c','d','e','f','g','h','i','.','{','.','(','+','.',\n    '.','j','k','l','m','n','o','p','q','r','.','}','.',')','.','.',\n    '.','~','s','t','u','v','w','x','y','z','.','.','.','.','.','.',\n    '.','.','.','.','.','.','.','.','.','.','[',']','.','.','.','-',\n    '{','A','B','C','D','E','F','G','H','I','.','.','.','.','.','.',\n    '}','J','K','L','M','N','O','P','Q','R','.','.','.','.','.','.',\n    '.','.','S','T','U','V','W','X','Y','Z','.','.','.','.','.','.',\n    '0','1','2','3','4','5','6','7','8','9','.','.','.','.','.','.'\n];\n"
  },
  {
    "path": "src/input.rs",
    "content": "use std::fs;\nuse std::io::{self, copy, sink, Read, Seek, SeekFrom};\n\npub enum Input<'a> {\n    File(fs::File),\n    Stdin(io::StdinLock<'a>),\n}\n\nimpl Read for Input<'_> {\n    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {\n        match *self {\n            Input::File(ref mut file) => file.read(buf),\n            Input::Stdin(ref mut stdin) => stdin.read(buf),\n        }\n    }\n}\n\nimpl Seek for Input<'_> {\n    fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {\n        fn try_skip<R>(reader: R, pos: SeekFrom, err_desc: &'static str) -> io::Result<u64>\n        where\n            R: Read,\n        {\n            let cant_seek_abs_err = || Err(io::Error::other(err_desc));\n\n            let offset = match pos {\n                SeekFrom::Current(o) => u64::try_from(o).or_else(|_e| cant_seek_abs_err())?,\n                SeekFrom::Start(_) | SeekFrom::End(_) => cant_seek_abs_err()?,\n            };\n\n            copy(&mut reader.take(offset), &mut sink())\n        }\n\n        match *self {\n            Input::File(ref mut file) => {\n                let seek_res = file.seek(pos);\n                if let Err(Some(libc::ESPIPE)) = seek_res.as_ref().map_err(|err| err.raw_os_error())\n                {\n                    try_skip(\n                        file,\n                        pos,\n                        \"Pipes only support seeking forward with a relative offset\",\n                    )\n                } else {\n                    seek_res\n                }\n            }\n            Input::Stdin(ref mut stdin) => try_skip(\n                stdin,\n                pos,\n                \"STDIN only supports seeking forward with a relative offset\",\n            ),\n        }\n    }\n}\n\nimpl<'a> Input<'a> {\n    pub fn into_inner(self) -> Box<dyn Read + 'a> {\n        match self {\n            Input::File(file) => Box::new(file),\n            Input::Stdin(stdin) => Box::new(stdin),\n        }\n    }\n}\n"
  },
  {
    "path": "src/lib.rs",
    "content": "pub(crate) mod colors;\npub(crate) mod input;\n\npub use colors::*;\npub use input::*;\n\nuse std::io::{self, BufReader, Read, Write};\n\nuse clap::ValueEnum;\n\npub enum Base {\n    Binary,\n    Octal,\n    Decimal,\n    Hexadecimal,\n}\n\n#[derive(Copy, Clone)]\npub enum ByteCategory {\n    Null,\n    AsciiPrintable,\n    AsciiWhitespace,\n    AsciiOther,\n    NonAscii,\n}\n\npub enum IncludeMode {\n    File(String), // filename\n    Stdin,\n    Slice,\n    Off,\n}\n\n#[derive(Copy, Clone, Debug, Default, ValueEnum)]\n#[non_exhaustive]\npub enum CharacterTable {\n    /// Show printable ASCII characters as-is, '⋄' for NULL bytes, ' ' for\n    /// space, '_' for other ASCII whitespace, '•' for other ASCII characters,\n    /// and '×' for non-ASCII bytes.\n    #[default]\n    Default,\n\n    /// Show printable ASCII as-is, ' ' for space, '.' for everything else.\n    Ascii,\n\n    /// Show printable EBCDIC as-is, ' ' for space, '.' for everything else.\n    #[value(name = \"codepage-1047\")]\n    CP1047,\n\n    /// Uses code page 437 (for non-ASCII bytes).\n    #[value(name = \"codepage-437\")]\n    CP437,\n\n    /// Uses braille characters for non-printable bytes.\n    Braille,\n}\n\n#[derive(Copy, Clone, Debug, Default, ValueEnum)]\n#[non_exhaustive]\npub enum ColorScheme {\n    /// Show the default colors: bright black for NULL bytes, green for ASCII\n    /// space characters and non-printable ASCII, cyan for printable ASCII characters,\n    /// and yellow for non-ASCII bytes.\n    #[default]\n    Default,\n\n    /// Show bright black for NULL bytes, cyan for printable ASCII characters, a gradient\n    /// from pink to violet for non-printable ASCII characters and a heatmap-like gradient\n    /// from red to yellow to white for non-ASCII bytes.\n    Gradient,\n}\n\n#[derive(Copy, Clone, Debug, Default, ValueEnum)]\npub enum Endianness {\n    /// Print out groups in little-endian format.\n    Little,\n\n    /// Print out groups in big-endian format.\n    #[default]\n    Big,\n}\n\n#[derive(PartialEq)]\nenum Squeezer {\n    Print,\n    Delete,\n    Ignore,\n    Disabled,\n}\n\n#[derive(Copy, Clone)]\nstruct Byte(u8);\n\nimpl Byte {\n    fn category(self) -> ByteCategory {\n        if self.0 == 0x00 {\n            ByteCategory::Null\n        } else if self.0.is_ascii_graphic() {\n            ByteCategory::AsciiPrintable\n        } else if self.0.is_ascii_whitespace() {\n            ByteCategory::AsciiWhitespace\n        } else if self.0.is_ascii() {\n            ByteCategory::AsciiOther\n        } else {\n            ByteCategory::NonAscii\n        }\n    }\n\n    fn color(self, color_scheme: ColorScheme) -> &'static [u8] {\n        use crate::ByteCategory::*;\n        match color_scheme {\n            ColorScheme::Default => match self.category() {\n                Null => COLOR_NULL.as_bytes(),\n                AsciiPrintable => COLOR_ASCII_PRINTABLE.as_bytes(),\n                AsciiWhitespace => COLOR_ASCII_WHITESPACE.as_bytes(),\n                AsciiOther => COLOR_ASCII_OTHER.as_bytes(),\n                NonAscii => COLOR_NONASCII.as_bytes(),\n            },\n            ColorScheme::Gradient => match self.category() {\n                Null => COLOR_NULL_RGB,\n                AsciiWhitespace if self.0 == b' ' => &COLOR_GRADIENT_ASCII_PRINTABLE[0],\n                AsciiPrintable => &COLOR_GRADIENT_ASCII_PRINTABLE[(self.0 - b' ') as usize],\n                AsciiWhitespace | AsciiOther => {\n                    if self.0 == 0x7f {\n                        COLOR_DEL\n                    } else {\n                        &COLOR_GRADIENT_ASCII_NONPRINTABLE[self.0 as usize - 1]\n                    }\n                }\n                NonAscii => &COLOR_GRADIENT_NONASCII[(self.0 - 128) as usize],\n            },\n        }\n    }\n\n    fn as_char(self, character_table: CharacterTable) -> char {\n        use crate::ByteCategory::*;\n        match character_table {\n            CharacterTable::Default => match self.category() {\n                Null => '⋄',\n                AsciiPrintable => self.0 as char,\n                AsciiWhitespace if self.0 == 0x20 => ' ',\n                AsciiWhitespace => '_',\n                AsciiOther => '•',\n                NonAscii => '×',\n            },\n            CharacterTable::Ascii => match self.category() {\n                Null => '.',\n                AsciiPrintable => self.0 as char,\n                AsciiWhitespace if self.0 == 0x20 => ' ',\n                AsciiWhitespace => '.',\n                AsciiOther => '.',\n                NonAscii => '.',\n            },\n            CharacterTable::CP1047 => CP1047[self.0 as usize],\n            CharacterTable::CP437 => CP437[self.0 as usize],\n            CharacterTable::Braille => match self.category() {\n                // null is important enough to get its own symbol\n                Null => '⋄',\n                AsciiPrintable => self.0 as char,\n                AsciiWhitespace if self.0 == b' ' => ' ',\n                // `\\t`, `\\n` and `\\r` are important enough to get their own symbols\n                AsciiWhitespace if self.0 == b'\\t' => '→',\n                AsciiWhitespace if self.0 == b'\\n' => '↵',\n                AsciiWhitespace if self.0 == b'\\r' => '←',\n                AsciiWhitespace | AsciiOther | NonAscii => {\n                    /// Adjust the bits from the original number to a new number.\n                    ///\n                    /// Bit positions in braille are adjusted as follows:\n                    ///\n                    /// ```text\n                    /// 0 3 => 0 1\n                    /// 1 4 => 2 3\n                    /// 2 5 => 4 5\n                    /// 6 7 => 6 7\n                    /// ```\n                    fn to_braille_bits(byte: u8) -> u8 {\n                        let mut out = 0;\n                        for (from, to) in [0, 3, 1, 4, 2, 5, 6, 7].into_iter().enumerate() {\n                            out |= (byte >> from & 1) << to;\n                        }\n                        out\n                    }\n\n                    char::from_u32(0x2800 + to_braille_bits(self.0) as u32).unwrap()\n                }\n            },\n        }\n    }\n}\n\nstruct BorderElements {\n    left_corner: char,\n    horizontal_line: char,\n    column_separator: char,\n    right_corner: char,\n}\n\n#[derive(Clone, Copy, Debug, Default, ValueEnum)]\npub enum BorderStyle {\n    /// Draw a border with Unicode characters.\n    #[default]\n    Unicode,\n\n    /// Draw a border with ASCII characters.\n    Ascii,\n\n    /// Do not draw a border at all.\n    None,\n}\n\nimpl BorderStyle {\n    fn header_elems(&self) -> Option<BorderElements> {\n        match self {\n            BorderStyle::Unicode => Some(BorderElements {\n                left_corner: '┌',\n                horizontal_line: '─',\n                column_separator: '┬',\n                right_corner: '┐',\n            }),\n            BorderStyle::Ascii => Some(BorderElements {\n                left_corner: '+',\n                horizontal_line: '-',\n                column_separator: '+',\n                right_corner: '+',\n            }),\n            BorderStyle::None => None,\n        }\n    }\n\n    fn footer_elems(&self) -> Option<BorderElements> {\n        match self {\n            BorderStyle::Unicode => Some(BorderElements {\n                left_corner: '└',\n                horizontal_line: '─',\n                column_separator: '┴',\n                right_corner: '┘',\n            }),\n            BorderStyle::Ascii => Some(BorderElements {\n                left_corner: '+',\n                horizontal_line: '-',\n                column_separator: '+',\n                right_corner: '+',\n            }),\n            BorderStyle::None => None,\n        }\n    }\n\n    fn outer_sep(&self) -> char {\n        match self {\n            BorderStyle::Unicode => '│',\n            BorderStyle::Ascii => '|',\n            BorderStyle::None => ' ',\n        }\n    }\n\n    fn inner_sep(&self) -> char {\n        match self {\n            BorderStyle::Unicode => '┊',\n            BorderStyle::Ascii => '|',\n            BorderStyle::None => ' ',\n        }\n    }\n}\n\npub struct PrinterBuilder<'a, Writer: Write> {\n    writer: &'a mut Writer,\n    show_color: bool,\n    show_char_panel: bool,\n    show_position_panel: bool,\n    border_style: BorderStyle,\n    use_squeeze: bool,\n    panels: u64,\n    group_size: u8,\n    base: Base,\n    endianness: Endianness,\n    character_table: CharacterTable,\n    include_mode: IncludeMode,\n    color_scheme: ColorScheme,\n}\n\nimpl<'a, Writer: Write> PrinterBuilder<'a, Writer> {\n    pub fn new(writer: &'a mut Writer) -> Self {\n        PrinterBuilder {\n            writer,\n            show_color: true,\n            show_char_panel: true,\n            show_position_panel: true,\n            border_style: BorderStyle::Unicode,\n            use_squeeze: true,\n            panels: 2,\n            group_size: 1,\n            base: Base::Hexadecimal,\n            endianness: Endianness::Big,\n            character_table: CharacterTable::Default,\n            include_mode: IncludeMode::Off,\n            color_scheme: ColorScheme::Default,\n        }\n    }\n\n    pub fn show_color(mut self, show_color: bool) -> Self {\n        self.show_color = show_color;\n        self\n    }\n\n    pub fn show_char_panel(mut self, show_char_panel: bool) -> Self {\n        self.show_char_panel = show_char_panel;\n        self\n    }\n\n    pub fn show_position_panel(mut self, show_position_panel: bool) -> Self {\n        self.show_position_panel = show_position_panel;\n        self\n    }\n\n    pub fn with_border_style(mut self, border_style: BorderStyle) -> Self {\n        self.border_style = border_style;\n        self\n    }\n\n    pub fn enable_squeezing(mut self, enable: bool) -> Self {\n        self.use_squeeze = enable;\n        self\n    }\n\n    pub fn num_panels(mut self, num: u64) -> Self {\n        self.panels = num;\n        self\n    }\n\n    pub fn group_size(mut self, num: u8) -> Self {\n        self.group_size = num;\n        self\n    }\n\n    pub fn with_base(mut self, base: Base) -> Self {\n        self.base = base;\n        self\n    }\n\n    pub fn endianness(mut self, endianness: Endianness) -> Self {\n        self.endianness = endianness;\n        self\n    }\n\n    pub fn character_table(mut self, character_table: CharacterTable) -> Self {\n        self.character_table = character_table;\n        self\n    }\n\n    pub fn include_mode(mut self, include: IncludeMode) -> Self {\n        self.include_mode = include;\n        self\n    }\n\n    pub fn color_scheme(mut self, color_scheme: ColorScheme) -> Self {\n        self.color_scheme = color_scheme;\n        self\n    }\n\n    pub fn build(self) -> Printer<'a, Writer> {\n        Printer {\n            idx: 0,\n            line_buf: vec![0x0; 8 * self.panels as usize],\n            writer: self.writer,\n            show_char_panel: self.show_char_panel,\n            show_position_panel: self.show_position_panel,\n            show_color: self.show_color,\n            curr_color: None,\n            color_scheme: self.color_scheme,\n            border_style: self.border_style,\n            byte_hex_panel: (0u8..=u8::MAX)\n                .map(|i| match self.base {\n                    Base::Binary => format!(\"{i:08b}\"),\n                    Base::Octal => format!(\"{i:03o}\"),\n                    Base::Decimal => format!(\"{i:03}\"),\n                    Base::Hexadecimal => format!(\"{i:02x}\"),\n                })\n                .collect(),\n            byte_char_panel: (0u8..=u8::MAX)\n                .map(|i| format!(\"{}\", Byte(i).as_char(self.character_table)))\n                .collect(),\n            byte_hex_panel_g: (0u8..=u8::MAX).map(|i| format!(\"{i:02x}\")).collect(),\n            squeezer: if self.use_squeeze {\n                Squeezer::Ignore\n            } else {\n                Squeezer::Disabled\n            },\n            display_offset: 0,\n            panels: self.panels,\n            squeeze_byte: 0x00,\n            group_size: self.group_size,\n            base_digits: match self.base {\n                Base::Binary => 8,\n                Base::Octal => 3,\n                Base::Decimal => 3,\n                Base::Hexadecimal => 2,\n            },\n            endianness: self.endianness,\n            include_mode: self.include_mode,\n        }\n    }\n}\n\npub struct Printer<'a, Writer: Write> {\n    idx: u64,\n    /// the buffer containing all the bytes in a line for character printing\n    line_buf: Vec<u8>,\n    writer: &'a mut Writer,\n    show_char_panel: bool,\n    show_position_panel: bool,\n    show_color: bool,\n    curr_color: Option<&'static [u8]>,\n    color_scheme: ColorScheme,\n    border_style: BorderStyle,\n    byte_hex_panel: Vec<String>,\n    byte_char_panel: Vec<String>,\n    // same as previous but in Fixed(242) gray color, for position panel\n    byte_hex_panel_g: Vec<String>,\n    squeezer: Squeezer,\n    display_offset: u64,\n    /// The number of panels to draw.\n    panels: u64,\n    squeeze_byte: usize,\n    /// The number of octets per group.\n    group_size: u8,\n    /// The number of digits used to write the base.\n    base_digits: u8,\n    /// Whether to show groups in little or big endian format.\n    endianness: Endianness,\n    /// Whether to output in C include file style.\n    include_mode: IncludeMode,\n}\n\nimpl<'a, Writer: Write> Printer<'a, Writer> {\n    pub fn display_offset(&mut self, display_offset: u64) -> &mut Self {\n        self.display_offset = display_offset;\n        self\n    }\n\n    fn panel_sz(&self) -> usize {\n        // add one to include the trailing space of a group\n        let group_sz = self.base_digits as usize * self.group_size as usize + 1;\n        let group_per_panel = 8 / self.group_size as usize;\n        // add one to include the leading space\n        1 + group_sz * group_per_panel\n    }\n\n    fn write_border(&mut self, border_elements: BorderElements) -> io::Result<()> {\n        let h = border_elements.horizontal_line;\n        let c = border_elements.column_separator;\n        let l = border_elements.left_corner;\n        let r = border_elements.right_corner;\n        let h8 = h.to_string().repeat(8);\n        let h_repeat = h.to_string().repeat(self.panel_sz());\n\n        if self.show_position_panel {\n            write!(self.writer, \"{l}{h8}{c}\")?;\n        } else {\n            write!(self.writer, \"{l}\")?;\n        }\n\n        for _ in 0..self.panels - 1 {\n            write!(self.writer, \"{h_repeat}{c}\")?;\n        }\n        if self.show_char_panel {\n            write!(self.writer, \"{h_repeat}{c}\")?;\n        } else {\n            write!(self.writer, \"{h_repeat}\")?;\n        }\n\n        if self.show_char_panel {\n            for _ in 0..self.panels - 1 {\n                write!(self.writer, \"{h8}{c}\")?;\n            }\n            writeln!(self.writer, \"{h8}{r}\")?;\n        } else {\n            writeln!(self.writer, \"{r}\")?;\n        }\n\n        Ok(())\n    }\n\n    pub fn print_header(&mut self) -> io::Result<()> {\n        if let Some(e) = self.border_style.header_elems() {\n            self.write_border(e)?\n        }\n        Ok(())\n    }\n\n    pub fn print_footer(&mut self) -> io::Result<()> {\n        if let Some(e) = self.border_style.footer_elems() {\n            self.write_border(e)?\n        }\n        Ok(())\n    }\n\n    fn print_position_panel(&mut self) -> io::Result<()> {\n        self.writer.write_all(\n            self.border_style\n                .outer_sep()\n                .encode_utf8(&mut [0; 4])\n                .as_bytes(),\n        )?;\n        if self.show_color {\n            self.writer.write_all(COLOR_OFFSET.as_bytes())?;\n        }\n        if self.show_position_panel {\n            match self.squeezer {\n                Squeezer::Print => {\n                    self.writer.write_all(b\"*\")?;\n                    if self.show_color {\n                        self.writer.write_all(COLOR_RESET.as_bytes())?;\n                    }\n                    self.writer.write_all(b\"       \")?;\n                }\n                Squeezer::Ignore | Squeezer::Disabled | Squeezer::Delete => {\n                    let byte_index: [u8; 8] = (self.idx + self.display_offset).to_be_bytes();\n                    let mut i = 0;\n                    while byte_index[i] == 0x0 && i < 4 {\n                        i += 1;\n                    }\n                    for &byte in byte_index.iter().skip(i) {\n                        self.writer\n                            .write_all(self.byte_hex_panel_g[byte as usize].as_bytes())?;\n                    }\n                    if self.show_color {\n                        self.writer.write_all(COLOR_RESET.as_bytes())?;\n                    }\n                }\n            }\n            self.writer.write_all(\n                self.border_style\n                    .outer_sep()\n                    .encode_utf8(&mut [0; 4])\n                    .as_bytes(),\n            )?;\n        }\n        Ok(())\n    }\n\n    fn print_char(&mut self, i: u64) -> io::Result<()> {\n        match self.squeezer {\n            Squeezer::Print | Squeezer::Delete => self.writer.write_all(b\" \")?,\n            Squeezer::Ignore | Squeezer::Disabled => {\n                if let Some(&b) = self.line_buf.get(i as usize) {\n                    if self.show_color && self.curr_color != Some(Byte(b).color(self.color_scheme))\n                    {\n                        self.writer.write_all(Byte(b).color(self.color_scheme))?;\n                        self.curr_color = Some(Byte(b).color(self.color_scheme));\n                    }\n                    self.writer\n                        .write_all(self.byte_char_panel[b as usize].as_bytes())?;\n                } else {\n                    self.squeezer = Squeezer::Print;\n                }\n            }\n        }\n        if i == 8 * self.panels - 1 {\n            if self.show_color {\n                self.writer.write_all(COLOR_RESET.as_bytes())?;\n                self.curr_color = None;\n            }\n            self.writer.write_all(\n                self.border_style\n                    .outer_sep()\n                    .encode_utf8(&mut [0; 4])\n                    .as_bytes(),\n            )?;\n        } else if i % 8 == 7 {\n            if self.show_color {\n                self.writer.write_all(COLOR_RESET.as_bytes())?;\n                self.curr_color = None;\n            }\n            self.writer.write_all(\n                self.border_style\n                    .inner_sep()\n                    .encode_utf8(&mut [0; 4])\n                    .as_bytes(),\n            )?;\n        }\n\n        Ok(())\n    }\n\n    pub fn print_char_panel(&mut self) -> io::Result<()> {\n        for i in 0..self.line_buf.len() {\n            self.print_char(i as u64)?;\n        }\n        Ok(())\n    }\n\n    fn print_byte(&mut self, i: usize, b: u8) -> io::Result<()> {\n        match self.squeezer {\n            Squeezer::Print => {\n                if !self.show_position_panel && i == 0 {\n                    if self.show_color {\n                        self.writer.write_all(COLOR_OFFSET.as_bytes())?;\n                    }\n                    self.writer\n                        .write_all(self.byte_char_panel[b'*' as usize].as_bytes())?;\n                    if self.show_color {\n                        self.writer.write_all(COLOR_RESET.as_bytes())?;\n                    }\n                } else if i.is_multiple_of(self.group_size as usize) {\n                    self.writer.write_all(b\" \")?;\n                }\n                for _ in 0..self.base_digits {\n                    self.writer.write_all(b\" \")?;\n                }\n            }\n            Squeezer::Delete => self.writer.write_all(b\"   \")?,\n            Squeezer::Ignore | Squeezer::Disabled => {\n                if i.is_multiple_of(self.group_size as usize) {\n                    self.writer.write_all(b\" \")?;\n                }\n                if self.show_color && self.curr_color != Some(Byte(b).color(self.color_scheme)) {\n                    self.writer.write_all(Byte(b).color(self.color_scheme))?;\n                    self.curr_color = Some(Byte(b).color(self.color_scheme));\n                }\n                self.writer\n                    .write_all(self.byte_hex_panel[b as usize].as_bytes())?;\n            }\n        }\n        // byte is last in panel\n        if i % 8 == 7 {\n            if self.show_color {\n                self.curr_color = None;\n                self.writer.write_all(COLOR_RESET.as_bytes())?;\n            }\n            self.writer.write_all(b\" \")?;\n            // byte is last in last panel\n            if i as u64 % (8 * self.panels) == 8 * self.panels - 1 {\n                self.writer.write_all(\n                    self.border_style\n                        .outer_sep()\n                        .encode_utf8(&mut [0; 4])\n                        .as_bytes(),\n                )?;\n            } else {\n                self.writer.write_all(\n                    self.border_style\n                        .inner_sep()\n                        .encode_utf8(&mut [0; 4])\n                        .as_bytes(),\n                )?;\n            }\n        }\n        Ok(())\n    }\n\n    fn reorder_buffer_to_little_endian(&self, buf: &mut [u8]) {\n        let n = buf.len();\n        let group_sz = self.group_size as usize;\n\n        for idx in (0..n).step_by(group_sz) {\n            let remaining = n - idx;\n            let total = remaining.min(group_sz);\n\n            buf[idx..idx + total].reverse();\n        }\n    }\n\n    pub fn print_bytes(&mut self) -> io::Result<()> {\n        let mut buf = self.line_buf.clone();\n\n        if matches!(self.endianness, Endianness::Little) {\n            self.reorder_buffer_to_little_endian(&mut buf);\n        };\n\n        for (i, &b) in buf.iter().enumerate() {\n            self.print_byte(i, b)?;\n        }\n        Ok(())\n    }\n\n    /// Loop through the given `Reader`, printing until the `Reader` buffer\n    /// is exhausted.\n    pub fn print_all<Reader: Read>(&mut self, reader: Reader) -> io::Result<()> {\n        let mut is_empty = true;\n\n        let mut buf = BufReader::new(reader);\n\n        // special handler for include mode\n        match &self.include_mode {\n            // Input from a file\n            // Output like `unsigned char <filename>[] = { ... }; unsigned int <filename>_len = ...;`\n            IncludeMode::File(filename) => {\n                // convert non-alphanumeric characters to '_'\n                let var_name = filename\n                    .chars()\n                    .map(|c| if c.is_alphanumeric() { c } else { '_' })\n                    .collect::<String>();\n\n                writeln!(self.writer, \"unsigned char {}[] = {{\", var_name)?;\n\n                let total_bytes = self.print_bytes_in_include_style(&mut buf)?;\n\n                writeln!(self.writer, \"}};\")?;\n                writeln!(\n                    self.writer,\n                    \"unsigned int {}_len = {};\",\n                    var_name, total_bytes\n                )?;\n                return Ok(());\n            }\n            IncludeMode::Stdin | IncludeMode::Slice => {\n                self.print_bytes_in_include_style(&mut buf)?;\n                return Ok(());\n            }\n            IncludeMode::Off => {}\n        }\n\n        let leftover = loop {\n            // read a maximum of 8 * self.panels bytes from the reader\n            if let Ok(n) = buf.read(&mut self.line_buf) {\n                if n > 0 && n < 8 * self.panels as usize {\n                    // if less are read, that indicates end of file after\n                    if is_empty {\n                        self.print_header()?;\n                        is_empty = false;\n                    }\n                    let mut leftover = n;\n                    // loop until input is ceased\n                    if let Some(s) = loop {\n                        if let Ok(n) = buf.read(&mut self.line_buf[leftover..]) {\n                            leftover += n;\n                            // there is no more input being read\n                            if n == 0 {\n                                self.line_buf.resize(leftover, 0);\n                                break Some(leftover);\n                            }\n                            // amount read has exceeded line buffer\n                            if leftover >= 8 * self.panels as usize {\n                                break None;\n                            }\n                        }\n                    } {\n                        break Some(s);\n                    };\n                } else if n == 0 {\n                    // if no bytes are read, that indicates end of file\n                    if self.squeezer == Squeezer::Delete {\n                        // empty the last line when ending is squeezed\n                        self.line_buf.clear();\n                        break Some(0);\n                    }\n                    break None;\n                }\n            }\n            if is_empty {\n                self.print_header()?;\n            }\n\n            // squeeze is active, check if the line is the same\n            // skip print if still squeezed, otherwise print and deactivate squeeze\n            if matches!(self.squeezer, Squeezer::Print | Squeezer::Delete) {\n                if self\n                    .line_buf\n                    .chunks_exact(std::mem::size_of::<usize>())\n                    .all(|w| usize::from_ne_bytes(w.try_into().unwrap()) == self.squeeze_byte)\n                {\n                    if self.squeezer == Squeezer::Delete {\n                        self.idx += 8 * self.panels;\n                        continue;\n                    }\n                } else {\n                    self.squeezer = Squeezer::Ignore;\n                }\n            }\n\n            // print the line\n            self.print_position_panel()?;\n            self.print_bytes()?;\n            if self.show_char_panel {\n                self.print_char_panel()?;\n            }\n            self.writer.write_all(b\"\\n\")?;\n\n            if is_empty {\n                self.writer.flush()?;\n                is_empty = false;\n            }\n\n            // increment index to next line\n            self.idx += 8 * self.panels;\n\n            // change from print to delete if squeeze is still active\n            if self.squeezer == Squeezer::Print {\n                self.squeezer = Squeezer::Delete;\n            }\n\n            // repeat the first byte in the line until it's a usize\n            // compare that usize with each usize chunk in the line\n            // if they are all the same, change squeezer to print\n            let repeat_byte = (self.line_buf[0] as usize) * (usize::MAX / 255);\n            if !matches!(self.squeezer, Squeezer::Disabled | Squeezer::Delete)\n                && self\n                    .line_buf\n                    .chunks_exact(std::mem::size_of::<usize>())\n                    .all(|w| usize::from_ne_bytes(w.try_into().unwrap()) == repeat_byte)\n            {\n                self.squeezer = Squeezer::Print;\n                self.squeeze_byte = repeat_byte;\n            };\n        };\n\n        // special ending\n\n        if is_empty {\n            self.base_digits = 2;\n            self.print_header()?;\n            if self.show_position_panel {\n                write!(self.writer, \"{0:9}\", \"│\")?;\n            }\n            write!(\n                self.writer,\n                \"{0:2}{1:2$}{0}{0:>3$}\",\n                \"│\",\n                \"No content\",\n                self.panel_sz() - 1,\n                self.panel_sz() + 1,\n            )?;\n            if self.show_char_panel {\n                write!(self.writer, \"{0:>9}{0:>9}\", \"│\")?;\n            }\n            writeln!(self.writer)?;\n        } else if let Some(n) = leftover {\n            // last line is incomplete\n            self.squeezer = Squeezer::Ignore;\n            self.print_position_panel()?;\n            self.print_bytes()?;\n            self.squeezer = Squeezer::Print;\n            for i in n..8 * self.panels as usize {\n                self.print_byte(i, 0)?;\n            }\n            if self.show_char_panel {\n                self.squeezer = Squeezer::Ignore;\n                self.print_char_panel()?;\n                self.squeezer = Squeezer::Print;\n                for i in n..8 * self.panels as usize {\n                    self.print_char(i as u64)?;\n                }\n            }\n            self.writer.write_all(b\"\\n\")?;\n        }\n\n        self.print_footer()?;\n\n        self.writer.flush()?;\n\n        Ok(())\n    }\n\n    /// Print the bytes in C include file style\n    /// Return the number of bytes read  \n    fn print_bytes_in_include_style<Reader: Read>(\n        &mut self,\n        buf: &mut BufReader<Reader>,\n    ) -> Result<usize, io::Error> {\n        let mut buffer = [0; 1024];\n        let mut total_bytes = 0;\n        let mut is_first_chunk = true;\n        let mut line_counter = 0;\n        loop {\n            match buf.read(&mut buffer) {\n                Ok(0) => break, // EOF\n                Ok(bytes_read) => {\n                    total_bytes += bytes_read;\n\n                    for &byte in &buffer[..bytes_read] {\n                        if line_counter % 12 == 0 {\n                            if !is_first_chunk || line_counter > 0 {\n                                writeln!(self.writer, \",\")?;\n                            }\n                            // indentation of first line\n                            write!(self.writer, \"  \")?;\n                            is_first_chunk = false;\n                        } else {\n                            write!(self.writer, \", \")?;\n                        }\n                        write!(self.writer, \"0x{:02x}\", byte)?;\n                        line_counter += 1;\n                    }\n                }\n                Err(e) => return Err(e),\n            }\n        }\n        writeln!(self.writer)?;\n        Ok(total_bytes)\n    }\n}\n\n#[cfg(test)]\nmod tests {\n    use std::io;\n    use std::str;\n\n    use super::*;\n\n    fn assert_print_all_output<Reader: Read>(input: Reader, expected_string: String) {\n        let mut output = vec![];\n        let mut printer = PrinterBuilder::new(&mut output)\n            .show_color(false)\n            .show_char_panel(true)\n            .show_position_panel(true)\n            .with_border_style(BorderStyle::Unicode)\n            .enable_squeezing(true)\n            .num_panels(2)\n            .group_size(1)\n            .with_base(Base::Hexadecimal)\n            .endianness(Endianness::Big)\n            .character_table(CharacterTable::Default)\n            .include_mode(IncludeMode::Off)\n            .color_scheme(ColorScheme::Default)\n            .build();\n\n        printer.print_all(input).unwrap();\n\n        let actual_string: &str = str::from_utf8(&output).unwrap();\n        assert_eq!(actual_string, expected_string,)\n    }\n\n    #[test]\n    fn empty_file_passes() {\n        let input = io::empty();\n        let expected_string = \"\\\n┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n│        │ No content              │                         │        │        │\n└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n\"\n        .to_owned();\n        assert_print_all_output(input, expected_string);\n    }\n\n    #[test]\n    fn short_input_passes() {\n        let input = io::Cursor::new(b\"spam\");\n        let expected_string = \"\\\n┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n│00000000│ 73 70 61 6d             ┊                         │spam    ┊        │\n└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n\"\n        .to_owned();\n        assert_print_all_output(input, expected_string);\n    }\n\n    #[test]\n    fn display_offset() {\n        let input = io::Cursor::new(b\"spamspamspamspamspam\");\n        let expected_string = \"\\\n┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n│deadbeef│ 73 70 61 6d 73 70 61 6d ┊ 73 70 61 6d 73 70 61 6d │spamspam┊spamspam│\n│deadbeff│ 73 70 61 6d             ┊                         │spam    ┊        │\n└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n\"\n        .to_owned();\n\n        let mut output = vec![];\n        let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)\n            .show_color(false)\n            .show_char_panel(true)\n            .show_position_panel(true)\n            .with_border_style(BorderStyle::Unicode)\n            .enable_squeezing(true)\n            .num_panels(2)\n            .group_size(1)\n            .with_base(Base::Hexadecimal)\n            .endianness(Endianness::Big)\n            .character_table(CharacterTable::Default)\n            .include_mode(IncludeMode::Off)\n            .color_scheme(ColorScheme::Default)\n            .build();\n        printer.display_offset(0xdeadbeef);\n\n        printer.print_all(input).unwrap();\n\n        let actual_string: &str = str::from_utf8(&output).unwrap();\n        assert_eq!(actual_string, expected_string)\n    }\n\n    #[test]\n    fn multiple_panels() {\n        let input = io::Cursor::new(b\"supercalifragilisticexpialidocioussupercalifragilisticexpialidocioussupercalifragilisticexpialidocious\");\n        let expected_string = \"\\\n┌────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬────────┬────────┬────────┬────────┐\n│00000000│ 73 75 70 65 72 63 61 6c ┊ 69 66 72 61 67 69 6c 69 ┊ 73 74 69 63 65 78 70 69 ┊ 61 6c 69 64 6f 63 69 6f │supercal┊ifragili┊sticexpi┊alidocio│\n│00000020│ 75 73 73 75 70 65 72 63 ┊ 61 6c 69 66 72 61 67 69 ┊ 6c 69 73 74 69 63 65 78 ┊ 70 69 61 6c 69 64 6f 63 │ussuperc┊alifragi┊listicex┊pialidoc│\n│00000040│ 69 6f 75 73 73 75 70 65 ┊ 72 63 61 6c 69 66 72 61 ┊ 67 69 6c 69 73 74 69 63 ┊ 65 78 70 69 61 6c 69 64 │ioussupe┊rcalifra┊gilistic┊expialid│\n│00000060│ 6f 63 69 6f 75 73       ┊                         ┊                         ┊                         │ocious  ┊        ┊        ┊        │\n└────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴────────┴────────┴────────┴────────┘\n\"\n        .to_owned();\n\n        let mut output = vec![];\n        let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)\n            .show_color(false)\n            .show_char_panel(true)\n            .show_position_panel(true)\n            .with_border_style(BorderStyle::Unicode)\n            .enable_squeezing(true)\n            .num_panels(4)\n            .group_size(1)\n            .with_base(Base::Hexadecimal)\n            .endianness(Endianness::Big)\n            .character_table(CharacterTable::Default)\n            .include_mode(IncludeMode::Off)\n            .color_scheme(ColorScheme::Default)\n            .build();\n\n        printer.print_all(input).unwrap();\n\n        let actual_string: &str = str::from_utf8(&output).unwrap();\n        assert_eq!(actual_string, expected_string)\n    }\n\n    #[test]\n    fn squeeze_works() {\n        let input = io::Cursor::new(b\"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\");\n        let expected_string = \"\\\n┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n│00000000│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\n│*       │                         ┊                         │        ┊        │\n│00000020│ 00                      ┊                         │⋄       ┊        │\n└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n\"\n        .to_owned();\n        assert_print_all_output(input, expected_string);\n    }\n\n    #[test]\n    fn squeeze_nonzero() {\n        let input = io::Cursor::new(b\"000000000000000000000000000000000\");\n        let expected_string = \"\\\n┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n│00000000│ 30 30 30 30 30 30 30 30 ┊ 30 30 30 30 30 30 30 30 │00000000┊00000000│\n│*       │                         ┊                         │        ┊        │\n│00000020│ 30                      ┊                         │0       ┊        │\n└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n\"\n        .to_owned();\n        assert_print_all_output(input, expected_string);\n    }\n\n    #[test]\n    fn squeeze_multiple_panels() {\n        let input = io::Cursor::new(b\"0000000000000000000000000000000000000000000000000\");\n        let expected_string = \"\\\n┌────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬────────┬────────┬────────┐\n│00000000│ 30 30 30 30 30 30 30 30 ┊ 30 30 30 30 30 30 30 30 ┊ 30 30 30 30 30 30 30 30 │00000000┊00000000┊00000000│\n│*       │                         ┊                         ┊                         │        ┊        ┊        │\n│00000030│ 30                      ┊                         ┊                         │0       ┊        ┊        │\n└────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴────────┴────────┴────────┘\n\"\n        .to_owned();\n\n        let mut output = vec![];\n        let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)\n            .show_color(false)\n            .show_char_panel(true)\n            .show_position_panel(true)\n            .with_border_style(BorderStyle::Unicode)\n            .enable_squeezing(true)\n            .num_panels(3)\n            .group_size(1)\n            .with_base(Base::Hexadecimal)\n            .endianness(Endianness::Big)\n            .character_table(CharacterTable::Default)\n            .include_mode(IncludeMode::Off)\n            .color_scheme(ColorScheme::Default)\n            .build();\n\n        printer.print_all(input).unwrap();\n\n        let actual_string: &str = str::from_utf8(&output).unwrap();\n        assert_eq!(actual_string, expected_string)\n    }\n\n    // issue#238\n    #[test]\n    fn display_offset_in_last_line() {\n        let input = io::Cursor::new(b\"AAAAAAAAAAAAAAAACCCC\");\n        let expected_string = \"\\\n┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n│00000000│ 41 41 41 41 41 41 41 41 ┊ 41 41 41 41 41 41 41 41 │AAAAAAAA┊AAAAAAAA│\n│00000010│ 43 43 43 43             ┊                         │CCCC    ┊        │\n└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n\"\n        .to_owned();\n        assert_print_all_output(input, expected_string);\n    }\n\n    #[test]\n    fn include_mode_from_file() {\n        let input = io::Cursor::new(b\"spamspamspamspamspam\");\n        let expected_string = \"unsigned char test_txt[] = {\n  0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d,\n  0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d\n};\nunsigned int test_txt_len = 20;\n\"\n        .to_owned();\n        let mut output = vec![];\n        let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)\n            .show_color(false)\n            .show_char_panel(true)\n            .show_position_panel(true)\n            .with_border_style(BorderStyle::Unicode)\n            .enable_squeezing(true)\n            .num_panels(2)\n            .group_size(1)\n            .with_base(Base::Hexadecimal)\n            .endianness(Endianness::Big)\n            .character_table(CharacterTable::Default)\n            .include_mode(IncludeMode::File(\"test.txt\".to_owned()))\n            .color_scheme(ColorScheme::Default)\n            .build();\n\n        printer.print_all(input).unwrap();\n\n        let actual_string: &str = str::from_utf8(&output).unwrap();\n        assert_eq!(actual_string, expected_string)\n    }\n\n    #[test]\n    fn include_mode_from_stdin() {\n        let input = io::Cursor::new(b\"spamspamspamspamspam\");\n        let expected_string =\n            \"  0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d,\n  0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d\n\"\n            .to_owned();\n        let mut output = vec![];\n        let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)\n            .show_color(false)\n            .show_char_panel(true)\n            .show_position_panel(true)\n            .with_border_style(BorderStyle::Unicode)\n            .enable_squeezing(true)\n            .num_panels(2)\n            .group_size(1)\n            .with_base(Base::Hexadecimal)\n            .endianness(Endianness::Big)\n            .character_table(CharacterTable::Default)\n            .include_mode(IncludeMode::Stdin)\n            .color_scheme(ColorScheme::Default)\n            .build();\n\n        printer.print_all(input).unwrap();\n\n        let actual_string: &str = str::from_utf8(&output).unwrap();\n        assert_eq!(actual_string, expected_string)\n    }\n}\n"
  },
  {
    "path": "src/main.rs",
    "content": "use std::fs::File;\nuse std::io::{self, prelude::*, BufWriter, SeekFrom};\nuse std::num::{NonZeroI64, NonZeroU64};\nuse std::path::PathBuf;\n\nuse clap::builder::styling::{AnsiColor, Effects};\nuse clap::builder::ArgPredicate;\nuse clap::builder::Styles;\nuse clap::{ArgAction, CommandFactory, Parser, ValueEnum};\nuse clap_complete::aot::{generate, Shell};\n\nuse anyhow::{anyhow, bail, Context, Result};\n\nuse const_format::formatcp;\n\nuse thiserror::Error as ThisError;\n\nuse terminal_size::terminal_size;\n\nuse hexyl::{\n    Base, BorderStyle, CharacterTable, ColorScheme, Endianness, IncludeMode, Input, PrinterBuilder,\n};\n\nuse hexyl::{\n    COLOR_ASCII_OTHER, COLOR_ASCII_PRINTABLE, COLOR_ASCII_WHITESPACE, COLOR_NONASCII, COLOR_NULL,\n    COLOR_RESET,\n};\n\n#[cfg(test)]\nmod tests;\n\nconst DEFAULT_BLOCK_SIZE: i64 = 512;\n\nconst LENGTH_HELP_TEXT: &str = \"Only read N bytes from the input. The N argument can also include \\\n                                a unit with a decimal prefix (kB, MB, ..) or binary prefix (kiB, \\\n                                MiB, ..), or can be specified using a hex number. The short \\\n                                option '-l' can be used as an alias.\nExamples: --length=64, --length=4KiB, --length=0xff\";\n\nconst SKIP_HELP_TEXT: &str = \"Skip the first N bytes of the input. The N argument can also \\\n                              include a unit (see `--length` for details).\nA negative value is valid and will seek from the end of the file.\";\n\nconst BLOCK_SIZE_HELP_TEXT: &str = \"Sets the size of the `block` unit to SIZE.\nExamples: --block-size=1024, --block-size=4kB\";\n\nconst DISPLAY_OFFSET_HELP_TEXT: &str = \"Add N bytes to the displayed file position. The N \\\n                                        argument can also include a unit (see `--length` for \\\n                                        details).\nA negative value is valid and calculates an offset relative to the end of the file.\";\n\nconst TERMINAL_WIDTH_HELP_TEXT: &str = \"Sets the number of terminal columns to be displayed.\nSince the terminal width may not be an evenly divisible by the width per hex data column, this \\\n                                        will use the greatest number of hex data panels that can \\\n                                        fit in the requested width but still leave some space to \\\n                                        the right.\nCannot be used with other width-setting options.\";\n\nconst STYLES: Styles = Styles::styled()\n    .header(AnsiColor::Green.on_default().effects(Effects::BOLD))\n    .usage(AnsiColor::Green.on_default().effects(Effects::BOLD))\n    .literal(AnsiColor::Cyan.on_default().effects(Effects::BOLD))\n    .placeholder(AnsiColor::Cyan.on_default());\n\n#[derive(Debug, Parser)]\n#[command(version, about, max_term_width(90), styles = STYLES)]\nstruct Opt {\n    /// The file to display. If no FILE argument is given, read from STDIN.\n    #[arg(value_name(\"FILE\"))]\n    file: Option<PathBuf>,\n\n    #[arg(\n        help(LENGTH_HELP_TEXT),\n        short('n'),\n        long,\n        visible_short_alias('c'),\n        visible_alias(\"bytes\"),\n        short_alias('l'),\n        value_name(\"N\")\n    )]\n    length: Option<String>,\n\n    #[arg(help(SKIP_HELP_TEXT), short, long, value_name(\"N\"))]\n    skip: Option<String>,\n\n    #[arg(\n        help(BLOCK_SIZE_HELP_TEXT),\n        long,\n        default_value(formatcp!(\"{DEFAULT_BLOCK_SIZE}\")),\n        value_name(\"SIZE\")\n    )]\n    block_size: String,\n\n    /// Displays all input data. Otherwise any number of groups of output lines\n    /// which would be identical to the preceding group of lines, are replaced\n    /// with a line comprised of a single asterisk.\n    #[arg(short('v'), long)]\n    no_squeezing: bool,\n\n    /// When to use colors.\n    #[arg(\n        long,\n        value_enum,\n        default_value_t,\n        value_name(\"WHEN\"),\n        default_value_if(\"plain\", ArgPredicate::IsPresent, Some(\"never\"))\n    )]\n    color: ColorWhen,\n\n    /// Whether to draw a border.\n    #[arg(\n        long,\n        value_enum,\n        default_value_t,\n        value_name(\"STYLE\"),\n        default_value_if(\"plain\", ArgPredicate::IsPresent, Some(\"none\"))\n    )]\n    border: BorderStyle,\n\n    /// Display output with --no-characters, --no-position, --border=none, and\n    /// --color=never.\n    #[arg(short, long)]\n    plain: bool,\n\n    /// Do not show the character panel on the right.\n    #[arg(long)]\n    no_characters: bool,\n\n    /// Show the character panel on the right. This is the default, unless\n    /// --no-characters has been specified.\n    #[arg(\n        short('C'),\n        long,\n        action(ArgAction::SetTrue),\n        overrides_with(\"no_characters\")\n    )]\n    characters: (),\n\n    /// Defines how bytes are mapped to characters.\n    #[arg(long, value_enum, default_value_t, value_name(\"FORMAT\"))]\n    character_table: CharacterTable,\n\n    /// Defines the color scheme for the characters.\n    #[arg(long, value_enum, default_value_t, value_name(\"FORMAT\"))]\n    color_scheme: ColorScheme,\n\n    /// Whether to display the position panel on the left.\n    #[arg(short('P'), long)]\n    no_position: bool,\n\n    #[arg(\n        help(DISPLAY_OFFSET_HELP_TEXT),\n        short('o'),\n        long,\n        default_value(\"0\"),\n        value_name(\"N\")\n    )]\n    display_offset: String,\n\n    /// Sets the number of hex data panels to be displayed. `--panels=auto` will\n    /// display the maximum number of hex data panels based on the current\n    /// terminal width. By default, hexyl will show two panels, unless the\n    /// terminal is not wide enough for that.\n    #[arg(long, value_name(\"N\"))]\n    panels: Option<String>,\n\n    /// Number of bytes/octets that should be grouped together. You can use the\n    /// '--endianness' option to control the ordering of the bytes within a\n    /// group. '--groupsize' can be used as an alias (xxd-compatibility).\n    #[arg(\n        short('g'),\n        long,\n        value_enum,\n        default_value_t,\n        alias(\"groupsize\"),\n        value_name(\"N\")\n    )]\n    group_size: GroupSize,\n\n    /// Whether to print out groups in little-endian or big-endian format. This\n    /// option only has an effect if the '--group-size' is larger than 1. '-e'\n    /// can be used as an alias for '--endianness=little'.\n    #[arg(long, value_enum, default_value_t, value_name(\"FORMAT\"))]\n    endianness: Endianness,\n\n    /// An alias for '--endianness=little'.\n    #[arg(short('e'), hide(true), overrides_with(\"endianness\"))]\n    little_endian_format: bool,\n\n    /// Sets the base used for the bytes. The possible options are binary,\n    /// octal, decimal, and hexadecimal.\n    #[arg(short('b'), long, default_value(\"hexadecimal\"), value_name(\"B\"))]\n    base: String,\n\n    #[arg(\n        help(TERMINAL_WIDTH_HELP_TEXT),\n        long,\n        value_name(\"N\"),\n        conflicts_with(\"panels\")\n    )]\n    terminal_width: Option<NonZeroU64>,\n\n    /// Print a table showing how different types of bytes are colored.\n    #[arg(long)]\n    print_color_table: bool,\n\n    /// Output in C include file style (similar to xxd -i).\n    #[arg(\n        short('i'),\n        long(\"include\"),\n        help = \"Output in C include file style\",\n        conflicts_with(\"little_endian_format\"),\n        conflicts_with(\"endianness\")\n    )]\n    include_mode: bool,\n\n    /// Show shell completion for a certain shell\n    #[arg(long, value_name(\"SHELL\"))]\n    completion: Option<Shell>,\n}\n\n#[derive(Clone, Debug, Default, ValueEnum)]\nenum ColorWhen {\n    /// Always use colorized output.\n    #[default]\n    Always,\n\n    /// Only displays colors if the output goes to an interactive terminal.\n    Auto,\n\n    /// Do not use colorized output.\n    Never,\n\n    /// Override the NO_COLOR environment variable.\n    Force,\n}\n\n#[derive(Clone, Debug, Default, ValueEnum)]\nenum GroupSize {\n    /// Grouped together every byte/octet.\n    #[default]\n    #[value(name = \"1\")]\n    One,\n\n    /// Grouped together every 2 bytes/octets.\n    #[value(name = \"2\")]\n    Two,\n\n    /// Grouped together every 4 bytes/octets.\n    #[value(name = \"4\")]\n    Four,\n\n    /// Grouped together every 8 bytes/octets.\n    #[value(name = \"8\")]\n    Eight,\n}\n\nimpl From<GroupSize> for u8 {\n    fn from(number: GroupSize) -> Self {\n        match number {\n            GroupSize::One => 1,\n            GroupSize::Two => 2,\n            GroupSize::Four => 4,\n            GroupSize::Eight => 8,\n        }\n    }\n}\n\nfn run() -> Result<()> {\n    let opt = Opt::parse();\n\n    if opt.print_color_table {\n        return print_color_table().map_err(|e| anyhow!(e));\n    }\n\n    if let Some(sh) = opt.completion {\n        let mut cmd = Opt::command();\n        let name = cmd.get_name().to_string();\n        generate(sh, &mut cmd, name, &mut io::stdout());\n        return Ok(());\n    }\n\n    let stdin = io::stdin();\n\n    let mut reader = match &opt.file {\n        Some(filename) => {\n            if filename.as_os_str() == \"-\" {\n                Input::Stdin(stdin.lock())\n            } else {\n                if filename.is_dir() {\n                    bail!(\"'{}' is a directory.\", filename.to_string_lossy());\n                }\n                let file = File::open(filename)?;\n\n                Input::File(file)\n            }\n        }\n        None => Input::Stdin(stdin.lock()),\n    };\n\n    if let Some(hex_number) = try_parse_as_hex_number(&opt.block_size) {\n        return hex_number\n            .map_err(|e| anyhow!(e))\n            .and_then(|x| {\n                PositiveI64::new(x).ok_or_else(|| anyhow!(\"block size argument must be positive\"))\n            })\n            .map(|_| ());\n    }\n    let (num, unit) = extract_num_and_unit_from(&opt.block_size)?;\n    if let Unit::Block { custom_size: _ } = unit {\n        return Err(anyhow!(\n            \"can not use 'block(s)' as a unit to specify block size\"\n        ));\n    };\n    let block_size = num\n        .checked_mul(unit.get_multiplier())\n        .ok_or_else(|| anyhow!(ByteOffsetParseError::UnitMultiplicationOverflow))\n        .and_then(|x| {\n            PositiveI64::new(x).ok_or_else(|| anyhow!(\"block size argument must be positive\"))\n        })?;\n\n    let skip_arg = opt\n        .skip\n        .as_ref()\n        .map(|s| {\n            parse_byte_offset(s, block_size).context(anyhow!(\n                \"failed to parse `--skip` arg {:?} as byte count\",\n                s\n            ))\n        })\n        .transpose()?;\n\n    let skip_offset = if let Some(ByteOffset { kind, value }) = skip_arg {\n        let value = value.into_inner();\n        reader\n            .seek(match kind {\n                ByteOffsetKind::ForwardFromBeginning | ByteOffsetKind::ForwardFromLastOffset => {\n                    SeekFrom::Current(value)\n                }\n                ByteOffsetKind::BackwardFromEnd => SeekFrom::End(value.checked_neg().unwrap()),\n            })\n            .map_err(|_| {\n                anyhow!(\n                    \"Failed to jump to the desired input position. \\\n                     This could be caused by a negative offset that is too large or by \\\n                     an input that is not seek-able (e.g. if the input comes from a pipe).\"\n                )\n            })?\n    } else {\n        0\n    };\n\n    let parse_byte_count = |s| -> Result<u64> {\n        Ok(parse_byte_offset(s, block_size)?\n            .assume_forward_offset_from_start()?\n            .into())\n    };\n\n    let mut reader = if let Some(ref length) = opt.length {\n        let length = parse_byte_count(length).context(anyhow!(\n            \"failed to parse `--length` arg {:?} as byte count\",\n            length\n        ))?;\n        Box::new(reader.take(length))\n    } else {\n        reader.into_inner()\n    };\n\n    let no_color = std::env::var_os(\"NO_COLOR\").is_some();\n    let show_color = match opt.color {\n        ColorWhen::Never => false,\n        ColorWhen::Always => !no_color,\n        ColorWhen::Force => true,\n        ColorWhen::Auto => {\n            if no_color {\n                false\n            } else {\n                supports_color::on(supports_color::Stream::Stdout)\n                    .map(|level| level.has_basic)\n                    .unwrap_or(false)\n            }\n        }\n    };\n\n    let border_style = opt.border;\n\n    let &squeeze = &!opt.no_squeezing;\n\n    let show_char_panel = !opt.no_characters && !opt.plain;\n\n    let show_position_panel = !opt.no_position && !opt.plain;\n\n    let display_offset: u64 = parse_byte_count(&opt.display_offset).context(anyhow!(\n        \"failed to parse `--display-offset` arg {:?} as byte count\",\n        opt.display_offset\n    ))?;\n\n    let max_panels_fn = |terminal_width: u64, base_digits: u64, group_size: u64| {\n        let offset = if show_position_panel { 10 } else { 1 };\n        let col_width = if show_char_panel {\n            ((8 / group_size) * (base_digits * group_size + 1)) + 2 + 8\n        } else {\n            ((8 / group_size) * (base_digits * group_size + 1)) + 2\n        };\n        if (terminal_width.saturating_sub(offset)) / col_width < 1 {\n            1\n        } else {\n            (terminal_width - offset) / col_width\n        }\n    };\n\n    let base = if let Ok(base_num) = opt.base.parse::<u8>() {\n        match base_num {\n            2 => Ok(Base::Binary),\n            8 => Ok(Base::Octal),\n            10 => Ok(Base::Decimal),\n            16 => Ok(Base::Hexadecimal),\n            _ => Err(anyhow!(\n                \"The number provided is not a valid base. Valid bases are 2, 8, 10, and 16.\"\n            )),\n        }\n    } else {\n        match opt.base.as_str() {\n            \"b\" | \"bin\" | \"binary\" => Ok(Base::Binary),\n            \"o\" | \"oct\" | \"octal\" => Ok(Base::Octal),\n            \"d\" | \"dec\" | \"decimal\" => Ok(Base::Decimal),\n            \"x\" | \"hex\" | \"hexadecimal\" => Ok(Base::Hexadecimal),\n            _ => Err(anyhow!(\n                \"The base provided is not valid. Valid bases are \\\"b\\\", \\\"o\\\", \\\"d\\\", and \\\"x\\\".\"\n            )),\n        }\n    }?;\n\n    let base_digits = match base {\n        Base::Binary => 8,\n        Base::Octal => 3,\n        Base::Decimal => 3,\n        Base::Hexadecimal => 2,\n    };\n\n    let group_size = u8::from(opt.group_size);\n\n    let terminal_width = terminal_size().map(|s| s.0 .0 as u64).unwrap_or(80);\n\n    let panels = if opt.panels.as_deref() == Some(\"auto\") {\n        max_panels_fn(terminal_width, base_digits, group_size.into())\n    } else if let Some(panels) = opt.panels {\n        panels\n            .parse::<NonZeroU64>()\n            .map(u64::from)\n            .context(anyhow!(\n                \"failed to parse `--panels` arg {:?} as unsigned nonzero integer\",\n                panels\n            ))?\n    } else if let Some(terminal_width) = opt.terminal_width {\n        max_panels_fn(terminal_width.into(), base_digits, group_size.into())\n    } else {\n        std::cmp::min(\n            2,\n            max_panels_fn(terminal_width, base_digits, group_size.into()),\n        )\n    };\n\n    let endianness = if opt.little_endian_format {\n        Endianness::Little\n    } else {\n        opt.endianness\n    };\n\n    let character_table = opt.character_table;\n\n    let color_scheme = opt.color_scheme;\n\n    let mut stdout = BufWriter::new(io::stdout().lock());\n\n    let include_mode = match opt.include_mode {\n        // include mode on\n        true => {\n            if let Some(include_file) = opt.file {\n                // input from a file\n                if include_file.as_os_str() == \"-\" {\n                    IncludeMode::File(\"stdin\".to_string())\n                } else {\n                    IncludeMode::File(\n                        include_file\n                            .file_name()\n                            .and_then(|n| n.to_str())\n                            .unwrap_or(\"file\")\n                            .to_string(),\n                    )\n                }\n            } else {\n                // input from stdin\n                IncludeMode::Stdin\n            }\n        }\n        // include mode off\n        false => IncludeMode::Off,\n    };\n\n    let mut printer = PrinterBuilder::new(&mut stdout)\n        .show_color(show_color)\n        .show_char_panel(show_char_panel)\n        .show_position_panel(show_position_panel)\n        .with_border_style(border_style)\n        .enable_squeezing(squeeze)\n        .num_panels(panels)\n        .group_size(group_size)\n        .with_base(base)\n        .endianness(endianness)\n        .character_table(character_table)\n        .include_mode(include_mode)\n        .color_scheme(color_scheme)\n        .build();\n    printer.display_offset(skip_offset + display_offset);\n    printer.print_all(&mut reader).map_err(|e| anyhow!(e))?;\n\n    Ok(())\n}\n\nfn main() {\n    let result = run();\n\n    if let Err(err) = result {\n        if let Some(io_error) = err.downcast_ref::<io::Error>() {\n            if io_error.kind() == ::std::io::ErrorKind::BrokenPipe {\n                std::process::exit(0);\n            }\n        }\n        eprintln!(\"Error: {err:?}\");\n        std::process::exit(1);\n    }\n}\n\n#[derive(Clone, Copy, Debug, Default, Hash, Eq, Ord, PartialEq, PartialOrd)]\npub struct NonNegativeI64(i64);\n\nimpl NonNegativeI64 {\n    pub fn new(x: i64) -> Option<Self> {\n        if x.is_negative() {\n            None\n        } else {\n            Some(Self(x))\n        }\n    }\n\n    pub fn into_inner(self) -> i64 {\n        self.0\n    }\n}\n\nimpl From<NonNegativeI64> for u64 {\n    fn from(x: NonNegativeI64) -> u64 {\n        u64::try_from(x.0)\n            .expect(\"invariant broken: NonNegativeI64 should contain a non-negative i64 value\")\n    }\n}\n\nfn print_color_table() -> io::Result<()> {\n    let mut stdout = BufWriter::new(io::stdout().lock());\n\n    writeln!(stdout, \"hexyl color reference:\\n\")?;\n\n    // NULL bytes\n    stdout.write_all(COLOR_NULL.as_bytes())?;\n    writeln!(stdout, \"⋄ NULL bytes (0x00)\")?;\n    stdout.write_all(COLOR_RESET.as_bytes())?;\n\n    // ASCII printable\n    stdout.write_all(COLOR_ASCII_PRINTABLE.as_bytes())?;\n    writeln!(stdout, \"a ASCII printable characters (0x20 - 0x7E)\")?;\n    stdout.write_all(COLOR_RESET.as_bytes())?;\n\n    // ASCII whitespace\n    stdout.write_all(COLOR_ASCII_WHITESPACE.as_bytes())?;\n    writeln!(stdout, \"_ ASCII whitespace (0x09 - 0x0D, 0x20)\")?;\n    stdout.write_all(COLOR_RESET.as_bytes())?;\n\n    // ASCII other\n    stdout.write_all(COLOR_ASCII_OTHER.as_bytes())?;\n    writeln!(\n        stdout,\n        \"• ASCII control characters (except NULL and whitespace)\"\n    )?;\n    stdout.write_all(COLOR_RESET.as_bytes())?;\n\n    // Non-ASCII\n    stdout.write_all(COLOR_NONASCII.as_bytes())?;\n    writeln!(stdout, \"× Non-ASCII bytes (0x80 - 0xFF)\")?;\n    stdout.write_all(COLOR_RESET.as_bytes())?;\n\n    Ok(())\n}\n\n#[derive(Clone, Copy, Debug, Default, Hash, Eq, Ord, PartialEq, PartialOrd)]\npub struct PositiveI64(i64);\n\nimpl PositiveI64 {\n    pub fn new(x: i64) -> Option<Self> {\n        if x < 1 {\n            None\n        } else {\n            Some(Self(x))\n        }\n    }\n\n    pub fn into_inner(self) -> i64 {\n        self.0\n    }\n}\n\nimpl From<PositiveI64> for u64 {\n    fn from(x: PositiveI64) -> u64 {\n        u64::try_from(x.0)\n            .expect(\"invariant broken: PositiveI64 should contain a positive i64 value\")\n    }\n}\n\n#[derive(Debug, PartialEq)]\nenum Unit {\n    Byte,\n    Kilobyte,\n    Megabyte,\n    Gigabyte,\n    Terabyte,\n    Kibibyte,\n    Mebibyte,\n    Gibibyte,\n    Tebibyte,\n    /// a customizable amount of bytes\n    Block {\n        custom_size: Option<NonZeroI64>,\n    },\n}\n\nimpl Unit {\n    const fn get_multiplier(self) -> i64 {\n        match self {\n            Self::Byte => 1,\n            Self::Kilobyte => 1000,\n            Self::Megabyte => 1_000_000,\n            Self::Gigabyte => 1_000_000_000,\n            Self::Terabyte => 1_000_000_000_000,\n            Self::Kibibyte => 1 << 10,\n            Self::Mebibyte => 1 << 20,\n            Self::Gibibyte => 1 << 30,\n            Self::Tebibyte => 1 << 40,\n            Self::Block {\n                custom_size: Some(size),\n            } => size.get(),\n            Self::Block { custom_size: None } => DEFAULT_BLOCK_SIZE,\n        }\n    }\n}\n\nconst HEX_PREFIX: &str = \"0x\";\n\n#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]\nenum ByteOffsetKind {\n    ForwardFromBeginning,\n    ForwardFromLastOffset,\n    BackwardFromEnd,\n}\n\n#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]\nstruct ByteOffset {\n    value: NonNegativeI64,\n    kind: ByteOffsetKind,\n}\n\n#[derive(Clone, Debug, ThisError)]\n#[error(\n    \"negative offset specified, but only positive offsets (counts) are accepted in this context\"\n)]\nstruct NegativeOffsetSpecifiedError;\n\nimpl ByteOffset {\n    fn assume_forward_offset_from_start(\n        &self,\n    ) -> Result<NonNegativeI64, NegativeOffsetSpecifiedError> {\n        let &Self { value, kind } = self;\n        match kind {\n            ByteOffsetKind::ForwardFromBeginning | ByteOffsetKind::ForwardFromLastOffset => {\n                Ok(value)\n            }\n            ByteOffsetKind::BackwardFromEnd => Err(NegativeOffsetSpecifiedError),\n        }\n    }\n}\n\n#[derive(Clone, Debug, Eq, PartialEq, ThisError)]\nenum ByteOffsetParseError {\n    #[error(\"no character data found, did you forget to write it?\")]\n    Empty,\n    #[error(\"no digits found after sign, did you forget to write them?\")]\n    EmptyAfterSign,\n    #[error(\n        \"found {0:?} sign after hex prefix ({:?}); signs should go before it\",\n        HEX_PREFIX\n    )]\n    SignFoundAfterHexPrefix(char),\n    #[error(\"{0:?} is not of the expected form <pos-integer>[<unit>]\")]\n    InvalidNumAndUnit(String),\n    #[error(\"{0:?} is a valid unit, but an integer should come before it\")]\n    EmptyWithUnit(String),\n    #[error(\"invalid unit {0:?}\")]\n    InvalidUnit(String),\n    #[error(\"failed to parse integer part\")]\n    ParseNum(#[source] std::num::ParseIntError),\n    #[error(\"count multiplied by the unit overflowed a signed 64-bit integer; are you sure it should be that big?\")]\n    UnitMultiplicationOverflow,\n}\n\nfn parse_byte_offset(n: &str, block_size: PositiveI64) -> Result<ByteOffset, ByteOffsetParseError> {\n    use ByteOffsetParseError::*;\n\n    let (n, kind) = process_sign_of(n)?;\n\n    let into_byte_offset = |value| {\n        Ok(ByteOffset {\n            value: NonNegativeI64::new(value).unwrap(),\n            kind,\n        })\n    };\n\n    if let Some(hex_number) = try_parse_as_hex_number(n) {\n        return hex_number.map(into_byte_offset)?;\n    }\n\n    let (num, mut unit) = extract_num_and_unit_from(n)?;\n    if let Unit::Block { custom_size: None } = unit {\n        unit = Unit::Block {\n            custom_size: Some(\n                NonZeroI64::new(block_size.into_inner()).expect(\"PositiveI64 was zero\"),\n            ),\n        };\n    }\n\n    num.checked_mul(unit.get_multiplier())\n        .ok_or(UnitMultiplicationOverflow)\n        .and_then(into_byte_offset)\n}\n\n/// Takes a string containing a base-10 number and an optional unit, and returns them with their proper types.\n/// The unit must directly follow the number (e.g. no whitespace is allowed between them).\n/// When no unit is given, [Unit::Byte] is assumed.\n/// When the unit is [Unit::Block], it is returned without custom size.\n/// No normalization is performed, that is \"1024\" is extracted to (1024, Byte), not (1, Kibibyte).\nfn extract_num_and_unit_from(n: &str) -> Result<(i64, Unit), ByteOffsetParseError> {\n    use ByteOffsetParseError::*;\n    if n.is_empty() {\n        return Err(Empty);\n    }\n    match n.chars().position(|c| !c.is_ascii_digit()) {\n        Some(unit_begin_idx) => {\n            let (n, raw_unit) = n.split_at(unit_begin_idx);\n            let unit = match raw_unit.to_lowercase().as_str() {\n                \"\" => Unit::Byte, // no \"b\" => Byte to allow hex nums with units\n                \"kb\" => Unit::Kilobyte,\n                \"mb\" => Unit::Megabyte,\n                \"gb\" => Unit::Gigabyte,\n                \"tb\" => Unit::Terabyte,\n                \"kib\" => Unit::Kibibyte,\n                \"mib\" => Unit::Mebibyte,\n                \"gib\" => Unit::Gibibyte,\n                \"tib\" => Unit::Tebibyte,\n                \"block\" | \"blocks\" => Unit::Block { custom_size: None },\n                _ => {\n                    return if n.is_empty() {\n                        Err(InvalidNumAndUnit(raw_unit.to_string()))\n                    } else {\n                        Err(InvalidUnit(raw_unit.to_string()))\n                    }\n                }\n            };\n            let num = n.parse::<i64>().map_err(|e| {\n                if n.is_empty() {\n                    EmptyWithUnit(raw_unit.to_owned())\n                } else {\n                    ParseNum(e)\n                }\n            })?;\n            Ok((num, unit))\n        }\n        None => {\n            // no unit part\n            let num = n.parse::<i64>().map_err(ParseNum)?;\n            Ok((num, Unit::Byte))\n        }\n    }\n}\n\n/// Extracts a [ByteOffsetKind] based on the sign at the beginning of the given string.\n/// Returns the input string without the sign (or an equal string if there wasn't any sign).\nfn process_sign_of(n: &str) -> Result<(&str, ByteOffsetKind), ByteOffsetParseError> {\n    use ByteOffsetParseError::*;\n    let mut chars = n.chars();\n    let next_char = chars.next();\n    let check_empty_after_sign = || {\n        if chars.clone().next().is_none() {\n            Err(EmptyAfterSign)\n        } else {\n            Ok(chars.as_str())\n        }\n    };\n    match next_char {\n        Some('+') => Ok((\n            check_empty_after_sign()?,\n            ByteOffsetKind::ForwardFromLastOffset,\n        )),\n        Some('-') => Ok((check_empty_after_sign()?, ByteOffsetKind::BackwardFromEnd)),\n        None => Err(Empty),\n        _ => Ok((n, ByteOffsetKind::ForwardFromBeginning)),\n    }\n}\n\n/// If `n` starts with a hex prefix, its remaining part is returned as some number (if possible),\n/// otherwise None is returned.\nfn try_parse_as_hex_number(n: &str) -> Option<Result<i64, ByteOffsetParseError>> {\n    use ByteOffsetParseError::*;\n    n.strip_prefix(HEX_PREFIX).map(|num| {\n        let mut chars = num.chars();\n        match chars.next() {\n            Some(c @ '+') | Some(c @ '-') => {\n                return if chars.next().is_none() {\n                    Err(EmptyAfterSign)\n                } else {\n                    Err(SignFoundAfterHexPrefix(c))\n                }\n            }\n            _ => (),\n        }\n        i64::from_str_radix(num, 16).map_err(ParseNum)\n    })\n}\n"
  },
  {
    "path": "src/tests.rs",
    "content": "use super::*;\n\n#[test]\nfn unit_multipliers() {\n    use Unit::*;\n    assert_eq!(Kilobyte.get_multiplier(), 1000 * Byte.get_multiplier());\n    assert_eq!(Megabyte.get_multiplier(), 1000 * Kilobyte.get_multiplier());\n    assert_eq!(Gigabyte.get_multiplier(), 1000 * Megabyte.get_multiplier());\n    assert_eq!(Terabyte.get_multiplier(), 1000 * Gigabyte.get_multiplier());\n\n    assert_eq!(Kibibyte.get_multiplier(), 1024 * Byte.get_multiplier());\n    assert_eq!(Mebibyte.get_multiplier(), 1024 * Kibibyte.get_multiplier());\n    assert_eq!(Gibibyte.get_multiplier(), 1024 * Mebibyte.get_multiplier());\n    assert_eq!(Tebibyte.get_multiplier(), 1024 * Gibibyte.get_multiplier());\n}\n\n#[test]\nfn test_process_sign() {\n    use ByteOffsetKind::*;\n    use ByteOffsetParseError::*;\n    assert_eq!(process_sign_of(\"123\"), Ok((\"123\", ForwardFromBeginning)));\n    assert_eq!(process_sign_of(\"+123\"), Ok((\"123\", ForwardFromLastOffset)));\n    assert_eq!(process_sign_of(\"-123\"), Ok((\"123\", BackwardFromEnd)));\n    assert_eq!(process_sign_of(\"-\"), Err(EmptyAfterSign));\n    assert_eq!(process_sign_of(\"+\"), Err(EmptyAfterSign));\n    assert_eq!(process_sign_of(\"\"), Err(Empty));\n}\n\n#[test]\nfn test_parse_as_hex() {\n    assert_eq!(try_parse_as_hex_number(\"73\"), None);\n    assert_eq!(try_parse_as_hex_number(\"0x1337\"), Some(Ok(0x1337)));\n    assert!(matches!(try_parse_as_hex_number(\"0xnope\"), Some(Err(_))));\n    assert!(matches!(try_parse_as_hex_number(\"0x-1\"), Some(Err(_))));\n}\n\n#[test]\nfn extract_num_and_unit() {\n    use ByteOffsetParseError::*;\n    use Unit::*;\n    // byte is default unit\n    assert_eq!(extract_num_and_unit_from(\"4\"), Ok((4, Byte)));\n    // blocks are returned without customization\n    assert_eq!(\n        extract_num_and_unit_from(\"2blocks\"),\n        Ok((2, Block { custom_size: None }))\n    );\n    // no normalization is performed\n    assert_eq!(extract_num_and_unit_from(\"1024kb\"), Ok((1024, Kilobyte)));\n\n    // unit without number results in error\n    assert_eq!(\n        extract_num_and_unit_from(\"gib\"),\n        Err(EmptyWithUnit(\"gib\".to_string()))\n    );\n    // empty string results in error\n    assert_eq!(extract_num_and_unit_from(\"\"), Err(Empty));\n    // an invalid unit results in an error\n    assert_eq!(\n        extract_num_and_unit_from(\"25litres\"),\n        Err(InvalidUnit(\"litres\".to_string()))\n    );\n}\n\n#[test]\nfn test_parse_byte_offset() {\n    use ByteOffsetParseError::*;\n\n    macro_rules! success {\n        ($input: expr, $expected_kind: ident $expected_value: expr) => {\n            success!($input, $expected_kind $expected_value; block_size: DEFAULT_BLOCK_SIZE)\n        };\n        ($input: expr, $expected_kind: ident $expected_value: expr; block_size: $block_size: expr) => {\n            assert_eq!(\n                parse_byte_offset($input, PositiveI64::new($block_size).unwrap()),\n                Ok(\n                    ByteOffset {\n                        value: NonNegativeI64::new($expected_value).unwrap(),\n                        kind: ByteOffsetKind::$expected_kind,\n                    }\n                ),\n            );\n        };\n    }\n\n    macro_rules! error {\n        ($input: expr, $expected_err: expr) => {\n            assert_eq!(\n                parse_byte_offset($input, PositiveI64::new(DEFAULT_BLOCK_SIZE).unwrap()),\n                Err($expected_err),\n            );\n        };\n    }\n\n    success!(\"0\", ForwardFromBeginning 0);\n    success!(\"1\", ForwardFromBeginning 1);\n    success!(\"1\", ForwardFromBeginning 1);\n    success!(\"100\", ForwardFromBeginning 100);\n    success!(\"+100\", ForwardFromLastOffset 100);\n\n    success!(\"0x0\", ForwardFromBeginning 0);\n    success!(\"0xf\", ForwardFromBeginning 15);\n    success!(\"0xdeadbeef\", ForwardFromBeginning 3_735_928_559);\n\n    success!(\"1KB\", ForwardFromBeginning 1000);\n    success!(\"2MB\", ForwardFromBeginning 2000000);\n    success!(\"3GB\", ForwardFromBeginning 3000000000);\n    success!(\"4TB\", ForwardFromBeginning 4000000000000);\n    success!(\"+4TB\", ForwardFromLastOffset 4000000000000);\n\n    success!(\"1GiB\", ForwardFromBeginning 1073741824);\n    success!(\"2TiB\", ForwardFromBeginning 2199023255552);\n    success!(\"+2TiB\", ForwardFromLastOffset 2199023255552);\n\n    success!(\"0xff\", ForwardFromBeginning 255);\n    success!(\"0xEE\", ForwardFromBeginning 238);\n    success!(\"+0xFF\", ForwardFromLastOffset 255);\n\n    success!(\"1block\", ForwardFromBeginning 512; block_size: 512);\n    success!(\"2block\", ForwardFromBeginning 1024; block_size: 512);\n    success!(\"1block\", ForwardFromBeginning 4; block_size: 4);\n    success!(\"2block\", ForwardFromBeginning 8; block_size: 4);\n\n    // empty string is invalid\n    error!(\"\", Empty);\n    // These are also bad.\n    error!(\"+\", EmptyAfterSign);\n    error!(\"-\", EmptyAfterSign);\n    error!(\"K\", InvalidNumAndUnit(\"K\".to_owned()));\n    error!(\"k\", InvalidNumAndUnit(\"k\".to_owned()));\n    error!(\"m\", InvalidNumAndUnit(\"m\".to_owned()));\n    error!(\"block\", EmptyWithUnit(\"block\".to_owned()));\n    // leading/trailing space is invalid\n    error!(\" 0\", InvalidNumAndUnit(\" 0\".to_owned()));\n    error!(\"0 \", InvalidUnit(\" \".to_owned()));\n    // Signs after the hex prefix make no sense\n    error!(\"0x-12\", SignFoundAfterHexPrefix('-'));\n    // This was previously accepted but shouldn't be.\n    error!(\"0x+12\", SignFoundAfterHexPrefix('+'));\n    // invalid suffix\n    error!(\"1234asdf\", InvalidUnit(\"asdf\".to_owned()));\n    // bad numbers\n    error!(\"asdf1234\", InvalidNumAndUnit(\"asdf1234\".to_owned()));\n    error!(\"a1s2d3f4\", InvalidNumAndUnit(\"a1s2d3f4\".to_owned()));\n    // multiplication overflows u64\n    error!(\"20000000TiB\", UnitMultiplicationOverflow);\n\n    assert!(\n        match parse_byte_offset(\"99999999999999999999\", PositiveI64::new(512).unwrap()) {\n            // We can't check against the kind of the `ParseIntError`, so we'll just make sure it's the\n            // same as trying to do the parse directly.\n            Err(ParseNum(e)) => e == \"99999999999999999999\".parse::<i64>().unwrap_err(),\n            _ => false,\n        }\n    );\n}\n"
  },
  {
    "path": "tests/examples/.gitattributes",
    "content": "ascii text eol=lf\n"
  },
  {
    "path": "tests/examples/ascii",
    "content": "0123456789abcde\n"
  },
  {
    "path": "tests/examples/empty",
    "content": ""
  },
  {
    "path": "tests/integration_tests.rs",
    "content": "use assert_cmd::Command;\n\nfn hexyl() -> Command {\n    let mut cmd = Command::new(assert_cmd::cargo_bin!(\"hexyl\"));\n    cmd.current_dir(\"tests/examples\");\n    cmd\n}\ntrait PrettyAssert<S>\nwhere\n    S: AsRef<str>,\n{\n    fn pretty_stdout(self, other: S);\n}\n\n// https://github.com/assert-rs/assert_cmd/issues/121#issuecomment-849937376\n//\nimpl<S> PrettyAssert<S> for assert_cmd::assert::Assert\nwhere\n    S: AsRef<str>,\n{\n    fn pretty_stdout(self, other: S) {\n        println!(\"{}\", other.as_ref().len());\n        let self_str = String::from_utf8(self.get_output().stdout.clone()).unwrap();\n        println!(\"{}\", self_str.len());\n        pretty_assertions::assert_eq!(self_str, other.as_ref());\n    }\n}\n\nmod basic {\n    use super::hexyl;\n\n    #[test]\n    fn can_print_simple_ascii_file() {\n        hexyl()\n        .arg(\"ascii\")\n        .arg(\"--color=never\")\n        .assert()\n        .success()\n        .stdout(\n            \"┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\\n\\\n             │00000000│ 30 31 32 33 34 35 36 37 ┊ 38 39 61 62 63 64 65 0a │01234567┊89abcde_│\\n\\\n             └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\\n\",\n        );\n    }\n\n    #[test]\n    fn can_read_input_from_stdin() {\n        hexyl()\n        .arg(\"--color=never\")\n        .write_stdin(\"abc\")\n        .assert()\n        .success()\n        .stdout(\n            \"┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\\n\\\n             │00000000│ 61 62 63                ┊                         │abc     ┊        │\\n\\\n             └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\\n\",\n        );\n    }\n\n    #[test]\n    fn fails_on_non_existing_input() {\n        hexyl().arg(\"non-existing\").assert().failure();\n    }\n\n    #[test]\n    fn prints_warning_on_empty_content() {\n        hexyl()\n        .arg(\"empty\")\n        .arg(\"--color=never\")\n        .assert()\n        .success()\n        .stdout(\n            \"┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\\n\\\n             │        │ No content              │                         │        │        │\\n\\\n             └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\\n\",\n        );\n    }\n}\n\nmod length {\n    use super::hexyl;\n\n    #[test]\n    fn length_restricts_output_size() {\n        hexyl()\n        .arg(\"hello_world_elf64\")\n        .arg(\"--color=never\")\n        .arg(\"--length=32\")\n        .assert()\n        .success()\n        .stdout(\n            \"┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\\n\\\n             │00000000│ 7f 45 4c 46 02 01 01 00 ┊ 00 00 00 00 00 00 00 00 │•ELF•••⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\\n\\\n             │00000010│ 02 00 3e 00 01 00 00 00 ┊ 00 10 40 00 00 00 00 00 │•⋄>⋄•⋄⋄⋄┊⋄•@⋄⋄⋄⋄⋄│\\n\\\n             └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\\n\",\n        );\n    }\n\n    #[test]\n    fn fail_if_length_and_bytes_options_are_used_simultaneously() {\n        hexyl()\n            .arg(\"hello_world_elf64\")\n            .arg(\"--length=32\")\n            .arg(\"--bytes=10\")\n            .assert()\n            .failure();\n    }\n\n    #[test]\n    fn fail_if_length_and_count_options_are_used_simultaneously() {\n        hexyl()\n            .arg(\"hello_world_elf64\")\n            .arg(\"--length=32\")\n            .arg(\"-l=10\")\n            .assert()\n            .failure();\n    }\n}\n\nmod bytes {\n    use super::hexyl;\n\n    #[test]\n    fn fail_if_bytes_and_count_options_are_used_simultaneously() {\n        hexyl()\n            .arg(\"hello_world_elf64\")\n            .arg(\"--bytes=32\")\n            .arg(\"-l=10\")\n            .assert()\n            .failure();\n    }\n}\n\nmod skip {\n    use super::hexyl;\n\n    #[test]\n    fn basic() {\n        hexyl()\n        .arg(\"ascii\")\n        .arg(\"--color=never\")\n        .arg(\"--skip=2\")\n        .arg(\"--length=4\")\n        .assert()\n        .success()\n        .stdout(\n            \"┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\\n\\\n             │00000002│ 32 33 34 35             ┊                         │2345    ┊        │\\n\\\n             └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\\n\",\n        );\n    }\n\n    #[test]\n    fn prints_warning_when_skipping_past_the_end() {\n        hexyl()\n        .arg(\"ascii\")\n        .arg(\"--color=never\")\n        .arg(\"--skip=1000\")\n        .assert()\n        .success()\n        .stdout(\n            \"┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\\n\\\n             │        │ No content              │                         │        │        │\\n\\\n             └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\\n\",\n        );\n    }\n\n    #[test]\n    fn negative_offset() {\n        hexyl()\n        .arg(\"ascii\")\n        .arg(\"--color=never\")\n        .arg(\"--skip=-4\")\n        .arg(\"--length=3\")\n        .assert()\n        .success()\n        .stdout(\n            \"┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\\n\\\n             │0000000c│ 63 64 65                ┊                         │cde     ┊        │\\n\\\n             └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\\n\",\n        );\n    }\n\n    #[test]\n    fn fails_if_negative_offset_is_too_large() {\n        hexyl()\n            .arg(\"ascii\")\n            .arg(\"--color=never\")\n            .arg(\"--skip=-1MiB\")\n            .assert()\n            .failure()\n            .stderr(predicates::str::contains(\"Failed to jump\"));\n    }\n}\n\nmod display_offset {\n    use super::hexyl;\n\n    #[test]\n    fn basic() {\n        hexyl()\n        .arg(\"ascii\")\n        .arg(\"--color=never\")\n        .arg(\"--display-offset=0xc0ffee\")\n        .assert()\n        .success()\n        .stdout(\n            \"┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\\n\\\n             │00c0ffee│ 30 31 32 33 34 35 36 37 ┊ 38 39 61 62 63 64 65 0a │01234567┊89abcde_│\\n\\\n             └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\\n\",\n        );\n    }\n\n    #[test]\n    fn display_offset_and_skip() {\n        hexyl()\n        .arg(\"hello_world_elf64\")\n        .arg(\"--color=never\")\n        .arg(\"--display-offset=0x20\")\n        .arg(\"--skip=0x10\")\n        .arg(\"--length=0x10\")\n        .assert()\n        .success()\n        .stdout(\n            \"┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\\n\\\n             │00000030│ 02 00 3e 00 01 00 00 00 ┊ 00 10 40 00 00 00 00 00 │•⋄>⋄•⋄⋄⋄┊⋄•@⋄⋄⋄⋄⋄│\\n\\\n             └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\\n\",\n        );\n    }\n}\n\nmod blocksize {\n    use super::hexyl;\n\n    #[test]\n    fn fails_for_zero_or_negative_blocksize() {\n        hexyl()\n            .arg(\"ascii\")\n            .arg(\"--block-size=0\")\n            .assert()\n            .failure();\n\n        hexyl()\n            .arg(\"ascii\")\n            .arg(\"--block-size=-16\")\n            .assert()\n            .failure();\n    }\n}\n\nmod display_settings {\n    use super::hexyl;\n\n    #[test]\n    fn plain() {\n        hexyl()\n            .arg(\"ascii\")\n            .arg(\"--plain\")\n            .assert()\n            .success()\n            .stdout(\"  30 31 32 33 34 35 36 37   38 39 61 62 63 64 65 0a  \\n\");\n    }\n\n    #[test]\n    fn no_chars() {\n        hexyl()\n            .arg(\"ascii\")\n            .arg(\"--no-characters\")\n            .arg(\"--color=never\")\n            .assert()\n            .success()\n            .stdout(\n                \"┌────────┬─────────────────────────┬─────────────────────────┐\\n\\\n                 │00000000│ 30 31 32 33 34 35 36 37 ┊ 38 39 61 62 63 64 65 0a │\\n\\\n                 └────────┴─────────────────────────┴─────────────────────────┘\\n\",\n            );\n    }\n\n    #[test]\n    fn no_position() {\n        hexyl()\n            .arg(\"ascii\")\n            .arg(\"--no-position\")\n            .arg(\"--color=never\")\n            .assert()\n            .success()\n            .stdout(\n                \"┌─────────────────────────┬─────────────────────────┬────────┬────────┐\\n\\\n                 │ 30 31 32 33 34 35 36 37 ┊ 38 39 61 62 63 64 65 0a │01234567┊89abcde_│\\n\\\n                 └─────────────────────────┴─────────────────────────┴────────┴────────┘\\n\",\n            );\n    }\n}\n\nmod group_and_endianness {\n    use super::hexyl;\n    use super::PrettyAssert;\n\n    #[test]\n    fn group_2_bytes_be() {\n        hexyl()\n            .arg(\"ascii\")\n            .arg(\"--color=never\")\n            .arg(\"--group-size=2\")\n            .assert()\n            .success()\n            .stdout(\n                \"┌────────┬─────────────────────┬─────────────────────┬────────┬────────┐\\n\\\n                 │00000000│ 3031 3233 3435 3637 ┊ 3839 6162 6364 650a │01234567┊89abcde_│\\n\\\n                 └────────┴─────────────────────┴─────────────────────┴────────┴────────┘\\n\",\n            );\n    }\n\n    #[test]\n    fn group_2_bytes_le() {\n        hexyl()\n            .arg(\"ascii\")\n            .arg(\"--color=never\")\n            .arg(\"--group-size=2\")\n            .arg(\"--endianness=little\")\n            .assert()\n            .success()\n            .stdout(\n                \"┌────────┬─────────────────────┬─────────────────────┬────────┬────────┐\\n\\\n                 │00000000│ 3130 3332 3534 3736 ┊ 3938 6261 6463 0a65 │01234567┊89abcde_│\\n\\\n                 └────────┴─────────────────────┴─────────────────────┴────────┴────────┘\\n\",\n            );\n    }\n\n    #[test]\n    fn group_4_bytes_be() {\n        hexyl()\n            .arg(\"ascii\")\n            .arg(\"--color=never\")\n            .arg(\"--group-size=4\")\n            .assert()\n            .success()\n            .stdout(\n                \"┌────────┬───────────────────┬───────────────────┬────────┬────────┐\\n\\\n                 │00000000│ 30313233 34353637 ┊ 38396162 6364650a │01234567┊89abcde_│\\n\\\n                 └────────┴───────────────────┴───────────────────┴────────┴────────┘\\n\",\n            );\n    }\n\n    #[test]\n    fn group_4_bytes_le() {\n        hexyl()\n            .arg(\"ascii\")\n            .arg(\"--color=never\")\n            .arg(\"--group-size=4\")\n            .arg(\"--endianness=little\")\n            .assert()\n            .success()\n            .stdout(\n                \"┌────────┬───────────────────┬───────────────────┬────────┬────────┐\\n\\\n                 │00000000│ 33323130 37363534 ┊ 62613938 0a656463 │01234567┊89abcde_│\\n\\\n                 └────────┴───────────────────┴───────────────────┴────────┴────────┘\\n\",\n            );\n    }\n\n    #[test]\n    fn group_8_bytes_be() {\n        hexyl()\n            .arg(\"ascii\")\n            .arg(\"--color=never\")\n            .arg(\"--group-size=8\")\n            .assert()\n            .success()\n            .stdout(\n                \"┌────────┬──────────────────┬──────────────────┬────────┬────────┐\\n\\\n                 │00000000│ 3031323334353637 ┊ 383961626364650a │01234567┊89abcde_│\\n\\\n                 └────────┴──────────────────┴──────────────────┴────────┴────────┘\\n\",\n            );\n    }\n\n    #[test]\n    fn group_8_bytes_le() {\n        hexyl()\n            .arg(\"ascii\")\n            .arg(\"--color=never\")\n            .arg(\"--group-size=8\")\n            .arg(\"--endianness=little\")\n            .assert()\n            .success()\n            .stdout(\n                \"┌────────┬──────────────────┬──────────────────┬────────┬────────┐\\n\\\n                 │00000000│ 3736353433323130 ┊ 0a65646362613938 │01234567┊89abcde_│\\n\\\n                 └────────┴──────────────────┴──────────────────┴────────┴────────┘\\n\",\n            );\n    }\n\n    #[test]\n    fn group_size_plain() {\n        hexyl()\n            .arg(\"ascii\")\n            .arg(\"--color=never\")\n            .arg(\"--plain\")\n            .arg(\"--group-size=2\")\n            .assert()\n            .success()\n            .stdout(\"  3031 3233 3435 3637   3839 6162 6364 650a  \\n\");\n    }\n\n    #[test]\n    fn group_size_fill_space() {\n        hexyl()\n            .arg(\"--color=never\")\n            .arg(\"--group-size=2\")\n            .write_stdin(\"abc\")\n            .assert()\n            .success()\n            .stdout(\n                \"┌────────┬─────────────────────┬─────────────────────┬────────┬────────┐\\n\\\n                 │00000000│ 6162 63             ┊                     │abc     ┊        │\\n\\\n                 └────────┴─────────────────────┴─────────────────────┴────────┴────────┘\\n\",\n            );\n    }\n\n    #[test]\n    fn group_size_invalid() {\n        hexyl()\n            .arg(\"ascii\")\n            .arg(\"--color=never\")\n            .arg(\"--plain\")\n            .arg(\"--group-size=3\")\n            .assert()\n            .failure();\n    }\n    #[test]\n    fn squeeze_no_chars() {\n        hexyl()\n            .arg(\"hello_world_elf64\")\n            .arg(\"--color=never\")\n            .arg(\"--skip=1024\")\n            .arg(\"--length=4096\")\n            .arg(\"--no-characters\")\n            .assert()\n            .success()\n            .pretty_stdout(\n                \"\\\n┌────────┬─────────────────────────┬─────────────────────────┐\n│00000400│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │\n│*       │                         ┊                         │\n│00001000│ ba 0e 00 00 00 b9 00 20 ┊ 40 00 bb 01 00 00 00 b8 │\n│00001010│ 04 00 00 00 cd 80 b8 01 ┊ 00 00 00 cd 80 00 00 00 │\n│00001020│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │\n│*       │                         ┊                         │\n│00001400│                         ┊                         │\n└────────┴─────────────────────────┴─────────────────────────┘\n\",\n            );\n    }\n    #[test]\n    fn squeeze_no_chars_one_panel() {\n        hexyl()\n            .arg(\"hello_world_elf64\")\n            .arg(\"--color=never\")\n            .arg(\"--skip=1024\")\n            .arg(\"--length=4096\")\n            .arg(\"--no-characters\")\n            .arg(\"--panels=1\")\n            .assert()\n            .success()\n            .pretty_stdout(\n                \"\\\n┌────────┬─────────────────────────┐\n│00000400│ 00 00 00 00 00 00 00 00 │\n│*       │                         │\n│00001000│ ba 0e 00 00 00 b9 00 20 │\n│00001008│ 40 00 bb 01 00 00 00 b8 │\n│00001010│ 04 00 00 00 cd 80 b8 01 │\n│00001018│ 00 00 00 cd 80 00 00 00 │\n│00001020│ 00 00 00 00 00 00 00 00 │\n│*       │                         │\n│00001400│                         │\n└────────┴─────────────────────────┘\n\",\n            );\n    }\n    #[test]\n    fn squeeze_no_position() {\n        hexyl()\n            .arg(\"hello_world_elf64\")\n            .arg(\"--color=never\")\n            .arg(\"--skip=1024\")\n            .arg(\"--length=4096\")\n            .arg(\"--no-position\")\n            .assert()\n            .success()\n            .pretty_stdout(\n                \"\\\n┌─────────────────────────┬─────────────────────────┬────────┬────────┐\n│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\n│*                        ┊                         │        ┊        │\n│ ba 0e 00 00 00 b9 00 20 ┊ 40 00 bb 01 00 00 00 b8 │×•⋄⋄⋄×⋄ ┊@⋄×•⋄⋄⋄×│\n│ 04 00 00 00 cd 80 b8 01 ┊ 00 00 00 cd 80 00 00 00 │•⋄⋄⋄×××•┊⋄⋄⋄××⋄⋄⋄│\n│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\n│*                        ┊                         │        ┊        │\n│*                        ┊                         │        ┊        │\n└─────────────────────────┴─────────────────────────┴────────┴────────┘\n\",\n            );\n    }\n    #[test]\n    fn squeeze_no_position_one_panel() {\n        hexyl()\n            .arg(\"hello_world_elf64\")\n            .arg(\"--color=never\")\n            .arg(\"--skip=1024\")\n            .arg(\"--length=4096\")\n            .arg(\"--no-position\")\n            .arg(\"--panels=1\")\n            .assert()\n            .success()\n            .pretty_stdout(\n                \"\\\n┌─────────────────────────┬────────┐\n│ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄│\n│*                        │        │\n│ ba 0e 00 00 00 b9 00 20 │×•⋄⋄⋄×⋄ │\n│ 40 00 bb 01 00 00 00 b8 │@⋄×•⋄⋄⋄×│\n│ 04 00 00 00 cd 80 b8 01 │•⋄⋄⋄×××•│\n│ 00 00 00 cd 80 00 00 00 │⋄⋄⋄××⋄⋄⋄│\n│ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄│\n│*                        │        │\n│*                        │        │\n└─────────────────────────┴────────┘\n\",\n            );\n    }\n    #[test]\n    fn squeeze_odd_panels_remainder_bytes() {\n        hexyl()\n            .arg(\"hello_world_elf64\")\n            .arg(\"--color=never\")\n            .arg(\"--skip=1024\")\n            .arg(\"--length=4092\") // 4 byte remainder\n            .arg(\"--panels=3\")\n            .assert()\n            .success()\n            .pretty_stdout(\n                \"\\\n┌────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬────────┬────────┬────────┐\n│00000400│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\n│*       │                         ┊                         ┊                         │        ┊        ┊        │\n│00001000│ ba 0e 00 00 00 b9 00 20 ┊ 40 00 bb 01 00 00 00 b8 ┊ 04 00 00 00 cd 80 b8 01 │×•⋄⋄⋄×⋄ ┊@⋄×•⋄⋄⋄×┊•⋄⋄⋄×××•│\n│00001018│ 00 00 00 cd 80 00 00 00 ┊ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄××⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\n│00001030│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\n│*       │                         ┊                         ┊                         │        ┊        ┊        │\n│000013f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00             ┊                         │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄    ┊        │\n└────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴────────┴────────┴────────┘\n\",\n            );\n    }\n\n    #[test]\n    fn squeeze_plain() {\n        hexyl()\n            .arg(\"hello_world_elf64\")\n            .arg(\"--color=never\")\n            .arg(\"--skip=1024\")\n            .arg(\"--length=4096\")\n            .arg(\"--plain\")\n            .assert()\n            .success()\n            .pretty_stdout(\n                \"  \\\n  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00  \n *                                                   \n  ba 0e 00 00 00 b9 00 20   40 00 bb 01 00 00 00 b8  \n  04 00 00 00 cd 80 b8 01   00 00 00 cd 80 00 00 00  \n  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00  \n *                                                   \n *                                                   \n\",\n            );\n    }\n\n    #[test]\n    fn squeeze_plain_remainder() {\n        hexyl()\n            .arg(\"hello_world_elf64\")\n            .arg(\"--color=never\")\n            .arg(\"--skip=1024\")\n            .arg(\"--length=4092\") // 4 byte remainder\n            .arg(\"--plain\")\n            .assert()\n            .success()\n            .pretty_stdout(\n                \"  \\\n  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00  \n *                                                   \n  ba 0e 00 00 00 b9 00 20   40 00 bb 01 00 00 00 b8  \n  04 00 00 00 cd 80 b8 01   00 00 00 cd 80 00 00 00  \n  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00  \n *                                                   \n  00 00 00 00 00 00 00 00   00 00 00 00              \n\",\n            );\n    }\n}\n\nmod base {\n    use super::hexyl;\n    use super::PrettyAssert;\n\n    #[test]\n    fn base2() {\n        hexyl()\n            .arg(\"ascii\")\n            .arg(\"--plain\")\n            .arg(\"--base=binary\")\n            .assert()\n            .success()\n            .pretty_stdout(\n                \"  00110000 00110001 00110010 00110011 00110100 00110101 00110110 00110111  \\n  \\\n                   00111000 00111001 01100001 01100010 01100011 01100100 01100101 00001010  \\n\",\n            );\n    }\n}\n\nmod character_table {\n    use super::hexyl;\n    use super::PrettyAssert;\n\n    #[test]\n    fn ascii() {\n        hexyl()\n            .arg(\"hello_world_elf64\")\n            .arg(\"--color=never\")\n            .arg(\"--character-table=ascii\")\n            .assert()\n            .success()\n            .pretty_stdout(\n                \"┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n│00000000│ 7f 45 4c 46 02 01 01 00 ┊ 00 00 00 00 00 00 00 00 │.ELF....┊........│\n│00000010│ 02 00 3e 00 01 00 00 00 ┊ 00 10 40 00 00 00 00 00 │..>.....┊..@.....│\n│00000020│ 40 00 00 00 00 00 00 00 ┊ 28 20 00 00 00 00 00 00 │@.......┊( ......│\n│00000030│ 00 00 00 00 40 00 38 00 ┊ 03 00 40 00 04 00 03 00 │....@.8.┊..@.....│\n│00000040│ 01 00 00 00 04 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│\n│00000050│ 00 00 40 00 00 00 00 00 ┊ 00 00 40 00 00 00 00 00 │..@.....┊..@.....│\n│00000060│ e8 00 00 00 00 00 00 00 ┊ e8 00 00 00 00 00 00 00 │........┊........│\n│00000070│ 00 10 00 00 00 00 00 00 ┊ 01 00 00 00 05 00 00 00 │........┊........│\n│00000080│ 00 10 00 00 00 00 00 00 ┊ 00 10 40 00 00 00 00 00 │........┊..@.....│\n│00000090│ 00 10 40 00 00 00 00 00 ┊ 1d 00 00 00 00 00 00 00 │..@.....┊........│\n│000000a0│ 1d 00 00 00 00 00 00 00 ┊ 00 10 00 00 00 00 00 00 │........┊........│\n│000000b0│ 01 00 00 00 06 00 00 00 ┊ 00 20 00 00 00 00 00 00 │........┊. ......│\n│000000c0│ 00 20 40 00 00 00 00 00 ┊ 00 20 40 00 00 00 00 00 │. @.....┊. @.....│\n│000000d0│ 0e 00 00 00 00 00 00 00 ┊ 0e 00 00 00 00 00 00 00 │........┊........│\n│000000e0│ 00 10 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│\n│000000f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│\n│*       │                         ┊                         │        ┊        │\n│00001000│ ba 0e 00 00 00 b9 00 20 ┊ 40 00 bb 01 00 00 00 b8 │....... ┊@.......│\n│00001010│ 04 00 00 00 cd 80 b8 01 ┊ 00 00 00 cd 80 00 00 00 │........┊........│\n│00001020│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│\n│*       │                         ┊                         │        ┊        │\n│00002000│ 48 65 6c 6c 6f 2c 20 77 ┊ 6f 72 6c 64 21 0a 00 2e │Hello, w┊orld!...│\n│00002010│ 73 68 73 74 72 74 61 62 ┊ 00 2e 74 65 78 74 00 2e │shstrtab┊..text..│\n│00002020│ 64 61 74 61 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │data....┊........│\n│00002030│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│\n│*       │                         ┊                         │        ┊        │\n│00002060│ 00 00 00 00 00 00 00 00 ┊ 0b 00 00 00 01 00 00 00 │........┊........│\n│00002070│ 06 00 00 00 00 00 00 00 ┊ 00 10 40 00 00 00 00 00 │........┊..@.....│\n│00002080│ 00 10 00 00 00 00 00 00 ┊ 1d 00 00 00 00 00 00 00 │........┊........│\n│00002090│ 00 00 00 00 00 00 00 00 ┊ 10 00 00 00 00 00 00 00 │........┊........│\n│000020a0│ 00 00 00 00 00 00 00 00 ┊ 11 00 00 00 01 00 00 00 │........┊........│\n│000020b0│ 03 00 00 00 00 00 00 00 ┊ 00 20 40 00 00 00 00 00 │........┊. @.....│\n│000020c0│ 00 20 00 00 00 00 00 00 ┊ 0e 00 00 00 00 00 00 00 │. ......┊........│\n│000020d0│ 00 00 00 00 00 00 00 00 ┊ 04 00 00 00 00 00 00 00 │........┊........│\n│000020e0│ 00 00 00 00 00 00 00 00 ┊ 01 00 00 00 03 00 00 00 │........┊........│\n│000020f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│\n│00002100│ 0e 20 00 00 00 00 00 00 ┊ 17 00 00 00 00 00 00 00 │. ......┊........│\n│00002110│ 00 00 00 00 00 00 00 00 ┊ 01 00 00 00 00 00 00 00 │........┊........│\n│00002120│ 00 00 00 00 00 00 00 00 ┊                         │........┊        │\n└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n\",\n            );\n    }\n\n    #[test]\n    fn codepage_437() {\n        hexyl()\n            .arg(\"hello_world_elf64\")\n            .arg(\"--color=never\")\n            .arg(\"--character-table=codepage-437\")\n            .assert()\n            .success()\n            .pretty_stdout(\n                \"┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n│00000000│ 7f 45 4c 46 02 01 01 00 ┊ 00 00 00 00 00 00 00 00 │⌂ELF☻☺☺⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\n│00000010│ 02 00 3e 00 01 00 00 00 ┊ 00 10 40 00 00 00 00 00 │☻⋄>⋄☺⋄⋄⋄┊⋄►@⋄⋄⋄⋄⋄│\n│00000020│ 40 00 00 00 00 00 00 00 ┊ 28 20 00 00 00 00 00 00 │@⋄⋄⋄⋄⋄⋄⋄┊( ⋄⋄⋄⋄⋄⋄│\n│00000030│ 00 00 00 00 40 00 38 00 ┊ 03 00 40 00 04 00 03 00 │⋄⋄⋄⋄@⋄8⋄┊♥⋄@⋄♦⋄♥⋄│\n│00000040│ 01 00 00 00 04 00 00 00 ┊ 00 00 00 00 00 00 00 00 │☺⋄⋄⋄♦⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\n│00000050│ 00 00 40 00 00 00 00 00 ┊ 00 00 40 00 00 00 00 00 │⋄⋄@⋄⋄⋄⋄⋄┊⋄⋄@⋄⋄⋄⋄⋄│\n│00000060│ e8 00 00 00 00 00 00 00 ┊ e8 00 00 00 00 00 00 00 │Φ⋄⋄⋄⋄⋄⋄⋄┊Φ⋄⋄⋄⋄⋄⋄⋄│\n│00000070│ 00 10 00 00 00 00 00 00 ┊ 01 00 00 00 05 00 00 00 │⋄►⋄⋄⋄⋄⋄⋄┊☺⋄⋄⋄♣⋄⋄⋄│\n│00000080│ 00 10 00 00 00 00 00 00 ┊ 00 10 40 00 00 00 00 00 │⋄►⋄⋄⋄⋄⋄⋄┊⋄►@⋄⋄⋄⋄⋄│\n│00000090│ 00 10 40 00 00 00 00 00 ┊ 1d 00 00 00 00 00 00 00 │⋄►@⋄⋄⋄⋄⋄┊↔⋄⋄⋄⋄⋄⋄⋄│\n│000000a0│ 1d 00 00 00 00 00 00 00 ┊ 00 10 00 00 00 00 00 00 │↔⋄⋄⋄⋄⋄⋄⋄┊⋄►⋄⋄⋄⋄⋄⋄│\n│000000b0│ 01 00 00 00 06 00 00 00 ┊ 00 20 00 00 00 00 00 00 │☺⋄⋄⋄♠⋄⋄⋄┊⋄ ⋄⋄⋄⋄⋄⋄│\n│000000c0│ 00 20 40 00 00 00 00 00 ┊ 00 20 40 00 00 00 00 00 │⋄ @⋄⋄⋄⋄⋄┊⋄ @⋄⋄⋄⋄⋄│\n│000000d0│ 0e 00 00 00 00 00 00 00 ┊ 0e 00 00 00 00 00 00 00 │♫⋄⋄⋄⋄⋄⋄⋄┊♫⋄⋄⋄⋄⋄⋄⋄│\n│000000e0│ 00 10 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄►⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\n│000000f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\n│*       │                         ┊                         │        ┊        │\n│00001000│ ba 0e 00 00 00 b9 00 20 ┊ 40 00 bb 01 00 00 00 b8 │║♫⋄⋄⋄╣⋄ ┊@⋄╗☺⋄⋄⋄╕│\n│00001010│ 04 00 00 00 cd 80 b8 01 ┊ 00 00 00 cd 80 00 00 00 │♦⋄⋄⋄═Ç╕☺┊⋄⋄⋄═Ç⋄⋄⋄│\n│00001020│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\n│*       │                         ┊                         │        ┊        │\n│00002000│ 48 65 6c 6c 6f 2c 20 77 ┊ 6f 72 6c 64 21 0a 00 2e │Hello, w┊orld!◙⋄.│\n│00002010│ 73 68 73 74 72 74 61 62 ┊ 00 2e 74 65 78 74 00 2e │shstrtab┊⋄.text⋄.│\n│00002020│ 64 61 74 61 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │data⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\n│00002030│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\n│*       │                         ┊                         │        ┊        │\n│00002060│ 00 00 00 00 00 00 00 00 ┊ 0b 00 00 00 01 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊♂⋄⋄⋄☺⋄⋄⋄│\n│00002070│ 06 00 00 00 00 00 00 00 ┊ 00 10 40 00 00 00 00 00 │♠⋄⋄⋄⋄⋄⋄⋄┊⋄►@⋄⋄⋄⋄⋄│\n│00002080│ 00 10 00 00 00 00 00 00 ┊ 1d 00 00 00 00 00 00 00 │⋄►⋄⋄⋄⋄⋄⋄┊↔⋄⋄⋄⋄⋄⋄⋄│\n│00002090│ 00 00 00 00 00 00 00 00 ┊ 10 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊►⋄⋄⋄⋄⋄⋄⋄│\n│000020a0│ 00 00 00 00 00 00 00 00 ┊ 11 00 00 00 01 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊◄⋄⋄⋄☺⋄⋄⋄│\n│000020b0│ 03 00 00 00 00 00 00 00 ┊ 00 20 40 00 00 00 00 00 │♥⋄⋄⋄⋄⋄⋄⋄┊⋄ @⋄⋄⋄⋄⋄│\n│000020c0│ 00 20 00 00 00 00 00 00 ┊ 0e 00 00 00 00 00 00 00 │⋄ ⋄⋄⋄⋄⋄⋄┊♫⋄⋄⋄⋄⋄⋄⋄│\n│000020d0│ 00 00 00 00 00 00 00 00 ┊ 04 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊♦⋄⋄⋄⋄⋄⋄⋄│\n│000020e0│ 00 00 00 00 00 00 00 00 ┊ 01 00 00 00 03 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊☺⋄⋄⋄♥⋄⋄⋄│\n│000020f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\n│00002100│ 0e 20 00 00 00 00 00 00 ┊ 17 00 00 00 00 00 00 00 │♫ ⋄⋄⋄⋄⋄⋄┊↨⋄⋄⋄⋄⋄⋄⋄│\n│00002110│ 00 00 00 00 00 00 00 00 ┊ 01 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊☺⋄⋄⋄⋄⋄⋄⋄│\n│00002120│ 00 00 00 00 00 00 00 00 ┊                         │⋄⋄⋄⋄⋄⋄⋄⋄┊        │\n└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n\",\n            );\n    }\n\n    #[test]\n    fn codepage_1047() {\n        hexyl()\n            .arg(\"hello_world_elf64\")\n            .arg(\"--color=never\")\n            .arg(\"--character-table=codepage-1047\")\n            .assert()\n            .success()\n            .pretty_stdout(\n                \"┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n│00000000│ 7f 45 4c 46 02 01 01 00 ┊ 00 00 00 00 00 00 00 00 │..<.....┊........│\n│00000010│ 02 00 3e 00 01 00 00 00 ┊ 00 10 40 00 00 00 00 00 │........┊.. .....│\n│00000020│ 40 00 00 00 00 00 00 00 ┊ 28 20 00 00 00 00 00 00 │ .......┊........│\n│00000030│ 00 00 00 00 40 00 38 00 ┊ 03 00 40 00 04 00 03 00 │.... ...┊.. .....│\n│00000040│ 01 00 00 00 04 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│\n│00000050│ 00 00 40 00 00 00 00 00 ┊ 00 00 40 00 00 00 00 00 │.. .....┊.. .....│\n│00000060│ e8 00 00 00 00 00 00 00 ┊ e8 00 00 00 00 00 00 00 │Y.......┊Y.......│\n│00000070│ 00 10 00 00 00 00 00 00 ┊ 01 00 00 00 05 00 00 00 │........┊........│\n│00000080│ 00 10 00 00 00 00 00 00 ┊ 00 10 40 00 00 00 00 00 │........┊.. .....│\n│00000090│ 00 10 40 00 00 00 00 00 ┊ 1d 00 00 00 00 00 00 00 │.. .....┊........│\n│000000a0│ 1d 00 00 00 00 00 00 00 ┊ 00 10 00 00 00 00 00 00 │........┊........│\n│000000b0│ 01 00 00 00 06 00 00 00 ┊ 00 20 00 00 00 00 00 00 │........┊........│\n│000000c0│ 00 20 40 00 00 00 00 00 ┊ 00 20 40 00 00 00 00 00 │.. .....┊.. .....│\n│000000d0│ 0e 00 00 00 00 00 00 00 ┊ 0e 00 00 00 00 00 00 00 │........┊........│\n│000000e0│ 00 10 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│\n│000000f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│\n│*       │                         ┊                         │        ┊        │\n│00001000│ ba 0e 00 00 00 b9 00 20 ┊ 40 00 bb 01 00 00 00 b8 │[.......┊ .].....│\n│00001010│ 04 00 00 00 cd 80 b8 01 ┊ 00 00 00 cd 80 00 00 00 │........┊........│\n│00001020│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│\n│*       │                         ┊                         │        ┊        │\n│00002000│ 48 65 6c 6c 6f 2c 20 77 ┊ 6f 72 6c 64 21 0a 00 2e │..%%?...┊?.%.....│\n│00002010│ 73 68 73 74 72 74 61 62 ┊ 00 2e 74 65 78 74 00 2e │....../.┊........│\n│00002020│ 64 61 74 61 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │././....┊........│\n│00002030│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│\n│*       │                         ┊                         │        ┊        │\n│00002060│ 00 00 00 00 00 00 00 00 ┊ 0b 00 00 00 01 00 00 00 │........┊........│\n│00002070│ 06 00 00 00 00 00 00 00 ┊ 00 10 40 00 00 00 00 00 │........┊.. .....│\n│00002080│ 00 10 00 00 00 00 00 00 ┊ 1d 00 00 00 00 00 00 00 │........┊........│\n│00002090│ 00 00 00 00 00 00 00 00 ┊ 10 00 00 00 00 00 00 00 │........┊........│\n│000020a0│ 00 00 00 00 00 00 00 00 ┊ 11 00 00 00 01 00 00 00 │........┊........│\n│000020b0│ 03 00 00 00 00 00 00 00 ┊ 00 20 40 00 00 00 00 00 │........┊.. .....│\n│000020c0│ 00 20 00 00 00 00 00 00 ┊ 0e 00 00 00 00 00 00 00 │........┊........│\n│000020d0│ 00 00 00 00 00 00 00 00 ┊ 04 00 00 00 00 00 00 00 │........┊........│\n│000020e0│ 00 00 00 00 00 00 00 00 ┊ 01 00 00 00 03 00 00 00 │........┊........│\n│000020f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│\n│00002100│ 0e 20 00 00 00 00 00 00 ┊ 17 00 00 00 00 00 00 00 │........┊........│\n│00002110│ 00 00 00 00 00 00 00 00 ┊ 01 00 00 00 00 00 00 00 │........┊........│\n│00002120│ 00 00 00 00 00 00 00 00 ┊                         │........┊        │\n└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n\",\n            );\n    }\n}\n\nmod colors {\n    use super::hexyl;\n    use owo_colors::{colors, Color};\n    use std::collections::HashMap;\n\n    // This is a helper for testing color in output. Writing tests to expect\n    // raw color codes is ugly and hard to look at. Loading expected output\n    // from files works fine, but you end up with a lot of files for all the\n    // tests, and you have to cross-reference the file with the test that uses\n    // it. The files also suffer from the same problem of being hard to\n    // visually inspect. Just catting the file to see the colorized output\n    // loses the nuance of where exactly the color codes appear (before or\n    // after spaces, for example), or whether there are redundant codes.\n    //\n    // So this ColorMap solves the problem neatly by having two inputs:\n    // - the easy to read expected output in plain format without any colors\n    // - a mapping with identical structure except some characters replaced\n    //   with single character color codes.\n    // This makes it easy to reference the output and expected colors side by\n    // side, and provides fairly precise control over exactly where color codes\n    // are expected (the only caveat being you can't have two color codes back\n    // to back). ColorMap combines these into the actual expected output.\n    //\n    // The color mapping needs to be identical to the expected output, except\n    // it has some chars replaced by color code stand ins. These are replaced\n    // with the actual color codes by the colorize method. The '.' character\n    // is also ignored (it doesn't need to match the input). This makes the\n    // color map more readable and avoids input characters from conflicting\n    // with color chars.\n    struct ColorMap {\n        text_map: &'static str,\n        char_to_color: HashMap<char, &'static str>,\n    }\n\n    impl ColorMap {\n        fn from(text_map: &'static str) -> Self {\n            ColorMap {\n                text_map,\n                char_to_color: HashMap::new(),\n            }\n        }\n\n        fn with<C: Color>(&mut self, c: char) -> &mut Self {\n            self.char_to_color.insert(c, C::ANSI_FG);\n            self\n        }\n\n        fn colorize(&self, input: &str) -> String {\n            let mut output = String::new();\n            let mut input_chars = input.chars();\n            for c in self.text_map.chars() {\n                let next_input = input_chars.next().expect(\"input and color map don't match\");\n                if let Some(color) = self.char_to_color.get(&c) {\n                    output.push_str(color);\n                } else if c != '.' {\n                    // ignore '.' in the mapping for readability\n                    assert_eq!(c, next_input, \"input and color map don't match\");\n                }\n                output.push(next_input);\n            }\n            output\n        }\n    }\n\n    #[test]\n    fn hex_colors() {\n        let input = b\"He\\x11\\0 \\xff\\0\\xdd\";\n        let expected_text = \"\\\n            ┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\\n\\\n            │00000000│ 48 65 11 00 20 ff 00 dd ┊                         │He•⋄ ×⋄×┊        │\\n\\\n            └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\\n\";\n        let expected = ColorMap::from(\n            \"\\\n            ┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\\n\\\n            │r.......d y. .. b. c. g. m. c. m.d┊                        d│y.bcgmcmd        d\\n\\\n            └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\\n\",\n        )\n        .with::<colors::Red>('r')\n        .with::<colors::Default>('d')\n        .with::<colors::Yellow>('y')\n        .with::<colors::Blue>('b')\n        .with::<colors::Green>('g')\n        .with::<colors::BrightMagenta>('m')\n        .with::<colors::CustomColor<0xab, 0xcd, 0xef>>('c')\n        .colorize(expected_text);\n\n        hexyl()\n            .write_stdin(input)\n            .arg(\"--color=always\")\n            .env(\"HEXYL_COLOR_OFFSET\", \"red\")\n            .env(\"HEXYL_COLOR_ASCII_PRINTABLE\", \"yellow\")\n            .env(\"HEXYL_COLOR_ASCII_WHITESPACE\", \"green\")\n            .env(\"HEXYL_COLOR_ASCII_OTHER\", \"blue\")\n            .env(\"HEXYL_COLOR_NONASCII\", \"bright magenta\")\n            .env(\"HEXYL_COLOR_NULL\", \"#abcdef\")\n            .assert()\n            .success()\n            .stdout(expected);\n    }\n\n    #[test]\n    fn binary_colors() {\n        let input = b\"He\\x11\\0 \\xff\\0\\xdd\";\n        let expected_text = \"\\\n            ┌────────┬─────────────────────────────────────────────────────────────────────────┬────────┐\\n\\\n            │00000000│ 01001000 01100101 00010001 00000000 00100000 11111111 00000000 11011101 │He•⋄ ×⋄×│\\n\\\n            └────────┴─────────────────────────────────────────────────────────────────────────┴────────┘\\n\";\n        let expected = ColorMap::from(\n            \"\\\n            ┌────────┬─────────────────────────────────────────────────────────────────────────┬────────┐\\n\\\n            │r.......d y....... ........ b....... c....... g....... m....... c....... m.......d│y.bcgmcmd\\n\\\n            └────────┴─────────────────────────────────────────────────────────────────────────┴────────┘\\n\"\n        )\n        .with::<colors::Red>('r')\n        .with::<colors::Default>('d')\n        .with::<colors::Yellow>('y')\n        .with::<colors::Blue>('b')\n        .with::<colors::Green>('g')\n        .with::<colors::BrightMagenta>('m')\n        .with::<colors::CustomColor<0xab, 0xcd, 0xef>>('c')\n        .colorize(expected_text);\n\n        hexyl()\n            .write_stdin(input)\n            .arg(\"--color=always\")\n            .arg(\"--panels=1\")\n            .arg(\"--base=binary\")\n            .env(\"HEXYL_COLOR_OFFSET\", \"red\")\n            .env(\"HEXYL_COLOR_ASCII_PRINTABLE\", \"yellow\")\n            .env(\"HEXYL_COLOR_ASCII_WHITESPACE\", \"green\")\n            .env(\"HEXYL_COLOR_ASCII_OTHER\", \"blue\")\n            .env(\"HEXYL_COLOR_NONASCII\", \"bright magenta\")\n            .env(\"HEXYL_COLOR_NULL\", \"#abcdef\")\n            .assert()\n            .success()\n            .stdout(expected);\n    }\n\n    #[test]\n    fn groupsize_colors() {\n        let input = b\"He\\x11\\0 \\xff\\0\\xdd\";\n        let expected_text = \"\\\n            ┌────────┬─────────────────────┬────────┐\\n\\\n            │00000000│ 4865 1100 20ff 00dd │He•⋄ ×⋄×│\\n\\\n            └────────┴─────────────────────┴────────┘\\n\";\n        let expected = ColorMap::from(\n            \"\\\n            ┌────────┬─────────────────────┬────────┐\\n\\\n            │r.......d y... b.c. g.m. c.m.d│y.bcgmcmd\\n\\\n            └────────┴─────────────────────┴────────┘\\n\",\n        )\n        .with::<colors::Red>('r')\n        .with::<colors::Default>('d')\n        .with::<colors::Yellow>('y')\n        .with::<colors::Blue>('b')\n        .with::<colors::Green>('g')\n        .with::<colors::BrightMagenta>('m')\n        .with::<colors::CustomColor<0xab, 0xcd, 0xef>>('c')\n        .colorize(expected_text);\n\n        hexyl()\n            .write_stdin(input)\n            .arg(\"--color=always\")\n            .arg(\"--panels=1\")\n            .arg(\"--groupsize=2\")\n            .env(\"HEXYL_COLOR_OFFSET\", \"red\")\n            .env(\"HEXYL_COLOR_ASCII_PRINTABLE\", \"yellow\")\n            .env(\"HEXYL_COLOR_ASCII_WHITESPACE\", \"green\")\n            .env(\"HEXYL_COLOR_ASCII_OTHER\", \"blue\")\n            .env(\"HEXYL_COLOR_NONASCII\", \"bright magenta\")\n            .env(\"HEXYL_COLOR_NULL\", \"#abcdef\")\n            .assert()\n            .success()\n            .stdout(expected);\n    }\n}\n"
  }
]