mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge pull request #871 from modelorganism/odupdate
add functionality to od
This commit is contained in:
commit
57693af48d
3 changed files with 266 additions and 42 deletions
1
Makefile
1
Makefile
|
@ -149,6 +149,7 @@ TEST_PROGS := \
|
|||
mktemp \
|
||||
mv \
|
||||
nl \
|
||||
od \
|
||||
paste \
|
||||
printf \
|
||||
ptx \
|
||||
|
|
169
src/od/od.rs
169
src/od/od.rs
|
@ -14,11 +14,19 @@ extern crate getopts;
|
|||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::mem;
|
||||
use std::path::Path;
|
||||
use std::io::BufReader;
|
||||
use std::io::Write;
|
||||
use std::io;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Radix { Decimal, Hexadecimal, Octal, Binary }
|
||||
|
||||
#[derive(Debug)]
|
||||
enum InputSource<'a> {
|
||||
FileName(&'a str ),
|
||||
Stdin
|
||||
}
|
||||
|
||||
pub fn uumain(args: Vec<String>) -> i32 {
|
||||
let mut opts = getopts::Options::new();
|
||||
|
||||
|
@ -51,50 +59,137 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
|||
Err(f) => { panic!("Invalid -A/--address-radix\n{}", f) }
|
||||
};
|
||||
|
||||
let fname = match args.last() {
|
||||
Some(n) => n,
|
||||
None => { panic!("Need fname for now") ; }
|
||||
};
|
||||
// Gather up file names - args which don't start with '-'
|
||||
let fnames = args[1..]
|
||||
.iter()
|
||||
.filter(|w| !w.starts_with('-') || w == &"--" ) // "--" starts with '-', but it denotes stdin, not a flag
|
||||
.map(|x| match x.as_str() { "--" => InputSource::Stdin, x => InputSource::FileName(x)})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
odfunc(&input_offset_base, &fname);
|
||||
|
||||
0
|
||||
// With no filenames, od uses stdin as input.
|
||||
if fnames.len() == 0 {
|
||||
odfunc(&input_offset_base, &[InputSource::Stdin])
|
||||
}
|
||||
else {
|
||||
odfunc(&input_offset_base, &fnames)
|
||||
}
|
||||
}
|
||||
|
||||
fn odfunc(input_offset_base: &Radix, fname: &str) {
|
||||
let mut f = match File::open(Path::new(fname)) {
|
||||
Ok(f) => f,
|
||||
Err(e) => panic!("file error: {}", e)
|
||||
};
|
||||
const LINEBYTES:usize = 16;
|
||||
const WORDBYTES:usize = 2;
|
||||
|
||||
let mut addr = 0;
|
||||
let bytes = &mut [b'\x00'; 16];
|
||||
loop {
|
||||
match f.read(bytes) {
|
||||
Ok(0) => {
|
||||
print_with_radix(input_offset_base, addr);
|
||||
print!("\n");
|
||||
break;
|
||||
}
|
||||
Ok(n) => {
|
||||
print_with_radix(input_offset_base, addr);
|
||||
for b in 0 .. n / mem::size_of::<u16>() {
|
||||
let bs = &bytes[(2 * b) .. (2 * b + 2)];
|
||||
let p: u16 = (bs[1] as u16) << 8 | bs[0] as u16;
|
||||
print!(" {:06o}", p);
|
||||
fn odfunc(input_offset_base: &Radix, fnames: &[InputSource]) -> i32 {
|
||||
|
||||
let mut status = 0;
|
||||
let mut ni = fnames.iter();
|
||||
{
|
||||
// Open and return the next file to process as a BufReader
|
||||
// Returns None when no more files.
|
||||
let mut next_file = || -> Option<Box<io::Read>> {
|
||||
// loop retries with subsequent files if err - normally 'loops' once
|
||||
loop {
|
||||
match ni.next() {
|
||||
None => return None,
|
||||
Some(input) => match *input {
|
||||
InputSource::Stdin => return Some(Box::new(BufReader::new(std::io::stdin()))),
|
||||
InputSource::FileName(fname) => match File::open(fname) {
|
||||
Ok(f) => return Some(Box::new(BufReader::new(f))),
|
||||
Err(e) => {
|
||||
// If any file can't be opened,
|
||||
// print an error at the time that the file is needed,
|
||||
// then move on the the next file.
|
||||
// This matches the behavior of the original `od`
|
||||
let _ = writeln!(&mut std::io::stderr(), "od: '{}': {}", fname, e);
|
||||
if status == 0 {status = 1}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if n % mem::size_of::<u16>() == 1 {
|
||||
print!(" {:06o}", bytes[n - 1]);
|
||||
}
|
||||
print!("\n");
|
||||
addr += n;
|
||||
},
|
||||
Err(_) => {
|
||||
print_with_radix(input_offset_base, addr);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
let mut curr_file: Box<io::Read> = match next_file() {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
let mut exhausted = false; // There is no more input, gone to the end of the last file.
|
||||
|
||||
// Fill buf with bytes read from the list of files
|
||||
// Returns Ok(<number of bytes read>)
|
||||
// Handles io errors itself, thus always returns OK
|
||||
// Fills the provided buffer completely, unless it has run out of input.
|
||||
// If any call returns short (< buf.len()), all subsequent calls will return Ok<0>
|
||||
let mut f_read = |buf: &mut [u8]| -> io::Result<usize> {
|
||||
if exhausted {
|
||||
Ok(0)
|
||||
} else {
|
||||
let mut xfrd = 0;
|
||||
// while buffer we are filling is not full.. May go thru several files.
|
||||
'fillloop: while xfrd < buf.len() {
|
||||
loop { // stdin may return on 'return' (enter), even though the buffer isn't full.
|
||||
xfrd += match curr_file.read(&mut buf[xfrd..]) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => n,
|
||||
Err(e) => panic!("file error: {}", e),
|
||||
};
|
||||
if xfrd == buf.len() {
|
||||
// transferred all that was asked for.
|
||||
break 'fillloop;
|
||||
}
|
||||
}
|
||||
curr_file = match next_file() {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
exhausted = true;
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(xfrd)
|
||||
}
|
||||
};
|
||||
|
||||
let mut addr = 0;
|
||||
let bytes = &mut [b'\x00'; LINEBYTES];
|
||||
loop { // print each line
|
||||
print_with_radix(input_offset_base, addr); // print offset
|
||||
match f_read(bytes) {
|
||||
Ok(0) => {
|
||||
print!("\n");
|
||||
break;
|
||||
}
|
||||
Ok(n) => {
|
||||
print!(" "); // 4 spaces after offset - we print 2 more before each word
|
||||
|
||||
for b in 0 .. n / mem::size_of::<u16>() {
|
||||
let bs = &bytes[(2 * b) .. (2 * b + 2)];
|
||||
let p: u16 = (bs[1] as u16) << 8 | bs[0] as u16;
|
||||
print!(" {:06o}", p);
|
||||
}
|
||||
if n % mem::size_of::<u16>() == 1 {
|
||||
print!(" {:06o}", bytes[n - 1]);
|
||||
}
|
||||
|
||||
// Add extra spaces to pad out the short, presumably last, line.
|
||||
if n<LINEBYTES {
|
||||
// calc # of items we did not print, must be short at least WORDBYTES to be missing any.
|
||||
let words_short = (LINEBYTES-n)/WORDBYTES;
|
||||
print!("{:>width$}", "", width=(words_short)*(6+2));
|
||||
}
|
||||
|
||||
print!("\n");
|
||||
addr += n;
|
||||
},
|
||||
Err(_) => {
|
||||
break;
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
status
|
||||
}
|
||||
|
||||
fn parse_radix(radix_str: Option<String>) -> Result<Radix, &'static str> {
|
||||
|
|
128
tests/od.rs
Normal file
128
tests/od.rs
Normal file
|
@ -0,0 +1,128 @@
|
|||
#[macro_use]
|
||||
mod common;
|
||||
|
||||
use common::util::*;
|
||||
use std::path::Path;
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
use std::fs::File;
|
||||
use std::fs::remove_file;
|
||||
|
||||
static UTIL_NAME: &'static str = "od";
|
||||
|
||||
// octal dump of 'abcdefghijklmnopqrstuvwxyz\n'
|
||||
static ALPHA_OUT: &'static str = "0000000 061141 062143 063145 064147 065151 066153 067155 070157\n0000020 071161 072163 073165 074167 075171 000012 \n0000033\n";
|
||||
|
||||
// XXX We could do a better job of ensuring that we have a fresh temp dir to ourself,
|
||||
// not a general one ful of other proc's leftovers.
|
||||
|
||||
// Test that od can read one file and dump with default format
|
||||
#[test]
|
||||
fn test_file() {
|
||||
let (_, mut ucmd) = testing(UTIL_NAME);
|
||||
use std::env;
|
||||
let temp = env::temp_dir();
|
||||
let tmpdir = Path::new(&temp);
|
||||
let file = tmpdir.join("test");
|
||||
|
||||
{
|
||||
let mut f = File::create(&file).unwrap();
|
||||
match f.write_all(b"abcdefghijklmnopqrstuvwxyz\n") {
|
||||
Err(_) => panic!("Test setup failed - could not write file"),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let result = ucmd.arg(file.as_os_str()).run();
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, ALPHA_OUT);
|
||||
|
||||
let _ = remove_file(file);
|
||||
}
|
||||
|
||||
// Test that od can read 2 files and concatenate the contents
|
||||
#[test]
|
||||
fn test_2files() {
|
||||
let (_, mut ucmd) = testing(UTIL_NAME);
|
||||
let temp = env::temp_dir();
|
||||
let tmpdir = Path::new(&temp);
|
||||
let file1 = tmpdir.join("test1");
|
||||
let file2 = tmpdir.join("test2");
|
||||
|
||||
for &(n,a) in [(1,"a"), (2,"b")].iter() {
|
||||
println!("number: {} letter:{}", n, a);
|
||||
}
|
||||
|
||||
for &(path,data)in &[(&file1, "abcdefghijklmnop"),(&file2, "qrstuvwxyz\n")] {
|
||||
let mut f = File::create(&path).unwrap();
|
||||
match f.write_all(data.as_bytes()) {
|
||||
Err(_) => panic!("Test setup failed - could not write file"),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let result = ucmd.arg(file1.as_os_str()).arg(file2.as_os_str()).run();
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, ALPHA_OUT);
|
||||
|
||||
let _ = remove_file(file1);
|
||||
let _ = remove_file(file2);
|
||||
}
|
||||
|
||||
// Test that od gives non-0 exit val for filename that dosen't exist.
|
||||
#[test]
|
||||
fn test_no_file() {
|
||||
let (_, mut ucmd) = testing(UTIL_NAME);
|
||||
let temp = env::temp_dir();
|
||||
let tmpdir = Path::new(&temp);
|
||||
let file = tmpdir.join("}surely'none'would'thus'a'file'name");
|
||||
|
||||
let result = ucmd.arg(file.as_os_str()).run();
|
||||
|
||||
assert!(!result.success);
|
||||
}
|
||||
|
||||
// Test that od reads from stdin instead of a file
|
||||
#[test]
|
||||
fn test_from_stdin() {
|
||||
let (_, mut ucmd) = testing(UTIL_NAME);
|
||||
|
||||
let input = "abcdefghijklmnopqrstuvwxyz\n";
|
||||
let result = ucmd.run_piped_stdin(input.as_bytes());
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, ALPHA_OUT);
|
||||
|
||||
}
|
||||
|
||||
// Test that od reads from stdin and also from files
|
||||
#[test]
|
||||
fn test_from_mixed() {
|
||||
let (_, mut ucmd) = testing(UTIL_NAME);
|
||||
|
||||
let temp = env::temp_dir();
|
||||
let tmpdir = Path::new(&temp);
|
||||
let file1 = tmpdir.join("test-1");
|
||||
let file3 = tmpdir.join("test-3");
|
||||
|
||||
let (data1, data2, data3) = ("abcdefg","hijklmnop","qrstuvwxyz\n");
|
||||
for &(path,data)in &[(&file1, data1),(&file3, data3)] {
|
||||
let mut f = File::create(&path).unwrap();
|
||||
match f.write_all(data.as_bytes()) {
|
||||
Err(_) => panic!("Test setup failed - could not write file"),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let result = ucmd.arg(file1.as_os_str()).arg("--").arg(file3.as_os_str()).run_piped_stdin(data2.as_bytes());
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, ALPHA_OUT);
|
||||
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue