Added file input + stdin support
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details

+ Files/stdin input can be binary or ascii
+ Handles piping from other programs
+ Misc test fixes
+ Updated readme
+ clippy + fmt
main
Joey Hines 2022-05-07 18:35:06 -06:00
parent 287ba32ea7
commit f2cec4c89e
No known key found for this signature in database
GPG Key ID: 80F567B5C968F91B
6 changed files with 146 additions and 17 deletions

View File

@ -64,16 +64,18 @@ Formaty 0.1.0
Arbitrary Binary Data Formatting
USAGE:
formaty [OPTIONS] <format> [data]...
formaty [FLAGS] [OPTIONS] <format> [data]...
FLAGS:
-h, --help Prints help information
-s, --stdin Input data from stdin
-V, --version Prints version information
OPTIONS:
-b, --base <base> Base of the input values
-c, --config <config> Path to the format config [env: FORMATY_CONFIG=]
-g, --global_config <global-config> Path to the global config directory [env: FORMATY_GLOBAL_CONFIG=]
-f, --file <input-file> Input data from file
-i, --input_type <input-type> Input data type [default: array]
ARGS:

View File

@ -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

View File

@ -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")
}
}

View File

@ -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<std::io::Error> for FormatConfigError {
}
}
impl From<std::str::Utf8Error> for FormatConfigError {
fn from(e: std::str::Utf8Error) -> Self {
impl From<str::Utf8Error> 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()
}

View File

@ -42,6 +42,12 @@ pub struct Args {
#[structopt(help = "Base of the input values", short = "b", long = "base")]
base: Option<u32>,
#[structopt(help = "Input data from file", short = "f", long = "file")]
input_file: Option<PathBuf>,
#[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<u8>, 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))
}

View File

@ -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<String>,
base: Option<u32>,
@ -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<dyn BufRead>,
base: Option<u32>,
) -> Result<Vec<u8>, ByteArrayParseErr> {
let mut data: Vec<u8> = Vec::new();
let mut done = false;
while !done {
let mut buf: Vec<u8> = 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<u32>,
) -> Result<Vec<u8>, 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<u32>) -> Result<Vec<u8>, 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<ParseIntError> for ByteArrayParseErr {
@ -97,11 +153,28 @@ impl From<ParseIntError> for ByteArrayParseErr {
}
}
impl From<std::io::Error> for ByteArrayParseErr {
fn from(e: Error) -> Self {
Self::FileError(e)
}
}
impl From<FromUtf8Error> 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)