diff --git a/src/od/od.rs b/src/od/od.rs index b4f128f06..c8cb5a30b 100644 --- a/src/od/od.rs +++ b/src/od/od.rs @@ -95,6 +95,7 @@ pub fn uumain(args: Vec) -> i32 { "BYTES"); opts.optflag("h", "help", "display this help 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..]) { Ok(m) => m, @@ -149,12 +150,19 @@ pub fn uumain(args: Vec) -> i32 { } }; + let mut label: Option = None; + let input_strings = match parse_inputs(&matches) { - CommandLineInputs::FileNames(v) => v, - CommandLineInputs::FileAndOffset((f, s, _)) => { + Ok(CommandLineInputs::FileNames(v)) => v, + Ok(CommandLineInputs::FileAndOffset((f, s, l))) => { skip_bytes = s; + label = l; vec!{f} }, + Err(e) => { + disp_err!("Invalid inputs: {}", e); + return 1; + } }; let inputs = input_strings .iter() @@ -203,12 +211,13 @@ pub fn uumain(args: Vec) -> i32 { }; 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, fnames: Vec, formats: &[ParsedFormatterItemInfo], output_duplicates: bool, - skip_bytes: usize, read_bytes: Option) -> i32 { + skip_bytes: usize, read_bytes: Option, mut label: Option) -> i32 { let mf = MultifileReader::new(fnames); 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) { Ok((0, _)) => { - print_final_offset(input_offset_base, addr); + print_final_offset(input_offset_base, addr, label); break; } 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_with_radix(input_offset_base, addr), + &print_with_radix(input_offset_base, addr, label), &spaced_formatters, byte_size_block, print_width_line); } addr += n; + if let Some(l) = label { + label = Some(l + n); + } } Err(e) => { show_error!("{}", e); - print_final_offset(input_offset_base, addr); + print_final_offset(input_offset_base, addr, label); return 1; } }; @@ -415,18 +427,24 @@ fn parse_radix(radix_str: Option) -> Result { } } -fn print_with_radix(r: Radix, x: usize) -> String{ - match r { - Radix::Decimal => format!("{:07}", x), - Radix::Hexadecimal => format!("{:06X}", x), - Radix::Octal => format!("{:07o}", x), - Radix::NoPrefix => String::from(""), +fn print_with_radix(r: Radix, x: usize, label: Option) -> String{ + match (r, label) { + (Radix::Decimal, None) => format!("{:07}", x), + (Radix::Decimal, Some(l)) => format!("{:07} ({:07})", x, l), + (Radix::Hexadecimal, None) => format!("{:06X}", x), + (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) { - if r != Radix::NoPrefix { - print!("{}\n", print_with_radix(r, x)); +/// Prints the byte offset followed by a newline, or nothing at all if +/// both `Radix::NoPrefix` was set and no label (--traditional) is used. +fn print_final_offset(r: Radix, x: usize, label: Option) { + if r != Radix::NoPrefix || label.is_some() { + print!("{}\n", print_with_radix(r, x, label)); } } diff --git a/src/od/parse_inputs.rs b/src/od/parse_inputs.rs index 4db4826e2..e87a85a06 100644 --- a/src/od/parse_inputs.rs +++ b/src/od/parse_inputs.rs @@ -39,11 +39,16 @@ pub enum CommandLineInputs { /// Offset and label are specified in bytes. /// '-' is used as filename if stdin is meant. This is also returned if /// there is no input, as stdin is the default input. -pub fn parse_inputs(matches: &CommandLineOpts) -> CommandLineInputs { +pub fn parse_inputs(matches: &CommandLineOpts) -> Result { let mut input_strings: Vec = matches.inputs(); + if matches.opts_present(&["traditional"]) { + return parse_inputs_traditional(input_strings); + } + // test if commandline contains: [file] + // fall-through if no (valid) offset is found 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 !matches.opts_present(&["A", "j", "N", "t", "v", "w"]) { @@ -53,10 +58,10 @@ pub fn parse_inputs(matches: &CommandLineOpts) -> CommandLineInputs { Ok(n) => { // if there is just 1 input (stdin), an offset must start 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 { - 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 { 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) -> Result { + 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 @@ -148,27 +193,27 @@ mod tests { assert_eq!(CommandLineInputs::FileNames(vec!{"-".to_string()}), parse_inputs(&MockOptions::new( vec!{}, - vec!{}))); + vec!{})).unwrap()); assert_eq!(CommandLineInputs::FileNames(vec!{"-".to_string()}), parse_inputs(&MockOptions::new( vec!{"-"}, - vec!{}))); + vec!{})).unwrap()); assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string()}), parse_inputs(&MockOptions::new( vec!{"file1"}, - vec!{}))); + vec!{})).unwrap()); assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string(), "file2".to_string()}), parse_inputs(&MockOptions::new( vec!{"file1", "file2"}, - vec!{}))); + vec!{})).unwrap()); assert_eq!(CommandLineInputs::FileNames(vec!{"-".to_string(), "file1".to_string(), "file2".to_string()}), parse_inputs(&MockOptions::new( vec!{"-", "file1", "file2"}, - vec!{}))); + vec!{})).unwrap()); } #[test] @@ -177,58 +222,112 @@ mod tests { assert_eq!(CommandLineInputs::FileAndOffset(("-".to_string(), 8, None)), parse_inputs(&MockOptions::new( vec!{"+10"}, - vec!{}))); + vec!{})).unwrap()); // offset must start with "+" if no input is specified. assert_eq!(CommandLineInputs::FileNames(vec!{"10".to_string()}), parse_inputs(&MockOptions::new( vec!{"10"}, - vec!{""}))); + vec!{""})).unwrap()); // offset is not valid, so it is considered a filename. assert_eq!(CommandLineInputs::FileNames(vec!{"+10a".to_string()}), parse_inputs(&MockOptions::new( vec!{"+10a"}, - vec!{""}))); + vec!{""})).unwrap()); // if -j is included in the commandline, there cannot be an offset. assert_eq!(CommandLineInputs::FileNames(vec!{"+10".to_string()}), parse_inputs(&MockOptions::new( vec!{"+10"}, - vec!{"j"}))); + vec!{"j"})).unwrap()); // if -v is included in the commandline, there cannot be an offset. assert_eq!(CommandLineInputs::FileNames(vec!{"+10".to_string()}), parse_inputs(&MockOptions::new( vec!{"+10"}, - vec!{"o", "v"}))); + vec!{"o", "v"})).unwrap()); assert_eq!(CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)), parse_inputs(&MockOptions::new( vec!{"file1", "+10"}, - vec!{}))); + vec!{})).unwrap()); // offset does not need to start with "+" if a filename is included. assert_eq!(CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)), parse_inputs(&MockOptions::new( vec!{"file1", "10"}, - vec!{}))); + vec!{})).unwrap()); assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string(), "+10a".to_string()}), parse_inputs(&MockOptions::new( vec!{"file1", "+10a"}, - vec!{""}))); + vec!{""})).unwrap()); assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string(), "+10".to_string()}), parse_inputs(&MockOptions::new( vec!{"file1", "+10"}, - vec!{"j"}))); + vec!{"j"})).unwrap()); // offset must be last on the commandline assert_eq!(CommandLineInputs::FileNames(vec!{"+10".to_string(), "file1".to_string()}), parse_inputs(&MockOptions::new( 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 { diff --git a/tests/fixtures/od/0 b/tests/fixtures/od/0 new file mode 100644 index 000000000..26af6a865 --- /dev/null +++ b/tests/fixtures/od/0 @@ -0,0 +1 @@ +zero diff --git a/tests/test_od.rs b/tests/test_od.rs index 5aa0a1185..adddacbe9 100644 --- a/tests/test_od.rs +++ b/tests/test_od.rs @@ -591,3 +591,71 @@ fn test_file_offset(){ 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) + ")); +}