mirror of
https://github.com/sharkdp/hexyl.git
synced 2026-10-28 08:05:52 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ecc29b9c8 | ||
|
|
8eb6d4771c | ||
|
|
5d05382df8 | ||
|
|
626f03bfbd | ||
|
|
04d74bae47 | ||
|
|
96e427cb4a | ||
|
|
7f8db4f113 | ||
|
|
2e2643782d | ||
|
|
269860ba7e | ||
|
|
2c8be77181 | ||
|
|
f16e2c96ea | ||
|
|
585d06293a | ||
|
|
909dd7d947 | ||
|
|
5a295296fe | ||
|
|
38301f3759 | ||
|
|
f2547aed69 | ||
|
|
9ef7c346dd | ||
|
|
6220cae644 | ||
|
|
ad553e9e33 | ||
|
|
fbbd5e3733 | ||
|
|
742c2fcbfd | ||
|
|
79f7076b02 | ||
|
|
cba09bf7b7 | ||
|
|
06ff4b750e | ||
|
|
630d92fb8f |
No files matched your search
+15
-14
@@ -36,7 +36,7 @@ jobs:
|
|||||||
|
|
||||||
ensure_cargo_fmt:
|
ensure_cargo_fmt:
|
||||||
name: Ensure 'cargo fmt' has been run
|
name: Ensure 'cargo fmt' has been run
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
- uses: dtolnay/rust-toolchain@stable
|
||||||
with:
|
with:
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
|
|
||||||
min_version:
|
min_version:
|
||||||
name: Minimum supported rust version
|
name: Minimum supported rust version
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-24.04
|
||||||
needs: crate_metadata
|
needs: crate_metadata
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
@@ -70,18 +70,19 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
job:
|
job:
|
||||||
- { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
|
- { target: aarch64-unknown-linux-gnu , os: ubuntu-24.04, use-cross: true }
|
||||||
- { target: arm-unknown-linux-gnueabihf , os: ubuntu-20.04, use-cross: true }
|
- { target: arm-unknown-linux-gnueabihf , os: ubuntu-24.04, use-cross: true }
|
||||||
- { target: arm-unknown-linux-musleabihf, os: ubuntu-20.04, use-cross: true }
|
- { target: arm-unknown-linux-musleabihf, os: ubuntu-24.04, use-cross: true }
|
||||||
- { target: i686-pc-windows-msvc , os: windows-2019 }
|
- { target: i686-pc-windows-msvc , os: windows-2025 }
|
||||||
- { target: i686-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
|
- { target: i686-unknown-linux-gnu , os: ubuntu-24.04, use-cross: true }
|
||||||
- { target: i686-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
- { target: i686-unknown-linux-musl , os: ubuntu-24.04, use-cross: true }
|
||||||
- { target: x86_64-apple-darwin , os: macos-13 }
|
- { target: x86_64-apple-darwin , os: macos-15-intel }
|
||||||
- { target: aarch64-apple-darwin , os: macos-15 }
|
- { target: aarch64-apple-darwin , os: macos-15 }
|
||||||
- { target: x86_64-pc-windows-gnu , os: windows-2019 }
|
# Was causing CI failures unrelated to app logic
|
||||||
- { target: x86_64-pc-windows-msvc , os: windows-2019 }
|
# - { target: x86_64-pc-windows-gnu , os: windows-2019 }
|
||||||
- { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04, use-cross: true }
|
- { target: x86_64-pc-windows-msvc , os: windows-2025 }
|
||||||
- { target: x86_64-unknown-linux-musl , os: ubuntu-20.04, use-cross: true }
|
- { target: x86_64-unknown-linux-gnu , os: ubuntu-24.04, use-cross: true }
|
||||||
|
- { target: x86_64-unknown-linux-musl , os: ubuntu-24.04, use-cross: true }
|
||||||
env:
|
env:
|
||||||
BUILD_CMD: cargo
|
BUILD_CMD: cargo
|
||||||
steps:
|
steps:
|
||||||
@@ -155,7 +156,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
# test only library unit tests and binary for arm-type targets
|
# test only library unit tests and binary for arm-type targets
|
||||||
unset CARGO_TEST_OPTIONS
|
unset CARGO_TEST_OPTIONS
|
||||||
unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--lib --bin ${{ needs.crate_metadata.outputs.name }}" ;; esac;
|
case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS="--lib --bin ${{ needs.crate_metadata.outputs.name }}" ;; esac;
|
||||||
echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_OUTPUT
|
echo "CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
|
|||||||
+9
-1
@@ -1,9 +1,17 @@
|
|||||||
# unreleased
|
# v0.17.0
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
- Add option to output result in C include file style, see #242 (@wpcwzy)
|
||||||
|
- Add `--color-scheme` option, see #247 (@aticu)
|
||||||
|
- Add `braille` character table, see #247 (@aticu)
|
||||||
|
- Add command line argument to generate shell completion, see #155 (@friedz)
|
||||||
|
- Add colors to `--help`/`-h`, see #253 (@starsep)
|
||||||
|
|
||||||
## Bugfixes
|
## Bugfixes
|
||||||
|
|
||||||
|
- Fix memory allocation bug when terminal width is less than 10, see #244 (@selfup)
|
||||||
|
|
||||||
|
|
||||||
# v0.16.0
|
# v0.16.0
|
||||||
|
|
||||||
|
|||||||
Generated
+14
-11
@@ -1,6 +1,6 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
@@ -68,13 +68,12 @@ checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "assert_cmd"
|
name = "assert_cmd"
|
||||||
version = "2.0.16"
|
version = "2.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d"
|
checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"bstr",
|
"bstr",
|
||||||
"doc-comment",
|
|
||||||
"libc",
|
"libc",
|
||||||
"predicates",
|
"predicates",
|
||||||
"predicates-core",
|
"predicates-core",
|
||||||
@@ -128,6 +127,15 @@ dependencies = [
|
|||||||
"terminal_size",
|
"terminal_size",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_complete"
|
||||||
|
version = "4.5.55"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.18"
|
version = "4.5.18"
|
||||||
@@ -184,12 +192,6 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "doc-comment"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
@@ -217,11 +219,12 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hexyl"
|
name = "hexyl"
|
||||||
version = "0.16.0"
|
version = "0.17.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
"clap",
|
"clap",
|
||||||
|
"clap_complete",
|
||||||
"const_format",
|
"const_format",
|
||||||
"libc",
|
"libc",
|
||||||
"owo-colors",
|
"owo-colors",
|
||||||
|
|||||||
+5
-3
@@ -1,15 +1,16 @@
|
|||||||
[package]
|
[package]
|
||||||
authors = ["David Peter <mail@david-peter.de>"]
|
authors = ["David Peter <mail@david-peter.de>"]
|
||||||
categories = ["command-line-utilities"]
|
categories = ["command-line-utilities"]
|
||||||
|
keywords = ["hex", "viewer"]
|
||||||
description = "A command-line hex viewer"
|
description = "A command-line hex viewer"
|
||||||
homepage = "https://github.com/sharkdp/hexyl"
|
homepage = "https://github.com/sharkdp/hexyl"
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
name = "hexyl"
|
name = "hexyl"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/sharkdp/hexyl"
|
repository = "https://github.com/sharkdp/hexyl"
|
||||||
version = "0.16.0"
|
version = "0.17.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.74"
|
rust-version = "1.88"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
@@ -19,13 +20,14 @@ owo-colors = "4"
|
|||||||
supports-color = "3"
|
supports-color = "3"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
terminal_size = "0.4"
|
terminal_size = "0.4"
|
||||||
|
clap_complete = "4"
|
||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
version = "4"
|
version = "4"
|
||||||
features = ["derive", "wrap_help"]
|
features = ["derive", "wrap_help"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_cmd = "2.0"
|
assert_cmd = "2.1"
|
||||||
predicates = "3.0"
|
predicates = "3.0"
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
|
|
||||||
|
|||||||
@@ -181,6 +181,22 @@ scoop install hexyl
|
|||||||
x env use hexyl
|
x env use hexyl
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
`hexyl` colors can be configured via environment variables. The variables used are as follows:
|
||||||
|
|
||||||
|
* `HEXYL_COLOR_ASCII_PRINTABLE`: Any non-whitespace printable ASCII character
|
||||||
|
* `HEXYL_COLOR_ASCII_WHITESPACE`: Whitespace such as space or newline (only visible in middle panel with byte values)
|
||||||
|
* `HEXYL_COLOR_ASCII_OTHER`: Any other ASCII character (< `0x80`) besides null
|
||||||
|
* `HEXYL_COLOR_NULL`: The null byte (`0x00`)
|
||||||
|
* `HEXYL_COLOR_NONASCII`: Any non-ASCII byte (> `0x7F`)
|
||||||
|
* `HEXYL_COLOR_OFFSET`: The lefthand file offset
|
||||||
|
|
||||||
|
The colors can be any of the 8 standard terminal colors: `black`, `blue`, `cyan`, `green`, `magenta`, `red`,
|
||||||
|
`yellow` and `white`. The "bright" variants are also supported (e.g., `bright blue`). Additionally, you can use
|
||||||
|
the RGB hex format, `#abcdef`. For example, `HEXYL_COLOR_ASCII_PRINTABLE=blue HEXYL_COLOR_ASCII_WHITESPACE="bright green"
|
||||||
|
HEXYL_COLOR_ASCII_OTHER="#ff7f99"`.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Licensed under either of
|
Licensed under either of
|
||||||
|
|||||||
@@ -106,6 +106,22 @@ _FILE_
|
|||||||
**-V**, **\--version**
|
**-V**, **\--version**
|
||||||
: Prints version information.
|
: Prints version information.
|
||||||
|
|
||||||
|
# ENVIRONMENT VARIABLES
|
||||||
|
|
||||||
|
**hexyl** colors can be configured via environment variables. The variables used are as follows:
|
||||||
|
|
||||||
|
: - **HEXYL_COLOR_ASCII_PRINTABLE**: Any non-whitespace printable ASCII character
|
||||||
|
- **HEXYL_COLOR_ASCII_WHITESPACE**: Whitespace such as space or newline (only visible in middle panel with byte values)
|
||||||
|
- **HEXYL_COLOR_ASCII_OTHER**: Any other ASCII character (< **0x80**) besides null
|
||||||
|
- **HEXYL_COLOR_NULL**: The null byte (**0x00**)
|
||||||
|
- **HEXYL_COLOR_NONASCII**: Any non-ASCII byte (> **0x7F**)
|
||||||
|
- **HEXYL_COLOR_OFFSET**: The lefthand file offset
|
||||||
|
|
||||||
|
The colors can be any of the 8 standard terminal colors: **black**, **blue**, **cyan**, **green**, **magenta**, **red**,
|
||||||
|
**yellow** and **white**. The "bright" variants are also supported (e.g., **bright blue**). Additionally, you can use
|
||||||
|
the RGB hex format, **#abcdef**. For example, **HEXYL_COLOR_ASCII_PRINTABLE=blue HEXYL_COLOR_ASCII_WHITESPACE="bright green"
|
||||||
|
HEXYL_COLOR_ASCII_OTHER="#ff7f99"**.
|
||||||
|
|
||||||
# NOTES
|
# NOTES
|
||||||
|
|
||||||
Source repository:
|
Source repository:
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ use std::io;
|
|||||||
use hexyl::{BorderStyle, PrinterBuilder};
|
use hexyl::{BorderStyle, PrinterBuilder};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let input = vec![
|
let input = [
|
||||||
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44,
|
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,
|
0x52, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x44, 0x08, 0x02, 0x00, 0x00, 0x00,
|
||||||
];
|
];
|
||||||
|
|||||||
+110
-8
@@ -1,12 +1,114 @@
|
|||||||
use owo_colors::{colors, Color};
|
use owo_colors::{colors, AnsiColors, Color, DynColors, OwoColorize};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
pub const COLOR_NULL: &[u8] = colors::BrightBlack::ANSI_FG.as_bytes();
|
pub static COLOR_NULL: LazyLock<String> =
|
||||||
pub const COLOR_OFFSET: &[u8] = colors::BrightBlack::ANSI_FG.as_bytes();
|
LazyLock::new(|| init_color("NULL", AnsiColors::BrightBlack));
|
||||||
pub const COLOR_ASCII_PRINTABLE: &[u8] = colors::Cyan::ANSI_FG.as_bytes();
|
pub static COLOR_OFFSET: LazyLock<String> =
|
||||||
pub const COLOR_ASCII_WHITESPACE: &[u8] = colors::Green::ANSI_FG.as_bytes();
|
LazyLock::new(|| init_color("OFFSET", AnsiColors::BrightBlack));
|
||||||
pub const COLOR_ASCII_OTHER: &[u8] = colors::Green::ANSI_FG.as_bytes();
|
pub static COLOR_ASCII_PRINTABLE: LazyLock<String> =
|
||||||
pub const COLOR_NONASCII: &[u8] = colors::Yellow::ANSI_FG.as_bytes();
|
LazyLock::new(|| init_color("ASCII_PRINTABLE", AnsiColors::Cyan));
|
||||||
pub const COLOR_RESET: &[u8] = colors::Default::ANSI_FG.as_bytes();
|
pub static COLOR_ASCII_WHITESPACE: LazyLock<String> =
|
||||||
|
LazyLock::new(|| init_color("ASCII_WHITESPACE", AnsiColors::Green));
|
||||||
|
pub static COLOR_ASCII_OTHER: LazyLock<String> =
|
||||||
|
LazyLock::new(|| init_color("ASCII_OTHER", AnsiColors::Green));
|
||||||
|
pub static COLOR_NONASCII: LazyLock<String> =
|
||||||
|
LazyLock::new(|| init_color("NONASCII", AnsiColors::Yellow));
|
||||||
|
pub const COLOR_RESET: &str = colors::Default::ANSI_FG;
|
||||||
|
|
||||||
|
fn init_color(name: &str, default_ansi: AnsiColors) -> String {
|
||||||
|
let default = DynColors::Ansi(default_ansi);
|
||||||
|
let env_var = format!("HEXYL_COLOR_{name}");
|
||||||
|
let color = match std::env::var(env_var).as_deref() {
|
||||||
|
Ok(color) => match DynColors::from_str(color) {
|
||||||
|
Ok(color) => color,
|
||||||
|
_ => default,
|
||||||
|
},
|
||||||
|
_ => default,
|
||||||
|
};
|
||||||
|
// owo_colors' API isn't designed to get the terminal codes directly for
|
||||||
|
// dynamic colors, so we use this hack to get them from the LHS of some text.
|
||||||
|
format!("{}", "|".color(color))
|
||||||
|
.split_once("|")
|
||||||
|
.unwrap()
|
||||||
|
.0
|
||||||
|
.to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const COLOR_NULL_RGB: &[u8] = &rgb_bytes(100, 100, 100);
|
||||||
|
|
||||||
|
pub const COLOR_DEL: &[u8] = &rgb_bytes(64, 128, 0);
|
||||||
|
|
||||||
|
pub const COLOR_GRADIENT_NONASCII: [[u8; 19]; 128] =
|
||||||
|
generate_color_gradient(&[(255, 0, 0, 0.0), (255, 255, 0, 0.66), (255, 255, 255, 1.0)]);
|
||||||
|
|
||||||
|
pub const COLOR_GRADIENT_ASCII_NONPRINTABLE: [[u8; 19]; 31] =
|
||||||
|
generate_color_gradient(&[(255, 0, 255, 0.0), (128, 0, 255, 1.0)]);
|
||||||
|
|
||||||
|
pub const COLOR_GRADIENT_ASCII_PRINTABLE: [[u8; 19]; 95] =
|
||||||
|
generate_color_gradient(&[(0, 128, 255, 0.0), (0, 255, 128, 1.0)]);
|
||||||
|
|
||||||
|
const fn as_dec(byte: u8) -> [u8; 3] {
|
||||||
|
[
|
||||||
|
b'0' + (byte / 100),
|
||||||
|
b'0' + ((byte % 100) / 10),
|
||||||
|
b'0' + (byte % 10),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn rgb_bytes(r: u8, g: u8, b: u8) -> [u8; 19] {
|
||||||
|
let mut buf = *b"\x1b[38;2;rrr;ggg;bbbm";
|
||||||
|
|
||||||
|
// r 7
|
||||||
|
buf[7] = as_dec(r)[0];
|
||||||
|
buf[8] = as_dec(r)[1];
|
||||||
|
buf[9] = as_dec(r)[2];
|
||||||
|
|
||||||
|
// g 11
|
||||||
|
buf[11] = as_dec(g)[0];
|
||||||
|
buf[12] = as_dec(g)[1];
|
||||||
|
buf[13] = as_dec(g)[2];
|
||||||
|
|
||||||
|
// b 15
|
||||||
|
buf[15] = as_dec(b)[0];
|
||||||
|
buf[16] = as_dec(b)[1];
|
||||||
|
buf[17] = as_dec(b)[2];
|
||||||
|
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn generate_color_gradient<const N: usize>(stops: &[(u8, u8, u8, f64)]) -> [[u8; 19]; N] {
|
||||||
|
let mut out = [rgb_bytes(0, 0, 0); N];
|
||||||
|
|
||||||
|
assert!(stops.len() >= 2, "need at least two stops for the gradient");
|
||||||
|
|
||||||
|
let mut byte = 0;
|
||||||
|
while byte < N {
|
||||||
|
let relative_byte = byte as f64 / N as f64;
|
||||||
|
|
||||||
|
let mut i = 1;
|
||||||
|
while i < stops.len() && stops[i].3 < relative_byte {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
if i >= stops.len() {
|
||||||
|
i = stops.len() - 1;
|
||||||
|
}
|
||||||
|
let prev_stop = stops[i - 1];
|
||||||
|
let stop = stops[i];
|
||||||
|
let diff = stop.3 - prev_stop.3;
|
||||||
|
let t = (relative_byte - prev_stop.3) / diff;
|
||||||
|
|
||||||
|
let r = (prev_stop.0 as f64 + (t * (stop.0 as f64 - prev_stop.0 as f64))) as u8;
|
||||||
|
let g = (prev_stop.1 as f64 + (t * (stop.1 as f64 - prev_stop.1 as f64))) as u8;
|
||||||
|
let b = (prev_stop.2 as f64 + (t * (stop.2 as f64 - prev_stop.2 as f64))) as u8;
|
||||||
|
|
||||||
|
out[byte] = rgb_bytes(r, g, b);
|
||||||
|
|
||||||
|
byte += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
pub const CP437: [char; 256] = [
|
pub const CP437: [char; 256] = [
|
||||||
|
|||||||
+3
-3
@@ -6,7 +6,7 @@ pub enum Input<'a> {
|
|||||||
Stdin(io::StdinLock<'a>),
|
Stdin(io::StdinLock<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Read for Input<'a> {
|
impl Read for Input<'_> {
|
||||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
match *self {
|
match *self {
|
||||||
Input::File(ref mut file) => file.read(buf),
|
Input::File(ref mut file) => file.read(buf),
|
||||||
@@ -15,13 +15,13 @@ impl<'a> Read for Input<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Seek for Input<'a> {
|
impl Seek for Input<'_> {
|
||||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||||
fn try_skip<R>(reader: R, pos: SeekFrom, err_desc: &'static str) -> io::Result<u64>
|
fn try_skip<R>(reader: R, pos: SeekFrom, err_desc: &'static str) -> io::Result<u64>
|
||||||
where
|
where
|
||||||
R: Read,
|
R: Read,
|
||||||
{
|
{
|
||||||
let cant_seek_abs_err = || Err(io::Error::new(io::ErrorKind::Other, err_desc));
|
let cant_seek_abs_err = || Err(io::Error::other(err_desc));
|
||||||
|
|
||||||
let offset = match pos {
|
let offset = match pos {
|
||||||
SeekFrom::Current(o) => u64::try_from(o).or_else(|_e| cant_seek_abs_err())?,
|
SeekFrom::Current(o) => u64::try_from(o).or_else(|_e| cant_seek_abs_err())?,
|
||||||
|
|||||||
+355
-144
@@ -24,6 +24,13 @@ pub enum ByteCategory {
|
|||||||
NonAscii,
|
NonAscii,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum IncludeMode {
|
||||||
|
File(String), // filename
|
||||||
|
Stdin,
|
||||||
|
Slice,
|
||||||
|
Off,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, ValueEnum)]
|
#[derive(Copy, Clone, Debug, Default, ValueEnum)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum CharacterTable {
|
pub enum CharacterTable {
|
||||||
@@ -43,6 +50,24 @@ pub enum CharacterTable {
|
|||||||
/// Uses code page 437 (for non-ASCII bytes).
|
/// Uses code page 437 (for non-ASCII bytes).
|
||||||
#[value(name = "codepage-437")]
|
#[value(name = "codepage-437")]
|
||||||
CP437,
|
CP437,
|
||||||
|
|
||||||
|
/// Uses braille characters for non-printable bytes.
|
||||||
|
Braille,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default, ValueEnum)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum ColorScheme {
|
||||||
|
/// Show the default colors: bright black for NULL bytes, green for ASCII
|
||||||
|
/// space characters and non-printable ASCII, cyan for printable ASCII characters,
|
||||||
|
/// and yellow for non-ASCII bytes.
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
|
||||||
|
/// Show bright black for NULL bytes, cyan for printable ASCII characters, a gradient
|
||||||
|
/// from pink to violet for non-printable ASCII characters and a heatmap-like gradient
|
||||||
|
/// from red to yellow to white for non-ASCII bytes.
|
||||||
|
Gradient,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Default, ValueEnum)]
|
#[derive(Copy, Clone, Debug, Default, ValueEnum)]
|
||||||
@@ -81,14 +106,29 @@ impl Byte {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn color(self) -> &'static [u8] {
|
fn color(self, color_scheme: ColorScheme) -> &'static [u8] {
|
||||||
use crate::ByteCategory::*;
|
use crate::ByteCategory::*;
|
||||||
match self.category() {
|
match color_scheme {
|
||||||
Null => COLOR_NULL,
|
ColorScheme::Default => match self.category() {
|
||||||
AsciiPrintable => COLOR_ASCII_PRINTABLE,
|
Null => COLOR_NULL.as_bytes(),
|
||||||
AsciiWhitespace => COLOR_ASCII_WHITESPACE,
|
AsciiPrintable => COLOR_ASCII_PRINTABLE.as_bytes(),
|
||||||
AsciiOther => COLOR_ASCII_OTHER,
|
AsciiWhitespace => COLOR_ASCII_WHITESPACE.as_bytes(),
|
||||||
NonAscii => COLOR_NONASCII,
|
AsciiOther => COLOR_ASCII_OTHER.as_bytes(),
|
||||||
|
NonAscii => COLOR_NONASCII.as_bytes(),
|
||||||
|
},
|
||||||
|
ColorScheme::Gradient => match self.category() {
|
||||||
|
Null => COLOR_NULL_RGB,
|
||||||
|
AsciiWhitespace if self.0 == b' ' => &COLOR_GRADIENT_ASCII_PRINTABLE[0],
|
||||||
|
AsciiPrintable => &COLOR_GRADIENT_ASCII_PRINTABLE[(self.0 - b' ') as usize],
|
||||||
|
AsciiWhitespace | AsciiOther => {
|
||||||
|
if self.0 == 0x7f {
|
||||||
|
COLOR_DEL
|
||||||
|
} else {
|
||||||
|
&COLOR_GRADIENT_ASCII_NONPRINTABLE[self.0 as usize - 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NonAscii => &COLOR_GRADIENT_NONASCII[(self.0 - 128) as usize],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +153,37 @@ impl Byte {
|
|||||||
},
|
},
|
||||||
CharacterTable::CP1047 => CP1047[self.0 as usize],
|
CharacterTable::CP1047 => CP1047[self.0 as usize],
|
||||||
CharacterTable::CP437 => CP437[self.0 as usize],
|
CharacterTable::CP437 => CP437[self.0 as usize],
|
||||||
|
CharacterTable::Braille => match self.category() {
|
||||||
|
// null is important enough to get its own symbol
|
||||||
|
Null => '⋄',
|
||||||
|
AsciiPrintable => self.0 as char,
|
||||||
|
AsciiWhitespace if self.0 == b' ' => ' ',
|
||||||
|
// `\t`, `\n` and `\r` are important enough to get their own symbols
|
||||||
|
AsciiWhitespace if self.0 == b'\t' => '→',
|
||||||
|
AsciiWhitespace if self.0 == b'\n' => '↵',
|
||||||
|
AsciiWhitespace if self.0 == b'\r' => '←',
|
||||||
|
AsciiWhitespace | AsciiOther | NonAscii => {
|
||||||
|
/// Adjust the bits from the original number to a new number.
|
||||||
|
///
|
||||||
|
/// Bit positions in braille are adjusted as follows:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// 0 3 => 0 1
|
||||||
|
/// 1 4 => 2 3
|
||||||
|
/// 2 5 => 4 5
|
||||||
|
/// 6 7 => 6 7
|
||||||
|
/// ```
|
||||||
|
fn to_braille_bits(byte: u8) -> u8 {
|
||||||
|
let mut out = 0;
|
||||||
|
for (from, to) in [0, 3, 1, 4, 2, 5, 6, 7].into_iter().enumerate() {
|
||||||
|
out |= (byte >> from & 1) << to;
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
char::from_u32(0x2800 + to_braille_bits(self.0) as u32).unwrap()
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,6 +274,8 @@ pub struct PrinterBuilder<'a, Writer: Write> {
|
|||||||
base: Base,
|
base: Base,
|
||||||
endianness: Endianness,
|
endianness: Endianness,
|
||||||
character_table: CharacterTable,
|
character_table: CharacterTable,
|
||||||
|
include_mode: IncludeMode,
|
||||||
|
color_scheme: ColorScheme,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
||||||
@@ -219,6 +292,8 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
|||||||
base: Base::Hexadecimal,
|
base: Base::Hexadecimal,
|
||||||
endianness: Endianness::Big,
|
endianness: Endianness::Big,
|
||||||
character_table: CharacterTable::Default,
|
character_table: CharacterTable::Default,
|
||||||
|
include_mode: IncludeMode::Off,
|
||||||
|
color_scheme: ColorScheme::Default,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,20 +347,57 @@ impl<'a, Writer: Write> PrinterBuilder<'a, Writer> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn include_mode(mut self, include: IncludeMode) -> Self {
|
||||||
|
self.include_mode = include;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn color_scheme(mut self, color_scheme: ColorScheme) -> Self {
|
||||||
|
self.color_scheme = color_scheme;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build(self) -> Printer<'a, Writer> {
|
pub fn build(self) -> Printer<'a, Writer> {
|
||||||
Printer::new(
|
Printer {
|
||||||
self.writer,
|
idx: 0,
|
||||||
self.show_color,
|
line_buf: vec![0x0; 8 * self.panels as usize],
|
||||||
self.show_char_panel,
|
writer: self.writer,
|
||||||
self.show_position_panel,
|
show_char_panel: self.show_char_panel,
|
||||||
self.border_style,
|
show_position_panel: self.show_position_panel,
|
||||||
self.use_squeeze,
|
show_color: self.show_color,
|
||||||
self.panels,
|
curr_color: None,
|
||||||
self.group_size,
|
color_scheme: self.color_scheme,
|
||||||
self.base,
|
border_style: self.border_style,
|
||||||
self.endianness,
|
byte_hex_panel: (0u8..=u8::MAX)
|
||||||
self.character_table,
|
.map(|i| match self.base {
|
||||||
)
|
Base::Binary => format!("{i:08b}"),
|
||||||
|
Base::Octal => format!("{i:03o}"),
|
||||||
|
Base::Decimal => format!("{i:03}"),
|
||||||
|
Base::Hexadecimal => format!("{i:02x}"),
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
byte_char_panel: (0u8..=u8::MAX)
|
||||||
|
.map(|i| format!("{}", Byte(i).as_char(self.character_table)))
|
||||||
|
.collect(),
|
||||||
|
byte_hex_panel_g: (0u8..=u8::MAX).map(|i| format!("{i:02x}")).collect(),
|
||||||
|
squeezer: if self.use_squeeze {
|
||||||
|
Squeezer::Ignore
|
||||||
|
} else {
|
||||||
|
Squeezer::Disabled
|
||||||
|
},
|
||||||
|
display_offset: 0,
|
||||||
|
panels: self.panels,
|
||||||
|
squeeze_byte: 0x00,
|
||||||
|
group_size: self.group_size,
|
||||||
|
base_digits: match self.base {
|
||||||
|
Base::Binary => 8,
|
||||||
|
Base::Octal => 3,
|
||||||
|
Base::Decimal => 3,
|
||||||
|
Base::Hexadecimal => 2,
|
||||||
|
},
|
||||||
|
endianness: self.endianness,
|
||||||
|
include_mode: self.include_mode,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,6 +410,7 @@ pub struct Printer<'a, Writer: Write> {
|
|||||||
show_position_panel: bool,
|
show_position_panel: bool,
|
||||||
show_color: bool,
|
show_color: bool,
|
||||||
curr_color: Option<&'static [u8]>,
|
curr_color: Option<&'static [u8]>,
|
||||||
|
color_scheme: ColorScheme,
|
||||||
border_style: BorderStyle,
|
border_style: BorderStyle,
|
||||||
byte_hex_panel: Vec<String>,
|
byte_hex_panel: Vec<String>,
|
||||||
byte_char_panel: Vec<String>,
|
byte_char_panel: Vec<String>,
|
||||||
@@ -314,62 +427,11 @@ pub struct Printer<'a, Writer: Write> {
|
|||||||
base_digits: u8,
|
base_digits: u8,
|
||||||
/// Whether to show groups in little or big endian format.
|
/// Whether to show groups in little or big endian format.
|
||||||
endianness: Endianness,
|
endianness: Endianness,
|
||||||
|
/// Whether to output in C include file style.
|
||||||
|
include_mode: IncludeMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Writer: Write> Printer<'a, Writer> {
|
impl<'a, Writer: Write> Printer<'a, Writer> {
|
||||||
fn new(
|
|
||||||
writer: &'a mut Writer,
|
|
||||||
show_color: bool,
|
|
||||||
show_char_panel: bool,
|
|
||||||
show_position_panel: bool,
|
|
||||||
border_style: BorderStyle,
|
|
||||||
use_squeeze: bool,
|
|
||||||
panels: u64,
|
|
||||||
group_size: u8,
|
|
||||||
base: Base,
|
|
||||||
endianness: Endianness,
|
|
||||||
character_table: CharacterTable,
|
|
||||||
) -> Printer<'a, Writer> {
|
|
||||||
Printer {
|
|
||||||
idx: 0,
|
|
||||||
line_buf: vec![0x0; 8 * panels as usize],
|
|
||||||
writer,
|
|
||||||
show_char_panel,
|
|
||||||
show_position_panel,
|
|
||||||
show_color,
|
|
||||||
curr_color: None,
|
|
||||||
border_style,
|
|
||||||
byte_hex_panel: (0u8..=u8::MAX)
|
|
||||||
.map(|i| match base {
|
|
||||||
Base::Binary => format!("{i:08b}"),
|
|
||||||
Base::Octal => format!("{i:03o}"),
|
|
||||||
Base::Decimal => format!("{i:03}"),
|
|
||||||
Base::Hexadecimal => format!("{i:02x}"),
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
byte_char_panel: (0u8..=u8::MAX)
|
|
||||||
.map(|i| format!("{}", Byte(i).as_char(character_table)))
|
|
||||||
.collect(),
|
|
||||||
byte_hex_panel_g: (0u8..=u8::MAX).map(|i| format!("{i:02x}")).collect(),
|
|
||||||
squeezer: if use_squeeze {
|
|
||||||
Squeezer::Ignore
|
|
||||||
} else {
|
|
||||||
Squeezer::Disabled
|
|
||||||
},
|
|
||||||
display_offset: 0,
|
|
||||||
panels,
|
|
||||||
squeeze_byte: 0x00,
|
|
||||||
group_size,
|
|
||||||
base_digits: match base {
|
|
||||||
Base::Binary => 8,
|
|
||||||
Base::Octal => 3,
|
|
||||||
Base::Decimal => 3,
|
|
||||||
Base::Hexadecimal => 2,
|
|
||||||
},
|
|
||||||
endianness,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn display_offset(&mut self, display_offset: u64) -> &mut Self {
|
pub fn display_offset(&mut self, display_offset: u64) -> &mut Self {
|
||||||
self.display_offset = display_offset;
|
self.display_offset = display_offset;
|
||||||
self
|
self
|
||||||
@@ -440,14 +502,14 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
|||||||
.as_bytes(),
|
.as_bytes(),
|
||||||
)?;
|
)?;
|
||||||
if self.show_color {
|
if self.show_color {
|
||||||
self.writer.write_all(COLOR_OFFSET)?;
|
self.writer.write_all(COLOR_OFFSET.as_bytes())?;
|
||||||
}
|
}
|
||||||
if self.show_position_panel {
|
if self.show_position_panel {
|
||||||
match self.squeezer {
|
match self.squeezer {
|
||||||
Squeezer::Print => {
|
Squeezer::Print => {
|
||||||
self.writer.write_all(&[b'*'])?;
|
self.writer.write_all(b"*")?;
|
||||||
if self.show_color {
|
if self.show_color {
|
||||||
self.writer.write_all(COLOR_RESET)?;
|
self.writer.write_all(COLOR_RESET.as_bytes())?;
|
||||||
}
|
}
|
||||||
self.writer.write_all(b" ")?;
|
self.writer.write_all(b" ")?;
|
||||||
}
|
}
|
||||||
@@ -462,7 +524,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
|||||||
.write_all(self.byte_hex_panel_g[byte as usize].as_bytes())?;
|
.write_all(self.byte_hex_panel_g[byte as usize].as_bytes())?;
|
||||||
}
|
}
|
||||||
if self.show_color {
|
if self.show_color {
|
||||||
self.writer.write_all(COLOR_RESET)?;
|
self.writer.write_all(COLOR_RESET.as_bytes())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -481,9 +543,10 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
|||||||
Squeezer::Print | Squeezer::Delete => self.writer.write_all(b" ")?,
|
Squeezer::Print | Squeezer::Delete => self.writer.write_all(b" ")?,
|
||||||
Squeezer::Ignore | Squeezer::Disabled => {
|
Squeezer::Ignore | Squeezer::Disabled => {
|
||||||
if let Some(&b) = self.line_buf.get(i as usize) {
|
if let Some(&b) = self.line_buf.get(i as usize) {
|
||||||
if self.show_color && self.curr_color != Some(Byte(b).color()) {
|
if self.show_color && self.curr_color != Some(Byte(b).color(self.color_scheme))
|
||||||
self.writer.write_all(Byte(b).color())?;
|
{
|
||||||
self.curr_color = Some(Byte(b).color());
|
self.writer.write_all(Byte(b).color(self.color_scheme))?;
|
||||||
|
self.curr_color = Some(Byte(b).color(self.color_scheme));
|
||||||
}
|
}
|
||||||
self.writer
|
self.writer
|
||||||
.write_all(self.byte_char_panel[b as usize].as_bytes())?;
|
.write_all(self.byte_char_panel[b as usize].as_bytes())?;
|
||||||
@@ -494,7 +557,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
|||||||
}
|
}
|
||||||
if i == 8 * self.panels - 1 {
|
if i == 8 * self.panels - 1 {
|
||||||
if self.show_color {
|
if self.show_color {
|
||||||
self.writer.write_all(COLOR_RESET)?;
|
self.writer.write_all(COLOR_RESET.as_bytes())?;
|
||||||
self.curr_color = None;
|
self.curr_color = None;
|
||||||
}
|
}
|
||||||
self.writer.write_all(
|
self.writer.write_all(
|
||||||
@@ -505,7 +568,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
|||||||
)?;
|
)?;
|
||||||
} else if i % 8 == 7 {
|
} else if i % 8 == 7 {
|
||||||
if self.show_color {
|
if self.show_color {
|
||||||
self.writer.write_all(COLOR_RESET)?;
|
self.writer.write_all(COLOR_RESET.as_bytes())?;
|
||||||
self.curr_color = None;
|
self.curr_color = None;
|
||||||
}
|
}
|
||||||
self.writer.write_all(
|
self.writer.write_all(
|
||||||
@@ -531,14 +594,14 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
|||||||
Squeezer::Print => {
|
Squeezer::Print => {
|
||||||
if !self.show_position_panel && i == 0 {
|
if !self.show_position_panel && i == 0 {
|
||||||
if self.show_color {
|
if self.show_color {
|
||||||
self.writer.write_all(COLOR_OFFSET)?;
|
self.writer.write_all(COLOR_OFFSET.as_bytes())?;
|
||||||
}
|
}
|
||||||
self.writer
|
self.writer
|
||||||
.write_all(self.byte_char_panel[b'*' as usize].as_bytes())?;
|
.write_all(self.byte_char_panel[b'*' as usize].as_bytes())?;
|
||||||
if self.show_color {
|
if self.show_color {
|
||||||
self.writer.write_all(COLOR_RESET)?;
|
self.writer.write_all(COLOR_RESET.as_bytes())?;
|
||||||
}
|
}
|
||||||
} else if i % (self.group_size as usize) == 0 {
|
} else if i.is_multiple_of(self.group_size as usize) {
|
||||||
self.writer.write_all(b" ")?;
|
self.writer.write_all(b" ")?;
|
||||||
}
|
}
|
||||||
for _ in 0..self.base_digits {
|
for _ in 0..self.base_digits {
|
||||||
@@ -547,12 +610,12 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
|||||||
}
|
}
|
||||||
Squeezer::Delete => self.writer.write_all(b" ")?,
|
Squeezer::Delete => self.writer.write_all(b" ")?,
|
||||||
Squeezer::Ignore | Squeezer::Disabled => {
|
Squeezer::Ignore | Squeezer::Disabled => {
|
||||||
if i % (self.group_size as usize) == 0 {
|
if i.is_multiple_of(self.group_size as usize) {
|
||||||
self.writer.write_all(b" ")?;
|
self.writer.write_all(b" ")?;
|
||||||
}
|
}
|
||||||
if self.show_color && self.curr_color != Some(Byte(b).color()) {
|
if self.show_color && self.curr_color != Some(Byte(b).color(self.color_scheme)) {
|
||||||
self.writer.write_all(Byte(b).color())?;
|
self.writer.write_all(Byte(b).color(self.color_scheme))?;
|
||||||
self.curr_color = Some(Byte(b).color());
|
self.curr_color = Some(Byte(b).color(self.color_scheme));
|
||||||
}
|
}
|
||||||
self.writer
|
self.writer
|
||||||
.write_all(self.byte_hex_panel[b as usize].as_bytes())?;
|
.write_all(self.byte_hex_panel[b as usize].as_bytes())?;
|
||||||
@@ -562,7 +625,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
|||||||
if i % 8 == 7 {
|
if i % 8 == 7 {
|
||||||
if self.show_color {
|
if self.show_color {
|
||||||
self.curr_color = None;
|
self.curr_color = None;
|
||||||
self.writer.write_all(COLOR_RESET)?;
|
self.writer.write_all(COLOR_RESET.as_bytes())?;
|
||||||
}
|
}
|
||||||
self.writer.write_all(b" ")?;
|
self.writer.write_all(b" ")?;
|
||||||
// byte is last in last panel
|
// byte is last in last panel
|
||||||
@@ -585,7 +648,7 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reorder_buffer_to_little_endian(&self, buf: &mut Vec<u8>) {
|
fn reorder_buffer_to_little_endian(&self, buf: &mut [u8]) {
|
||||||
let n = buf.len();
|
let n = buf.len();
|
||||||
let group_sz = self.group_size as usize;
|
let group_sz = self.group_size as usize;
|
||||||
|
|
||||||
@@ -617,6 +680,36 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
|||||||
|
|
||||||
let mut buf = BufReader::new(reader);
|
let mut buf = BufReader::new(reader);
|
||||||
|
|
||||||
|
// special handler for include mode
|
||||||
|
match &self.include_mode {
|
||||||
|
// Input from a file
|
||||||
|
// Output like `unsigned char <filename>[] = { ... }; unsigned int <filename>_len = ...;`
|
||||||
|
IncludeMode::File(filename) => {
|
||||||
|
// convert non-alphanumeric characters to '_'
|
||||||
|
let var_name = filename
|
||||||
|
.chars()
|
||||||
|
.map(|c| if c.is_alphanumeric() { c } else { '_' })
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
writeln!(self.writer, "unsigned char {}[] = {{", var_name)?;
|
||||||
|
|
||||||
|
let total_bytes = self.print_bytes_in_include_style(&mut buf)?;
|
||||||
|
|
||||||
|
writeln!(self.writer, "}};")?;
|
||||||
|
writeln!(
|
||||||
|
self.writer,
|
||||||
|
"unsigned int {}_len = {};",
|
||||||
|
var_name, total_bytes
|
||||||
|
)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
IncludeMode::Stdin | IncludeMode::Slice => {
|
||||||
|
self.print_bytes_in_include_style(&mut buf)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
IncludeMode::Off => {}
|
||||||
|
}
|
||||||
|
|
||||||
let leftover = loop {
|
let leftover = loop {
|
||||||
// read a maximum of 8 * self.panels bytes from the reader
|
// read a maximum of 8 * self.panels bytes from the reader
|
||||||
if let Ok(n) = buf.read(&mut self.line_buf) {
|
if let Ok(n) = buf.read(&mut self.line_buf) {
|
||||||
@@ -733,8 +826,8 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
|||||||
writeln!(self.writer)?;
|
writeln!(self.writer)?;
|
||||||
} else if let Some(n) = leftover {
|
} else if let Some(n) = leftover {
|
||||||
// last line is incomplete
|
// last line is incomplete
|
||||||
self.print_position_panel()?;
|
|
||||||
self.squeezer = Squeezer::Ignore;
|
self.squeezer = Squeezer::Ignore;
|
||||||
|
self.print_position_panel()?;
|
||||||
self.print_bytes()?;
|
self.print_bytes()?;
|
||||||
self.squeezer = Squeezer::Print;
|
self.squeezer = Squeezer::Print;
|
||||||
for i in n..8 * self.panels as usize {
|
for i in n..8 * self.panels as usize {
|
||||||
@@ -757,6 +850,44 @@ impl<'a, Writer: Write> Printer<'a, Writer> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Print the bytes in C include file style
|
||||||
|
/// Return the number of bytes read
|
||||||
|
fn print_bytes_in_include_style<Reader: Read>(
|
||||||
|
&mut self,
|
||||||
|
buf: &mut BufReader<Reader>,
|
||||||
|
) -> Result<usize, io::Error> {
|
||||||
|
let mut buffer = [0; 1024];
|
||||||
|
let mut total_bytes = 0;
|
||||||
|
let mut is_first_chunk = true;
|
||||||
|
let mut line_counter = 0;
|
||||||
|
loop {
|
||||||
|
match buf.read(&mut buffer) {
|
||||||
|
Ok(0) => break, // EOF
|
||||||
|
Ok(bytes_read) => {
|
||||||
|
total_bytes += bytes_read;
|
||||||
|
|
||||||
|
for &byte in &buffer[..bytes_read] {
|
||||||
|
if line_counter % 12 == 0 {
|
||||||
|
if !is_first_chunk || line_counter > 0 {
|
||||||
|
writeln!(self.writer, ",")?;
|
||||||
|
}
|
||||||
|
// indentation of first line
|
||||||
|
write!(self.writer, " ")?;
|
||||||
|
is_first_chunk = false;
|
||||||
|
} else {
|
||||||
|
write!(self.writer, ", ")?;
|
||||||
|
}
|
||||||
|
write!(self.writer, "0x{:02x}", byte)?;
|
||||||
|
line_counter += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln!(self.writer)?;
|
||||||
|
Ok(total_bytes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -768,19 +899,20 @@ mod tests {
|
|||||||
|
|
||||||
fn assert_print_all_output<Reader: Read>(input: Reader, expected_string: String) {
|
fn assert_print_all_output<Reader: Read>(input: Reader, expected_string: String) {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let mut printer = Printer::new(
|
let mut printer = PrinterBuilder::new(&mut output)
|
||||||
&mut output,
|
.show_color(false)
|
||||||
false,
|
.show_char_panel(true)
|
||||||
true,
|
.show_position_panel(true)
|
||||||
true,
|
.with_border_style(BorderStyle::Unicode)
|
||||||
BorderStyle::Unicode,
|
.enable_squeezing(true)
|
||||||
true,
|
.num_panels(2)
|
||||||
2,
|
.group_size(1)
|
||||||
1,
|
.with_base(Base::Hexadecimal)
|
||||||
Base::Hexadecimal,
|
.endianness(Endianness::Big)
|
||||||
Endianness::Big,
|
.character_table(CharacterTable::Default)
|
||||||
CharacterTable::Default,
|
.include_mode(IncludeMode::Off)
|
||||||
);
|
.color_scheme(ColorScheme::Default)
|
||||||
|
.build();
|
||||||
|
|
||||||
printer.print_all(input).unwrap();
|
printer.print_all(input).unwrap();
|
||||||
|
|
||||||
@@ -824,19 +956,20 @@ mod tests {
|
|||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let mut printer: Printer<Vec<u8>> = Printer::new(
|
let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
|
||||||
&mut output,
|
.show_color(false)
|
||||||
false,
|
.show_char_panel(true)
|
||||||
true,
|
.show_position_panel(true)
|
||||||
true,
|
.with_border_style(BorderStyle::Unicode)
|
||||||
BorderStyle::Unicode,
|
.enable_squeezing(true)
|
||||||
true,
|
.num_panels(2)
|
||||||
2,
|
.group_size(1)
|
||||||
1,
|
.with_base(Base::Hexadecimal)
|
||||||
Base::Hexadecimal,
|
.endianness(Endianness::Big)
|
||||||
Endianness::Big,
|
.character_table(CharacterTable::Default)
|
||||||
CharacterTable::Default,
|
.include_mode(IncludeMode::Off)
|
||||||
);
|
.color_scheme(ColorScheme::Default)
|
||||||
|
.build();
|
||||||
printer.display_offset(0xdeadbeef);
|
printer.display_offset(0xdeadbeef);
|
||||||
|
|
||||||
printer.print_all(input).unwrap();
|
printer.print_all(input).unwrap();
|
||||||
@@ -859,19 +992,20 @@ mod tests {
|
|||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let mut printer: Printer<Vec<u8>> = Printer::new(
|
let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
|
||||||
&mut output,
|
.show_color(false)
|
||||||
false,
|
.show_char_panel(true)
|
||||||
true,
|
.show_position_panel(true)
|
||||||
true,
|
.with_border_style(BorderStyle::Unicode)
|
||||||
BorderStyle::Unicode,
|
.enable_squeezing(true)
|
||||||
true,
|
.num_panels(4)
|
||||||
4,
|
.group_size(1)
|
||||||
1,
|
.with_base(Base::Hexadecimal)
|
||||||
Base::Hexadecimal,
|
.endianness(Endianness::Big)
|
||||||
Endianness::Big,
|
.character_table(CharacterTable::Default)
|
||||||
CharacterTable::Default,
|
.include_mode(IncludeMode::Off)
|
||||||
);
|
.color_scheme(ColorScheme::Default)
|
||||||
|
.build();
|
||||||
|
|
||||||
printer.print_all(input).unwrap();
|
printer.print_all(input).unwrap();
|
||||||
|
|
||||||
@@ -920,19 +1054,96 @@ mod tests {
|
|||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let mut printer: Printer<Vec<u8>> = Printer::new(
|
let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
|
||||||
&mut output,
|
.show_color(false)
|
||||||
false,
|
.show_char_panel(true)
|
||||||
true,
|
.show_position_panel(true)
|
||||||
true,
|
.with_border_style(BorderStyle::Unicode)
|
||||||
BorderStyle::Unicode,
|
.enable_squeezing(true)
|
||||||
true,
|
.num_panels(3)
|
||||||
3,
|
.group_size(1)
|
||||||
1,
|
.with_base(Base::Hexadecimal)
|
||||||
Base::Hexadecimal,
|
.endianness(Endianness::Big)
|
||||||
Endianness::Big,
|
.character_table(CharacterTable::Default)
|
||||||
CharacterTable::Default,
|
.include_mode(IncludeMode::Off)
|
||||||
);
|
.color_scheme(ColorScheme::Default)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
printer.print_all(input).unwrap();
|
||||||
|
|
||||||
|
let actual_string: &str = str::from_utf8(&output).unwrap();
|
||||||
|
assert_eq!(actual_string, expected_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// issue#238
|
||||||
|
#[test]
|
||||||
|
fn display_offset_in_last_line() {
|
||||||
|
let input = io::Cursor::new(b"AAAAAAAAAAAAAAAACCCC");
|
||||||
|
let expected_string = "\
|
||||||
|
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
|
||||||
|
│00000000│ 41 41 41 41 41 41 41 41 ┊ 41 41 41 41 41 41 41 41 │AAAAAAAA┊AAAAAAAA│
|
||||||
|
│00000010│ 43 43 43 43 ┊ │CCCC ┊ │
|
||||||
|
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘
|
||||||
|
"
|
||||||
|
.to_owned();
|
||||||
|
assert_print_all_output(input, expected_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn include_mode_from_file() {
|
||||||
|
let input = io::Cursor::new(b"spamspamspamspamspam");
|
||||||
|
let expected_string = "unsigned char test_txt[] = {
|
||||||
|
0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d,
|
||||||
|
0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d
|
||||||
|
};
|
||||||
|
unsigned int test_txt_len = 20;
|
||||||
|
"
|
||||||
|
.to_owned();
|
||||||
|
let mut output = vec![];
|
||||||
|
let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
|
||||||
|
.show_color(false)
|
||||||
|
.show_char_panel(true)
|
||||||
|
.show_position_panel(true)
|
||||||
|
.with_border_style(BorderStyle::Unicode)
|
||||||
|
.enable_squeezing(true)
|
||||||
|
.num_panels(2)
|
||||||
|
.group_size(1)
|
||||||
|
.with_base(Base::Hexadecimal)
|
||||||
|
.endianness(Endianness::Big)
|
||||||
|
.character_table(CharacterTable::Default)
|
||||||
|
.include_mode(IncludeMode::File("test.txt".to_owned()))
|
||||||
|
.color_scheme(ColorScheme::Default)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
printer.print_all(input).unwrap();
|
||||||
|
|
||||||
|
let actual_string: &str = str::from_utf8(&output).unwrap();
|
||||||
|
assert_eq!(actual_string, expected_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn include_mode_from_stdin() {
|
||||||
|
let input = io::Cursor::new(b"spamspamspamspamspam");
|
||||||
|
let expected_string =
|
||||||
|
" 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d,
|
||||||
|
0x73, 0x70, 0x61, 0x6d, 0x73, 0x70, 0x61, 0x6d
|
||||||
|
"
|
||||||
|
.to_owned();
|
||||||
|
let mut output = vec![];
|
||||||
|
let mut printer: Printer<Vec<u8>> = PrinterBuilder::new(&mut output)
|
||||||
|
.show_color(false)
|
||||||
|
.show_char_panel(true)
|
||||||
|
.show_position_panel(true)
|
||||||
|
.with_border_style(BorderStyle::Unicode)
|
||||||
|
.enable_squeezing(true)
|
||||||
|
.num_panels(2)
|
||||||
|
.group_size(1)
|
||||||
|
.with_base(Base::Hexadecimal)
|
||||||
|
.endianness(Endianness::Big)
|
||||||
|
.character_table(CharacterTable::Default)
|
||||||
|
.include_mode(IncludeMode::Stdin)
|
||||||
|
.color_scheme(ColorScheme::Default)
|
||||||
|
.build();
|
||||||
|
|
||||||
printer.print_all(input).unwrap();
|
printer.print_all(input).unwrap();
|
||||||
|
|
||||||
|
|||||||
+89
-20
@@ -3,8 +3,11 @@ use std::io::{self, prelude::*, BufWriter, SeekFrom};
|
|||||||
use std::num::{NonZeroI64, NonZeroU64};
|
use std::num::{NonZeroI64, NonZeroU64};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use clap::builder::styling::{AnsiColor, Effects};
|
||||||
use clap::builder::ArgPredicate;
|
use clap::builder::ArgPredicate;
|
||||||
use clap::{ArgAction, Parser, ValueEnum};
|
use clap::builder::Styles;
|
||||||
|
use clap::{ArgAction, CommandFactory, Parser, ValueEnum};
|
||||||
|
use clap_complete::aot::{generate, Shell};
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
|
|
||||||
@@ -14,7 +17,9 @@ use thiserror::Error as ThisError;
|
|||||||
|
|
||||||
use terminal_size::terminal_size;
|
use terminal_size::terminal_size;
|
||||||
|
|
||||||
use hexyl::{Base, BorderStyle, CharacterTable, Endianness, Input, PrinterBuilder};
|
use hexyl::{
|
||||||
|
Base, BorderStyle, CharacterTable, ColorScheme, Endianness, IncludeMode, Input, PrinterBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
use hexyl::{
|
use hexyl::{
|
||||||
COLOR_ASCII_OTHER, COLOR_ASCII_PRINTABLE, COLOR_ASCII_WHITESPACE, COLOR_NONASCII, COLOR_NULL,
|
COLOR_ASCII_OTHER, COLOR_ASCII_PRINTABLE, COLOR_ASCII_WHITESPACE, COLOR_NONASCII, COLOR_NULL,
|
||||||
@@ -51,8 +56,14 @@ Since the terminal width may not be an evenly divisible by the width per hex dat
|
|||||||
the right.
|
the right.
|
||||||
Cannot be used with other width-setting options.";
|
Cannot be used with other width-setting options.";
|
||||||
|
|
||||||
|
const STYLES: Styles = Styles::styled()
|
||||||
|
.header(AnsiColor::Green.on_default().effects(Effects::BOLD))
|
||||||
|
.usage(AnsiColor::Green.on_default().effects(Effects::BOLD))
|
||||||
|
.literal(AnsiColor::Cyan.on_default().effects(Effects::BOLD))
|
||||||
|
.placeholder(AnsiColor::Cyan.on_default());
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(version, about, max_term_width(90))]
|
#[command(version, about, max_term_width(90), styles = STYLES)]
|
||||||
struct Opt {
|
struct Opt {
|
||||||
/// The file to display. If no FILE argument is given, read from STDIN.
|
/// The file to display. If no FILE argument is given, read from STDIN.
|
||||||
#[arg(value_name("FILE"))]
|
#[arg(value_name("FILE"))]
|
||||||
@@ -129,6 +140,10 @@ struct Opt {
|
|||||||
#[arg(long, value_enum, default_value_t, value_name("FORMAT"))]
|
#[arg(long, value_enum, default_value_t, value_name("FORMAT"))]
|
||||||
character_table: CharacterTable,
|
character_table: CharacterTable,
|
||||||
|
|
||||||
|
/// Defines the color scheme for the characters.
|
||||||
|
#[arg(long, value_enum, default_value_t, value_name("FORMAT"))]
|
||||||
|
color_scheme: ColorScheme,
|
||||||
|
|
||||||
/// Whether to display the position panel on the left.
|
/// Whether to display the position panel on the left.
|
||||||
#[arg(short('P'), long)]
|
#[arg(short('P'), long)]
|
||||||
no_position: bool,
|
no_position: bool,
|
||||||
@@ -188,6 +203,20 @@ struct Opt {
|
|||||||
/// Print a table showing how different types of bytes are colored.
|
/// Print a table showing how different types of bytes are colored.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
print_color_table: bool,
|
print_color_table: bool,
|
||||||
|
|
||||||
|
/// Output in C include file style (similar to xxd -i).
|
||||||
|
#[arg(
|
||||||
|
short('i'),
|
||||||
|
long("include"),
|
||||||
|
help = "Output in C include file style",
|
||||||
|
conflicts_with("little_endian_format"),
|
||||||
|
conflicts_with("endianness")
|
||||||
|
)]
|
||||||
|
include_mode: bool,
|
||||||
|
|
||||||
|
/// Show shell completion for a certain shell
|
||||||
|
#[arg(long, value_name("SHELL"))]
|
||||||
|
completion: Option<Shell>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, ValueEnum)]
|
#[derive(Clone, Debug, Default, ValueEnum)]
|
||||||
@@ -244,16 +273,27 @@ fn run() -> Result<()> {
|
|||||||
return print_color_table().map_err(|e| anyhow!(e));
|
return print_color_table().map_err(|e| anyhow!(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(sh) = opt.completion {
|
||||||
|
let mut cmd = Opt::command();
|
||||||
|
let name = cmd.get_name().to_string();
|
||||||
|
generate(sh, &mut cmd, name, &mut io::stdout());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
|
|
||||||
let mut reader = match opt.file {
|
let mut reader = match &opt.file {
|
||||||
Some(filename) => {
|
Some(filename) => {
|
||||||
if filename.is_dir() {
|
if filename.as_os_str() == "-" {
|
||||||
bail!("'{}' is a directory.", filename.to_string_lossy());
|
Input::Stdin(stdin.lock())
|
||||||
}
|
} else {
|
||||||
let file = File::open(&filename)?;
|
if filename.is_dir() {
|
||||||
|
bail!("'{}' is a directory.", filename.to_string_lossy());
|
||||||
|
}
|
||||||
|
let file = File::open(filename)?;
|
||||||
|
|
||||||
Input::File(file)
|
Input::File(file)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => Input::Stdin(stdin.lock()),
|
None => Input::Stdin(stdin.lock()),
|
||||||
};
|
};
|
||||||
@@ -362,7 +402,7 @@ fn run() -> Result<()> {
|
|||||||
} else {
|
} else {
|
||||||
((8 / group_size) * (base_digits * group_size + 1)) + 2
|
((8 / group_size) * (base_digits * group_size + 1)) + 2
|
||||||
};
|
};
|
||||||
if (terminal_width - offset) / col_width < 1 {
|
if (terminal_width.saturating_sub(offset)) / col_width < 1 {
|
||||||
1
|
1
|
||||||
} else {
|
} else {
|
||||||
(terminal_width - offset) / col_width
|
(terminal_width - offset) / col_width
|
||||||
@@ -429,8 +469,35 @@ fn run() -> Result<()> {
|
|||||||
|
|
||||||
let character_table = opt.character_table;
|
let character_table = opt.character_table;
|
||||||
|
|
||||||
|
let color_scheme = opt.color_scheme;
|
||||||
|
|
||||||
let mut stdout = BufWriter::new(io::stdout().lock());
|
let mut stdout = BufWriter::new(io::stdout().lock());
|
||||||
|
|
||||||
|
let include_mode = match opt.include_mode {
|
||||||
|
// include mode on
|
||||||
|
true => {
|
||||||
|
if let Some(include_file) = opt.file {
|
||||||
|
// input from a file
|
||||||
|
if include_file.as_os_str() == "-" {
|
||||||
|
IncludeMode::File("stdin".to_string())
|
||||||
|
} else {
|
||||||
|
IncludeMode::File(
|
||||||
|
include_file
|
||||||
|
.file_name()
|
||||||
|
.and_then(|n| n.to_str())
|
||||||
|
.unwrap_or("file")
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// input from stdin
|
||||||
|
IncludeMode::Stdin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// include mode off
|
||||||
|
false => IncludeMode::Off,
|
||||||
|
};
|
||||||
|
|
||||||
let mut printer = PrinterBuilder::new(&mut stdout)
|
let mut printer = PrinterBuilder::new(&mut stdout)
|
||||||
.show_color(show_color)
|
.show_color(show_color)
|
||||||
.show_char_panel(show_char_panel)
|
.show_char_panel(show_char_panel)
|
||||||
@@ -442,6 +509,8 @@ fn run() -> Result<()> {
|
|||||||
.with_base(base)
|
.with_base(base)
|
||||||
.endianness(endianness)
|
.endianness(endianness)
|
||||||
.character_table(character_table)
|
.character_table(character_table)
|
||||||
|
.include_mode(include_mode)
|
||||||
|
.color_scheme(color_scheme)
|
||||||
.build();
|
.build();
|
||||||
printer.display_offset(skip_offset + display_offset);
|
printer.display_offset(skip_offset + display_offset);
|
||||||
printer.print_all(&mut reader).map_err(|e| anyhow!(e))?;
|
printer.print_all(&mut reader).map_err(|e| anyhow!(e))?;
|
||||||
@@ -493,32 +562,32 @@ fn print_color_table() -> io::Result<()> {
|
|||||||
writeln!(stdout, "hexyl color reference:\n")?;
|
writeln!(stdout, "hexyl color reference:\n")?;
|
||||||
|
|
||||||
// NULL bytes
|
// NULL bytes
|
||||||
stdout.write_all(COLOR_NULL)?;
|
stdout.write_all(COLOR_NULL.as_bytes())?;
|
||||||
writeln!(stdout, "⋄ NULL bytes (0x00)")?;
|
writeln!(stdout, "⋄ NULL bytes (0x00)")?;
|
||||||
stdout.write_all(COLOR_RESET)?;
|
stdout.write_all(COLOR_RESET.as_bytes())?;
|
||||||
|
|
||||||
// ASCII printable
|
// ASCII printable
|
||||||
stdout.write_all(COLOR_ASCII_PRINTABLE)?;
|
stdout.write_all(COLOR_ASCII_PRINTABLE.as_bytes())?;
|
||||||
writeln!(stdout, "a ASCII printable characters (0x20 - 0x7E)")?;
|
writeln!(stdout, "a ASCII printable characters (0x20 - 0x7E)")?;
|
||||||
stdout.write_all(COLOR_RESET)?;
|
stdout.write_all(COLOR_RESET.as_bytes())?;
|
||||||
|
|
||||||
// ASCII whitespace
|
// ASCII whitespace
|
||||||
stdout.write_all(COLOR_ASCII_WHITESPACE)?;
|
stdout.write_all(COLOR_ASCII_WHITESPACE.as_bytes())?;
|
||||||
writeln!(stdout, "_ ASCII whitespace (0x09 - 0x0D, 0x20)")?;
|
writeln!(stdout, "_ ASCII whitespace (0x09 - 0x0D, 0x20)")?;
|
||||||
stdout.write_all(COLOR_RESET)?;
|
stdout.write_all(COLOR_RESET.as_bytes())?;
|
||||||
|
|
||||||
// ASCII other
|
// ASCII other
|
||||||
stdout.write_all(COLOR_ASCII_OTHER)?;
|
stdout.write_all(COLOR_ASCII_OTHER.as_bytes())?;
|
||||||
writeln!(
|
writeln!(
|
||||||
stdout,
|
stdout,
|
||||||
"• ASCII control characters (except NULL and whitespace)"
|
"• ASCII control characters (except NULL and whitespace)"
|
||||||
)?;
|
)?;
|
||||||
stdout.write_all(COLOR_RESET)?;
|
stdout.write_all(COLOR_RESET.as_bytes())?;
|
||||||
|
|
||||||
// Non-ASCII
|
// Non-ASCII
|
||||||
stdout.write_all(COLOR_NONASCII)?;
|
stdout.write_all(COLOR_NONASCII.as_bytes())?;
|
||||||
writeln!(stdout, "× Non-ASCII bytes (0x80 - 0xFF)")?;
|
writeln!(stdout, "× Non-ASCII bytes (0x80 - 0xFF)")?;
|
||||||
stdout.write_all(COLOR_RESET)?;
|
stdout.write_all(COLOR_RESET.as_bytes())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+178
-1
@@ -1,7 +1,7 @@
|
|||||||
use assert_cmd::Command;
|
use assert_cmd::Command;
|
||||||
|
|
||||||
fn hexyl() -> Command {
|
fn hexyl() -> Command {
|
||||||
let mut cmd = Command::cargo_bin("hexyl").unwrap();
|
let mut cmd = Command::new(assert_cmd::cargo_bin!("hexyl"));
|
||||||
cmd.current_dir("tests/examples");
|
cmd.current_dir("tests/examples");
|
||||||
cmd
|
cmd
|
||||||
}
|
}
|
||||||
@@ -785,3 +785,180 @@ mod character_table {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod colors {
|
||||||
|
use super::hexyl;
|
||||||
|
use owo_colors::{colors, Color};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
// This is a helper for testing color in output. Writing tests to expect
|
||||||
|
// raw color codes is ugly and hard to look at. Loading expected output
|
||||||
|
// from files works fine, but you end up with a lot of files for all the
|
||||||
|
// tests, and you have to cross-reference the file with the test that uses
|
||||||
|
// it. The files also suffer from the same problem of being hard to
|
||||||
|
// visually inspect. Just catting the file to see the colorized output
|
||||||
|
// loses the nuance of where exactly the color codes appear (before or
|
||||||
|
// after spaces, for example), or whether there are redundant codes.
|
||||||
|
//
|
||||||
|
// So this ColorMap solves the problem neatly by having two inputs:
|
||||||
|
// - the easy to read expected output in plain format without any colors
|
||||||
|
// - a mapping with identical structure except some characters replaced
|
||||||
|
// with single character color codes.
|
||||||
|
// This makes it easy to reference the output and expected colors side by
|
||||||
|
// side, and provides fairly precise control over exactly where color codes
|
||||||
|
// are expected (the only caveat being you can't have two color codes back
|
||||||
|
// to back). ColorMap combines these into the actual expected output.
|
||||||
|
//
|
||||||
|
// The color mapping needs to be identical to the expected output, except
|
||||||
|
// it has some chars replaced by color code stand ins. These are replaced
|
||||||
|
// with the actual color codes by the colorize method. The '.' character
|
||||||
|
// is also ignored (it doesn't need to match the input). This makes the
|
||||||
|
// color map more readable and avoids input characters from conflicting
|
||||||
|
// with color chars.
|
||||||
|
struct ColorMap {
|
||||||
|
text_map: &'static str,
|
||||||
|
char_to_color: HashMap<char, &'static str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ColorMap {
|
||||||
|
fn from(text_map: &'static str) -> Self {
|
||||||
|
ColorMap {
|
||||||
|
text_map,
|
||||||
|
char_to_color: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with<C: Color>(&mut self, c: char) -> &mut Self {
|
||||||
|
self.char_to_color.insert(c, C::ANSI_FG);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn colorize(&self, input: &str) -> String {
|
||||||
|
let mut output = String::new();
|
||||||
|
let mut input_chars = input.chars();
|
||||||
|
for c in self.text_map.chars() {
|
||||||
|
let next_input = input_chars.next().expect("input and color map don't match");
|
||||||
|
if let Some(color) = self.char_to_color.get(&c) {
|
||||||
|
output.push_str(color);
|
||||||
|
} else if c != '.' {
|
||||||
|
// ignore '.' in the mapping for readability
|
||||||
|
assert_eq!(c, next_input, "input and color map don't match");
|
||||||
|
}
|
||||||
|
output.push(next_input);
|
||||||
|
}
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hex_colors() {
|
||||||
|
let input = b"He\x11\0 \xff\0\xdd";
|
||||||
|
let expected_text = "\
|
||||||
|
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n\
|
||||||
|
│00000000│ 48 65 11 00 20 ff 00 dd ┊ │He•⋄ ×⋄×┊ │\n\
|
||||||
|
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n";
|
||||||
|
let expected = ColorMap::from(
|
||||||
|
"\
|
||||||
|
┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐\n\
|
||||||
|
│r.......d y. .. b. c. g. m. c. m.d┊ d│y.bcgmcmd d\n\
|
||||||
|
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘\n",
|
||||||
|
)
|
||||||
|
.with::<colors::Red>('r')
|
||||||
|
.with::<colors::Default>('d')
|
||||||
|
.with::<colors::Yellow>('y')
|
||||||
|
.with::<colors::Blue>('b')
|
||||||
|
.with::<colors::Green>('g')
|
||||||
|
.with::<colors::BrightMagenta>('m')
|
||||||
|
.with::<colors::CustomColor<0xab, 0xcd, 0xef>>('c')
|
||||||
|
.colorize(expected_text);
|
||||||
|
|
||||||
|
hexyl()
|
||||||
|
.write_stdin(input)
|
||||||
|
.arg("--color=always")
|
||||||
|
.env("HEXYL_COLOR_OFFSET", "red")
|
||||||
|
.env("HEXYL_COLOR_ASCII_PRINTABLE", "yellow")
|
||||||
|
.env("HEXYL_COLOR_ASCII_WHITESPACE", "green")
|
||||||
|
.env("HEXYL_COLOR_ASCII_OTHER", "blue")
|
||||||
|
.env("HEXYL_COLOR_NONASCII", "bright magenta")
|
||||||
|
.env("HEXYL_COLOR_NULL", "#abcdef")
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn binary_colors() {
|
||||||
|
let input = b"He\x11\0 \xff\0\xdd";
|
||||||
|
let expected_text = "\
|
||||||
|
┌────────┬─────────────────────────────────────────────────────────────────────────┬────────┐\n\
|
||||||
|
│00000000│ 01001000 01100101 00010001 00000000 00100000 11111111 00000000 11011101 │He•⋄ ×⋄×│\n\
|
||||||
|
└────────┴─────────────────────────────────────────────────────────────────────────┴────────┘\n";
|
||||||
|
let expected = ColorMap::from(
|
||||||
|
"\
|
||||||
|
┌────────┬─────────────────────────────────────────────────────────────────────────┬────────┐\n\
|
||||||
|
│r.......d y....... ........ b....... c....... g....... m....... c....... m.......d│y.bcgmcmd\n\
|
||||||
|
└────────┴─────────────────────────────────────────────────────────────────────────┴────────┘\n"
|
||||||
|
)
|
||||||
|
.with::<colors::Red>('r')
|
||||||
|
.with::<colors::Default>('d')
|
||||||
|
.with::<colors::Yellow>('y')
|
||||||
|
.with::<colors::Blue>('b')
|
||||||
|
.with::<colors::Green>('g')
|
||||||
|
.with::<colors::BrightMagenta>('m')
|
||||||
|
.with::<colors::CustomColor<0xab, 0xcd, 0xef>>('c')
|
||||||
|
.colorize(expected_text);
|
||||||
|
|
||||||
|
hexyl()
|
||||||
|
.write_stdin(input)
|
||||||
|
.arg("--color=always")
|
||||||
|
.arg("--panels=1")
|
||||||
|
.arg("--base=binary")
|
||||||
|
.env("HEXYL_COLOR_OFFSET", "red")
|
||||||
|
.env("HEXYL_COLOR_ASCII_PRINTABLE", "yellow")
|
||||||
|
.env("HEXYL_COLOR_ASCII_WHITESPACE", "green")
|
||||||
|
.env("HEXYL_COLOR_ASCII_OTHER", "blue")
|
||||||
|
.env("HEXYL_COLOR_NONASCII", "bright magenta")
|
||||||
|
.env("HEXYL_COLOR_NULL", "#abcdef")
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout(expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn groupsize_colors() {
|
||||||
|
let input = b"He\x11\0 \xff\0\xdd";
|
||||||
|
let expected_text = "\
|
||||||
|
┌────────┬─────────────────────┬────────┐\n\
|
||||||
|
│00000000│ 4865 1100 20ff 00dd │He•⋄ ×⋄×│\n\
|
||||||
|
└────────┴─────────────────────┴────────┘\n";
|
||||||
|
let expected = ColorMap::from(
|
||||||
|
"\
|
||||||
|
┌────────┬─────────────────────┬────────┐\n\
|
||||||
|
│r.......d y... b.c. g.m. c.m.d│y.bcgmcmd\n\
|
||||||
|
└────────┴─────────────────────┴────────┘\n",
|
||||||
|
)
|
||||||
|
.with::<colors::Red>('r')
|
||||||
|
.with::<colors::Default>('d')
|
||||||
|
.with::<colors::Yellow>('y')
|
||||||
|
.with::<colors::Blue>('b')
|
||||||
|
.with::<colors::Green>('g')
|
||||||
|
.with::<colors::BrightMagenta>('m')
|
||||||
|
.with::<colors::CustomColor<0xab, 0xcd, 0xef>>('c')
|
||||||
|
.colorize(expected_text);
|
||||||
|
|
||||||
|
hexyl()
|
||||||
|
.write_stdin(input)
|
||||||
|
.arg("--color=always")
|
||||||
|
.arg("--panels=1")
|
||||||
|
.arg("--groupsize=2")
|
||||||
|
.env("HEXYL_COLOR_OFFSET", "red")
|
||||||
|
.env("HEXYL_COLOR_ASCII_PRINTABLE", "yellow")
|
||||||
|
.env("HEXYL_COLOR_ASCII_WHITESPACE", "green")
|
||||||
|
.env("HEXYL_COLOR_ASCII_OTHER", "blue")
|
||||||
|
.env("HEXYL_COLOR_NONASCII", "bright magenta")
|
||||||
|
.env("HEXYL_COLOR_NULL", "#abcdef")
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user