54 Commits
Author SHA1 Message Date
sharkdp f8f410b8ee Simplify is_printable check, closes #78 2020-06-07 15:26:07 +02:00
sharkdp 7307077a66 Run lib example on TravisCI 2020-06-07 15:14:14 +02:00
sharkdp 3ca002638c Improve help text 2020-06-07 15:12:35 +02:00
sharkdp ccfceaf58c Minor formatting 2020-06-07 15:12:35 +02:00
sharkdp 751b670d5b Fix clippy suggestions 2020-06-07 15:12:35 +02:00
sharkdp 478cc25ffd Bump version to 0.8.0 2020-06-07 15:12:35 +02:00
sharkdp b1c4b0bf2b Update dependencies 2020-06-07 15:12:35 +02:00
sharkdp 1951c59b0c Add CHANGELOG 2020-06-07 15:12:35 +02:00
Erich Gubler ddc3a5c04a BREAKING CHANGE: improvement: change byte counts to be a positive i64 (with newtype PositiveI64) instead of a u64 to take advantage of new skip impl for --seek with non-Seek Inputs like STDIN
This is technically a breaking change because it halves the upper range
of accepted values for byte counts. I thought that fine under the
assumption it will be rare for users to ever want to specify something
in the upper half of `u64`'s value range, especially considering that
this can pave the way for having negative byte offsets (instead of just
counts).
2020-06-07 11:08:01 +02:00
Erich Gubler 6d5c61b671 BREAKING CHANGE: improvement(lib): use u64 for Printer::display_offset and Squeezer::process, not usize
Technically a breaking change for architectures where
`size_of::<usize>() > size_of::<u64>()`, but that strikes me as
extremely unlikely.

This change is important because most file seek offsets, even on older
architectures with word sizes smaller than `u32`, accept a `u64`. In
particular, Rust's own `std` acknowledges this. So, we're actually going
against prior art in Rust by using a `usize`.
2020-06-07 11:08:01 +02:00
Erich Gubler c5c8d1190f BREAKING CHANGE: feat(lib): skip forward when relative forward seeks are specified on non-seekable streams
This is technically a breaking change, since the data and behavior of
`Input` is exposed publicly. However, it seems unlikely to me that
clients actually depended on a failure in the case that's being changed.
2020-06-07 11:08:01 +02:00
Erich Gubler 1459616d27 improvement: use or instead of unwrap_or_else for display_offset 2020-06-07 11:08:01 +02:00
Erich Gubler 8af8a94699 improvement(lib): align string literals better for black-box tests 2020-05-28 18:44:23 +02:00
Erich Gubler 07cdc97e74 improvement: replace trim_start_matches with slicing op 2020-05-28 18:43:53 +02:00
Erich Gubler bcca1fae03 feat: add block unit and block-size option 2020-05-28 18:43:53 +02:00
Erich Gubler 48ad95efe6 feat: add units system to decimals with {k,m,g,t}[[i]b] support 2020-05-28 18:43:53 +02:00
Erich Gubler 3b3889967c refactor: extract "0x" literal into HEX_PREFIX 2020-05-28 18:43:53 +02:00
Erich Gubler 57f1fb4dc4 BREAKING CHANGE: improvement: disallow leading '+' after "0x" prefix for u64 parse 2020-05-28 18:43:53 +02:00
Erich Gubler ef40be0d53 improvement: fix or_fun_call lint from clippy 2020-05-28 18:43:53 +02:00
Erich Gubler 6b235b1d2e improvement: remove unused atty import 2020-05-28 18:43:53 +02:00
notramo 7cff10b274 Added Void Linux package install to README 2020-05-26 20:03:09 +02:00
Mario Reder 5327b71215 fix review comments 2020-05-13 18:55:24 +02:00
Mario Reder c336259159 skip option error messages and display offset 2020-05-13 18:55:24 +02:00
sharkdp 133fdd9156 Remove unnecessary RefCell, backport to Rust 1.36 2020-05-13 18:55:24 +02:00
Mario Reder 244f71691d feat: add skip option
resolves #16 and #87
2020-05-13 18:55:24 +02:00
sharkdp 6f73f4c0f0 Remove custom .github folder 2020-03-16 07:55:46 +01:00
sharkdp 7843b2d038 Update links 2020-03-15 11:33:08 +01:00
David Peter 692ff64e4b Add links to Ubuntu / Debian packages
closes #77
2020-03-09 21:05:26 +01:00
sharkdp 1d156282da Try -gnu 2020-03-09 20:58:50 +01:00
sharkdp b4bdf0f85e Re-enable Windows build 2020-03-09 20:58:50 +01:00
sharkdp 2bda250906 Bump version 2020-03-09 20:41:00 +01:00
sharkdp f32431e552 Update dependencies 2020-03-09 20:33:08 +01:00
sharkdp f9cb2bc872 Fix tests 2020-03-09 20:23:46 +01:00
sharkdp f71ad1e00d Remove Ctrl-C handler, fixes #84 2020-03-09 20:20:12 +01:00
sharkdp 3b01d0ce19 Disable Windows build 2020-01-26 08:50:55 +01:00
sharkdp abc79396df Add simple hexyl-as-a-library example, see #81 2020-01-26 08:12:02 +01:00
David Peter 67b6c2533d Clarify Windows Terminal requirements 2019-10-08 20:52:54 +02:00
sharkdp c75a882081 Update dependencies 2019-10-08 20:32:56 +02:00
sharkdp 81787d0858 Bump version 2019-10-08 20:31:56 +02:00
sharkdp 8e77507686 Enable unit tests on Travis CI 2019-10-08 20:29:52 +02:00
sharkdp 9c46ced572 Enable LTO 2019-10-08 20:09:10 +02:00
sharkdp 161ddd7ea1 Only print to buffer if line is not squeezed 2019-10-08 20:09:10 +02:00
David Peter 53110bac6f Add sponsor button as an experiment :-) 2019-10-08 18:35:09 +02:00
Tom Milligan d64053771c hexyl: add display offset option 2019-10-07 23:36:53 +02:00
Noah AL-Shihabi 23f199ffbd Remove additional space found on short input 2019-10-01 20:23:57 +02:00
Tom Milligan 175847787d fix crate import for travis tests 2019-07-17 19:31:37 +02:00
Tom Milligan 622a084ea6 run: factor out mainloop and add tests 2019-07-17 19:31:37 +02:00
Tom Milligan 4c48eedb18 printer: accept any Write as output 2019-07-17 19:31:37 +02:00
Tom Milligan ca8970b5cc lib: main and run in bin, everything else in lib 2019-07-17 19:31:37 +02:00
Tom Milligan fb49b633b2 lib: created, moved bin 2019-07-17 19:31:37 +02:00
sharkdp 82c34fe39b Bump version 2019-05-30 15:35:21 +02:00
Armin Widegreen c4028bb213 Consider color when printing squeeze indicator
When color option has been set to never, the squeeze indicator
character asterisk '*' should not be printed in color.

