use uucore::display::Quotable;
use crate::formatteriteminfo::FormatterItemInfo;
use crate::prn_char::*;
use crate::prn_float::*;
use crate::prn_int::*;
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct ParsedFormatterItemInfo {
pub formatter_item_info: FormatterItemInfo,
pub add_ascii_dump: bool,
}
impl ParsedFormatterItemInfo {
pub fn new(formatter_item_info: FormatterItemInfo, add_ascii_dump: bool) -> Self {
Self {
formatter_item_info,
add_ascii_dump,
}
}
}
fn od_argument_traditional_format(ch: char) -> Option<FormatterItemInfo> {
match ch {
'a' => Some(FORMAT_ITEM_A),
'B' => Some(FORMAT_ITEM_OCT16),
'b' => Some(FORMAT_ITEM_OCT8),
'c' => Some(FORMAT_ITEM_C),
'D' => Some(FORMAT_ITEM_DEC32U),
'd' => Some(FORMAT_ITEM_DEC16U),
'e' => Some(FORMAT_ITEM_F64),
'F' => Some(FORMAT_ITEM_F64),
'f' => Some(FORMAT_ITEM_F32),
'H' => Some(FORMAT_ITEM_HEX32),
'h' => Some(FORMAT_ITEM_HEX16),
'i' => Some(FORMAT_ITEM_DEC32S),
'I' => Some(FORMAT_ITEM_DEC64S),
'L' => Some(FORMAT_ITEM_DEC64S),
'l' => Some(FORMAT_ITEM_DEC64S),
'O' => Some(FORMAT_ITEM_OCT32),
'o' => Some(FORMAT_ITEM_OCT16),
's' => Some(FORMAT_ITEM_DEC16S),
'X' => Some(FORMAT_ITEM_HEX32),
'x' => Some(FORMAT_ITEM_HEX16),
_ => None,
}
}
fn od_format_type(type_char: FormatType, byte_size: u8) -> Option<FormatterItemInfo> {
match (type_char, byte_size) {
(FormatType::Ascii, _) => Some(FORMAT_ITEM_A),
(FormatType::Char, _) => Some(FORMAT_ITEM_C),
(FormatType::DecimalInt, 1) => Some(FORMAT_ITEM_DEC8S),
(FormatType::DecimalInt, 2) => Some(FORMAT_ITEM_DEC16S),
(FormatType::DecimalInt, 0 | 4) => Some(FORMAT_ITEM_DEC32S),
(FormatType::DecimalInt, 8) => Some(FORMAT_ITEM_DEC64S),
(FormatType::OctalInt, 1) => Some(FORMAT_ITEM_OCT8),
(FormatType::OctalInt, 2) => Some(FORMAT_ITEM_OCT16),
(FormatType::OctalInt, 0 | 4) => Some(FORMAT_ITEM_OCT32),
(FormatType::OctalInt, 8) => Some(FORMAT_ITEM_OCT64),
(FormatType::UnsignedInt, 1) => Some(FORMAT_ITEM_DEC8U),
(FormatType::UnsignedInt, 2) => Some(FORMAT_ITEM_DEC16U),
(FormatType::UnsignedInt, 0 | 4) => Some(FORMAT_ITEM_DEC32U),
(FormatType::UnsignedInt, 8) => Some(FORMAT_ITEM_DEC64U),
(FormatType::HexadecimalInt, 1) => Some(FORMAT_ITEM_HEX8),
(FormatType::HexadecimalInt, 2) => Some(FORMAT_ITEM_HEX16),
(FormatType::HexadecimalInt, 0 | 4) => Some(FORMAT_ITEM_HEX32),
(FormatType::HexadecimalInt, 8) => Some(FORMAT_ITEM_HEX64),
(FormatType::Float, 2) => Some(FORMAT_ITEM_F16),
(FormatType::Float, 0 | 4) => Some(FORMAT_ITEM_F32),
(FormatType::Float, 8) => Some(FORMAT_ITEM_F64),
_ => None,
}
}
fn od_argument_with_option(ch: char) -> bool {
matches!(ch, 'A' | 'j' | 'N' | 'S' | 'w')
}
#[allow(clippy::cognitive_complexity)]
pub fn parse_format_flags(args: &[String]) -> Result<Vec<ParsedFormatterItemInfo>, String> {
let mut formats = Vec::new();
let arg_iter = args.iter().skip(1);
let mut expect_type_string = false;
for arg in arg_iter {
if expect_type_string {
let v = parse_type_string(arg)?;
formats.extend(v.into_iter());
expect_type_string = false;
} else if arg.starts_with("--") {
if arg.len() == 2 {
break;
}
if arg.starts_with("--format=") {
let params: String = arg.chars().skip_while(|c| *c != '=').skip(1).collect();
let v = parse_type_string(¶ms)?;
formats.extend(v.into_iter());
}
if arg == "--format" {
expect_type_string = true;
}
} else if arg.starts_with('-') {
let flags = arg.chars().skip(1);
let mut format_spec = String::new();
for c in flags {
if expect_type_string {
format_spec.push(c);
} else if od_argument_with_option(c) {
break;
} else if c == 't' {
expect_type_string = true;
} else {
if let Some(r) = od_argument_traditional_format(c) {
formats.push(ParsedFormatterItemInfo::new(r, false));
}
}
}
if !format_spec.is_empty() {
let v = parse_type_string(&format_spec)?;
formats.extend(v.into_iter());
expect_type_string = false;
}
}
}
if expect_type_string {
return Err("missing format specification after '--format' / '-t'".to_string());
}
if formats.is_empty() {
formats.push(ParsedFormatterItemInfo::new(FORMAT_ITEM_OCT16, false)); }
Ok(formats)
}
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
enum FormatType {
Ascii,
Char,
DecimalInt,
OctalInt,
UnsignedInt,
HexadecimalInt,
Float,
}
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
enum FormatTypeCategory {
Char,
Integer,
Float,
}
fn format_type(ch: char) -> Option<FormatType> {
match ch {
'a' => Some(FormatType::Ascii),
'c' => Some(FormatType::Char),
'd' => Some(FormatType::DecimalInt),
'o' => Some(FormatType::OctalInt),
'u' => Some(FormatType::UnsignedInt),
'x' => Some(FormatType::HexadecimalInt),
'f' => Some(FormatType::Float),
_ => None,
}
}
fn format_type_category(t: FormatType) -> FormatTypeCategory {
match t {
FormatType::Ascii | FormatType::Char => FormatTypeCategory::Char,
FormatType::DecimalInt
| FormatType::OctalInt
| FormatType::UnsignedInt
| FormatType::HexadecimalInt => FormatTypeCategory::Integer,
FormatType::Float => FormatTypeCategory::Float,
}
}
fn is_format_size_char(
ch: Option<char>,
format_type: FormatTypeCategory,
byte_size: &mut u8,
) -> bool {
match (format_type, ch) {
(FormatTypeCategory::Integer, Some('C')) => {
*byte_size = 1;
true
}
(FormatTypeCategory::Integer, Some('S')) => {
*byte_size = 2;
true
}
(FormatTypeCategory::Integer, Some('I')) => {
*byte_size = 4;
true
}
(FormatTypeCategory::Integer, Some('L')) => {
*byte_size = 8;
true
}
(FormatTypeCategory::Float, Some('F')) => {
*byte_size = 4;
true
}
(FormatTypeCategory::Float, Some('D')) => {
*byte_size = 8;
true
}
_ => false,
}
}
fn is_format_size_decimal(
ch: Option<char>,
format_type: FormatTypeCategory,
decimal_size: &mut String,
) -> bool {
if format_type == FormatTypeCategory::Char {
return false;
}
match ch {
Some(d) if d.is_ascii_digit() => {
decimal_size.push(d);
true
}
_ => false,
}
}
fn is_format_dump_char(ch: Option<char>, show_ascii_dump: &mut bool) -> bool {
match ch {
Some('z') => {
*show_ascii_dump = true;
true
}
_ => false,
}
}
fn parse_type_string(params: &str) -> Result<Vec<ParsedFormatterItemInfo>, String> {
let mut formats = Vec::new();
let mut chars = params.chars();
let mut ch = chars.next();
while let Some(type_char) = ch {
let type_char = format_type(type_char).ok_or_else(|| {
format!(
"unexpected char '{}' in format specification {}",
type_char,
params.quote()
)
})?;
let type_cat = format_type_category(type_char);
ch = chars.next();
let mut byte_size = 0u8;
let mut show_ascii_dump = false;
if is_format_size_char(ch, type_cat, &mut byte_size) {
ch = chars.next();
} else {
let mut decimal_size = String::new();
while is_format_size_decimal(ch, type_cat, &mut decimal_size) {
ch = chars.next();
}
if !decimal_size.is_empty() {
byte_size = decimal_size.parse().map_err(|_| {
format!(
"invalid number {} in format specification {}",
decimal_size.quote(),
params.quote()
)
})?;
}
}
if is_format_dump_char(ch, &mut show_ascii_dump) {
ch = chars.next();
}
let ft = od_format_type(type_char, byte_size).ok_or_else(|| {
format!(
"invalid size '{}' in format specification {}",
byte_size,
params.quote()
)
})?;
formats.push(ParsedFormatterItemInfo::new(ft, show_ascii_dump));
}
Ok(formats)
}
#[cfg(test)]
pub fn parse_format_flags_str(args_str: &[&'static str]) -> Result<Vec<FormatterItemInfo>, String> {
let args: Vec<String> = args_str.iter().map(|s| s.to_string()).collect();
parse_format_flags(&args).map(|v| {
v.into_iter()
.inspect(|f| assert!(!f.add_ascii_dump))
.map(|f| f.formatter_item_info)
.collect()
})
}
#[test]
fn test_no_options() {
assert_eq!(
parse_format_flags_str(&["od"]).unwrap(),
vec![FORMAT_ITEM_OCT16]
);
}
#[test]
fn test_one_option() {
assert_eq!(
parse_format_flags_str(&["od", "-F"]).unwrap(),
vec![FORMAT_ITEM_F64]
);
}
#[test]
fn test_two_separate_options() {
assert_eq!(
parse_format_flags_str(&["od", "-F", "-x"]).unwrap(),
vec![FORMAT_ITEM_F64, FORMAT_ITEM_HEX16]
);
}
#[test]
fn test_two_combined_options() {
assert_eq!(
parse_format_flags_str(&["od", "-Fx"]).unwrap(),
vec![FORMAT_ITEM_F64, FORMAT_ITEM_HEX16]
);
}
#[test]
fn test_ignore_non_format_parameters() {
assert_eq!(
parse_format_flags_str(&["od", "-d", "-Ax"]).unwrap(),
vec![FORMAT_ITEM_DEC16U]
);
}
#[test]
fn test_ignore_separate_parameters() {
assert_eq!(
parse_format_flags_str(&["od", "-I", "-A", "x"]).unwrap(),
vec![FORMAT_ITEM_DEC64S]
);
}
#[test]
fn test_ignore_trailing_vals() {
assert_eq!(
parse_format_flags_str(&["od", "-D", "--", "-x"]).unwrap(),
vec![FORMAT_ITEM_DEC32U]
);
}
#[test]
fn test_invalid_long_format() {
parse_format_flags_str(&["od", "--format=X"]).unwrap_err();
parse_format_flags_str(&["od", "--format=xX"]).unwrap_err();
parse_format_flags_str(&["od", "--format=aC"]).unwrap_err();
parse_format_flags_str(&["od", "--format=fI"]).unwrap_err();
parse_format_flags_str(&["od", "--format=xD"]).unwrap_err();
parse_format_flags_str(&["od", "--format=xC1"]).unwrap_err();
parse_format_flags_str(&["od", "--format=x1C"]).unwrap_err();
parse_format_flags_str(&["od", "--format=xz1"]).unwrap_err();
parse_format_flags_str(&["od", "--format=xzC"]).unwrap_err();
parse_format_flags_str(&["od", "--format=xzz"]).unwrap_err();
parse_format_flags_str(&["od", "--format=xCC"]).unwrap_err();
parse_format_flags_str(&["od", "--format=c1"]).unwrap_err();
parse_format_flags_str(&["od", "--format=x256"]).unwrap_err();
parse_format_flags_str(&["od", "--format=d5"]).unwrap_err();
parse_format_flags_str(&["od", "--format=f1"]).unwrap_err();
}
#[test]
fn test_long_format_a() {
assert_eq!(
parse_format_flags_str(&["od", "--format=a"]).unwrap(),
vec![FORMAT_ITEM_A]
);
}
#[test]
fn test_long_format_cz() {
assert_eq!(
parse_format_flags(&["od".to_string(), "--format=cz".to_string()]).unwrap(),
vec![ParsedFormatterItemInfo::new(FORMAT_ITEM_C, true)]
);
}
#[test]
fn test_long_format_d() {
assert_eq!(
parse_format_flags_str(&["od", "--format=d8"]).unwrap(),
vec![FORMAT_ITEM_DEC64S]
);
}
#[test]
fn test_long_format_d_default() {
assert_eq!(
parse_format_flags_str(&["od", "--format=d"]).unwrap(),
vec![FORMAT_ITEM_DEC32S]
);
}
#[test]
fn test_long_format_o_default() {
assert_eq!(
parse_format_flags_str(&["od", "--format=o"]).unwrap(),
vec![FORMAT_ITEM_OCT32]
);
}
#[test]
fn test_long_format_u_default() {
assert_eq!(
parse_format_flags_str(&["od", "--format=u"]).unwrap(),
vec![FORMAT_ITEM_DEC32U]
);
}
#[test]
fn test_long_format_x_default() {
assert_eq!(
parse_format_flags_str(&["od", "--format=x"]).unwrap(),
vec![FORMAT_ITEM_HEX32]
);
}
#[test]
fn test_long_format_f_default() {
assert_eq!(
parse_format_flags_str(&["od", "--format=f"]).unwrap(),
vec![FORMAT_ITEM_F32]
);
}
#[test]
fn test_long_format_next_arg() {
assert_eq!(
parse_format_flags_str(&["od", "--format", "f8"]).unwrap(),
vec![FORMAT_ITEM_F64]
);
}
#[test]
fn test_short_format_next_arg() {
assert_eq!(
parse_format_flags_str(&["od", "-t", "x8"]).unwrap(),
vec![FORMAT_ITEM_HEX64]
);
}
#[test]
fn test_short_format_combined_arg() {
assert_eq!(
parse_format_flags_str(&["od", "-tu8"]).unwrap(),
vec![FORMAT_ITEM_DEC64U]
);
}
#[test]
fn test_format_next_arg_invalid() {
parse_format_flags_str(&["od", "--format", "-v"]).unwrap_err();
parse_format_flags_str(&["od", "--format"]).unwrap_err();
parse_format_flags_str(&["od", "-t", "-v"]).unwrap_err();
parse_format_flags_str(&["od", "-t"]).unwrap_err();
}
#[test]
fn test_mixed_formats() {
assert_eq!(
parse_format_flags(&[
"od".to_string(),
"--skip-bytes=2".to_string(),
"-vItu1z".to_string(),
"-N".to_string(),
"1000".to_string(),
"-xt".to_string(),
"acdx1".to_string(),
"--format=u2c".to_string(),
"--format".to_string(),
"f".to_string(),
"-xAx".to_string(),
"--".to_string(),
"-h".to_string(),
"--format=f8".to_string(),
])
.unwrap(),
vec![
ParsedFormatterItemInfo::new(FORMAT_ITEM_DEC64S, false), ParsedFormatterItemInfo::new(FORMAT_ITEM_DEC8U, true), ParsedFormatterItemInfo::new(FORMAT_ITEM_HEX16, false), ParsedFormatterItemInfo::new(FORMAT_ITEM_A, false), ParsedFormatterItemInfo::new(FORMAT_ITEM_C, false), ParsedFormatterItemInfo::new(FORMAT_ITEM_DEC32S, false), ParsedFormatterItemInfo::new(FORMAT_ITEM_HEX8, false), ParsedFormatterItemInfo::new(FORMAT_ITEM_DEC16U, false), ParsedFormatterItemInfo::new(FORMAT_ITEM_C, false), ParsedFormatterItemInfo::new(FORMAT_ITEM_F32, false), ParsedFormatterItemInfo::new(FORMAT_ITEM_HEX16, false), ]
);
}