mirror of
https://github.com/uutils/coreutils
synced 2024-07-23 19:04:18 +00:00
od: implement --traditional
This commit is contained in:
parent
26ec46835c
commit
2f12b06ba1
50
src/od/od.rs
50
src/od/od.rs
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
1
tests/fixtures/od/0
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
zero
|
|
@ -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)
|
||||
"));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue