Added file input + stdin support
+ Files/stdin input can be binary or ascii + Handles piping from other programs + Misc test fixes + Updated readme + clippy + fmtmain
parent
287ba32ea7
commit
f2cec4c89e
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
15
src/main.rs
15
src/main.rs
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue