├── .github └── workflows │ └── CICD.yml ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── doc ├── hexyl.1.md ├── logo.svg ├── sponsors.md └── sponsors │ └── warp-logo.png ├── examples └── simple.rs ├── src ├── colors.rs ├── input.rs ├── lib.rs ├── main.rs └── tests.rs └── tests ├── examples ├── .gitattributes ├── ascii ├── empty └── hello_world_elf64 └── integration_tests.rs /.github/workflows/CICD.yml: -------------------------------------------------------------------------------- 1 | name: CICD 2 | 3 | env: 4 | CICD_INTERMEDIATES_DIR: "_cicd-intermediates" 5 | MSRV_FEATURES: "" 6 | 7 | on: 8 | workflow_dispatch: 9 | pull_request: 10 | push: 11 | branches: 12 | - master 13 | tags: 14 | - '*' 15 | 16 | jobs: 17 | crate_metadata: 18 | name: Extract crate metadata 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Extract crate information 23 | id: crate_metadata 24 | run: | 25 | cargo metadata --no-deps --format-version 1 | jq -r '"name=" + .packages[0].name' | tee -a $GITHUB_OUTPUT 26 | cargo metadata --no-deps --format-version 1 | jq -r '"version=" + .packages[0].version' | tee -a $GITHUB_OUTPUT 27 | cargo metadata --no-deps --format-version 1 | jq -r '"maintainer=" + .packages[0].authors[0]' | tee -a $GITHUB_OUTPUT 28 | cargo metadata --no-deps --format-version 1 | jq -r '"homepage=" + .packages[0].homepage' | tee -a $GITHUB_OUTPUT 29 | cargo metadata --no-deps --format-version 1 | jq -r '"msrv=" + .packages[0].rust_version' | tee -a $GITHUB_OUTPUT 30 | outputs: 31 | name: ${{ steps.crate_metadata.outputs.name }} 32 | version: ${{ steps.crate_metadata.outputs.version }} 33 | maintainer: ${{ steps.crate_metadata.outputs.maintainer }} 34 | homepage: ${{ steps.crate_metadata.outputs.homepage }} 35 | msrv: ${{ steps.crate_metadata.outputs.msrv }} 36 | 37 | ensure_cargo_fmt: 38 | name: Ensure 'cargo fmt' has been run 39 | runs-on: ubuntu-20.04 40 | steps: 41 | - uses: dtolnay/rust-toolchain@stable 42 | with: 43 | components: rustfmt 44 | - uses: actions/checkout@v3 45 | - run: cargo fmt -- --check 46 | 47 | min_version: 48 | name: Minimum supported rust version 49 | runs-on: ubuntu-20.04 50 | needs: crate_metadata 51 | steps: 52 | - name: Checkout source code 53 | uses: actions/checkout@v3 54 | 55 | - name: Install rust toolchain (v${{ needs.crate_metadata.outputs.msrv }}) 56 | uses: dtolnay/rust-toolchain@master 57 | with: 58 | toolchain: ${{ needs.crate_metadata.outputs.msrv }} 59 | components: clippy 60 | - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix) 61 | run: cargo clippy --locked --all-targets ${{ env.MSRV_FEATURES }} 62 | - name: Run tests 63 | run: cargo test --locked ${{ env.MSRV_FEATURES }} 64 | 65 | build: 66 | name: ${{ matrix.job.target }} (${{ matrix.job.os }}) 67 | runs-on: ${{ matrix.job.os }} 68 | needs: crate_metadata 69 | strategy: 70 | fail-fast: false 71 | matrix: 72 | job: 73 | - { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } 74 | - { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true } 75 | - { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true } 76 | - { target: i686-pc-windows-msvc , os: windows-2019 } 77 | - { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } 78 | - { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } 79 | - { target: x86_64-apple-darwin , os: macos-13 } 80 | - { target: aarch64-apple-darwin , os: macos-15 } 81 | - { target: x86_64-pc-windows-gnu , os: windows-2019 } 82 | - { target: x86_64-pc-windows-msvc , os: windows-2019 } 83 | - { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true } 84 | - { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true } 85 | env: 86 | BUILD_CMD: cargo 87 | steps: 88 | - name: Checkout source code 89 | uses: actions/checkout@v3 90 | 91 | - name: Install prerequisites 92 | shell: bash 93 | run: | 94 | case ${{ matrix.job.target }} in 95 | arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;; 96 | aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;; 97 | esac 98 | 99 | - name: Install Rust toolchain 100 | uses: dtolnay/rust-toolchain@stable 101 | with: 102 | targets: ${{ matrix.job.target }} 103 | 104 | - name: Install cross 105 | if: matrix.job.use-cross 106 | uses: taiki-e/install-action@v2 107 | with: 108 | tool: cross 109 | 110 | - name: Overwrite build command env variable 111 | if: matrix.job.use-cross 112 | shell: bash 113 | run: echo "BUILD_CMD=cross" >> $GITHUB_ENV 114 | 115 | - name: Show version information (Rust, cargo, GCC) 116 | shell: bash 117 | run: | 118 | gcc --version || true 119 | rustup -V 120 | rustup toolchain list 121 | rustup default 122 | cargo -V 123 | rustc -V 124 | 125 | - name: Build 126 | shell: bash 127 | run: $BUILD_CMD build --locked --release --target=${{ matrix.job.target }} 128 | 129 | - name: Run example 130 | if: ${{ !matrix.job.use-cross }} 131 | shell: bash 132 | run: $BUILD_CMD run --release --target=${{ matrix.job.target }} --example=simple 133 | 134 | - name: Set binary name & path 135 | id: bin 136 | shell: bash 137 | run: | 138 | # Figure out suffix of binary 139 | EXE_suffix="" 140 | case ${{ matrix.job.target }} in 141 | *-pc-windows-*) EXE_suffix=".exe" ;; 142 | esac; 143 | 144 | # Setup paths 145 | BIN_NAME="${{ needs.crate_metadata.outputs.name }}${EXE_suffix}" 146 | BIN_PATH="target/${{ matrix.job.target }}/release/${BIN_NAME}" 147 | 148 | # Let subsequent steps know where to find the binary 149 | echo "BIN_PATH=${BIN_PATH}" >> $GITHUB_OUTPUT 150 | echo "BIN_NAME=${BIN_NAME}" >> $GITHUB_OUTPUT 151 | 152 | - name: Set testing options 153 | id: test-options 154 | shell: bash 155 | run: | 156 | # test only library unit tests and binary for arm-type targets 157 | unset CARGO_TEST_OPTIONS 158 | unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--lib --bin ${{ needs.crate_metadata.outputs.name }}" ;; esac; 159 | echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_OUTPUT 160 | 161 | - name: Run tests 162 | shell: bash 163 | run: $BUILD_CMD test --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}} 164 | 165 | - name: Setup Pandoc 166 | uses: r-lib/actions/setup-pandoc@v2 167 | 168 | - name: Generate man page 169 | run: pandoc -s -f markdown -t man -o "doc/${{ needs.crate_metadata.outputs.name }}.1" "doc/${{ needs.crate_metadata.outputs.name }}.1.md" 170 | 171 | - name: Create tarball 172 | id: package 173 | shell: bash 174 | run: | 175 | PKG_suffix=".tar.gz" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=".zip" ;; esac; 176 | PKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-v${{ needs.crate_metadata.outputs.version }}-${{ matrix.job.target }} 177 | PKG_NAME=${PKG_BASENAME}${PKG_suffix} 178 | echo "PKG_NAME=${PKG_NAME}" >> $GITHUB_OUTPUT 179 | 180 | PKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/package" 181 | ARCHIVE_DIR="${PKG_STAGING}/${PKG_BASENAME}/" 182 | mkdir -p "${ARCHIVE_DIR}" 183 | 184 | # Binary 185 | cp "${{ steps.bin.outputs.BIN_PATH }}" "$ARCHIVE_DIR" 186 | 187 | # README, LICENSE and CHANGELOG files 188 | cp "README.md" "LICENSE-MIT" "LICENSE-APACHE" "CHANGELOG.md" "$ARCHIVE_DIR" 189 | 190 | # Man page 191 | cp "doc/${{ needs.crate_metadata.outputs.name }}.1" "$ARCHIVE_DIR" 192 | 193 | # base compressed package 194 | pushd "${PKG_STAGING}/" >/dev/null 195 | case ${{ matrix.job.target }} in 196 | *-pc-windows-*) 7z -y a "${PKG_NAME}" "${PKG_BASENAME}"/* | tail -2 ;; 197 | *) tar czf "${PKG_NAME}" "${PKG_BASENAME}"/* ;; 198 | esac; 199 | popd >/dev/null 200 | 201 | # Let subsequent steps know where to find the compressed package 202 | echo "PKG_PATH=${PKG_STAGING}/${PKG_NAME}" >> $GITHUB_OUTPUT 203 | 204 | - name: Create Debian package 205 | id: debian-package 206 | shell: bash 207 | if: startsWith(matrix.job.os, 'ubuntu') 208 | run: | 209 | COPYRIGHT_YEARS="2018 - "$(date "+%Y") 210 | DPKG_STAGING="${{ env.CICD_INTERMEDIATES_DIR }}/debian-package" 211 | DPKG_DIR="${DPKG_STAGING}/dpkg" 212 | mkdir -p "${DPKG_DIR}" 213 | 214 | DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }} 215 | DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }}-musl 216 | case ${{ matrix.job.target }} in *-musl*) DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-musl ; DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }} ;; esac; 217 | DPKG_VERSION=${{ needs.crate_metadata.outputs.version }} 218 | 219 | unset DPKG_ARCH 220 | case ${{ matrix.job.target }} in 221 | aarch64-*-linux-*) DPKG_ARCH=arm64 ;; 222 | arm-*-linux-*hf) DPKG_ARCH=armhf ;; 223 | i686-*-linux-*) DPKG_ARCH=i686 ;; 224 | x86_64-*-linux-*) DPKG_ARCH=amd64 ;; 225 | *) DPKG_ARCH=notset ;; 226 | esac; 227 | 228 | DPKG_NAME="${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb" 229 | echo "DPKG_NAME=${DPKG_NAME}" >> $GITHUB_OUTPUT 230 | 231 | # Binary 232 | install -Dm755 "${{ steps.bin.outputs.BIN_PATH }}" "${DPKG_DIR}/usr/bin/${{ steps.bin.outputs.BIN_NAME }}" 233 | 234 | # Man page 235 | install -Dm644 'doc/${{ needs.crate_metadata.outputs.name }}.1' "${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1" 236 | gzip -n --best "${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1" 237 | 238 | # README and LICENSE 239 | install -Dm644 "README.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md" 240 | install -Dm644 "LICENSE-MIT" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-MIT" 241 | install -Dm644 "LICENSE-APACHE" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-APACHE" 242 | install -Dm644 "CHANGELOG.md" "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog" 243 | gzip -n --best "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog" 244 | 245 | cat > "${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright" < "${DPKG_DIR}/DEBIAN/control" <> $GITHUB_OUTPUT 303 | 304 | # build dpkg 305 | fakeroot dpkg-deb --build "${DPKG_DIR}" "${DPKG_PATH}" 306 | 307 | - name: "Artifact upload: tarball" 308 | uses: actions/upload-artifact@main 309 | with: 310 | name: ${{ steps.package.outputs.PKG_NAME }} 311 | path: ${{ steps.package.outputs.PKG_PATH }} 312 | 313 | - name: "Artifact upload: Debian package" 314 | uses: actions/upload-artifact@main 315 | if: steps.debian-package.outputs.DPKG_NAME 316 | with: 317 | name: ${{ steps.debian-package.outputs.DPKG_NAME }} 318 | path: ${{ steps.debian-package.outputs.DPKG_PATH }} 319 | 320 | - name: Check for release 321 | id: is-release 322 | shell: bash 323 | run: | 324 | unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/v[0-9].* ]]; then IS_RELEASE='true' ; fi 325 | echo "IS_RELEASE=${IS_RELEASE}" >> $GITHUB_OUTPUT 326 | 327 | - name: Publish archives and packages 328 | uses: softprops/action-gh-release@v1 329 | if: steps.is-release.outputs.IS_RELEASE 330 | with: 331 | files: | 332 | ${{ steps.package.outputs.PKG_PATH }} 333 | ${{ steps.debian-package.outputs.DPKG_PATH }} 334 | env: 335 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 336 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | 4 | # Generated files 5 | hexyl.1 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # unreleased 2 | 3 | ## Features 4 | 5 | ## Bugfixes 6 | 7 | 8 | # v0.16.0 9 | 10 | ## Features 11 | 12 | * New `--print-color-table` option, see #229 (@sahinfalcon) 13 | 14 | ## Bugfixes 15 | 16 | - Throw an error when try to view a directory, see #234 (@Integral-Tech) 17 | 18 | 19 | # v0.15.0 20 | 21 | ## Features 22 | 23 | - Add codepage 1047 for EBCDIC, see #226 (@v1gnesh) 24 | 25 | ## Other 26 | 27 | - Rewrite CLI using the derive API, see #225 (@sorairolake) 28 | 29 | 30 | # v0.14.0 31 | 32 | ## Features 33 | 34 | * 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) 35 | * New `--character-table=ascii` option for a ASCII-only character table, see #212 and #36 (@sharkdp) 36 | 37 | ## Bugfixes 38 | 39 | * Show output when doing `hexyl /dev/zero`, see #211 (@sharifhsn) 40 | * Respect NO_COLOR environment variable, see #210 (@sharkdp) 41 | 42 | 43 | # v0.13.1 44 | 45 | ## Bugfixes 46 | 47 | - Correctly handle discontinuous input (stdin), see #196 and #197 (@sharifhsn) 48 | 49 | # v0.13.0 50 | 51 | ## Features 52 | 53 | - Support both little and big Endian dumps using `--endianness={little,big}`, see #189 and #104 (@RinHizakura) 54 | 55 | ## Changes 56 | 57 | - **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) 58 | 59 | ## `hexyl` as a library 60 | 61 | - New `endianness` method for `PrinterBuilder` 62 | 63 | 64 | # v0.12.0 65 | 66 | ## Features 67 | 68 | - Only show one panel by default if the terminal width is not wide enough for two panels, see #182 (@sharkdp) 69 | - Respect the `NO_COLOR` environment variable, see #179 (@sharifhsn) 70 | 71 | ## Bugfixes 72 | 73 | - Do not fail with an error if `--panels=auto` is used and the output is piped, see #184 (@sharkdp) 74 | 75 | ## Changes 76 | 77 | - Breaking: For `xxd`-compatibility reasons, `--group_bytes` has been renamed to `--group-size` (with an `--groupsize` alias), see #121 (@sharkdp) 78 | 79 | ## `hexyl` as a library 80 | 81 | - Breaking: `num_group_bytes` has been renamed to `group_size`. 82 | 83 | 84 | # v0.11.0 85 | 86 | ## Features 87 | 88 | - Significantly improved performance, see #173 and #176 (@sharifhsn) 89 | - 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) 90 | - Added new `--group-bytes`/`-g` option, see #104 and #170 (@RinHizakura) 91 | - Added new `--base B` option (where `B` can be `binary`, `octal`, `decimal` or `hexadecimal`), see #147 and #178 (@sharifhsn) 92 | - Show actual zero bytes as `⋄` in the character panel (previously: `0`), in order not to confuse them with ASCII 93 | `0` bytes if colors are deactivated. Closes #166 (@sharkdp) 94 | 95 | ## `hexyl` as a library 96 | 97 | - 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) 98 | 99 | ## Other 100 | 101 | - More tests for the squeezing feature, see #177 (@mkatychev) 102 | 103 | ## Thank you 104 | 105 | Special thanks go to @sharifhsn, not just for the new features, 106 | bugfixes and performance improvements. But also for many internal 107 | improvements of the code base and other maintenance tasks. 108 | 109 | 110 | # v0.10.0 111 | 112 | ## Features 113 | 114 | - Added new `--plain`, `--no-characters`, and `--no-position` flags, see #154 (@mkatychev) 115 | - Allow hex numbers and units for `--block-size` argument, see #111 and #144 (@merkrafter) 116 | 117 | ## Other 118 | 119 | - Added a man page, see #151 (@sorairolake) 120 | - Mention ability to specify length in hex, see #143 (@merkrafter) 121 | - `--length` and `--bytes` are now marked as conflicting command-line options, see #152 (@sorairolake) 122 | 123 | 124 | # v0.9.0 125 | 126 | ## Changes 127 | 128 | - 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`. 129 | 130 | ## Features 131 | 132 | - Allow relative and negative byte offsets (e.g. `hexyl --skip=-1block`), see #99 (@ErichDonGubler) 133 | - Added `-l` as another alias for '-n/--length' (`xxd` compatibility), see #121 and #135 (@TheDoctor314) 134 | 135 | ## Bugfixes 136 | 137 | - Argument `--length` silently takes precedence over `--bytes`, see #105 138 | - Print warning on empty content, see #107 and #108 139 | - Disallow block sizes of zero, see #110 140 | - Fix newline appearing in `--version` output, see #131 and #133 (@scimas) 141 | 142 | ## Other 143 | 144 | - Better diagnostic messages, see #98 (@ErichDonGubler) 145 | 146 | ## Packaging 147 | 148 | - `hexyl` is now available on snapstore, see #116 (@purveshpatel511) 149 | 150 | 151 | # v0.8.0 152 | 153 | ## Features 154 | 155 | - A new `--skip ` / `-s ` option can be used to skip the first `N` bytes of the input, see #16, #88 (@Tarnadas, @MaxJohansen, @ErichDonGubler) 156 | - The `--length`/`--bytes`/`--skip`/`--display-offset` options can now take units for their value argument, for example: 157 | ``` bash 158 | hexyl /dev/random --length=1KiB 159 | hexyl $(which hexyl) --skip=1MiB --length=10KiB 160 | ``` 161 | Both decimal SI prefixes (kB, MB, …) as well as binary IEC prefixes (KiB, MiB, …) are supported. 162 | In addition, there is a new `--block-size ` option that can be used to control the size of the `block` 163 | unit: 164 | ``` bash 165 | hexyl /dev/random --block-size=4kB --length=2block 166 | ``` 167 | See: #44 (@ErichDonGubler and @aswild) 168 | 169 | ## Other 170 | 171 | - Various improvements throughout the code base by @ErichDonGubler 172 | 173 | ## Packaging 174 | 175 | - `hexyl` is now available on Void Linux, see #91 (@notramo) 176 | 177 | 178 | # v0.7.0 179 | 180 | ## Bugfixes 181 | 182 | - hexyl can now be closed with `Ctrl-C` when reading input from STDIN, see #84 183 | 184 | ## Changes 185 | 186 | - 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. 187 | - Added an example on how to use `hexyl` as a library: https://github.com/sharkdp/hexyl/blob/v0.7.0/examples/simple.rs 188 | 189 | 190 | # v0.6.0 191 | 192 | ## Features 193 | 194 | - `hexyl` can now be used as a library, see #67 (@tommilligan) 195 | 196 | - Added a new `-o`/`--display-offset` option to add a certain offset to the 197 | reported file positions, see #57 (@tommilligan) 198 | 199 | ## Bugfixes 200 | 201 | - Remove additional space on short input, see #69 (@nalshihabi) 202 | 203 | ## Other 204 | 205 | - Performance improvements, see #73 and #66 206 | 207 | 208 | # v0.5.1 209 | 210 | ## Bugfixes 211 | 212 | - A bug in the squeezing logic caused a wrong hexdump, see #62 (@awidegreen) 213 | - Some colors are printed even if they're disabled, see #64 (@awidegreen) 214 | - Fixed build failure on OpenBSD 6.5, see #61 215 | 216 | 217 | # v0.5.0 218 | 219 | ## Features 220 | 221 | - 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) 222 | - Added a new `--border` option with support for various styles (Unicode, ASCII, None), see #54 (@dmke) 223 | - The `--length`/`-n` argument can be passed as a hexadecimal number (`hexyl -n 0xff /dev/urandom`), see #45 (@Qyriad) 224 | - Added `--bytes`/`-c` as an alias for `--length`/`-n`, see #48 (@selfup) 225 | 226 | ## Changes 227 | 228 | - Print header immediately before the first line, see #51 (@mziter) 229 | 230 | 231 | # v0.4.0 232 | 233 | ## Features 234 | 235 | - Added a new `--color=always/auto/never` option which can be used 236 | to control `hexyl`s color output, see #30 (@bennetthardwick) 237 | - Use 16 colors instead of 256, see #38 238 | 239 | ## Changes 240 | 241 | - Various speed improvements, see #33 (@kballard) 242 | 243 | ## Bugfixes 244 | 245 | - Proper Ctrl-C handling, see #35 246 | - Proper handling of broken pipes (`hexyl … | head`) 247 | 248 | 249 | # v0.3.1 250 | 251 | - Various (huge) performance improvements, see #23 and #24 (@kballard) 252 | - Replaced 24-bit truecolor ANSI codes by 8-bit codes to support 253 | more terminal emulators, fixes #9 254 | 255 | 256 | # v0.3.0 257 | 258 | Windows support 259 | 260 | 261 | # v0.2.0 262 | 263 | Initial release 264 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for considering to contribute to `hexyl`! 4 | 5 | 6 | 7 | ## Add an entry to the changelog 8 | 9 | If your contribution changes the behavior of `hexyl` (as opposed to a typo-fix 10 | in the documentation), please update the [`CHANGELOG.md`](CHANGELOG.md) file 11 | and describe your changes. This makes the release process much easier and 12 | therefore helps to get your changes into a new `hexyl` release faster. 13 | 14 | The top of the `CHANGELOG` contains a *"unreleased"* section with a few 15 | subsections (Features, Bugfixes, …). Please add your entry to the subsection 16 | that best describes your change 17 | 18 | Entries follow this format: 19 | ``` 20 | - Short description of what has been changed, see #123 (@user) 21 | ``` 22 | Here, `#123` is the number of the original issue and/or your pull request. 23 | Please replace `@user` by your GitHub username. 24 | 25 | 26 | ## Adding a new feature 27 | 28 | Please consider opening a [ticket](https://github.com/sharkdp/hexyl/issues/new) 29 | first in order to give us a chance to discuss the feature first. 30 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.17" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is_terminal_polyfill", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.9" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.6" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.1.2" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 49 | dependencies = [ 50 | "windows-sys 0.59.0", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "3.0.6" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 58 | dependencies = [ 59 | "anstyle", 60 | "windows-sys 0.59.0", 61 | ] 62 | 63 | [[package]] 64 | name = "anyhow" 65 | version = "1.0.91" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" 68 | 69 | [[package]] 70 | name = "assert_cmd" 71 | version = "2.0.16" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" 74 | dependencies = [ 75 | "anstyle", 76 | "bstr", 77 | "doc-comment", 78 | "libc", 79 | "predicates", 80 | "predicates-core", 81 | "predicates-tree", 82 | "wait-timeout", 83 | ] 84 | 85 | [[package]] 86 | name = "autocfg" 87 | version = "1.4.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 90 | 91 | [[package]] 92 | name = "bitflags" 93 | version = "2.6.0" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 96 | 97 | [[package]] 98 | name = "bstr" 99 | version = "1.10.0" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" 102 | dependencies = [ 103 | "memchr", 104 | "regex-automata", 105 | "serde", 106 | ] 107 | 108 | [[package]] 109 | name = "clap" 110 | version = "4.5.20" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" 113 | dependencies = [ 114 | "clap_builder", 115 | "clap_derive", 116 | ] 117 | 118 | [[package]] 119 | name = "clap_builder" 120 | version = "4.5.20" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" 123 | dependencies = [ 124 | "anstream", 125 | "anstyle", 126 | "clap_lex", 127 | "strsim", 128 | "terminal_size", 129 | ] 130 | 131 | [[package]] 132 | name = "clap_derive" 133 | version = "4.5.18" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 136 | dependencies = [ 137 | "heck", 138 | "proc-macro2", 139 | "quote", 140 | "syn", 141 | ] 142 | 143 | [[package]] 144 | name = "clap_lex" 145 | version = "0.7.2" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 148 | 149 | [[package]] 150 | name = "colorchoice" 151 | version = "1.0.3" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 154 | 155 | [[package]] 156 | name = "const_format" 157 | version = "0.2.33" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" 160 | dependencies = [ 161 | "const_format_proc_macros", 162 | ] 163 | 164 | [[package]] 165 | name = "const_format_proc_macros" 166 | version = "0.2.33" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" 169 | dependencies = [ 170 | "proc-macro2", 171 | "quote", 172 | "unicode-xid", 173 | ] 174 | 175 | [[package]] 176 | name = "diff" 177 | version = "0.1.13" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 180 | 181 | [[package]] 182 | name = "difflib" 183 | version = "0.4.0" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 186 | 187 | [[package]] 188 | name = "doc-comment" 189 | version = "0.3.3" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 192 | 193 | [[package]] 194 | name = "errno" 195 | version = "0.3.9" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 198 | dependencies = [ 199 | "libc", 200 | "windows-sys 0.52.0", 201 | ] 202 | 203 | [[package]] 204 | name = "float-cmp" 205 | version = "0.9.0" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" 208 | dependencies = [ 209 | "num-traits", 210 | ] 211 | 212 | [[package]] 213 | name = "heck" 214 | version = "0.5.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 217 | 218 | [[package]] 219 | name = "hexyl" 220 | version = "0.16.0" 221 | dependencies = [ 222 | "anyhow", 223 | "assert_cmd", 224 | "clap", 225 | "const_format", 226 | "libc", 227 | "owo-colors", 228 | "predicates", 229 | "pretty_assertions", 230 | "supports-color", 231 | "terminal_size", 232 | "thiserror", 233 | ] 234 | 235 | [[package]] 236 | name = "is_ci" 237 | version = "1.2.0" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" 240 | 241 | [[package]] 242 | name = "is_terminal_polyfill" 243 | version = "1.70.1" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 246 | 247 | [[package]] 248 | name = "libc" 249 | version = "0.2.161" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" 252 | 253 | [[package]] 254 | name = "linux-raw-sys" 255 | version = "0.4.14" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 258 | 259 | [[package]] 260 | name = "memchr" 261 | version = "2.7.4" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 264 | 265 | [[package]] 266 | name = "normalize-line-endings" 267 | version = "0.3.0" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" 270 | 271 | [[package]] 272 | name = "num-traits" 273 | version = "0.2.19" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 276 | dependencies = [ 277 | "autocfg", 278 | ] 279 | 280 | [[package]] 281 | name = "owo-colors" 282 | version = "4.1.0" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" 285 | 286 | [[package]] 287 | name = "predicates" 288 | version = "3.1.2" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" 291 | dependencies = [ 292 | "anstyle", 293 | "difflib", 294 | "float-cmp", 295 | "normalize-line-endings", 296 | "predicates-core", 297 | "regex", 298 | ] 299 | 300 | [[package]] 301 | name = "predicates-core" 302 | version = "1.0.8" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" 305 | 306 | [[package]] 307 | name = "predicates-tree" 308 | version = "1.0.11" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" 311 | dependencies = [ 312 | "predicates-core", 313 | "termtree", 314 | ] 315 | 316 | [[package]] 317 | name = "pretty_assertions" 318 | version = "1.4.1" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" 321 | dependencies = [ 322 | "diff", 323 | "yansi", 324 | ] 325 | 326 | [[package]] 327 | name = "proc-macro2" 328 | version = "1.0.89" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" 331 | dependencies = [ 332 | "unicode-ident", 333 | ] 334 | 335 | [[package]] 336 | name = "quote" 337 | version = "1.0.37" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 340 | dependencies = [ 341 | "proc-macro2", 342 | ] 343 | 344 | [[package]] 345 | name = "regex" 346 | version = "1.11.1" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 349 | dependencies = [ 350 | "aho-corasick", 351 | "memchr", 352 | "regex-automata", 353 | "regex-syntax", 354 | ] 355 | 356 | [[package]] 357 | name = "regex-automata" 358 | version = "0.4.8" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 361 | dependencies = [ 362 | "aho-corasick", 363 | "memchr", 364 | "regex-syntax", 365 | ] 366 | 367 | [[package]] 368 | name = "regex-syntax" 369 | version = "0.8.5" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 372 | 373 | [[package]] 374 | name = "rustix" 375 | version = "0.38.38" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" 378 | dependencies = [ 379 | "bitflags", 380 | "errno", 381 | "libc", 382 | "linux-raw-sys", 383 | "windows-sys 0.52.0", 384 | ] 385 | 386 | [[package]] 387 | name = "serde" 388 | version = "1.0.214" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" 391 | dependencies = [ 392 | "serde_derive", 393 | ] 394 | 395 | [[package]] 396 | name = "serde_derive" 397 | version = "1.0.214" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" 400 | dependencies = [ 401 | "proc-macro2", 402 | "quote", 403 | "syn", 404 | ] 405 | 406 | [[package]] 407 | name = "strsim" 408 | version = "0.11.1" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 411 | 412 | [[package]] 413 | name = "supports-color" 414 | version = "3.0.1" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77" 417 | dependencies = [ 418 | "is_ci", 419 | ] 420 | 421 | [[package]] 422 | name = "syn" 423 | version = "2.0.85" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" 426 | dependencies = [ 427 | "proc-macro2", 428 | "quote", 429 | "unicode-ident", 430 | ] 431 | 432 | [[package]] 433 | name = "terminal_size" 434 | version = "0.4.0" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" 437 | dependencies = [ 438 | "rustix", 439 | "windows-sys 0.59.0", 440 | ] 441 | 442 | [[package]] 443 | name = "termtree" 444 | version = "0.4.1" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" 447 | 448 | [[package]] 449 | name = "thiserror" 450 | version = "1.0.65" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" 453 | dependencies = [ 454 | "thiserror-impl", 455 | ] 456 | 457 | [[package]] 458 | name = "thiserror-impl" 459 | version = "1.0.65" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" 462 | dependencies = [ 463 | "proc-macro2", 464 | "quote", 465 | "syn", 466 | ] 467 | 468 | [[package]] 469 | name = "unicode-ident" 470 | version = "1.0.13" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 473 | 474 | [[package]] 475 | name = "unicode-xid" 476 | version = "0.2.6" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 479 | 480 | [[package]] 481 | name = "utf8parse" 482 | version = "0.2.2" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 485 | 486 | [[package]] 487 | name = "wait-timeout" 488 | version = "0.2.0" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" 491 | dependencies = [ 492 | "libc", 493 | ] 494 | 495 | [[package]] 496 | name = "windows-sys" 497 | version = "0.52.0" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 500 | dependencies = [ 501 | "windows-targets", 502 | ] 503 | 504 | [[package]] 505 | name = "windows-sys" 506 | version = "0.59.0" 507 | source = "registry+https://github.com/rust-lang/crates.io-index" 508 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 509 | dependencies = [ 510 | "windows-targets", 511 | ] 512 | 513 | [[package]] 514 | name = "windows-targets" 515 | version = "0.52.6" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 518 | dependencies = [ 519 | "windows_aarch64_gnullvm", 520 | "windows_aarch64_msvc", 521 | "windows_i686_gnu", 522 | "windows_i686_gnullvm", 523 | "windows_i686_msvc", 524 | "windows_x86_64_gnu", 525 | "windows_x86_64_gnullvm", 526 | "windows_x86_64_msvc", 527 | ] 528 | 529 | [[package]] 530 | name = "windows_aarch64_gnullvm" 531 | version = "0.52.6" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 534 | 535 | [[package]] 536 | name = "windows_aarch64_msvc" 537 | version = "0.52.6" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 540 | 541 | [[package]] 542 | name = "windows_i686_gnu" 543 | version = "0.52.6" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 546 | 547 | [[package]] 548 | name = "windows_i686_gnullvm" 549 | version = "0.52.6" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 552 | 553 | [[package]] 554 | name = "windows_i686_msvc" 555 | version = "0.52.6" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 558 | 559 | [[package]] 560 | name = "windows_x86_64_gnu" 561 | version = "0.52.6" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 564 | 565 | [[package]] 566 | name = "windows_x86_64_gnullvm" 567 | version = "0.52.6" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 570 | 571 | [[package]] 572 | name = "windows_x86_64_msvc" 573 | version = "0.52.6" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 576 | 577 | [[package]] 578 | name = "yansi" 579 | version = "1.0.1" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" 582 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["David Peter "] 3 | categories = ["command-line-utilities"] 4 | description = "A command-line hex viewer" 5 | homepage = "https://github.com/sharkdp/hexyl" 6 | license = "MIT/Apache-2.0" 7 | name = "hexyl" 8 | readme = "README.md" 9 | repository = "https://github.com/sharkdp/hexyl" 10 | version = "0.16.0" 11 | edition = "2021" 12 | rust-version = "1.74" 13 | 14 | [dependencies] 15 | anyhow = "1.0" 16 | const_format = "0.2" 17 | libc = "0.2" 18 | owo-colors = "4" 19 | supports-color = "3" 20 | thiserror = "1.0" 21 | terminal_size = "0.4" 22 | 23 | [dependencies.clap] 24 | version = "4" 25 | features = ["derive", "wrap_help"] 26 | 27 | [dev-dependencies] 28 | assert_cmd = "2.0" 29 | predicates = "3.0" 30 | pretty_assertions = "1.4.0" 31 | 32 | [profile.release] 33 | lto = true 34 | codegen-units = 1 35 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in all 9 | copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](doc/logo.svg) 2 | 3 | [![CICD](https://github.com/sharkdp/hexyl/actions/workflows/CICD.yml/badge.svg)](https://github.com/sharkdp/hexyl/actions/workflows/CICD.yml) 4 | [![](https://img.shields.io/crates/l/hexyl.svg?colorB=22ba4c)](https://crates.io/crates/hexyl) 5 | ![](https://img.shields.io/crates/v/hexyl.svg?colorB=00aa88) 6 | 7 | `hexyl` is a hex viewer for the terminal. It uses a colored output to distinguish different categories 8 | of bytes (NULL bytes, printable ASCII characters, ASCII whitespace characters, other ASCII characters and non-ASCII). 9 | 10 | ### Sponsors 11 | 12 | A special *thank you* goes to our biggest sponsor:
13 | 14 | 15 | Warp 16 |
17 | Warp, the intelligent terminal 18 |
19 | Available on MacOS, Linux, Windows 20 |
21 | 22 | ## Preview 23 | 24 | ![](https://i.imgur.com/MWO9uSL.png) 25 | 26 | ![](https://i.imgur.com/Dp7Wncz.png) 27 | 28 | ![](https://i.imgur.com/ln3TniI.png) 29 | 30 | ![](https://i.imgur.com/f8nm8g6.png) 31 | 32 | 33 | ## Installation 34 | 35 | ### On Ubuntu 36 | 37 | *... and other Debian-based Linux distributions.* 38 | 39 | If you run Ubuntu 19.10 (Eoan Ermine) or newer, you can install the [officially maintained package](https://packages.ubuntu.com/search?keywords=hexyl): 40 | ```bash 41 | sudo apt install hexyl 42 | ``` 43 | If you use an older version of Ubuntu, you can download 44 | the latest `.deb` package from the release page and install it via: 45 | 46 | ``` bash 47 | sudo dpkg -i hexyl_0.15.0_amd64.deb # adapt version number and architecture 48 | ``` 49 | 50 | ### On Debian 51 | 52 | If you run Debian Buster or newer, you can install the [officially maintained Debian package](https://packages.debian.org/search?searchon=names&keywords=hexyl): 53 | ```bash 54 | sudo apt-get install hexyl 55 | ``` 56 | 57 | If you run an older version of Debian, see above for instructions on how to 58 | manually install `hexyl`. 59 | 60 | ### On Fedora 61 | 62 | If you run Fedora 35 or newer, you can install the [officially maintained Fedora package](https://packages.fedoraproject.org/pkgs/rust-hexyl/hexyl): 63 | 64 | ```bash 65 | sudo dnf install hexyl 66 | ``` 67 | 68 | ### On Arch Linux 69 | 70 | You can install `hexyl` from [the official package repository](https://archlinux.org/packages/extra/x86_64/hexyl/): 71 | 72 | ``` 73 | pacman -S hexyl 74 | ``` 75 | 76 | ### On Void Linux 77 | 78 | ``` 79 | xbps-install hexyl 80 | ``` 81 | 82 | ### On Gentoo Linux 83 | 84 | Available in [dm9pZCAq overlay](https://github.com/gentoo-mirror/dm9pZCAq) 85 | 86 | ``` 87 | sudo eselect repository enable dm9pZCAq 88 | sudo emerge --sync dm9pZCAq 89 | sudo emerge sys-apps/hexyl::dm9pZCAq 90 | ``` 91 | 92 | ### On macOS 93 | 94 | Via [Homebrew](https://brew.sh): 95 | 96 | ``` 97 | brew install hexyl 98 | ``` 99 | 100 | ...or via [MacPorts](https://www.macports.org): 101 | 102 | ``` 103 | sudo port install hexyl 104 | ``` 105 | 106 | ### On FreeBSD 107 | 108 | ``` 109 | pkg install hexyl 110 | ``` 111 | 112 | ### On NetBSD 113 | 114 | ``` 115 | pkgin install hexyl 116 | ``` 117 | 118 | ### On OpenBSD 119 | 120 | ``` 121 | doas pkg_add hexyl 122 | ``` 123 | 124 | ### on Termux 125 | ``` 126 | pkg install hexyl 127 | ``` 128 | or 129 | ``` 130 | apt install hexyl 131 | ``` 132 | 133 | ### Via Nix 134 | 135 | ``` 136 | nix-env -i hexyl 137 | ``` 138 | 139 | ### Via Guix 140 | 141 | ``` 142 | guix package -i hexyl 143 | ``` 144 | 145 | Or add the `hexyl` package in the list of packages to be installed in your system configuration (e.g., `/etc/config.scm`). 146 | 147 | ### On other distributions 148 | 149 | Check out the [release page](https://github.com/sharkdp/hexyl/releases) for binary builds. 150 | 151 | ### On Windows 152 | 153 | Check out the [release page](https://github.com/sharkdp/hexyl/releases) for binary builds. 154 | Alternatively, install from source via `cargo`, `snap` or `scoop` (see below). 155 | Make sure that you use a terminal that supports ANSI escape sequences (like ConHost v2 since Windows 10 1703 156 | or Windows Terminal since Windows 10 1903). 157 | 158 | ### Via cargo 159 | 160 | If you have Rust 1.56 or higher, you can install `hexyl` from source via `cargo`: 161 | ``` 162 | cargo install hexyl 163 | ``` 164 | 165 | Alternatively, you can install `hexyl` directly from the repository by using: 166 | ``` 167 | git clone https://github.com/sharkdp/hexyl 168 | cargo install --path ./hexyl 169 | ``` 170 | 171 | Note: To convert the man page, you will need [Pandoc](https://pandoc.org/). 172 | 173 | You can convert from Markdown by using (in the project root): 174 | ``` 175 | pandoc -s -f markdown -t man -o ./doc/hexyl.1 ./doc/hexyl.1.md 176 | ``` 177 | 178 | ### Via snap package 179 | 180 | ``` 181 | sudo snap install hexyl 182 | ``` 183 | [Get it from the Snap Store](https://snapcraft.io/hexyl) 184 | 185 | 186 | ### Via [Scoop](https://scoop.sh) 187 | ``` 188 | scoop install hexyl 189 | ``` 190 | 191 | ### Via [X-CMD](https://x-cmd.com) 192 | ``` 193 | x env use hexyl 194 | ``` 195 | 196 | ## License 197 | 198 | Licensed under either of 199 | 200 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) 201 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) 202 | 203 | at your option. 204 | -------------------------------------------------------------------------------- /doc/hexyl.1.md: -------------------------------------------------------------------------------- 1 | % HEXYL(1) hexyl 0.12.0 | General Commands Manual 2 | % 3 | % 2022-12-05 4 | 5 | # NAME 6 | 7 | hexyl - a command-line hex viewer 8 | 9 | # SYNOPSIS 10 | 11 | **hexyl** [_OPTIONS_] [_FILE_] 12 | 13 | # DESCRIPTION 14 | 15 | **hexyl** is a simple hex viewer for the terminal. 16 | It uses a colored output to distinguish different categories of bytes (NULL 17 | bytes, printable ASCII characters, ASCII whitespace characters, other ASCII 18 | characters and non-ASCII). 19 | 20 | # POSITIONAL ARGUMENTS 21 | 22 | _FILE_ 23 | : The file to display. 24 | If no _FILE_ argument is given, read from STDIN. 25 | 26 | # OPTIONS 27 | 28 | **-n**, **\--length** _N_ 29 | : Only read _N_ bytes from the input. 30 | The _N_ argument can also include a unit with a decimal prefix (kB, MB, ..) 31 | or binary prefix (kiB, MiB, ..), or can be specified using a hex number. 32 | 33 | Examples: 34 | 35 | : 36 | 37 | Read the first 64 bytes: 38 | : $ **hexyl \--length=64** 39 | 40 | Read the first 4 kibibytes: 41 | : $ **hexyl \--length=4KiB** 42 | 43 | Read the first 255 bytes (specified using a hex number): 44 | : $ **hexyl \--length=0xff** 45 | 46 | **-c**, **\--bytes** _N_ 47 | : An alias for **-n**/**\--length**. 48 | 49 | **-l** _N_ 50 | : Yet another alias for **-n**/**\--length**. 51 | 52 | **-s**, **\--skip** _N_ 53 | : Skip the first _N_ bytes of the input. 54 | The _N_ argument can also include a unit (see **\--length** for details). 55 | A negative value is valid and will seek from the end of the file. 56 | 57 | **\--block-size** _SIZE_ 58 | : Sets the size of the block unit to _SIZE_ (default is 512). 59 | 60 | Examples: 61 | 62 | : 63 | 64 | Sets the block size to 1024 bytes: 65 | : $ **hexyl \--block-size=1024 \--length=5block** 66 | 67 | Sets the block size to 4 kilobytes: 68 | : $ **hexyl \--block-size=4kB \--length=2block** 69 | 70 | **-v**, **\--no-squeezing** 71 | : Displays all input data. 72 | Otherwise any number of groups of output lines which would be identical to 73 | the preceding group of lines, are replaced with a line comprised of a 74 | single asterisk. 75 | 76 | **\--color** _WHEN_ 77 | : When to use colors. 78 | The auto-mode only displays colors if the output goes to an interactive 79 | terminal. 80 | 81 | Possible values: 82 | 83 | : - **always** (default) 84 | - **auto** 85 | - **never** 86 | 87 | **\--border** _STYLE_ 88 | : Whether to draw a border with Unicode characters, ASCII characters, or none 89 | at all. 90 | 91 | Possible values: 92 | 93 | : - **unicode** (default) 94 | - **ascii** 95 | - **none** 96 | 97 | **-o**, **\--display-offset** _N_ 98 | : Add _N_ bytes to the displayed file position. 99 | The _N_ argument can also include a unit (see **\--length** for details). 100 | A negative value is valid and calculates an offset relative to the end of 101 | the file. 102 | 103 | **-h**, **\--help** 104 | : Prints help information. 105 | 106 | **-V**, **\--version** 107 | : Prints version information. 108 | 109 | # NOTES 110 | 111 | Source repository: 112 | : 113 | 114 | # EXAMPLES 115 | 116 | Print a given file: 117 | : $ **hexyl small.png** 118 | 119 | Print and view a given file in the terminal pager: 120 | : $ **hexyl big.png | less -r** 121 | 122 | Print the first 256 bytes of a given special file: 123 | : $ **hexyl -n 256 /dev/urandom** 124 | 125 | # AUTHORS 126 | 127 | **hexyl** was written by David Peter . 128 | 129 | # REPORTING BUGS 130 | 131 | Bugs can be reported on GitHub at: 132 | : 133 | 134 | # COPYRIGHT 135 | 136 | **hexyl** is dual-licensed under: 137 | 138 | : - Apache License 2.0 () 139 | - MIT License () 140 | 141 | # SEE ALSO 142 | 143 | **hexdump**(1), **xxd**(1) 144 | -------------------------------------------------------------------------------- /doc/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 29 | 36 | 43 | 44 | 71 | 83 | 84 | 86 | 87 | 89 | image/svg+xml 90 | 92 | 93 | 94 | 95 | 96 | 101 | 104 | 107 | 110 | 113 | 116 | 119 | 122 | 128 | 133 | 138 | 143 | 144 | 149 | 154 | 159 | 164 | 165 | 169 | 173 | 177 | 181 | 185 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | -------------------------------------------------------------------------------- /doc/sponsors.md: -------------------------------------------------------------------------------- 1 | ## Sponsors 2 | 3 | `hexyl` development is sponsored by many individuals and companies. Thank you very much! 4 | 5 | Please note, that being sponsored does not affect the individuality of the `hexyl` 6 | project or affect the maintainers' actions in any way. 7 | We remain impartial and continue to assess pull requests solely on merit - the 8 | features added, bugs solved, and effect on the overall complexity of the code. 9 | No issue will have a different priority based on sponsorship status of the 10 | reporter. 11 | 12 | Contributions from anybody are most welcomed. 13 | 14 | If you want to see our biggest sponsors, check the top of [`README.md`](../README.md#sponsors). 15 | -------------------------------------------------------------------------------- /doc/sponsors/warp-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharkdp/hexyl/742c2fcbfd117b5c9eeff94dfe3324d9890db699/doc/sponsors/warp-logo.png -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | 3 | use hexyl::{BorderStyle, PrinterBuilder}; 4 | 5 | fn main() { 6 | let input = vec![ 7 | 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 8 | 0x52, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x44, 0x08, 0x02, 0x00, 0x00, 0x00, 9 | ]; 10 | 11 | let stdout = io::stdout(); 12 | let mut handle = stdout.lock(); 13 | 14 | let mut printer = PrinterBuilder::new(&mut handle) 15 | .show_color(true) 16 | .show_char_panel(true) 17 | .show_position_panel(true) 18 | .with_border_style(BorderStyle::Unicode) 19 | .enable_squeezing(false) 20 | .num_panels(2) 21 | .group_size(1) 22 | .build(); 23 | printer.print_all(&input[..]).unwrap(); 24 | } 25 | -------------------------------------------------------------------------------- /src/colors.rs: -------------------------------------------------------------------------------- 1 | use owo_colors::{colors, Color}; 2 | 3 | pub const COLOR_NULL: &[u8] = colors::BrightBlack::ANSI_FG.as_bytes(); 4 | pub const COLOR_OFFSET: &[u8] = colors::BrightBlack::ANSI_FG.as_bytes(); 5 | pub const COLOR_ASCII_PRINTABLE: &[u8] = colors::Cyan::ANSI_FG.as_bytes(); 6 | pub const COLOR_ASCII_WHITESPACE: &[u8] = colors::Green::ANSI_FG.as_bytes(); 7 | pub const COLOR_ASCII_OTHER: &[u8] = colors::Green::ANSI_FG.as_bytes(); 8 | pub const COLOR_NONASCII: &[u8] = colors::Yellow::ANSI_FG.as_bytes(); 9 | pub const COLOR_RESET: &[u8] = colors::Default::ANSI_FG.as_bytes(); 10 | 11 | #[rustfmt::skip] 12 | pub const CP437: [char; 256] = [ 13 | // Copyright (c) 2016, Delan Azabani 14 | // 15 | // Permission to use, copy, modify, and/or distribute this software for any 16 | // purpose with or without fee is hereby granted, provided that the above 17 | // copyright notice and this permission notice appear in all copies. 18 | // 19 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 20 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 21 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 22 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 24 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 25 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 26 | // 27 | // modified to use the ⋄ character instead of ␀ 28 | 29 | // use https://en.wikipedia.org/w/index.php?title=Code_page_437&oldid=978947122 30 | // not ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP437.TXT 31 | // because we want the graphic versions of 01h–1Fh + 7Fh 32 | '⋄','☺','☻','♥','♦','♣','♠','•','◘','○','◙','♂','♀','♪','♫','☼', 33 | '►','◄','↕','‼','¶','§','▬','↨','↑','↓','→','←','∟','↔','▲','▼', 34 | ' ','!','"','#','$','%','&','\'','(',')','*','+',',','-','.','/', 35 | '0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?', 36 | '@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O', 37 | 'P','Q','R','S','T','U','V','W','X','Y','Z','[','\\',']','^','_', 38 | '`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o', 39 | 'p','q','r','s','t','u','v','w','x','y','z','{','|','}','~','⌂', 40 | 'Ç','ü','é','â','ä','à','å','ç','ê','ë','è','ï','î','ì','Ä','Å', 41 | 'É','æ','Æ','ô','ö','ò','û','ù','ÿ','Ö','Ü','¢','£','¥','₧','ƒ', 42 | 'á','í','ó','ú','ñ','Ñ','ª','º','¿','⌐','¬','½','¼','¡','«','»', 43 | '░','▒','▓','│','┤','╡','╢','╖','╕','╣','║','╗','╝','╜','╛','┐', 44 | '└','┴','┬','├','─','┼','╞','╟','╚','╔','╩','╦','╠','═','╬','╧', 45 | '╨','╤','╥','╙','╘','╒','╓','╫','╪','┘','┌','█','▄','▌','▐','▀', 46 | 'α','ß','Γ','π','Σ','σ','µ','τ','Φ','Θ','Ω','δ','∞','φ','ε','∩', 47 | '≡','±','≥','≤','⌠','⌡','÷','≈','°','∙','·','√','ⁿ','²','■','ff', 48 | ]; 49 | 50 | #[rustfmt::skip] 51 | pub const CP1047: [char; 256] = [ 52 | // 53 | // Copyright (c) 2016,2024 IBM Corporation and other Contributors. 54 | // 55 | // All rights reserved. This program and the accompanying materials 56 | // are made available under the terms of the Eclipse Public License v1.0 57 | // which accompanies this distribution, and is available at 58 | // http://www.eclipse.org/legal/epl-v10.html 59 | // 60 | // Contributors: 61 | // Mark Taylor - Initial Contribution 62 | // 63 | 64 | // ref1 https://github.com/ibm-messaging/mq-smf-csv/blob/master/src/smfConv.c 65 | // ref2 https://web.archive.org/web/20150607033635/http://www-01.ibm.com/software/globalization/cp/cp01047.html 66 | '.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.', 67 | '.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.', 68 | '.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.', 69 | '.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.', 70 | ' ','.','.','.','.','.','.','.','.','.','$','.','<','(','+','|', 71 | '&','.','.','.','.','.','.','.','.','.','!','$','*',')',';','.', 72 | '-','/','.','.','.','.','.','.','.','.','.',',','%','_','>','?', 73 | '.','.','.','.','.','.','.','.','.','.',':','#','@','\'','=','.', 74 | '.','a','b','c','d','e','f','g','h','i','.','{','.','(','+','.', 75 | '.','j','k','l','m','n','o','p','q','r','.','}','.',')','.','.', 76 | '.','~','s','t','u','v','w','x','y','z','.','.','.','.','.','.', 77 | '.','.','.','.','.','.','.','.','.','.','[',']','.','.','.','-', 78 | '{','A','B','C','D','E','F','G','H','I','.','.','.','.','.','.', 79 | '}','J','K','L','M','N','O','P','Q','R','.','.','.','.','.','.', 80 | '.','.','S','T','U','V','W','X','Y','Z','.','.','.','.','.','.', 81 | '0','1','2','3','4','5','6','7','8','9','.','.','.','.','.','.' 82 | ]; 83 | -------------------------------------------------------------------------------- /src/input.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io::{self, copy, sink, Read, Seek, SeekFrom}; 3 | 4 | pub enum Input<'a> { 5 | File(fs::File), 6 | Stdin(io::StdinLock<'a>), 7 | } 8 | 9 | impl Read for Input<'_> { 10 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 11 | match *self { 12 | Input::File(ref mut file) => file.read(buf), 13 | Input::Stdin(ref mut stdin) => stdin.read(buf), 14 | } 15 | } 16 | } 17 | 18 | impl Seek for Input<'_> { 19 | fn seek(&mut self, pos: SeekFrom) -> io::Result { 20 | fn try_skip(reader: R, pos: SeekFrom, err_desc: &'static str) -> io::Result 21 | where 22 | R: Read, 23 | { 24 | let cant_seek_abs_err = || Err(io::Error::new(io::ErrorKind::Other, err_desc)); 25 | 26 | let offset = match pos { 27 | SeekFrom::Current(o) => u64::try_from(o).or_else(|_e| cant_seek_abs_err())?, 28 | SeekFrom::Start(_) | SeekFrom::End(_) => cant_seek_abs_err()?, 29 | }; 30 | 31 | copy(&mut reader.take(offset), &mut sink()) 32 | } 33 | 34 | match *self { 35 | Input::File(ref mut file) => { 36 | let seek_res = file.seek(pos); 37 | if let Err(Some(libc::ESPIPE)) = seek_res.as_ref().map_err(|err| err.raw_os_error()) 38 | { 39 | try_skip( 40 | file, 41 | pos, 42 | "Pipes only support seeking forward with a relative offset", 43 | ) 44 | } else { 45 | seek_res 46 | } 47 | } 48 | Input::Stdin(ref mut stdin) => try_skip( 49 | stdin, 50 | pos, 51 | "STDIN only supports seeking forward with a relative offset", 52 | ), 53 | } 54 | } 55 | } 56 | 57 | impl<'a> Input<'a> { 58 | pub fn into_inner(self) -> Box { 59 | match self { 60 | Input::File(file) => Box::new(file), 61 | Input::Stdin(stdin) => Box::new(stdin), 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod colors; 2 | pub(crate) mod input; 3 | 4 | pub use colors::*; 5 | pub use input::*; 6 | 7 | use std::io::{self, BufReader, Read, Write}; 8 | 9 | use clap::ValueEnum; 10 | 11 | pub enum Base { 12 | Binary, 13 | Octal, 14 | Decimal, 15 | Hexadecimal, 16 | } 17 | 18 | #[derive(Copy, Clone)] 19 | pub enum ByteCategory { 20 | Null, 21 | AsciiPrintable, 22 | AsciiWhitespace, 23 | AsciiOther, 24 | NonAscii, 25 | } 26 | 27 | #[derive(Copy, Clone, Debug, Default, ValueEnum)] 28 | #[non_exhaustive] 29 | pub enum CharacterTable { 30 | /// Show printable ASCII characters as-is, '⋄' for NULL bytes, ' ' for 31 | /// space, '_' for other ASCII whitespace, '•' for other ASCII characters, 32 | /// and '×' for non-ASCII bytes. 33 | #[default] 34 | Default, 35 | 36 | /// Show printable ASCII as-is, ' ' for space, '.' for everything else. 37 | Ascii, 38 | 39 | /// Show printable EBCDIC as-is, ' ' for space, '.' for everything else. 40 | #[value(name = "codepage-1047")] 41 | CP1047, 42 | 43 | /// Uses code page 437 (for non-ASCII bytes). 44 | #[value(name = "codepage-437")] 45 | CP437, 46 | } 47 | 48 | #[derive(Copy, Clone, Debug, Default, ValueEnum)] 49 | pub enum Endianness { 50 | /// Print out groups in little-endian format. 51 | Little, 52 | 53 | /// Print out groups in big-endian format. 54 | #[default] 55 | Big, 56 | } 57 | 58 | #[derive(PartialEq)] 59 | enum Squeezer { 60 | Print, 61 | Delete, 62 | Ignore, 63 | Disabled, 64 | } 65 | 66 | #[derive(Copy, Clone)] 67 | struct Byte(u8); 68 | 69 | impl Byte { 70 | fn category(self) -> ByteCategory { 71 | if self.0 == 0x00 { 72 | ByteCategory::Null 73 | } else if self.0.is_ascii_graphic() { 74 | ByteCategory::AsciiPrintable 75 | } else if self.0.is_ascii_whitespace() { 76 | ByteCategory::AsciiWhitespace 77 | } else if self.0.is_ascii() { 78 | ByteCategory::AsciiOther 79 | } else { 80 | ByteCategory::NonAscii 81 | } 82 | } 83 | 84 | fn color(self) -> &'static [u8] { 85 | use crate::ByteCategory::*; 86 | match self.category() { 87 | Null => COLOR_NULL, 88 | AsciiPrintable => COLOR_ASCII_PRINTABLE, 89 | AsciiWhitespace => COLOR_ASCII_WHITESPACE, 90 | AsciiOther => COLOR_ASCII_OTHER, 91 | NonAscii => COLOR_NONASCII, 92 | } 93 | } 94 | 95 | fn as_char(self, character_table: CharacterTable) -> char { 96 | use crate::ByteCategory::*; 97 | match character_table { 98 | CharacterTable::Default => match self.category() { 99 | Null => '⋄', 100 | AsciiPrintable => self.0 as char, 101 | AsciiWhitespace if self.0 == 0x20 => ' ', 102 | AsciiWhitespace => '_', 103 | AsciiOther => '•', 104 | NonAscii => '×', 105 | }, 106 | CharacterTable::Ascii => match self.category() { 107 | Null => '.', 108 | AsciiPrintable => self.0 as char, 109 | AsciiWhitespace if self.0 == 0x20 => ' ', 110 | AsciiWhitespace => '.', 111 | AsciiOther => '.', 112 | NonAscii => '.', 113 | }, 114 | CharacterTable::CP1047 => CP1047[self.0 as usize], 115 | CharacterTable::CP437 => CP437[self.0 as usize], 116 | } 117 | } 118 | } 119 | 120 | struct BorderElements { 121 | left_corner: char, 122 | horizontal_line: char, 123 | column_separator: char, 124 | right_corner: char, 125 | } 126 | 127 | #[derive(Clone, Copy, Debug, Default, ValueEnum)] 128 | pub enum BorderStyle { 129 | /// Draw a border with Unicode characters. 130 | #[default] 131 | Unicode, 132 | 133 | /// Draw a border with ASCII characters. 134 | Ascii, 135 | 136 | /// Do not draw a border at all. 137 | None, 138 | } 139 | 140 | impl BorderStyle { 141 | fn header_elems(&self) -> Option { 142 | match self { 143 | BorderStyle::Unicode => Some(BorderElements { 144 | left_corner: '┌', 145 | horizontal_line: '─', 146 | column_separator: '┬', 147 | right_corner: '┐', 148 | }), 149 | BorderStyle::Ascii => Some(BorderElements { 150 | left_corner: '+', 151 | horizontal_line: '-', 152 | column_separator: '+', 153 | right_corner: '+', 154 | }), 155 | BorderStyle::None => None, 156 | } 157 | } 158 | 159 | fn footer_elems(&self) -> Option { 160 | match self { 161 | BorderStyle::Unicode => Some(BorderElements { 162 | left_corner: '└', 163 | horizontal_line: '─', 164 | column_separator: '┴', 165 | right_corner: '┘', 166 | }), 167 | BorderStyle::Ascii => Some(BorderElements { 168 | left_corner: '+', 169 | horizontal_line: '-', 170 | column_separator: '+', 171 | right_corner: '+', 172 | }), 173 | BorderStyle::None => None, 174 | } 175 | } 176 | 177 | fn outer_sep(&self) -> char { 178 | match self { 179 | BorderStyle::Unicode => '│', 180 | BorderStyle::Ascii => '|', 181 | BorderStyle::None => ' ', 182 | } 183 | } 184 | 185 | fn inner_sep(&self) -> char { 186 | match self { 187 | BorderStyle::Unicode => '┊', 188 | BorderStyle::Ascii => '|', 189 | BorderStyle::None => ' ', 190 | } 191 | } 192 | } 193 | 194 | pub struct PrinterBuilder<'a, Writer: Write> { 195 | writer: &'a mut Writer, 196 | show_color: bool, 197 | show_char_panel: bool, 198 | show_position_panel: bool, 199 | border_style: BorderStyle, 200 | use_squeeze: bool, 201 | panels: u64, 202 | group_size: u8, 203 | base: Base, 204 | endianness: Endianness, 205 | character_table: CharacterTable, 206 | } 207 | 208 | impl<'a, Writer: Write> PrinterBuilder<'a, Writer> { 209 | pub fn new(writer: &'a mut Writer) -> Self { 210 | PrinterBuilder { 211 | writer, 212 | show_color: true, 213 | show_char_panel: true, 214 | show_position_panel: true, 215 | border_style: BorderStyle::Unicode, 216 | use_squeeze: true, 217 | panels: 2, 218 | group_size: 1, 219 | base: Base::Hexadecimal, 220 | endianness: Endianness::Big, 221 | character_table: CharacterTable::Default, 222 | } 223 | } 224 | 225 | pub fn show_color(mut self, show_color: bool) -> Self { 226 | self.show_color = show_color; 227 | self 228 | } 229 | 230 | pub fn show_char_panel(mut self, show_char_panel: bool) -> Self { 231 | self.show_char_panel = show_char_panel; 232 | self 233 | } 234 | 235 | pub fn show_position_panel(mut self, show_position_panel: bool) -> Self { 236 | self.show_position_panel = show_position_panel; 237 | self 238 | } 239 | 240 | pub fn with_border_style(mut self, border_style: BorderStyle) -> Self { 241 | self.border_style = border_style; 242 | self 243 | } 244 | 245 | pub fn enable_squeezing(mut self, enable: bool) -> Self { 246 | self.use_squeeze = enable; 247 | self 248 | } 249 | 250 | pub fn num_panels(mut self, num: u64) -> Self { 251 | self.panels = num; 252 | self 253 | } 254 | 255 | pub fn group_size(mut self, num: u8) -> Self { 256 | self.group_size = num; 257 | self 258 | } 259 | 260 | pub fn with_base(mut self, base: Base) -> Self { 261 | self.base = base; 262 | self 263 | } 264 | 265 | pub fn endianness(mut self, endianness: Endianness) -> Self { 266 | self.endianness = endianness; 267 | self 268 | } 269 | 270 | pub fn character_table(mut self, character_table: CharacterTable) -> Self { 271 | self.character_table = character_table; 272 | self 273 | } 274 | 275 | pub fn build(self) -> Printer<'a, Writer> { 276 | Printer::new( 277 | self.writer, 278 | self.show_color, 279 | self.show_char_panel, 280 | self.show_position_panel, 281 | self.border_style, 282 | self.use_squeeze, 283 | self.panels, 284 | self.group_size, 285 | self.base, 286 | self.endianness, 287 | self.character_table, 288 | ) 289 | } 290 | } 291 | 292 | pub struct Printer<'a, Writer: Write> { 293 | idx: u64, 294 | /// the buffer containing all the bytes in a line for character printing 295 | line_buf: Vec, 296 | writer: &'a mut Writer, 297 | show_char_panel: bool, 298 | show_position_panel: bool, 299 | show_color: bool, 300 | curr_color: Option<&'static [u8]>, 301 | border_style: BorderStyle, 302 | byte_hex_panel: Vec, 303 | byte_char_panel: Vec, 304 | // same as previous but in Fixed(242) gray color, for position panel 305 | byte_hex_panel_g: Vec, 306 | squeezer: Squeezer, 307 | display_offset: u64, 308 | /// The number of panels to draw. 309 | panels: u64, 310 | squeeze_byte: usize, 311 | /// The number of octets per group. 312 | group_size: u8, 313 | /// The number of digits used to write the base. 314 | base_digits: u8, 315 | /// Whether to show groups in little or big endian format. 316 | endianness: Endianness, 317 | } 318 | 319 | impl<'a, Writer: Write> Printer<'a, Writer> { 320 | fn new( 321 | writer: &'a mut Writer, 322 | show_color: bool, 323 | show_char_panel: bool, 324 | show_position_panel: bool, 325 | border_style: BorderStyle, 326 | use_squeeze: bool, 327 | panels: u64, 328 | group_size: u8, 329 | base: Base, 330 | endianness: Endianness, 331 | character_table: CharacterTable, 332 | ) -> Printer<'a, Writer> { 333 | Printer { 334 | idx: 0, 335 | line_buf: vec![0x0; 8 * panels as usize], 336 | writer, 337 | show_char_panel, 338 | show_position_panel, 339 | show_color, 340 | curr_color: None, 341 | border_style, 342 | byte_hex_panel: (0u8..=u8::MAX) 343 | .map(|i| match base { 344 | Base::Binary => format!("{i:08b}"), 345 | Base::Octal => format!("{i:03o}"), 346 | Base::Decimal => format!("{i:03}"), 347 | Base::Hexadecimal => format!("{i:02x}"), 348 | }) 349 | .collect(), 350 | byte_char_panel: (0u8..=u8::MAX) 351 | .map(|i| format!("{}", Byte(i).as_char(character_table))) 352 | .collect(), 353 | byte_hex_panel_g: (0u8..=u8::MAX).map(|i| format!("{i:02x}")).collect(), 354 | squeezer: if use_squeeze { 355 | Squeezer::Ignore 356 | } else { 357 | Squeezer::Disabled 358 | }, 359 | display_offset: 0, 360 | panels, 361 | squeeze_byte: 0x00, 362 | group_size, 363 | base_digits: match base { 364 | Base::Binary => 8, 365 | Base::Octal => 3, 366 | Base::Decimal => 3, 367 | Base::Hexadecimal => 2, 368 | }, 369 | endianness, 370 | } 371 | } 372 | 373 | pub fn display_offset(&mut self, display_offset: u64) -> &mut Self { 374 | self.display_offset = display_offset; 375 | self 376 | } 377 | 378 | fn panel_sz(&self) -> usize { 379 | // add one to include the trailing space of a group 380 | let group_sz = self.base_digits as usize * self.group_size as usize + 1; 381 | let group_per_panel = 8 / self.group_size as usize; 382 | // add one to include the leading space 383 | 1 + group_sz * group_per_panel 384 | } 385 | 386 | fn write_border(&mut self, border_elements: BorderElements) -> io::Result<()> { 387 | let h = border_elements.horizontal_line; 388 | let c = border_elements.column_separator; 389 | let l = border_elements.left_corner; 390 | let r = border_elements.right_corner; 391 | let h8 = h.to_string().repeat(8); 392 | let h_repeat = h.to_string().repeat(self.panel_sz()); 393 | 394 | if self.show_position_panel { 395 | write!(self.writer, "{l}{h8}{c}")?; 396 | } else { 397 | write!(self.writer, "{l}")?; 398 | } 399 | 400 | for _ in 0..self.panels - 1 { 401 | write!(self.writer, "{h_repeat}{c}")?; 402 | } 403 | if self.show_char_panel { 404 | write!(self.writer, "{h_repeat}{c}")?; 405 | } else { 406 | write!(self.writer, "{h_repeat}")?; 407 | } 408 | 409 | if self.show_char_panel { 410 | for _ in 0..self.panels - 1 { 411 | write!(self.writer, "{h8}{c}")?; 412 | } 413 | writeln!(self.writer, "{h8}{r}")?; 414 | } else { 415 | writeln!(self.writer, "{r}")?; 416 | } 417 | 418 | Ok(()) 419 | } 420 | 421 | pub fn print_header(&mut self) -> io::Result<()> { 422 | if let Some(e) = self.border_style.header_elems() { 423 | self.write_border(e)? 424 | } 425 | Ok(()) 426 | } 427 | 428 | pub fn print_footer(&mut self) -> io::Result<()> { 429 | if let Some(e) = self.border_style.footer_elems() { 430 | self.write_border(e)? 431 | } 432 | Ok(()) 433 | } 434 | 435 | fn print_position_panel(&mut self) -> io::Result<()> { 436 | self.writer.write_all( 437 | self.border_style 438 | .outer_sep() 439 | .encode_utf8(&mut [0; 4]) 440 | .as_bytes(), 441 | )?; 442 | if self.show_color { 443 | self.writer.write_all(COLOR_OFFSET)?; 444 | } 445 | if self.show_position_panel { 446 | match self.squeezer { 447 | Squeezer::Print => { 448 | self.writer.write_all(b"*")?; 449 | if self.show_color { 450 | self.writer.write_all(COLOR_RESET)?; 451 | } 452 | self.writer.write_all(b" ")?; 453 | } 454 | Squeezer::Ignore | Squeezer::Disabled | Squeezer::Delete => { 455 | let byte_index: [u8; 8] = (self.idx + self.display_offset).to_be_bytes(); 456 | let mut i = 0; 457 | while byte_index[i] == 0x0 && i < 4 { 458 | i += 1; 459 | } 460 | for &byte in byte_index.iter().skip(i) { 461 | self.writer 462 | .write_all(self.byte_hex_panel_g[byte as usize].as_bytes())?; 463 | } 464 | if self.show_color { 465 | self.writer.write_all(COLOR_RESET)?; 466 | } 467 | } 468 | } 469 | self.writer.write_all( 470 | self.border_style 471 | .outer_sep() 472 | .encode_utf8(&mut [0; 4]) 473 | .as_bytes(), 474 | )?; 475 | } 476 | Ok(()) 477 | } 478 | 479 | fn print_char(&mut self, i: u64) -> io::Result<()> { 480 | match self.squeezer { 481 | Squeezer::Print | Squeezer::Delete => self.writer.write_all(b" ")?, 482 | Squeezer::Ignore | Squeezer::Disabled => { 483 | if let Some(&b) = self.line_buf.get(i as usize) { 484 | if self.show_color && self.curr_color != Some(Byte(b).color()) { 485 | self.writer.write_all(Byte(b).color())?; 486 | self.curr_color = Some(Byte(b).color()); 487 | } 488 | self.writer 489 | .write_all(self.byte_char_panel[b as usize].as_bytes())?; 490 | } else { 491 | self.squeezer = Squeezer::Print; 492 | } 493 | } 494 | } 495 | if i == 8 * self.panels - 1 { 496 | if self.show_color { 497 | self.writer.write_all(COLOR_RESET)?; 498 | self.curr_color = None; 499 | } 500 | self.writer.write_all( 501 | self.border_style 502 | .outer_sep() 503 | .encode_utf8(&mut [0; 4]) 504 | .as_bytes(), 505 | )?; 506 | } else if i % 8 == 7 { 507 | if self.show_color { 508 | self.writer.write_all(COLOR_RESET)?; 509 | self.curr_color = None; 510 | } 511 | self.writer.write_all( 512 | self.border_style 513 | .inner_sep() 514 | .encode_utf8(&mut [0; 4]) 515 | .as_bytes(), 516 | )?; 517 | } 518 | 519 | Ok(()) 520 | } 521 | 522 | pub fn print_char_panel(&mut self) -> io::Result<()> { 523 | for i in 0..self.line_buf.len() { 524 | self.print_char(i as u64)?; 525 | } 526 | Ok(()) 527 | } 528 | 529 | fn print_byte(&mut self, i: usize, b: u8) -> io::Result<()> { 530 | match self.squeezer { 531 | Squeezer::Print => { 532 | if !self.show_position_panel && i == 0 { 533 | if self.show_color { 534 | self.writer.write_all(COLOR_OFFSET)?; 535 | } 536 | self.writer 537 | .write_all(self.byte_char_panel[b'*' as usize].as_bytes())?; 538 | if self.show_color { 539 | self.writer.write_all(COLOR_RESET)?; 540 | } 541 | } else if i % (self.group_size as usize) == 0 { 542 | self.writer.write_all(b" ")?; 543 | } 544 | for _ in 0..self.base_digits { 545 | self.writer.write_all(b" ")?; 546 | } 547 | } 548 | Squeezer::Delete => self.writer.write_all(b" ")?, 549 | Squeezer::Ignore | Squeezer::Disabled => { 550 | if i % (self.group_size as usize) == 0 { 551 | self.writer.write_all(b" ")?; 552 | } 553 | if self.show_color && self.curr_color != Some(Byte(b).color()) { 554 | self.writer.write_all(Byte(b).color())?; 555 | self.curr_color = Some(Byte(b).color()); 556 | } 557 | self.writer 558 | .write_all(self.byte_hex_panel[b as usize].as_bytes())?; 559 | } 560 | } 561 | // byte is last in panel 562 | if i % 8 == 7 { 563 | if self.show_color { 564 | self.curr_color = None; 565 | self.writer.write_all(COLOR_RESET)?; 566 | } 567 | self.writer.write_all(b" ")?; 568 | // byte is last in last panel 569 | if i as u64 % (8 * self.panels) == 8 * self.panels - 1 { 570 | self.writer.write_all( 571 | self.border_style 572 | .outer_sep() 573 | .encode_utf8(&mut [0; 4]) 574 | .as_bytes(), 575 | )?; 576 | } else { 577 | self.writer.write_all( 578 | self.border_style 579 | .inner_sep() 580 | .encode_utf8(&mut [0; 4]) 581 | .as_bytes(), 582 | )?; 583 | } 584 | } 585 | Ok(()) 586 | } 587 | 588 | fn reorder_buffer_to_little_endian(&self, buf: &mut [u8]) { 589 | let n = buf.len(); 590 | let group_sz = self.group_size as usize; 591 | 592 | for idx in (0..n).step_by(group_sz) { 593 | let remaining = n - idx; 594 | let total = remaining.min(group_sz); 595 | 596 | buf[idx..idx + total].reverse(); 597 | } 598 | } 599 | 600 | pub fn print_bytes(&mut self) -> io::Result<()> { 601 | let mut buf = self.line_buf.clone(); 602 | 603 | if matches!(self.endianness, Endianness::Little) { 604 | self.reorder_buffer_to_little_endian(&mut buf); 605 | }; 606 | 607 | for (i, &b) in buf.iter().enumerate() { 608 | self.print_byte(i, b)?; 609 | } 610 | Ok(()) 611 | } 612 | 613 | /// Loop through the given `Reader`, printing until the `Reader` buffer 614 | /// is exhausted. 615 | pub fn print_all(&mut self, reader: Reader) -> io::Result<()> { 616 | let mut is_empty = true; 617 | 618 | let mut buf = BufReader::new(reader); 619 | 620 | let leftover = loop { 621 | // read a maximum of 8 * self.panels bytes from the reader 622 | if let Ok(n) = buf.read(&mut self.line_buf) { 623 | if n > 0 && n < 8 * self.panels as usize { 624 | // if less are read, that indicates end of file after 625 | if is_empty { 626 | self.print_header()?; 627 | is_empty = false; 628 | } 629 | let mut leftover = n; 630 | // loop until input is ceased 631 | if let Some(s) = loop { 632 | if let Ok(n) = buf.read(&mut self.line_buf[leftover..]) { 633 | leftover += n; 634 | // there is no more input being read 635 | if n == 0 { 636 | self.line_buf.resize(leftover, 0); 637 | break Some(leftover); 638 | } 639 | // amount read has exceeded line buffer 640 | if leftover >= 8 * self.panels as usize { 641 | break None; 642 | } 643 | } 644 | } { 645 | break Some(s); 646 | }; 647 | } else if n == 0 { 648 | // if no bytes are read, that indicates end of file 649 | if self.squeezer == Squeezer::Delete { 650 | // empty the last line when ending is squeezed 651 | self.line_buf.clear(); 652 | break Some(0); 653 | } 654 | break None; 655 | } 656 | } 657 | if is_empty { 658 | self.print_header()?; 659 | } 660 | 661 | // squeeze is active, check if the line is the same 662 | // skip print if still squeezed, otherwise print and deactivate squeeze 663 | if matches!(self.squeezer, Squeezer::Print | Squeezer::Delete) { 664 | if self 665 | .line_buf 666 | .chunks_exact(std::mem::size_of::()) 667 | .all(|w| usize::from_ne_bytes(w.try_into().unwrap()) == self.squeeze_byte) 668 | { 669 | if self.squeezer == Squeezer::Delete { 670 | self.idx += 8 * self.panels; 671 | continue; 672 | } 673 | } else { 674 | self.squeezer = Squeezer::Ignore; 675 | } 676 | } 677 | 678 | // print the line 679 | self.print_position_panel()?; 680 | self.print_bytes()?; 681 | if self.show_char_panel { 682 | self.print_char_panel()?; 683 | } 684 | self.writer.write_all(b"\n")?; 685 | 686 | if is_empty { 687 | self.writer.flush()?; 688 | is_empty = false; 689 | } 690 | 691 | // increment index to next line 692 | self.idx += 8 * self.panels; 693 | 694 | // change from print to delete if squeeze is still active 695 | if self.squeezer == Squeezer::Print { 696 | self.squeezer = Squeezer::Delete; 697 | } 698 | 699 | // repeat the first byte in the line until it's a usize 700 | // compare that usize with each usize chunk in the line 701 | // if they are all the same, change squeezer to print 702 | let repeat_byte = (self.line_buf[0] as usize) * (usize::MAX / 255); 703 | if !matches!(self.squeezer, Squeezer::Disabled | Squeezer::Delete) 704 | && self 705 | .line_buf 706 | .chunks_exact(std::mem::size_of::()) 707 | .all(|w| usize::from_ne_bytes(w.try_into().unwrap()) == repeat_byte) 708 | { 709 | self.squeezer = Squeezer::Print; 710 | self.squeeze_byte = repeat_byte; 711 | }; 712 | }; 713 | 714 | // special ending 715 | 716 | if is_empty { 717 | self.base_digits = 2; 718 | self.print_header()?; 719 | if self.show_position_panel { 720 | write!(self.writer, "{0:9}", "│")?; 721 | } 722 | write!( 723 | self.writer, 724 | "{0:2}{1:2$}{0}{0:>3$}", 725 | "│", 726 | "No content", 727 | self.panel_sz() - 1, 728 | self.panel_sz() + 1, 729 | )?; 730 | if self.show_char_panel { 731 | write!(self.writer, "{0:>9}{0:>9}", "│")?; 732 | } 733 | writeln!(self.writer)?; 734 | } else if let Some(n) = leftover { 735 | // last line is incomplete 736 | self.squeezer = Squeezer::Ignore; 737 | self.print_position_panel()?; 738 | self.print_bytes()?; 739 | self.squeezer = Squeezer::Print; 740 | for i in n..8 * self.panels as usize { 741 | self.print_byte(i, 0)?; 742 | } 743 | if self.show_char_panel { 744 | self.squeezer = Squeezer::Ignore; 745 | self.print_char_panel()?; 746 | self.squeezer = Squeezer::Print; 747 | for i in n..8 * self.panels as usize { 748 | self.print_char(i as u64)?; 749 | } 750 | } 751 | self.writer.write_all(b"\n")?; 752 | } 753 | 754 | self.print_footer()?; 755 | 756 | self.writer.flush()?; 757 | 758 | Ok(()) 759 | } 760 | } 761 | 762 | #[cfg(test)] 763 | mod tests { 764 | use std::io; 765 | use std::str; 766 | 767 | use super::*; 768 | 769 | fn assert_print_all_output(input: Reader, expected_string: String) { 770 | let mut output = vec![]; 771 | let mut printer = Printer::new( 772 | &mut output, 773 | false, 774 | true, 775 | true, 776 | BorderStyle::Unicode, 777 | true, 778 | 2, 779 | 1, 780 | Base::Hexadecimal, 781 | Endianness::Big, 782 | CharacterTable::Default, 783 | ); 784 | 785 | printer.print_all(input).unwrap(); 786 | 787 | let actual_string: &str = str::from_utf8(&output).unwrap(); 788 | assert_eq!(actual_string, expected_string,) 789 | } 790 | 791 | #[test] 792 | fn empty_file_passes() { 793 | let input = io::empty(); 794 | let expected_string = "\ 795 | ┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐ 796 | │ │ No content │ │ │ │ 797 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘ 798 | " 799 | .to_owned(); 800 | assert_print_all_output(input, expected_string); 801 | } 802 | 803 | #[test] 804 | fn short_input_passes() { 805 | let input = io::Cursor::new(b"spam"); 806 | let expected_string = "\ 807 | ┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐ 808 | │00000000│ 73 70 61 6d ┊ │spam ┊ │ 809 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘ 810 | " 811 | .to_owned(); 812 | assert_print_all_output(input, expected_string); 813 | } 814 | 815 | #[test] 816 | fn display_offset() { 817 | let input = io::Cursor::new(b"spamspamspamspamspam"); 818 | let expected_string = "\ 819 | ┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐ 820 | │deadbeef│ 73 70 61 6d 73 70 61 6d ┊ 73 70 61 6d 73 70 61 6d │spamspam┊spamspam│ 821 | │deadbeff│ 73 70 61 6d ┊ │spam ┊ │ 822 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘ 823 | " 824 | .to_owned(); 825 | 826 | let mut output = vec![]; 827 | let mut printer: Printer> = Printer::new( 828 | &mut output, 829 | false, 830 | true, 831 | true, 832 | BorderStyle::Unicode, 833 | true, 834 | 2, 835 | 1, 836 | Base::Hexadecimal, 837 | Endianness::Big, 838 | CharacterTable::Default, 839 | ); 840 | printer.display_offset(0xdeadbeef); 841 | 842 | printer.print_all(input).unwrap(); 843 | 844 | let actual_string: &str = str::from_utf8(&output).unwrap(); 845 | assert_eq!(actual_string, expected_string) 846 | } 847 | 848 | #[test] 849 | fn multiple_panels() { 850 | let input = io::Cursor::new(b"supercalifragilisticexpialidocioussupercalifragilisticexpialidocioussupercalifragilisticexpialidocious"); 851 | let expected_string = "\ 852 | ┌────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬────────┬────────┬────────┬────────┐ 853 | │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│ 854 | │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│ 855 | │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│ 856 | │00000060│ 6f 63 69 6f 75 73 ┊ ┊ ┊ │ocious ┊ ┊ ┊ │ 857 | └────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴────────┴────────┴────────┴────────┘ 858 | " 859 | .to_owned(); 860 | 861 | let mut output = vec![]; 862 | let mut printer: Printer> = Printer::new( 863 | &mut output, 864 | false, 865 | true, 866 | true, 867 | BorderStyle::Unicode, 868 | true, 869 | 4, 870 | 1, 871 | Base::Hexadecimal, 872 | Endianness::Big, 873 | CharacterTable::Default, 874 | ); 875 | 876 | printer.print_all(input).unwrap(); 877 | 878 | let actual_string: &str = str::from_utf8(&output).unwrap(); 879 | assert_eq!(actual_string, expected_string) 880 | } 881 | 882 | #[test] 883 | fn squeeze_works() { 884 | 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"); 885 | let expected_string = "\ 886 | ┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐ 887 | │00000000│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│ 888 | │* │ ┊ │ ┊ │ 889 | │00000020│ 00 ┊ │⋄ ┊ │ 890 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘ 891 | " 892 | .to_owned(); 893 | assert_print_all_output(input, expected_string); 894 | } 895 | 896 | #[test] 897 | fn squeeze_nonzero() { 898 | let input = io::Cursor::new(b"000000000000000000000000000000000"); 899 | let expected_string = "\ 900 | ┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐ 901 | │00000000│ 30 30 30 30 30 30 30 30 ┊ 30 30 30 30 30 30 30 30 │00000000┊00000000│ 902 | │* │ ┊ │ ┊ │ 903 | │00000020│ 30 ┊ │0 ┊ │ 904 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘ 905 | " 906 | .to_owned(); 907 | assert_print_all_output(input, expected_string); 908 | } 909 | 910 | #[test] 911 | fn squeeze_multiple_panels() { 912 | let input = io::Cursor::new(b"0000000000000000000000000000000000000000000000000"); 913 | let expected_string = "\ 914 | ┌────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬────────┬────────┬────────┐ 915 | │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│ 916 | │* │ ┊ ┊ │ ┊ ┊ │ 917 | │00000030│ 30 ┊ ┊ │0 ┊ ┊ │ 918 | └────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴────────┴────────┴────────┘ 919 | " 920 | .to_owned(); 921 | 922 | let mut output = vec![]; 923 | let mut printer: Printer> = Printer::new( 924 | &mut output, 925 | false, 926 | true, 927 | true, 928 | BorderStyle::Unicode, 929 | true, 930 | 3, 931 | 1, 932 | Base::Hexadecimal, 933 | Endianness::Big, 934 | CharacterTable::Default, 935 | ); 936 | 937 | printer.print_all(input).unwrap(); 938 | 939 | let actual_string: &str = str::from_utf8(&output).unwrap(); 940 | assert_eq!(actual_string, expected_string) 941 | } 942 | 943 | // issue#238 944 | #[test] 945 | fn display_offset_in_last_line() { 946 | let input = io::Cursor::new(b"AAAAAAAAAAAAAAAACCCC"); 947 | let expected_string = "\ 948 | ┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐ 949 | │00000000│ 41 41 41 41 41 41 41 41 ┊ 41 41 41 41 41 41 41 41 │AAAAAAAA┊AAAAAAAA│ 950 | │00000010│ 43 43 43 43 ┊ │CCCC ┊ │ 951 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘ 952 | " 953 | .to_owned(); 954 | assert_print_all_output(input, expected_string); 955 | } 956 | } 957 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{self, prelude::*, BufWriter, SeekFrom}; 3 | use std::num::{NonZeroI64, NonZeroU64}; 4 | use std::path::PathBuf; 5 | 6 | use clap::builder::ArgPredicate; 7 | use clap::{ArgAction, Parser, ValueEnum}; 8 | 9 | use anyhow::{anyhow, bail, Context, Result}; 10 | 11 | use const_format::formatcp; 12 | 13 | use thiserror::Error as ThisError; 14 | 15 | use terminal_size::terminal_size; 16 | 17 | use hexyl::{Base, BorderStyle, CharacterTable, Endianness, Input, PrinterBuilder}; 18 | 19 | use hexyl::{ 20 | COLOR_ASCII_OTHER, COLOR_ASCII_PRINTABLE, COLOR_ASCII_WHITESPACE, COLOR_NONASCII, COLOR_NULL, 21 | COLOR_RESET, 22 | }; 23 | 24 | #[cfg(test)] 25 | mod tests; 26 | 27 | const DEFAULT_BLOCK_SIZE: i64 = 512; 28 | 29 | const LENGTH_HELP_TEXT: &str = "Only read N bytes from the input. The N argument can also include \ 30 | a unit with a decimal prefix (kB, MB, ..) or binary prefix (kiB, \ 31 | MiB, ..), or can be specified using a hex number. The short \ 32 | option '-l' can be used as an alias. 33 | Examples: --length=64, --length=4KiB, --length=0xff"; 34 | 35 | const SKIP_HELP_TEXT: &str = "Skip the first N bytes of the input. The N argument can also \ 36 | include a unit (see `--length` for details). 37 | A negative value is valid and will seek from the end of the file."; 38 | 39 | const BLOCK_SIZE_HELP_TEXT: &str = "Sets the size of the `block` unit to SIZE. 40 | Examples: --block-size=1024, --block-size=4kB"; 41 | 42 | const DISPLAY_OFFSET_HELP_TEXT: &str = "Add N bytes to the displayed file position. The N \ 43 | argument can also include a unit (see `--length` for \ 44 | details). 45 | A negative value is valid and calculates an offset relative to the end of the file."; 46 | 47 | const TERMINAL_WIDTH_HELP_TEXT: &str = "Sets the number of terminal columns to be displayed. 48 | Since the terminal width may not be an evenly divisible by the width per hex data column, this \ 49 | will use the greatest number of hex data panels that can \ 50 | fit in the requested width but still leave some space to \ 51 | the right. 52 | Cannot be used with other width-setting options."; 53 | 54 | #[derive(Debug, Parser)] 55 | #[command(version, about, max_term_width(90))] 56 | struct Opt { 57 | /// The file to display. If no FILE argument is given, read from STDIN. 58 | #[arg(value_name("FILE"))] 59 | file: Option, 60 | 61 | #[arg( 62 | help(LENGTH_HELP_TEXT), 63 | short('n'), 64 | long, 65 | visible_short_alias('c'), 66 | visible_alias("bytes"), 67 | short_alias('l'), 68 | value_name("N") 69 | )] 70 | length: Option, 71 | 72 | #[arg(help(SKIP_HELP_TEXT), short, long, value_name("N"))] 73 | skip: Option, 74 | 75 | #[arg( 76 | help(BLOCK_SIZE_HELP_TEXT), 77 | long, 78 | default_value(formatcp!("{DEFAULT_BLOCK_SIZE}")), 79 | value_name("SIZE") 80 | )] 81 | block_size: String, 82 | 83 | /// Displays all input data. Otherwise any number of groups of output lines 84 | /// which would be identical to the preceding group of lines, are replaced 85 | /// with a line comprised of a single asterisk. 86 | #[arg(short('v'), long)] 87 | no_squeezing: bool, 88 | 89 | /// When to use colors. 90 | #[arg( 91 | long, 92 | value_enum, 93 | default_value_t, 94 | value_name("WHEN"), 95 | default_value_if("plain", ArgPredicate::IsPresent, Some("never")) 96 | )] 97 | color: ColorWhen, 98 | 99 | /// Whether to draw a border. 100 | #[arg( 101 | long, 102 | value_enum, 103 | default_value_t, 104 | value_name("STYLE"), 105 | default_value_if("plain", ArgPredicate::IsPresent, Some("none")) 106 | )] 107 | border: BorderStyle, 108 | 109 | /// Display output with --no-characters, --no-position, --border=none, and 110 | /// --color=never. 111 | #[arg(short, long)] 112 | plain: bool, 113 | 114 | /// Do not show the character panel on the right. 115 | #[arg(long)] 116 | no_characters: bool, 117 | 118 | /// Show the character panel on the right. This is the default, unless 119 | /// --no-characters has been specified. 120 | #[arg( 121 | short('C'), 122 | long, 123 | action(ArgAction::SetTrue), 124 | overrides_with("no_characters") 125 | )] 126 | characters: (), 127 | 128 | /// Defines how bytes are mapped to characters. 129 | #[arg(long, value_enum, default_value_t, value_name("FORMAT"))] 130 | character_table: CharacterTable, 131 | 132 | /// Whether to display the position panel on the left. 133 | #[arg(short('P'), long)] 134 | no_position: bool, 135 | 136 | #[arg( 137 | help(DISPLAY_OFFSET_HELP_TEXT), 138 | short('o'), 139 | long, 140 | default_value("0"), 141 | value_name("N") 142 | )] 143 | display_offset: String, 144 | 145 | /// Sets the number of hex data panels to be displayed. `--panels=auto` will 146 | /// display the maximum number of hex data panels based on the current 147 | /// terminal width. By default, hexyl will show two panels, unless the 148 | /// terminal is not wide enough for that. 149 | #[arg(long, value_name("N"))] 150 | panels: Option, 151 | 152 | /// Number of bytes/octets that should be grouped together. You can use the 153 | /// '--endianness' option to control the ordering of the bytes within a 154 | /// group. '--groupsize' can be used as an alias (xxd-compatibility). 155 | #[arg( 156 | short('g'), 157 | long, 158 | value_enum, 159 | default_value_t, 160 | alias("groupsize"), 161 | value_name("N") 162 | )] 163 | group_size: GroupSize, 164 | 165 | /// Whether to print out groups in little-endian or big-endian format. This 166 | /// option only has an effect if the '--group-size' is larger than 1. '-e' 167 | /// can be used as an alias for '--endianness=little'. 168 | #[arg(long, value_enum, default_value_t, value_name("FORMAT"))] 169 | endianness: Endianness, 170 | 171 | /// An alias for '--endianness=little'. 172 | #[arg(short('e'), hide(true), overrides_with("endianness"))] 173 | little_endian_format: bool, 174 | 175 | /// Sets the base used for the bytes. The possible options are binary, 176 | /// octal, decimal, and hexadecimal. 177 | #[arg(short('b'), long, default_value("hexadecimal"), value_name("B"))] 178 | base: String, 179 | 180 | #[arg( 181 | help(TERMINAL_WIDTH_HELP_TEXT), 182 | long, 183 | value_name("N"), 184 | conflicts_with("panels") 185 | )] 186 | terminal_width: Option, 187 | 188 | /// Print a table showing how different types of bytes are colored. 189 | #[arg(long)] 190 | print_color_table: bool, 191 | } 192 | 193 | #[derive(Clone, Debug, Default, ValueEnum)] 194 | enum ColorWhen { 195 | /// Always use colorized output. 196 | #[default] 197 | Always, 198 | 199 | /// Only displays colors if the output goes to an interactive terminal. 200 | Auto, 201 | 202 | /// Do not use colorized output. 203 | Never, 204 | 205 | /// Override the NO_COLOR environment variable. 206 | Force, 207 | } 208 | 209 | #[derive(Clone, Debug, Default, ValueEnum)] 210 | enum GroupSize { 211 | /// Grouped together every byte/octet. 212 | #[default] 213 | #[value(name = "1")] 214 | One, 215 | 216 | /// Grouped together every 2 bytes/octets. 217 | #[value(name = "2")] 218 | Two, 219 | 220 | /// Grouped together every 4 bytes/octets. 221 | #[value(name = "4")] 222 | Four, 223 | 224 | /// Grouped together every 8 bytes/octets. 225 | #[value(name = "8")] 226 | Eight, 227 | } 228 | 229 | impl From for u8 { 230 | fn from(number: GroupSize) -> Self { 231 | match number { 232 | GroupSize::One => 1, 233 | GroupSize::Two => 2, 234 | GroupSize::Four => 4, 235 | GroupSize::Eight => 8, 236 | } 237 | } 238 | } 239 | 240 | fn run() -> Result<()> { 241 | let opt = Opt::parse(); 242 | 243 | if opt.print_color_table { 244 | return print_color_table().map_err(|e| anyhow!(e)); 245 | } 246 | 247 | let stdin = io::stdin(); 248 | 249 | let mut reader = match opt.file { 250 | Some(filename) => { 251 | if filename.is_dir() { 252 | bail!("'{}' is a directory.", filename.to_string_lossy()); 253 | } 254 | let file = File::open(&filename)?; 255 | 256 | Input::File(file) 257 | } 258 | None => Input::Stdin(stdin.lock()), 259 | }; 260 | 261 | if let Some(hex_number) = try_parse_as_hex_number(&opt.block_size) { 262 | return hex_number 263 | .map_err(|e| anyhow!(e)) 264 | .and_then(|x| { 265 | PositiveI64::new(x).ok_or_else(|| anyhow!("block size argument must be positive")) 266 | }) 267 | .map(|_| ()); 268 | } 269 | let (num, unit) = extract_num_and_unit_from(&opt.block_size)?; 270 | if let Unit::Block { custom_size: _ } = unit { 271 | return Err(anyhow!( 272 | "can not use 'block(s)' as a unit to specify block size" 273 | )); 274 | }; 275 | let block_size = num 276 | .checked_mul(unit.get_multiplier()) 277 | .ok_or_else(|| anyhow!(ByteOffsetParseError::UnitMultiplicationOverflow)) 278 | .and_then(|x| { 279 | PositiveI64::new(x).ok_or_else(|| anyhow!("block size argument must be positive")) 280 | })?; 281 | 282 | let skip_arg = opt 283 | .skip 284 | .as_ref() 285 | .map(|s| { 286 | parse_byte_offset(s, block_size).context(anyhow!( 287 | "failed to parse `--skip` arg {:?} as byte count", 288 | s 289 | )) 290 | }) 291 | .transpose()?; 292 | 293 | let skip_offset = if let Some(ByteOffset { kind, value }) = skip_arg { 294 | let value = value.into_inner(); 295 | reader 296 | .seek(match kind { 297 | ByteOffsetKind::ForwardFromBeginning | ByteOffsetKind::ForwardFromLastOffset => { 298 | SeekFrom::Current(value) 299 | } 300 | ByteOffsetKind::BackwardFromEnd => SeekFrom::End(value.checked_neg().unwrap()), 301 | }) 302 | .map_err(|_| { 303 | anyhow!( 304 | "Failed to jump to the desired input position. \ 305 | This could be caused by a negative offset that is too large or by \ 306 | an input that is not seek-able (e.g. if the input comes from a pipe)." 307 | ) 308 | })? 309 | } else { 310 | 0 311 | }; 312 | 313 | let parse_byte_count = |s| -> Result { 314 | Ok(parse_byte_offset(s, block_size)? 315 | .assume_forward_offset_from_start()? 316 | .into()) 317 | }; 318 | 319 | let mut reader = if let Some(ref length) = opt.length { 320 | let length = parse_byte_count(length).context(anyhow!( 321 | "failed to parse `--length` arg {:?} as byte count", 322 | length 323 | ))?; 324 | Box::new(reader.take(length)) 325 | } else { 326 | reader.into_inner() 327 | }; 328 | 329 | let no_color = std::env::var_os("NO_COLOR").is_some(); 330 | let show_color = match opt.color { 331 | ColorWhen::Never => false, 332 | ColorWhen::Always => !no_color, 333 | ColorWhen::Force => true, 334 | ColorWhen::Auto => { 335 | if no_color { 336 | false 337 | } else { 338 | supports_color::on(supports_color::Stream::Stdout) 339 | .map(|level| level.has_basic) 340 | .unwrap_or(false) 341 | } 342 | } 343 | }; 344 | 345 | let border_style = opt.border; 346 | 347 | let &squeeze = &!opt.no_squeezing; 348 | 349 | let show_char_panel = !opt.no_characters && !opt.plain; 350 | 351 | let show_position_panel = !opt.no_position && !opt.plain; 352 | 353 | let display_offset: u64 = parse_byte_count(&opt.display_offset).context(anyhow!( 354 | "failed to parse `--display-offset` arg {:?} as byte count", 355 | opt.display_offset 356 | ))?; 357 | 358 | let max_panels_fn = |terminal_width: u64, base_digits: u64, group_size: u64| { 359 | let offset = if show_position_panel { 10 } else { 1 }; 360 | let col_width = if show_char_panel { 361 | ((8 / group_size) * (base_digits * group_size + 1)) + 2 + 8 362 | } else { 363 | ((8 / group_size) * (base_digits * group_size + 1)) + 2 364 | }; 365 | if (terminal_width - offset) / col_width < 1 { 366 | 1 367 | } else { 368 | (terminal_width - offset) / col_width 369 | } 370 | }; 371 | 372 | let base = if let Ok(base_num) = opt.base.parse::() { 373 | match base_num { 374 | 2 => Ok(Base::Binary), 375 | 8 => Ok(Base::Octal), 376 | 10 => Ok(Base::Decimal), 377 | 16 => Ok(Base::Hexadecimal), 378 | _ => Err(anyhow!( 379 | "The number provided is not a valid base. Valid bases are 2, 8, 10, and 16." 380 | )), 381 | } 382 | } else { 383 | match opt.base.as_str() { 384 | "b" | "bin" | "binary" => Ok(Base::Binary), 385 | "o" | "oct" | "octal" => Ok(Base::Octal), 386 | "d" | "dec" | "decimal" => Ok(Base::Decimal), 387 | "x" | "hex" | "hexadecimal" => Ok(Base::Hexadecimal), 388 | _ => Err(anyhow!( 389 | "The base provided is not valid. Valid bases are \"b\", \"o\", \"d\", and \"x\"." 390 | )), 391 | } 392 | }?; 393 | 394 | let base_digits = match base { 395 | Base::Binary => 8, 396 | Base::Octal => 3, 397 | Base::Decimal => 3, 398 | Base::Hexadecimal => 2, 399 | }; 400 | 401 | let group_size = u8::from(opt.group_size); 402 | 403 | let terminal_width = terminal_size().map(|s| s.0 .0 as u64).unwrap_or(80); 404 | 405 | let panels = if opt.panels.as_deref() == Some("auto") { 406 | max_panels_fn(terminal_width, base_digits, group_size.into()) 407 | } else if let Some(panels) = opt.panels { 408 | panels 409 | .parse::() 410 | .map(u64::from) 411 | .context(anyhow!( 412 | "failed to parse `--panels` arg {:?} as unsigned nonzero integer", 413 | panels 414 | ))? 415 | } else if let Some(terminal_width) = opt.terminal_width { 416 | max_panels_fn(terminal_width.into(), base_digits, group_size.into()) 417 | } else { 418 | std::cmp::min( 419 | 2, 420 | max_panels_fn(terminal_width, base_digits, group_size.into()), 421 | ) 422 | }; 423 | 424 | let endianness = if opt.little_endian_format { 425 | Endianness::Little 426 | } else { 427 | opt.endianness 428 | }; 429 | 430 | let character_table = opt.character_table; 431 | 432 | let mut stdout = BufWriter::new(io::stdout().lock()); 433 | 434 | let mut printer = PrinterBuilder::new(&mut stdout) 435 | .show_color(show_color) 436 | .show_char_panel(show_char_panel) 437 | .show_position_panel(show_position_panel) 438 | .with_border_style(border_style) 439 | .enable_squeezing(squeeze) 440 | .num_panels(panels) 441 | .group_size(group_size) 442 | .with_base(base) 443 | .endianness(endianness) 444 | .character_table(character_table) 445 | .build(); 446 | printer.display_offset(skip_offset + display_offset); 447 | printer.print_all(&mut reader).map_err(|e| anyhow!(e))?; 448 | 449 | Ok(()) 450 | } 451 | 452 | fn main() { 453 | let result = run(); 454 | 455 | if let Err(err) = result { 456 | if let Some(io_error) = err.downcast_ref::() { 457 | if io_error.kind() == ::std::io::ErrorKind::BrokenPipe { 458 | std::process::exit(0); 459 | } 460 | } 461 | eprintln!("Error: {err:?}"); 462 | std::process::exit(1); 463 | } 464 | } 465 | 466 | #[derive(Clone, Copy, Debug, Default, Hash, Eq, Ord, PartialEq, PartialOrd)] 467 | pub struct NonNegativeI64(i64); 468 | 469 | impl NonNegativeI64 { 470 | pub fn new(x: i64) -> Option { 471 | if x.is_negative() { 472 | None 473 | } else { 474 | Some(Self(x)) 475 | } 476 | } 477 | 478 | pub fn into_inner(self) -> i64 { 479 | self.0 480 | } 481 | } 482 | 483 | impl From for u64 { 484 | fn from(x: NonNegativeI64) -> u64 { 485 | u64::try_from(x.0) 486 | .expect("invariant broken: NonNegativeI64 should contain a non-negative i64 value") 487 | } 488 | } 489 | 490 | fn print_color_table() -> io::Result<()> { 491 | let mut stdout = BufWriter::new(io::stdout().lock()); 492 | 493 | writeln!(stdout, "hexyl color reference:\n")?; 494 | 495 | // NULL bytes 496 | stdout.write_all(COLOR_NULL)?; 497 | writeln!(stdout, "⋄ NULL bytes (0x00)")?; 498 | stdout.write_all(COLOR_RESET)?; 499 | 500 | // ASCII printable 501 | stdout.write_all(COLOR_ASCII_PRINTABLE)?; 502 | writeln!(stdout, "a ASCII printable characters (0x20 - 0x7E)")?; 503 | stdout.write_all(COLOR_RESET)?; 504 | 505 | // ASCII whitespace 506 | stdout.write_all(COLOR_ASCII_WHITESPACE)?; 507 | writeln!(stdout, "_ ASCII whitespace (0x09 - 0x0D, 0x20)")?; 508 | stdout.write_all(COLOR_RESET)?; 509 | 510 | // ASCII other 511 | stdout.write_all(COLOR_ASCII_OTHER)?; 512 | writeln!( 513 | stdout, 514 | "• ASCII control characters (except NULL and whitespace)" 515 | )?; 516 | stdout.write_all(COLOR_RESET)?; 517 | 518 | // Non-ASCII 519 | stdout.write_all(COLOR_NONASCII)?; 520 | writeln!(stdout, "× Non-ASCII bytes (0x80 - 0xFF)")?; 521 | stdout.write_all(COLOR_RESET)?; 522 | 523 | Ok(()) 524 | } 525 | 526 | #[derive(Clone, Copy, Debug, Default, Hash, Eq, Ord, PartialEq, PartialOrd)] 527 | pub struct PositiveI64(i64); 528 | 529 | impl PositiveI64 { 530 | pub fn new(x: i64) -> Option { 531 | if x < 1 { 532 | None 533 | } else { 534 | Some(Self(x)) 535 | } 536 | } 537 | 538 | pub fn into_inner(self) -> i64 { 539 | self.0 540 | } 541 | } 542 | 543 | impl From for u64 { 544 | fn from(x: PositiveI64) -> u64 { 545 | u64::try_from(x.0) 546 | .expect("invariant broken: PositiveI64 should contain a positive i64 value") 547 | } 548 | } 549 | 550 | #[derive(Debug, PartialEq)] 551 | enum Unit { 552 | Byte, 553 | Kilobyte, 554 | Megabyte, 555 | Gigabyte, 556 | Terabyte, 557 | Kibibyte, 558 | Mebibyte, 559 | Gibibyte, 560 | Tebibyte, 561 | /// a customizable amount of bytes 562 | Block { 563 | custom_size: Option, 564 | }, 565 | } 566 | 567 | impl Unit { 568 | const fn get_multiplier(self) -> i64 { 569 | match self { 570 | Self::Byte => 1, 571 | Self::Kilobyte => 1000, 572 | Self::Megabyte => 1_000_000, 573 | Self::Gigabyte => 1_000_000_000, 574 | Self::Terabyte => 1_000_000_000_000, 575 | Self::Kibibyte => 1 << 10, 576 | Self::Mebibyte => 1 << 20, 577 | Self::Gibibyte => 1 << 30, 578 | Self::Tebibyte => 1 << 40, 579 | Self::Block { 580 | custom_size: Some(size), 581 | } => size.get(), 582 | Self::Block { custom_size: None } => DEFAULT_BLOCK_SIZE, 583 | } 584 | } 585 | } 586 | 587 | const HEX_PREFIX: &str = "0x"; 588 | 589 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 590 | enum ByteOffsetKind { 591 | ForwardFromBeginning, 592 | ForwardFromLastOffset, 593 | BackwardFromEnd, 594 | } 595 | 596 | #[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 597 | struct ByteOffset { 598 | value: NonNegativeI64, 599 | kind: ByteOffsetKind, 600 | } 601 | 602 | #[derive(Clone, Debug, ThisError)] 603 | #[error( 604 | "negative offset specified, but only positive offsets (counts) are accepted in this context" 605 | )] 606 | struct NegativeOffsetSpecifiedError; 607 | 608 | impl ByteOffset { 609 | fn assume_forward_offset_from_start( 610 | &self, 611 | ) -> Result { 612 | let &Self { value, kind } = self; 613 | match kind { 614 | ByteOffsetKind::ForwardFromBeginning | ByteOffsetKind::ForwardFromLastOffset => { 615 | Ok(value) 616 | } 617 | ByteOffsetKind::BackwardFromEnd => Err(NegativeOffsetSpecifiedError), 618 | } 619 | } 620 | } 621 | 622 | #[derive(Clone, Debug, Eq, PartialEq, ThisError)] 623 | enum ByteOffsetParseError { 624 | #[error("no character data found, did you forget to write it?")] 625 | Empty, 626 | #[error("no digits found after sign, did you forget to write them?")] 627 | EmptyAfterSign, 628 | #[error( 629 | "found {0:?} sign after hex prefix ({:?}); signs should go before it", 630 | HEX_PREFIX 631 | )] 632 | SignFoundAfterHexPrefix(char), 633 | #[error("{0:?} is not of the expected form []")] 634 | InvalidNumAndUnit(String), 635 | #[error("{0:?} is a valid unit, but an integer should come before it")] 636 | EmptyWithUnit(String), 637 | #[error("invalid unit {0:?}")] 638 | InvalidUnit(String), 639 | #[error("failed to parse integer part")] 640 | ParseNum(#[source] std::num::ParseIntError), 641 | #[error("count multiplied by the unit overflowed a signed 64-bit integer; are you sure it should be that big?")] 642 | UnitMultiplicationOverflow, 643 | } 644 | 645 | fn parse_byte_offset(n: &str, block_size: PositiveI64) -> Result { 646 | use ByteOffsetParseError::*; 647 | 648 | let (n, kind) = process_sign_of(n)?; 649 | 650 | let into_byte_offset = |value| { 651 | Ok(ByteOffset { 652 | value: NonNegativeI64::new(value).unwrap(), 653 | kind, 654 | }) 655 | }; 656 | 657 | if let Some(hex_number) = try_parse_as_hex_number(n) { 658 | return hex_number.map(into_byte_offset)?; 659 | } 660 | 661 | let (num, mut unit) = extract_num_and_unit_from(n)?; 662 | if let Unit::Block { custom_size: None } = unit { 663 | unit = Unit::Block { 664 | custom_size: Some( 665 | NonZeroI64::new(block_size.into_inner()).expect("PositiveI64 was zero"), 666 | ), 667 | }; 668 | } 669 | 670 | num.checked_mul(unit.get_multiplier()) 671 | .ok_or(UnitMultiplicationOverflow) 672 | .and_then(into_byte_offset) 673 | } 674 | 675 | /// Takes a string containing a base-10 number and an optional unit, and returns them with their proper types. 676 | /// The unit must directly follow the number (e.g. no whitespace is allowed between them). 677 | /// When no unit is given, [Unit::Byte] is assumed. 678 | /// When the unit is [Unit::Block], it is returned without custom size. 679 | /// No normalization is performed, that is "1024" is extracted to (1024, Byte), not (1, Kibibyte). 680 | fn extract_num_and_unit_from(n: &str) -> Result<(i64, Unit), ByteOffsetParseError> { 681 | use ByteOffsetParseError::*; 682 | if n.is_empty() { 683 | return Err(Empty); 684 | } 685 | match n.chars().position(|c| !c.is_ascii_digit()) { 686 | Some(unit_begin_idx) => { 687 | let (n, raw_unit) = n.split_at(unit_begin_idx); 688 | let unit = match raw_unit.to_lowercase().as_str() { 689 | "" => Unit::Byte, // no "b" => Byte to allow hex nums with units 690 | "kb" => Unit::Kilobyte, 691 | "mb" => Unit::Megabyte, 692 | "gb" => Unit::Gigabyte, 693 | "tb" => Unit::Terabyte, 694 | "kib" => Unit::Kibibyte, 695 | "mib" => Unit::Mebibyte, 696 | "gib" => Unit::Gibibyte, 697 | "tib" => Unit::Tebibyte, 698 | "block" | "blocks" => Unit::Block { custom_size: None }, 699 | _ => { 700 | return if n.is_empty() { 701 | Err(InvalidNumAndUnit(raw_unit.to_string())) 702 | } else { 703 | Err(InvalidUnit(raw_unit.to_string())) 704 | } 705 | } 706 | }; 707 | let num = n.parse::().map_err(|e| { 708 | if n.is_empty() { 709 | EmptyWithUnit(raw_unit.to_owned()) 710 | } else { 711 | ParseNum(e) 712 | } 713 | })?; 714 | Ok((num, unit)) 715 | } 716 | None => { 717 | // no unit part 718 | let num = n.parse::().map_err(ParseNum)?; 719 | Ok((num, Unit::Byte)) 720 | } 721 | } 722 | } 723 | 724 | /// Extracts a [ByteOffsetKind] based on the sign at the beginning of the given string. 725 | /// Returns the input string without the sign (or an equal string if there wasn't any sign). 726 | fn process_sign_of(n: &str) -> Result<(&str, ByteOffsetKind), ByteOffsetParseError> { 727 | use ByteOffsetParseError::*; 728 | let mut chars = n.chars(); 729 | let next_char = chars.next(); 730 | let check_empty_after_sign = || { 731 | if chars.clone().next().is_none() { 732 | Err(EmptyAfterSign) 733 | } else { 734 | Ok(chars.as_str()) 735 | } 736 | }; 737 | match next_char { 738 | Some('+') => Ok(( 739 | check_empty_after_sign()?, 740 | ByteOffsetKind::ForwardFromLastOffset, 741 | )), 742 | Some('-') => Ok((check_empty_after_sign()?, ByteOffsetKind::BackwardFromEnd)), 743 | None => Err(Empty), 744 | _ => Ok((n, ByteOffsetKind::ForwardFromBeginning)), 745 | } 746 | } 747 | 748 | /// If `n` starts with a hex prefix, its remaining part is returned as some number (if possible), 749 | /// otherwise None is returned. 750 | fn try_parse_as_hex_number(n: &str) -> Option> { 751 | use ByteOffsetParseError::*; 752 | n.strip_prefix(HEX_PREFIX).map(|num| { 753 | let mut chars = num.chars(); 754 | match chars.next() { 755 | Some(c @ '+') | Some(c @ '-') => { 756 | return if chars.next().is_none() { 757 | Err(EmptyAfterSign) 758 | } else { 759 | Err(SignFoundAfterHexPrefix(c)) 760 | } 761 | } 762 | _ => (), 763 | } 764 | i64::from_str_radix(num, 16).map_err(ParseNum) 765 | }) 766 | } 767 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[test] 4 | fn unit_multipliers() { 5 | use Unit::*; 6 | assert_eq!(Kilobyte.get_multiplier(), 1000 * Byte.get_multiplier()); 7 | assert_eq!(Megabyte.get_multiplier(), 1000 * Kilobyte.get_multiplier()); 8 | assert_eq!(Gigabyte.get_multiplier(), 1000 * Megabyte.get_multiplier()); 9 | assert_eq!(Terabyte.get_multiplier(), 1000 * Gigabyte.get_multiplier()); 10 | 11 | assert_eq!(Kibibyte.get_multiplier(), 1024 * Byte.get_multiplier()); 12 | assert_eq!(Mebibyte.get_multiplier(), 1024 * Kibibyte.get_multiplier()); 13 | assert_eq!(Gibibyte.get_multiplier(), 1024 * Mebibyte.get_multiplier()); 14 | assert_eq!(Tebibyte.get_multiplier(), 1024 * Gibibyte.get_multiplier()); 15 | } 16 | 17 | #[test] 18 | fn test_process_sign() { 19 | use ByteOffsetKind::*; 20 | use ByteOffsetParseError::*; 21 | assert_eq!(process_sign_of("123"), Ok(("123", ForwardFromBeginning))); 22 | assert_eq!(process_sign_of("+123"), Ok(("123", ForwardFromLastOffset))); 23 | assert_eq!(process_sign_of("-123"), Ok(("123", BackwardFromEnd))); 24 | assert_eq!(process_sign_of("-"), Err(EmptyAfterSign)); 25 | assert_eq!(process_sign_of("+"), Err(EmptyAfterSign)); 26 | assert_eq!(process_sign_of(""), Err(Empty)); 27 | } 28 | 29 | #[test] 30 | fn test_parse_as_hex() { 31 | assert_eq!(try_parse_as_hex_number("73"), None); 32 | assert_eq!(try_parse_as_hex_number("0x1337"), Some(Ok(0x1337))); 33 | assert!(matches!(try_parse_as_hex_number("0xnope"), Some(Err(_)))); 34 | assert!(matches!(try_parse_as_hex_number("0x-1"), Some(Err(_)))); 35 | } 36 | 37 | #[test] 38 | fn extract_num_and_unit() { 39 | use ByteOffsetParseError::*; 40 | use Unit::*; 41 | // byte is default unit 42 | assert_eq!(extract_num_and_unit_from("4"), Ok((4, Byte))); 43 | // blocks are returned without customization 44 | assert_eq!( 45 | extract_num_and_unit_from("2blocks"), 46 | Ok((2, Block { custom_size: None })) 47 | ); 48 | // no normalization is performed 49 | assert_eq!(extract_num_and_unit_from("1024kb"), Ok((1024, Kilobyte))); 50 | 51 | // unit without number results in error 52 | assert_eq!( 53 | extract_num_and_unit_from("gib"), 54 | Err(EmptyWithUnit("gib".to_string())) 55 | ); 56 | // empty string results in error 57 | assert_eq!(extract_num_and_unit_from(""), Err(Empty)); 58 | // an invalid unit results in an error 59 | assert_eq!( 60 | extract_num_and_unit_from("25litres"), 61 | Err(InvalidUnit("litres".to_string())) 62 | ); 63 | } 64 | 65 | #[test] 66 | fn test_parse_byte_offset() { 67 | use ByteOffsetParseError::*; 68 | 69 | macro_rules! success { 70 | ($input: expr, $expected_kind: ident $expected_value: expr) => { 71 | success!($input, $expected_kind $expected_value; block_size: DEFAULT_BLOCK_SIZE) 72 | }; 73 | ($input: expr, $expected_kind: ident $expected_value: expr; block_size: $block_size: expr) => { 74 | assert_eq!( 75 | parse_byte_offset($input, PositiveI64::new($block_size).unwrap()), 76 | Ok( 77 | ByteOffset { 78 | value: NonNegativeI64::new($expected_value).unwrap(), 79 | kind: ByteOffsetKind::$expected_kind, 80 | } 81 | ), 82 | ); 83 | }; 84 | } 85 | 86 | macro_rules! error { 87 | ($input: expr, $expected_err: expr) => { 88 | assert_eq!( 89 | parse_byte_offset($input, PositiveI64::new(DEFAULT_BLOCK_SIZE).unwrap()), 90 | Err($expected_err), 91 | ); 92 | }; 93 | } 94 | 95 | success!("0", ForwardFromBeginning 0); 96 | success!("1", ForwardFromBeginning 1); 97 | success!("1", ForwardFromBeginning 1); 98 | success!("100", ForwardFromBeginning 100); 99 | success!("+100", ForwardFromLastOffset 100); 100 | 101 | success!("0x0", ForwardFromBeginning 0); 102 | success!("0xf", ForwardFromBeginning 15); 103 | success!("0xdeadbeef", ForwardFromBeginning 3_735_928_559); 104 | 105 | success!("1KB", ForwardFromBeginning 1000); 106 | success!("2MB", ForwardFromBeginning 2000000); 107 | success!("3GB", ForwardFromBeginning 3000000000); 108 | success!("4TB", ForwardFromBeginning 4000000000000); 109 | success!("+4TB", ForwardFromLastOffset 4000000000000); 110 | 111 | success!("1GiB", ForwardFromBeginning 1073741824); 112 | success!("2TiB", ForwardFromBeginning 2199023255552); 113 | success!("+2TiB", ForwardFromLastOffset 2199023255552); 114 | 115 | success!("0xff", ForwardFromBeginning 255); 116 | success!("0xEE", ForwardFromBeginning 238); 117 | success!("+0xFF", ForwardFromLastOffset 255); 118 | 119 | success!("1block", ForwardFromBeginning 512; block_size: 512); 120 | success!("2block", ForwardFromBeginning 1024; block_size: 512); 121 | success!("1block", ForwardFromBeginning 4; block_size: 4); 122 | success!("2block", ForwardFromBeginning 8; block_size: 4); 123 | 124 | // empty string is invalid 125 | error!("", Empty); 126 | // These are also bad. 127 | error!("+", EmptyAfterSign); 128 | error!("-", EmptyAfterSign); 129 | error!("K", InvalidNumAndUnit("K".to_owned())); 130 | error!("k", InvalidNumAndUnit("k".to_owned())); 131 | error!("m", InvalidNumAndUnit("m".to_owned())); 132 | error!("block", EmptyWithUnit("block".to_owned())); 133 | // leading/trailing space is invalid 134 | error!(" 0", InvalidNumAndUnit(" 0".to_owned())); 135 | error!("0 ", InvalidUnit(" ".to_owned())); 136 | // Signs after the hex prefix make no sense 137 | error!("0x-12", SignFoundAfterHexPrefix('-')); 138 | // This was previously accepted but shouldn't be. 139 | error!("0x+12", SignFoundAfterHexPrefix('+')); 140 | // invalid suffix 141 | error!("1234asdf", InvalidUnit("asdf".to_owned())); 142 | // bad numbers 143 | error!("asdf1234", InvalidNumAndUnit("asdf1234".to_owned())); 144 | error!("a1s2d3f4", InvalidNumAndUnit("a1s2d3f4".to_owned())); 145 | // multiplication overflows u64 146 | error!("20000000TiB", UnitMultiplicationOverflow); 147 | 148 | assert!( 149 | match parse_byte_offset("99999999999999999999", PositiveI64::new(512).unwrap()) { 150 | // We can't check against the kind of the `ParseIntError`, so we'll just make sure it's the 151 | // same as trying to do the parse directly. 152 | Err(ParseNum(e)) => e == "99999999999999999999".parse::().unwrap_err(), 153 | _ => false, 154 | } 155 | ); 156 | } 157 | -------------------------------------------------------------------------------- /tests/examples/.gitattributes: -------------------------------------------------------------------------------- 1 | ascii text eol=lf 2 | -------------------------------------------------------------------------------- /tests/examples/ascii: -------------------------------------------------------------------------------- 1 | 0123456789abcde 2 | -------------------------------------------------------------------------------- /tests/examples/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharkdp/hexyl/742c2fcbfd117b5c9eeff94dfe3324d9890db699/tests/examples/empty -------------------------------------------------------------------------------- /tests/examples/hello_world_elf64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sharkdp/hexyl/742c2fcbfd117b5c9eeff94dfe3324d9890db699/tests/examples/hello_world_elf64 -------------------------------------------------------------------------------- /tests/integration_tests.rs: -------------------------------------------------------------------------------- 1 | use assert_cmd::Command; 2 | 3 | fn hexyl() -> Command { 4 | let mut cmd = Command::cargo_bin("hexyl").unwrap(); 5 | cmd.current_dir("tests/examples"); 6 | cmd 7 | } 8 | trait PrettyAssert 9 | where 10 | S: AsRef, 11 | { 12 | fn pretty_stdout(self, other: S); 13 | } 14 | 15 | // https://github.com/assert-rs/assert_cmd/issues/121#issuecomment-849937376 16 | // 17 | impl PrettyAssert for assert_cmd::assert::Assert 18 | where 19 | S: AsRef, 20 | { 21 | fn pretty_stdout(self, other: S) { 22 | println!("{}", other.as_ref().len()); 23 | let self_str = String::from_utf8(self.get_output().stdout.clone()).unwrap(); 24 | println!("{}", self_str.len()); 25 | pretty_assertions::assert_eq!(self_str, other.as_ref()); 26 | } 27 | } 28 | 29 | mod basic { 30 | use super::hexyl; 31 | 32 | #[test] 33 | fn can_print_simple_ascii_file() { 34 | hexyl() 35 | .arg("ascii") 36 | .arg("--color=never") 37 | .assert() 38 | .success() 39 | .stdout( 40 | "┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n\ 41 | │00000000│ 30 31 32 33 34 35 36 37 ┊ 38 39 61 62 63 64 65 0a │01234567┊89abcde_│\n\ 42 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n", 43 | ); 44 | } 45 | 46 | #[test] 47 | fn can_read_input_from_stdin() { 48 | hexyl() 49 | .arg("--color=never") 50 | .write_stdin("abc") 51 | .assert() 52 | .success() 53 | .stdout( 54 | "┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n\ 55 | │00000000│ 61 62 63 ┊ │abc ┊ │\n\ 56 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n", 57 | ); 58 | } 59 | 60 | #[test] 61 | fn fails_on_non_existing_input() { 62 | hexyl().arg("non-existing").assert().failure(); 63 | } 64 | 65 | #[test] 66 | fn prints_warning_on_empty_content() { 67 | hexyl() 68 | .arg("empty") 69 | .arg("--color=never") 70 | .assert() 71 | .success() 72 | .stdout( 73 | "┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n\ 74 | │ │ No content │ │ │ │\n\ 75 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n", 76 | ); 77 | } 78 | } 79 | 80 | mod length { 81 | use super::hexyl; 82 | 83 | #[test] 84 | fn length_restricts_output_size() { 85 | hexyl() 86 | .arg("hello_world_elf64") 87 | .arg("--color=never") 88 | .arg("--length=32") 89 | .assert() 90 | .success() 91 | .stdout( 92 | "┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n\ 93 | │00000000│ 7f 45 4c 46 02 01 01 00 ┊ 00 00 00 00 00 00 00 00 │•ELF•••⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│\n\ 94 | │00000010│ 02 00 3e 00 01 00 00 00 ┊ 00 10 40 00 00 00 00 00 │•⋄>⋄•⋄⋄⋄┊⋄•@⋄⋄⋄⋄⋄│\n\ 95 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n", 96 | ); 97 | } 98 | 99 | #[test] 100 | fn fail_if_length_and_bytes_options_are_used_simultaneously() { 101 | hexyl() 102 | .arg("hello_world_elf64") 103 | .arg("--length=32") 104 | .arg("--bytes=10") 105 | .assert() 106 | .failure(); 107 | } 108 | 109 | #[test] 110 | fn fail_if_length_and_count_options_are_used_simultaneously() { 111 | hexyl() 112 | .arg("hello_world_elf64") 113 | .arg("--length=32") 114 | .arg("-l=10") 115 | .assert() 116 | .failure(); 117 | } 118 | } 119 | 120 | mod bytes { 121 | use super::hexyl; 122 | 123 | #[test] 124 | fn fail_if_bytes_and_count_options_are_used_simultaneously() { 125 | hexyl() 126 | .arg("hello_world_elf64") 127 | .arg("--bytes=32") 128 | .arg("-l=10") 129 | .assert() 130 | .failure(); 131 | } 132 | } 133 | 134 | mod skip { 135 | use super::hexyl; 136 | 137 | #[test] 138 | fn basic() { 139 | hexyl() 140 | .arg("ascii") 141 | .arg("--color=never") 142 | .arg("--skip=2") 143 | .arg("--length=4") 144 | .assert() 145 | .success() 146 | .stdout( 147 | "┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n\ 148 | │00000002│ 32 33 34 35 ┊ │2345 ┊ │\n\ 149 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n", 150 | ); 151 | } 152 | 153 | #[test] 154 | fn prints_warning_when_skipping_past_the_end() { 155 | hexyl() 156 | .arg("ascii") 157 | .arg("--color=never") 158 | .arg("--skip=1000") 159 | .assert() 160 | .success() 161 | .stdout( 162 | "┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n\ 163 | │ │ No content │ │ │ │\n\ 164 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n", 165 | ); 166 | } 167 | 168 | #[test] 169 | fn negative_offset() { 170 | hexyl() 171 | .arg("ascii") 172 | .arg("--color=never") 173 | .arg("--skip=-4") 174 | .arg("--length=3") 175 | .assert() 176 | .success() 177 | .stdout( 178 | "┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n\ 179 | │0000000c│ 63 64 65 ┊ │cde ┊ │\n\ 180 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n", 181 | ); 182 | } 183 | 184 | #[test] 185 | fn fails_if_negative_offset_is_too_large() { 186 | hexyl() 187 | .arg("ascii") 188 | .arg("--color=never") 189 | .arg("--skip=-1MiB") 190 | .assert() 191 | .failure() 192 | .stderr(predicates::str::contains("Failed to jump")); 193 | } 194 | } 195 | 196 | mod display_offset { 197 | use super::hexyl; 198 | 199 | #[test] 200 | fn basic() { 201 | hexyl() 202 | .arg("ascii") 203 | .arg("--color=never") 204 | .arg("--display-offset=0xc0ffee") 205 | .assert() 206 | .success() 207 | .stdout( 208 | "┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n\ 209 | │00c0ffee│ 30 31 32 33 34 35 36 37 ┊ 38 39 61 62 63 64 65 0a │01234567┊89abcde_│\n\ 210 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n", 211 | ); 212 | } 213 | 214 | #[test] 215 | fn display_offset_and_skip() { 216 | hexyl() 217 | .arg("hello_world_elf64") 218 | .arg("--color=never") 219 | .arg("--display-offset=0x20") 220 | .arg("--skip=0x10") 221 | .arg("--length=0x10") 222 | .assert() 223 | .success() 224 | .stdout( 225 | "┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n\ 226 | │00000030│ 02 00 3e 00 01 00 00 00 ┊ 00 10 40 00 00 00 00 00 │•⋄>⋄•⋄⋄⋄┊⋄•@⋄⋄⋄⋄⋄│\n\ 227 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n", 228 | ); 229 | } 230 | } 231 | 232 | mod blocksize { 233 | use super::hexyl; 234 | 235 | #[test] 236 | fn fails_for_zero_or_negative_blocksize() { 237 | hexyl() 238 | .arg("ascii") 239 | .arg("--block-size=0") 240 | .assert() 241 | .failure(); 242 | 243 | hexyl() 244 | .arg("ascii") 245 | .arg("--block-size=-16") 246 | .assert() 247 | .failure(); 248 | } 249 | } 250 | 251 | mod display_settings { 252 | use super::hexyl; 253 | 254 | #[test] 255 | fn plain() { 256 | hexyl() 257 | .arg("ascii") 258 | .arg("--plain") 259 | .assert() 260 | .success() 261 | .stdout(" 30 31 32 33 34 35 36 37 38 39 61 62 63 64 65 0a \n"); 262 | } 263 | 264 | #[test] 265 | fn no_chars() { 266 | hexyl() 267 | .arg("ascii") 268 | .arg("--no-characters") 269 | .arg("--color=never") 270 | .assert() 271 | .success() 272 | .stdout( 273 | "┌────────┬─────────────────────────┬─────────────────────────┐\n\ 274 | │00000000│ 30 31 32 33 34 35 36 37 ┊ 38 39 61 62 63 64 65 0a │\n\ 275 | └────────┴─────────────────────────┴─────────────────────────┘\n", 276 | ); 277 | } 278 | 279 | #[test] 280 | fn no_position() { 281 | hexyl() 282 | .arg("ascii") 283 | .arg("--no-position") 284 | .arg("--color=never") 285 | .assert() 286 | .success() 287 | .stdout( 288 | "┌─────────────────────────┬─────────────────────────┬────────┬────────┐\n\ 289 | │ 30 31 32 33 34 35 36 37 ┊ 38 39 61 62 63 64 65 0a │01234567┊89abcde_│\n\ 290 | └─────────────────────────┴─────────────────────────┴────────┴────────┘\n", 291 | ); 292 | } 293 | } 294 | 295 | mod group_and_endianness { 296 | use super::hexyl; 297 | use super::PrettyAssert; 298 | 299 | #[test] 300 | fn group_2_bytes_be() { 301 | hexyl() 302 | .arg("ascii") 303 | .arg("--color=never") 304 | .arg("--group-size=2") 305 | .assert() 306 | .success() 307 | .stdout( 308 | "┌────────┬─────────────────────┬─────────────────────┬────────┬────────┐\n\ 309 | │00000000│ 3031 3233 3435 3637 ┊ 3839 6162 6364 650a │01234567┊89abcde_│\n\ 310 | └────────┴─────────────────────┴─────────────────────┴────────┴────────┘\n", 311 | ); 312 | } 313 | 314 | #[test] 315 | fn group_2_bytes_le() { 316 | hexyl() 317 | .arg("ascii") 318 | .arg("--color=never") 319 | .arg("--group-size=2") 320 | .arg("--endianness=little") 321 | .assert() 322 | .success() 323 | .stdout( 324 | "┌────────┬─────────────────────┬─────────────────────┬────────┬────────┐\n\ 325 | │00000000│ 3130 3332 3534 3736 ┊ 3938 6261 6463 0a65 │01234567┊89abcde_│\n\ 326 | └────────┴─────────────────────┴─────────────────────┴────────┴────────┘\n", 327 | ); 328 | } 329 | 330 | #[test] 331 | fn group_4_bytes_be() { 332 | hexyl() 333 | .arg("ascii") 334 | .arg("--color=never") 335 | .arg("--group-size=4") 336 | .assert() 337 | .success() 338 | .stdout( 339 | "┌────────┬───────────────────┬───────────────────┬────────┬────────┐\n\ 340 | │00000000│ 30313233 34353637 ┊ 38396162 6364650a │01234567┊89abcde_│\n\ 341 | └────────┴───────────────────┴───────────────────┴────────┴────────┘\n", 342 | ); 343 | } 344 | 345 | #[test] 346 | fn group_4_bytes_le() { 347 | hexyl() 348 | .arg("ascii") 349 | .arg("--color=never") 350 | .arg("--group-size=4") 351 | .arg("--endianness=little") 352 | .assert() 353 | .success() 354 | .stdout( 355 | "┌────────┬───────────────────┬───────────────────┬────────┬────────┐\n\ 356 | │00000000│ 33323130 37363534 ┊ 62613938 0a656463 │01234567┊89abcde_│\n\ 357 | └────────┴───────────────────┴───────────────────┴────────┴────────┘\n", 358 | ); 359 | } 360 | 361 | #[test] 362 | fn group_8_bytes_be() { 363 | hexyl() 364 | .arg("ascii") 365 | .arg("--color=never") 366 | .arg("--group-size=8") 367 | .assert() 368 | .success() 369 | .stdout( 370 | "┌────────┬──────────────────┬──────────────────┬────────┬────────┐\n\ 371 | │00000000│ 3031323334353637 ┊ 383961626364650a │01234567┊89abcde_│\n\ 372 | └────────┴──────────────────┴──────────────────┴────────┴────────┘\n", 373 | ); 374 | } 375 | 376 | #[test] 377 | fn group_8_bytes_le() { 378 | hexyl() 379 | .arg("ascii") 380 | .arg("--color=never") 381 | .arg("--group-size=8") 382 | .arg("--endianness=little") 383 | .assert() 384 | .success() 385 | .stdout( 386 | "┌────────┬──────────────────┬──────────────────┬────────┬────────┐\n\ 387 | │00000000│ 3736353433323130 ┊ 0a65646362613938 │01234567┊89abcde_│\n\ 388 | └────────┴──────────────────┴──────────────────┴────────┴────────┘\n", 389 | ); 390 | } 391 | 392 | #[test] 393 | fn group_size_plain() { 394 | hexyl() 395 | .arg("ascii") 396 | .arg("--color=never") 397 | .arg("--plain") 398 | .arg("--group-size=2") 399 | .assert() 400 | .success() 401 | .stdout(" 3031 3233 3435 3637 3839 6162 6364 650a \n"); 402 | } 403 | 404 | #[test] 405 | fn group_size_fill_space() { 406 | hexyl() 407 | .arg("--color=never") 408 | .arg("--group-size=2") 409 | .write_stdin("abc") 410 | .assert() 411 | .success() 412 | .stdout( 413 | "┌────────┬─────────────────────┬─────────────────────┬────────┬────────┐\n\ 414 | │00000000│ 6162 63 ┊ │abc ┊ │\n\ 415 | └────────┴─────────────────────┴─────────────────────┴────────┴────────┘\n", 416 | ); 417 | } 418 | 419 | #[test] 420 | fn group_size_invalid() { 421 | hexyl() 422 | .arg("ascii") 423 | .arg("--color=never") 424 | .arg("--plain") 425 | .arg("--group-size=3") 426 | .assert() 427 | .failure(); 428 | } 429 | #[test] 430 | fn squeeze_no_chars() { 431 | hexyl() 432 | .arg("hello_world_elf64") 433 | .arg("--color=never") 434 | .arg("--skip=1024") 435 | .arg("--length=4096") 436 | .arg("--no-characters") 437 | .assert() 438 | .success() 439 | .pretty_stdout( 440 | "\ 441 | ┌────────┬─────────────────────────┬─────────────────────────┐ 442 | │00000400│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │ 443 | │* │ ┊ │ 444 | │00001000│ ba 0e 00 00 00 b9 00 20 ┊ 40 00 bb 01 00 00 00 b8 │ 445 | │00001010│ 04 00 00 00 cd 80 b8 01 ┊ 00 00 00 cd 80 00 00 00 │ 446 | │00001020│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │ 447 | │* │ ┊ │ 448 | │00001400│ ┊ │ 449 | └────────┴─────────────────────────┴─────────────────────────┘ 450 | ", 451 | ); 452 | } 453 | #[test] 454 | fn squeeze_no_chars_one_panel() { 455 | hexyl() 456 | .arg("hello_world_elf64") 457 | .arg("--color=never") 458 | .arg("--skip=1024") 459 | .arg("--length=4096") 460 | .arg("--no-characters") 461 | .arg("--panels=1") 462 | .assert() 463 | .success() 464 | .pretty_stdout( 465 | "\ 466 | ┌────────┬─────────────────────────┐ 467 | │00000400│ 00 00 00 00 00 00 00 00 │ 468 | │* │ │ 469 | │00001000│ ba 0e 00 00 00 b9 00 20 │ 470 | │00001008│ 40 00 bb 01 00 00 00 b8 │ 471 | │00001010│ 04 00 00 00 cd 80 b8 01 │ 472 | │00001018│ 00 00 00 cd 80 00 00 00 │ 473 | │00001020│ 00 00 00 00 00 00 00 00 │ 474 | │* │ │ 475 | │00001400│ │ 476 | └────────┴─────────────────────────┘ 477 | ", 478 | ); 479 | } 480 | #[test] 481 | fn squeeze_no_position() { 482 | hexyl() 483 | .arg("hello_world_elf64") 484 | .arg("--color=never") 485 | .arg("--skip=1024") 486 | .arg("--length=4096") 487 | .arg("--no-position") 488 | .assert() 489 | .success() 490 | .pretty_stdout( 491 | "\ 492 | ┌─────────────────────────┬─────────────────────────┬────────┬────────┐ 493 | │ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│ 494 | │* ┊ │ ┊ │ 495 | │ ba 0e 00 00 00 b9 00 20 ┊ 40 00 bb 01 00 00 00 b8 │ו⋄⋄⋄×⋄ ┊@⋄ו⋄⋄⋄×│ 496 | │ 04 00 00 00 cd 80 b8 01 ┊ 00 00 00 cd 80 00 00 00 │•⋄⋄⋄××ו┊⋄⋄⋄××⋄⋄⋄│ 497 | │ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│ 498 | │* ┊ │ ┊ │ 499 | │* ┊ │ ┊ │ 500 | └─────────────────────────┴─────────────────────────┴────────┴────────┘ 501 | ", 502 | ); 503 | } 504 | #[test] 505 | fn squeeze_no_position_one_panel() { 506 | hexyl() 507 | .arg("hello_world_elf64") 508 | .arg("--color=never") 509 | .arg("--skip=1024") 510 | .arg("--length=4096") 511 | .arg("--no-position") 512 | .arg("--panels=1") 513 | .assert() 514 | .success() 515 | .pretty_stdout( 516 | "\ 517 | ┌─────────────────────────┬────────┐ 518 | │ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄│ 519 | │* │ │ 520 | │ ba 0e 00 00 00 b9 00 20 │ו⋄⋄⋄×⋄ │ 521 | │ 40 00 bb 01 00 00 00 b8 │@⋄ו⋄⋄⋄×│ 522 | │ 04 00 00 00 cd 80 b8 01 │•⋄⋄⋄××ו│ 523 | │ 00 00 00 cd 80 00 00 00 │⋄⋄⋄××⋄⋄⋄│ 524 | │ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄│ 525 | │* │ │ 526 | │* │ │ 527 | └─────────────────────────┴────────┘ 528 | ", 529 | ); 530 | } 531 | #[test] 532 | fn squeeze_odd_panels_remainder_bytes() { 533 | hexyl() 534 | .arg("hello_world_elf64") 535 | .arg("--color=never") 536 | .arg("--skip=1024") 537 | .arg("--length=4092") // 4 byte remainder 538 | .arg("--panels=3") 539 | .assert() 540 | .success() 541 | .pretty_stdout( 542 | "\ 543 | ┌────────┬─────────────────────────┬─────────────────────────┬─────────────────────────┬────────┬────────┬────────┐ 544 | │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 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│ 545 | │* │ ┊ ┊ │ ┊ ┊ │ 546 | │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 │ו⋄⋄⋄×⋄ ┊@⋄ו⋄⋄⋄×┊•⋄⋄⋄××ו│ 547 | │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 │⋄⋄⋄××⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│ 548 | │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 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│ 549 | │* │ ┊ ┊ │ ┊ ┊ │ 550 | │000013f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 ┊ │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄ ┊ │ 551 | └────────┴─────────────────────────┴─────────────────────────┴─────────────────────────┴────────┴────────┴────────┘ 552 | ", 553 | ); 554 | } 555 | 556 | #[test] 557 | fn squeeze_plain() { 558 | hexyl() 559 | .arg("hello_world_elf64") 560 | .arg("--color=never") 561 | .arg("--skip=1024") 562 | .arg("--length=4096") 563 | .arg("--plain") 564 | .assert() 565 | .success() 566 | .pretty_stdout( 567 | " \ 568 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 569 | * 570 | ba 0e 00 00 00 b9 00 20 40 00 bb 01 00 00 00 b8 571 | 04 00 00 00 cd 80 b8 01 00 00 00 cd 80 00 00 00 572 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 573 | * 574 | * 575 | ", 576 | ); 577 | } 578 | 579 | #[test] 580 | fn squeeze_plain_remainder() { 581 | hexyl() 582 | .arg("hello_world_elf64") 583 | .arg("--color=never") 584 | .arg("--skip=1024") 585 | .arg("--length=4092") // 4 byte remainder 586 | .arg("--plain") 587 | .assert() 588 | .success() 589 | .pretty_stdout( 590 | " \ 591 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 592 | * 593 | ba 0e 00 00 00 b9 00 20 40 00 bb 01 00 00 00 b8 594 | 04 00 00 00 cd 80 b8 01 00 00 00 cd 80 00 00 00 595 | 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 596 | * 597 | 00 00 00 00 00 00 00 00 00 00 00 00 598 | ", 599 | ); 600 | } 601 | } 602 | 603 | mod base { 604 | use super::hexyl; 605 | use super::PrettyAssert; 606 | 607 | #[test] 608 | fn base2() { 609 | hexyl() 610 | .arg("ascii") 611 | .arg("--plain") 612 | .arg("--base=binary") 613 | .assert() 614 | .success() 615 | .pretty_stdout( 616 | " 00110000 00110001 00110010 00110011 00110100 00110101 00110110 00110111 \n \ 617 | 00111000 00111001 01100001 01100010 01100011 01100100 01100101 00001010 \n", 618 | ); 619 | } 620 | } 621 | 622 | mod character_table { 623 | use super::hexyl; 624 | use super::PrettyAssert; 625 | 626 | #[test] 627 | fn ascii() { 628 | hexyl() 629 | .arg("hello_world_elf64") 630 | .arg("--color=never") 631 | .arg("--character-table=ascii") 632 | .assert() 633 | .success() 634 | .pretty_stdout( 635 | "┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐ 636 | │00000000│ 7f 45 4c 46 02 01 01 00 ┊ 00 00 00 00 00 00 00 00 │.ELF....┊........│ 637 | │00000010│ 02 00 3e 00 01 00 00 00 ┊ 00 10 40 00 00 00 00 00 │..>.....┊..@.....│ 638 | │00000020│ 40 00 00 00 00 00 00 00 ┊ 28 20 00 00 00 00 00 00 │@.......┊( ......│ 639 | │00000030│ 00 00 00 00 40 00 38 00 ┊ 03 00 40 00 04 00 03 00 │....@.8.┊..@.....│ 640 | │00000040│ 01 00 00 00 04 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│ 641 | │00000050│ 00 00 40 00 00 00 00 00 ┊ 00 00 40 00 00 00 00 00 │..@.....┊..@.....│ 642 | │00000060│ e8 00 00 00 00 00 00 00 ┊ e8 00 00 00 00 00 00 00 │........┊........│ 643 | │00000070│ 00 10 00 00 00 00 00 00 ┊ 01 00 00 00 05 00 00 00 │........┊........│ 644 | │00000080│ 00 10 00 00 00 00 00 00 ┊ 00 10 40 00 00 00 00 00 │........┊..@.....│ 645 | │00000090│ 00 10 40 00 00 00 00 00 ┊ 1d 00 00 00 00 00 00 00 │..@.....┊........│ 646 | │000000a0│ 1d 00 00 00 00 00 00 00 ┊ 00 10 00 00 00 00 00 00 │........┊........│ 647 | │000000b0│ 01 00 00 00 06 00 00 00 ┊ 00 20 00 00 00 00 00 00 │........┊. ......│ 648 | │000000c0│ 00 20 40 00 00 00 00 00 ┊ 00 20 40 00 00 00 00 00 │. @.....┊. @.....│ 649 | │000000d0│ 0e 00 00 00 00 00 00 00 ┊ 0e 00 00 00 00 00 00 00 │........┊........│ 650 | │000000e0│ 00 10 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│ 651 | │000000f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│ 652 | │* │ ┊ │ ┊ │ 653 | │00001000│ ba 0e 00 00 00 b9 00 20 ┊ 40 00 bb 01 00 00 00 b8 │....... ┊@.......│ 654 | │00001010│ 04 00 00 00 cd 80 b8 01 ┊ 00 00 00 cd 80 00 00 00 │........┊........│ 655 | │00001020│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│ 656 | │* │ ┊ │ ┊ │ 657 | │00002000│ 48 65 6c 6c 6f 2c 20 77 ┊ 6f 72 6c 64 21 0a 00 2e │Hello, w┊orld!...│ 658 | │00002010│ 73 68 73 74 72 74 61 62 ┊ 00 2e 74 65 78 74 00 2e │shstrtab┊..text..│ 659 | │00002020│ 64 61 74 61 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │data....┊........│ 660 | │00002030│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│ 661 | │* │ ┊ │ ┊ │ 662 | │00002060│ 00 00 00 00 00 00 00 00 ┊ 0b 00 00 00 01 00 00 00 │........┊........│ 663 | │00002070│ 06 00 00 00 00 00 00 00 ┊ 00 10 40 00 00 00 00 00 │........┊..@.....│ 664 | │00002080│ 00 10 00 00 00 00 00 00 ┊ 1d 00 00 00 00 00 00 00 │........┊........│ 665 | │00002090│ 00 00 00 00 00 00 00 00 ┊ 10 00 00 00 00 00 00 00 │........┊........│ 666 | │000020a0│ 00 00 00 00 00 00 00 00 ┊ 11 00 00 00 01 00 00 00 │........┊........│ 667 | │000020b0│ 03 00 00 00 00 00 00 00 ┊ 00 20 40 00 00 00 00 00 │........┊. @.....│ 668 | │000020c0│ 00 20 00 00 00 00 00 00 ┊ 0e 00 00 00 00 00 00 00 │. ......┊........│ 669 | │000020d0│ 00 00 00 00 00 00 00 00 ┊ 04 00 00 00 00 00 00 00 │........┊........│ 670 | │000020e0│ 00 00 00 00 00 00 00 00 ┊ 01 00 00 00 03 00 00 00 │........┊........│ 671 | │000020f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│ 672 | │00002100│ 0e 20 00 00 00 00 00 00 ┊ 17 00 00 00 00 00 00 00 │. ......┊........│ 673 | │00002110│ 00 00 00 00 00 00 00 00 ┊ 01 00 00 00 00 00 00 00 │........┊........│ 674 | │00002120│ 00 00 00 00 00 00 00 00 ┊ │........┊ │ 675 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘ 676 | ", 677 | ); 678 | } 679 | 680 | #[test] 681 | fn codepage_437() { 682 | hexyl() 683 | .arg("hello_world_elf64") 684 | .arg("--color=never") 685 | .arg("--character-table=codepage-437") 686 | .assert() 687 | .success() 688 | .pretty_stdout( 689 | "┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐ 690 | │00000000│ 7f 45 4c 46 02 01 01 00 ┊ 00 00 00 00 00 00 00 00 │⌂ELF☻☺☺⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│ 691 | │00000010│ 02 00 3e 00 01 00 00 00 ┊ 00 10 40 00 00 00 00 00 │☻⋄>⋄☺⋄⋄⋄┊⋄►@⋄⋄⋄⋄⋄│ 692 | │00000020│ 40 00 00 00 00 00 00 00 ┊ 28 20 00 00 00 00 00 00 │@⋄⋄⋄⋄⋄⋄⋄┊( ⋄⋄⋄⋄⋄⋄│ 693 | │00000030│ 00 00 00 00 40 00 38 00 ┊ 03 00 40 00 04 00 03 00 │⋄⋄⋄⋄@⋄8⋄┊♥⋄@⋄♦⋄♥⋄│ 694 | │00000040│ 01 00 00 00 04 00 00 00 ┊ 00 00 00 00 00 00 00 00 │☺⋄⋄⋄♦⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│ 695 | │00000050│ 00 00 40 00 00 00 00 00 ┊ 00 00 40 00 00 00 00 00 │⋄⋄@⋄⋄⋄⋄⋄┊⋄⋄@⋄⋄⋄⋄⋄│ 696 | │00000060│ e8 00 00 00 00 00 00 00 ┊ e8 00 00 00 00 00 00 00 │Φ⋄⋄⋄⋄⋄⋄⋄┊Φ⋄⋄⋄⋄⋄⋄⋄│ 697 | │00000070│ 00 10 00 00 00 00 00 00 ┊ 01 00 00 00 05 00 00 00 │⋄►⋄⋄⋄⋄⋄⋄┊☺⋄⋄⋄♣⋄⋄⋄│ 698 | │00000080│ 00 10 00 00 00 00 00 00 ┊ 00 10 40 00 00 00 00 00 │⋄►⋄⋄⋄⋄⋄⋄┊⋄►@⋄⋄⋄⋄⋄│ 699 | │00000090│ 00 10 40 00 00 00 00 00 ┊ 1d 00 00 00 00 00 00 00 │⋄►@⋄⋄⋄⋄⋄┊↔⋄⋄⋄⋄⋄⋄⋄│ 700 | │000000a0│ 1d 00 00 00 00 00 00 00 ┊ 00 10 00 00 00 00 00 00 │↔⋄⋄⋄⋄⋄⋄⋄┊⋄►⋄⋄⋄⋄⋄⋄│ 701 | │000000b0│ 01 00 00 00 06 00 00 00 ┊ 00 20 00 00 00 00 00 00 │☺⋄⋄⋄♠⋄⋄⋄┊⋄ ⋄⋄⋄⋄⋄⋄│ 702 | │000000c0│ 00 20 40 00 00 00 00 00 ┊ 00 20 40 00 00 00 00 00 │⋄ @⋄⋄⋄⋄⋄┊⋄ @⋄⋄⋄⋄⋄│ 703 | │000000d0│ 0e 00 00 00 00 00 00 00 ┊ 0e 00 00 00 00 00 00 00 │♫⋄⋄⋄⋄⋄⋄⋄┊♫⋄⋄⋄⋄⋄⋄⋄│ 704 | │000000e0│ 00 10 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄►⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│ 705 | │000000f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│ 706 | │* │ ┊ │ ┊ │ 707 | │00001000│ ba 0e 00 00 00 b9 00 20 ┊ 40 00 bb 01 00 00 00 b8 │║♫⋄⋄⋄╣⋄ ┊@⋄╗☺⋄⋄⋄╕│ 708 | │00001010│ 04 00 00 00 cd 80 b8 01 ┊ 00 00 00 cd 80 00 00 00 │♦⋄⋄⋄═Ç╕☺┊⋄⋄⋄═Ç⋄⋄⋄│ 709 | │00001020│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│ 710 | │* │ ┊ │ ┊ │ 711 | │00002000│ 48 65 6c 6c 6f 2c 20 77 ┊ 6f 72 6c 64 21 0a 00 2e │Hello, w┊orld!◙⋄.│ 712 | │00002010│ 73 68 73 74 72 74 61 62 ┊ 00 2e 74 65 78 74 00 2e │shstrtab┊⋄.text⋄.│ 713 | │00002020│ 64 61 74 61 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │data⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│ 714 | │00002030│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│ 715 | │* │ ┊ │ ┊ │ 716 | │00002060│ 00 00 00 00 00 00 00 00 ┊ 0b 00 00 00 01 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊♂⋄⋄⋄☺⋄⋄⋄│ 717 | │00002070│ 06 00 00 00 00 00 00 00 ┊ 00 10 40 00 00 00 00 00 │♠⋄⋄⋄⋄⋄⋄⋄┊⋄►@⋄⋄⋄⋄⋄│ 718 | │00002080│ 00 10 00 00 00 00 00 00 ┊ 1d 00 00 00 00 00 00 00 │⋄►⋄⋄⋄⋄⋄⋄┊↔⋄⋄⋄⋄⋄⋄⋄│ 719 | │00002090│ 00 00 00 00 00 00 00 00 ┊ 10 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊►⋄⋄⋄⋄⋄⋄⋄│ 720 | │000020a0│ 00 00 00 00 00 00 00 00 ┊ 11 00 00 00 01 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊◄⋄⋄⋄☺⋄⋄⋄│ 721 | │000020b0│ 03 00 00 00 00 00 00 00 ┊ 00 20 40 00 00 00 00 00 │♥⋄⋄⋄⋄⋄⋄⋄┊⋄ @⋄⋄⋄⋄⋄│ 722 | │000020c0│ 00 20 00 00 00 00 00 00 ┊ 0e 00 00 00 00 00 00 00 │⋄ ⋄⋄⋄⋄⋄⋄┊♫⋄⋄⋄⋄⋄⋄⋄│ 723 | │000020d0│ 00 00 00 00 00 00 00 00 ┊ 04 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊♦⋄⋄⋄⋄⋄⋄⋄│ 724 | │000020e0│ 00 00 00 00 00 00 00 00 ┊ 01 00 00 00 03 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊☺⋄⋄⋄♥⋄⋄⋄│ 725 | │000020f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│ 726 | │00002100│ 0e 20 00 00 00 00 00 00 ┊ 17 00 00 00 00 00 00 00 │♫ ⋄⋄⋄⋄⋄⋄┊↨⋄⋄⋄⋄⋄⋄⋄│ 727 | │00002110│ 00 00 00 00 00 00 00 00 ┊ 01 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊☺⋄⋄⋄⋄⋄⋄⋄│ 728 | │00002120│ 00 00 00 00 00 00 00 00 ┊ │⋄⋄⋄⋄⋄⋄⋄⋄┊ │ 729 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘ 730 | ", 731 | ); 732 | } 733 | 734 | #[test] 735 | fn codepage_1047() { 736 | hexyl() 737 | .arg("hello_world_elf64") 738 | .arg("--color=never") 739 | .arg("--character-table=codepage-1047") 740 | .assert() 741 | .success() 742 | .pretty_stdout( 743 | "┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐ 744 | │00000000│ 7f 45 4c 46 02 01 01 00 ┊ 00 00 00 00 00 00 00 00 │..<.....┊........│ 745 | │00000010│ 02 00 3e 00 01 00 00 00 ┊ 00 10 40 00 00 00 00 00 │........┊.. .....│ 746 | │00000020│ 40 00 00 00 00 00 00 00 ┊ 28 20 00 00 00 00 00 00 │ .......┊........│ 747 | │00000030│ 00 00 00 00 40 00 38 00 ┊ 03 00 40 00 04 00 03 00 │.... ...┊.. .....│ 748 | │00000040│ 01 00 00 00 04 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│ 749 | │00000050│ 00 00 40 00 00 00 00 00 ┊ 00 00 40 00 00 00 00 00 │.. .....┊.. .....│ 750 | │00000060│ e8 00 00 00 00 00 00 00 ┊ e8 00 00 00 00 00 00 00 │Y.......┊Y.......│ 751 | │00000070│ 00 10 00 00 00 00 00 00 ┊ 01 00 00 00 05 00 00 00 │........┊........│ 752 | │00000080│ 00 10 00 00 00 00 00 00 ┊ 00 10 40 00 00 00 00 00 │........┊.. .....│ 753 | │00000090│ 00 10 40 00 00 00 00 00 ┊ 1d 00 00 00 00 00 00 00 │.. .....┊........│ 754 | │000000a0│ 1d 00 00 00 00 00 00 00 ┊ 00 10 00 00 00 00 00 00 │........┊........│ 755 | │000000b0│ 01 00 00 00 06 00 00 00 ┊ 00 20 00 00 00 00 00 00 │........┊........│ 756 | │000000c0│ 00 20 40 00 00 00 00 00 ┊ 00 20 40 00 00 00 00 00 │.. .....┊.. .....│ 757 | │000000d0│ 0e 00 00 00 00 00 00 00 ┊ 0e 00 00 00 00 00 00 00 │........┊........│ 758 | │000000e0│ 00 10 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│ 759 | │000000f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│ 760 | │* │ ┊ │ ┊ │ 761 | │00001000│ ba 0e 00 00 00 b9 00 20 ┊ 40 00 bb 01 00 00 00 b8 │[.......┊ .].....│ 762 | │00001010│ 04 00 00 00 cd 80 b8 01 ┊ 00 00 00 cd 80 00 00 00 │........┊........│ 763 | │00001020│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│ 764 | │* │ ┊ │ ┊ │ 765 | │00002000│ 48 65 6c 6c 6f 2c 20 77 ┊ 6f 72 6c 64 21 0a 00 2e │..%%?...┊?.%.....│ 766 | │00002010│ 73 68 73 74 72 74 61 62 ┊ 00 2e 74 65 78 74 00 2e │....../.┊........│ 767 | │00002020│ 64 61 74 61 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │././....┊........│ 768 | │00002030│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│ 769 | │* │ ┊ │ ┊ │ 770 | │00002060│ 00 00 00 00 00 00 00 00 ┊ 0b 00 00 00 01 00 00 00 │........┊........│ 771 | │00002070│ 06 00 00 00 00 00 00 00 ┊ 00 10 40 00 00 00 00 00 │........┊.. .....│ 772 | │00002080│ 00 10 00 00 00 00 00 00 ┊ 1d 00 00 00 00 00 00 00 │........┊........│ 773 | │00002090│ 00 00 00 00 00 00 00 00 ┊ 10 00 00 00 00 00 00 00 │........┊........│ 774 | │000020a0│ 00 00 00 00 00 00 00 00 ┊ 11 00 00 00 01 00 00 00 │........┊........│ 775 | │000020b0│ 03 00 00 00 00 00 00 00 ┊ 00 20 40 00 00 00 00 00 │........┊.. .....│ 776 | │000020c0│ 00 20 00 00 00 00 00 00 ┊ 0e 00 00 00 00 00 00 00 │........┊........│ 777 | │000020d0│ 00 00 00 00 00 00 00 00 ┊ 04 00 00 00 00 00 00 00 │........┊........│ 778 | │000020e0│ 00 00 00 00 00 00 00 00 ┊ 01 00 00 00 03 00 00 00 │........┊........│ 779 | │000020f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│ 780 | │00002100│ 0e 20 00 00 00 00 00 00 ┊ 17 00 00 00 00 00 00 00 │........┊........│ 781 | │00002110│ 00 00 00 00 00 00 00 00 ┊ 01 00 00 00 00 00 00 00 │........┊........│ 782 | │00002120│ 00 00 00 00 00 00 00 00 ┊ │........┊ │ 783 | └────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘ 784 | ", 785 | ); 786 | } 787 | } 788 | --------------------------------------------------------------------------------