1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-27 19:17:43 +00:00

od: implement --traditional

This commit is contained in:
Wim Hueskes 2016-08-18 22:17:03 +02:00
parent 26ec46835c
commit 2f12b06ba1
4 changed files with 221 additions and 35 deletions

View file

@ -95,6 +95,7 @@ pub fn uumain(args: Vec<String>) -> i32 {
"BYTES"); "BYTES");
opts.optflag("h", "help", "display this help and exit."); opts.optflag("h", "help", "display this help and exit.");
opts.optflag("", "version", "output version information and exit."); opts.optflag("", "version", "output version information and exit.");
opts.optflag("", "traditional", "compatibility mode with one input, offset and label.");
let matches = match opts.parse(&args[1..]) { let matches = match opts.parse(&args[1..]) {
Ok(m) => m, Ok(m) => m,
@ -149,12 +150,19 @@ pub fn uumain(args: Vec<String>) -> i32 {
} }
}; };
let mut label: Option<usize> = None;
let input_strings = match parse_inputs(&matches) { let input_strings = match parse_inputs(&matches) {
CommandLineInputs::FileNames(v) => v, Ok(CommandLineInputs::FileNames(v)) => v,
CommandLineInputs::FileAndOffset((f, s, _)) => { Ok(CommandLineInputs::FileAndOffset((f, s, l))) => {
skip_bytes = s; skip_bytes = s;
label = l;
vec!{f} vec!{f}
}, },
Err(e) => {
disp_err!("Invalid inputs: {}", e);
return 1;
}
}; };
let inputs = input_strings let inputs = input_strings
.iter() .iter()
@ -203,12 +211,13 @@ pub fn uumain(args: Vec<String>) -> i32 {
}; };
odfunc(line_bytes, input_offset_base, byte_order, inputs, &formats[..], odfunc(line_bytes, input_offset_base, byte_order, inputs, &formats[..],
output_duplicates, skip_bytes, read_bytes) output_duplicates, skip_bytes, read_bytes, label)
} }
// TODO: refactor, too many arguments
fn odfunc(line_bytes: usize, input_offset_base: Radix, byte_order: ByteOrder, fn odfunc(line_bytes: usize, input_offset_base: Radix, byte_order: ByteOrder,
fnames: Vec<InputSource>, formats: &[ParsedFormatterItemInfo], output_duplicates: bool, fnames: Vec<InputSource>, formats: &[ParsedFormatterItemInfo], output_duplicates: bool,
skip_bytes: usize, read_bytes: Option<usize>) -> i32 { skip_bytes: usize, read_bytes: Option<usize>, mut label: Option<usize>) -> i32 {
let mf = MultifileReader::new(fnames); let mf = MultifileReader::new(fnames);
let pr = PartialReader::new(mf, skip_bytes, read_bytes); let pr = PartialReader::new(mf, skip_bytes, read_bytes);
@ -263,7 +272,7 @@ fn odfunc(line_bytes: usize, input_offset_base: Radix, byte_order: ByteOrder,
match input.peek_read(bytes.as_mut_slice(), PEEK_BUFFER_SIZE) { match input.peek_read(bytes.as_mut_slice(), PEEK_BUFFER_SIZE) {
Ok((0, _)) => { Ok((0, _)) => {
print_final_offset(input_offset_base, addr); print_final_offset(input_offset_base, addr, label);
break; break;
} }
Ok((n, peekbytes)) => { Ok((n, peekbytes)) => {
@ -297,15 +306,18 @@ fn odfunc(line_bytes: usize, input_offset_base: Radix, byte_order: ByteOrder,
} }
print_bytes(byte_order, &bytes, n, peekbytes, print_bytes(byte_order, &bytes, n, peekbytes,
&print_with_radix(input_offset_base, addr), &print_with_radix(input_offset_base, addr, label),
&spaced_formatters, byte_size_block, print_width_line); &spaced_formatters, byte_size_block, print_width_line);
} }
addr += n; addr += n;
if let Some(l) = label {
label = Some(l + n);
}
} }
Err(e) => { Err(e) => {
show_error!("{}", e); show_error!("{}", e);
print_final_offset(input_offset_base, addr); print_final_offset(input_offset_base, addr, label);
return 1; return 1;
} }
}; };
@ -415,18 +427,24 @@ fn parse_radix(radix_str: Option<String>) -> Result<Radix, &'static str> {
} }
} }
fn print_with_radix(r: Radix, x: usize) -> String{ fn print_with_radix(r: Radix, x: usize, label: Option<usize>) -> String{
match r { match (r, label) {
Radix::Decimal => format!("{:07}", x), (Radix::Decimal, None) => format!("{:07}", x),
Radix::Hexadecimal => format!("{:06X}", x), (Radix::Decimal, Some(l)) => format!("{:07} ({:07})", x, l),
Radix::Octal => format!("{:07o}", x), (Radix::Hexadecimal, None) => format!("{:06X}", x),
Radix::NoPrefix => String::from(""), (Radix::Hexadecimal, Some(l)) => format!("{:06X} ({:06X})", x, l),
(Radix::Octal, None) => format!("{:07o}", x),
(Radix::Octal, Some(l)) => format!("{:07o} ({:07o})", x, l),
(Radix::NoPrefix, None) => String::from(""),
(Radix::NoPrefix, Some(l)) => format!("({:07o})", l),
} }
} }
fn print_final_offset(r: Radix, x: usize) { /// Prints the byte offset followed by a newline, or nothing at all if
if r != Radix::NoPrefix { /// both `Radix::NoPrefix` was set and no label (--traditional) is used.
print!("{}\n", print_with_radix(r, x)); fn print_final_offset(r: Radix, x: usize, label: Option<usize>) {
if r != Radix::NoPrefix || label.is_some() {
print!("{}\n", print_with_radix(r, x, label));
} }
} }

