mirror of
https://github.com/sharkdp/hexyl.git
synced 2026-10-27 21:28:18 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbed12e423 | ||
|
|
1927610953 | ||
|
|
f8531b3e44 | ||
|
|
427a5524e1 | ||
|
|
120cf29adf | ||
|
|
d2cc025c6f | ||
|
|
f8b87cc5e2 | ||
|
|
252a6dbef4 | ||
|
|
16cc5d9757 | ||
|
|
d51086e0da | ||
|
|
d8a26da964 | ||
|
|
dcce9e1011 | ||
|
|
651087ca9d | ||
|
|
3bd84fd5cb | ||
|
|
0634a06470 | ||
|
|
7a9234c182 | ||
|
|
45fa72ec3b | ||
|
|
711627bfe8 | ||
|
|
ba89e88dfd | ||
|
|
1792090799 | ||
|
|
33d5b4dbda | ||
|
|
921327f2ae | ||
|
|
4bf875095c | ||
|
|
a78c00b10d | ||
|
|
2bab0c769e | ||
|
|
1310b1d19e | ||
|
|
a56b3b0c46 | ||
|
|
7ca00279ed | ||
|
|
dfb5d57ee7 | ||
|
|
a0a7d6761f | ||
|
|
7ef8e5aae4 | ||
|
|
8d5b355b76 | ||
|
|
19f325a691 | ||
|
|
10ddbbad7d | ||
|
|
58c8c06317 | ||
|
|
78477a90c9 | ||
|
|
9362c2313a | ||
|
|
e363f2fdc4 | ||
|
|
29e3350e69 | ||
|
|
0c192e979b | ||
|
|
c4f91959ef | ||
|
|
4e66357618 | ||
|
|
6a230d34a4 | ||
|
|
663e4c19ff | ||
|
|
894ca71b40 | ||
|
|
e85953eeb5 | ||
|
|
f5ce9946f6 | ||
|
|
668054fe50 | ||
|
|
d6cb7af643 | ||
|
|
7b59a3f374 | ||
|
|
3c69797f23 | ||
|
|
7e9bc5138d | ||
|
|
ce70246f80 | ||
|
|
a306c82024 | ||
|
|
e1e53335d3 | ||
|
|
c9f113c8c4 | ||
|
|
59557bd433 | ||
|
|
39a94379c6 | ||
|
|
5981fc8725 | ||
|
|
f4515b0f14 | ||
|
|
e3d406dd95 | ||
|
|
b6b7e2c79a | ||
|
|
dfad7b3faf | ||
|
|
ef9b530d6a | ||
|
|
a3268b8791 | ||
|
|
2a534483c8 | ||
|
|
e6ba196bcb | ||
|
|
d0bd971382 | ||
|
|
8eed3cadbb | ||
|
|
38a9662ea5 | ||
|
|
e10136d203 | ||
|
|
5f0373184f | ||
|
|
57d18482ba | ||
|
|
816779e968 | ||
|
|
9be018fbb9 | ||
|
|
cba717b783 | ||
|
|
580f865c4a | ||
|
|
20450310c9 | ||
|
|
b251575d94 | ||
|
|
58b038af27 | ||
|
|
d703cdfd78 | ||
|
|
43f8b92502 | ||
|
|
a966896d3f | ||
|
|
08b903270f | ||
|
|
dd601e4792 | ||
|
|
564cf64b1b | ||
|
|
c24280620a |
No files matched your search
@@ -212,7 +212,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 +304,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 }}
|
||||
|
||||
@@ -1,3 +1,48 @@
|
||||
# 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
|
||||
|
||||
- Correctly handle discontinuous input (stdin), see #196 and #197 (@sharifhsn)
|
||||
|
||||
# v0.13.0
|
||||
|
||||
## Features
|
||||
|
||||
- Support both little and big Endian dumps using `--endianness={little,big}`, see #189 and #104 (@RinHizakura)
|
||||
|
||||
## Changes
|
||||
|
||||
- **Breaking**: Changed the meaning of the short flag `-C` to be consistent with `hexdump -C`. Previously, this would *hide* the character panel, but now `-C` *shows* the character panel, in case it has been previously (e.g. in an `alias`) disabled with `--no-characters`, see #187 (@sharkdp)
|
||||
|
||||
## `hexyl` as a library
|
||||
|
||||
- New `endianness` method for `PrinterBuilder`
|
||||
|
||||
|
||||
# v0.12.0
|
||||
|
||||
## Features
|
||||
|
||||
Generated
+233
-255
@@ -4,128 +4,174 @@ version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.66"
|
||||
name = "anstream"
|
||||
version = "0.6.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
|
||||
checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8"
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.0.7"
|
||||
version = "2.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa3d466004a8b4cb1bc34044240a2fd29d17607e2e3bd613eb44fd48e8100da3"
|
||||
checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"bstr",
|
||||
"doc-comment",
|
||||
"libc",
|
||||
"predicates",
|
||||
"predicates-core",
|
||||
"predicates-tree",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi 0.1.19",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[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"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.0.1"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd"
|
||||
checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.77"
|
||||
name = "clap"
|
||||
version = "4.5.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
|
||||
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.0.29"
|
||||
name = "clap_builder"
|
||||
version = "4.5.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d63b9e9c07271b9957ad22c173bae2a4d9a81127680962039296abcd2f8251d"
|
||||
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"is-terminal",
|
||||
"once_cell",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"terminal_size",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.3.0"
|
||||
name = "clap_derive"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8"
|
||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
version = "0.2.30"
|
||||
name = "clap_lex"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7309d9b4d3d2c0641e018d449232f2e28f1b22933c137f157d3dbc14228b8c0e"
|
||||
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
version = "0.2.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b"
|
||||
dependencies = [
|
||||
"const_format_proc_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_format_proc_macros"
|
||||
version = "0.2.29"
|
||||
version = "0.2.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d897f47bf7270cf70d370f8f98c1abb6d2d4cf60a6845d30e05bfb90c6568650"
|
||||
checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.13"
|
||||
@@ -144,31 +190,14 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.2.8"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
|
||||
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
@@ -181,26 +210,14 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hexyl"
|
||||
version = "0.12.0"
|
||||
version = "0.15.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
@@ -215,60 +232,35 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "927609f78c2913a6f6ac3c27a4fe87f43e2a35367c0c4b0f8265e8f49a104330"
|
||||
dependencies = [
|
||||
"hermit-abi 0.2.6",
|
||||
"io-lifetimes",
|
||||
"rustix",
|
||||
"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.138"
|
||||
version = "0.2.161"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
|
||||
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.1.3"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f9f08d8963a6c613f4b1a78f4f4a4dbfadf8e6545b2d72861731e4858b8b47f"
|
||||
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"
|
||||
@@ -278,49 +270,28 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
|
||||
|
||||
[[package]]
|
||||
name = "output_vt100"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[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 = "2.1.4"
|
||||
version = "3.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f54fc5dc63ed3bbf19494623db4f3af16842c0d975818e469022d09e53f0aa05"
|
||||
checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"difflib",
|
||||
"float-cmp",
|
||||
"itertools",
|
||||
"normalize-line-endings",
|
||||
"predicates-core",
|
||||
"regex",
|
||||
@@ -328,15 +299,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "predicates-core"
|
||||
version = "1.0.5"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2"
|
||||
checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931"
|
||||
|
||||
[[package]]
|
||||
name = "predicates-tree"
|
||||
version = "1.0.7"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d"
|
||||
checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13"
|
||||
dependencies = [
|
||||
"predicates-core",
|
||||
"termtree",
|
||||
@@ -344,143 +315,150 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.3.0"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755"
|
||||
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
|
||||
dependencies = [
|
||||
"ctor",
|
||||
"diff",
|
||||
"output_vt100",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.47"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.21"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.0"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.28"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.36.5"
|
||||
version = "0.38.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3807b5d10909833d3e9acd1eb5fb988f79376ff10fce42937de71a449c4c588"
|
||||
checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.149"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055"
|
||||
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 = "1.3.1"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ba6faf2ca7ee42fdd458f4347ae0a9bd6bcc445ad7cb57ad82b383f18870d6f"
|
||||
checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"is_ci",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.105"
|
||||
version = "2.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908"
|
||||
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.2.3"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907"
|
||||
checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef"
|
||||
dependencies = [
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termtree"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8"
|
||||
checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.37"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e"
|
||||
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.37"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb"
|
||||
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -489,15 +467,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.5"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
|
||||
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.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
@@ -509,45 +493,33 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.42.0"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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",
|
||||
@@ -556,48 +528,54 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
|
||||
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.42.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.0"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
|
||||
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"
|
||||
+8
-9
@@ -7,28 +7,27 @@ license = "MIT/Apache-2.0"
|
||||
name = "hexyl"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/sharkdp/hexyl"
|
||||
version = "0.12.0"
|
||||
version = "0.15.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.60"
|
||||
rust-version = "1.74"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
const_format = "0.2"
|
||||
libc = "0.2"
|
||||
owo-colors = "3"
|
||||
supports-color = "1"
|
||||
owo-colors = "4"
|
||||
supports-color = "3"
|
||||
thiserror = "1.0"
|
||||
terminal_size = "0.2"
|
||||
terminal_size = "0.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"
|
||||
predicates = "2.0"
|
||||
pretty_assertions = "1.3.0"
|
||||
predicates = "3.0"
|
||||
pretty_assertions = "1.4.0"
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[](https://crates.io/crates/hexyl)
|
||||

|
||||
|
||||
`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,6 +17,20 @@ of bytes (NULL bytes, printable ASCII characters, ASCII whitespace characters, o
|
||||
|
||||

|
||||
|
||||
## Color Reference
|
||||
|
||||
|Type of Byte|Color|ANSI Code |
|
||||
|---|---|---|
|
||||
|NULL| Bright Black|90|
|
||||
|OFFSET| Bright Black|90|
|
||||
|ASCII Printable| Cyan|36|
|
||||
|ASCII Whitespace| Green|32|
|
||||
|ASCII Other| Green|32|
|
||||
|Non-ASCII| Yellow|33|
|
||||
|
||||
*Colors taken from the Ubuntu terminal color scheme, they could look different in your terminal*
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
### On Ubuntu
|
||||
@@ -31,7 +45,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.12.0_amd64.deb # adapt version number and architecture
|
||||
sudo dpkg -i hexyl_0.15.0_amd64.deb # adapt version number and architecture
|
||||
```
|
||||
|
||||
### On Debian
|
||||
@@ -54,7 +68,7 @@ sudo dnf install hexyl
|
||||
|
||||
### On Arch Linux
|
||||
|
||||
You can install `hexyl` from [the official package repository](https://www.archlinux.org/packages/community/x86_64/hexyl/):
|
||||
You can install `hexyl` from [the official package repository](https://archlinux.org/packages/extra/x86_64/hexyl/):
|
||||
|
||||
```
|
||||
pacman -S hexyl
|
||||
@@ -96,6 +110,18 @@ sudo port install hexyl
|
||||
pkg install hexyl
|
||||
```
|
||||
|
||||
### On NetBSD
|
||||
|
||||
```
|
||||
pkgin install hexyl
|
||||
```
|
||||
|
||||
### On OpenBSD
|
||||
|
||||
```
|
||||
doas pkg_add hexyl
|
||||
```
|
||||
|
||||
### on Termux
|
||||
```
|
||||
pkg install hexyl
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
%
|
||||
% 2022-12-05
|
||||
|
||||
<!--
|
||||
This man page is written in Pandoc's Markdown.
|
||||
See: https://pandoc.org/MANUAL.html#pandocs-markdown
|
||||
-->
|
||||
|
||||
# NAME
|
||||
|
||||
hexyl - a command-line hex viewer
|
||||
|
||||
@@ -1,880 +0,0 @@
|
||||
#[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 clap::builder::ArgPredicate;
|
||||
use clap::{crate_name, crate_version, Arg, ArgAction, ColorChoice, Command};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
|
||||
use const_format::formatcp;
|
||||
|
||||
use thiserror::Error as ThisError;
|
||||
|
||||
use terminal_size::terminal_size;
|
||||
|
||||
use hexyl::{Base, BorderStyle, Input, PrinterBuilder};
|
||||
|
||||
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"])
|
||||
.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",
|
||||
),
|
||||
)
|
||||
.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")
|
||||
.short('C')
|
||||
.long("no-characters")
|
||||
.action(ArgAction::SetFalse)
|
||||
.help("Whether to display the character panel on the right."),
|
||||
)
|
||||
.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. \
|
||||
'--groupsize can be used as an alias (xxd-compatibility).",
|
||||
),
|
||||
)
|
||||
.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.",
|
||||
),
|
||||
);
|
||||
|
||||
let matches = command.get_matches();
|
||||
|
||||
let stdin = io::stdin();
|
||||
|
||||
let mut reader = match matches.get_one::<String>("FILE") {
|
||||
Some(filename) => Input::File(File::open(filename)?),
|
||||
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());
|
||||
|
||||
let skip_arg = matches
|
||||
.get_one::<String>("skip")
|
||||
.map(|s| {
|
||||
parse_byte_offset(s, block_size).context(anyhow!(
|
||||
"failed to parse `--skip` arg {:?} as byte count",
|
||||
s
|
||||
))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let skip_offset = if let Some(ByteOffset { kind, value }) = skip_arg {
|
||||
let value = value.into_inner();
|
||||
reader
|
||||
.seek(match kind {
|
||||
ByteOffsetKind::ForwardFromBeginning | ByteOffsetKind::ForwardFromLastOffset => {
|
||||
SeekFrom::Current(value)
|
||||
}
|
||||
ByteOffsetKind::BackwardFromEnd => SeekFrom::End(value.checked_neg().unwrap()),
|
||||
})
|
||||
.map_err(|_| {
|
||||
anyhow!(
|
||||
"Failed to jump to the desired input position. \
|
||||
This could be caused by a negative offset that is too large or by \
|
||||
an input that is not seek-able (e.g. if the input comes from a pipe)."
|
||||
)
|
||||
})?
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let parse_byte_count = |s| -> Result<u64> {
|
||||
Ok(parse_byte_offset(s, block_size)?
|
||||
.assume_forward_offset_from_start()?
|
||||
.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()?
|
||||
{
|
||||
Box::new(reader.take(length))
|
||||
} else {
|
||||
reader.into_inner()
|
||||
};
|
||||
|
||||
let show_color = match matches.get_one::<String>("color").map(String::as_ref) {
|
||||
Some("never") => false,
|
||||
Some("always") => true,
|
||||
_ => supports_color::on(supports_color::Stream::Stdout)
|
||||
.map(|level| level.has_basic)
|
||||
.unwrap_or(false),
|
||||
};
|
||||
|
||||
let border_style = match matches.get_one::<String>("border").map(String::as_ref) {
|
||||
Some("unicode") => BorderStyle::Unicode,
|
||||
Some("ascii") => BorderStyle::Ascii,
|
||||
_ => BorderStyle::None,
|
||||
};
|
||||
|
||||
let &squeeze = matches.get_one::<bool>("nosqueezing").unwrap_or(&true);
|
||||
|
||||
let show_char_panel = *matches.get_one::<bool>("no_chars").unwrap_or(&true)
|
||||
&& !matches.get_one::<bool>("plain").unwrap_or(&false);
|
||||
|
||||
let show_position_panel = *matches.get_one::<bool>("no_position").unwrap_or(&true)
|
||||
&& !matches.get_one::<bool>("plain").unwrap_or(&false);
|
||||
|
||||
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 max_panels_fn = |terminal_width: u64, base_digits: u64, group_size: u64| {
|
||||
let offset = if show_position_panel { 10 } else { 1 };
|
||||
let col_width = if show_char_panel {
|
||||
((8 / group_size) * (base_digits * group_size + 1)) + 2 + 8
|
||||
} else {
|
||||
((8 / group_size) * (base_digits * group_size + 1)) + 2
|
||||
};
|
||||
if (terminal_width - 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\"."))
|
||||
}
|
||||
}
|
||||
}).transpose()? {
|
||||
base
|
||||
} else {
|
||||
Base::Hexadecimal
|
||||
};
|
||||
|
||||
let base_digits = match base {
|
||||
Base::Binary => 8,
|
||||
Base::Octal => 3,
|
||||
Base::Decimal => 3,
|
||||
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 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") {
|
||||
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()?
|
||||
{
|
||||
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())
|
||||
} else {
|
||||
std::cmp::min(
|
||||
2,
|
||||
max_panels_fn(terminal_width, base_digits, group_size.into()),
|
||||
)
|
||||
};
|
||||
|
||||
let stdout = io::stdout();
|
||||
let mut stdout_lock = BufWriter::new(stdout.lock());
|
||||
|
||||
let mut printer = PrinterBuilder::new(&mut stdout_lock)
|
||||
.show_color(show_color)
|
||||
.show_char_panel(show_char_panel)
|
||||
.show_position_panel(show_position_panel)
|
||||
.with_border_style(border_style)
|
||||
.enable_squeezing(squeeze)
|
||||
.num_panels(panels)
|
||||
.group_size(group_size)
|
||||
.with_base(base)
|
||||
.build();
|
||||
printer.display_offset(skip_offset + display_offset);
|
||||
printer.print_all(&mut reader).map_err(|e| anyhow!(e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let result = run();
|
||||
|
||||
if let Err(err) = result {
|
||||
if let Some(io_error) = err.downcast_ref::<io::Error>() {
|
||||
if io_error.kind() == ::std::io::ErrorKind::BrokenPipe {
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
eprintln!("Error: {err:?}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Hash, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct NonNegativeI64(i64);
|
||||
|
||||
impl NonNegativeI64 {
|
||||
pub fn new(x: i64) -> Option<Self> {
|
||||
if x.is_negative() {
|
||||
None
|
||||
} else {
|
||||
Some(Self(x))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> i64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NonNegativeI64> for u64 {
|
||||
fn from(x: NonNegativeI64) -> u64 {
|
||||
u64::try_from(x.0)
|
||||
.expect("invariant broken: NonNegativeI64 should contain a non-negative i64 value")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Hash, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct PositiveI64(i64);
|
||||
|
||||
impl PositiveI64 {
|
||||
pub fn new(x: i64) -> Option<Self> {
|
||||
if x < 1 {
|
||||
None
|
||||
} else {
|
||||
Some(Self(x))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> i64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PositiveI64> for u64 {
|
||||
fn from(x: PositiveI64) -> u64 {
|
||||
u64::try_from(x.0)
|
||||
.expect("invariant broken: PositiveI64 should contain a positive i64 value")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Unit {
|
||||
Byte,
|
||||
Kilobyte,
|
||||
Megabyte,
|
||||
Gigabyte,
|
||||
Terabyte,
|
||||
Kibibyte,
|
||||
Mebibyte,
|
||||
Gibibyte,
|
||||
Tebibyte,
|
||||
/// a customizable amount of bytes
|
||||
Block {
|
||||
custom_size: Option<NonZeroI64>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Unit {
|
||||
const fn get_multiplier(self) -> i64 {
|
||||
match self {
|
||||
Self::Byte => 1,
|
||||
Self::Kilobyte => 1000,
|
||||
Self::Megabyte => 1_000_000,
|
||||
Self::Gigabyte => 1_000_000_000,
|
||||
Self::Terabyte => 1_000_000_000_000,
|
||||
Self::Kibibyte => 1 << 10,
|
||||
Self::Mebibyte => 1 << 20,
|
||||
Self::Gibibyte => 1 << 30,
|
||||
Self::Tebibyte => 1 << 40,
|
||||
Self::Block {
|
||||
custom_size: Some(size),
|
||||
} => size.get(),
|
||||
Self::Block { custom_size: None } => DEFAULT_BLOCK_SIZE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const HEX_PREFIX: &str = "0x";
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
enum ByteOffsetKind {
|
||||
ForwardFromBeginning,
|
||||
ForwardFromLastOffset,
|
||||
BackwardFromEnd,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
struct ByteOffset {
|
||||
value: NonNegativeI64,
|
||||
kind: ByteOffsetKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, ThisError)]
|
||||
#[error(
|
||||
"negative offset specified, but only positive offsets (counts) are accepted in this context"
|
||||
)]
|
||||
struct NegativeOffsetSpecifiedError;
|
||||
|
||||
impl ByteOffset {
|
||||
fn assume_forward_offset_from_start(
|
||||
&self,
|
||||
) -> Result<NonNegativeI64, NegativeOffsetSpecifiedError> {
|
||||
let &Self { value, kind } = self;
|
||||
match kind {
|
||||
ByteOffsetKind::ForwardFromBeginning | ByteOffsetKind::ForwardFromLastOffset => {
|
||||
Ok(value)
|
||||
}
|
||||
ByteOffsetKind::BackwardFromEnd => Err(NegativeOffsetSpecifiedError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
|
||||
enum ByteOffsetParseError {
|
||||
#[error("no character data found, did you forget to write it?")]
|
||||
Empty,
|
||||
#[error("no digits found after sign, did you forget to write them?")]
|
||||
EmptyAfterSign,
|
||||
#[error(
|
||||
"found {0:?} sign after hex prefix ({:?}); signs should go before it",
|
||||
HEX_PREFIX
|
||||
)]
|
||||
SignFoundAfterHexPrefix(char),
|
||||
#[error("{0:?} is not of the expected form <pos-integer>[<unit>]")]
|
||||
InvalidNumAndUnit(String),
|
||||
#[error("{0:?} is a valid unit, but an integer should come before it")]
|
||||
EmptyWithUnit(String),
|
||||
#[error("invalid unit {0:?}")]
|
||||
InvalidUnit(String),
|
||||
#[error("failed to parse integer part")]
|
||||
ParseNum(#[source] std::num::ParseIntError),
|
||||
#[error("count multiplied by the unit overflowed a signed 64-bit integer; are you sure it should be that big?")]
|
||||
UnitMultiplicationOverflow,
|
||||
}
|
||||
|
||||
fn parse_byte_offset(n: &str, block_size: PositiveI64) -> Result<ByteOffset, ByteOffsetParseError> {
|
||||
use ByteOffsetParseError::*;
|
||||
|
||||
let (n, kind) = process_sign_of(n)?;
|
||||
|
||||
let into_byte_offset = |value| {
|
||||
Ok(ByteOffset {
|
||||
value: NonNegativeI64::new(value).unwrap(),
|
||||
kind,
|
||||
})
|
||||
};
|
||||
|
||||
if let Some(hex_number) = try_parse_as_hex_number(n) {
|
||||
return hex_number.map(into_byte_offset)?;
|
||||
}
|
||||
|
||||
let (num, mut unit) = extract_num_and_unit_from(n)?;
|
||||
if let Unit::Block { custom_size: None } = unit {
|
||||
unit = Unit::Block {
|
||||
custom_size: Some(
|
||||
NonZeroI64::new(block_size.into_inner()).expect("PositiveI64 was zero"),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
num.checked_mul(unit.get_multiplier())
|
||||
.ok_or(UnitMultiplicationOverflow)
|
||||
.and_then(into_byte_offset)
|
||||
}
|
||||
|
||||
/// Takes a string containing a base-10 number and an optional unit, and returns them with their proper types.
|
||||
/// The unit must directly follow the number (e.g. no whitespace is allowed between them).
|
||||
/// When no unit is given, [Unit::Byte] is assumed.
|
||||
/// When the unit is [Unit::Block], it is returned without custom size.
|
||||
/// No normalization is performed, that is "1024" is extracted to (1024, Byte), not (1, Kibibyte).
|
||||
fn extract_num_and_unit_from(n: &str) -> Result<(i64, Unit), ByteOffsetParseError> {
|
||||
use ByteOffsetParseError::*;
|
||||
if n.is_empty() {
|
||||
return Err(Empty);
|
||||
}
|
||||
match n.chars().position(|c| !c.is_ascii_digit()) {
|
||||
Some(unit_begin_idx) => {
|
||||
let (n, raw_unit) = n.split_at(unit_begin_idx);
|
||||
let unit = match raw_unit.to_lowercase().as_str() {
|
||||
"" => Unit::Byte, // no "b" => Byte to allow hex nums with units
|
||||
"kb" => Unit::Kilobyte,
|
||||
"mb" => Unit::Megabyte,
|
||||
"gb" => Unit::Gigabyte,
|
||||
"tb" => Unit::Terabyte,
|
||||
"kib" => Unit::Kibibyte,
|
||||
"mib" => Unit::Mebibyte,
|
||||
"gib" => Unit::Gibibyte,
|
||||
"tib" => Unit::Tebibyte,
|
||||
"block" | "blocks" => Unit::Block { custom_size: None },
|
||||
_ => {
|
||||
return if n.is_empty() {
|
||||
Err(InvalidNumAndUnit(raw_unit.to_string()))
|
||||
} else {
|
||||
Err(InvalidUnit(raw_unit.to_string()))
|
||||
}
|
||||
}
|
||||
};
|
||||
let num = n.parse::<i64>().map_err(|e| {
|
||||
if n.is_empty() {
|
||||
EmptyWithUnit(raw_unit.to_owned())
|
||||
} else {
|
||||
ParseNum(e)
|
||||
}
|
||||
})?;
|
||||
Ok((num, unit))
|
||||
}
|
||||
None => {
|
||||
// no unit part
|
||||
let num = n.parse::<i64>().map_err(ParseNum)?;
|
||||
Ok((num, Unit::Byte))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts a [ByteOffsetKind] based on the sign at the beginning of the given string.
|
||||
/// Returns the input string without the sign (or an equal string if there wasn't any sign).
|
||||
fn process_sign_of(n: &str) -> Result<(&str, ByteOffsetKind), ByteOffsetParseError> {
|
||||
use ByteOffsetParseError::*;
|
||||
let mut chars = n.chars();
|
||||
let next_char = chars.next();
|
||||
let check_empty_after_sign = || {
|
||||
if chars.clone().next().is_none() {
|
||||
Err(EmptyAfterSign)
|
||||
} else {
|
||||
Ok(chars.as_str())
|
||||
}
|
||||
};
|
||||
match next_char {
|
||||
Some('+') => Ok((
|
||||
check_empty_after_sign()?,
|
||||
ByteOffsetKind::ForwardFromLastOffset,
|
||||
)),
|
||||
Some('-') => Ok((check_empty_after_sign()?, ByteOffsetKind::BackwardFromEnd)),
|
||||
None => Err(Empty),
|
||||
_ => Ok((n, ByteOffsetKind::ForwardFromBeginning)),
|
||||
}
|
||||
}
|
||||
|
||||
/// If `n` starts with a hex prefix, its remaining part is returned as some number (if possible),
|
||||
/// otherwise None is returned.
|
||||
fn try_parse_as_hex_number(n: &str) -> Option<Result<i64, ByteOffsetParseError>> {
|
||||
use ByteOffsetParseError::*;
|
||||
n.strip_prefix(HEX_PREFIX).map(|num| {
|
||||
let mut chars = num.chars();
|
||||
match chars.next() {
|
||||
Some(c @ '+') | Some(c @ '-') => {
|
||||
return if chars.next().is_none() {
|
||||
Err(EmptyAfterSign)
|
||||
} else {
|
||||
Err(SignFoundAfterHexPrefix(c))
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
i64::from_str_radix(num, 16).map_err(ParseNum)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unit_multipliers() {
|
||||
use Unit::*;
|
||||
assert_eq!(Kilobyte.get_multiplier(), 1000 * Byte.get_multiplier());
|
||||
assert_eq!(Megabyte.get_multiplier(), 1000 * Kilobyte.get_multiplier());
|
||||
assert_eq!(Gigabyte.get_multiplier(), 1000 * Megabyte.get_multiplier());
|
||||
assert_eq!(Terabyte.get_multiplier(), 1000 * Gigabyte.get_multiplier());
|
||||
|
||||
assert_eq!(Kibibyte.get_multiplier(), 1024 * Byte.get_multiplier());
|
||||
assert_eq!(Mebibyte.get_multiplier(), 1024 * Kibibyte.get_multiplier());
|
||||
assert_eq!(Gibibyte.get_multiplier(), 1024 * Mebibyte.get_multiplier());
|
||||
assert_eq!(Tebibyte.get_multiplier(), 1024 * Gibibyte.get_multiplier());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_sign() {
|
||||
use ByteOffsetKind::*;
|
||||
use ByteOffsetParseError::*;
|
||||
assert_eq!(process_sign_of("123"), Ok(("123", ForwardFromBeginning)));
|
||||
assert_eq!(process_sign_of("+123"), Ok(("123", ForwardFromLastOffset)));
|
||||
assert_eq!(process_sign_of("-123"), Ok(("123", BackwardFromEnd)));
|
||||
assert_eq!(process_sign_of("-"), Err(EmptyAfterSign));
|
||||
assert_eq!(process_sign_of("+"), Err(EmptyAfterSign));
|
||||
assert_eq!(process_sign_of(""), Err(Empty));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_as_hex() {
|
||||
assert_eq!(try_parse_as_hex_number("73"), None);
|
||||
assert_eq!(try_parse_as_hex_number("0x1337"), Some(Ok(0x1337)));
|
||||
assert!(matches!(try_parse_as_hex_number("0xnope"), Some(Err(_))));
|
||||
assert!(matches!(try_parse_as_hex_number("0x-1"), Some(Err(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_num_and_unit() {
|
||||
use ByteOffsetParseError::*;
|
||||
use Unit::*;
|
||||
// byte is default unit
|
||||
assert_eq!(extract_num_and_unit_from("4"), Ok((4, Byte)));
|
||||
// blocks are returned without customization
|
||||
assert_eq!(
|
||||
extract_num_and_unit_from("2blocks"),
|
||||
Ok((2, Block { custom_size: None }))
|
||||
);
|
||||
// no normalization is performed
|
||||
assert_eq!(extract_num_and_unit_from("1024kb"), Ok((1024, Kilobyte)));
|
||||
|
||||
// unit without number results in error
|
||||
assert_eq!(
|
||||
extract_num_and_unit_from("gib"),
|
||||
Err(EmptyWithUnit("gib".to_string()))
|
||||
);
|
||||
// empty string results in error
|
||||
assert_eq!(extract_num_and_unit_from(""), Err(Empty));
|
||||
// an invalid unit results in an error
|
||||
assert_eq!(
|
||||
extract_num_and_unit_from("25litres"),
|
||||
Err(InvalidUnit("litres".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_byte_offset() {
|
||||
use ByteOffsetParseError::*;
|
||||
|
||||
macro_rules! success {
|
||||
($input: expr, $expected_kind: ident $expected_value: expr) => {
|
||||
success!($input, $expected_kind $expected_value; block_size: DEFAULT_BLOCK_SIZE)
|
||||
};
|
||||
($input: expr, $expected_kind: ident $expected_value: expr; block_size: $block_size: expr) => {
|
||||
assert_eq!(
|
||||
parse_byte_offset($input, PositiveI64::new($block_size).unwrap()),
|
||||
Ok(
|
||||
ByteOffset {
|
||||
value: NonNegativeI64::new($expected_value).unwrap(),
|
||||
kind: ByteOffsetKind::$expected_kind,
|
||||
}
|
||||
),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! error {
|
||||
($input: expr, $expected_err: expr) => {
|
||||
assert_eq!(
|
||||
parse_byte_offset($input, PositiveI64::new(DEFAULT_BLOCK_SIZE).unwrap()),
|
||||
Err($expected_err),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
success!("0", ForwardFromBeginning 0);
|
||||
success!("1", ForwardFromBeginning 1);
|
||||
success!("1", ForwardFromBeginning 1);
|
||||
success!("100", ForwardFromBeginning 100);
|
||||
success!("+100", ForwardFromLastOffset 100);
|
||||
|
||||
success!("0x0", ForwardFromBeginning 0);
|
||||
success!("0xf", ForwardFromBeginning 15);
|
||||
success!("0xdeadbeef", ForwardFromBeginning 3_735_928_559);
|
||||
|
||||
success!("1KB", ForwardFromBeginning 1000);
|
||||
success!("2MB", ForwardFromBeginning 2000000);
|
||||
success!("3GB", ForwardFromBeginning 3000000000);
|
||||
success!("4TB", ForwardFromBeginning 4000000000000);
|
||||
success!("+4TB", ForwardFromLastOffset 4000000000000);
|
||||
|
||||
success!("1GiB", ForwardFromBeginning 1073741824);
|
||||
success!("2TiB", ForwardFromBeginning 2199023255552);
|
||||
success!("+2TiB", ForwardFromLastOffset 2199023255552);
|
||||
|
||||
success!("0xff", ForwardFromBeginning 255);
|
||||
success!("0xEE", ForwardFromBeginning 238);
|
||||
success!("+0xFF", ForwardFromLastOffset 255);
|
||||
|
||||
success!("1block", ForwardFromBeginning 512; block_size: 512);
|
||||
success!("2block", ForwardFromBeginning 1024; block_size: 512);
|
||||
success!("1block", ForwardFromBeginning 4; block_size: 4);
|
||||
success!("2block", ForwardFromBeginning 8; block_size: 4);
|
||||
|
||||
// empty string is invalid
|
||||
error!("", Empty);
|
||||
// These are also bad.
|
||||
error!("+", EmptyAfterSign);
|
||||
error!("-", EmptyAfterSign);
|
||||
error!("K", InvalidNumAndUnit("K".to_owned()));
|
||||
error!("k", InvalidNumAndUnit("k".to_owned()));
|
||||
error!("m", InvalidNumAndUnit("m".to_owned()));
|
||||
error!("block", EmptyWithUnit("block".to_owned()));
|
||||
// leading/trailing space is invalid
|
||||
error!(" 0", InvalidNumAndUnit(" 0".to_owned()));
|
||||
error!("0 ", InvalidUnit(" ".to_owned()));
|
||||
// Signs after the hex prefix make no sense
|
||||
error!("0x-12", SignFoundAfterHexPrefix('-'));
|
||||
// This was previously accepted but shouldn't be.
|
||||
error!("0x+12", SignFoundAfterHexPrefix('+'));
|
||||
// invalid suffix
|
||||
error!("1234asdf", InvalidUnit("asdf".to_owned()));
|
||||
// bad numbers
|
||||
error!("asdf1234", InvalidNumAndUnit("asdf1234".to_owned()));
|
||||
error!("a1s2d3f4", InvalidNumAndUnit("a1s2d3f4".to_owned()));
|
||||
// multiplication overflows u64
|
||||
error!("20000000TiB", UnitMultiplicationOverflow);
|
||||
|
||||
assert!(
|
||||
match parse_byte_offset("99999999999999999999", PositiveI64::new(512).unwrap()) {
|
||||
// We can't check against the kind of the `ParseIntError`, so we'll just make sure it's the
|
||||
// same as trying to do the parse directly.
|
||||
Err(ParseNum(e)) => e == "99999999999999999999".parse::<i64>().unwrap_err(),
|
||||
_ => false,
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
use owo_colors::{colors, Color};
|
||||
|
||||
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();
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub const CP437: [char; 256] = [
|
||||
// Copyright (c) 2016, Delan Azabani <delan@azabani.com>
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
//
|
||||
// modified to use the ⋄ character instead of ␀
|
||||
|
||||
// use https://en.wikipedia.org/w/index.php?title=Code_page_437&oldid=978947122
|
||||
// not ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/PC/CP437.TXT
|
||||
// because we want the graphic versions of 01h–1Fh + 7Fh
|
||||
'⋄','☺','☻','♥','♦','♣','♠','•','◘','○','◙','♂','♀','♪','♫','☼',
|
||||
'►','◄','↕','‼','¶','§','▬','↨','↑','↓','→','←','∟','↔','▲','▼',
|
||||
' ','!','"','#','$','%','&','\'','(',')','*','+',',','-','.','/',
|
||||
'0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?',
|
||||
'@','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','{','|','}','~','⌂',
|
||||
'Ç','ü','é','â','ä','à','å','ç','ê','ë','è','ï','î','ì','Ä','Å',
|
||||
'É','æ','Æ','ô','ö','ò','û','ù','ÿ','Ö','Ü','¢','£','¥','₧','ƒ',
|
||||
'á','í','ó','ú','ñ','Ñ','ª','º','¿','⌐','¬','½','¼','¡','«','»',
|
||||
'░','▒','▓','│','┤','╡','╢','╖','╕','╣','║','╗','╝','╜','╛','┐',
|
||||
'└','┴','┬','├','─','┼','╞','╟','╚','╔','╩','╦','╠','═','╬','╧',
|
||||
'╨','╤','╥','╙','╘','╒','╓','╫','╪','┘','┌','█','▄','▌','▐','▀',
|
||||
'α','ß','Γ','π','Σ','σ','µ','τ','Φ','Θ','Ω','δ','∞','φ','ε','∩',
|
||||
'≡','±','≥','≤','⌠','⌡','÷','≈','°','∙','·','√','ⁿ','²','■','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','.','.','.','.','.','.'
|
||||
];
|
||||
@@ -1,4 +1,3 @@
|
||||
use std::convert::TryFrom;
|
||||
use std::fs;
|
||||
use std::io::{self, copy, sink, Read, Seek, SeekFrom};
|
||||
|
||||
|
||||
+134
-30
@@ -1,10 +1,12 @@
|
||||
pub(crate) mod colors;
|
||||
pub(crate) mod input;
|
||||
|
||||
pub use colors::*;
|
||||
pub use input::*;
|
||||
|
||||
use std::io::{self, BufReader, Read, Write};
|
||||
|
||||
use owo_colors::{colors, Color};
|
||||
use clap::ValueEnum;
|
||||
|
||||
pub enum Base {
|
||||
Binary,
|
||||
@@ -13,14 +15,7 @@ pub enum Base {
|
||||
Hexadecimal,
|
||||
}
|
||||
|
||||
const COLOR_NULL: &[u8] = colors::BrightBlack::ANSI_FG.as_bytes();
|
||||
const COLOR_OFFSET: &[u8] = colors::BrightBlack::ANSI_FG.as_bytes();
|
||||
const COLOR_ASCII_PRINTABLE: &[u8] = colors::Cyan::ANSI_FG.as_bytes();
|
||||
const COLOR_ASCII_WHITESPACE: &[u8] = colors::Green::ANSI_FG.as_bytes();
|
||||
const COLOR_ASCII_OTHER: &[u8] = colors::Green::ANSI_FG.as_bytes();
|
||||
const COLOR_NONASCII: &[u8] = colors::Yellow::ANSI_FG.as_bytes();
|
||||
const COLOR_RESET: &[u8] = colors::Default::ANSI_FG.as_bytes();
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum ByteCategory {
|
||||
Null,
|
||||
AsciiPrintable,
|
||||
@@ -29,6 +24,37 @@ pub enum ByteCategory {
|
||||
NonAscii,
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
enum Squeezer {
|
||||
Print,
|
||||
@@ -57,7 +83,6 @@ impl Byte {
|
||||
|
||||
fn color(self) -> &'static [u8] {
|
||||
use crate::ByteCategory::*;
|
||||
|
||||
match self.category() {
|
||||
Null => COLOR_NULL,
|
||||
AsciiPrintable => COLOR_ASCII_PRINTABLE,
|
||||
@@ -67,16 +92,27 @@ impl Byte {
|
||||
}
|
||||
}
|
||||
|
||||
fn as_char(self) -> char {
|
||||
fn as_char(self, character_table: CharacterTable) -> char {
|
||||
use crate::ByteCategory::*;
|
||||
|
||||
match self.category() {
|
||||
Null => '⋄',
|
||||
AsciiPrintable => self.0 as char,
|
||||
AsciiWhitespace if self.0 == 0x20 => ' ',
|
||||
AsciiWhitespace => '_',
|
||||
AsciiOther => '•',
|
||||
NonAscii => '×',
|
||||
match character_table {
|
||||
CharacterTable::Default => match self.category() {
|
||||
Null => '⋄',
|
||||
AsciiPrintable => self.0 as char,
|
||||
AsciiWhitespace if self.0 == 0x20 => ' ',
|
||||
AsciiWhitespace => '_',
|
||||
AsciiOther => '•',
|
||||
NonAscii => '×',
|
||||
},
|
||||
CharacterTable::Ascii => match self.category() {
|
||||
Null => '.',
|
||||
AsciiPrintable => self.0 as char,
|
||||
AsciiWhitespace if self.0 == 0x20 => ' ',
|
||||
AsciiWhitespace => '.',
|
||||
AsciiOther => '.',
|
||||
NonAscii => '.',
|
||||
},
|
||||
CharacterTable::CP1047 => CP1047[self.0 as usize],
|
||||
CharacterTable::CP437 => CP437[self.0 as usize],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,10 +124,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,
|
||||
}
|
||||
|
||||
@@ -159,6 +201,8 @@ pub struct PrinterBuilder<'a, Writer: Write> {
|
||||
panels: u64,
|
||||
group_size: u8,
|
||||
base: Base,
|
||||
endianness: Endianness,
|
||||
character_table: CharacterTable,
|
||||
}
|
||||
|
||||
impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
||||
@@ -173,6 +217,8 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
||||
panels: 2,
|
||||
group_size: 1,
|
||||
base: Base::Hexadecimal,
|
||||
endianness: Endianness::Big,
|
||||
character_table: CharacterTable::Default,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,6 +262,16 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn endianness(mut self, endianness: Endianness) -> Self {
|
||||
self.endianness = endianness;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn character_table(mut self, character_table: CharacterTable) -> Self {
|
||||
self.character_table = character_table;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Printer<'a, Writer> {
|
||||
Printer::new(
|
||||
self.writer,
|
||||
@@ -227,6 +283,8 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
||||
self.panels,
|
||||
self.group_size,
|
||||
self.base,
|
||||
self.endianness,
|
||||
self.character_table,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -254,6 +312,8 @@ pub struct Printer<'a, Writer: Write> {
|
||||
group_size: u8,
|
||||
/// The number of digits used to write the base.
|
||||
base_digits: u8,
|
||||
/// Whether to show groups in little or big endian format.
|
||||
endianness: Endianness,
|
||||
}
|
||||
|
||||
impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
@@ -267,6 +327,8 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
panels: u64,
|
||||
group_size: u8,
|
||||
base: Base,
|
||||
endianness: Endianness,
|
||||
character_table: CharacterTable,
|
||||
) -> Printer<'a, Writer> {
|
||||
Printer {
|
||||
idx: 0,
|
||||
@@ -286,7 +348,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
})
|
||||
.collect(),
|
||||
byte_char_panel: (0u8..=u8::MAX)
|
||||
.map(|i| format!("{}", Byte(i).as_char()))
|
||||
.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 {
|
||||
@@ -304,6 +366,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
Base::Decimal => 3,
|
||||
Base::Hexadecimal => 2,
|
||||
},
|
||||
endianness,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,8 +445,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
if self.show_position_panel {
|
||||
match self.squeezer {
|
||||
Squeezer::Print => {
|
||||
self.writer
|
||||
.write_all(self.byte_char_panel[b'*' as usize].as_bytes())?;
|
||||
self.writer.write_all(&[b'*'])?;
|
||||
if self.show_color {
|
||||
self.writer.write_all(COLOR_RESET)?;
|
||||
}
|
||||
@@ -523,8 +585,26 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reorder_buffer_to_little_endian(&self, buf: &mut Vec<u8>) {
|
||||
let n = buf.len();
|
||||
let group_sz = self.group_size as usize;
|
||||
|
||||
for idx in (0..n).step_by(group_sz) {
|
||||
let remaining = n - idx;
|
||||
let total = remaining.min(group_sz);
|
||||
|
||||
buf[idx..idx + total].reverse();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn print_bytes(&mut self) -> io::Result<()> {
|
||||
for (i, &b) in self.line_buf.clone().iter().enumerate() {
|
||||
let mut buf = self.line_buf.clone();
|
||||
|
||||
if matches!(self.endianness, Endianness::Little) {
|
||||
self.reorder_buffer_to_little_endian(&mut buf);
|
||||
};
|
||||
|
||||
for (i, &b) in buf.iter().enumerate() {
|
||||
self.print_byte(i, b)?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -546,11 +626,23 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
self.print_header()?;
|
||||
is_empty = false;
|
||||
}
|
||||
|
||||
// perform second check on read
|
||||
if let Ok(0) = buf.read(&mut self.line_buf[n..]) {
|
||||
self.line_buf.resize(n, 0);
|
||||
break Some(n);
|
||||
let mut leftover = n;
|
||||
// loop until input is ceased
|
||||
if let Some(s) = loop {
|
||||
if let Ok(n) = buf.read(&mut self.line_buf[leftover..]) {
|
||||
leftover += n;
|
||||
// there is no more input being read
|
||||
if n == 0 {
|
||||
self.line_buf.resize(leftover, 0);
|
||||
break Some(leftover);
|
||||
}
|
||||
// amount read has exceeded line buffer
|
||||
if leftover >= 8 * self.panels as usize {
|
||||
break None;
|
||||
}
|
||||
}
|
||||
} {
|
||||
break Some(s);
|
||||
};
|
||||
} else if n == 0 {
|
||||
// if no bytes are read, that indicates end of file
|
||||
@@ -564,7 +656,6 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
}
|
||||
if is_empty {
|
||||
self.print_header()?;
|
||||
is_empty = false;
|
||||
}
|
||||
|
||||
// squeeze is active, check if the line is the same
|
||||
@@ -592,6 +683,11 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||
}
|
||||
self.writer.write_all(b"\n")?;
|
||||
|
||||
if is_empty {
|
||||
self.writer.flush()?;
|
||||
is_empty = false;
|
||||
}
|
||||
|
||||
// increment index to next line
|
||||
self.idx += 8 * self.panels;
|
||||
|
||||
@@ -682,6 +778,8 @@ mod tests {
|
||||
2,
|
||||
1,
|
||||
Base::Hexadecimal,
|
||||
Endianness::Big,
|
||||
CharacterTable::Default,
|
||||
);
|
||||
|
||||
printer.print_all(input).unwrap();
|
||||
@@ -736,6 +834,8 @@ mod tests {
|
||||
2,
|
||||
1,
|
||||
Base::Hexadecimal,
|
||||
Endianness::Big,
|
||||
CharacterTable::Default,
|
||||
);
|
||||
printer.display_offset(0xdeadbeef);
|
||||
|
||||
@@ -769,6 +869,8 @@ mod tests {
|
||||
4,
|
||||
1,
|
||||
Base::Hexadecimal,
|
||||
Endianness::Big,
|
||||
CharacterTable::Default,
|
||||
);
|
||||
|
||||
printer.print_all(input).unwrap();
|
||||
@@ -828,6 +930,8 @@ mod tests {
|
||||
3,
|
||||
1,
|
||||
Base::Hexadecimal,
|
||||
Endianness::Big,
|
||||
CharacterTable::Default,
|
||||
);
|
||||
|
||||
printer.print_all(input).unwrap();
|
||||
|
||||
+711
@@ -0,0 +1,711 @@
|
||||
use std::fs::File;
|
||||
use std::io::{self, prelude::*, BufWriter, SeekFrom};
|
||||
use std::num::{NonZeroI64, NonZeroU64};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::builder::ArgPredicate;
|
||||
use clap::{ArgAction, Parser, ValueEnum};
|
||||
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
|
||||
use const_format::formatcp;
|
||||
|
||||
use thiserror::Error as ThisError;
|
||||
|
||||
use terminal_size::terminal_size;
|
||||
|
||||
use hexyl::{Base, BorderStyle, CharacterTable, Endianness, Input, PrinterBuilder};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
const DEFAULT_BLOCK_SIZE: i64 = 512;
|
||||
|
||||
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";
|
||||
|
||||
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.";
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(version, about, max_term_width(90))]
|
||||
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,
|
||||
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
||||
let stdin = io::stdin();
|
||||
|
||||
let mut reader = match opt.file {
|
||||
Some(filename) => Input::File(File::open(filename)?),
|
||||
None => Input::Stdin(stdin.lock()),
|
||||
};
|
||||
|
||||
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 = opt
|
||||
.skip
|
||||
.as_ref()
|
||||
.map(|s| {
|
||||
parse_byte_offset(s, block_size).context(anyhow!(
|
||||
"failed to parse `--skip` arg {:?} as byte count",
|
||||
s
|
||||
))
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let skip_offset = if let Some(ByteOffset { kind, value }) = skip_arg {
|
||||
let value = value.into_inner();
|
||||
reader
|
||||
.seek(match kind {
|
||||
ByteOffsetKind::ForwardFromBeginning | ByteOffsetKind::ForwardFromLastOffset => {
|
||||
SeekFrom::Current(value)
|
||||
}
|
||||
ByteOffsetKind::BackwardFromEnd => SeekFrom::End(value.checked_neg().unwrap()),
|
||||
})
|
||||
.map_err(|_| {
|
||||
anyhow!(
|
||||
"Failed to jump to the desired input position. \
|
||||
This could be caused by a negative offset that is too large or by \
|
||||
an input that is not seek-able (e.g. if the input comes from a pipe)."
|
||||
)
|
||||
})?
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let parse_byte_count = |s| -> Result<u64> {
|
||||
Ok(parse_byte_offset(s, block_size)?
|
||||
.assume_forward_offset_from_start()?
|
||||
.into())
|
||||
};
|
||||
|
||||
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 opt.color {
|
||||
ColorWhen::Never => false,
|
||||
ColorWhen::Always => !no_color,
|
||||
ColorWhen::Force => true,
|
||||
ColorWhen::Auto => {
|
||||
if no_color {
|
||||
false
|
||||
} else {
|
||||
supports_color::on(supports_color::Stream::Stdout)
|
||||
.map(|level| level.has_basic)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let border_style = opt.border;
|
||||
|
||||
let &squeeze = &!opt.no_squeezing;
|
||||
|
||||
let show_char_panel = !opt.no_characters && !opt.plain;
|
||||
|
||||
let show_position_panel = !opt.no_position && !opt.plain;
|
||||
|
||||
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 };
|
||||
let col_width = if show_char_panel {
|
||||
((8 / group_size) * (base_digits * group_size + 1)) + 2 + 8
|
||||
} else {
|
||||
((8 / group_size) * (base_digits * group_size + 1)) + 2
|
||||
};
|
||||
if (terminal_width - offset) / col_width < 1 {
|
||||
1
|
||||
} else {
|
||||
(terminal_width - offset) / col_width
|
||||
}
|
||||
};
|
||||
|
||||
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."
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
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,
|
||||
Base::Octal => 3,
|
||||
Base::Decimal => 3,
|
||||
Base::Hexadecimal => 2,
|
||||
};
|
||||
|
||||
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 opt.panels.as_deref() == Some("auto") {
|
||||
max_panels_fn(terminal_width, base_digits, group_size.into())
|
||||
} else if let Some(panels) = opt.panels {
|
||||
panels
|
||||
.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,
|
||||
max_panels_fn(terminal_width, base_digits, group_size.into()),
|
||||
)
|
||||
};
|
||||
|
||||
let endianness = if opt.little_endian_format {
|
||||
Endianness::Little
|
||||
} else {
|
||||
opt.endianness
|
||||
};
|
||||
|
||||
let character_table = opt.character_table;
|
||||
|
||||
let stdout = io::stdout();
|
||||
let mut stdout_lock = BufWriter::new(stdout.lock());
|
||||
|
||||
let mut printer = PrinterBuilder::new(&mut stdout_lock)
|
||||
.show_color(show_color)
|
||||
.show_char_panel(show_char_panel)
|
||||
.show_position_panel(show_position_panel)
|
||||
.with_border_style(border_style)
|
||||
.enable_squeezing(squeeze)
|
||||
.num_panels(panels)
|
||||
.group_size(group_size)
|
||||
.with_base(base)
|
||||
.endianness(endianness)
|
||||
.character_table(character_table)
|
||||
.build();
|
||||
printer.display_offset(skip_offset + display_offset);
|
||||
printer.print_all(&mut reader).map_err(|e| anyhow!(e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let result = run();
|
||||
|
||||
if let Err(err) = result {
|
||||
if let Some(io_error) = err.downcast_ref::<io::Error>() {
|
||||
if io_error.kind() == ::std::io::ErrorKind::BrokenPipe {
|
||||
std::process::exit(0);
|
||||
}
|
||||
}
|
||||
eprintln!("Error: {err:?}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Hash, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct NonNegativeI64(i64);
|
||||
|
||||
impl NonNegativeI64 {
|
||||
pub fn new(x: i64) -> Option<Self> {
|
||||
if x.is_negative() {
|
||||
None
|
||||
} else {
|
||||
Some(Self(x))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> i64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NonNegativeI64> for u64 {
|
||||
fn from(x: NonNegativeI64) -> u64 {
|
||||
u64::try_from(x.0)
|
||||
.expect("invariant broken: NonNegativeI64 should contain a non-negative i64 value")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Hash, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct PositiveI64(i64);
|
||||
|
||||
impl PositiveI64 {
|
||||
pub fn new(x: i64) -> Option<Self> {
|
||||
if x < 1 {
|
||||
None
|
||||
} else {
|
||||
Some(Self(x))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_inner(self) -> i64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PositiveI64> for u64 {
|
||||
fn from(x: PositiveI64) -> u64 {
|
||||
u64::try_from(x.0)
|
||||
.expect("invariant broken: PositiveI64 should contain a positive i64 value")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Unit {
|
||||
Byte,
|
||||
Kilobyte,
|
||||
Megabyte,
|
||||
Gigabyte,
|
||||
Terabyte,
|
||||
Kibibyte,
|
||||
Mebibyte,
|
||||
Gibibyte,
|
||||
Tebibyte,
|
||||
/// a customizable amount of bytes
|
||||
Block {
|
||||
custom_size: Option<NonZeroI64>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Unit {
|
||||
const fn get_multiplier(self) -> i64 {
|
||||
match self {
|
||||
Self::Byte => 1,
|
||||
Self::Kilobyte => 1000,
|
||||
Self::Megabyte => 1_000_000,
|
||||
Self::Gigabyte => 1_000_000_000,
|
||||
Self::Terabyte => 1_000_000_000_000,
|
||||
Self::Kibibyte => 1 << 10,
|
||||
Self::Mebibyte => 1 << 20,
|
||||
Self::Gibibyte => 1 << 30,
|
||||
Self::Tebibyte => 1 << 40,
|
||||
Self::Block {
|
||||
custom_size: Some(size),
|
||||
} => size.get(),
|
||||
Self::Block { custom_size: None } => DEFAULT_BLOCK_SIZE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const HEX_PREFIX: &str = "0x";
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
enum ByteOffsetKind {
|
||||
ForwardFromBeginning,
|
||||
ForwardFromLastOffset,
|
||||
BackwardFromEnd,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
struct ByteOffset {
|
||||
value: NonNegativeI64,
|
||||
kind: ByteOffsetKind,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, ThisError)]
|
||||
#[error(
|
||||
"negative offset specified, but only positive offsets (counts) are accepted in this context"
|
||||
)]
|
||||
struct NegativeOffsetSpecifiedError;
|
||||
|
||||
impl ByteOffset {
|
||||
fn assume_forward_offset_from_start(
|
||||
&self,
|
||||
) -> Result<NonNegativeI64, NegativeOffsetSpecifiedError> {
|
||||
let &Self { value, kind } = self;
|
||||
match kind {
|
||||
ByteOffsetKind::ForwardFromBeginning | ByteOffsetKind::ForwardFromLastOffset => {
|
||||
Ok(value)
|
||||
}
|
||||
ByteOffsetKind::BackwardFromEnd => Err(NegativeOffsetSpecifiedError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, ThisError)]
|
||||
enum ByteOffsetParseError {
|
||||
#[error("no character data found, did you forget to write it?")]
|
||||
Empty,
|
||||
#[error("no digits found after sign, did you forget to write them?")]
|
||||
EmptyAfterSign,
|
||||
#[error(
|
||||
"found {0:?} sign after hex prefix ({:?}); signs should go before it",
|
||||
HEX_PREFIX
|
||||
)]
|
||||
SignFoundAfterHexPrefix(char),
|
||||
#[error("{0:?} is not of the expected form <pos-integer>[<unit>]")]
|
||||
InvalidNumAndUnit(String),
|
||||
#[error("{0:?} is a valid unit, but an integer should come before it")]
|
||||
EmptyWithUnit(String),
|
||||
#[error("invalid unit {0:?}")]
|
||||
InvalidUnit(String),
|
||||
#[error("failed to parse integer part")]
|
||||
ParseNum(#[source] std::num::ParseIntError),
|
||||
#[error("count multiplied by the unit overflowed a signed 64-bit integer; are you sure it should be that big?")]
|
||||
UnitMultiplicationOverflow,
|
||||
}
|
||||
|
||||
fn parse_byte_offset(n: &str, block_size: PositiveI64) -> Result<ByteOffset, ByteOffsetParseError> {
|
||||
use ByteOffsetParseError::*;
|
||||
|
||||
let (n, kind) = process_sign_of(n)?;
|
||||
|
||||
let into_byte_offset = |value| {
|
||||
Ok(ByteOffset {
|
||||
value: NonNegativeI64::new(value).unwrap(),
|
||||
kind,
|
||||
})
|
||||
};
|
||||
|
||||
if let Some(hex_number) = try_parse_as_hex_number(n) {
|
||||
return hex_number.map(into_byte_offset)?;
|
||||
}
|
||||
|
||||
let (num, mut unit) = extract_num_and_unit_from(n)?;
|
||||
if let Unit::Block { custom_size: None } = unit {
|
||||
unit = Unit::Block {
|
||||
custom_size: Some(
|
||||
NonZeroI64::new(block_size.into_inner()).expect("PositiveI64 was zero"),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
num.checked_mul(unit.get_multiplier())
|
||||
.ok_or(UnitMultiplicationOverflow)
|
||||
.and_then(into_byte_offset)
|
||||
}
|
||||
|
||||
/// Takes a string containing a base-10 number and an optional unit, and returns them with their proper types.
|
||||
/// The unit must directly follow the number (e.g. no whitespace is allowed between them).
|
||||
/// When no unit is given, [Unit::Byte] is assumed.
|
||||
/// When the unit is [Unit::Block], it is returned without custom size.
|
||||
/// No normalization is performed, that is "1024" is extracted to (1024, Byte), not (1, Kibibyte).
|
||||
fn extract_num_and_unit_from(n: &str) -> Result<(i64, Unit), ByteOffsetParseError> {
|
||||
use ByteOffsetParseError::*;
|
||||
if n.is_empty() {
|
||||
return Err(Empty);
|
||||
}
|
||||
match n.chars().position(|c| !c.is_ascii_digit()) {
|
||||
Some(unit_begin_idx) => {
|
||||
let (n, raw_unit) = n.split_at(unit_begin_idx);
|
||||
let unit = match raw_unit.to_lowercase().as_str() {
|
||||
"" => Unit::Byte, // no "b" => Byte to allow hex nums with units
|
||||
"kb" => Unit::Kilobyte,
|
||||
"mb" => Unit::Megabyte,
|
||||
"gb" => Unit::Gigabyte,
|
||||
"tb" => Unit::Terabyte,
|
||||
"kib" => Unit::Kibibyte,
|
||||
"mib" => Unit::Mebibyte,
|
||||
"gib" => Unit::Gibibyte,
|
||||
"tib" => Unit::Tebibyte,
|
||||
"block" | "blocks" => Unit::Block { custom_size: None },
|
||||
_ => {
|
||||
return if n.is_empty() {
|
||||
Err(InvalidNumAndUnit(raw_unit.to_string()))
|
||||
} else {
|
||||
Err(InvalidUnit(raw_unit.to_string()))
|
||||
}
|
||||
}
|
||||
};
|
||||
let num = n.parse::<i64>().map_err(|e| {
|
||||
if n.is_empty() {
|
||||
EmptyWithUnit(raw_unit.to_owned())
|
||||
} else {
|
||||
ParseNum(e)
|
||||
}
|
||||
})?;
|
||||
Ok((num, unit))
|
||||
}
|
||||
None => {
|
||||
// no unit part
|
||||
let num = n.parse::<i64>().map_err(ParseNum)?;
|
||||
Ok((num, Unit::Byte))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts a [ByteOffsetKind] based on the sign at the beginning of the given string.
|
||||
/// Returns the input string without the sign (or an equal string if there wasn't any sign).
|
||||
fn process_sign_of(n: &str) -> Result<(&str, ByteOffsetKind), ByteOffsetParseError> {
|
||||
use ByteOffsetParseError::*;
|
||||
let mut chars = n.chars();
|
||||
let next_char = chars.next();
|
||||
let check_empty_after_sign = || {
|
||||
if chars.clone().next().is_none() {
|
||||
Err(EmptyAfterSign)
|
||||
} else {
|
||||
Ok(chars.as_str())
|
||||
}
|
||||
};
|
||||
match next_char {
|
||||
Some('+') => Ok((
|
||||
check_empty_after_sign()?,
|
||||
ByteOffsetKind::ForwardFromLastOffset,
|
||||
)),
|
||||
Some('-') => Ok((check_empty_after_sign()?, ByteOffsetKind::BackwardFromEnd)),
|
||||
None => Err(Empty),
|
||||
_ => Ok((n, ByteOffsetKind::ForwardFromBeginning)),
|
||||
}
|
||||
}
|
||||
|
||||
/// If `n` starts with a hex prefix, its remaining part is returned as some number (if possible),
|
||||
/// otherwise None is returned.
|
||||
fn try_parse_as_hex_number(n: &str) -> Option<Result<i64, ByteOffsetParseError>> {
|
||||
use ByteOffsetParseError::*;
|
||||
n.strip_prefix(HEX_PREFIX).map(|num| {
|
||||
let mut chars = num.chars();
|
||||
match chars.next() {
|
||||
Some(c @ '+') | Some(c @ '-') => {
|
||||
return if chars.next().is_none() {
|
||||
Err(EmptyAfterSign)
|
||||
} else {
|
||||
Err(SignFoundAfterHexPrefix(c))
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
i64::from_str_radix(num, 16).map_err(ParseNum)
|
||||
})
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn unit_multipliers() {
|
||||
use Unit::*;
|
||||
assert_eq!(Kilobyte.get_multiplier(), 1000 * Byte.get_multiplier());
|
||||
assert_eq!(Megabyte.get_multiplier(), 1000 * Kilobyte.get_multiplier());
|
||||
assert_eq!(Gigabyte.get_multiplier(), 1000 * Megabyte.get_multiplier());
|
||||
assert_eq!(Terabyte.get_multiplier(), 1000 * Gigabyte.get_multiplier());
|
||||
|
||||
assert_eq!(Kibibyte.get_multiplier(), 1024 * Byte.get_multiplier());
|
||||
assert_eq!(Mebibyte.get_multiplier(), 1024 * Kibibyte.get_multiplier());
|
||||
assert_eq!(Gibibyte.get_multiplier(), 1024 * Mebibyte.get_multiplier());
|
||||
assert_eq!(Tebibyte.get_multiplier(), 1024 * Gibibyte.get_multiplier());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_sign() {
|
||||
use ByteOffsetKind::*;
|
||||
use ByteOffsetParseError::*;
|
||||
assert_eq!(process_sign_of("123"), Ok(("123", ForwardFromBeginning)));
|
||||
assert_eq!(process_sign_of("+123"), Ok(("123", ForwardFromLastOffset)));
|
||||
assert_eq!(process_sign_of("-123"), Ok(("123", BackwardFromEnd)));
|
||||
assert_eq!(process_sign_of("-"), Err(EmptyAfterSign));
|
||||
assert_eq!(process_sign_of("+"), Err(EmptyAfterSign));
|
||||
assert_eq!(process_sign_of(""), Err(Empty));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_as_hex() {
|
||||
assert_eq!(try_parse_as_hex_number("73"), None);
|
||||
assert_eq!(try_parse_as_hex_number("0x1337"), Some(Ok(0x1337)));
|
||||
assert!(matches!(try_parse_as_hex_number("0xnope"), Some(Err(_))));
|
||||
assert!(matches!(try_parse_as_hex_number("0x-1"), Some(Err(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_num_and_unit() {
|
||||
use ByteOffsetParseError::*;
|
||||
use Unit::*;
|
||||
// byte is default unit
|
||||
assert_eq!(extract_num_and_unit_from("4"), Ok((4, Byte)));
|
||||
// blocks are returned without customization
|
||||
assert_eq!(
|
||||
extract_num_and_unit_from("2blocks"),
|
||||
Ok((2, Block { custom_size: None }))
|
||||
);
|
||||
// no normalization is performed
|
||||
assert_eq!(extract_num_and_unit_from("1024kb"), Ok((1024, Kilobyte)));
|
||||
|
||||
// unit without number results in error
|
||||
assert_eq!(
|
||||
extract_num_and_unit_from("gib"),
|
||||
Err(EmptyWithUnit("gib".to_string()))
|
||||
);
|
||||
// empty string results in error
|
||||
assert_eq!(extract_num_and_unit_from(""), Err(Empty));
|
||||
// an invalid unit results in an error
|
||||
assert_eq!(
|
||||
extract_num_and_unit_from("25litres"),
|
||||
Err(InvalidUnit("litres".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_byte_offset() {
|
||||
use ByteOffsetParseError::*;
|
||||
|
||||
macro_rules! success {
|
||||
($input: expr, $expected_kind: ident $expected_value: expr) => {
|
||||
success!($input, $expected_kind $expected_value; block_size: DEFAULT_BLOCK_SIZE)
|
||||
};
|
||||
($input: expr, $expected_kind: ident $expected_value: expr; block_size: $block_size: expr) => {
|
||||
assert_eq!(
|
||||
parse_byte_offset($input, PositiveI64::new($block_size).unwrap()),
|
||||
Ok(
|
||||
ByteOffset {
|
||||
value: NonNegativeI64::new($expected_value).unwrap(),
|
||||
kind: ByteOffsetKind::$expected_kind,
|
||||
}
|
||||
),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! error {
|
||||
($input: expr, $expected_err: expr) => {
|
||||
assert_eq!(
|
||||
parse_byte_offset($input, PositiveI64::new(DEFAULT_BLOCK_SIZE).unwrap()),
|
||||
Err($expected_err),
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
success!("0", ForwardFromBeginning 0);
|
||||
success!("1", ForwardFromBeginning 1);
|
||||
success!("1", ForwardFromBeginning 1);
|
||||
success!("100", ForwardFromBeginning 100);
|
||||
success!("+100", ForwardFromLastOffset 100);
|
||||
|
||||
success!("0x0", ForwardFromBeginning 0);
|
||||
success!("0xf", ForwardFromBeginning 15);
|
||||
success!("0xdeadbeef", ForwardFromBeginning 3_735_928_559);
|
||||
|
||||
success!("1KB", ForwardFromBeginning 1000);
|
||||
success!("2MB", ForwardFromBeginning 2000000);
|
||||
success!("3GB", ForwardFromBeginning 3000000000);
|
||||
success!("4TB", ForwardFromBeginning 4000000000000);
|
||||
success!("+4TB", ForwardFromLastOffset 4000000000000);
|
||||
|
||||
success!("1GiB", ForwardFromBeginning 1073741824);
|
||||
success!("2TiB", ForwardFromBeginning 2199023255552);
|
||||
success!("+2TiB", ForwardFromLastOffset 2199023255552);
|
||||
|
||||
success!("0xff", ForwardFromBeginning 255);
|
||||
success!("0xEE", ForwardFromBeginning 238);
|
||||
success!("+0xFF", ForwardFromLastOffset 255);
|
||||
|
||||
success!("1block", ForwardFromBeginning 512; block_size: 512);
|
||||
success!("2block", ForwardFromBeginning 1024; block_size: 512);
|
||||
success!("1block", ForwardFromBeginning 4; block_size: 4);
|
||||
success!("2block", ForwardFromBeginning 8; block_size: 4);
|
||||
|
||||
// empty string is invalid
|
||||
error!("", Empty);
|
||||
// These are also bad.
|
||||
error!("+", EmptyAfterSign);
|
||||
error!("-", EmptyAfterSign);
|
||||
error!("K", InvalidNumAndUnit("K".to_owned()));
|
||||
error!("k", InvalidNumAndUnit("k".to_owned()));
|
||||
error!("m", InvalidNumAndUnit("m".to_owned()));
|
||||
error!("block", EmptyWithUnit("block".to_owned()));
|
||||
// leading/trailing space is invalid
|
||||
error!(" 0", InvalidNumAndUnit(" 0".to_owned()));
|
||||
error!("0 ", InvalidUnit(" ".to_owned()));
|
||||
// Signs after the hex prefix make no sense
|
||||
error!("0x-12", SignFoundAfterHexPrefix('-'));
|
||||
// This was previously accepted but shouldn't be.
|
||||
error!("0x+12", SignFoundAfterHexPrefix('+'));
|
||||
// invalid suffix
|
||||
error!("1234asdf", InvalidUnit("asdf".to_owned()));
|
||||
// bad numbers
|
||||
error!("asdf1234", InvalidNumAndUnit("asdf1234".to_owned()));
|
||||
error!("a1s2d3f4", InvalidNumAndUnit("a1s2d3f4".to_owned()));
|
||||
// multiplication overflows u64
|
||||
error!("20000000TiB", UnitMultiplicationOverflow);
|
||||
|
||||
assert!(
|
||||
match parse_byte_offset("99999999999999999999", PositiveI64::new(512).unwrap()) {
|
||||
// We can't check against the kind of the `ParseIntError`, so we'll just make sure it's the
|
||||
// same as trying to do the parse directly.
|
||||
Err(ParseNum(e)) => e == "99999999999999999999".parse::<i64>().unwrap_err(),
|
||||
_ => false,
|
||||
}
|
||||
);
|
||||
}
|
||||
+219
-4
@@ -292,12 +292,12 @@ mod display_settings {
|
||||
}
|
||||
}
|
||||
|
||||
mod group {
|
||||
mod group_and_endianness {
|
||||
use super::hexyl;
|
||||
use super::PrettyAssert;
|
||||
|
||||
#[test]
|
||||
fn group_2_bytes() {
|
||||
fn group_2_bytes_be() {
|
||||
hexyl()
|
||||
.arg("ascii")
|
||||
.arg("--color=never")
|
||||
@@ -312,7 +312,23 @@ mod group {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_4_bytes() {
|
||||
fn group_2_bytes_le() {
|
||||
hexyl()
|
||||
.arg("ascii")
|
||||
.arg("--color=never")
|
||||
.arg("--group-size=2")
|
||||
.arg("--endianness=little")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(
|
||||
"┌────────┬─────────────────────┬─────────────────────┬────────┬────────┐\n\
|
||||
│00000000│ 3130 3332 3534 3736 ┊ 3938 6261 6463 0a65 │01234567┊89abcde_│\n\
|
||||
└────────┴─────────────────────┴─────────────────────┴────────┴────────┘\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_4_bytes_be() {
|
||||
hexyl()
|
||||
.arg("ascii")
|
||||
.arg("--color=never")
|
||||
@@ -327,7 +343,23 @@ mod group {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_8_bytes() {
|
||||
fn group_4_bytes_le() {
|
||||
hexyl()
|
||||
.arg("ascii")
|
||||
.arg("--color=never")
|
||||
.arg("--group-size=4")
|
||||
.arg("--endianness=little")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(
|
||||
"┌────────┬───────────────────┬───────────────────┬────────┬────────┐\n\
|
||||
│00000000│ 33323130 37363534 ┊ 62613938 0a656463 │01234567┊89abcde_│\n\
|
||||
└────────┴───────────────────┴───────────────────┴────────┴────────┘\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_8_bytes_be() {
|
||||
hexyl()
|
||||
.arg("ascii")
|
||||
.arg("--color=never")
|
||||
@@ -341,6 +373,22 @@ mod group {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_8_bytes_le() {
|
||||
hexyl()
|
||||
.arg("ascii")
|
||||
.arg("--color=never")
|
||||
.arg("--group-size=8")
|
||||
.arg("--endianness=little")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(
|
||||
"┌────────┬──────────────────┬──────────────────┬────────┬────────┐\n\
|
||||
│00000000│ 3736353433323130 ┊ 0a65646362613938 │01234567┊89abcde_│\n\
|
||||
└────────┴──────────────────┴──────────────────┴────────┴────────┘\n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_size_plain() {
|
||||
hexyl()
|
||||
@@ -570,3 +618,170 @@ mod base {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod character_table {
|
||||
use super::hexyl;
|
||||
use super::PrettyAssert;
|
||||
|
||||
#[test]
|
||||
fn ascii() {
|
||||
hexyl()
|
||||
.arg("hello_world_elf64")
|
||||
.arg("--color=never")
|
||||
.arg("--character-table=ascii")
|
||||
.assert()
|
||||
.success()
|
||||
.pretty_stdout(
|
||||
"┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
|
||||
│00000000│ 7f 45 4c 46 02 01 01 00 ┊ 00 00 00 00 00 00 00 00 │.ELF....┊........│
|
||||
│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 │....@.8.┊..@.....│
|
||||
│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 │........┊........│
|
||||
│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 │Hello, w┊orld!...│
|
||||
│00002010│ 73 68 73 74 72 74 61 62 ┊ 00 2e 74 65 78 74 00 2e │shstrtab┊..text..│
|
||||
│00002020│ 64 61 74 61 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │data....┊........│
|
||||
│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 ┊ │........┊ │
|
||||
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codepage_437() {
|
||||
hexyl()
|
||||
.arg("hello_world_elf64")
|
||||
.arg("--color=never")
|
||||
.arg("--character-table=codepage-437")
|
||||
.assert()
|
||||
.success()
|
||||
.pretty_stdout(
|
||||
"┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
|
||||
│00000000│ 7f 45 4c 46 02 01 01 00 ┊ 00 00 00 00 00 00 00 00 │⌂ELF☻☺☺⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│
|
||||
│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 │⋄⋄⋄⋄@⋄8⋄┊♥⋄@⋄♦⋄♥⋄│
|
||||
│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 │Φ⋄⋄⋄⋄⋄⋄⋄┊Φ⋄⋄⋄⋄⋄⋄⋄│
|
||||
│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 │Hello, w┊orld!◙⋄.│
|
||||
│00002010│ 73 68 73 74 72 74 61 62 ┊ 00 2e 74 65 78 74 00 2e │shstrtab┊⋄.text⋄.│
|
||||
│00002020│ 64 61 74 61 00 00 00 00 ┊ 00 00 00 00 00 00 00 00 │data⋄⋄⋄⋄┊⋄⋄⋄⋄⋄⋄⋄⋄│
|
||||
│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 ┊ │⋄⋄⋄⋄⋄⋄⋄⋄┊ │
|
||||
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[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 ┊ │........┊ │
|
||||
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
|
||||
",
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user