mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
od: implement +size to skip bytes
This commit is contained in:
parent
9e33c3a48c
commit
26ec46835c
3 changed files with 322 additions and 21 deletions
48
src/od/od.rs
48
src/od/od.rs
|
@ -26,6 +26,7 @@ mod prn_char;
|
|||
mod prn_float;
|
||||
mod parse_nrofbytes;
|
||||
mod parse_formats;
|
||||
mod parse_inputs;
|
||||
#[cfg(test)]
|
||||
mod mockstream;
|
||||
|
||||
|
@ -37,9 +38,10 @@ use multifilereader::*;
|
|||
use partialreader::*;
|
||||
use peekreader::*;
|
||||
use formatteriteminfo::*;
|
||||
use parse_nrofbytes::*;
|
||||
use parse_nrofbytes::parse_number_of_bytes;
|
||||
use parse_formats::{parse_format_flags, ParsedFormatterItemInfo};
|
||||
use prn_char::format_ascii_dump;
|
||||
use parse_inputs::{parse_inputs, CommandLineInputs};
|
||||
|
||||
static VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
const MAX_BYTES_PER_UNIT: usize = 8;
|
||||
|
@ -134,17 +136,33 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
|||
}
|
||||
};
|
||||
|
||||
// Gather up file names
|
||||
let mut inputs = matches.free
|
||||
let mut skip_bytes = match matches.opt_default("skip-bytes", "0") {
|
||||
None => 0,
|
||||
Some(s) => {
|
||||
match parse_number_of_bytes(&s) {
|
||||
Ok(i) => { i }
|
||||
Err(_) => {
|
||||
disp_err!("Invalid argument --skip-bytes={}", s);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let input_strings = match parse_inputs(&matches) {
|
||||
CommandLineInputs::FileNames(v) => v,
|
||||
CommandLineInputs::FileAndOffset((f, s, _)) => {
|
||||
skip_bytes = s;
|
||||
vec!{f}
|
||||
},
|
||||
};
|
||||
let inputs = input_strings
|
||||
.iter()
|
||||
.filter_map(|w| match w as &str {
|
||||
"-" => Some(InputSource::Stdin),
|
||||
x => Some(InputSource::FileName(x)),
|
||||
.map(|w| match w as &str {
|
||||
"-" => InputSource::Stdin,
|
||||
x => InputSource::FileName(x),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if inputs.len() == 0 {
|
||||
inputs.push(InputSource::Stdin);
|
||||
}
|
||||
|
||||
let formats = match parse_format_flags(&args) {
|
||||
Ok(f) => f,
|
||||
|
@ -171,18 +189,6 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
|||
|
||||
let output_duplicates = matches.opt_present("v");
|
||||
|
||||
let skip_bytes = match matches.opt_default("skip-bytes", "0") {
|
||||
None => 0,
|
||||
Some(s) => {
|
||||
match parse_number_of_bytes(&s) {
|
||||
Ok(i) => { i }
|
||||
Err(_) => {
|
||||
disp_err!("Invalid argument --skip-bytes={}", s);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let read_bytes = match matches.opt_str("read-bytes") {
|
||||
None => None,
|
||||
Some(s) => {
|
||||
|
|
268
src/od/parse_inputs.rs
Normal file
268
src/od/parse_inputs.rs
Normal file
|
@ -0,0 +1,268 @@
|
|||
use getopts::Matches;
|
||||
|
||||
/// Abstraction for getopts
|
||||
pub trait CommandLineOpts {
|
||||
/// returns all commandline parameters which do not belong to an option.
|
||||
fn inputs(&self) -> Vec<String>;
|
||||
/// tests if any of the specified options is present.
|
||||
fn opts_present(&self, &[&str]) -> bool;
|
||||
}
|
||||
|
||||
/// Implementation for `getopts`
|
||||
impl CommandLineOpts for Matches {
|
||||
fn inputs(&self) -> Vec<String> {
|
||||
self.free.clone()
|
||||
}
|
||||
fn opts_present(&self, opts: &[&str]) -> bool {
|
||||
self.opts_present(&opts.iter().map(|s| s.to_string()).collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains the Input filename(s) with an optional offset.
|
||||
///
|
||||
/// `FileNames` is used for one or more file inputs ("-" = stdin)
|
||||
/// `FileAndOffset` is used for a single file input, with an offset
|
||||
/// and an optional label. Offset and label are specified in bytes.
|
||||
/// `FileAndOffset` will be only used if an offset is specified,
|
||||
/// but it might be 0.
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum CommandLineInputs {
|
||||
FileNames(Vec<String>),
|
||||
FileAndOffset((String, usize, Option<usize>)),
|
||||
}
|
||||
|
||||
|
||||
/// Interprets the commandline inputs of od.
|
||||
///
|
||||
/// Returns either an unspecified number of filenames.
|
||||
/// Or it will return a single filename, with an offset and optional label.
|
||||
/// 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 {
|
||||
|
||||
let mut input_strings: Vec<String> = matches.inputs();
|
||||
|
||||
// test if commandline contains: [file] <offset>
|
||||
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"]) {
|
||||
// test if the last input can be parsed as an offset.
|
||||
let offset=parse_offset_operand(&input_strings[input_strings.len()-1]);
|
||||
match offset {
|
||||
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));
|
||||
}
|
||||
if input_strings.len() == 2 {
|
||||
return CommandLineInputs::FileAndOffset((input_strings[0].clone(), n, None));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// if it cannot be parsed, it is considered a filename
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if input_strings.len() == 0 {
|
||||
input_strings.push("-".to_string());
|
||||
}
|
||||
CommandLineInputs::FileNames(input_strings)
|
||||
}
|
||||
|
||||
/// parses format used by offset and label on the commandline
|
||||
pub fn parse_offset_operand(s: &String) -> Result<usize, &'static str> {
|
||||
let mut start = 0;
|
||||
let mut len = s.len();
|
||||
let mut radix = 8;
|
||||
let mut multiply = 1;
|
||||
|
||||
if s.starts_with("+") {
|
||||
start += 1;
|
||||
}
|
||||
|
||||
if s[start..len].starts_with("0x") || s[start..len].starts_with("0X") {
|
||||
start += 2;
|
||||
radix = 16;
|
||||
}
|
||||
else {
|
||||
if s[start..len].ends_with("b") {
|
||||
len -= 1;
|
||||
multiply = 512;
|
||||
}
|
||||
if s[start..len].ends_with(".") {
|
||||
len -= 1;
|
||||
radix = 10;
|
||||
}
|
||||
}
|
||||
match usize::from_str_radix(&s[start..len], radix) {
|
||||
Ok(i) => Ok(i * multiply),
|
||||
Err(_) => Err("parse failed"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A mock for the commandline options type
|
||||
///
|
||||
/// `inputs` are all commandline parameters which do not belong to an option.
|
||||
/// `option_names` are the names of the options on the commandline.
|
||||
struct MockOptions<'a> {
|
||||
inputs: Vec<String>,
|
||||
option_names: Vec<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> MockOptions<'a> {
|
||||
fn new(inputs: Vec<&'a str>, option_names: Vec<&'a str>) -> MockOptions<'a> {
|
||||
MockOptions {
|
||||
inputs: inputs.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
|
||||
option_names: option_names,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> CommandLineOpts for MockOptions<'a> {
|
||||
fn inputs(&self) -> Vec<String> {
|
||||
self.inputs.clone()
|
||||
}
|
||||
fn opts_present(&self, opts: &[&str]) -> bool {
|
||||
for expected in opts.iter() {
|
||||
for actual in self.option_names.iter() {
|
||||
if *expected==*actual {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_inputs_normal() {
|
||||
|
||||
assert_eq!(CommandLineInputs::FileNames(vec!{"-".to_string()}),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!{},
|
||||
vec!{})));
|
||||
|
||||
assert_eq!(CommandLineInputs::FileNames(vec!{"-".to_string()}),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!{"-"},
|
||||
vec!{})));
|
||||
|
||||
assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string()}),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!{"file1"},
|
||||
vec!{})));
|
||||
|
||||
assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string(), "file2".to_string()}),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!{"file1", "file2"},
|
||||
vec!{})));
|
||||
|
||||
assert_eq!(CommandLineInputs::FileNames(vec!{"-".to_string(), "file1".to_string(), "file2".to_string()}),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!{"-", "file1", "file2"},
|
||||
vec!{})));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_inputs_with_offset() {
|
||||
// offset is found without filename, so stdin will be used.
|
||||
assert_eq!(CommandLineInputs::FileAndOffset(("-".to_string(), 8, None)),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!{"+10"},
|
||||
vec!{})));
|
||||
|
||||
// offset must start with "+" if no input is specified.
|
||||
assert_eq!(CommandLineInputs::FileNames(vec!{"10".to_string()}),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!{"10"},
|
||||
vec!{""})));
|
||||
|
||||
// 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!{""})));
|
||||
|
||||
// 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"})));
|
||||
|
||||
// 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"})));
|
||||
|
||||
assert_eq!(CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!{"file1", "+10"},
|
||||
vec!{})));
|
||||
|
||||
// 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!{})));
|
||||
|
||||
assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string(), "+10a".to_string()}),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!{"file1", "+10a"},
|
||||
vec!{""})));
|
||||
|
||||
assert_eq!(CommandLineInputs::FileNames(vec!{"file1".to_string(), "+10".to_string()}),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!{"file1", "+10"},
|
||||
vec!{"j"})));
|
||||
|
||||
// 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!{""})));
|
||||
}
|
||||
|
||||
fn parse_offset_operand_str(s: &str) -> Result<usize, &'static str> {
|
||||
parse_offset_operand(&String::from(s))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_offset_operand_invalid() {
|
||||
parse_offset_operand_str("").unwrap_err();
|
||||
parse_offset_operand_str("a").unwrap_err();
|
||||
parse_offset_operand_str("+").unwrap_err();
|
||||
parse_offset_operand_str("+b").unwrap_err();
|
||||
parse_offset_operand_str("0x1.").unwrap_err();
|
||||
parse_offset_operand_str("0x1.b").unwrap_err();
|
||||
parse_offset_operand_str("-").unwrap_err();
|
||||
parse_offset_operand_str("-1").unwrap_err();
|
||||
parse_offset_operand_str("1e10").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_offset_operand() {
|
||||
assert_eq!(8, parse_offset_operand_str("10").unwrap()); // default octal
|
||||
assert_eq!(0, parse_offset_operand_str("0").unwrap());
|
||||
assert_eq!(8, parse_offset_operand_str("+10").unwrap()); // optional leading '+'
|
||||
assert_eq!(16, parse_offset_operand_str("0x10").unwrap()); // hex
|
||||
assert_eq!(16, parse_offset_operand_str("0X10").unwrap()); // hex
|
||||
assert_eq!(16, parse_offset_operand_str("+0X10").unwrap()); // hex
|
||||
assert_eq!(10, parse_offset_operand_str("10.").unwrap()); // decimal
|
||||
assert_eq!(10, parse_offset_operand_str("+10.").unwrap()); // decimal
|
||||
assert_eq!(4096, parse_offset_operand_str("10b").unwrap()); // b suffix = *512
|
||||
assert_eq!(4096, parse_offset_operand_str("+10b").unwrap()); // b suffix = *512
|
||||
assert_eq!(5120, parse_offset_operand_str("10.b").unwrap()); // b suffix = *512
|
||||
assert_eq!(5120, parse_offset_operand_str("+10.b").unwrap()); // b suffix = *512
|
||||
assert_eq!(267, parse_offset_operand_str("0x10b").unwrap()); // hex
|
||||
}
|
||||
|
||||
}
|
|
@ -564,3 +564,30 @@ fn test_filename_parsing(){
|
|||
000012
|
||||
"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stdin_offset(){
|
||||
|
||||
let input = "abcdefghijklmnopq";
|
||||
let result = new_ucmd!().arg("-c").arg("+5").run_piped_stdin(input.as_bytes());
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, unindent("
|
||||
0000005 f g h i j k l m n o p q
|
||||
0000021
|
||||
"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_offset(){
|
||||
|
||||
let result = new_ucmd!().arg("-c").arg("--").arg("-f").arg("10").run();
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, unindent(r"
|
||||
0000010 w e r c a s e f \n
|
||||
0000022
|
||||
"));
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue