From f2cec4c89ebafb90b4c5ed62479a182e529260f0 Mon Sep 17 00:00:00 2001 From: Joey Hines Date: Sat, 7 May 2022 18:35:06 -0600 Subject: [PATCH] Added file input + stdin support + Files/stdin input can be binary or ascii + Handles piping from other programs + Misc test fixes + Updated readme + clippy + fmt --- README.md | 4 +- formats/example.toml | 1 - src/formatter/format.rs | 42 +++++++++++++++++++ src/formatter/mod.rs | 8 ++-- src/main.rs | 15 ++++++- src/parser/mod.rs | 93 ++++++++++++++++++++++++++++++++++++----- 6 files changed, 146 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 9b27205..0b38617 100644 --- a/README.md +++ b/README.md @@ -64,16 +64,18 @@ Formaty 0.1.0 Arbitrary Binary Data Formatting USAGE: - formaty [OPTIONS] [data]... + formaty [FLAGS] [OPTIONS] [data]... FLAGS: -h, --help Prints help information + -s, --stdin Input data from stdin -V, --version Prints version information OPTIONS: -b, --base Base of the input values -c, --config Path to the format config [env: FORMATY_CONFIG=] -g, --global_config Path to the global config directory [env: FORMATY_GLOBAL_CONFIG=] + -f, --file Input data from file -i, --input_type Input data type [default: array] ARGS: diff --git a/formats/example.toml b/formats/example.toml index 3c9eb18..ad5df86 100644 --- a/formats/example.toml +++ b/formats/example.toml @@ -43,7 +43,6 @@ field_type = {type = "Float", endianness = "BigEndian"} # string field. Printed as a byte array [[formats.fields]] name = "string field" -print_type = {print = "ByteArray"} field_type = {type = "String", endianness = "BigEndian", max_len = 55} # byte field diff --git a/src/formatter/format.rs b/src/formatter/format.rs index 1451a92..bedd8ab 100644 --- a/src/formatter/format.rs +++ b/src/formatter/format.rs @@ -611,4 +611,46 @@ mod tests { assert_eq!(width, 16); assert_eq!(output, "[222, 173]") } + + #[test] + fn test_parse_str_big_endian() { + let field = Field { + field_type: FieldType::String { + max_len: 64, + endianness: Endianness::BigEndian, + }, + name: "test".to_string(), + bit_flip: None, + print_type: Default::default(), + }; + + let src_str = b"Test\x00abcdefg"; + let mut byte_stream = ByteStream::from(src_str.to_vec()); + + let (output, width) = field.format_data(&mut byte_stream, 0).unwrap(); + + assert_eq!(width, 40); + assert_eq!(output, "Test") + } + + #[test] + fn test_parse_str_little_endian() { + let field = Field { + field_type: FieldType::String { + max_len: 64, + endianness: Endianness::LittleEndian, + }, + name: "test".to_string(), + bit_flip: None, + print_type: Default::default(), + }; + + let src_str = b"Test\x00abcdefg"; + let mut byte_stream = ByteStream::from(src_str.to_vec()); + + let (output, width) = field.format_data(&mut byte_stream, 0).unwrap(); + + assert_eq!(width, 40); + assert_eq!(output, "Test") + } } diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs index ce70573..44c0f5e 100644 --- a/src/formatter/mod.rs +++ b/src/formatter/mod.rs @@ -20,7 +20,7 @@ pub enum FormatConfigError { file_name: String, err: toml::de::Error, }, - Utf8Error(std::str::Utf8Error), + Utf8Error(str::Utf8Error), FormatNotFound(String), } @@ -32,8 +32,8 @@ impl From for FormatConfigError { } } -impl From for FormatConfigError { - fn from(e: std::str::Utf8Error) -> Self { +impl From for FormatConfigError { + fn from(e: str::Utf8Error) -> Self { Self::Utf8Error(e) } } @@ -266,7 +266,7 @@ mod test { } fn get_ccsds_format() -> Format { - let format_config = FormatConfig::new(&None).unwrap(); + let format_config = FormatConfig::new(&None, &None).unwrap(); format_config.get_format("ccsds").unwrap() } diff --git a/src/main.rs b/src/main.rs index 6b98ac4..038c7a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,6 +42,12 @@ pub struct Args { #[structopt(help = "Base of the input values", short = "b", long = "base")] base: Option, + #[structopt(help = "Input data from file", short = "f", long = "file")] + input_file: Option, + + #[structopt(help = "Input data from stdin", short = "s", long = "stdin")] + input_stdin: bool, + #[structopt(help = "Format to parse data as")] format: String, @@ -56,7 +62,14 @@ fn init() -> Result<(Vec, Format), FormatyError> { let format = config.get_format(&args.format)?; - let data = args.input_type.parse_input(args.data, args.base)?; + let data = if args.input_stdin { + args.input_type.parse_stdin(args.base)? + } else if let Some(input_file) = args.input_file { + args.input_type.parse_file(&input_file, args.base)? + } else { + args.input_type.parse_arg_input(args.data, args.base)? + }; + Ok((data, format)) } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index accd6b8..fd41f6d 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,12 +1,16 @@ use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; +use std::io::{BufRead, BufReader, Error, Read}; use std::num::ParseIntError; +use std::path::Path; use std::str::FromStr; +use std::string::FromUtf8Error; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum InputTypes { Array, String, + Binary, } impl FromStr for InputTypes { @@ -18,6 +22,7 @@ impl FromStr for InputTypes { match s.as_str() { "array" | "a" => Ok(Self::Array), "string" | "s" => Ok(Self::String), + "binary" | "b" => Ok(Self::Binary), _ => Err(format!("Invalid input type '{}'", s)), } } @@ -55,7 +60,7 @@ impl InputTypes { .collect() } - pub fn parse_input( + pub fn parse_arg_input( &self, src: Vec, base: Option, @@ -79,16 +84,67 @@ impl InputTypes { let data = match self { InputTypes::Array => Self::parse_array(str_arr, base)?, InputTypes::String => str_arr.join(" ").as_bytes().to_vec(), + InputTypes::Binary => return Err(ByteArrayParseErr::UnsupportedFormat), }; Ok(data) } + + #[allow(clippy::unused_io_amount)] + fn parse_data_from_file( + &self, + mut buf_reader: Box, + base: Option, + ) -> Result, ByteArrayParseErr> { + let mut data: Vec = Vec::new(); + let mut done = false; + + while !done { + let mut buf: Vec = vec![0; 2048]; + let len = buf_reader.read(&mut buf)?; + + if len < buf.len() { + buf.truncate(len); + data.append(&mut buf); + done = true; + } else if len > 0 { + data.append(&mut buf); + } else { + done = true; + } + } + + match self { + InputTypes::Array | InputTypes::String => { + let str_data = String::from_utf8(data)?; + self.parse_arg_input(vec![str_data], base) + } + InputTypes::Binary => Ok(data), + } + } + + pub fn parse_file( + &self, + file_path: &Path, + base: Option, + ) -> Result, ByteArrayParseErr> { + let reader = Box::new(BufReader::new(std::fs::File::open(file_path)?)); + self.parse_data_from_file(reader, base) + } + + pub fn parse_stdin(&self, base: Option) -> Result, ByteArrayParseErr> { + let reader = Box::new(BufReader::new(std::io::stdin())); + self.parse_data_from_file(reader, base) + } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum ByteArrayParseErr { EmptySrcArray, ParseIntError(ParseIntError), + UnsupportedFormat, + FileError(std::io::Error), + ParseStringError(FromUtf8Error), } impl From for ByteArrayParseErr { @@ -97,11 +153,28 @@ impl From for ByteArrayParseErr { } } +impl From for ByteArrayParseErr { + fn from(e: Error) -> Self { + Self::FileError(e) + } +} + +impl From for ByteArrayParseErr { + fn from(e: FromUtf8Error) -> Self { + Self::ParseStringError(e) + } +} + impl Display for ByteArrayParseErr { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let err_msg = match self { ByteArrayParseErr::EmptySrcArray => "Provided array is empty!".to_string(), ByteArrayParseErr::ParseIntError(e) => format!("Failed to parse as int: {}", e), + ByteArrayParseErr::UnsupportedFormat => { + "Format is not support with the supplied input type".to_string() + } + ByteArrayParseErr::FileError(e) => format!("Unable to parse file: {}", e), + ByteArrayParseErr::ParseStringError(e) => format!("Unable to parse string: {}", e), }; write!(f, "{}", err_msg) @@ -116,7 +189,7 @@ mod tests { fn parse_base_10_array() { let array = vec!["[0, 1, 2, 3, 4]".to_string()]; - let out = InputTypes::Array.parse_input(array, None).unwrap(); + let out = InputTypes::Array.parse_arg_input(array, None).unwrap(); assert_eq!(out, vec![0, 1, 2, 3, 4]) } @@ -125,7 +198,7 @@ mod tests { fn parse_base_2_array() { let array = vec!["[0b0, 0b1, 0b10, 0b11, 0b100]".to_string()]; - let out = InputTypes::Array.parse_input(array, None).unwrap(); + let out = InputTypes::Array.parse_arg_input(array, None).unwrap(); assert_eq!(out, vec![0, 1, 2, 3, 4]) } @@ -134,7 +207,7 @@ mod tests { fn parse_base_8_array() { let array = vec!["[00, 01, 02, 03, 04]".to_string()]; - let out = InputTypes::Array.parse_input(array, None).unwrap(); + let out = InputTypes::Array.parse_arg_input(array, None).unwrap(); assert_eq!(out, vec![0, 1, 2, 3, 4]) } @@ -143,7 +216,7 @@ mod tests { fn parse_base_16_array() { let array = vec!["[0x0, 0x1, 0x2, h3, H4]".to_string()]; - let out = InputTypes::Array.parse_input(array, None).unwrap(); + let out = InputTypes::Array.parse_arg_input(array, None).unwrap(); assert_eq!(out, vec![0, 1, 2, 3, 4]) } @@ -152,7 +225,7 @@ mod tests { fn parse_base_mixed() { let array = vec!["[00 0x1, 2, h3, H4]".to_string()]; - let out = InputTypes::Array.parse_input(array, None).unwrap(); + let out = InputTypes::Array.parse_arg_input(array, None).unwrap(); assert_eq!(out, vec![0, 1, 2, 3, 4]) } @@ -166,7 +239,7 @@ mod tests { "3".to_string(), "4".to_string(), ]; - let out = InputTypes::Array.parse_input(array, None).unwrap(); + let out = InputTypes::Array.parse_arg_input(array, None).unwrap(); assert_eq!(out, vec![0, 1, 2, 3, 4]) } @@ -174,7 +247,7 @@ mod tests { #[test] fn parse_no_braces_one_arg() { let array = vec!["0 1 2 3 4".to_string()]; - let out = InputTypes::Array.parse_input(array, None).unwrap(); + let out = InputTypes::Array.parse_arg_input(array, None).unwrap(); assert_eq!(out, vec![0, 1, 2, 3, 4]) } @@ -184,7 +257,7 @@ mod tests { let string = "Hello World!"; let out = InputTypes::String - .parse_input(vec![string.to_string()], None) + .parse_arg_input(vec![string.to_string()], None) .unwrap(); assert_eq!(string.as_bytes(), out)