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");
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<String>) -> i32 {
}
};
let mut label: Option<usize> = 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<String>) -> 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<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 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<String>) -> Result<Radix, &'static str> {
}
}
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<usize>) -> 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<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.
/// '-' 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<CommandLineInputs, String> {
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>
// 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<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
@ -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<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
"));
}
#[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)
"));
}