summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulian Sitkevich <sitkevij@gmail.com>2020-04-20 22:15:40 -0700
committerGitHub <noreply@github.com>2020-04-20 22:15:40 -0700
commit8d531a39df1b63f4c700273b8d1a4322393f121d (patch)
treedcbccca4d2e8faec3b105e347bf5cd6bac16f1cd
parent66f0a6dab366c5d8f143fcaf3dcd8ec9edde0479 (diff)
parentdcfebef40ffc79192f2fa93dc71c54a4b3e2a083 (diff)
Merge pull request #18 from sitkevij/feature/stdin-supportv0.3.0
Feature/stdin support
-rw-r--r--Cargo.lock4
-rw-r--r--Cargo.toml3
-rw-r--r--Makefile3
-rw-r--r--README.md30
-rw-r--r--src/lib.rs239
-rw-r--r--src/main.rs53
6 files changed, 208 insertions, 124 deletions
diff --git a/Cargo.lock b/Cargo.lock
index e829e86..5c49a10 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,3 +1,5 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
[[package]]
name = "ansi_term"
version = "0.11.0"
@@ -37,7 +39,7 @@ dependencies = [
[[package]]
name = "hx"
-version = "0.2.1"
+version = "0.3.0"
dependencies = [
"ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)",
diff --git a/Cargo.toml b/Cargo.toml
index 7342a13..a1c0727 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,7 +7,8 @@ repository = "https://github.com/sitkevij/hex"
keywords = ["hexdump", "hexadecimal", "tools", "ascii", "hex"]
license = "MIT"
name = "hx"
-version = "0.2.1"
+version = "0.3.0"
+edition = "2018"
# see https://doc.rust-lang.org/cargo/reference/manifest.html
[badges]
diff --git a/Makefile b/Makefile
index 1f77742..69fd7a6 100644
--- a/Makefile
+++ b/Makefile
@@ -32,6 +32,9 @@ install: release debug test
install-force: clean release debug test
cargo install --path . --force
+clippy:
+ cargo clippy
+
docker:
docker build -t sitkevij/stretch-slim:$(BINARY)-0.2.0 .
diff --git a/README.md b/README.md
index 2c92cbf..b0de9bf 100644
--- a/README.md
+++ b/README.md
@@ -2,16 +2,30 @@
Futuristic take on hexdump.
-`hex` takes a file as input and outputs a hexadecimal colorized view to stdout.
+`hex` accepts a file path as input and outputs a hexadecimal colorized view to stdout.
```
-$ hx -c12 tests/files/alphanumeric.txt
-0x000000: 0x61 0x62 0x63 0x64 0x65 0x66 0x67 0x68 0x69 0x6a 0x6b 0x69 abcdefghijki
-0x00000c: 0x6c 0x6d 0x6e 0x6f 0x70 0x71 0x72 0x73 0x74 0x75 0x76 0x77 lmnopqrstuvw
-0x000018: 0x78 0x79 0x7a 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 xyz012345678
-0x000024: 0x39 0x0a 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 9.0123456789
-0x000030: 0x30 0x31 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x30 0x31 012345678901
-0x00003c: 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 23456789
+$ hx tests/files/alphanumeric.txt
+0x000000: 0x61 0x62 0x63 0x64 0x65 0x66 0x67 0x68 0x69 0x6a abcdefghij
+0x00000a: 0x6b 0x69 0x6c 0x6d 0x6e 0x6f 0x70 0x71 0x72 0x73 kilmnopqrs
+0x000014: 0x74 0x75 0x76 0x77 0x78 0x79 0x7a 0x30 0x31 0x32 tuvwxyz012
+0x00001e: 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x0a 0x30 0x31 3456789.01
+0x000028: 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x30 0x31 2345678901
+0x000032: 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x30 0x31 2345678901
+0x00003c: 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 23456789
+ bytes: 68
+```
+
+`hex` also accepts stdin as input.
+```
+cat "tests/files/alphanumeric.txt" | hx
+0x000000: 0x61 0x62 0x63 0x64 0x65 0x66 0x67 0x68 0x69 0x6a abcdefghij
+0x00000a: 0x6b 0x69 0x6c 0x6d 0x6e 0x6f 0x70 0x71 0x72 0x73 kilmnopqrs
+0x000014: 0x74 0x75 0x76 0x77 0x78 0x79 0x7a 0x30 0x31 0x32 tuvwxyz012
+0x00001e: 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x0a 0x30 0x31 3456789.01
+0x000028: 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x30 0x31 2345678901
+0x000032: 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 0x30 0x31 2345678901
+0x00003c: 0x32 0x33 0x34 0x35 0x36 0x37 0x38 0x39 23456789
bytes: 68
```
diff --git a/src/lib.rs b/src/lib.rs
index c3798c6..3fb1e71 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,5 @@
#![deny(
+ dead_code,
missing_docs,
missing_debug_implementations,
missing_copy_implementations,
@@ -9,18 +10,41 @@
unused_import_braces,
unused_qualifications
)]
-// #![allow(dead_code)]
//! general hex lib
extern crate ansi_term;
extern crate clap;
use clap::ArgMatches;
+use std::env;
+use std::error::Error;
use std::f64;
use std::fs;
-use std::fs::File;
use std::io::BufReader;
-use std::io::Read;
+use std::io::{self, BufRead, Read};
+
+/// arg cols
+pub const ARG_COL: &str = "cols";
+/// arg len
+pub const ARG_LEN: &str = "len";
+/// arg format
+pub const ARG_FMT: &str = "format";
+/// arg INPUTFILE
+pub const ARG_INP: &str = "INPUTFILE";
+/// arg color
+pub const ARG_CLR: &str = "color";
+/// arg array
+pub const ARG_ARR: &str = "array";
+/// arg func
+pub const ARG_FNC: &str = "func";
+/// arg places
+pub const ARG_PLC: &str = "places";
+
+const ARGS: [&str; 8] = [
+ ARG_COL, ARG_LEN, ARG_FMT, ARG_INP, ARG_CLR, ARG_ARR, ARG_FNC, ARG_PLC,
+];
+
+const DBG: u8 = 0x0;
/// nothing ⇒ Display
/// ? ⇒ Debug
@@ -180,29 +204,9 @@ pub fn print_byte(b: u8, format: Format, colorize: bool) {
}
}
-/// Function wave out.
-/// # Arguments
-///
-/// * `len` - Wave length.
-/// * `places` - Number of decimal places for function wave floats.
-pub fn func_out(len: u64, places: usize) {
- for y in 0..len {
- let y_float: f64 = y as f64;
- let len_float: f64 = len as f64;
- let x: f64 = (((y_float / len_float) * f64::consts::PI) / 2.0).sin();
- let formatted_number = format!("{:.*}", places, x);
- print!("{}", formatted_number);
- print!(",");
- if (y % 10) == 9 {
- println!();
- }
- }
- println!();
-}
-
/// In most hex editor applications, the data of the computer file is
-/// represented as hexadecimal values grouped in 4 groups of 4 bytes
-/// (or two groups of 8 bytes), followed by one group of 16 printable ASCII
+/// represented as hexadecimal values grouped in 4 groups of 4 bytes (or
+/// two groups of 8 bytes), followed by one group of 16 printable ASCII
/// characters which correspond to each pair of hex values (each byte).
/// Non-printable ASCII characters (e.g., Bell) and characters that would take
/// more than one character space (e.g., tab) are typically represented by a
@@ -211,30 +215,41 @@ pub fn func_out(len: u64, places: usize) {
/// # Arguments
///
/// * `matches` - Argument matches from command line.
-pub fn run(matches: ArgMatches) -> Result<(), Box<::std::error::Error>> {
+pub fn run(matches: ArgMatches) -> Result<(), Box<dyn Error>> {
let mut column_width: u64 = 10;
+ let mut truncate_len: u64 = 0x0;
if let Some(len) = matches.value_of("func") {
let mut p: usize = 4;
if let Some(places) = matches.value_of("places") {
p = places.parse::<usize>().unwrap();
}
- func_out(len.parse::<u64>().unwrap(), p);
- } else if let Some(file) = matches.value_of("INPUTFILE") {
- let f = File::open(file).unwrap();
- let mut buf_len = fs::metadata(file)?.len();
- let mut buf = BufReader::new(f);
+ output_function(len.parse::<u64>().unwrap(), p);
+ } else {
+ // cases:
+ // $ cat Cargo.toml | target/debug/hx
+ // $ cat Cargo.toml | target/debug/hx -a r
+ // $ target/debug/hx Cargo.toml
+ // $ target/debug/hx Cargo.toml -a r
+ let is_stdin = is_stdin(matches.clone());
+ let mut buf: Box<dyn BufRead> = if is_stdin.unwrap() {
+ Box::new(BufReader::new(io::stdin()))
+ } else {
+ Box::new(BufReader::new(
+ fs::File::open(matches.value_of(ARG_INP).unwrap()).unwrap(),
+ ))
+ };
let mut format_out = Format::LowerHex;
let mut colorize = true;
- if let Some(columns) = matches.value_of("cols") {
+ if let Some(columns) = matches.value_of(ARG_COL) {
column_width = columns.parse::<u64>().unwrap(); //turbofish
}
- if let Some(length) = matches.value_of("len") {
- buf_len = length.parse::<u64>().unwrap();
+ if let Some(length) = matches.value_of(ARG_LEN) {
+ truncate_len = length.parse::<u64>().unwrap();
}
- if let Some(format) = matches.value_of("format") {
+ if let Some(format) = matches.value_of(ARG_FMT) {
// o, x, X, p, b, e, E
match format {
"o" => format_out = Format::Octal,
@@ -248,7 +263,7 @@ pub fn run(matches: ArgMatches) -> Result<(), Box<::std::error::Error>> {
}
}
- if let Some(color) = matches.value_of("color") {
+ if let Some(color) = matches.value_of(ARG_CLR) {
let color_v = color.parse::<u8>().unwrap();
if color_v == 1 {
colorize = true;
@@ -257,54 +272,21 @@ pub fn run(matches: ArgMatches) -> Result<(), Box<::std::error::Error>> {
}
}
- match matches.occurrences_of("v") {
- 0 => print!(""),
- 1 => println!("verbose 1"),
- 2 => println!("verbose 2"),
- 3 | _ => println!("verbose max"),
- }
-
// array output mode is mutually exclusive
- if let Some(array) = matches.value_of("array") {
- let mut array_format = array;
- let mut page = buf_to_array(&mut buf, buf_len, column_width).unwrap();
- match array_format {
- "r" => println!("let ARRAY: [u8; {}] = [", page.bytes),
- "c" => println!("unsigned char ARRAY[{}] = {{", page.bytes),
- "g" => println!("a := [{}]byte{{", page.bytes),
- _ => println!("unknown array format"),
- }
-
- let mut i: u64 = 0x0;
- for line in page.body.iter() {
- print!(" ");
- for hex in line.hex_body.iter() {
- i += 1;
- if i == buf_len && array_format != "g" {
- print!("{}", hex_lower_hex(*hex));
- } else {
- print!("{}, ", hex_lower_hex(*hex));
- }
- }
- println!();
- }
- match array_format {
- "r" => println!("];"),
- "c" => println!("}};"),
- "g" => println!("}}"),
- _ => println!("unknown array format"),
- }
+ if let Some(array) = matches.value_of(ARG_ARR) {
+ output_array(array, buf, truncate_len, column_width);
} else {
// Transforms this Read instance to an Iterator over its bytes.
// The returned type implements Iterator where the Item is
// Result<u8, R::Err>. The yielded item is Ok if a byte was
- // successfully read and Err otherwise for I/O errors. EOF is mapped
- // to returning None from this iterator.
+ // successfully read and Err otherwise for I/O errors. EOF is
+ // mapped to returning None from this iterator.
// (https://doc.rust-lang.org/1.16.0/std/io/trait.Read.html#method.bytes)
let mut ascii_line: Line = Line::new();
let mut offset_counter: u64 = 0x0;
let mut byte_column: u64 = 0x0;
- let mut page = buf_to_array(&mut buf, buf_len, column_width).unwrap();
+ let page = buf_to_array(&mut buf, truncate_len, column_width).unwrap();
+
for line in page.body.iter() {
print_offset(offset_counter);
@@ -338,18 +320,114 @@ pub fn run(matches: ArgMatches) -> Result<(), Box<::std::error::Error>> {
Ok(())
}
+/// Detect stdin, file path and/or parameters.
+/// # Arguments
+///
+/// * `matches` - argument matches.
+pub fn is_stdin(matches: ArgMatches) -> Result<bool, Box<dyn Error>> {
+ let mut is_stdin = false;
+ if DBG > 0 {
+ dbg!(env::args().len(), matches.args.len());
+ dbg!(env::args().nth(0).unwrap());
+ }
+ if let Some(nth1) = env::args().nth(1) {
+ if DBG > 0 {
+ dbg!(nth1);
+ }
+ for arg in ARGS.iter() {
+ if let Some(index) = matches.index_of(arg) {
+ if let 2 = index {
+ is_stdin = true;
+ }
+ }
+ }
+ } else if matches.args.is_empty() {
+ is_stdin = true;
+ } else if let Some(file) = matches.value_of(ARG_INP) {
+ if DBG > 0 {
+ dbg!(file);
+ }
+ is_stdin = false;
+ }
+ if DBG > 0 {
+ dbg!(is_stdin);
+ }
+ Ok(is_stdin)
+}
+
+/// Output source code array format.
+/// # Arguments
+///
+/// * `array_format` - array format, rust (r), C (c), golang (g).
+/// * `buf` - BufRead.
+/// * `truncate_len` - truncate to length.
+/// * `column_width` - column width.
+pub fn output_array(
+ array_format: &str,
+ mut buf: Box<dyn BufRead>,
+ truncate_len: u64,
+ column_width: u64,
+) {
+ let page = buf_to_array(&mut buf, truncate_len, column_width).unwrap();
+ match array_format {
+ "r" => println!("let ARRAY: [u8; {}] = [", page.bytes),
+ "c" => println!("unsigned char ARRAY[{}] = {{", page.bytes),
+ "g" => println!("a := [{}]byte{{", page.bytes),
+ _ => println!("unknown array format"),
+ }
+ let mut i: u64 = 0x0;
+ for line in page.body.iter() {
+ print!(" ");
+ for hex in line.hex_body.iter() {
+ i += 1;
+ if i == page.bytes && array_format != "g" {
+ print!("{}", hex_lower_hex(*hex));
+ } else {
+ print!("{}, ", hex_lower_hex(*hex));
+ }
+ }
+ println!();
+ }
+ match array_format {
+ "r" => println!("];"),
+ "c" => println!("}};"),
+ "g" => println!("}}"),
+ _ => println!("unknown array format"),
+ }
+}
+
+/// Function wave out.
+/// # Arguments
+///
+/// * `len` - Wave length.
+/// * `places` - Number of decimal places for function wave floats.
+pub fn output_function(len: u64, places: usize) {
+ for y in 0..len {
+ let y_float: f64 = y as f64;
+ let len_float: f64 = len as f64;
+ let x: f64 = (((y_float / len_float) * f64::consts::PI) / 2.0).sin();
+ let formatted_number = format!("{:.*}", places, x);
+ print!("{}", formatted_number);
+ print!(",");
+ if (y % 10) == 9 {
+ println!();
+ }
+ }
+ println!();
+}
+
/// Buffer to array.
///
/// # Arguments
///
/// * `buf` - Buffer to be read.
-/// * `buf_len` - Buffer length.
+/// * `buf_len` - force buffer length.
/// * `column_width` - column width for output.
pub fn buf_to_array(
- buf: &mut Read,
+ buf: &mut dyn Read,
buf_len: u64,
column_width: u64,
-) -> Result<Page, Box<::std::error::Error>> {
+) -> Result<Page, Box<dyn ::std::error::Error>> {
let mut column_count: u64 = 0x0;
let max_array_size: u16 = <u16>::max_value(); // 2^16;
let mut page: Page = Page::new();
@@ -366,11 +444,12 @@ pub fn buf_to_array(
line = Line::new();
column_count = 0;
}
- if page.bytes == buf_len || u64::from(max_array_size) == buf_len {
- page.body.push(line);
+
+ if buf_len > 0 && (page.bytes == buf_len || u64::from(max_array_size) == buf_len) {
break;
}
}
+ page.body.push(line);
Ok(page)
}
diff --git a/src/main.rs b/src/main.rs
index 9ba3885..d008aba 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,6 +1,7 @@
extern crate clap;
mod lib;
use clap::{App, Arg};
+use lib::{ARG_ARR, ARG_CLR, ARG_COL, ARG_FMT, ARG_FNC, ARG_INP, ARG_LEN, ARG_PLC};
use std::env;
use std::process;
@@ -10,90 +11,74 @@ fn main() {
"{}\n{}",
env!("CARGO_PKG_DESCRIPTION"),
env!("CARGO_PKG_HOMEPAGE")
- )
- .to_string();
+ );
let app = App::new(env!("CARGO_PKG_NAME"))
.version(env!("CARGO_PKG_VERSION"))
.about(desc)
.arg(
- Arg::with_name("cols")
+ Arg::with_name(ARG_COL)
.short("c")
- .long("cols")
+ .long(ARG_COL)
.value_name("columns")
.help("Set column length")
.takes_value(true),
)
.arg(
- Arg::with_name("len")
+ Arg::with_name(ARG_LEN)
.short("l")
- .long("len")
- .value_name("len")
+ .long(ARG_LEN)
+ .value_name(ARG_LEN)
.help("Set <len> bytes to read")
.takes_value(true),
)
.arg(
- Arg::with_name("format")
+ Arg::with_name(ARG_FMT)
.short("f")
- .long("format")
+ .long(ARG_FMT)
.help("Set format of octet: Octal (o), LowerHex (x), UpperHex (X), Binary (b)")
.possible_values(&["o", "x", "X", "b"])
.takes_value(true),
)
.arg(
- Arg::with_name("INPUTFILE")
+ Arg::with_name(ARG_INP)
.help("Pass file path as an argument for hex dump")
- .required(true)
+ .required(false)
.index(1),
)
.arg(
- Arg::with_name("v")
- .short("v")
- .multiple(true)
- .help("Sets verbosity level"),
- )
- .arg(
- Arg::with_name("color")
+ Arg::with_name(ARG_CLR)
.short("t")
- .long("color")
+ .long(ARG_CLR)
.help("Set color tint terminal output. 0 to disable, 1 to enable")
- .default_value("1")
.possible_values(&["0", "1"])
.takes_value(true),
)
.arg(
- Arg::with_name("array")
+ Arg::with_name(ARG_ARR)
.short("a")
- .long("array")
+ .long(ARG_ARR)
.value_name("array_format")
.help("Set source code format output: rust (r), C (c), golang (g)")
.possible_values(&["r", "c", "g"])
.takes_value(true),
)
.arg(
- Arg::with_name("func")
+ Arg::with_name(ARG_FNC)
.short("u")
- .long("func")
+ .long(ARG_FNC)
.value_name("func_length")
.help("Set function wave length")
.takes_value(true),
)
.arg(
- Arg::with_name("places")
+ Arg::with_name(ARG_PLC)
.short("p")
- .long("places")
+ .long(ARG_PLC)
.value_name("func_places")
.help("Set function wave output decimal places")
.takes_value(true),
);
- let args: Vec<_> = env::args().collect();
- if args.len() == 1 {
- app.clone().print_help().unwrap();
- println!();
- println!();
- process::exit(0);
- }
-
let matches = app.get_matches();
match lib::run(matches) {
Ok(_) => {