mirror of
https://github.com/sharkdp/hexyl.git
synced 2026-10-26 20:16:31 +00:00
Rewrite CLI using derive API
This commit is contained in:
1 parent
16cc5d9757
commit
252a6dbef4
6 files changed
+320
-371
No files matched your search
@@ -8,6 +8,8 @@
|
|||||||
|
|
||||||
## Other
|
## Other
|
||||||
|
|
||||||
|
- Rewrite CLI using the derive API, see #225 (@sorairolake)
|
||||||
|
|
||||||
|
|
||||||
# v0.14.0
|
# v0.14.0
|
||||||
|
|
||||||
|
|||||||
Generated
+20
-1
@@ -126,6 +126,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "03aef18ddf7d879c15ce20f04826ef8418101c7e528014c3eeea13321047dca3"
|
checksum = "03aef18ddf7d879c15ce20f04826ef8418101c7e528014c3eeea13321047dca3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -137,11 +139,22 @@ dependencies = [
|
|||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"once_cell",
|
|
||||||
"strsim",
|
"strsim",
|
||||||
"terminal_size",
|
"terminal_size",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.3.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54a9bb5758fc5dfe728d1019941681eccaf0cf8a4189b692a0ee2f2ecf90a050"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_lex"
|
name = "clap_lex"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@@ -228,6 +241,12 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
|||||||
+1
-2
@@ -22,8 +22,7 @@ terminal_size = "0.2"
|
|||||||
|
|
||||||
[dependencies.clap]
|
[dependencies.clap]
|
||||||
version = "4"
|
version = "4"
|
||||||
default-features = false
|
features = ["derive", "wrap_help"]
|
||||||
features = ["std", "suggestions", "color", "wrap_help", "cargo", "help", "usage", "error-context"]
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_cmd = "2.0"
|
assert_cmd = "2.0"
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use std::convert::TryFrom;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{self, copy, sink, Read, Seek, SeekFrom};
|
use std::io::{self, copy, sink, Read, Seek, SeekFrom};
|
||||||
|
|
||||||
|
|||||||
+24
-3
@@ -6,6 +6,8 @@ pub use input::*;
|
|||||||
|
|
||||||
use std::io::{self, BufReader, Read, Write};
|
use std::io::{self, BufReader, Read, Write};
|
||||||
|
|
||||||
|
use clap::ValueEnum;
|
||||||
|
|
||||||
pub enum Base {
|
pub enum Base {
|
||||||
Binary,
|
Binary,
|
||||||
Octal,
|
Octal,
|
||||||
@@ -22,17 +24,30 @@ pub enum ByteCategory {
|
|||||||
NonAscii,
|
NonAscii,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug, Default, ValueEnum)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum CharacterTable {
|
pub enum CharacterTable {
|
||||||
|
/// Show printable ASCII characters as-is, '⋄' for NULL bytes, ' ' for
|
||||||
|
/// space, '_' for other ASCII whitespace, '•' for other ASCII characters,
|
||||||
|
/// and '×' for non-ASCII bytes.
|
||||||
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
|
|
||||||
|
/// Show printable ASCII as-is, ' ' for space, '.' for everything else.
|
||||||
Ascii,
|
Ascii,
|
||||||
|
|
||||||
|
/// Uses code page 437 (for non-ASCII bytes).
|
||||||
|
#[value(name = "codepage-437")]
|
||||||
CP437,
|
CP437,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug, Default, ValueEnum)]
|
||||||
pub enum Endianness {
|
pub enum Endianness {
|
||||||
|
/// Print out groups in little-endian format.
|
||||||
Little,
|
Little,
|
||||||
|
|
||||||
|
/// Print out groups in big-endian format.
|
||||||
|
#[default]
|
||||||
Big,
|
Big,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,10 +119,16 @@ struct BorderElements {
|
|||||||
right_corner: char,
|
right_corner: char,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Debug, Default, ValueEnum)]
|
||||||
pub enum BorderStyle {
|
pub enum BorderStyle {
|
||||||
|
/// Draw a border with Unicode characters.
|
||||||
|
#[default]
|
||||||
Unicode,
|
Unicode,
|
||||||
|
|
||||||
|
/// Draw a border with ASCII characters.
|
||||||
Ascii,
|
Ascii,
|
||||||
|
|
||||||
|
/// Do not draw a border at all.
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+273
-364
@@ -1,13 +1,10 @@
|
|||||||
#[macro_use]
|
|
||||||
extern crate clap;
|
|
||||||
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{self, prelude::*, BufWriter, SeekFrom};
|
use std::io::{self, prelude::*, BufWriter, SeekFrom};
|
||||||
use std::num::{NonZeroI64, NonZeroU64, NonZeroU8};
|
use std::num::{NonZeroI64, NonZeroU64};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::builder::ArgPredicate;
|
use clap::builder::ArgPredicate;
|
||||||
use clap::{crate_name, crate_version, Arg, ArgAction, ColorChoice, Command};
|
use clap::{ArgAction, Parser, ValueEnum};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
|
||||||
@@ -24,273 +21,252 @@ mod tests;
|
|||||||
|
|
||||||
const DEFAULT_BLOCK_SIZE: i64 = 512;
|
const DEFAULT_BLOCK_SIZE: i64 = 512;
|
||||||
|
|
||||||
fn run() -> Result<()> {
|
#[derive(Debug, Parser)]
|
||||||
let command = Command::new(crate_name!())
|
#[command(version, about, max_term_width(90))]
|
||||||
.color(ColorChoice::Auto)
|
struct Opt {
|
||||||
.max_term_width(90)
|
/// The file to display.
|
||||||
.version(crate_version!())
|
///
|
||||||
.about(crate_description!())
|
/// If no FILE argument is given, read from STDIN.
|
||||||
.arg(
|
#[arg(value_name("FILE"))]
|
||||||
Arg::new("FILE")
|
file: Option<PathBuf>,
|
||||||
.help("The file to display. If no FILE argument is given, read from STDIN."),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("length")
|
|
||||||
.short('n')
|
|
||||||
.long("length")
|
|
||||||
.num_args(1)
|
|
||||||
.value_name("N")
|
|
||||||
.help(
|
|
||||||
"Only read N bytes from the input. The N argument can also include a \
|
|
||||||
unit with a decimal prefix (kB, MB, ..) or binary prefix (kiB, MiB, ..), \
|
|
||||||
or can be specified using a hex number. \
|
|
||||||
The short option '-l' can be used as an alias.\n\
|
|
||||||
Examples: --length=64, --length=4KiB, --length=0xff",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("bytes")
|
|
||||||
.short('c')
|
|
||||||
.long("bytes")
|
|
||||||
.num_args(1)
|
|
||||||
.value_name("N")
|
|
||||||
.conflicts_with("length")
|
|
||||||
.help("An alias for -n/--length"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("count")
|
|
||||||
.short('l')
|
|
||||||
.num_args(1)
|
|
||||||
.value_name("N")
|
|
||||||
.conflicts_with_all(["length", "bytes"])
|
|
||||||
.hide(true)
|
|
||||||
.help("Yet another alias for -n/--length"),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("skip")
|
|
||||||
.short('s')
|
|
||||||
.long("skip")
|
|
||||||
.num_args(1)
|
|
||||||
.value_name("N")
|
|
||||||
.help(
|
|
||||||
"Skip the first N bytes of the input. The N argument can also include \
|
|
||||||
a unit (see `--length` for details)\n\
|
|
||||||
A negative value is valid and will seek from the end of the file.",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("block_size")
|
|
||||||
.long("block-size")
|
|
||||||
.num_args(1)
|
|
||||||
.value_name("SIZE")
|
|
||||||
.help(formatcp!(
|
|
||||||
"Sets the size of the `block` unit to SIZE (default is {}).\n\
|
|
||||||
Examples: --block-size=1024, --block-size=4kB",
|
|
||||||
DEFAULT_BLOCK_SIZE
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("nosqueezing")
|
|
||||||
.short('v')
|
|
||||||
.long("no-squeezing")
|
|
||||||
.action(ArgAction::SetFalse)
|
|
||||||
.help(
|
|
||||||
"Displays all input data. Otherwise any number of groups of output \
|
|
||||||
lines which would be identical to the preceding group of lines, are \
|
|
||||||
replaced with a line comprised of a single asterisk.",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("color")
|
|
||||||
.long("color")
|
|
||||||
.num_args(1)
|
|
||||||
.value_name("WHEN")
|
|
||||||
.value_parser(["always", "auto", "never", "force"])
|
|
||||||
.default_value_if("plain", ArgPredicate::IsPresent, Some("never"))
|
|
||||||
.default_value("always")
|
|
||||||
.help(
|
|
||||||
"When to use colors. The 'auto' mode only displays colors if the output \
|
|
||||||
goes to an interactive terminal. 'force' can be used to override the \
|
|
||||||
NO_COLOR environment variable.",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("border")
|
|
||||||
.long("border")
|
|
||||||
.num_args(1)
|
|
||||||
.value_name("STYLE")
|
|
||||||
.value_parser(["unicode", "ascii", "none"])
|
|
||||||
.default_value_if("plain", ArgPredicate::IsPresent, Some("none"))
|
|
||||||
.default_value("unicode")
|
|
||||||
.help(
|
|
||||||
"Whether to draw a border with Unicode characters, ASCII characters, \
|
|
||||||
or none at all",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.arg(Arg::new("plain").short('p').long("plain").action(ArgAction::SetTrue).help(
|
|
||||||
"Display output with --no-characters, --no-position, --border=none, and --color=never.",
|
|
||||||
))
|
|
||||||
.arg(
|
|
||||||
Arg::new("no_chars")
|
|
||||||
.long("no-characters")
|
|
||||||
.action(ArgAction::SetFalse)
|
|
||||||
.help("Do not show the character panel on the right."),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("chars")
|
|
||||||
.short('C')
|
|
||||||
.long("characters")
|
|
||||||
.overrides_with("no_chars")
|
|
||||||
.action(ArgAction::SetTrue)
|
|
||||||
.help("Show the character panel on the right. This is the default, unless --no-characters has been specified."),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("character-table")
|
|
||||||
.long("character-table")
|
|
||||||
.value_name("FORMAT")
|
|
||||||
.value_parser(["default", "ascii", "codepage-437"])
|
|
||||||
.default_value("default")
|
|
||||||
.help(
|
|
||||||
"Defines how bytes are mapped to characters:\n \
|
|
||||||
\"default\": show printable ASCII characters as-is, '⋄' for NULL bytes, \
|
|
||||||
' ' for space, '_' for other ASCII whitespace, \
|
|
||||||
'•' for other ASCII characters, and '×' for non-ASCII bytes.\n \
|
|
||||||
\"ascii\": show printable ASCII as-is, ' ' for space, '.' for everything else.\n \
|
|
||||||
\"codepage-437\": uses code page 437 (for non-ASCII bytes).\n"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("no_position")
|
|
||||||
.short('P')
|
|
||||||
.long("no-position")
|
|
||||||
.action(ArgAction::SetFalse)
|
|
||||||
.help("Whether to display the position panel on the left."),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("display_offset")
|
|
||||||
.short('o')
|
|
||||||
.long("display-offset")
|
|
||||||
.num_args(1)
|
|
||||||
.value_name("N")
|
|
||||||
.help(
|
|
||||||
"Add N bytes to the displayed file position. The N argument can also \
|
|
||||||
include a unit (see `--length` for details)\n\
|
|
||||||
A negative value is valid and calculates an offset relative to the \
|
|
||||||
end of the file.",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("panels")
|
|
||||||
.long("panels")
|
|
||||||
.num_args(1)
|
|
||||||
.value_name("N")
|
|
||||||
.help(
|
|
||||||
"Sets the number of hex data panels to be displayed. \
|
|
||||||
`--panels=auto` will display the maximum number of hex data panels \
|
|
||||||
based on the current terminal width. By default, hexyl will show \
|
|
||||||
two panels, unless the terminal is not wide enough for that.",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("group_size")
|
|
||||||
.short('g')
|
|
||||||
.long("group-size")
|
|
||||||
.alias("groupsize")
|
|
||||||
.num_args(1)
|
|
||||||
.value_name("N")
|
|
||||||
.help(
|
|
||||||
"Number of bytes/octets that should be grouped together. \
|
|
||||||
Possible group sizes are 1, 2, 4, 8. The default is 1. You \
|
|
||||||
can use the '--endianness' option to control the ordering of \
|
|
||||||
the bytes within a group. '--groupsize' can be used as an \
|
|
||||||
alias (xxd-compatibility).",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("endianness")
|
|
||||||
.long("endianness")
|
|
||||||
.num_args(1)
|
|
||||||
.value_name("FORMAT")
|
|
||||||
.value_parser(["big", "little"])
|
|
||||||
.default_value("big")
|
|
||||||
.help(
|
|
||||||
"Whether to print out groups in little-endian or big-endian \
|
|
||||||
format. This option only has an effect if the '--group-size' \
|
|
||||||
is larger than 1. '-e' can be used as an alias for \
|
|
||||||
'--endianness=little'.",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("little_endian_format")
|
|
||||||
.short('e')
|
|
||||||
.action(ArgAction::SetTrue)
|
|
||||||
.overrides_with("endianness")
|
|
||||||
.hide(true)
|
|
||||||
.help("An alias for '--endianness=little'."),
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("base")
|
|
||||||
.short('b')
|
|
||||||
.long("base")
|
|
||||||
.num_args(1)
|
|
||||||
.value_name("B")
|
|
||||||
.help(
|
|
||||||
"Sets the base used for the bytes. The possible options are \
|
|
||||||
binary, octal, decimal, and hexadecimal. The default base \
|
|
||||||
is hexadecimal."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.arg(
|
|
||||||
Arg::new("terminal_width")
|
|
||||||
.long("terminal-width")
|
|
||||||
.num_args(1)
|
|
||||||
.value_name("N")
|
|
||||||
.conflicts_with("panels")
|
|
||||||
.help(
|
|
||||||
"Sets the number of terminal columns to be displayed.\nSince the terminal \
|
|
||||||
width may not be an evenly divisible by the width per hex data column, this \
|
|
||||||
will use the greatest number of hex data panels that can fit in the requested \
|
|
||||||
width but still leave some space to the right.\nCannot be used with other \
|
|
||||||
width-setting options.",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
let matches = command.get_matches();
|
/// Only read N bytes from the input.
|
||||||
|
///
|
||||||
|
/// The N argument can also include a unit with a decimal prefix (kB, MB,
|
||||||
|
/// ..) or binary prefix (kiB, MiB, ..), or can be specified using a hex
|
||||||
|
/// number. The short option '-l' can be used as an alias.
|
||||||
|
/// Examples: --length=64, --length=4KiB, --length=0xff
|
||||||
|
#[arg(
|
||||||
|
verbatim_doc_comment,
|
||||||
|
short('n'),
|
||||||
|
long,
|
||||||
|
visible_short_alias('c'),
|
||||||
|
visible_alias("bytes"),
|
||||||
|
short_alias('l'),
|
||||||
|
value_name("N")
|
||||||
|
)]
|
||||||
|
length: Option<String>,
|
||||||
|
|
||||||
|
/// Skip the first N bytes of the input.
|
||||||
|
///
|
||||||
|
/// The N argument can also include a unit (see `--length` for details).
|
||||||
|
/// A negative value is valid and will seek from the end of the file.
|
||||||
|
#[arg(verbatim_doc_comment, short, long, value_name("N"))]
|
||||||
|
skip: Option<String>,
|
||||||
|
|
||||||
|
/// Sets the size of the `block` unit to SIZE.
|
||||||
|
///
|
||||||
|
/// Examples: --block-size=1024, --block-size=4kB
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
default_value(formatcp!("{DEFAULT_BLOCK_SIZE}")),
|
||||||
|
value_name("SIZE")
|
||||||
|
)]
|
||||||
|
block_size: String,
|
||||||
|
|
||||||
|
/// Displays all input data.
|
||||||
|
///
|
||||||
|
/// Otherwise any number of groups of output lines which would be identical
|
||||||
|
/// to the preceding group of lines, are replaced with a line comprised of a
|
||||||
|
/// single asterisk.
|
||||||
|
#[arg(short('v'), long)]
|
||||||
|
no_squeezing: bool,
|
||||||
|
|
||||||
|
/// When to use colors.
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
value_enum,
|
||||||
|
default_value_t,
|
||||||
|
value_name("WHEN"),
|
||||||
|
default_value_if("plain", ArgPredicate::IsPresent, Some("never"))
|
||||||
|
)]
|
||||||
|
color: ColorWhen,
|
||||||
|
|
||||||
|
/// Whether to draw a border with Unicode characters, ASCII characters, or
|
||||||
|
/// none at all.
|
||||||
|
#[arg(
|
||||||
|
long,
|
||||||
|
value_enum,
|
||||||
|
default_value_t,
|
||||||
|
value_name("STYLE"),
|
||||||
|
default_value_if("plain", ArgPredicate::IsPresent, Some("none"))
|
||||||
|
)]
|
||||||
|
border: BorderStyle,
|
||||||
|
|
||||||
|
/// Display output with --no-characters, --no-position, --border=none, and
|
||||||
|
/// --color=never.
|
||||||
|
#[arg(short, long)]
|
||||||
|
plain: bool,
|
||||||
|
|
||||||
|
/// Do not show the character panel on the right.
|
||||||
|
#[arg(long)]
|
||||||
|
no_characters: bool,
|
||||||
|
|
||||||
|
/// Show the character panel on the right.
|
||||||
|
///
|
||||||
|
/// This is the default, unless --no-characters has been specified.
|
||||||
|
#[arg(
|
||||||
|
short('C'),
|
||||||
|
long,
|
||||||
|
action(ArgAction::SetTrue),
|
||||||
|
overrides_with("no_characters")
|
||||||
|
)]
|
||||||
|
characters: (),
|
||||||
|
|
||||||
|
/// Defines how bytes are mapped to characters.
|
||||||
|
#[arg(long, value_enum, default_value_t, value_name("FORMAT"))]
|
||||||
|
character_table: CharacterTable,
|
||||||
|
|
||||||
|
/// Whether to display the position panel on the left.
|
||||||
|
#[arg(short('P'), long)]
|
||||||
|
no_position: bool,
|
||||||
|
|
||||||
|
/// Add N bytes to the displayed file position.
|
||||||
|
///
|
||||||
|
/// The N argument can also include a unit (see `--length` for details).
|
||||||
|
/// A negative value is valid and calculates an offset relative to the end
|
||||||
|
/// of the file.
|
||||||
|
#[arg(
|
||||||
|
verbatim_doc_comment,
|
||||||
|
short('o'),
|
||||||
|
long,
|
||||||
|
default_value("0"),
|
||||||
|
value_name("N")
|
||||||
|
)]
|
||||||
|
display_offset: String,
|
||||||
|
|
||||||
|
/// Sets the number of hex data panels to be displayed.
|
||||||
|
///
|
||||||
|
/// `--panels=auto` will display the maximum number of hex data panels based
|
||||||
|
/// on the current terminal width. By default, hexyl will show two panels,
|
||||||
|
/// unless the terminal is not wide enough for that.
|
||||||
|
#[arg(long, value_name("N"))]
|
||||||
|
panels: Option<String>,
|
||||||
|
|
||||||
|
/// Number of bytes/octets that should be grouped together.
|
||||||
|
///
|
||||||
|
/// You can use the '--endianness' option to control the ordering of the
|
||||||
|
/// bytes within a group. '--groupsize' can be used as an alias
|
||||||
|
/// (xxd-compatibility).
|
||||||
|
#[arg(
|
||||||
|
short('g'),
|
||||||
|
long,
|
||||||
|
value_enum,
|
||||||
|
default_value_t,
|
||||||
|
alias("groupsize"),
|
||||||
|
value_name("N")
|
||||||
|
)]
|
||||||
|
group_size: GroupSize,
|
||||||
|
|
||||||
|
/// Whether to print out groups in little-endian or big-endian format.
|
||||||
|
///
|
||||||
|
/// This option only has an effect if the '--group-size' is larger than 1.
|
||||||
|
/// '-e' can be used as an alias for '--endianness=little'.
|
||||||
|
#[arg(long, value_enum, default_value_t, value_name("FORMAT"))]
|
||||||
|
endianness: Endianness,
|
||||||
|
|
||||||
|
/// An alias for '--endianness=little'.
|
||||||
|
#[arg(short('e'), hide(true), overrides_with("endianness"))]
|
||||||
|
little_endian_format: bool,
|
||||||
|
|
||||||
|
/// Sets the base used for the bytes.
|
||||||
|
///
|
||||||
|
/// The possible options are binary, octal, decimal, and hexadecimal.
|
||||||
|
#[arg(short('b'), long, default_value("hexadecimal"), value_name("B"))]
|
||||||
|
base: String,
|
||||||
|
|
||||||
|
/// Sets the number of terminal columns to be displayed.
|
||||||
|
///
|
||||||
|
/// Since the terminal width may not be an evenly divisible by the width per
|
||||||
|
/// hex data column, this will use the greatest number of hex data panels
|
||||||
|
/// that can fit in the requested width but still leave some space to the
|
||||||
|
/// right.
|
||||||
|
/// Cannot be used with other width-setting options.
|
||||||
|
#[arg(verbatim_doc_comment, long, value_name("N"), conflicts_with("panels"))]
|
||||||
|
terminal_width: Option<NonZeroU64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, ValueEnum)]
|
||||||
|
enum ColorWhen {
|
||||||
|
/// Always use colorized output.
|
||||||
|
#[default]
|
||||||
|
Always,
|
||||||
|
|
||||||
|
/// Only displays colors if the output goes to an interactive terminal.
|
||||||
|
Auto,
|
||||||
|
|
||||||
|
/// Do not use colorized output.
|
||||||
|
Never,
|
||||||
|
|
||||||
|
/// Override the NO_COLOR environment variable.
|
||||||
|
Force,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, ValueEnum)]
|
||||||
|
enum GroupSize {
|
||||||
|
/// Grouped together every byte/octet.
|
||||||
|
#[default]
|
||||||
|
#[value(name = "1")]
|
||||||
|
One,
|
||||||
|
|
||||||
|
/// Grouped together every 2 bytes/octets.
|
||||||
|
#[value(name = "2")]
|
||||||
|
Two,
|
||||||
|
|
||||||
|
/// Grouped together every 4 bytes/octets.
|
||||||
|
#[value(name = "4")]
|
||||||
|
Four,
|
||||||
|
|
||||||
|
/// Grouped together every 8 bytes/octets.
|
||||||
|
#[value(name = "8")]
|
||||||
|
Eight,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<GroupSize> for u8 {
|
||||||
|
fn from(number: GroupSize) -> Self {
|
||||||
|
match number {
|
||||||
|
GroupSize::One => 1,
|
||||||
|
GroupSize::Two => 2,
|
||||||
|
GroupSize::Four => 4,
|
||||||
|
GroupSize::Eight => 8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run() -> Result<()> {
|
||||||
|
let opt = Opt::parse();
|
||||||
|
|
||||||
let stdin = io::stdin();
|
let stdin = io::stdin();
|
||||||
|
|
||||||
let mut reader = match matches.get_one::<String>("FILE") {
|
let mut reader = match opt.file {
|
||||||
Some(filename) => Input::File(File::open(filename)?),
|
Some(filename) => Input::File(File::open(filename)?),
|
||||||
None => Input::Stdin(stdin.lock()),
|
None => Input::Stdin(stdin.lock()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let block_size = matches
|
if let Some(hex_number) = try_parse_as_hex_number(&opt.block_size) {
|
||||||
.get_one::<String>("block_size")
|
return hex_number
|
||||||
.map(|bs| {
|
.map_err(|e| anyhow!(e))
|
||||||
if let Some(hex_number) = try_parse_as_hex_number(bs) {
|
.and_then(|x| {
|
||||||
return hex_number.map_err(|e| anyhow!(e)).and_then(|x| {
|
PositiveI64::new(x).ok_or_else(|| anyhow!("block size argument must be positive"))
|
||||||
PositiveI64::new(x)
|
})
|
||||||
.ok_or_else(|| anyhow!("block size argument must be positive"))
|
.map(|_| ());
|
||||||
});
|
|
||||||
}
|
}
|
||||||
let (num, unit) = extract_num_and_unit_from(bs)?;
|
let (num, unit) = extract_num_and_unit_from(&opt.block_size)?;
|
||||||
if let Unit::Block { custom_size: _ } = unit {
|
if let Unit::Block { custom_size: _ } = unit {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"can not use 'block(s)' as a unit to specify block size"
|
"can not use 'block(s)' as a unit to specify block size"
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
num.checked_mul(unit.get_multiplier())
|
let block_size = num
|
||||||
|
.checked_mul(unit.get_multiplier())
|
||||||
.ok_or_else(|| anyhow!(ByteOffsetParseError::UnitMultiplicationOverflow))
|
.ok_or_else(|| anyhow!(ByteOffsetParseError::UnitMultiplicationOverflow))
|
||||||
.and_then(|x| {
|
.and_then(|x| {
|
||||||
PositiveI64::new(x)
|
PositiveI64::new(x).ok_or_else(|| anyhow!("block size argument must be positive"))
|
||||||
.ok_or_else(|| anyhow!("block size argument must be positive"))
|
})?;
|
||||||
})
|
|
||||||
})
|
|
||||||
.transpose()?
|
|
||||||
.unwrap_or_else(|| PositiveI64::new(DEFAULT_BLOCK_SIZE).unwrap());
|
|
||||||
|
|
||||||
let skip_arg = matches
|
let skip_arg = opt
|
||||||
.get_one::<String>("skip")
|
.skip
|
||||||
|
.as_ref()
|
||||||
.map(|s| {
|
.map(|s| {
|
||||||
parse_byte_offset(s, block_size).context(anyhow!(
|
parse_byte_offset(s, block_size).context(anyhow!(
|
||||||
"failed to parse `--skip` arg {:?} as byte count",
|
"failed to parse `--skip` arg {:?} as byte count",
|
||||||
@@ -325,29 +301,22 @@ fn run() -> Result<()> {
|
|||||||
.into())
|
.into())
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut reader = if let Some(length) = matches
|
let mut reader = if let Some(ref length) = opt.length {
|
||||||
.get_one::<String>("length")
|
let length = parse_byte_count(length).context(anyhow!(
|
||||||
.or_else(|| matches.get_one::<String>("bytes"))
|
|
||||||
.or_else(|| matches.get_one::<String>("count"))
|
|
||||||
.map(|s| {
|
|
||||||
parse_byte_count(s).context(anyhow!(
|
|
||||||
"failed to parse `--length` arg {:?} as byte count",
|
"failed to parse `--length` arg {:?} as byte count",
|
||||||
s
|
length
|
||||||
))
|
))?;
|
||||||
})
|
|
||||||
.transpose()?
|
|
||||||
{
|
|
||||||
Box::new(reader.take(length))
|
Box::new(reader.take(length))
|
||||||
} else {
|
} else {
|
||||||
reader.into_inner()
|
reader.into_inner()
|
||||||
};
|
};
|
||||||
|
|
||||||
let no_color = std::env::var_os("NO_COLOR").is_some();
|
let no_color = std::env::var_os("NO_COLOR").is_some();
|
||||||
let show_color = match matches.get_one::<String>("color").map(String::as_ref) {
|
let show_color = match opt.color {
|
||||||
Some("never") => false,
|
ColorWhen::Never => false,
|
||||||
Some("always") => !no_color,
|
ColorWhen::Always => !no_color,
|
||||||
Some("force") => true,
|
ColorWhen::Force => true,
|
||||||
_ => {
|
ColorWhen::Auto => {
|
||||||
if no_color {
|
if no_color {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
@@ -358,30 +327,18 @@ fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let border_style = match matches.get_one::<String>("border").map(String::as_ref) {
|
let border_style = opt.border;
|
||||||
Some("unicode") => BorderStyle::Unicode,
|
|
||||||
Some("ascii") => BorderStyle::Ascii,
|
|
||||||
_ => BorderStyle::None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let &squeeze = matches.get_one::<bool>("nosqueezing").unwrap_or(&true);
|
let &squeeze = &!opt.no_squeezing;
|
||||||
|
|
||||||
let show_char_panel = *matches.get_one::<bool>("no_chars").unwrap_or(&true)
|
let show_char_panel = !opt.no_characters && !opt.plain;
|
||||||
&& !matches.get_one::<bool>("plain").unwrap_or(&false);
|
|
||||||
|
|
||||||
let show_position_panel = *matches.get_one::<bool>("no_position").unwrap_or(&true)
|
let show_position_panel = !opt.no_position && !opt.plain;
|
||||||
&& !matches.get_one::<bool>("plain").unwrap_or(&false);
|
|
||||||
|
|
||||||
let display_offset: u64 = matches
|
let display_offset: u64 = parse_byte_count(&opt.display_offset).context(anyhow!(
|
||||||
.get_one::<String>("display_offset")
|
|
||||||
.map(|s| {
|
|
||||||
parse_byte_count(s).context(anyhow!(
|
|
||||||
"failed to parse `--display-offset` arg {:?} as byte count",
|
"failed to parse `--display-offset` arg {:?} as byte count",
|
||||||
s
|
opt.display_offset
|
||||||
))
|
))?;
|
||||||
})
|
|
||||||
.transpose()?
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
let max_panels_fn = |terminal_width: u64, base_digits: u64, group_size: u64| {
|
let max_panels_fn = |terminal_width: u64, base_digits: u64, group_size: u64| {
|
||||||
let offset = if show_position_panel { 10 } else { 1 };
|
let offset = if show_position_panel { 10 } else { 1 };
|
||||||
@@ -397,30 +354,27 @@ fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let base = if let Some(base) = matches.get_one::<String>("base")
|
let base = if let Ok(base_num) = opt.base.parse::<u8>() {
|
||||||
.map(|s| {
|
|
||||||
if let Ok(base_num) = s.parse::<u8>() {
|
|
||||||
match base_num {
|
match base_num {
|
||||||
2 => Ok(Base::Binary),
|
2 => Ok(Base::Binary),
|
||||||
8 => Ok(Base::Octal),
|
8 => Ok(Base::Octal),
|
||||||
10 => Ok(Base::Decimal),
|
10 => Ok(Base::Decimal),
|
||||||
16 => Ok(Base::Hexadecimal),
|
16 => Ok(Base::Hexadecimal),
|
||||||
_ => Err(anyhow!("The number provided is not a valid base. Valid bases are 2, 8, 10, and 16.")),
|
_ => Err(anyhow!(
|
||||||
|
"The number provided is not a valid base. Valid bases are 2, 8, 10, and 16."
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match s.as_str() {
|
match opt.base.as_str() {
|
||||||
"b" | "bin" | "binary" => Ok(Base::Binary),
|
"b" | "bin" | "binary" => Ok(Base::Binary),
|
||||||
"o" | "oct" | "octal" => Ok(Base::Octal),
|
"o" | "oct" | "octal" => Ok(Base::Octal),
|
||||||
"d" | "dec" | "decimal" => Ok(Base::Decimal),
|
"d" | "dec" | "decimal" => Ok(Base::Decimal),
|
||||||
"x" | "hex" | "hexadecimal" => Ok(Base::Hexadecimal),
|
"x" | "hex" | "hexadecimal" => Ok(Base::Hexadecimal),
|
||||||
_ => Err(anyhow!("The base provided is not valid. Valid bases are \"b\", \"o\", \"d\", and \"x\"."))
|
_ => Err(anyhow!(
|
||||||
|
"The base provided is not valid. Valid bases are \"b\", \"o\", \"d\", and \"x\"."
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}?;
|
||||||
}).transpose()? {
|
|
||||||
base
|
|
||||||
} else {
|
|
||||||
Base::Hexadecimal
|
|
||||||
};
|
|
||||||
|
|
||||||
let base_digits = match base {
|
let base_digits = match base {
|
||||||
Base::Binary => 8,
|
Base::Binary => 8,
|
||||||
@@ -429,53 +383,22 @@ fn run() -> Result<()> {
|
|||||||
Base::Hexadecimal => 2,
|
Base::Hexadecimal => 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
let group_size = if let Some(group_size) = matches
|
let group_size = u8::from(opt.group_size);
|
||||||
.get_one::<String>("group_size")
|
|
||||||
.map(|s| {
|
|
||||||
s.parse::<NonZeroU8>().map(u8::from).context(anyhow!(
|
|
||||||
"Failed to parse `--group-size`/`-g` argument {:?} as unsigned nonzero integer",
|
|
||||||
s
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.transpose()?
|
|
||||||
{
|
|
||||||
if (group_size <= 8) && ((group_size & (group_size - 1)) == 0) {
|
|
||||||
group_size
|
|
||||||
} else {
|
|
||||||
return Err(anyhow!(
|
|
||||||
"Possible sizes for the `--group-size`/`-g` option are 1, 2, 4 or 8. "
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
};
|
|
||||||
|
|
||||||
let terminal_width = terminal_size().map(|s| s.0 .0 as u64).unwrap_or(80);
|
let terminal_width = terminal_size().map(|s| s.0 .0 as u64).unwrap_or(80);
|
||||||
|
|
||||||
let panels = if matches.get_one::<String>("panels").map(String::as_ref) == Some("auto") {
|
let panels = if opt.panels.as_deref() == Some("auto") {
|
||||||
max_panels_fn(terminal_width, base_digits, group_size.into())
|
max_panels_fn(terminal_width, base_digits, group_size.into())
|
||||||
} else if let Some(panels) = matches
|
} else if let Some(panels) = opt.panels {
|
||||||
.get_one::<String>("panels")
|
|
||||||
.map(|s| {
|
|
||||||
s.parse::<NonZeroU64>().map(u64::from).context(anyhow!(
|
|
||||||
"failed to parse `--panels` arg {:?} as unsigned nonzero integer",
|
|
||||||
s
|
|
||||||
))
|
|
||||||
})
|
|
||||||
.transpose()?
|
|
||||||
{
|
|
||||||
panels
|
panels
|
||||||
} else if let Some(terminal_width) = matches
|
.parse::<NonZeroU64>()
|
||||||
.get_one::<String>("terminal_width")
|
.map(u64::from)
|
||||||
.map(|s| {
|
.context(anyhow!(
|
||||||
s.parse::<NonZeroU64>().map(u64::from).context(anyhow!(
|
"failed to parse `--panels` arg {:?} as unsigned nonzero integer",
|
||||||
"failed to parse `--terminal-width` arg {:?} as unsigned nonzero integer",
|
panels
|
||||||
s
|
))?
|
||||||
))
|
} else if let Some(terminal_width) = opt.terminal_width {
|
||||||
})
|
max_panels_fn(terminal_width.into(), base_digits, group_size.into())
|
||||||
.transpose()?
|
|
||||||
{
|
|
||||||
max_panels_fn(terminal_width, base_digits, group_size.into())
|
|
||||||
} else {
|
} else {
|
||||||
std::cmp::min(
|
std::cmp::min(
|
||||||
2,
|
2,
|
||||||
@@ -483,27 +406,13 @@ fn run() -> Result<()> {
|
|||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
let little_endian_format = *matches.get_one::<bool>("little_endian_format").unwrap();
|
let endianness = if opt.little_endian_format {
|
||||||
let endianness = matches.get_one::<String>("endianness");
|
Endianness::Little
|
||||||
let endianness = match (
|
} else {
|
||||||
endianness.map(|s| s.as_ref()).unwrap(),
|
opt.endianness
|
||||||
little_endian_format,
|
|
||||||
) {
|
|
||||||
(_, true) | ("little", _) => Endianness::Little,
|
|
||||||
("big", _) => Endianness::Big,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let character_table = match matches
|
let character_table = opt.character_table;
|
||||||
.get_one::<String>("character-table")
|
|
||||||
.unwrap()
|
|
||||||
.as_ref()
|
|
||||||
{
|
|
||||||
"default" => CharacterTable::Default,
|
|
||||||
"ascii" => CharacterTable::Ascii,
|
|
||||||
"codepage-437" => CharacterTable::CP437,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let stdout = io::stdout();
|
let stdout = io::stdout();
|
||||||
let mut stdout_lock = BufWriter::new(stdout.lock());
|
let mut stdout_lock = BufWriter::new(stdout.lock());
|
||||||
|
|||||||
Reference in new issue
Block a user