diff options
author | Julian Sitkevich <sitkevij@gmail.com> | 2020-04-20 22:15:40 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-20 22:15:40 -0700 |
commit | 8d531a39df1b63f4c700273b8d1a4322393f121d (patch) | |
tree | dcbccca4d2e8faec3b105e347bf5cd6bac16f1cd | |
parent | 66f0a6dab366c5d8f143fcaf3dcd8ec9edde0479 (diff) | |
parent | dcfebef40ffc79192f2fa93dc71c54a4b3e2a083 (diff) |
Merge pull request #18 from sitkevij/feature/stdin-supportv0.3.0
Feature/stdin support
-rw-r--r-- | Cargo.lock | 4 | ||||
-rw-r--r-- | Cargo.toml | 3 | ||||
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | README.md | 30 | ||||
-rw-r--r-- | src/lib.rs | 239 | ||||
-rw-r--r-- | src/main.rs | 53 |
6 files changed, 208 insertions, 124 deletions
@@ -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)", @@ -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] @@ -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 . @@ -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 ``` @@ -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(_) => { |