52 Commits
Author SHA1 Message Date
David Peter 6ecc29b9c8 Remove sponsors 2026-04-30 10:24:05 +02:00
David Peter 8eb6d4771c Bump version to v0.17.0 2026-02-14 13:53:54 +01:00
David Peter 5d05382df8 Update README 2026-02-14 13:50:33 +01:00
David Peter 626f03bfbd Fix #254 2026-02-14 13:49:10 +01:00
n4n5 04d74bae47 feat: handle standard input "-" (#257) 2026-02-14 13:33:23 +01:00
n4n5 96e427cb4a Improve crate && fix CI (#256)
* feat: improve crate

* fix: CI
2026-02-14 13:09:13 +01:00
Filip Czaplicki 7f8db4f113 Add colors to --help (#253)
Thank you!
2026-02-14 13:08:00 +01:00
王品呈 2e2643782d Add option to output result in C include file style (#242) (#246)
* feat: Implement "-i/-include" option

* make clippy happy

Suppress clippy warning for too many arguments in Printer struct

* add tests & use buffered reading for include mode to handle large files

* Update CHANGELOG
2025-10-19 21:38:21 +02:00
David Peter 269860ba7e Update README 2025-09-23 19:38:25 +02:00
David Peter 2c8be77181 Update README 2025-09-04 09:00:22 +02:00
David Peter f16e2c96ea Fix CHANGELOG formatting 2025-08-31 21:40:15 +02:00
Friedrich 'friedɀ' SchwedlerandDavid Peter 585d06293a Add shell completion (#251)
* add commandline argument to generate shell completion

* update changelog

* fixup! add commandline argument to generate shell completion

* Update option name and description

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-08-31 21:39:17 +02:00
David Peter 909dd7d947 Update Windows runners 2025-08-31 21:22:59 +02:00
David Peter 5a295296fe Fix formatting 2025-08-31 21:21:22 +02:00
David Peter 38301f3759 Fix clippy, CI 2025-08-31 21:17:54 +02:00
Niclas SchwarzloseandDavid Peter f2547aed69 Add new braille character table and color schemes (#247)
* Add new braille character table and color schemes

* Add character for tab and change color for 0x7f

---------

Co-authored-by: David Peter <mail@david-peter.de>
2025-08-31 21:13:57 +02:00
Dan Hipschman 9ef7c346dd Enable custom colors with environment variables (#248)
* Enable custom colors with environment variables

* Rename HEXYL_XYZ env vars to HEXYL_COLOR_XYZ
2025-06-16 22:41:37 +02:00
Regis Boudinot 6220cae644 Check if terminal_width is less than offset and return 1 (see #244) (#245) 2025-06-08 21:35:42 +02:00
David Peter ad553e9e33 Merge pull request #249 from dan-hipschman/fix-ci
Fix CI
2025-06-08 21:11:18 +02:00
Dan Hipschman fbbd5e3733 Fix CI 2025-05-13 15:18:02 -07:00
David Peter 742c2fcbfd Update README 2025-03-09 19:29:26 +01:00
David Peter 79f7076b02 Merge pull request #239 from ilmanzo/issue238
Fix issue #238
2025-03-02 19:28:12 +01:00
David Peter cba09bf7b7 Merge pull request #241 from saulvaldelvira/master
Fix some clippy warnings
2025-02-19 21:39:49 +01:00
Saúl Valdelvira 06ff4b750e Fix some clippy warnings
Changes:
- Elid lifetimes when possible
- Use a slice instead of a Vec as function argument
- Rewrite &[b'*''] to b"*"
2025-02-19 12:44:08 +01:00
Andrea Manzini 630d92fb8f Fix issue #238 2025-01-26 18:54:13 +01:00
David Peter abc20a380c Bump version 2024-12-27 14:43:18 +01:00
David Peter dd89c22327 Remove color reference from README 2024-12-27 14:42:27 +01:00
David Peter 45a557e0df Merge pull request #235 from sahinfalcon/print-colour-table
PR #229 • Add option to print color table
2024-12-27 14:41:12 +01:00
David Peter 73cfd05a10 Minor updates 2024-12-27 14:37:04 +01:00
Taylan Sahin 37e9ef6a64 cargo fmt 2024-12-27 14:35:04 +01:00
Taylan Sahin 568f38ae77 PR #229 • Add option to print color table 2024-12-27 14:35:03 +01:00
David Peter 6e0bff2cc5 Merge pull request #234 from Integral-Tech/error-on-dir
fix: throw an error when try to view a directory
2024-12-27 14:29:34 +01:00
David Peter 1783fe6a9b Merge pull request #233 from lunrenyi/patch-1
docs(install): add x-cmd method to install hexyl
2024-12-27 14:26:02 +01:00
David Peter 733b9e12e4 Fix CI 2024-12-27 14:25:01 +01:00
David Peter 19198c2aac Minor updates 2024-12-27 14:23:02 +01:00
Integral c65b298e60 fix: throw an error when try to view a directory 2024-11-21 00:55:37 +08:00
lunrenyi f986c196d2 docs(install): add x-cmd method to install hexyl 2024-11-12 14:30:33 +08:00
David Peter dbed12e423 Update MSRV 2024-10-29 22:44:53 +01:00
David Peter 1927610953 Update MSRV 2024-10-29 22:42:12 +01:00
David Peter f8531b3e44 Updates for v0.15.0 2024-10-29 22:17:12 +01:00
David Peter 427a5524e1 Merge pull request #226 from v1gnesh/master
Add codepage 1047 for EBCDIC
2024-09-08 13:09:17 +02:00
v1gnesh 120cf29adf Add codepage 1047 for EBCDIC 2024-08-04 15:18:38 +05:30
David Peter d2cc025c6f Merge pull request #225 from sorairolake/rewrite-in-derive-api
Rewrite CLI using the derive API
2024-07-16 21:29:59 +02:00
Shun Sakai f8b87cc5e2 Change help output
Even if `-h` is specified, other than the first sentence will also be
printed.
2024-07-02 18:23:30 +09:00
Shun Sakai 252a6dbef4 Rewrite CLI using derive API 2024-07-01 22:39:28 +09:00
David Peter 16cc5d9757 Merge pull request #222 from wxpppp/master
deps: bump libc from 0.2.147 to 0.2.155
2024-06-22 21:35:19 +02:00
wangweijie d51086e0da deps: bump libc from 0.2.147 to 0.2.155
Signed-off-by: wangweijie <wangweijie@loongson.cn>
2024-06-06 10:17:30 +08:00
David Peter d8a26da964 Merge pull request #216 from FilipRazek/fix-upload-artifact-job
ci: Fix Debian artifact names
2024-01-25 09:15:36 +01:00
Filip Razek dcce9e1011 ci: Fix Debian artifact names 2024-01-24 22:38:28 +01:00
David Peter 651087ca9d Remove 'simple' adjective 2023-12-11 23:42:43 +01:00
David Peter 3bd84fd5cb Adapt version number 2023-12-11 23:42:22 +01:00
David Peter 0634a06470 Update CHANGELOG 2023-12-11 23:38:51 +01:00
12 changed files with 1485 additions and 817 deletions

No files matched your search

+19 -17
View File
@@ -36,7 +36,7 @@ jobs:
ensure_cargo_fmt:
name: Ensure 'cargo fmt' has been run
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: dtolnay/rust-toolchain@stable
with:
@@ -46,7 +46,7 @@ jobs:
min_version:
name: Minimum supported rust version
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
needs: crate_metadata
steps:
- name: Checkout source code
@@ -70,17 +70,19 @@ jobs:
fail-fast: false
matrix:
job:
- { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
- { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true }
- { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true }
- { target: i686-pc-windows-msvc , os: windows-2019 }
- { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
- { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
- { target: x86_64-apple-darwin , os: macos-12 }
- { target: x86_64-pc-windows-gnu , os: windows-2019 }
- { target: x86_64-pc-windows-msvc , os: windows-2019 }
- { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
- { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
- { target: aarch64-unknown-linux-gnu , os: ubuntu-24.04, use-cross: true }
- { target: arm-unknown-linux-gnueabihf , os: ubuntu-24.04, use-cross: true }
- { target: arm-unknown-linux-musleabihf, os: ubuntu-24.04, use-cross: true }
- { target: i686-pc-windows-msvc , os: windows-2025 }
- { target: i686-unknown-linux-gnu , os: ubuntu-24.04, use-cross: true }
- { target: i686-unknown-linux-musl , os: ubuntu-24.04, use-cross: true }
- { target: x86_64-apple-darwin , os: macos-15-intel }
- { target: aarch64-apple-darwin , os: macos-15 }
# Was causing CI failures unrelated to app logic
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
- { target: x86_64-pc-windows-msvc , os: windows-2025 }
- { target: x86_64-unknown-linux-gnu , os: ubuntu-24.04, use-cross: true }
- { target: x86_64-unknown-linux-musl , os: ubuntu-24.04, use-cross: true }
env:
BUILD_CMD: cargo
steps:
@@ -154,7 +156,7 @@ jobs:
run: |
# test only library unit tests and binary for arm-type targets
unset CARGO_TEST_OPTIONS
unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--lib --bin ${{ needs.crate_metadata.outputs.name }}" ;; esac;
case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--lib --bin ${{ needs.crate_metadata.outputs.name }}" ;; esac;
echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_OUTPUT
- name: Run tests
@@ -212,7 +214,7 @@ jobs:
DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}
DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }}-musl
case ${{ matrix.job.target }} in *-musl) DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-musl ; DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }} ;; esac;
case ${{ matrix.job.target }} in *-musl*) DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-musl ; DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }} ;; esac;
DPKG_VERSION=${{ needs.crate_metadata.outputs.version }}
unset DPKG_ARCH
@@ -304,13 +306,13 @@ jobs:
fakeroot dpkg-deb --build "${DPKG_DIR}" "${DPKG_PATH}"
- name: "Artifact upload: tarball"
uses: actions/upload-artifact@master
uses: actions/upload-artifact@main
with:
name: ${{ steps.package.outputs.PKG_NAME }}
path: ${{ steps.package.outputs.PKG_PATH }}
- name: "Artifact upload: Debian package"
uses: actions/upload-artifact@master
uses: actions/upload-artifact@main
if: steps.debian-package.outputs.DPKG_NAME
with:
name: ${{ steps.debian-package.outputs.DPKG_NAME }}
+50
View File
@@ -1,3 +1,53 @@
# v0.17.0
## Features
- Add option to output result in C include file style, see #242 (@wpcwzy)
- Add `--color-scheme` option, see #247 (@aticu)
- Add `braille` character table, see #247 (@aticu)
- Add command line argument to generate shell completion, see #155 (@friedz)
- Add colors to `--help`/`-h`, see #253 (@starsep)
## Bugfixes
- Fix memory allocation bug when terminal width is less than 10, see #244 (@selfup)
# v0.16.0
## Features
* New `--print-color-table` option, see #229 (@sahinfalcon)
## Bugfixes
- Throw an error when try to view a directory, see #234 (@Integral-Tech)
# v0.15.0
## Features
- Add codepage 1047 for EBCDIC, see #226 (@v1gnesh)
## Other
- Rewrite CLI using the derive API, see #225 (@sorairolake)
# v0.14.0
## Features
* 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)
* New `--character-table=ascii` option for a ASCII-only character table, see #212 and #36 (@sharkdp)
## Bugfixes
* Show output when doing `hexyl /dev/zero`, see #211 (@sharifhsn)
* Respect NO_COLOR environment variable, see #210 (@sharkdp)
# v0.13.1
## Bugfixes
Generated
+180 -219
View File
@@ -1,80 +1,80 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "aho-corasick"
version = "1.0.4"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "anstream"
version = "0.3.2"
version = "0.6.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is-terminal",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.1"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd"
checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56"
[[package]]
name = "anstyle-parse"
version = "0.2.1"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "938874ff5980b03a87c5524b3ae5b59cf99b1d6bc836848df7bc5ada9643c333"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "1.0.2"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [
"anstyle",
"windows-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "anyhow"
version = "1.0.75"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8"
[[package]]
name = "assert_cmd"
version = "2.0.12"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6"
checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514"
dependencies = [
"anstyle",
"bstr",
"doc-comment",
"libc",
"predicates",
"predicates-core",
"predicates-tree",
@@ -83,91 +83,97 @@ dependencies = [
[[package]]
name = "autocfg"
version = "1.1.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bitflags"
version = "1.3.2"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bstr"
version = "1.6.0"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
dependencies = [
"memchr",
"regex-automata",
"serde",
]
[[package]]
name = "cc"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [
"libc",
]
[[package]]
name = "clap"
version = "4.3.23"
version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03aef18ddf7d879c15ce20f04826ef8418101c7e528014c3eeea13321047dca3"
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.3.23"
version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ce6fffb678c9b80a70b6b6de0aad31df727623a70fd9a842c30cd573e2fa98"
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"once_cell",
"strsim",
"terminal_size",
]
[[package]]
name = "clap_lex"
version = "0.5.0"
name = "clap_complete"
version = "4.5.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a"
dependencies = [
"clap",
]
[[package]]
name = "clap_derive"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
[[package]]
name = "colorchoice"
version = "1.0.0"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "const_format"
version = "0.2.31"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c990efc7a285731f9a4378d81aff2f0e85a2c8781a05ef0f8baa8dac54d0ff48"
checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b"
dependencies = [
"const_format_proc_macros",
]
[[package]]
name = "const_format_proc_macros"
version = "0.2.31"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e026b6ce194a874cb9cf32cd5772d1ef9767cc8fcb5765948d74f37a9d8b2bf6"
checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1"
dependencies = [
"proc-macro2",
"quote",
@@ -186,37 +192,14 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "either"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
[[package]]
name = "errno"
version = "0.3.2"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
"windows-sys 0.52.0",
]
[[package]]
@@ -229,18 +212,19 @@ dependencies = [
]
[[package]]
name = "hermit-abi"
version = "0.3.2"
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hexyl"
version = "0.14.0"
version = "0.17.0"
dependencies = [
"anyhow",
"assert_cmd",
"clap",
"clap_complete",
"const_format",
"libc",
"owo-colors",
@@ -251,66 +235,35 @@ dependencies = [
"thiserror",
]
[[package]]
name = "io-lifetimes"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi",
"rustix 0.38.8",
"windows-sys",
]
[[package]]
name = "is_ci"
version = "1.1.1"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
[[package]]
name = "itertools"
version = "0.10.5"
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "libc"
version = "0.2.147"
version = "0.2.161"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]]
name = "linux-raw-sys"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
[[package]]
name = "memchr"
version = "2.5.0"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "normalize-line-endings"
@@ -320,35 +273,28 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
[[package]]
name = "num-traits"
version = "0.2.16"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "owo-colors"
version = "3.5.0"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56"
[[package]]
name = "predicates"
version = "3.0.3"
version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9"
checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97"
dependencies = [
"anstyle",
"difflib",
"float-cmp",
"itertools",
"normalize-line-endings",
"predicates-core",
"regex",
@@ -356,15 +302,15 @@ dependencies = [
[[package]]
name = "predicates-core"
version = "1.0.6"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174"
checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931"
[[package]]
name = "predicates-tree"
version = "1.0.9"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf"
checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13"
dependencies = [
"predicates-core",
"termtree",
@@ -372,9 +318,9 @@ dependencies = [
[[package]]
name = "pretty_assertions"
version = "1.4.0"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
dependencies = [
"diff",
"yansi",
@@ -382,27 +328,27 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.66"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.33"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.9.3"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
@@ -412,9 +358,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.3.6"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
dependencies = [
"aho-corasick",
"memchr",
@@ -423,64 +369,63 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.7.4"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustix"
version = "0.37.23"
version = "0.38.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a"
dependencies = [
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys 0.3.8",
"windows-sys",
]
[[package]]
name = "rustix"
version = "0.38.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f"
dependencies = [
"bitflags 2.4.0",
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.4.5",
"windows-sys",
"linux-raw-sys",
"windows-sys 0.52.0",
]
[[package]]
name = "serde"
version = "1.0.185"
version = "1.0.214"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31"
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.214"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "strsim"
version = "0.10.0"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "supports-color"
version = "2.0.0"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4950e7174bffabe99455511c39707310e7e9b440364a2fcb1cc21521be57b354"
checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77"
dependencies = [
"is-terminal",
"is_ci",
]
[[package]]
name = "syn"
version = "2.0.29"
version = "2.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
dependencies = [
"proc-macro2",
"quote",
@@ -489,12 +434,12 @@ dependencies = [
[[package]]
name = "terminal_size"
version = "0.2.6"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237"
checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef"
dependencies = [
"rustix 0.37.23",
"windows-sys",
"rustix",
"windows-sys 0.59.0",
]
[[package]]
@@ -505,18 +450,18 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
[[package]]
name = "thiserror"
version = "1.0.47"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.47"
version = "1.0.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
dependencies = [
"proc-macro2",
"quote",
@@ -525,21 +470,21 @@ dependencies = [
[[package]]
name = "unicode-ident"
version = "1.0.11"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-xid"
version = "0.2.4"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "utf8parse"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "wait-timeout"
@@ -552,22 +497,32 @@ dependencies = [
[[package]]
name = "windows-sys"
version = "0.48.0"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
@@ -576,48 +531,54 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "yansi"
version = "0.5.1"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+10 -9
View File
@@ -1,34 +1,35 @@
[package]
authors = ["David Peter <mail@david-peter.de>"]
categories = ["command-line-utilities"]
keywords = ["hex", "viewer"]
description = "A command-line hex viewer"
homepage = "https://github.com/sharkdp/hexyl"
license = "MIT/Apache-2.0"
name = "hexyl"
readme = "README.md"
repository = "https://github.com/sharkdp/hexyl"
version = "0.14.0"
version = "0.17.0"
edition = "2021"
rust-version = "1.66"
rust-version = "1.88"
[dependencies]
anyhow = "1.0"
const_format = "0.2"
libc = "0.2"
owo-colors = "3"
supports-color = "2"
owo-colors = "4"
supports-color = "3"
thiserror = "1.0"
terminal_size = "0.2"
terminal_size = "0.4"
clap_complete = "4"
[dependencies.clap]
version = "4"
default-features = false
features = ["std", "suggestions", "color", "wrap_help", "cargo", "help", "usage", "error-context"]
features = ["derive", "wrap_help"]
[dev-dependencies]
assert_cmd = "2.0"
assert_cmd = "2.1"
predicates = "3.0"
pretty_assertions = "1.3.0"
pretty_assertions = "1.4.0"
[profile.release]
lto = true
+22 -15
View File
@@ -4,7 +4,7 @@
[![](https://img.shields.io/crates/l/hexyl.svg?colorB=22ba4c)](https://crates.io/crates/hexyl)
![](https://img.shields.io/crates/v/hexyl.svg?colorB=00aa88)
`hexyl` is a simple hex viewer for the terminal. It uses a colored output to distinguish different categories
`hexyl` is a hex viewer for the terminal. It uses a colored output to distinguish different categories
of bytes (NULL bytes, printable ASCII characters, ASCII whitespace characters, other ASCII characters and non-ASCII).
## Preview
@@ -17,19 +17,6 @@ of bytes (NULL bytes, printable ASCII characters, ASCII whitespace characters, o
![](https://i.imgur.com/f8nm8g6.png)
## Color Reference
|Type of Byte|Color|ANSI Code |
|---|---|---|
|NULL|![#555753](https://placehold.co/10x10/555753/555753.png) Bright Black|90|
|OFFSET|![#555753](https://placehold.co/10x10/555753/555753.png) Bright Black|90|
|ASCII Printable|![#06989a](https://placehold.co/10x10/06989a/06989a.png) Cyan|36|
|ASCII Whitespace|![#4e9a06](https://placehold.co/10x10/4e9a06/4e9a06.png) Green|32|
|ASCII Other|![#4e9a06](https://placehold.co/10x10/4e9a06/4e9a06.png) Green|32|
|Non-ASCII|![#c4a000](https://placehold.co/10x10/c4a000/c4a000.png) Yellow|33|
*Colors taken from the Ubuntu terminal color scheme, they could look different in your terminal*
## Installation
@@ -45,7 +32,7 @@ If you use an older version of Ubuntu, you can download
the latest `.deb` package from the release page and install it via:
``` bash
sudo dpkg -i hexyl_0.13.1_amd64.deb # adapt version number and architecture
sudo dpkg -i hexyl_0.15.0_amd64.deb # adapt version number and architecture
```
### On Debian
@@ -189,6 +176,26 @@ sudo snap install hexyl
scoop install hexyl
```
### Via [X-CMD](https://x-cmd.com)
```
x env use hexyl
```
## Configuration
`hexyl` colors can be configured via environment variables. The variables used are as follows:
* `HEXYL_COLOR_ASCII_PRINTABLE`: Any non-whitespace printable ASCII character
* `HEXYL_COLOR_ASCII_WHITESPACE`: Whitespace such as space or newline (only visible in middle panel with byte values)
* `HEXYL_COLOR_ASCII_OTHER`: Any other ASCII character (< `0x80`) besides null
* `HEXYL_COLOR_NULL`: The null byte (`0x00`)
* `HEXYL_COLOR_NONASCII`: Any non-ASCII byte (> `0x7F`)
* `HEXYL_COLOR_OFFSET`: The lefthand file offset
The colors can be any of the 8 standard terminal colors: `black`, `blue`, `cyan`, `green`, `magenta`, `red`,
`yellow` and `white`. The "bright" variants are also supported (e.g., `bright blue`). Additionally, you can use
the RGB hex format, `#abcdef`. For example, `HEXYL_COLOR_ASCII_PRINTABLE=blue HEXYL_COLOR_ASCII_WHITESPACE="bright green"
HEXYL_COLOR_ASCII_OTHER="#ff7f99"`.
## License
+16
View File
@@ -106,6 +106,22 @@ _FILE_
**-V**, **\--version**
: Prints version information.
# ENVIRONMENT VARIABLES
**hexyl** colors can be configured via environment variables. The variables used are as follows:
: - **HEXYL_COLOR_ASCII_PRINTABLE**: Any non-whitespace printable ASCII character
- **HEXYL_COLOR_ASCII_WHITESPACE**: Whitespace such as space or newline (only visible in middle panel with byte values)
- **HEXYL_COLOR_ASCII_OTHER**: Any other ASCII character (< **0x80**) besides null
- **HEXYL_COLOR_NULL**: The null byte (**0x00**)
- **HEXYL_COLOR_NONASCII**: Any non-ASCII byte (> **0x7F**)
- **HEXYL_COLOR_OFFSET**: The lefthand file offset
The colors can be any of the 8 standard terminal colors: **black**, **blue**, **cyan**, **green**, **magenta**, **red**,
**yellow** and **white**. The "bright" variants are also supported (e.g., **bright blue**). Additionally, you can use
the RGB hex format, **#abcdef**. For example, **HEXYL_COLOR_ASCII_PRINTABLE=blue HEXYL_COLOR_ASCII_WHITESPACE="bright green"
HEXYL_COLOR_ASCII_OTHER="#ff7f99"**.
# NOTES
Source repository:
+1 -1
View File
@@ -3,7 +3,7 @@ use std::io;
use hexyl::{BorderStyle, PrinterBuilder};
fn main() {
let input = vec![
let input = [
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44,
0x52, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x44, 0x08, 0x02, 0x00, 0x00, 0x00,
];
+144 -8
View File
@@ -1,12 +1,114 @@
use owo_colors::{colors, Color};
use owo_colors::{colors, AnsiColors, Color, DynColors, OwoColorize};
use std::str::FromStr;
use std::sync::LazyLock;
pub const COLOR_NULL: &[u8] = colors::BrightBlack::ANSI_FG.as_bytes();
pub const COLOR_OFFSET: &[u8] = colors::BrightBlack::ANSI_FG.as_bytes();
pub const COLOR_ASCII_PRINTABLE: &[u8] = colors::Cyan::ANSI_FG.as_bytes();
pub const COLOR_ASCII_WHITESPACE: &[u8] = colors::Green::ANSI_FG.as_bytes();
pub const COLOR_ASCII_OTHER: &[u8] = colors::Green::ANSI_FG.as_bytes();
pub const COLOR_NONASCII: &[u8] = colors::Yellow::ANSI_FG.as_bytes();
pub const COLOR_RESET: &[u8] = colors::Default::ANSI_FG.as_bytes();
pub static COLOR_NULL: LazyLock<String> =
LazyLock::new(|| init_color("NULL", AnsiColors::BrightBlack));
pub static COLOR_OFFSET: LazyLock<String> =
LazyLock::new(|| init_color("OFFSET", AnsiColors::BrightBlack));
pub static COLOR_ASCII_PRINTABLE: LazyLock<String> =
LazyLock::new(|| init_color("ASCII_PRINTABLE", AnsiColors::Cyan));
pub static COLOR_ASCII_WHITESPACE: LazyLock<String> =
LazyLock::new(|| init_color("ASCII_WHITESPACE", AnsiColors::Green));
pub static COLOR_ASCII_OTHER: LazyLock<String> =
LazyLock::new(|| init_color("ASCII_OTHER", AnsiColors::Green));
pub static COLOR_NONASCII: LazyLock<String> =
LazyLock::new(|| init_color("NONASCII", AnsiColors::Yellow));
pub const COLOR_RESET: &str = colors::Default::ANSI_FG;
fn init_color(name: &str, default_ansi: AnsiColors) -> String {
let default = DynColors::Ansi(default_ansi);
let env_var = format!("HEXYL_COLOR_{name}");
let color = match std::env::var(env_var).as_deref() {
Ok(color) => match DynColors::from_str(color) {
Ok(color) => color,
_ => default,
},
_ => default,
};
// owo_colors' API isn't designed to get the terminal codes directly for
// dynamic colors, so we use this hack to get them from the LHS of some text.
format!("{}", "|".color(color))
.split_once("|")
.unwrap()
.0
.to_owned()
}
pub const COLOR_NULL_RGB: &[u8] = &rgb_bytes(100, 100, 100);
pub const COLOR_DEL: &[u8] = &rgb_bytes(64, 128, 0);
pub const COLOR_GRADIENT_NONASCII: [[u8; 19]; 128] =
generate_color_gradient(&[(255, 0, 0, 0.0), (255, 255, 0, 0.66), (255, 255, 255, 1.0)]);
pub const COLOR_GRADIENT_ASCII_NONPRINTABLE: [[u8; 19]; 31] =
generate_color_gradient(&[(255, 0, 255, 0.0), (128, 0, 255, 1.0)]);
pub const COLOR_GRADIENT_ASCII_PRINTABLE: [[u8; 19]; 95] =
generate_color_gradient(&[(0, 128, 255, 0.0), (0, 255, 128, 1.0)]);
const fn as_dec(byte: u8) -> [u8; 3] {
[
b'0' + (byte / 100),
b'0' + ((byte % 100) / 10),
b'0' + (byte % 10),
]
}
const fn rgb_bytes(r: u8, g: u8, b: u8) -> [u8; 19] {
let mut buf = *b"\x1b[38;2;rrr;ggg;bbbm";
// r 7
buf[7] = as_dec(r)[0];
buf[8] = as_dec(r)[1];
buf[9] = as_dec(r)[2];
// g 11
buf[11] = as_dec(g)[0];
buf[12] = as_dec(g)[1];
buf[13] = as_dec(g)[2];
// b 15
buf[15] = as_dec(b)[0];
buf[16] = as_dec(b)[1];
buf[17] = as_dec(b)[2];
buf
}
const fn generate_color_gradient<const N: usize>(stops: &[(u8, u8, u8, f64)]) -> [[u8; 19]; N] {
let mut out = [rgb_bytes(0, 0, 0); N];
assert!(stops.len() >= 2, "need at least two stops for the gradient");
let mut byte = 0;
while byte < N {
let relative_byte = byte as f64 / N as f64;
let mut i = 1;
while i < stops.len() && stops[i].3 < relative_byte {
i += 1;
}
if i >= stops.len() {
i = stops.len() - 1;
}
let prev_stop = stops[i - 1];
let stop = stops[i];
let diff = stop.3 - prev_stop.3;
let t = (relative_byte - prev_stop.3) / diff;
let r = (prev_stop.0 as f64 + (t * (stop.0 as f64 - prev_stop.0 as f64))) as u8;
let g = (prev_stop.1 as f64 + (t * (stop.1 as f64 - prev_stop.1 as f64))) as u8;
let b = (prev_stop.2 as f64 + (t * (stop.2 as f64 - prev_stop.2 as f64))) as u8;
out[byte] = rgb_bytes(r, g, b);
byte += 1;
}
out
}
#[rustfmt::skip]
pub const CP437: [char; 256] = [
@@ -46,3 +148,37 @@ pub const CP437: [char; 256] = [
'α','ß','Γ','π','Σ','σ','µ','τ','Φ','Θ','Ω','δ','∞','φ','ε','∩',
'≡','±','≥','≤','⌠','⌡','÷','≈','°','∙','·','√','ⁿ','²','■','ff',
];
#[rustfmt::skip]
pub const CP1047: [char; 256] = [
//
// Copyright (c) 2016,2024 IBM Corporation and other Contributors.
//
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// Contributors:
// Mark Taylor - Initial Contribution
//
// ref1 https://github.com/ibm-messaging/mq-smf-csv/blob/master/src/smfConv.c
// ref2 https://web.archive.org/web/20150607033635/http://www-01.ibm.com/software/globalization/cp/cp01047.html
'.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.',
'.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.',
'.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.',
'.','.','.','.','.','.','.','.','.','.','.','.','.','.','.','.',
' ','.','.','.','.','.','.','.','.','.','$','.','<','(','+','|',
'&','.','.','.','.','.','.','.','.','.','!','$','*',')',';','.',
'-','/','.','.','.','.','.','.','.','.','.',',','%','_','>','?',
'.','.','.','.','.','.','.','.','.','.',':','#','@','\'','=','.',
'.','a','b','c','d','e','f','g','h','i','.','{','.','(','+','.',
'.','j','k','l','m','n','o','p','q','r','.','}','.',')','.','.',
'.','~','s','t','u','v','w','x','y','z','.','.','.','.','.','.',
'.','.','.','.','.','.','.','.','.','.','[',']','.','.','.','-',
'{','A','B','C','D','E','F','G','H','I','.','.','.','.','.','.',
'}','J','K','L','M','N','O','P','Q','R','.','.','.','.','.','.',
'.','.','S','T','U','V','W','X','Y','Z','.','.','.','.','.','.',
'0','1','2','3','4','5','6','7','8','9','.','.','.','.','.','.'
];
+3 -4
View File
@@ -1,4 +1,3 @@
use std::convert::TryFrom;
use std::fs;
use std::io::{self, copy, sink, Read, Seek, SeekFrom};
@@ -7,7 +6,7 @@ pub enum Input<'a> {
Stdin(io::StdinLock<'a>),
}
impl<'a> Read for Input<'a> {
impl Read for Input<'_> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match *self {
Input::File(ref mut file) => file.read(buf),
@@ -16,13 +15,13 @@ impl<'a> Read for Input<'a> {
}
}
impl<'a> Seek for Input<'a> {
impl Seek for Input<'_> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
fn try_skip<R>(reader: R, pos: SeekFrom, err_desc: &'static str) -> io::Result<u64>
where
R: Read,
{
let cant_seek_abs_err = || Err(io::Error::new(io::ErrorKind::Other, err_desc));
let cant_seek_abs_err = || Err(io::Error::other(err_desc));
let offset = match pos {
SeekFrom::Current(o) => u64::try_from(o).or_else(|_e| cant_seek_abs_err())?,
+389 -152
View File
@@ -6,6 +6,8 @@ pub use input::*;
use std::io::{self, BufReader, Read, Write};
use clap::ValueEnum;
pub enum Base {
Binary,
Octal,
@@ -22,17 +24,59 @@ pub enum ByteCategory {
NonAscii,
}
#[derive(Copy, Clone)]
#[non_exhaustive]
pub enum CharacterTable {
Default,
Ascii,
CP437,
pub enum IncludeMode {
File(String), // filename
Stdin,
Slice,
Off,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug, Default, ValueEnum)]
#[non_exhaustive]
pub enum CharacterTable {
/// Show printable ASCII characters as-is, '⋄' for NULL bytes, ' ' for
/// space, '_' for other ASCII whitespace, '•' for other ASCII characters,
/// and '×' for non-ASCII bytes.
#[default]
Default,
/// Show printable ASCII as-is, ' ' for space, '.' for everything else.
Ascii,
/// Show printable EBCDIC as-is, ' ' for space, '.' for everything else.
#[value(name = "codepage-1047")]
CP1047,
/// Uses code page 437 (for non-ASCII bytes).
#[value(name = "codepage-437")]
CP437,
/// Uses braille characters for non-printable bytes.
Braille,
}
#[derive(Copy, Clone, Debug, Default, ValueEnum)]
#[non_exhaustive]
pub enum ColorScheme {
/// Show the default colors: bright black for NULL bytes, green for ASCII
/// space characters and non-printable ASCII, cyan for printable ASCII characters,
/// and yellow for non-ASCII bytes.
#[default]
Default,
/// Show bright black for NULL bytes, cyan for printable ASCII characters, a gradient
/// from pink to violet for non-printable ASCII characters and a heatmap-like gradient
/// from red to yellow to white for non-ASCII bytes.
Gradient,
}
#[derive(Copy, Clone, Debug, Default, ValueEnum)]
pub enum Endianness {
/// Print out groups in little-endian format.
Little,
/// Print out groups in big-endian format.
#[default]
Big,
}
@@ -62,14 +106,29 @@ impl Byte {
}
}
fn color(self) -> &'static [u8] {
fn color(self, color_scheme: ColorScheme) -> &'static [u8] {
use crate::ByteCategory::*;
match self.category() {
Null => COLOR_NULL,
AsciiPrintable => COLOR_ASCII_PRINTABLE,
AsciiWhitespace => COLOR_ASCII_WHITESPACE,
AsciiOther => COLOR_ASCII_OTHER,
NonAscii => COLOR_NONASCII,
match color_scheme {
ColorScheme::Default => match self.category() {
Null => COLOR_NULL.as_bytes(),
AsciiPrintable => COLOR_ASCII_PRINTABLE.as_bytes(),
AsciiWhitespace => COLOR_ASCII_WHITESPACE.as_bytes(),
AsciiOther => COLOR_ASCII_OTHER.as_bytes(),
NonAscii => COLOR_NONASCII.as_bytes(),
},
ColorScheme::Gradient => match self.category() {
Null => COLOR_NULL_RGB,
AsciiWhitespace if self.0 == b' ' => &COLOR_GRADIENT_ASCII_PRINTABLE[0],
AsciiPrintable => &COLOR_GRADIENT_ASCII_PRINTABLE[(self.0 - b' ') as usize],
AsciiWhitespace | AsciiOther => {
if self.0 == 0x7f {
COLOR_DEL
} else {
&COLOR_GRADIENT_ASCII_NONPRINTABLE[self.0 as usize - 1]
}
}
NonAscii => &COLOR_GRADIENT_NONASCII[(self.0 - 128) as usize],
},
}
}
@@ -92,7 +151,39 @@ impl Byte {
AsciiOther => '.',
NonAscii => '.',
},
CharacterTable::CP1047 => CP1047[self.0 as usize],
CharacterTable::CP437 => CP437[self.0 as usize],
CharacterTable::Braille => match self.category() {
// null is important enough to get its own symbol
Null => '⋄',
AsciiPrintable => self.0 as char,
AsciiWhitespace if self.0 == b' ' => ' ',
// `\t`, `\n` and `\r` are important enough to get their own symbols
AsciiWhitespace if self.0 == b'\t' => '→',
AsciiWhitespace if self.0 == b'\n' => '↵',
AsciiWhitespace if self.0 == b'\r' => '←',
AsciiWhitespace | AsciiOther | NonAscii => {
/// Adjust the bits from the original number to a new number.
///
/// Bit positions in braille are adjusted as follows:
///
/// ```text
/// 0 3 => 0 1
/// 1 4 => 2 3
/// 2 5 => 4 5
/// 6 7 => 6 7
/// ```
fn to_braille_bits(byte: u8) -> u8 {
let mut out = 0;
for (from, to) in [0, 3, 1, 4, 2, 5, 6, 7].into_iter().enumerate() {
out |= (byte >> from & 1) << to;
}
out
}
char::from_u32(0x2800 + to_braille_bits(self.0) as u32).unwrap()
}
},
}
}
}
@@ -104,10 +195,16 @@ struct BorderElements {
right_corner: char,
}
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug, Default, ValueEnum)]
pub enum BorderStyle {
/// Draw a border with Unicode characters.
#[default]
Unicode,
/// Draw a border with ASCII characters.
Ascii,
/// Do not draw a border at all.
None,
}
@@ -177,6 +274,8 @@ pub struct PrinterBuilder<'a, Writer: Write> {
base: Base,
endianness: Endianness,
character_table: CharacterTable,
include_mode: IncludeMode,
color_scheme: ColorScheme,
}
impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
@@ -193,6 +292,8 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
base: Base::Hexadecimal,
endianness: Endianness::Big,
character_table: CharacterTable::Default,
include_mode: IncludeMode::Off,
color_scheme: ColorScheme::Default,
}
}
@@ -246,20 +347,57 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
self
}
pub fn include_mode(mut self, include: IncludeMode) -> Self {
self.include_mode = include;
self
}
pub fn color_scheme(mut self, color_scheme: ColorScheme) -> Self {
self.color_scheme = color_scheme;
self
}
pub fn build(self) -> Printer<'a, Writer> {
Printer::new(
self.writer,
self.show_color,
self.show_char_panel,
self.show_position_panel,
self.border_style,
self.use_squeeze,
self.panels,
self.group_size,
self.base,
self.endianness,
self.character_table,
)
Printer {
idx: 0,
line_buf: vec![0x0; 8 * self.panels as usize],
writer: self.writer,
show_char_panel: self.show_char_panel,
show_position_panel: self.show_position_panel,
show_color: self.show_color,
curr_color: None,
color_scheme: self.color_scheme,
border_style: self.border_style,
byte_hex_panel: (0u8..=u8::MAX)
.map(|i| match self.base {
Base::Binary => format!("{i:08b}"),
Base::Octal => format!("{i:03o}"),
Base::Decimal => format!("{i:03}"),
Base::Hexadecimal => format!("{i:02x}"),
})
.collect(),
byte_char_panel: (0u8..=u8::MAX)
.map(|i| format!("{}", Byte(i).as_char(self.character_table)))
.collect(),
byte_hex_panel_g: (0u8..=u8::MAX).map(|i| format!("{i:02x}")).collect(),
squeezer: if self.use_squeeze {
Squeezer::Ignore
} else {
Squeezer::Disabled
},
display_offset: 0,
panels: self.panels,
squeeze_byte: 0x00,
group_size: self.group_size,
base_digits: match self.base {
Base::Binary => 8,
Base::Octal => 3,
Base::Decimal => 3,
Base::Hexadecimal => 2,
},
endianness: self.endianness,
include_mode: self.include_mode,
}
}
}
@@ -272,6 +410,7 @@ pub struct Printer<'a, Writer: Write> {
show_position_panel: bool,
show_color: bool,
curr_color: Option<&'static [u8]>,
color_scheme: ColorScheme,
border_style: BorderStyle,
byte_hex_panel: Vec<String>,
byte_char_panel: Vec<String>,
@@ -288,62 +427,11 @@ pub struct Printer<'a, Writer: Write> {
base_digits: u8,
/// Whether to show groups in little or big endian format.
endianness: Endianness,
/// Whether to output in C include file style.
include_mode: IncludeMode,
}
impl<'a, Writer: Write> Printer<'a, Writer> {
fn new(
writer: &'a mut Writer,
show_color: bool,
show_char_panel: bool,
show_position_panel: bool,
border_style: BorderStyle,
use_squeeze: bool,
panels: u64,
group_size: u8,
base: Base,
endianness: Endianness,
character_table: CharacterTable,
) -> Printer<'a, Writer> {
Printer {
idx: 0,
line_buf: vec![0x0; 8 * panels as usize],
writer,
show_char_panel,
show_position_panel,
show_color,
curr_color: None,
border_style,
byte_hex_panel: (0u8..=u8::MAX)
.map(|i| match base {
Base::Binary => format!("{i:08b}"),
Base::Octal => format!("{i:03o}"),
Base::Decimal => format!("{i:03}"),
Base::Hexadecimal => format!("{i:02x}"),
})
.collect(),
byte_char_panel: (0u8..=u8::MAX)
.map(|i| format!("{}", Byte(i).as_char(character_table)))
.collect(),
byte_hex_panel_g: (0u8..=u8::MAX).map(|i| format!("{i:02x}")).collect(),
squeezer: if use_squeeze {
Squeezer::Ignore
} else {
Squeezer::Disabled
},
display_offset: 0,
panels,
squeeze_byte: 0x00,
group_size,
base_digits: match base {
Base::Binary => 8,
Base::Octal => 3,
Base::Decimal => 3,
Base::Hexadecimal => 2,
},
endianness,
}
}
pub fn display_offset(&mut self, display_offset: u64) -> &mut Self {
self.display_offset = display_offset;
self
@@ -414,14 +502,14 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
.as_bytes(),
)?;
if self.show_color {
self.writer.write_all(COLOR_OFFSET)?;
self.writer.write_all(COLOR_OFFSET.as_bytes())?;
}
if self.show_position_panel {
match self.squeezer {
Squeezer::Print => {
self.writer.write_all(&[b'*'])?;
self.writer.write_all(b"*")?;
if self.show_color {
self.writer.write_all(COLOR_RESET)?;
self.writer.write_all(COLOR_RESET.as_bytes())?;
}
self.writer.write_all(b" ")?;
}
@@ -436,7 +524,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
.write_all(self.byte_hex_panel_g[byte as usize].as_bytes())?;
}
if self.show_color {
self.writer.write_all(COLOR_RESET)?;
self.writer.write_all(COLOR_RESET.as_bytes())?;
}
}
}
@@ -455,9 +543,10 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
Squeezer::Print | Squeezer::Delete => self.writer.write_all(b" ")?,
Squeezer::Ignore | Squeezer::Disabled => {
if let Some(&b) = self.line_buf.get(i as usize) {
if self.show_color && self.curr_color != Some(Byte(b).color()) {
self.writer.write_all(Byte(b).color())?;
self.curr_color = Some(Byte(b).color());
if self.show_color && self.curr_color != Some(Byte(b).color(self.color_scheme))
{
self.writer.write_all(Byte(b).color(self.color_scheme))?;
self.curr_color = Some(Byte(b).color(self.color_scheme));
}
self.writer
.write_all(self.byte_char_panel[b as usize].as_bytes())?;
@@ -468,7 +557,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
}
if i == 8 * self.panels - 1 {
if self.show_color {
self.writer.write_all(COLOR_RESET)?;
self.writer.write_all(COLOR_RESET.as_bytes())?;
self.curr_color = None;
}
self.writer.write_all(
@@ -479,7 +568,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
)?;
} else if i % 8 == 7 {
if self.show_color {
self.writer.write_all(COLOR_RESET)?;
self.writer.write_all(COLOR_RESET.as_bytes())?;
self.curr_color = None;
}
self.writer.write_all(
@@ -505,14 +594,14 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
Squeezer::Print => {
if !self.show_position_panel && i == 0 {
if self.show_color {
self.writer.write_all(COLOR_OFFSET)?;
self.writer.write_all(COLOR_OFFSET.as_bytes())?;
}
self.writer
.write_all(self.byte_char_panel[b'*' as usize].as_bytes())?;
if self.show_color {
self.writer.write_all(COLOR_RESET)?;
self.writer.write_all(COLOR_RESET.as_bytes())?;
}
} else if i % (self.group_size as usize) == 0 {
} else if i.is_multiple_of(self.group_size as usize) {
self.writer.write_all(b" ")?;
}
for _ in 0..self.base_digits {
@@ -521,12 +610,12 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
}
Squeezer::Delete => self.writer.write_all(b" ")?,
Squeezer::Ignore | Squeezer::Disabled => {
if i % (self.group_size as usize) == 0 {
if i.is_multiple_of(self.group_size as usize) {
self.writer.write_all(b" ")?;
}
if self.show_color && self.curr_color != Some(Byte(b).color()) {
self.writer.write_all(Byte(b).color())?;
self.curr_color = Some(Byte(b).color());
if self.show_color && self.curr_color != Some(Byte(b).color(self.color_scheme)) {
self.writer.write_all(Byte(b).color(self.color_scheme))?;
self.curr_color = Some(Byte(b).color(self.color_scheme));
}
self.writer
.write_all(self.byte_hex_panel[b as usize].as_bytes())?;
@@ -536,7 +625,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
if i % 8 == 7 {
if self.show_color {
self.curr_color = None;
self.writer.write_all(COLOR_RESET)?;
self.writer.write_all(COLOR_RESET.as_bytes())?;
}
self.writer.write_all(b" ")?;
// byte is last in last panel
@@ -559,7 +648,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
Ok(())
}
fn reorder_buffer_to_little_endian(&self, buf: &mut Vec<u8>) {
fn reorder_buffer_to_little_endian(&self, buf: &mut [u8]) {
let n = buf.len();
let group_sz = self.group_size as usize;
@@ -591,6 +680,36 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
let mut buf = BufReader::new(reader);
// special handler for include mode
match &self.include_mode {
// Input from a file
// Output like `unsigned char <filename>[] = { ... }; unsigned int <filename>_len = ...;`
IncludeMode::File(filename) => {
// convert non-alphanumeric characters to '_'
let var_name = filename
.chars()
.map(|c| if c.is_alphanumeric() { c } else { '_' })
.collect::<String>();
writeln!(self.writer, "unsigned char {}[] = {{", var_name)?;
let total_bytes = self.print_bytes_in_include_style(&mut buf)?;
writeln!(self.writer, "}};")?;
writeln!(
self.writer,
"unsigned int {}_len = {};",
var_name, total_bytes
)?;
return Ok(());
}
IncludeMode::Stdin | IncludeMode::Slice => {
self.print_bytes_in_include_style(&mut buf)?;
return Ok(());
}
IncludeMode::Off => {}
}
let leftover = loop {
// read a maximum of 8 * self.panels bytes from the reader
if let Ok(n) = buf.read(&mut self.line_buf) {
@@ -707,8 +826,8 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
writeln!(self.writer)?;
} else if let Some(n) = leftover {
// last line is incomplete
self.print_position_panel()?;
self.squeezer = Squeezer::Ignore;
self.print_position_panel()?;
self.print_bytes()?;
self.squeezer = Squeezer::Print;
for i in n..8 * self.panels as usize {
@@ -731,6 +850,44 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
Ok(())
}
/// Print the bytes in C include file style
/// Return the number of bytes read
fn print_bytes_in_include_style<Reader: Read>(
&mut self,
buf: &mut BufReader<Reader>,
) -> Result<usize, io::Error> {
let mut buffer = [0; 1024];
let mut total_bytes = 0;
let mut is_first_chunk = true;
let mut line_counter = 0;
loop {
match buf.read(&mut buffer) {
Ok(0) => break, // EOF
Ok(bytes_read) => {
total_bytes += bytes_read;
for &byte in &buffer[..bytes_read] {
if line_counter % 12 == 0 {
if !is_first_chunk || line_counter > 0 {
writeln!(self.writer, ",")?;
}
// indentation of first line
write!(self.writer, " ")?;
is_first_chunk = false;
} else {
write!(self.writer, ", ")?;
}
write!(self.writer, "0x{:02x}", byte)?;
line_counter += 1;
}
}
Err(e) => return Err(e),
}
}
writeln!(self.writer)?;
Ok(total_bytes)
}
}
#[cfg(test)]
@@ -742,19 +899,20 @@ mod tests {
fn assert_print_all_output<Reader: Read>(input: Reader, expected_string: String) {
let mut output = vec![];
let mut printer = Printer::new(
&mut output,
false,
true,
true,
BorderStyle::Unicode,
true,
2,
1,
Base::Hexadecimal,
Endianness::Big,
CharacterTable::Default,
);
let mut printer = PrinterBuilder::new(&mut output)
.show_color(false)
.show_char_panel(true)
.show_position_panel(true)
.with_border_style(BorderStyle::Unicode)
.enable_squeezing(true)
.num_panels(2)
.group_size(1)
.with_base(Base::Hexadecimal)
.endianness(Endianness::Big)
.character_table(CharacterTable::Default)
.include_mode(IncludeMode::Off)
.color_scheme(ColorScheme::Default)
.build();
printer.print_all(input).unwrap();
@@ -798,19 +956,20 @@ mod tests {
.to_owned();
let mut output = vec![];
let mut printer: Printer<Vec<u8>> = Printer::new(
&mut output,
false,
true,
true,
BorderStyle::Unicode,
true,
2,
1,
Base::Hexadecimal,
Endianness::Big,
CharacterTable::Default,
);
let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
.show_color(false)
.show_char_panel(true)
.show_position_panel(true)
.with_border_style(BorderStyle::Unicode)
.enable_squeezing(true)
.num_panels(2)
.group_size(1)
.with_base(Base::Hexadecimal)
.endianness(Endianness::Big)
.character_table(CharacterTable::Default)
.include_mode(IncludeMode::Off)
.color_scheme(ColorScheme::Default)
.build();
printer.display_offset(0xdeadbeef);
printer.print_all(input).unwrap();
@@ -833,19 +992,20 @@ mod tests {
.to_owned();
let mut output = vec![];
let mut printer: Printer<Vec<u8>> = Printer::new(
&mut output,
false,
true,
true,
BorderStyle::Unicode,
true,
4,
1,
Base::Hexadecimal,
Endianness::Big,
CharacterTable::Default,
);
let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
.show_color(false)
.show_char_panel(true)
.show_position_panel(true)
.with_border_style(BorderStyle::Unicode)
.enable_squeezing(true)
.num_panels(4)
.group_size(1)
.with_base(Base::Hexadecimal)
.endianness(Endianness::Big)
.character_table(CharacterTable::Default)
.include_mode(IncludeMode::Off)
.color_scheme(ColorScheme::Default)
.build();
printer.print_all(input).unwrap();
@@ -894,19 +1054,96 @@ mod tests {
.to_owned();
let mut output = vec![];
let mut printer: Printer<Vec<u8>> = Printer::new(
&mut output,
false,
true,
true,
BorderStyle::Unicode,
true,
3,
1,
Base::Hexadecimal,
Endianness::Big,
CharacterTable::Default,
);
let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
.show_color(false)
.show_char_panel(true)
.show_position_panel(true)
.with_border_style(BorderStyle::Unicode)
.enable_squeezing(true)
.num_panels(3)
.group_size(1)
.with_base(Base::Hexadecimal)
.endianness(Endianness::Big)
.character_table(CharacterTable::Default)
.include_mode(IncludeMode::Off)
.color_scheme(ColorScheme::Default)
.build();
printer.print_all(input).unwrap();
let actual_string: &str = str::from_utf8(&output).unwrap();
assert_eq!(actual_string, expected_string)
}
// issue#238
#[test]
fn display_offset_in_last_line() {
let input = io::Cursor::new(b"AAAAAAAAAAAAAAAACCCC");
let expected_string = "\
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 41 41 41 41 41 41 41 41 ┊ 41 41 41 41 41 41 41 41 │AAAAAAAA┊AAAAAAAA│
│00000010│ 43 43 43 43 ┊ │CCCC ┊ │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
"
.to_owned();
assert_print_all_output(input, expected_string);
}
#[test]
fn include_mode_from_file() {
let input = io::Cursor::new(b"spamspamspamspamspam");
let expected_string = "unsigned char test_txt[] = {
0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d,
0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d
};
unsigned int test_txt_len = 20;
"
.to_owned();
let mut output = vec![];
let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
.show_color(false)
.show_char_panel(true)
.show_position_panel(true)
.with_border_style(BorderStyle::Unicode)
.enable_squeezing(true)
.num_panels(2)
.group_size(1)
.with_base(Base::Hexadecimal)
.endianness(Endianness::Big)
.character_table(CharacterTable::Default)
.include_mode(IncludeMode::File("test.txt".to_owned()))
.color_scheme(ColorScheme::Default)
.build();
printer.print_all(input).unwrap();
let actual_string: &str = str::from_utf8(&output).unwrap();
assert_eq!(actual_string, expected_string)
}
#[test]
fn include_mode_from_stdin() {
let input = io::Cursor::new(b"spamspamspamspamspam");
let expected_string =
" 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d,
0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d
"
.to_owned();
let mut output = vec![];
let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
.show_color(false)
.show_char_panel(true)
.show_position_panel(true)
.with_border_style(BorderStyle::Unicode)
.enable_squeezing(true)
.num_panels(2)
.group_size(1)
.with_base(Base::Hexadecimal)
.endianness(Endianness::Big)
.character_table(CharacterTable::Default)
.include_mode(IncludeMode::Stdin)
.color_scheme(ColorScheme::Default)
.build();
printer.print_all(input).unwrap();
+419 -391
View File
@@ -1,15 +1,15 @@
#[macro_use]
extern crate clap;
use std::convert::TryFrom;
use std::fs::File;
use std::io::{self, prelude::*, BufWriter, SeekFrom};
use std::num::{NonZeroI64, NonZeroU64, NonZeroU8};
use std::num::{NonZeroI64, NonZeroU64};
use std::path::PathBuf;
use clap::builder::styling::{AnsiColor, Effects};
use clap::builder::ArgPredicate;
use clap::{crate_name, crate_version, Arg, ArgAction, ColorChoice, Command};
use clap::builder::Styles;
use clap::{ArgAction, CommandFactory, Parser, ValueEnum};
use clap_complete::aot::{generate, Shell};
use anyhow::{anyhow, Context, Result};
use anyhow::{anyhow, bail, Context, Result};
use const_format::formatcp;
@@ -17,280 +17,311 @@ use thiserror::Error as ThisError;
use terminal_size::terminal_size;
use hexyl::{Base, BorderStyle, CharacterTable, Endianness, Input, PrinterBuilder};
use hexyl::{
Base, BorderStyle, CharacterTable, ColorScheme, Endianness, IncludeMode, Input, PrinterBuilder,
};
use hexyl::{
COLOR_ASCII_OTHER, COLOR_ASCII_PRINTABLE, COLOR_ASCII_WHITESPACE, COLOR_NONASCII, COLOR_NULL,
COLOR_RESET,
};
#[cfg(test)]
mod tests;
const DEFAULT_BLOCK_SIZE: i64 = 512;
fn run() -> Result<()> {
let command = Command::new(crate_name!())
.color(ColorChoice::Auto)
.max_term_width(90)
.version(crate_version!())
.about(crate_description!())
.arg(
Arg::new("FILE")
.help("The file to display. If no FILE argument is given, read from STDIN."),
)
.arg(
Arg::new("length")
.short('n')
.long("length")
.num_args(1)
.value_name("N")
.help(
"Only read N bytes from the input. The N argument can also include a \
unit with a decimal prefix (kB, MB, ..) or binary prefix (kiB, MiB, ..), \
or can be specified using a hex number. \
The short option '-l' can be used as an alias.\n\
Examples: --length=64, --length=4KiB, --length=0xff",
),
)
.arg(
Arg::new("bytes")
.short('c')
.long("bytes")
.num_args(1)
.value_name("N")
.conflicts_with("length")
.help("An alias for -n/--length"),
)
.arg(
Arg::new("count")
.short('l')
.num_args(1)
.value_name("N")
.conflicts_with_all(["length", "bytes"])
.hide(true)
.help("Yet another alias for -n/--length"),
)
.arg(
Arg::new("skip")
.short('s')
.long("skip")
.num_args(1)
.value_name("N")
.help(
"Skip the first N bytes of the input. The N argument can also include \
a unit (see `--length` for details)\n\
A negative value is valid and will seek from the end of the file.",
),
)
.arg(
Arg::new("block_size")
.long("block-size")
.num_args(1)
.value_name("SIZE")
.help(formatcp!(
"Sets the size of the `block` unit to SIZE (default is {}).\n\
Examples: --block-size=1024, --block-size=4kB",
DEFAULT_BLOCK_SIZE
)),
)
.arg(
Arg::new("nosqueezing")
.short('v')
.long("no-squeezing")
.action(ArgAction::SetFalse)
.help(
"Displays all input data. Otherwise any number of groups of output \
lines which would be identical to the preceding group of lines, are \
replaced with a line comprised of a single asterisk.",
),
)
.arg(
Arg::new("color")
.long("color")
.num_args(1)
.value_name("WHEN")
.value_parser(["always", "auto", "never", "force"])
.default_value_if("plain", ArgPredicate::IsPresent, Some("never"))
.default_value("always")
.help(
"When to use colors. The 'auto' mode only displays colors if the output \
goes to an interactive terminal. 'force' can be used to override the \
NO_COLOR environment variable.",
),
)
.arg(
Arg::new("border")
.long("border")
.num_args(1)
.value_name("STYLE")
.value_parser(["unicode", "ascii", "none"])
.default_value_if("plain", ArgPredicate::IsPresent, Some("none"))
.default_value("unicode")
.help(
"Whether to draw a border with Unicode characters, ASCII characters, \
or none at all",
),
)
.arg(Arg::new("plain").short('p').long("plain").action(ArgAction::SetTrue).help(
"Display output with --no-characters, --no-position, --border=none, and --color=never.",
))
.arg(
Arg::new("no_chars")
.long("no-characters")
.action(ArgAction::SetFalse)
.help("Do not show the character panel on the right."),
)
.arg(
Arg::new("chars")
.short('C')
.long("characters")
.overrides_with("no_chars")
.action(ArgAction::SetTrue)
.help("Show the character panel on the right. This is the default, unless --no-characters has been specified."),
)
.arg(
Arg::new("character-table")
.long("character-table")
.value_name("FORMAT")
.value_parser(["default", "ascii", "codepage-437"])
.default_value("default")
.help(
"Defines how bytes are mapped to characters:\n \
\"default\": show printable ASCII characters as-is, '⋄' for NULL bytes, \
' ' for space, '_' for other ASCII whitespace, \
'•' for other ASCII characters, and '×' for non-ASCII bytes.\n \
\"ascii\": show printable ASCII as-is, ' ' for space, '.' for everything else.\n \
\"codepage-437\": uses code page 437 (for non-ASCII bytes).\n"
),
)
.arg(
Arg::new("no_position")
.short('P')
.long("no-position")
.action(ArgAction::SetFalse)
.help("Whether to display the position panel on the left."),
)
.arg(
Arg::new("display_offset")
.short('o')
.long("display-offset")
.num_args(1)
.value_name("N")
.help(
"Add N bytes to the displayed file position. The N argument can also \
include a unit (see `--length` for details)\n\
A negative value is valid and calculates an offset relative to the \
end of the file.",
),
)
.arg(
Arg::new("panels")
.long("panels")
.num_args(1)
.value_name("N")
.help(
"Sets the number of hex data panels to be displayed. \
`--panels=auto` will display the maximum number of hex data panels \
based on the current terminal width. By default, hexyl will show \
two panels, unless the terminal is not wide enough for that.",
),
)
.arg(
Arg::new("group_size")
.short('g')
.long("group-size")
.alias("groupsize")
.num_args(1)
.value_name("N")
.help(
"Number of bytes/octets that should be grouped together. \
Possible group sizes are 1, 2, 4, 8. The default is 1. You \
can use the '--endianness' option to control the ordering of \
the bytes within a group. '--groupsize' can be used as an \
alias (xxd-compatibility).",
),
)
.arg(
Arg::new("endianness")
.long("endianness")
.num_args(1)
.value_name("FORMAT")
.value_parser(["big", "little"])
.default_value("big")
.help(
"Whether to print out groups in little-endian or big-endian \
format. This option only has an effect if the '--group-size' \
is larger than 1. '-e' can be used as an alias for \
'--endianness=little'.",
),
)
.arg(
Arg::new("little_endian_format")
.short('e')
.action(ArgAction::SetTrue)
.overrides_with("endianness")
.hide(true)
.help("An alias for '--endianness=little'."),
)
.arg(
Arg::new("base")
.short('b')
.long("base")
.num_args(1)
.value_name("B")
.help(
"Sets the base used for the bytes. The possible options are \
binary, octal, decimal, and hexadecimal. The default base \
is hexadecimal."
)
)
.arg(
Arg::new("terminal_width")
.long("terminal-width")
.num_args(1)
.value_name("N")
.conflicts_with("panels")
.help(
"Sets the number of terminal columns to be displayed.\nSince the terminal \
width may not be an evenly divisible by the width per hex data column, this \
will use the greatest number of hex data panels that can fit in the requested \
width but still leave some space to the right.\nCannot be used with other \
width-setting options.",
),
);
const LENGTH_HELP_TEXT: &str = "Only read N bytes from the input. The N argument can also include \
a unit with a decimal prefix (kB, MB, ..) or binary prefix (kiB, \
MiB, ..), or can be specified using a hex number. The short \
option '-l' can be used as an alias.
Examples: --length=64, --length=4KiB, --length=0xff";
let matches = command.get_matches();
const SKIP_HELP_TEXT: &str = "Skip the first N bytes of the input. The N argument can also \
include a unit (see `--length` for details).
A negative value is valid and will seek from the end of the file.";
const BLOCK_SIZE_HELP_TEXT: &str = "Sets the size of the `block` unit to SIZE.
Examples: --block-size=1024, --block-size=4kB";
const DISPLAY_OFFSET_HELP_TEXT: &str = "Add N bytes to the displayed file position. The N \
argument can also include a unit (see `--length` for \
details).
A negative value is valid and calculates an offset relative to the end of the file.";
const TERMINAL_WIDTH_HELP_TEXT: &str = "Sets the number of terminal columns to be displayed.
Since the terminal width may not be an evenly divisible by the width per hex data column, this \
will use the greatest number of hex data panels that can \
fit in the requested width but still leave some space to \
the right.
Cannot be used with other width-setting options.";
const STYLES: Styles = Styles::styled()
.header(AnsiColor::Green.on_default().effects(Effects::BOLD))
.usage(AnsiColor::Green.on_default().effects(Effects::BOLD))
.literal(AnsiColor::Cyan.on_default().effects(Effects::BOLD))
.placeholder(AnsiColor::Cyan.on_default());
#[derive(Debug, Parser)]
#[command(version, about, max_term_width(90), styles = STYLES)]
struct Opt {
/// The file to display. If no FILE argument is given, read from STDIN.
#[arg(value_name("FILE"))]
file: Option<PathBuf>,
#[arg(
help(LENGTH_HELP_TEXT),
short('n'),
long,
visible_short_alias('c'),
visible_alias("bytes"),
short_alias('l'),
value_name("N")
)]
length: Option<String>,
#[arg(help(SKIP_HELP_TEXT), short, long, value_name("N"))]
skip: Option<String>,
#[arg(
help(BLOCK_SIZE_HELP_TEXT),
long,
default_value(formatcp!("{DEFAULT_BLOCK_SIZE}")),
value_name("SIZE")
)]
block_size: String,
/// Displays all input data. Otherwise any number of groups of output lines
/// which would be identical to the preceding group of lines, are replaced
/// with a line comprised of a single asterisk.
#[arg(short('v'), long)]
no_squeezing: bool,
/// When to use colors.
#[arg(
long,
value_enum,
default_value_t,
value_name("WHEN"),
default_value_if("plain", ArgPredicate::IsPresent, Some("never"))
)]
color: ColorWhen,
/// Whether to draw a border.
#[arg(
long,
value_enum,
default_value_t,
value_name("STYLE"),
default_value_if("plain", ArgPredicate::IsPresent, Some("none"))
)]
border: BorderStyle,
/// Display output with --no-characters, --no-position, --border=none, and
/// --color=never.
#[arg(short, long)]
plain: bool,
/// Do not show the character panel on the right.
#[arg(long)]
no_characters: bool,
/// Show the character panel on the right. This is the default, unless
/// --no-characters has been specified.
#[arg(
short('C'),
long,
action(ArgAction::SetTrue),
overrides_with("no_characters")
)]
characters: (),
/// Defines how bytes are mapped to characters.
#[arg(long, value_enum, default_value_t, value_name("FORMAT"))]
character_table: CharacterTable,
/// Defines the color scheme for the characters.
#[arg(long, value_enum, default_value_t, value_name("FORMAT"))]
color_scheme: ColorScheme,
/// Whether to display the position panel on the left.
#[arg(short('P'), long)]
no_position: bool,
#[arg(
help(DISPLAY_OFFSET_HELP_TEXT),
short('o'),
long,
default_value("0"),
value_name("N")
)]
display_offset: String,
/// Sets the number of hex data panels to be displayed. `--panels=auto` will
/// display the maximum number of hex data panels based on the current
/// terminal width. By default, hexyl will show two panels, unless the
/// terminal is not wide enough for that.
#[arg(long, value_name("N"))]
panels: Option<String>,
/// Number of bytes/octets that should be grouped together. You can use the
/// '--endianness' option to control the ordering of the bytes within a
/// group. '--groupsize' can be used as an alias (xxd-compatibility).
#[arg(
short('g'),
long,
value_enum,
default_value_t,
alias("groupsize"),
value_name("N")
)]
group_size: GroupSize,
/// Whether to print out groups in little-endian or big-endian format. This
/// option only has an effect if the '--group-size' is larger than 1. '-e'
/// can be used as an alias for '--endianness=little'.
#[arg(long, value_enum, default_value_t, value_name("FORMAT"))]
endianness: Endianness,
/// An alias for '--endianness=little'.
#[arg(short('e'), hide(true), overrides_with("endianness"))]
little_endian_format: bool,
/// Sets the base used for the bytes. The possible options are binary,
/// octal, decimal, and hexadecimal.
#[arg(short('b'), long, default_value("hexadecimal"), value_name("B"))]
base: String,
#[arg(
help(TERMINAL_WIDTH_HELP_TEXT),
long,
value_name("N"),
conflicts_with("panels")
)]
terminal_width: Option<NonZeroU64>,
/// Print a table showing how different types of bytes are colored.
#[arg(long)]
print_color_table: bool,
/// Output in C include file style (similar to xxd -i).
#[arg(
short('i'),
long("include"),
help = "Output in C include file style",
conflicts_with("little_endian_format"),
conflicts_with("endianness")
)]
include_mode: bool,
/// Show shell completion for a certain shell
#[arg(long, value_name("SHELL"))]
completion: Option<Shell>,
}
#[derive(Clone, Debug, Default, ValueEnum)]
enum ColorWhen {
/// Always use colorized output.
#[default]
Always,
/// Only displays colors if the output goes to an interactive terminal.
Auto,
/// Do not use colorized output.
Never,
/// Override the NO_COLOR environment variable.
Force,
}
#[derive(Clone, Debug, Default, ValueEnum)]
enum GroupSize {
/// Grouped together every byte/octet.
#[default]
#[value(name = "1")]
One,
/// Grouped together every 2 bytes/octets.
#[value(name = "2")]
Two,
/// Grouped together every 4 bytes/octets.
#[value(name = "4")]
Four,
/// Grouped together every 8 bytes/octets.
#[value(name = "8")]
Eight,
}
impl From<GroupSize> for u8 {
fn from(number: GroupSize) -> Self {
match number {
GroupSize::One => 1,
GroupSize::Two => 2,
GroupSize::Four => 4,
GroupSize::Eight => 8,
}
}
}
fn run() -> Result<()> {
let opt = Opt::parse();
if opt.print_color_table {
return print_color_table().map_err(|e| anyhow!(e));
}
if let Some(sh) = opt.completion {
let mut cmd = Opt::command();
let name = cmd.get_name().to_string();
generate(sh, &mut cmd, name, &mut io::stdout());
return Ok(());
}
let stdin = io::stdin();
let mut reader = match matches.get_one::<String>("FILE") {
Some(filename) => Input::File(File::open(filename)?),
let mut reader = match &opt.file {
Some(filename) => {
if filename.as_os_str() == "-" {
Input::Stdin(stdin.lock())
} else {
if filename.is_dir() {
bail!("'{}' is a directory.", filename.to_string_lossy());
}
let file = File::open(filename)?;
Input::File(file)
}
}
None => Input::Stdin(stdin.lock()),
};
let block_size = matches
.get_one::<String>("block_size")
.map(|bs| {
if let Some(hex_number) = try_parse_as_hex_number(bs) {
return hex_number.map_err(|e| anyhow!(e)).and_then(|x| {
PositiveI64::new(x)
.ok_or_else(|| anyhow!("block size argument must be positive"))
});
}
let (num, unit) = extract_num_and_unit_from(bs)?;
if let Unit::Block { custom_size: _ } = unit {
return Err(anyhow!(
"can not use 'block(s)' as a unit to specify block size"
));
};
num.checked_mul(unit.get_multiplier())
.ok_or_else(|| anyhow!(ByteOffsetParseError::UnitMultiplicationOverflow))
.and_then(|x| {
PositiveI64::new(x)
.ok_or_else(|| anyhow!("block size argument must be positive"))
})
})
.transpose()?
.unwrap_or_else(|| PositiveI64::new(DEFAULT_BLOCK_SIZE).unwrap());
if let Some(hex_number) = try_parse_as_hex_number(&opt.block_size) {
return hex_number
.map_err(|e| anyhow!(e))
.and_then(|x| {
PositiveI64::new(x).ok_or_else(|| anyhow!("block size argument must be positive"))
})
.map(|_| ());
}
let (num, unit) = extract_num_and_unit_from(&opt.block_size)?;
if let Unit::Block { custom_size: _ } = unit {
return Err(anyhow!(
"can not use 'block(s)' as a unit to specify block size"
));
};
let block_size = num
.checked_mul(unit.get_multiplier())
.ok_or_else(|| anyhow!(ByteOffsetParseError::UnitMultiplicationOverflow))
.and_then(|x| {
PositiveI64::new(x).ok_or_else(|| anyhow!("block size argument must be positive"))
})?;
let skip_arg = matches
.get_one::<String>("skip")
let skip_arg = opt
.skip
.as_ref()
.map(|s| {
parse_byte_offset(s, block_size).context(anyhow!(
"failed to parse `--skip` arg {:?} as byte count",
@@ -325,29 +356,22 @@ fn run() -> Result<()> {
.into())
};
let mut reader = if let Some(length) = matches
.get_one::<String>("length")
.or_else(|| matches.get_one::<String>("bytes"))
.or_else(|| matches.get_one::<String>("count"))
.map(|s| {
parse_byte_count(s).context(anyhow!(
"failed to parse `--length` arg {:?} as byte count",
s
))
})
.transpose()?
{
let mut reader = if let Some(ref length) = opt.length {
let length = parse_byte_count(length).context(anyhow!(
"failed to parse `--length` arg {:?} as byte count",
length
))?;
Box::new(reader.take(length))
} else {
reader.into_inner()
};
let no_color = std::env::var_os("NO_COLOR").is_some();
let show_color = match matches.get_one::<String>("color").map(String::as_ref) {
Some("never") => false,
Some("always") => !no_color,
Some("force") => true,
_ => {
let show_color = match opt.color {
ColorWhen::Never => false,
ColorWhen::Always => !no_color,
ColorWhen::Force => true,
ColorWhen::Auto => {
if no_color {
false
} else {
@@ -358,30 +382,18 @@ fn run() -> Result<()> {
}
};
let border_style = match matches.get_one::<String>("border").map(String::as_ref) {
Some("unicode") => BorderStyle::Unicode,
Some("ascii") => BorderStyle::Ascii,
_ => BorderStyle::None,
};
let border_style = opt.border;
let &squeeze = matches.get_one::<bool>("nosqueezing").unwrap_or(&true);
let &squeeze = &!opt.no_squeezing;
let show_char_panel = *matches.get_one::<bool>("no_chars").unwrap_or(&true)
&& !matches.get_one::<bool>("plain").unwrap_or(&false);
let show_char_panel = !opt.no_characters && !opt.plain;
let show_position_panel = *matches.get_one::<bool>("no_position").unwrap_or(&true)
&& !matches.get_one::<bool>("plain").unwrap_or(&false);
let show_position_panel = !opt.no_position && !opt.plain;
let display_offset: u64 = matches
.get_one::<String>("display_offset")
.map(|s| {
parse_byte_count(s).context(anyhow!(
"failed to parse `--display-offset` arg {:?} as byte count",
s
))
})
.transpose()?
.unwrap_or(0);
let display_offset: u64 = parse_byte_count(&opt.display_offset).context(anyhow!(
"failed to parse `--display-offset` arg {:?} as byte count",
opt.display_offset
))?;
let max_panels_fn = |terminal_width: u64, base_digits: u64, group_size: u64| {
let offset = if show_position_panel { 10 } else { 1 };
@@ -390,37 +402,34 @@ fn run() -> Result<()> {
} else {
((8 / group_size) * (base_digits * group_size + 1)) + 2
};
if (terminal_width - offset) / col_width < 1 {
if (terminal_width.saturating_sub(offset)) / col_width < 1 {
1
} else {
(terminal_width - offset) / col_width
}
};
let base = if let Some(base) = matches.get_one::<String>("base")
.map(|s| {
if let Ok(base_num) = s.parse::<u8>() {
match base_num {
2 => Ok(Base::Binary),
8 => Ok(Base::Octal),
10 => Ok(Base::Decimal),
16 => Ok(Base::Hexadecimal),
_ => Err(anyhow!("The number provided is not a valid base. Valid bases are 2, 8, 10, and 16.")),
}
} else {
match s.as_str() {
"b" | "bin" | "binary" => Ok(Base::Binary),
"o" | "oct" | "octal" => Ok(Base::Octal),
"d" | "dec" | "decimal" => Ok(Base::Decimal),
"x" | "hex" | "hexadecimal" => Ok(Base::Hexadecimal),
_ => Err(anyhow!("The base provided is not valid. Valid bases are \"b\", \"o\", \"d\", and \"x\"."))
}
let base = if let Ok(base_num) = opt.base.parse::<u8>() {
match base_num {
2 => Ok(Base::Binary),
8 => Ok(Base::Octal),
10 => Ok(Base::Decimal),
16 => Ok(Base::Hexadecimal),
_ => Err(anyhow!(
"The number provided is not a valid base. Valid bases are 2, 8, 10, and 16."
)),
}
}).transpose()? {
base
} else {
Base::Hexadecimal
};
match opt.base.as_str() {
"b" | "bin" | "binary" => Ok(Base::Binary),
"o" | "oct" | "octal" => Ok(Base::Octal),
"d" | "dec" | "decimal" => Ok(Base::Decimal),
"x" | "hex" | "hexadecimal" => Ok(Base::Hexadecimal),
_ => Err(anyhow!(
"The base provided is not valid. Valid bases are \"b\", \"o\", \"d\", and \"x\"."
)),
}
}?;
let base_digits = match base {
Base::Binary => 8,
@@ -429,53 +438,22 @@ fn run() -> Result<()> {
Base::Hexadecimal => 2,
};
let group_size = if let Some(group_size) = matches
.get_one::<String>("group_size")
.map(|s| {
s.parse::<NonZeroU8>().map(u8::from).context(anyhow!(
"Failed to parse `--group-size`/`-g` argument {:?} as unsigned nonzero integer",
s
))
})
.transpose()?
{
if (group_size <= 8) && ((group_size & (group_size - 1)) == 0) {
group_size
} else {
return Err(anyhow!(
"Possible sizes for the `--group-size`/`-g` option are 1, 2, 4 or 8. "
));
}
} else {
1
};
let group_size = u8::from(opt.group_size);
let terminal_width = terminal_size().map(|s| s.0 .0 as u64).unwrap_or(80);
let panels = if matches.get_one::<String>("panels").map(String::as_ref) == Some("auto") {
let panels = if opt.panels.as_deref() == Some("auto") {
max_panels_fn(terminal_width, base_digits, group_size.into())
} else if let Some(panels) = matches
.get_one::<String>("panels")
.map(|s| {
s.parse::<NonZeroU64>().map(u64::from).context(anyhow!(
"failed to parse `--panels` arg {:?} as unsigned nonzero integer",
s
))
})
.transpose()?
{
} else if let Some(panels) = opt.panels {
panels
} else if let Some(terminal_width) = matches
.get_one::<String>("terminal_width")
.map(|s| {
s.parse::<NonZeroU64>().map(u64::from).context(anyhow!(
"failed to parse `--terminal-width` arg {:?} as unsigned nonzero integer",
s
))
})
.transpose()?
{
max_panels_fn(terminal_width, base_digits, group_size.into())
.parse::<NonZeroU64>()
.map(u64::from)
.context(anyhow!(
"failed to parse `--panels` arg {:?} as unsigned nonzero integer",
panels
))?
} else if let Some(terminal_width) = opt.terminal_width {
max_panels_fn(terminal_width.into(), base_digits, group_size.into())
} else {
std::cmp::min(
2,
@@ -483,32 +461,44 @@ fn run() -> Result<()> {
)
};
let little_endian_format = *matches.get_one::<bool>("little_endian_format").unwrap();
let endianness = matches.get_one::<String>("endianness");
let endianness = match (
endianness.map(|s| s.as_ref()).unwrap(),
little_endian_format,
) {
(_, true) | ("little", _) => Endianness::Little,
("big", _) => Endianness::Big,
_ => unreachable!(),
let endianness = if opt.little_endian_format {
Endianness::Little
} else {
opt.endianness
};
let character_table = match matches
.get_one::<String>("character-table")
.unwrap()
.as_ref()
{
"default" => CharacterTable::Default,
"ascii" => CharacterTable::Ascii,
"codepage-437" => CharacterTable::CP437,
_ => unreachable!(),
let character_table = opt.character_table;
let color_scheme = opt.color_scheme;
let mut stdout = BufWriter::new(io::stdout().lock());
let include_mode = match opt.include_mode {
// include mode on
true => {
if let Some(include_file) = opt.file {
// input from a file
if include_file.as_os_str() == "-" {
IncludeMode::File("stdin".to_string())
} else {
IncludeMode::File(
include_file
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("file")
.to_string(),
)
}
} else {
// input from stdin
IncludeMode::Stdin
}
}
// include mode off
false => IncludeMode::Off,
};
let stdout = io::stdout();
let mut stdout_lock = BufWriter::new(stdout.lock());
let mut printer = PrinterBuilder::new(&mut stdout_lock)
let mut printer = PrinterBuilder::new(&mut stdout)
.show_color(show_color)
.show_char_panel(show_char_panel)
.show_position_panel(show_position_panel)
@@ -519,6 +509,8 @@ fn run() -> Result<()> {
.with_base(base)
.endianness(endianness)
.character_table(character_table)
.include_mode(include_mode)
.color_scheme(color_scheme)
.build();
printer.display_offset(skip_offset + display_offset);
printer.print_all(&mut reader).map_err(|e| anyhow!(e))?;
@@ -564,6 +556,42 @@ impl From<NonNegativeI64> for u64 {
}
}
fn print_color_table() -> io::Result<()> {
let mut stdout = BufWriter::new(io::stdout().lock());
writeln!(stdout, "hexyl color reference:\n")?;
// NULL bytes
stdout.write_all(COLOR_NULL.as_bytes())?;
writeln!(stdout, "⋄ NULL bytes (0x00)")?;
stdout.write_all(COLOR_RESET.as_bytes())?;
// ASCII printable
stdout.write_all(COLOR_ASCII_PRINTABLE.as_bytes())?;
writeln!(stdout, "a ASCII printable characters (0x20 - 0x7E)")?;
stdout.write_all(COLOR_RESET.as_bytes())?;
// ASCII whitespace
stdout.write_all(COLOR_ASCII_WHITESPACE.as_bytes())?;
writeln!(stdout, "_ ASCII whitespace (0x09 - 0x0D, 0x20)")?;
stdout.write_all(COLOR_RESET.as_bytes())?;
// ASCII other
stdout.write_all(COLOR_ASCII_OTHER.as_bytes())?;
writeln!(
stdout,
"• ASCII control characters (except NULL and whitespace)"
)?;
stdout.write_all(COLOR_RESET.as_bytes())?;
// Non-ASCII
stdout.write_all(COLOR_NONASCII.as_bytes())?;
writeln!(stdout, "× Non-ASCII bytes (0x80 - 0xFF)")?;
stdout.write_all(COLOR_RESET.as_bytes())?;
Ok(())
}
#[derive(Clone, Copy, Debug, Default, Hash, Eq, Ord, PartialEq, PartialOrd)]
pub struct PositiveI64(i64);
+232 -1
View File
@@ -1,7 +1,7 @@
use assert_cmd::Command;
fn hexyl() -> Command {
let mut cmd = Command::cargo_bin("hexyl").unwrap();
let mut cmd = Command::new(assert_cmd::cargo_bin!("hexyl"));
cmd.current_dir("tests/examples");
cmd
}
@@ -727,7 +727,238 @@ mod character_table {
│00002110│ 00 00 00 00 00 00 00 00 ┊ 01 00 00 00 00 00 00 00 │⋄⋄⋄⋄⋄⋄⋄⋄┊☺⋄⋄⋄⋄⋄⋄⋄│
│00002120│ 00 00 00 00 00 00 00 00 ┊ │⋄⋄⋄⋄⋄⋄⋄⋄┊ │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
",
);
}
#[test]
fn codepage_1047() {
hexyl()
.arg("hello_world_elf64")
.arg("--color=never")
.arg("--character-table=codepage-1047")
.assert()
.success()
.pretty_stdout(
"┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 7f 45 4c 46 02 01 01 00 ┊ 00 00 00 00 00 00 00 00 │..<.....┊........│
│00000010│ 02 00 3e 00 01 00 00 00 ┊ 00 10 40 00 00 00 00 00 │........┊.. .....│
│00000020│ 40 00 00 00 00 00 00 00 ┊ 28 20 00 00 00 00 00 00 │ .......┊........│
│00000030│ 00 00 00 00 40 00 38 00 ┊ 03 00 40 00 04 00 03 00 │.... ...┊.. .....│
│00000040│ 01 00 00 00 04 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│
│00000050│ 00 00 40 00 00 00 00 00 ┊ 00 00 40 00 00 00 00 00 │.. .....┊.. .....│
│00000060│ e8 00 00 00 00 00 00 00 ┊ e8 00 00 00 00 00 00 00 │Y.......┊Y.......│
│00000070│ 00 10 00 00 00 00 00 00 ┊ 01 00 00 00 05 00 00 00 │........┊........│
│00000080│ 00 10 00 00 00 00 00 00 ┊ 00 10 40 00 00 00 00 00 │........┊.. .....│
│00000090│ 00 10 40 00 00 00 00 00 ┊ 1d 00 00 00 00 00 00 00 │.. .....┊........│
│000000a0│ 1d 00 00 00 00 00 00 00 ┊ 00 10 00 00 00 00 00 00 │........┊........│
│000000b0│ 01 00 00 00 06 00 00 00 ┊ 00 20 00 00 00 00 00 00 │........┊........│
│000000c0│ 00 20 40 00 00 00 00 00 ┊ 00 20 40 00 00 00 00 00 │.. .....┊.. .....│
│000000d0│ 0e 00 00 00 00 00 00 00 ┊ 0e 00 00 00 00 00 00 00 │........┊........│
│000000e0│ 00 10 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│
│000000f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│
│* │ ┊ │ ┊ │
│00001000│ ba 0e 00 00 00 b9 00 20 ┊ 40 00 bb 01 00 00 00 b8 │[.......┊ .].....│
│00001010│ 04 00 00 00 cd 80 b8 01 ┊ 00 00 00 cd 80 00 00 00 │........┊........│
│00001020│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│
│* │ ┊ │ ┊ │
│00002000│ 48 65 6c 6c 6f 2c 20 77 ┊ 6f 72 6c 64 21 0a 00 2e │..%%?...┊?.%.....│
│00002010│ 73 68 73 74 72 74 61 62 ┊ 00 2e 74 65 78 74 00 2e │....../.┊........│
│00002020│ 64 61 74 61 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │././....┊........│
│00002030│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│
│* │ ┊ │ ┊ │
│00002060│ 00 00 00 00 00 00 00 00 ┊ 0b 00 00 00 01 00 00 00 │........┊........│
│00002070│ 06 00 00 00 00 00 00 00 ┊ 00 10 40 00 00 00 00 00 │........┊.. .....│
│00002080│ 00 10 00 00 00 00 00 00 ┊ 1d 00 00 00 00 00 00 00 │........┊........│
│00002090│ 00 00 00 00 00 00 00 00 ┊ 10 00 00 00 00 00 00 00 │........┊........│
│000020a0│ 00 00 00 00 00 00 00 00 ┊ 11 00 00 00 01 00 00 00 │........┊........│
│000020b0│ 03 00 00 00 00 00 00 00 ┊ 00 20 40 00 00 00 00 00 │........┊.. .....│
│000020c0│ 00 20 00 00 00 00 00 00 ┊ 0e 00 00 00 00 00 00 00 │........┊........│
│000020d0│ 00 00 00 00 00 00 00 00 ┊ 04 00 00 00 00 00 00 00 │........┊........│
│000020e0│ 00 00 00 00 00 00 00 00 ┊ 01 00 00 00 03 00 00 00 │........┊........│
│000020f0│ 00 00 00 00 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │........┊........│
│00002100│ 0e 20 00 00 00 00 00 00 ┊ 17 00 00 00 00 00 00 00 │........┊........│
│00002110│ 00 00 00 00 00 00 00 00 ┊ 01 00 00 00 00 00 00 00 │........┊........│
│00002120│ 00 00 00 00 00 00 00 00 ┊ │........┊ │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
",
);
}
}
mod colors {
use super::hexyl;
use owo_colors::{colors, Color};
use std::collections::HashMap;
// This is a helper for testing color in output. Writing tests to expect
// raw color codes is ugly and hard to look at. Loading expected output
// from files works fine, but you end up with a lot of files for all the
// tests, and you have to cross-reference the file with the test that uses
// it. The files also suffer from the same problem of being hard to
// visually inspect. Just catting the file to see the colorized output
// loses the nuance of where exactly the color codes appear (before or
// after spaces, for example), or whether there are redundant codes.
//
// So this ColorMap solves the problem neatly by having two inputs:
// - the easy to read expected output in plain format without any colors
// - a mapping with identical structure except some characters replaced
// with single character color codes.
// This makes it easy to reference the output and expected colors side by
// side, and provides fairly precise control over exactly where color codes
// are expected (the only caveat being you can't have two color codes back
// to back). ColorMap combines these into the actual expected output.
//
// The color mapping needs to be identical to the expected output, except
// it has some chars replaced by color code stand ins. These are replaced
// with the actual color codes by the colorize method. The '.' character
// is also ignored (it doesn't need to match the input). This makes the
// color map more readable and avoids input characters from conflicting
// with color chars.
struct ColorMap {
text_map: &'static str,
char_to_color: HashMap<char, &'static str>,
}
impl ColorMap {
fn from(text_map: &'static str) -> Self {
ColorMap {
text_map,
char_to_color: HashMap::new(),
}
}
fn with<C: Color>(&mut self, c: char) -> &mut Self {
self.char_to_color.insert(c, C::ANSI_FG);
self
}
fn colorize(&self, input: &str) -> String {
let mut output = String::new();
let mut input_chars = input.chars();
for c in self.text_map.chars() {
let next_input = input_chars.next().expect("input and color map don't match");
if let Some(color) = self.char_to_color.get(&c) {
output.push_str(color);
} else if c != '.' {
// ignore '.' in the mapping for readability
assert_eq!(c, next_input, "input and color map don't match");
}
output.push(next_input);
}
output
}
}
#[test]
fn hex_colors() {
let input = b"He\x11\0 \xff\0\xdd";
let expected_text = "\
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n\
│00000000│ 48 65 11 00 20 ff 00 dd ┊ │He•⋄ ×⋄×┊ │\n\
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n";
let expected = ColorMap::from(
"\
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n\
│r.......d y. .. b. c. g. m. c. m.d┊ d│y.bcgmcmd d\n\
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n",
)
.with::<colors::Red>('r')
.with::<colors::Default>('d')
.with::<colors::Yellow>('y')
.with::<colors::Blue>('b')
.with::<colors::Green>('g')
.with::<colors::BrightMagenta>('m')
.with::<colors::CustomColor<0xab, 0xcd, 0xef>>('c')
.colorize(expected_text);
hexyl()
.write_stdin(input)
.arg("--color=always")
.env("HEXYL_COLOR_OFFSET", "red")
.env("HEXYL_COLOR_ASCII_PRINTABLE", "yellow")
.env("HEXYL_COLOR_ASCII_WHITESPACE", "green")
.env("HEXYL_COLOR_ASCII_OTHER", "blue")
.env("HEXYL_COLOR_NONASCII", "bright magenta")
.env("HEXYL_COLOR_NULL", "#abcdef")
.assert()
.success()
.stdout(expected);
}
#[test]
fn binary_colors() {
let input = b"He\x11\0 \xff\0\xdd";
let expected_text = "\
┌────────┬─────────────────────────────────────────────────────────────────────────┬────────┐\n\
│00000000│ 01001000 01100101 00010001 00000000 00100000 11111111 00000000 11011101 │He•⋄ ×⋄×│\n\
└────────┴─────────────────────────────────────────────────────────────────────────┴────────┘\n";
let expected = ColorMap::from(
"\
┌────────┬─────────────────────────────────────────────────────────────────────────┬────────┐\n\
│r.......d y....... ........ b....... c....... g....... m....... c....... m.......d│y.bcgmcmd\n\
└────────┴─────────────────────────────────────────────────────────────────────────┴────────┘\n"
)
.with::<colors::Red>('r')
.with::<colors::Default>('d')
.with::<colors::Yellow>('y')
.with::<colors::Blue>('b')
.with::<colors::Green>('g')
.with::<colors::BrightMagenta>('m')
.with::<colors::CustomColor<0xab, 0xcd, 0xef>>('c')
.colorize(expected_text);
hexyl()
.write_stdin(input)
.arg("--color=always")
.arg("--panels=1")
.arg("--base=binary")
.env("HEXYL_COLOR_OFFSET", "red")
.env("HEXYL_COLOR_ASCII_PRINTABLE", "yellow")
.env("HEXYL_COLOR_ASCII_WHITESPACE", "green")
.env("HEXYL_COLOR_ASCII_OTHER", "blue")
.env("HEXYL_COLOR_NONASCII", "bright magenta")
.env("HEXYL_COLOR_NULL", "#abcdef")
.assert()
.success()
.stdout(expected);
}
#[test]
fn groupsize_colors() {
let input = b"He\x11\0 \xff\0\xdd";
let expected_text = "\
┌────────┬─────────────────────┬────────┐\n\
│00000000│ 4865 1100 20ff 00dd │He•⋄ ×⋄×│\n\
└────────┴─────────────────────┴────────┘\n";
let expected = ColorMap::from(
"\
┌────────┬─────────────────────┬────────┐\n\
│r.......d y... b.c. g.m. c.m.d│y.bcgmcmd\n\
└────────┴─────────────────────┴────────┘\n",
)
.with::<colors::Red>('r')
.with::<colors::Default>('d')
.with::<colors::Yellow>('y')
.with::<colors::Blue>('b')
.with::<colors::Green>('g')
.with::<colors::BrightMagenta>('m')
.with::<colors::CustomColor<0xab, 0xcd, 0xef>>('c')
.colorize(expected_text);
hexyl()
.write_stdin(input)
.arg("--color=always")
.arg("--panels=1")
.arg("--groupsize=2")
.env("HEXYL_COLOR_OFFSET", "red")
.env("HEXYL_COLOR_ASCII_PRINTABLE", "yellow")
.env("HEXYL_COLOR_ASCII_WHITESPACE", "green")
.env("HEXYL_COLOR_ASCII_OTHER", "blue")
.env("HEXYL_COLOR_NONASCII", "bright magenta")
.env("HEXYL_COLOR_NULL", "#abcdef")
.assert()
.success()
.stdout(expected);
}
}