Fixes #63
2019-05-30 15:29:37 +02:00
Armin Widegreen def066ff92 Fixes squeeze state-machine for first/last char
Consider equality for first and last character of a line.

If the first or last character of a line is not equal to previous character,
the state machine should be set back to probe as the line should not be
squeezed.

The unit tests has been expanded for testing the use case described in #62.
Further the body of all unit tests has been simplified.

Fixes #62
2019-05-30 15:29:37 +02:00
sharkdp 17c2c61bad Update dependencies, closes #61 2019-05-28 21:37:33 +02:00
10 changed files with 1029 additions and 418 deletions

No files matched your search

+5 -4
View File
@@ -14,11 +14,11 @@ matrix:
env: TARGET=x86_64-apple-darwin
- os: windows
rust: stable
env: TARGET=x86_64-pc-windows-msvc
env: TARGET=x86_64-pc-windows-gnu
# Minimum Rust supported channel.
- os: linux
rust: 1.31.0
rust: 1.36.0
env: TARGET=x86_64-unknown-linux-gnu
@@ -43,7 +43,8 @@ install:
script:
# Incorporate TARGET env var to the build and test process
- cargo build --target $TARGET --verbose
#- cargo test --target $TARGET --verbose
- cargo test --target $TARGET --verbose
- cargo run --target $TARGET --example=simple
before_deploy:
- bash ci/before_deploy.bash
@@ -72,7 +73,7 @@ deploy:
# deploy only if we push a tag
tags: true
# deploy only on stable channel that has TARGET env variable sets
condition: $TRAVIS_RUST_VERSION = stable && $TARGET != "x86_64-pc-windows-msvc"
condition: $TRAVIS_RUST_VERSION = stable && $TARGET != "x86_64-pc-windows-gnu"
notifications:
email:
+106
View File
@@ -0,0 +1,106 @@
# v0.8.0
## Features
- A new `--skip <N>` / `-s <N>` option can be used to skip the first `N` bytes of the input, see #16, #88 (@Tarnadas, @MaxJohansen, @ErichDonGubler)
- The `--length`/`--bytes`/`--skip`/`--display-offset` options can now take units for their value argument, for example:
``` bash
hexyl /dev/random --length=1KiB
hexyl $(which hexyl) --skip=1MiB --length=10KiB
```
Both decimal SI prefixes (kB, MB, …) as well as binary IEC prefixes (KiB, MiB, …) are supported.
In addition, there is a new `--block-size <SIZE>` option that can be used to control the size of the `block`
unit:
``` bash
hexyl /dev/random --block-size=4kB --length=2block
```
See: #44 (@ErichDonGubler and @aswild)
## Other
- Various improvements throughout the code base by @ErichDonGubler
## Packaging
- `hexyl` is now available on Void Linux, see #91 (@notramo)
# v0.7.0
## Bugfixes
- hexyl can now be closed with `Ctrl-C` when reading input from STDIN, see #84
## Changes
- Breaking change (library): [`Printer::print_all`](https://docs.rs/hexyl/latest/hexyl/struct.Printer.html#method.print_all) does not take a second argument anymore.
- Added an example on how to use `hexyl` as a library: https://github.com/sharkdp/hexyl/blob/v0.7.0/examples/simple.rs
# v0.6.0
## Features
- `hexyl` can now be used as a library, see #67 (@tommilligan)
- Added a new `-o`/`--display-offset` option to add a certain offset to the
reported file positions, see #57 (@tommilligan)
## Bugfixes
- Remove additional space on short input, see #69 (@nalshihabi)
## Other
- Performance improvements, see #73 and #66
# v0.5.1
## Bugfixes
- A bug in the squeezing logic caused a wrong hexdump, see #62 (@awidegreen)
- Some colors are printed even if they're disabled, see #64 (@awidegreen)
- Fixed build failure on OpenBSD 6.5, see #61
# v0.5.0
## Features
- Added support for squeezing where reoccuring lines are squashed together and visualized with an asterisk. A new `-v`/`--no-squeezing` option can be used to disable the feature. For details, see #59 (@awidegreen)
- Added a new `--border` option with support for various styles (Unicode, ASCII, None), see #54 (@dmke)
- The `--length`/`-n` argument can be passed as a hexadecimal number (`hexyl -n 0xff /dev/urandom`), see #45 (@Qyriad)
- Added `--bytes`/`-c` as an alias for `--length`/`-n`, see #48 (@selfup)
## Changes
- Print header immediately before the first line, see #51 (@mziter)
# v0.4.0
## Features
- Added a new `--color=always/auto/never` option which can be used
to control `hexyl`s color output, see #30 (@bennetthardwick)
- Use 16 colors instead of 256, see #38
## Changes
- Various speed improvements, see #33 (@kballard)
## Bugfixes
- Proper Ctrl-C handling, see #35
- Proper handling of broken pipes (`hexyl … | head`)
# v0.3.1
- Various (huge) performance improvements, see #23 and #24 (@kballard)
- Replaced 24-bit truecolor ANSI codes by 8-bit codes to support
more terminal emulators, fixes #9
# v0.3.0
Windows support
# v0.2.0
Initial release
Generated
+47 -119
View File
@@ -5,107 +5,70 @@ name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "atty"
version = "0.2.11"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
"termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bitflags"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cc"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "cfg-if"
version = "0.1.7"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "clap"
version = "2.33.0"
version = "2.33.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ctrlc"
version = "3.1.1"
name = "hermit-abi"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "hexyl"
version = "0.5.0"
version = "0.8.0"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
"atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libc"
version = "0.2.51"
version = "0.2.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "nix"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "redox_syscall"
version = "0.1.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "redox_termios"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "strsim"
version = "0.8.0"
@@ -113,22 +76,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "term_size"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "termion"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)",
"redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@@ -136,44 +88,29 @@ name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "unicode-width"
version = "0.1.5"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "vec_map"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "void"
version = "1.0.2"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi"
version = "0.3.7"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
@@ -186,26 +123,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12"
"checksum cc 1.0.35 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5f3fee5eeb60324c2781f1e41286bdee933850fff9b3c672587fed5ec58c83"
"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)" = "bedcc7a809076656486ffe045abeeac163da1b558e963a31e29fbfbeba916917"
"checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17"
"checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252"
"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
"checksum ansi_term 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum clap 2.33.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
"checksum hermit-abi 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71"
"checksum libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum term_size 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9e5b9a66db815dcfd2da92db471106457082577c3c278d4138ab3e3b4e189327"
"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
"checksum term_size 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9"
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770"
"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"checksum vec_map 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+7 -3
View File
@@ -7,14 +7,18 @@ license = "MIT/Apache-2.0"
name = "hexyl"
readme = "README.md"
repository = "https://github.com/sharkdp/hexyl"
version = "0.5.0"
version = "0.8.0"
edition = "2018"
[dependencies]
ansi_term = "0.11"
ansi_term = "0.12"
atty = "0.2"
ctrlc = "3.1"
libc = "0.2"
[dependencies.clap]
version = "2"
features = ["suggestions", "color", "wrap_help"]
[profile.release]
lto = true
codegen-units = 1
+30 -6
View File
@@ -19,13 +19,31 @@ of bytes (NULL bytes, printable ASCII characters, ASCII whitespace characters, o
## Installation
### On Debian-based systems
### On Ubuntu
*... and other Debian-based Linux distributions.*
If you run Ubuntu 19.10 (Eoan Ermine) or newer, you can install the [officially maintained package](https://packages.ubuntu.com/eoan/hexyl):
```bash
sudo apt install hexyl
```
If you use an older version of Ubuntu, you can download
the latest `.deb` package from the release page and install it via:
``` bash
wget "https://github.com/sharkdp/hexyl/releases/download/v0.5.0/hexyl_0.5.0_amd64.deb"
sudo dpkg -i hexyl_0.5.0_amd64.deb
sudo dpkg -i hexyl_0.7.0_amd64.deb # adapt version number and architecture
```
### On Debian
If you run Debian Buster or newer, you can install the [officially maintained Debian package](https://packages.debian.org/buster/hexyl):
```bash
sudo apt-get install hexyl
```
If you run an older version of Debian, see above for instructions on how to
manually install `hexyl`.
### On Arch Linux
You can install `hexyl` from [the official package repository](https://www.archlinux.org/packages/community/x86_64/hexyl/):
@@ -34,6 +52,11 @@ You can install `hexyl` from [the official package repository](https://www.archl
pacman -S hexyl
```
### On Void Linux
```
xbps-install hexyl
```
### On macOS
```
@@ -58,12 +81,13 @@ Check out the [release page](https://github.com/sharkdp/hexyl/releases) for bina
### On Windows
For now, you will have to install from source via `cargo` (see below). Make sure that
you use a terminal that supports ANSI escape sequences (like PowerShell on Windows 10).
For now, you will have to install from source via `cargo` (see below). Make sure that you
use a terminal that supports ANSI escape sequences (like ConHost v2 since Windows 10 1703
or Windows Terminal since Windows 10 1903).
### Via cargo
If you have Rust 1.31 or higher, you can install `hexyl` from source via `cargo`:
If you have Rust 1.36 or higher, you can install `hexyl` from source via `cargo`:
```
cargo install hexyl
```
+20
View File
@@ -0,0 +1,20 @@
use std::io;
use hexyl::{BorderStyle, Printer};
fn main() {
let input = vec![
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44,
0x52, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x44, 0x08, 0x02, 0x00, 0x00, 0x00,
];
let stdout = io::stdout();
let mut handle = stdout.lock();
let show_color = true;
let use_squeezing = false;
let border_style = BorderStyle::Unicode;
let mut printer = Printer::new(&mut handle, show_color, border_style, use_squeezing);
printer.print_all(&input[..]).unwrap();
}
+352
View File
@@ -0,0 +1,352 @@
#[macro_use]
extern crate clap;
use std::convert::TryFrom;
use std::fs::File;
use std::io::{self, prelude::*, SeekFrom};
use clap::{App, AppSettings, Arg};
use atty::Stream;
use hexyl::{BorderStyle, Input, Printer};
fn run() -> Result<(), Box<dyn std::error::Error>> {
let app = App::new(crate_name!())
.setting(AppSettings::ColorAuto)
.setting(AppSettings::ColoredHelp)
.setting(AppSettings::DeriveDisplayOrder)
.setting(AppSettings::UnifiedHelpMessage)
.version(crate_version!())
.about(crate_description!())
.arg(
Arg::with_name("FILE")
.help("The file to display. If no FILE argument is given, read from STDIN."),
)
.arg(
Arg::with_name("length")
.short("n")
.long("length")
.takes_value(true)
.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, ..).\n\
Examples: --length=64, --length=4KiB",
),
)
.arg(
Arg::with_name("bytes")
.short("c")
.long("bytes")
.takes_value(true)
.value_name("N")
.help("An alias for -n/--length"),
)
.arg(
Arg::with_name("skip")
.short("s")
.long("skip")
.takes_value(true)
.value_name("N")
.help(
"Skip the first N bytes of the input. The N argument can also include \
a unit (see `--length` for details)",
),
)
.arg(
Arg::with_name("block_size")
.long("block-size")
.takes_value(true)
.value_name("SIZE")
.help(
"Sets the size of the `block` unit to SIZE.\n\
Examples: --block-size=1024, --block-size=4kB",
),
)
.arg(
Arg::with_name("nosqueezing")
.short("v")
.long("no-squeezing")
.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::with_name("color")
.long("color")
.takes_value(true)
.value_name("WHEN")
.possible_values(&["always", "auto", "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::with_name("border")
.long("border")
.takes_value(true)
.value_name("STYLE")
.possible_values(&["unicode", "ascii", "none"])
.default_value("unicode")
.help("Whether to draw a border with Unicode characters, ASCII characters, or none at all"),
)
.arg(
Arg::with_name("display_offset")
.short("o")
.long("display-offset")
.takes_value(true)
.value_name("N")
.help("Add N bytes to the displayed file position. The N argument can also include \
a unit (see `--length` for details)"),
);
let matches = app.get_matches_safe()?;
let stdin = io::stdin();
let mut reader: Input = match matches.value_of("FILE") {
Some(filename) => Input::File(File::open(filename)?),
None => Input::Stdin(stdin.lock()),
};
let block_size = matches
.value_of("block_size")
.and_then(|bs| bs.parse().ok().and_then(PositiveI64::new))
.unwrap_or_else(|| PositiveI64::new(512).unwrap());
let skip_arg = matches
.value_of("skip")
.and_then(|s| parse_byte_count(s, block_size));
if let Some(skip) = skip_arg {
reader.seek(SeekFrom::Current(skip.into_inner()))?;
}
let length_arg = matches
.value_of("length")
.or_else(|| matches.value_of("bytes"));
let mut reader = if let Some(length) = length_arg.and_then(|s| parse_byte_count(s, block_size))
{
Box::new(reader.take(length.into()))
} else {
reader.into_inner()
};
let show_color = match matches.value_of("color") {
Some("never") => false,
Some("auto") => atty::is(Stream::Stdout),
_ => true,
};
let border_style = match matches.value_of("border") {
Some("unicode") => BorderStyle::Unicode,
Some("ascii") => BorderStyle::Ascii,
_ => BorderStyle::None,
};
let squeeze = !matches.is_present("nosqueezing");
let display_offset = matches
.value_of("display_offset")
.and_then(|s| parse_byte_count(s, block_size))
.or(skip_arg)
.unwrap_or_default()
.into();
let stdout = io::stdout();
let mut stdout_lock = stdout.lock();
let mut printer = Printer::new(&mut stdout_lock, show_color, border_style, squeeze);
printer.display_offset(display_offset);
printer.print_all(&mut reader)?;
Ok(())
}
fn main() {
// Enable ANSI support for Windows
#[cfg(windows)]
let _ = ansi_term::enable_ansi_support();
let result = run();
if let Err(err) = result {
if let Some(clap_err) = err.downcast_ref::<clap::Error>() {
eprint!("{}", clap_err); // Clap errors already have newlines
match clap_err.kind {
// The exit code should not indicate an error for --help / --version
clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed => {
std::process::exit(0)
}
_ => (),
}
} else {
eprintln!("Error: {}", err);
}
std::process::exit(1);
}
}
#[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.is_negative() {
None
} else {
Some(Self(x))
}
}
pub fn into_inner(self) -> i64 {
self.0
}
}
impl Into<u64> for PositiveI64 {
fn into(self) -> u64 {
u64::try_from(self.0)
.expect("invariant broken: PositiveI64 should contain a positive i64 value")
}
}
fn parse_byte_count(n: &str, block_size: PositiveI64) -> Option<PositiveI64> {
const HEX_PREFIX: &str = "0x";
let n = {
let mut chars = n.chars();
match chars.next()? {
'+' => chars.as_str(),
'-' => return None,
_ => n,
}
};
if n.starts_with(HEX_PREFIX) {
let n = &n[HEX_PREFIX.len()..];
match n.chars().next() {
Some('+') | Some('-') => return None,
_ => (),
}
return i64::from_str_radix(n, 16).ok().and_then(PositiveI64::new);
}
let (n, unit_multiplier) = match n.chars().position(|c| !c.is_ascii_digit()) {
Some(unit_begin_idx) => {
let (n, raw_unit) = n.split_at(unit_begin_idx);
let raw_unit = raw_unit.to_lowercase();
(
n,
[
("b", 1),
("kb", 1000i64.pow(1)),
("mb", 1000i64.pow(2)),
("gb", 1000i64.pow(3)),
("tb", 1000i64.pow(4)),
("kib", 1024i64.pow(1)),
("mib", 1024i64.pow(2)),
("gib", 1024i64.pow(3)),
("tib", 1024i64.pow(4)),
("block", block_size.into_inner()),
]
.iter()
.cloned()
.find_map(|(unit, multiplier)| {
if unit == raw_unit {
Some(multiplier)
} else {
None
}
})?,
)
}
None => (n, 1),
};
n.parse::<i64>()
.ok()?
.checked_mul(unit_multiplier)
.and_then(PositiveI64::new)
}
#[test]
fn test_parse_byte_count() {
macro_rules! success {
($input: expr, $expected: expr) => {
success!($input, 512, $expected)
};
($input: expr, $block_size: expr, $expected: expr) => {
assert_eq!(
parse_byte_count($input, PositiveI64::new($block_size).unwrap()),
Some(PositiveI64::new($expected).unwrap())
);
};
}
macro_rules! error {
($input: expr) => {
assert_eq!(
parse_byte_count($input, PositiveI64::new(512).unwrap()),
None
);
};
}
success!("0", 0);
success!("1", 1);
success!("1", 1);
success!("100", 100);
success!("+100", 100);
success!("1KB", 1000);
success!("2MB", 2000000);
success!("3GB", 3000000000);
success!("4TB", 4000000000000);
success!("+4TB", 4000000000000);
success!("1GiB", 1073741824);
success!("2TiB", 2199023255552);
success!("+2TiB", 2199023255552);
success!("0xff", 255);
success!("0xEE", 238);
success!("+0xFF", 255);
success!("1block", 512, 512);
success!("2block", 512, 1024);
success!("1block", 4, 4);
success!("2block", 4, 8);
// empty string is invalid
error!("");
// These are also bad.
error!("+");
error!("-");
error!("K");
error!("k");
error!("m");
error!("block");
// leading/trailing space is invalid
error!(" 0");
error!("0 ");
// Negatives make no sense for byte counts
error!("-1");
error!("0x-12");
// This was previously accepted but shouldn't be.
error!("0x+12");
// invalid suffix
error!("1234asdf");
// bad numbers
error!("asdf1234");
error!("a1s2d3f4");
// multiplication overflows u64
error!("20000000TiB");
}
+65
View File
@@ -0,0 +1,65 @@
use std::convert::TryFrom;
use std::fs;
use std::io::{self, copy, sink, Read, Seek, SeekFrom};
pub enum Input<'a> {
File(fs::File),
Stdin(io::StdinLock<'a>),
}
impl<'a> Read for Input<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match *self {
Input::File(ref mut file) => file.read(buf),
Input::Stdin(ref mut stdin) => stdin.read(buf),
}
}
}
impl<'a> Seek for Input<'a> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
fn try_skip<R>(reader: R, pos: SeekFrom, err_desc: &'static str) -> io::Result<u64>
where
R: Read,
{
let cant_seek_abs_err = || Err(io::Error::new(io::ErrorKind::Other, err_desc));
let offset = match pos {
SeekFrom::Current(o) => u64::try_from(o).or_else(|_e| cant_seek_abs_err())?,
SeekFrom::Start(_) | SeekFrom::End(_) => cant_seek_abs_err()?,
};
copy(&mut reader.take(offset), &mut sink())
}
match *self {
Input::File(ref mut file) => {
let seek_res = file.seek(pos);
if let Err(Some(libc::ESPIPE)) = seek_res.as_ref().map_err(|err| err.raw_os_error())
{
try_skip(
file,
pos,
"Pipes only support seeking forward with a relative offset",
)
} else {
seek_res
}
}
Input::Stdin(ref mut stdin) => try_skip(
stdin,
pos,
"STDIN only supports seeking forward with a relative offset",
),
}
}
}
impl<'a> Input<'a> {
pub fn into_inner(self) -> Box<dyn Read + 'a> {
match self {
Input::File(file) => Box::new(file),
Input::Stdin(stdin) => Box::new(stdin),
}
}
}
+179 -233
View File
@@ -1,22 +1,13 @@
#[macro_use]
extern crate clap;
pub(crate) mod input;
pub mod squeezer;
use atty;
use ctrlc;
pub use input::*;
mod squeezer;
use std::fs::File;
use std::io::{self, prelude::*, StdoutLock};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use clap::{App, AppSettings, Arg};
use std::io::{self, Read, Write};
use ansi_term::Color;
use ansi_term::Color::Fixed;
use atty::Stream;
use crate::squeezer::{SqueezeAction, Squeezer};
const BUFFER_SIZE: usize = 256;
@@ -28,7 +19,7 @@ const COLOR_ASCII_WHITESPACE: Color = Color::Green;
const COLOR_ASCII_OTHER: Color = Color::Purple;
const COLOR_NONASCII: Color = Color::Yellow;
enum ByteCategory {
pub enum ByteCategory {
Null,
AsciiPrintable,
AsciiWhitespace,
@@ -43,10 +34,7 @@ impl Byte {
fn category(self) -> ByteCategory {
if self.0 == 0x00 {
ByteCategory::Null
} else if self.0.is_ascii_alphanumeric()
|| self.0.is_ascii_punctuation()
|| self.0.is_ascii_graphic()
{
} else if self.0.is_ascii_graphic() {
ByteCategory::AsciiPrintable
} else if self.0.is_ascii_whitespace() {
ByteCategory::AsciiWhitespace
@@ -90,7 +78,7 @@ struct BorderElements {
right_corner: char,
}
enum BorderStyle {
pub enum BorderStyle {
Unicode,
Ascii,
None,
@@ -150,33 +138,34 @@ impl BorderStyle {
}
}
struct Printer<'a> {
idx: usize,
pub struct Printer<'a, Writer: Write> {
idx: u64,
/// The raw bytes used as input for the current line.
raw_line: Vec<u8>,
/// The buffered line built with each byte, ready to print to stdout.
/// The buffered line built with each byte, ready to print to writer.
buffer_line: Vec<u8>,
stdout: StdoutLock<'a>,
writer: &'a mut Writer,
show_color: bool,
border_style: BorderStyle,
header_was_printed: bool,
byte_hex_table: Vec<String>,
byte_char_table: Vec<String>,
squeezer: Squeezer,
display_offset: u64,
}
impl<'a> Printer<'a> {
fn new(
stdout: StdoutLock<'_>,
impl<'a, Writer: Write> Printer<'a, Writer> {
pub fn new(
writer: &'a mut Writer,
show_color: bool,
border_style: BorderStyle,
use_squeeze: bool,
) -> Printer<'_> {
) -> Printer<'a, Writer> {
Printer {
idx: 1,
raw_line: vec![],
buffer_line: vec![],
stdout,
writer,
show_color,
border_style,
header_was_printed: false,
@@ -201,17 +190,23 @@ impl<'a> Printer<'a> {
})
.collect(),
squeezer: Squeezer::new(use_squeeze),
display_offset: 0,
}
}
fn header(&mut self) {
pub fn display_offset(&mut self, display_offset: u64) -> &mut Self {
self.display_offset = display_offset;
self
}
pub fn header(&mut self) {
if let Some(border_elements) = self.border_style.header_elems() {
let h = border_elements.horizontal_line;
let h8 = h.to_string().repeat(8);
let h25 = h.to_string().repeat(25);
writeln!(
self.stdout,
self.writer,
"{l}{h8}{c}{h25}{c}{h25}{c}{h8}{c}{h8}{r}",
l = border_elements.left_corner,
c = border_elements.column_separator,
@@ -223,14 +218,14 @@ impl<'a> Printer<'a> {
}
}
fn footer(&mut self) {
pub fn footer(&mut self) {
if let Some(border_elements) = self.border_style.footer_elems() {
let h = border_elements.horizontal_line;
let h8 = h.to_string().repeat(8);
let h25 = h.to_string().repeat(25);
writeln!(
self.stdout,
self.writer,
"{l}{h8}{c}{h25}{c}{h25}{c}{h8}{c}{h8}{r}",
l = border_elements.left_corner,
c = border_elements.column_separator,
@@ -249,7 +244,7 @@ impl<'a> Printer<'a> {
}
let style = COLOR_OFFSET.normal();
let byte_index = format!("{:08x}", self.idx - 1);
let byte_index = format!("{:08x}", self.idx - 1 + self.display_offset);
let formatted_string = if self.show_color {
format!("{}", style.paint(byte_index))
} else {
@@ -264,7 +259,7 @@ impl<'a> Printer<'a> {
);
}
fn print_byte(&mut self, b: u8) -> io::Result<()> {
pub fn print_byte(&mut self, b: u8) -> io::Result<()> {
if self.idx % 16 == 1 {
self.print_position_indicator();
}
@@ -289,7 +284,7 @@ impl<'a> Printer<'a> {
Ok(())
}
fn print_textline(&mut self) -> io::Result<()> {
pub fn print_textline(&mut self) -> io::Result<()> {
let len = self.raw_line.len();
if len == 0 {
@@ -305,74 +300,83 @@ impl<'a> Printer<'a> {
self.border_style.inner_sep(),
self.border_style.outer_sep(),
);
self.stdout.write_all(&self.buffer_line)?;
self.writer.write_all(&self.buffer_line)?;
}
return Ok(());
}
if len < 8 {
let _ = write!(
&mut self.buffer_line,
"{0:1$}{3}{0:2$}{4}",
"",
3 * (8 - len),
1 + 3 * 8,
self.border_style.inner_sep(),
self.border_style.outer_sep(),
);
} else {
let _ = write!(
&mut self.buffer_line,
"{0:1$}{2}",
"",
3 * (16 - len),
self.border_style.outer_sep()
);
}
let squeeze_action = self.squeezer.action();
let mut idx = 1;
for &b in self.raw_line.iter() {
let _ = write!(
&mut self.buffer_line,
"{}",
self.byte_char_table[b as usize]
);
if idx == 8 {
let _ = write!(&mut self.buffer_line, "{}", self.border_style.inner_sep());
if squeeze_action != SqueezeAction::Delete {
if len < 8 {
let _ = write!(
&mut self.buffer_line,
"{0:1$}{3}{0:2$}{4}",
"",
3 * (8 - len),
1 + 3 * 8,
self.border_style.inner_sep(),
self.border_style.outer_sep(),
);
} else {
let _ = write!(
&mut self.buffer_line,
"{0:1$}{2}",
"",
3 * (16 - len),
self.border_style.outer_sep()
);
}
idx += 1;
let mut idx = 1;
for &b in self.raw_line.iter() {
let _ = write!(
&mut self.buffer_line,
"{}",
self.byte_char_table[b as usize]
);
if idx == 8 {
let _ = write!(&mut self.buffer_line, "{}", self.border_style.inner_sep());
}
idx += 1;
}
if len < 8 {
let _ = writeln!(
&mut self.buffer_line,
"{0:1$}{3}{0:2$}{4}",
"",
8 - len,
8,
self.border_style.inner_sep(),
self.border_style.outer_sep(),
);
} else {
let _ = writeln!(
&mut self.buffer_line,
"{0:1$}{2}",
"",
16 - len,
self.border_style.outer_sep()
);
}
}
if len < 8 {
let _ = writeln!(
&mut self.buffer_line,
"{0:1$}{3}{0:2$}{4} ",
"",
8 - len,
8,
self.border_style.inner_sep(),
self.border_style.outer_sep(),
);
} else {
let _ = writeln!(
&mut self.buffer_line,
"{0:1$}{2}",
"",
16 - len,
self.border_style.outer_sep()
);
}
match self.squeezer.action() {
match squeeze_action {
SqueezeAction::Print => {
self.buffer_line.clear();
let style = COLOR_OFFSET.normal();
let asterisk = if self.show_color {
format!("{}", style.paint("*"))
} else {
String::from("*")
};
let _ = writeln!(
&mut self.buffer_line,
"{5}{0}{1:2$}{5}{1:3$}{6}{1:3$}{5}{1:4$}{6}{1:4$}{5}",
style.paint("*"),
asterisk,
"",
7,
25,
@@ -385,171 +389,113 @@ impl<'a> Printer<'a> {
SqueezeAction::Ignore => (),
}
self.stdout.write_all(&self.buffer_line)?;
self.writer.write_all(&self.buffer_line)?;
self.raw_line.clear();
self.buffer_line.clear();
self.squeezer.advance();
Ok(())
}
pub fn header_was_printed(&self) -> bool {
self.header_was_printed
}
/// Loop through the given `Reader`, printing until the `Reader` buffer
/// is exhausted.
pub fn print_all<Reader: Read>(
&mut self,
mut reader: Reader,
) -> Result<(), Box<dyn std::error::Error>> {
let mut buffer = [0; BUFFER_SIZE];
'mainloop: loop {
let size = reader.read(&mut buffer)?;
if size == 0 {
break;
}
for b in &buffer[..size] {
let res = self.print_byte(*b);
if res.is_err() {
// Broken pipe
break 'mainloop;
}
}
}
// Finish last line
self.print_textline().ok();
if !self.header_was_printed() {
self.header();
}
self.footer();
Ok(())
}
}
fn run() -> Result<(), Box<dyn std::error::Error>> {
let app = App::new(crate_name!())
.setting(AppSettings::ColorAuto)
.setting(AppSettings::ColoredHelp)
.setting(AppSettings::DeriveDisplayOrder)
.setting(AppSettings::UnifiedHelpMessage)
.version(crate_version!())
.about(crate_description!())
.arg(Arg::with_name("file").help("File to display"))
.arg(
Arg::with_name("length")
.short("n")
.long("length")
.takes_value(true)
.value_name("N")
.help("Read only N bytes from the input"),
)
.arg(
Arg::with_name("bytes")
.short("c")
.long("bytes")
.takes_value(true)
.value_name("N")
.help("An alias for -n/--length"),
)
.arg(
Arg::with_name("nosqueezing")
.short("v")
.long("no-squeezing")
.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::with_name("color")
.long("color")
.takes_value(true)
.value_name("when")
.possible_values(&["always", "auto", "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::with_name("border")
.long("border")
.takes_value(true)
.possible_values(&["unicode", "ascii", "none"])
.default_value("unicode")
.help("Whether to draw a border with unicode or ASCII characters, or none at all"),
);
#[cfg(test)]
mod tests {
use std::io;
use std::str;
let matches = app.get_matches_safe()?;
use super::*;
let stdin = io::stdin();
fn assert_print_all_output<Reader: Read>(input: Reader, expected_string: String) -> () {
let mut output = vec![];
let mut printer = Printer::new(&mut output, false, BorderStyle::Unicode, true);
let mut reader: Box<dyn Read> = match matches.value_of("file") {
Some(filename) => Box::new(File::open(filename)?),
None => Box::new(stdin.lock()),
};
printer.print_all(input).unwrap();
let length_arg = matches.value_of("length").or(matches.value_of("bytes"));
if let Some(length) = length_arg.and_then(|n| {
if n.starts_with("0x") {
u64::from_str_radix(n.trim_start_matches("0x"), 16).ok()
} else {
n.parse::<u64>().ok()
}
}) {
reader = Box::new(reader.take(length));
let actual_string: &str = str::from_utf8(&output).unwrap();
assert_eq!(actual_string, expected_string)
}
let show_color = match matches.value_of("color") {
Some("never") => false,
Some("auto") => atty::is(Stream::Stdout),
_ => true,
};
let border_style = match matches.value_of("border") {
Some("unicode") => BorderStyle::Unicode,
Some("ascii") => BorderStyle::Ascii,
_ => BorderStyle::None,
};
let squeeze = !matches.is_present("nosqueezing");
// Set up Ctrl-C handler
let cancelled = Arc::new(AtomicBool::new(false));
let c = cancelled.clone();
ctrlc::set_handler(move || {
c.store(true, Ordering::SeqCst);
})
.expect("Error setting Ctrl-C handler");
let stdout = io::stdout();
let mut printer = Printer::new(stdout.lock(), show_color, border_style, squeeze);
let mut buffer = [0; BUFFER_SIZE];
'mainloop: loop {
let size = reader.read(&mut buffer)?;
if size == 0 {
break;
}
if cancelled.load(Ordering::SeqCst) {
eprintln!("hexyl has been cancelled.");
std::process::exit(130); // Set exit code to 128 + SIGINT
}
for b in &buffer[..size] {
let res = printer.print_byte(*b);
if res.is_err() {
// Broken pipe
break 'mainloop;
}
}
#[test]
fn empty_file_passes() {
let input = io::empty();
let expected_string = "\
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
"
.to_owned();
assert_print_all_output(input, expected_string);
}
// Finish last line
printer.print_textline().ok();
if !printer.header_was_printed {
printer.header();
#[test]
fn short_input_passes() {
let input = io::Cursor::new(b"spam");
let expected_string = "\
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 73 70 61 6d ┊ │spam ┊ │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
"
.to_owned();
assert_print_all_output(input, expected_string);
}
printer.footer();
Ok(())
}
#[test]
fn display_offset() {
let input = io::Cursor::new(b"spamspamspamspamspam");
let expected_string = "\
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│deadbeef│ 73 70 61 6d 73 70 61 6d ┊ 73 70 61 6d 73 70 61 6d │spamspam┊spamspam│
│deadbeff│ 73 70 61 6d ┊ │spam ┊ │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
"
.to_owned();
fn main() {
// Enable ANSI support for Windows
#[cfg(windows)]
let _ = ansi_term::enable_ansi_support();
let mut output = vec![];
let mut printer: Printer<Vec<u8>> =
Printer::new(&mut output, false, BorderStyle::Unicode, true);
printer.display_offset(0xdeadbeef);
let result = run();
printer.print_all(input).unwrap();
if let Err(err) = result {
if let Some(clap_err) = err.downcast_ref::<clap::Error>() {
eprint!("{}", clap_err); // Clap errors already have newlines
match clap_err.kind {
// The exit code should not indicate an error for --help / --version
clap::ErrorKind::HelpDisplayed | clap::ErrorKind::VersionDisplayed => {
std::process::exit(0)
}
_ => (),
}
} else {
eprintln!("Error: {}", err);
}
std::process::exit(1);
let actual_string: &str = str::from_utf8(&output).unwrap();
assert_eq!(actual_string, expected_string)
}
}
+218 -53
View File
@@ -32,7 +32,7 @@ pub enum SqueezeAction {
}
/// line size
const LSIZE: usize = 16;
const LSIZE: u64 = 16;
impl Squeezer {
pub fn new(enabled: bool) -> Squeezer {
@@ -46,7 +46,7 @@ impl Squeezer {
}
}
pub fn process(&mut self, b: u8, i: usize) {
pub fn process(&mut self, b: u8, i: u64) {
use self::SqueezeState::*;
if self.state == Disabled {
return;
@@ -54,17 +54,23 @@ impl Squeezer {
let eq = b == self.byte;
if i % LSIZE == 0 {
self.state = match self.state {
NoSqueeze => Probe,
Probe => SqueezeActiveFirstLine,
SqueezeActiveFirstLine => SqueezeFirstLine,
SqueezeFirstLine => SqueezeActive,
SqueezeActive => Squeeze,
Squeeze => SqueezeActive,
Disabled => Disabled,
};
if !eq {
self.state = Probe;
} else {
self.state = match self.state {
NoSqueeze => Probe,
Probe => SqueezeActiveFirstLine,
SqueezeActiveFirstLine => SqueezeFirstLine,
SqueezeFirstLine => SqueezeActive,
SqueezeActive => Squeeze,
Squeeze => SqueezeActive,
Disabled => Disabled,
};
}
} else if !eq {
if (i % LSIZE == 1 && self.state != Probe) || i % LSIZE != 1 {
if i % LSIZE == 1 {
self.state = Probe;
} else if i % LSIZE != 1 {
self.state = NoSqueeze;
}
}
@@ -80,17 +86,23 @@ impl Squeezer {
}
}
pub fn action(&mut self) -> SqueezeAction {
pub fn action(&self) -> SqueezeAction {
match self.state {
SqueezeState::SqueezeFirstLine => SqueezeAction::Print,
SqueezeState::Squeeze => SqueezeAction::Delete,
_ => SqueezeAction::Ignore,
}
}
pub fn advance(&mut self) {
match self.state {
SqueezeState::SqueezeFirstLine => {
self.state = SqueezeState::SqueezeActive;
SqueezeAction::Print
}
SqueezeState::Squeeze => {
self.state = SqueezeState::SqueezeActive;
SqueezeAction::Delete
}
_ => SqueezeAction::Ignore,
_ => {}
}
}
}
@@ -99,13 +111,16 @@ impl Squeezer {
mod tests {
use super::*;
const LSIZE_USIZE: usize = LSIZE as usize;
#[test]
fn three_same_lines() {
const LINES: usize = 3;
let v = vec![0u8; LINES * LSIZE];
let v = vec![0u8; LINES * LSIZE_USIZE];
let mut s = Squeezer::new(true);
// just initialized
assert_eq!(s.action(), SqueezeAction::Ignore);
s.advance();
let exp = vec![
SqueezeAction::Ignore, // first line, print as is
@@ -113,22 +128,28 @@ mod tests {
SqueezeAction::Delete, // delete reoccurring line
];
for z in 0..LINES {
for i in z * LSIZE..(z + 1) * LSIZE {
s.process(v[i], i + 1);
let mut line = 0;
let mut idx = 1;
for z in v.chunks(LSIZE_USIZE) {
for i in z {
s.process(*i, idx);
idx += 1;
}
assert_eq!(s.action(), exp[z]);
let action = s.action();
s.advance();
assert_eq!(action, exp[line]);
line += 1;
}
}
#[test]
fn incomplete_while_squeeze() {
const LINES: usize = 3;
// third line only has 12 bytes and should be printed
let v = vec![0u8; LINES * LSIZE + 12];
// fourth line only has 12 bytes and should be printed
let v = vec![0u8; 3 * LSIZE_USIZE + 12];
let mut s = Squeezer::new(true);
// just initialized
assert_eq!(s.action(), SqueezeAction::Ignore);
s.advance();
let exp = vec![
SqueezeAction::Ignore, // first line, print as is
@@ -137,23 +158,22 @@ mod tests {
SqueezeAction::Ignore, // last line only 12 bytes, print it
];
for z in 0..LINES {
for i in z * LSIZE..(z + 1) * LSIZE {
s.process(v[i], i + 1);
let mut line = 0;
let mut idx = 1;
for z in v.chunks(LSIZE_USIZE) {
for i in z {
s.process(*i, idx);
idx += 1;
}
assert_eq!(s.action(), exp[z]);
assert_eq!(s.action(), exp[line]);
s.advance();
line += 1;
}
for i in LSIZE * LINES..LINES * LSIZE + 12 {
s.process(v[i], i + 1);
}
assert_eq!(s.action(), exp[3]);
}
#[test]
/// all three lines are different, print all
fn three_different_lines() {
const LINES: usize = 3;
let mut v: Vec<u8> = vec![];
v.extend(vec![0u8; 16]);
v.extend(vec![1u8; 16]);
@@ -162,6 +182,7 @@ mod tests {
let mut s = Squeezer::new(true);
// just initialized
assert_eq!(s.action(), SqueezeAction::Ignore);
s.advance();
let exp = vec![
SqueezeAction::Ignore, // first line, print as is
@@ -169,11 +190,17 @@ mod tests {
SqueezeAction::Ignore, // different
];
for z in 0..LINES {
for i in z * LSIZE..(z + 1) * LSIZE {
s.process(v[i], i + 1);
let mut line = 0;
let mut idx = 1;
for z in v.chunks(LSIZE_USIZE) {
for i in z {
s.process(*i, idx);
idx += 1;
}
assert_eq!(s.action(), exp[z]);
let action = s.action();
assert_eq!(action, exp[line]);
s.advance();
line += 1;
}
}
@@ -182,12 +209,13 @@ mod tests {
/// print
fn one_squeeze_no_delete() {
const LINES: usize = 3;
let mut v = vec![0u8; (LINES - 1) * LSIZE];
let mut v = vec![0u8; (LINES - 1) * LSIZE_USIZE];
v.extend(vec![1u8; 16]);
let mut s = Squeezer::new(true);
// just initialized
assert_eq!(s.action(), SqueezeAction::Ignore);
s.advance();
let exp = vec![
SqueezeAction::Ignore, // first line, print as is
@@ -195,11 +223,17 @@ mod tests {
SqueezeAction::Ignore, // different lines, print again
];
for z in 0..LINES {
for i in z * LSIZE..(z + 1) * LSIZE {
s.process(v[i], i + 1);
let mut line = 0;
let mut idx = 1;
for z in v.chunks(LSIZE_USIZE) {
for i in z {
s.process(*i, idx);
idx += 1;
}
assert_eq!(s.action(), exp[z]);
let action = s.action();
s.advance();
assert_eq!(action, exp[line]);
line += 1;
}
}
@@ -207,31 +241,37 @@ mod tests {
/// First line all eq, 2nd half eq with first line, then change
fn second_line_different() {
const LINES: usize = 2;
let mut v = vec![0u8; (LINES - 1) * LSIZE];
let mut v = vec![0u8; (LINES - 1) * LSIZE_USIZE];
v.extend(vec![0u8; 8]);
v.extend(vec![1u8; 8]);
let mut s = Squeezer::new(true);
// just initialized
assert_eq!(s.action(), SqueezeAction::Ignore);
s.advance();
let exp = vec![
SqueezeAction::Ignore, // first line, print as is
SqueezeAction::Ignore, // print squeeze symbol
];
for z in 0..LINES {
for i in z * LSIZE..(z + 1) * LSIZE {
s.process(v[i], i + 1);
let mut line = 0;
let mut idx = 1;
for z in v.chunks(LSIZE_USIZE) {
for i in z {
s.process(*i, idx);
idx += 1;
}
assert_eq!(s.action(), exp[z]);
let action = s.action();
s.advance();
assert_eq!(action, exp[line]);
line += 1;
}
}
#[test]
/// all three lines never become squeeze candidate (diff within line)
fn never_squeeze_candidate() {
const LINES: usize = 3;
let mut v = vec![];
v.extend(vec![0u8; 8]);
v.extend(vec![1u8; 8]);
@@ -243,6 +283,7 @@ mod tests {
let mut s = Squeezer::new(true);
// just initialized
assert_eq!(s.action(), SqueezeAction::Ignore);
s.advance();
let exp = vec![
SqueezeAction::Ignore, // first line, print as is
@@ -250,11 +291,135 @@ mod tests {
SqueezeAction::Ignore, // print squeeze symbol
];
for z in 0..LINES {
for i in z * LSIZE..(z + 1) * LSIZE {
s.process(v[i], i + 1);
let mut line = 0;
let mut idx = 1;
for z in v.chunks(LSIZE_USIZE) {
for i in z {
s.process(*i, idx);
idx += 1;
}
assert_eq!(s.action(), exp[z]);
let action = s.action();
s.advance();
assert_eq!(action, exp[line]);
line += 1;
}
}
#[test]
fn mix_everything() {
let mut v = vec![];
v.extend(vec![10u8; 16]); // print
v.extend(vec![20u8; 16]); // print
v.extend(vec![0u8; 16]); // print
v.extend(vec![0u8; 16]); // *
v.extend(vec![10u8; 16]); // print
v.extend(vec![20u8; 16]); // print
v.extend(vec![0u8; 16]); // print
v.extend(vec![0u8; 16]); // *
v.extend(vec![0u8; 16]); // delete
v.extend(vec![0u8; 16]); // delete*
v.extend(vec![20u8; 16]); // print
v.extend(vec![0u8; 12]); // print, only 12 bytes
let mut s = Squeezer::new(true);
// just initialized
assert_eq!(s.action(), SqueezeAction::Ignore);
s.advance();
let exp = vec![
SqueezeAction::Ignore,
SqueezeAction::Ignore,
SqueezeAction::Ignore,
SqueezeAction::Print,
SqueezeAction::Ignore,
SqueezeAction::Ignore,
SqueezeAction::Ignore,
SqueezeAction::Print,
SqueezeAction::Delete,
SqueezeAction::Delete,
SqueezeAction::Ignore,
SqueezeAction::Ignore,
];
let mut line = 0;
let mut idx = 1;
for z in v.chunks(LSIZE_USIZE) {
for i in z {
s.process(*i, idx);
idx += 1;
}
let action = s.action();
s.advance();
assert_eq!(action, exp[line]);
line += 1;
}
}
#[test]
fn last_char_diff() {
// see issue #62
let mut v = vec![];
v.extend(vec![20u8; 16]);
v.extend(vec![20u8; 15]);
v.push(61);
v.extend(vec![20u8; 16]);
v.extend(vec![20u8; 16]);
let mut s = Squeezer::new(true);
// just initialized
assert_eq!(s.action(), SqueezeAction::Ignore);
s.advance();
let exp = vec![
SqueezeAction::Ignore, // print as is
SqueezeAction::Ignore, // print as is
SqueezeAction::Ignore, // print as is
SqueezeAction::Print, // print '*' char
];
let mut line = 0;
let mut idx = 1;
for z in v.chunks(LSIZE_USIZE) {
for i in z {
s.process(*i, idx);
idx += 1;
}
assert_eq!(s.action(), exp[line]);
s.advance();
line += 1;
}
}
#[test]
fn first_char_diff() {
// see issue #62
let mut v = vec![];
v.extend(vec![20u8; 16]);
v.push(61);
v.extend(vec![20u8; 15]);
v.extend(vec![20u8; 16]);
let mut s = Squeezer::new(true);
// just initialized
assert_eq!(s.action(), SqueezeAction::Ignore);
s.advance();
let exp = vec![
SqueezeAction::Ignore, // print as is
SqueezeAction::Ignore, // print as is
SqueezeAction::Ignore, // print as is
];
let mut line = 0;
let mut idx = 1;
for z in v.chunks(LSIZE_USIZE) {
for i in z {
s.process(*i, idx);
idx += 1;
}
assert_eq!(s.action(), exp[line]);
s.advance();
line += 1;
}
}
}