View file

@ -39,11 +39,16 @@ pub enum CommandLineInputs {
/// Offset and label are specified in bytes. /// Offset and label are specified in bytes.
/// '-' is used as filename if stdin is meant. This is also returned if /// '-' is used as filename if stdin is meant. This is also returned if
/// there is no input, as stdin is the default input. /// there is no input, as stdin is the default input.
pub fn parse_inputs(matches: &CommandLineOpts) -> CommandLineInputs { pub fn parse_inputs(matches: &CommandLineOpts) -> Result<CommandLineInputs, String> {
let mut input_strings: Vec<String> = matches.inputs(); let mut input_strings: Vec<String> = matches.inputs();
if matches.opts_present(&["traditional"]) {
return parse_inputs_traditional(input_strings);
}
// test if commandline contains: [file] <offset> // test if commandline contains: [file] <offset>
// fall-through if no (valid) offset is found
if input_strings.len() == 1 || input_strings.len() == 2 { if input_strings.len() == 1 || input_strings.len() == 2 {
// if any of the options -A, -j, -N, -t, -v or -w are present there is no offset // if any of the options -A, -j, -N, -t, -v or -w are present there is no offset
if !matches.opts_present(&["A", "j", "N", "t", "v", "w"]) { if !matches.opts_present(&["A", "j", "N", "t", "v", "w"]) {
@ -53,10 +58,10 @@ pub fn parse_inputs(matches: &CommandLineOpts) -> CommandLineInputs {
Ok(n) => { Ok(n) => {
// if there is just 1 input (stdin), an offset must start with '+' // if there is just 1 input (stdin), an offset must start with '+'
if input_strings.len() == 1 && input_strings[0].starts_with("+") { if input_strings.len() == 1 && input_strings[0].starts_with("+") {
return CommandLineInputs::FileAndOffset(("-".to_string(), n, None)); return Ok(CommandLineInputs::FileAndOffset(("-".to_string(), n, None)));
} }
if input_strings.len() == 2 { if input_strings.len() == 2 {
return CommandLineInputs::FileAndOffset((input_strings[0].clone(), n, None)); return Ok(CommandLineInputs::FileAndOffset((input_strings[0].clone(), n, None)));
} }
} }
_ => { _ => {
@ -69,7 +74,47 @@ pub fn parse_inputs(matches: &CommandLineOpts) -> CommandLineInputs {
if input_strings.len() == 0 { if input_strings.len() == 0 {
input_strings.push("-".to_string()); input_strings.push("-".to_string());
} }
CommandLineInputs::FileNames(input_strings) Ok(CommandLineInputs::FileNames(input_strings))
}
/// interprets inputs when --traditional is on the commandline
///
/// normally returns CommandLineInputs::FileAndOffset, but if no offset is found,
/// it returns CommandLineInputs::FileNames (also to differentiate from the offset==0)
pub fn parse_inputs_traditional(input_strings: Vec<String>) -> Result<CommandLineInputs, String> {
match input_strings.len() {
0 => {
Ok(CommandLineInputs::FileNames(vec!{"-".to_string()}))
}
1 => {
let offset0=parse_offset_operand(&input_strings[0]);
Ok(match offset0 {
Ok(n) => CommandLineInputs::FileAndOffset(("-".to_string(), n, None)),
_ => CommandLineInputs::FileNames(input_strings),
})
}
2 => {
let offset0=parse_offset_operand(&input_strings[0]);
let offset1=parse_offset_operand(&input_strings[1]);
match (offset0, offset1) {
(Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset(("-".to_string(), n, Some(m)))),
(_, Ok(m)) => Ok(CommandLineInputs::FileAndOffset((input_strings[0].clone(), m, None))),
_ => Err(format!("invalid offset: {}", input_strings[1])),
}
}
3 => {
let offset=parse_offset_operand(&input_strings[1]);
let label=parse_offset_operand(&input_strings[2]);
match (offset, label) {
(Ok(n), Ok(m)) => Ok(CommandLineInputs::FileAndOffset((input_strings[0].clone(), n, Some(m)))),
(Err(_), _) => Err(format!("invalid offset: {}", input_strings[1])),
(_, Err(_)) => Err(format!("invalid label: {}", input_strings[2])),
}
}
_ => {
Err(format!("too many inputs after --traditional: {}", input_strings[3]))
}
}
} }
/// parses format used by offset and label on the commandline /// parses format used by offset and label on the commandline
@ -148,27 +193,27 @@ mod tests {
assert_eq!(CommandLineInputs::FileNames(vec!{"-".to_string()}), assert_eq!(CommandLineInputs::FileNames(vec!{"-".to_string()}),
parse_inputs(&MockOptions::new( parse_inputs(&MockOptions::new(
vec!{}, vec!{},
vec!{}))); vec!{})).unwrap());
assert_eq!(CommandLineInputs::FileNames(vec!{"-".to_string()}), assert_eq!(CommandLineInputs::FileNames(vec!{"-".to_string()}),
parse_inputs(&MockOptions::new( parse_inputs(&MockOptions::new(
vec!{"-"}, vec!{"-"},
vec!{}))); vec!{})).unwrap());
assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string()}), assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string()}),
parse_inputs(&MockOptions::new( parse_inputs(&MockOptions::new(
vec!{"file1"}, vec!{"file1"},
vec!{}))); vec!{})).unwrap());
assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string(), "file2".to_string()}), assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string(), "file2".to_string()}),
parse_inputs(&MockOptions::new( parse_inputs(&MockOptions::new(
vec!{"file1", "file2"}, vec!{"file1", "file2"},
vec!{}))); vec!{})).unwrap());
assert_eq!(CommandLineInputs::FileNames(vec!{"-".to_string(), "file1".to_string(), "file2".to_string()}), assert_eq!(CommandLineInputs::FileNames(vec!{"-".to_string(), "file1".to_string(), "file2".to_string()}),
parse_inputs(&MockOptions::new( parse_inputs(&MockOptions::new(
vec!{"-", "file1", "file2"}, vec!{"-", "file1", "file2"},
vec!{}))); vec!{})).unwrap());
} }
#[test] #[test]
@ -177,58 +222,112 @@ mod tests {
assert_eq!(CommandLineInputs::FileAndOffset(("-".to_string(), 8, None)), assert_eq!(CommandLineInputs::FileAndOffset(("-".to_string(), 8, None)),
parse_inputs(&MockOptions::new( parse_inputs(&MockOptions::new(
vec!{"+10"}, vec!{"+10"},
vec!{}))); vec!{})).unwrap());
// offset must start with "+" if no input is specified. // offset must start with "+" if no input is specified.
assert_eq!(CommandLineInputs::FileNames(vec!{"10".to_string()}), assert_eq!(CommandLineInputs::FileNames(vec!{"10".to_string()}),
parse_inputs(&MockOptions::new( parse_inputs(&MockOptions::new(
vec!{"10"}, vec!{"10"},
vec!{""}))); vec!{""})).unwrap());
// offset is not valid, so it is considered a filename. // offset is not valid, so it is considered a filename.
assert_eq!(CommandLineInputs::FileNames(vec!{"+10a".to_string()}), assert_eq!(CommandLineInputs::FileNames(vec!{"+10a".to_string()}),
parse_inputs(&MockOptions::new( parse_inputs(&MockOptions::new(
vec!{"+10a"}, vec!{"+10a"},
vec!{""}))); vec!{""})).unwrap());
// if -j is included in the commandline, there cannot be an offset. // if -j is included in the commandline, there cannot be an offset.
assert_eq!(CommandLineInputs::FileNames(vec!{"+10".to_string()}), assert_eq!(CommandLineInputs::FileNames(vec!{"+10".to_string()}),
parse_inputs(&MockOptions::new( parse_inputs(&MockOptions::new(
vec!{"+10"}, vec!{"+10"},
vec!{"j"}))); vec!{"j"})).unwrap());
// if -v is included in the commandline, there cannot be an offset. // if -v is included in the commandline, there cannot be an offset.
assert_eq!(CommandLineInputs::FileNames(vec!{"+10".to_string()}), assert_eq!(CommandLineInputs::FileNames(vec!{"+10".to_string()}),
parse_inputs(&MockOptions::new( parse_inputs(&MockOptions::new(
vec!{"+10"}, vec!{"+10"},
vec!{"o", "v"}))); vec!{"o", "v"})).unwrap());
assert_eq!(CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)), assert_eq!(CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)),
parse_inputs(&MockOptions::new( parse_inputs(&MockOptions::new(
vec!{"file1", "+10"}, vec!{"file1", "+10"},
vec!{}))); vec!{})).unwrap());
// offset does not need to start with "+" if a filename is included. // offset does not need to start with "+" if a filename is included.
assert_eq!(CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)), assert_eq!(CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)),
parse_inputs(&MockOptions::new( parse_inputs(&MockOptions::new(
vec!{"file1", "10"}, vec!{"file1", "10"},
vec!{}))); vec!{})).unwrap());
assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string(), "+10a".to_string()}), assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string(), "+10a".to_string()}),
parse_inputs(&MockOptions::new( parse_inputs(&MockOptions::new(
vec!{"file1", "+10a"}, vec!{"file1", "+10a"},
vec!{""}))); vec!{""})).unwrap());
assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string(), "+10".to_string()}), assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string(), "+10".to_string()}),
parse_inputs(&MockOptions::new( parse_inputs(&MockOptions::new(
vec!{"file1", "+10"}, vec!{"file1", "+10"},
vec!{"j"}))); vec!{"j"})).unwrap());
// offset must be last on the commandline // offset must be last on the commandline
assert_eq!(CommandLineInputs::FileNames(vec!{"+10".to_string(), "file1".to_string()}), assert_eq!(CommandLineInputs::FileNames(vec!{"+10".to_string(), "file1".to_string()}),
parse_inputs(&MockOptions::new( parse_inputs(&MockOptions::new(
vec!{"+10", "file1"}, vec!{"+10", "file1"},
vec!{""}))); vec!{""})).unwrap());
}
#[test]
fn test_parse_inputs_traditional() {
// it should not return FileAndOffset to signal no offset was entered on the commandline.
assert_eq!(CommandLineInputs::FileNames(vec!{"-".to_string()}),
parse_inputs(&MockOptions::new(
vec!{},
vec!{"traditional"})).unwrap());
assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string()}),
parse_inputs(&MockOptions::new(
vec!{"file1"},
vec!{"traditional"})).unwrap());
// offset does not need to start with a +
assert_eq!(CommandLineInputs::FileAndOffset(("-".to_string(), 8, None)),
parse_inputs(&MockOptions::new(
vec!{"10"},
vec!{"traditional"})).unwrap());
// valid offset and valid label
assert_eq!(CommandLineInputs::FileAndOffset(("-".to_string(), 8, Some(8))),
parse_inputs(&MockOptions::new(
vec!{"10", "10"},
vec!{"traditional"})).unwrap());
assert_eq!(CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)),
parse_inputs(&MockOptions::new(
vec!{"file1", "10"},
vec!{"traditional"})).unwrap());
// only one file is allowed, it must be the first
parse_inputs(&MockOptions::new(
vec!{"10", "file1"},
vec!{"traditional"})).unwrap_err();
assert_eq!(CommandLineInputs::FileAndOffset(("file1".to_string(), 8, Some(8))),
parse_inputs(&MockOptions::new(
vec!{"file1", "10", "10"},
vec!{"traditional"})).unwrap());
parse_inputs(&MockOptions::new(
vec!{"10", "file1", "10"},
vec!{"traditional"})).unwrap_err();
parse_inputs(&MockOptions::new(
vec!{"10", "10", "file1"},
vec!{"traditional"})).unwrap_err();
parse_inputs(&MockOptions::new(
vec!{"10", "10", "10", "10"},
vec!{"traditional"})).unwrap_err();
} }
fn parse_offset_operand_str(s: &str) -> Result<usize, &'static str> { fn parse_offset_operand_str(s: &str) -> Result<usize, &'static str> {

1
tests/fixtures/od/0 vendored Normal file
View file

@ -0,0 +1 @@
zero

View file

@ -591,3 +591,71 @@ fn test_file_offset(){
0000022 0000022
")); "));
} }
#[test]
fn test_traditional(){
// note gnu od does not align both lines
let input = "abcdefghijklmnopq";
let result = new_ucmd!().arg("--traditional").arg("-a").arg("-c").arg("-").arg("10").arg("0").run_piped_stdin(input.as_bytes());
assert_empty_stderr!(result);
assert!(result.success);
assert_eq!(result.stdout, unindent(r"
0000010 (0000000) i j k l m n o p q
i j k l m n o p q
0000021 (0000011)
"));
}
#[test]
fn test_traditional_with_skip_bytes_override(){
// --skip-bytes is ignored in this case
let input = "abcdefghijklmnop";
let result = new_ucmd!().arg("--traditional").arg("--skip-bytes=10").arg("-c").arg("0").run_piped_stdin(input.as_bytes());
assert_empty_stderr!(result);
assert!(result.success);
assert_eq!(result.stdout, unindent(r"
0000000 a b c d e f g h i j k l m n o p
0000020
"));
}
#[test]
fn test_traditional_with_skip_bytes_non_override(){
// no offset specified in the traditional way, so --skip-bytes is used
let input = "abcdefghijklmnop";
let result = new_ucmd!().arg("--traditional").arg("--skip-bytes=10").arg("-c").run_piped_stdin(input.as_bytes());
assert_empty_stderr!(result);
assert!(result.success);
assert_eq!(result.stdout, unindent(r"
0000012 k l m n o p
0000020
"));
}
#[test]
fn test_traditional_error(){
// file "0" exists - don't fail on that, but --traditional only accepts a single input
let input = "abcdefghijklmnopq";
let result = new_ucmd!().arg("--traditional").arg("0").arg("0").arg("0").arg("0").run_piped_stdin(input.as_bytes());
assert!(!result.success);
}
#[test]
fn test_traditional_only_label(){
let input = "abcdefghijklmnopqrstuvwxyz";
let result = new_ucmd!().arg("-An").arg("--traditional").arg("-a").arg("-c").arg("-").arg("10").arg("0x10").run_piped_stdin(input.as_bytes());
assert_empty_stderr!(result);
assert!(result.success);
assert_eq!(result.stdout, unindent(r"
(0000020) i j k l m n o p q r s t u v w x
i j k l m n o p q r s t u v w x
(0000040) y z
y z
(0000042)
"));
}