mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 11:37:44 +00:00
Merge pull request #965 from wimh/od
od: implement remaining functionality
This commit is contained in:
commit
4f5e8f4566
26 changed files with 3809 additions and 442 deletions
|
@ -1,2 +1,4 @@
|
|||
CONFIG_FEATURE_FANCY_HEAD=y
|
||||
CONFIG_UNICODE_SUPPORT=y
|
||||
CONFIG_DESKTOP=y
|
||||
CONFIG_LONG_OPTS=y
|
||||
|
|
19
Cargo.lock
generated
19
Cargo.lock
generated
|
@ -90,6 +90,7 @@ dependencies = [
|
|||
"tty 0.0.1",
|
||||
"uname 0.0.1",
|
||||
"unexpand 0.0.1",
|
||||
"unindent 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uniq 0.0.1",
|
||||
"unlink 0.0.1",
|
||||
"uptime 0.0.1",
|
||||
|
@ -175,6 +176,11 @@ name = "bitflags"
|
|||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "cat"
|
||||
version = "0.0.1"
|
||||
|
@ -386,6 +392,11 @@ dependencies = [
|
|||
"uucore 0.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "hashsum"
|
||||
version = "0.0.1"
|
||||
|
@ -657,8 +668,11 @@ dependencies = [
|
|||
name = "od"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"half 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"uucore 0.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1129,6 +1143,11 @@ name = "unicode-width"
|
|||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unindent"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "uniq"
|
||||
version = "0.0.1"
|
||||
|
|
|
@ -203,6 +203,7 @@ libc = "*"
|
|||
regex="*"
|
||||
rand="*"
|
||||
tempdir="*"
|
||||
unindent="*"
|
||||
|
||||
[[bin]]
|
||||
name = "uutils"
|
||||
|
|
|
@ -201,7 +201,7 @@ To do
|
|||
* [x] nohup
|
||||
* [x] nproc
|
||||
* [ ] numfmt
|
||||
* [ ] od (in progress, needs lots of work)
|
||||
* [ ] od (almost complete, `--strings` and 128-bit datatypes are missing)
|
||||
* [x] paste
|
||||
* [x] pathchk
|
||||
* [x] pinky
|
||||
|
|
|
@ -10,6 +10,9 @@ path = "od.rs"
|
|||
[dependencies]
|
||||
getopts = "*"
|
||||
libc = "*"
|
||||
byteorder = "*"
|
||||
half = "*"
|
||||
uucore = { path="../uucore" }
|
||||
|
||||
[[bin]]
|
||||
name = "od"
|
||||
|
|
50
src/od/byteorder_io.rs
Normal file
50
src/od/byteorder_io.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
// workaround until https://github.com/BurntSushi/byteorder/issues/41 has been fixed
|
||||
// based on: https://github.com/netvl/immeta/blob/4460ee/src/utils.rs#L76
|
||||
|
||||
use byteorder::{NativeEndian, LittleEndian, BigEndian};
|
||||
use byteorder::ByteOrder as ByteOrderTrait;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum ByteOrder {
|
||||
Little,
|
||||
Big,
|
||||
Native,
|
||||
}
|
||||
|
||||
macro_rules! gen_byte_order_ops {
|
||||
($($read_name:ident, $write_name:ident -> $tpe:ty),+) => {
|
||||
impl ByteOrder {
|
||||
$(
|
||||
#[allow(dead_code)]
|
||||
#[inline]
|
||||
pub fn $read_name(self, source: &[u8]) -> $tpe {
|
||||
match self {
|
||||
ByteOrder::Little => LittleEndian::$read_name(source),
|
||||
ByteOrder::Big => BigEndian::$read_name(source),
|
||||
ByteOrder::Native => NativeEndian::$read_name(source),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn $write_name(self, target: &mut [u8], n: $tpe) {
|
||||
match self {
|
||||
ByteOrder::Little => LittleEndian::$write_name(target, n),
|
||||
ByteOrder::Big => BigEndian::$write_name(target, n),
|
||||
ByteOrder::Native => NativeEndian::$write_name(target, n),
|
||||
}
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gen_byte_order_ops! {
|
||||
read_u16, write_u16 -> u16,
|
||||
read_u32, write_u32 -> u32,
|
||||
read_u64, write_u64 -> u64,
|
||||
read_i16, write_i16 -> i16,
|
||||
read_i32, write_i32 -> i32,
|
||||
read_i64, write_i64 -> i64,
|
||||
read_f32, write_f32 -> f32,
|
||||
read_f64, write_f64 -> f64
|
||||
}
|
56
src/od/formatteriteminfo.rs
Normal file
56
src/od/formatteriteminfo.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use std::fmt;
|
||||
|
||||
#[derive(Copy)]
|
||||
pub enum FormatWriter {
|
||||
IntWriter(fn(u64) -> String),
|
||||
FloatWriter(fn(f64) -> String),
|
||||
MultibyteWriter(fn(&[u8]) -> String),
|
||||
}
|
||||
|
||||
impl Clone for FormatWriter {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for FormatWriter {
|
||||
fn eq(&self, other: &FormatWriter) -> bool {
|
||||
use formatteriteminfo::FormatWriter::*;
|
||||
|
||||
match (self, other) {
|
||||
(&IntWriter(ref a), &IntWriter(ref b)) => a == b,
|
||||
(&FloatWriter(ref a), &FloatWriter(ref b)) => a == b,
|
||||
(&MultibyteWriter(ref a), &MultibyteWriter(ref b)) => *a as usize == *b as usize,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for FormatWriter {}
|
||||
|
||||
impl fmt::Debug for FormatWriter {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
&FormatWriter::IntWriter(ref p) => {
|
||||
try!(f.write_str("IntWriter:"));
|
||||
fmt::Pointer::fmt(p, f)
|
||||
},
|
||||
&FormatWriter::FloatWriter(ref p) => {
|
||||
try!(f.write_str("FloatWriter:"));
|
||||
fmt::Pointer::fmt(p, f)
|
||||
},
|
||||
&FormatWriter::MultibyteWriter(ref p) => {
|
||||
try!(f.write_str("MultibyteWriter:"));
|
||||
fmt::Pointer::fmt(&(*p as *const ()), f)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct FormatterItemInfo {
|
||||
pub byte_size: usize,
|
||||
pub print_width: usize, // including a space in front of the text
|
||||
pub formatter: FormatWriter,
|
||||
}
|
183
src/od/inputdecoder.rs
Normal file
183
src/od/inputdecoder.rs
Normal file
|
@ -0,0 +1,183 @@
|
|||
use std::io;
|
||||
use byteorder_io::ByteOrder;
|
||||
use multifilereader::HasError;
|
||||
use peekreader::PeekRead;
|
||||
use half::f16;
|
||||
|
||||
/// Processes an input and provides access to the data read in various formats
|
||||
///
|
||||
/// Currently only useful if the input implements `PeekRead`.
|
||||
pub struct InputDecoder<'a, I> where I: 'a {
|
||||
/// The input from which data is read
|
||||
input: &'a mut I,
|
||||
|
||||
/// A memory buffer, it's size is set in `new`.
|
||||
data: Vec<u8>,
|
||||
/// The numer of bytes in the buffer reserved for the peek data from `PeekRead`.
|
||||
reserved_peek_length: usize,
|
||||
|
||||
/// The number of (valid) bytes in the buffer.
|
||||
used_normal_length: usize,
|
||||
/// The number of peek bytes in the buffer.
|
||||
used_peek_length: usize,
|
||||
|
||||
/// Byte order used to read data from the buffer.
|
||||
byte_order: ByteOrder,
|
||||
}
|
||||
|
||||
impl<'a, I> InputDecoder<'a, I> {
|
||||
/// Creates a new `InputDecoder` with an allocated buffer of `normal_length` + `peek_length` bytes.
|
||||
/// `byte_order` determines how to read multibyte formats from the buffer.
|
||||
pub fn new(input: &mut I, normal_length: usize, peek_length: usize, byte_order: ByteOrder) -> InputDecoder<I> {
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(normal_length + peek_length);
|
||||
unsafe { bytes.set_len(normal_length + peek_length); } // fast but uninitialized
|
||||
|
||||
InputDecoder {
|
||||
input: input,
|
||||
data: bytes,
|
||||
reserved_peek_length: peek_length,
|
||||
used_normal_length: 0,
|
||||
used_peek_length: 0,
|
||||
byte_order: byte_order,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<'a, I> InputDecoder<'a, I> where I: PeekRead {
|
||||
/// calls `peek_read` on the internal stream to (re)fill the buffer. Returns a
|
||||
/// MemoryDecoder providing access to the result or returns an i/o error.
|
||||
pub fn peek_read(&mut self) -> io::Result<MemoryDecoder> {
|
||||
match self.input.peek_read(self.data.as_mut_slice(), self.reserved_peek_length) {
|
||||
Ok((n, p)) => {
|
||||
self.used_normal_length = n;
|
||||
self.used_peek_length = p;
|
||||
Ok(MemoryDecoder {
|
||||
data: &mut self.data,
|
||||
used_normal_length: self.used_normal_length,
|
||||
used_peek_length: self.used_peek_length,
|
||||
byte_order: self.byte_order,
|
||||
})
|
||||
},
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I> HasError for InputDecoder<'a, I> where I: HasError {
|
||||
/// calls has_error on the internal stream.
|
||||
fn has_error(&self) -> bool {
|
||||
self.input.has_error()
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides access to the internal data in various formats
|
||||
pub struct MemoryDecoder<'a> {
|
||||
/// A reference to the parents' data
|
||||
data: &'a mut Vec<u8>,
|
||||
/// The number of (valid) bytes in the buffer.
|
||||
used_normal_length: usize,
|
||||
/// The number of peek bytes in the buffer.
|
||||
used_peek_length: usize,
|
||||
/// Byte order used to read data from the buffer.
|
||||
byte_order: ByteOrder,
|
||||
}
|
||||
|
||||
impl<'a> MemoryDecoder<'a> {
|
||||
/// Set a part of the internal buffer to zero.
|
||||
/// access to the whole buffer is possible, not just to the valid data.
|
||||
pub fn zero_out_buffer(&mut self, start:usize, end:usize) {
|
||||
for i in start..end {
|
||||
self.data[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current length of the buffer. (ie. how much valid data it contains.)
|
||||
pub fn length(&self) -> usize {
|
||||
self.used_normal_length
|
||||
}
|
||||
|
||||
/// Creates a clone of the internal buffer. The clone only contain the valid data.
|
||||
pub fn clone_buffer(&self, other: &mut Vec<u8>) {
|
||||
other.clone_from(&self.data);
|
||||
other.resize(self.used_normal_length, 0);
|
||||
}
|
||||
|
||||
/// Returns a slice to the internal buffer starting at `start`.
|
||||
pub fn get_buffer(&self, start: usize) -> &[u8] {
|
||||
&self.data[start..self.used_normal_length]
|
||||
}
|
||||
|
||||
/// Returns a slice to the internal buffer including the peek data starting at `start`.
|
||||
pub fn get_full_buffer(&self, start: usize) -> &[u8] {
|
||||
&self.data[start..self.used_normal_length + self.used_peek_length]
|
||||
}
|
||||
|
||||
/// Returns a u8/u16/u32/u64 from the internal buffer at position `start`.
|
||||
pub fn read_uint(&self, start: usize, byte_size: usize) -> u64 {
|
||||
match byte_size {
|
||||
1 => self.data[start] as u64,
|
||||
2 => self.byte_order.read_u16(&self.data[start..start + 2]) as u64,
|
||||
4 => self.byte_order.read_u32(&self.data[start..start + 4]) as u64,
|
||||
8 => self.byte_order.read_u64(&self.data[start..start + 8]),
|
||||
_ => panic!("Invalid byte_size: {}", byte_size),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a f32/f64 from the internal buffer at position `start`.
|
||||
pub fn read_float(&self, start: usize, byte_size: usize) -> f64 {
|
||||
match byte_size {
|
||||
2 => f64::from(f16::from_bits(self.byte_order.read_u16(&self.data[start..start + 2]))),
|
||||
4 => self.byte_order.read_f32(&self.data[start..start + 4]) as f64,
|
||||
8 => self.byte_order.read_f64(&self.data[start..start + 8]),
|
||||
_ => panic!("Invalid byte_size: {}", byte_size),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::Cursor;
|
||||
use peekreader::PeekReader;
|
||||
use byteorder_io::ByteOrder;
|
||||
|
||||
#[test]
|
||||
fn smoke_test() {
|
||||
let data = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xff, 0xff];
|
||||
let mut input = PeekReader::new(Cursor::new(&data));
|
||||
let mut sut = InputDecoder::new(&mut input, 8, 2, ByteOrder::Little);
|
||||
|
||||
match sut.peek_read() {
|
||||
Ok(mut mem) => {
|
||||
assert_eq!(8, mem.length());
|
||||
|
||||
assert_eq!(-2.0, mem.read_float(0, 8));
|
||||
assert_eq!(-2.0, mem.read_float(4, 4));
|
||||
assert_eq!(0xc000000000000000, mem.read_uint(0, 8));
|
||||
assert_eq!(0xc0000000, mem.read_uint(4, 4));
|
||||
assert_eq!(0xc000, mem.read_uint(6, 2));
|
||||
assert_eq!(0xc0, mem.read_uint(7, 1));
|
||||
assert_eq!(&[0, 0xc0], mem.get_buffer(6));
|
||||
assert_eq!(&[0, 0xc0, 0xff, 0xff], mem.get_full_buffer(6));
|
||||
|
||||
let mut copy: Vec<u8> = Vec::new();
|
||||
mem.clone_buffer(&mut copy);
|
||||
assert_eq!(vec![0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0], copy);
|
||||
|
||||
mem.zero_out_buffer(7, 8);
|
||||
assert_eq!(&[0, 0, 0xff, 0xff], mem.get_full_buffer(6));
|
||||
}
|
||||
Err(e) => { assert!(false, e); }
|
||||
}
|
||||
|
||||
match sut.peek_read() {
|
||||
Ok(mem) => {
|
||||
assert_eq!(2, mem.length());
|
||||
assert_eq!(0xffff, mem.read_uint(0, 2));
|
||||
}
|
||||
Err(e) => { assert!(false, e); }
|
||||
}
|
||||
}
|
||||
}
|
110
src/od/inputoffset.rs
Normal file
110
src/od/inputoffset.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Radix { Decimal, Hexadecimal, Octal, NoPrefix }
|
||||
|
||||
/// provides the byte offset printed at the left margin
|
||||
pub struct InputOffset {
|
||||
/// The radix to print the byte offset. NoPrefix will not print a byte offset.
|
||||
radix: Radix,
|
||||
/// The current position. Initialize at `new`, increase using `increase_position`.
|
||||
byte_pos: usize,
|
||||
/// An optional label printed in parentheses, typically different from `byte_pos`,
|
||||
/// but will increase with the same value if `byte_pos` in increased.
|
||||
label: Option<usize>,
|
||||
}
|
||||
|
||||
impl InputOffset {
|
||||
/// creates a new `InputOffset` using the provided values.
|
||||
pub fn new(radix: Radix, byte_pos: usize, label: Option<usize>) -> InputOffset {
|
||||
InputOffset {
|
||||
radix: radix,
|
||||
byte_pos: byte_pos,
|
||||
label: label,
|
||||
}
|
||||
}
|
||||
|
||||
/// Increase `byte_pos` and `label` if a label is used.
|
||||
pub fn increase_position(&mut self, n: usize) {
|
||||
self.byte_pos += n;
|
||||
if let Some(l) = self.label {
|
||||
self.label = Some(l + n);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn set_radix(&mut self, radix: Radix) {
|
||||
self.radix = radix;
|
||||
}
|
||||
|
||||
/// returns a string with the current byte offset
|
||||
pub fn format_byte_offset(&self) -> String {
|
||||
match (self.radix, self.label) {
|
||||
(Radix::Decimal, None) => format!("{:07}", self.byte_pos),
|
||||
(Radix::Decimal, Some(l)) => format!("{:07} ({:07})", self.byte_pos, l),
|
||||
(Radix::Hexadecimal, None) => format!("{:06X}", self.byte_pos),
|
||||
(Radix::Hexadecimal, Some(l)) => format!("{:06X} ({:06X})", self.byte_pos, l),
|
||||
(Radix::Octal, None) => format!("{:07o}", self.byte_pos),
|
||||
(Radix::Octal, Some(l)) => format!("{:07o} ({:07o})", self.byte_pos, l),
|
||||
(Radix::NoPrefix, None) => String::from(""),
|
||||
(Radix::NoPrefix, Some(l)) => format!("({:07o})", l),
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the byte offset followed by a newline, or nothing at all if
|
||||
/// both `Radix::NoPrefix` was set and no label (--traditional) is used.
|
||||
pub fn print_final_offset(&self) {
|
||||
if self.radix != Radix::NoPrefix || self.label.is_some() {
|
||||
print!("{}\n", self.format_byte_offset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_input_offset() {
|
||||
let mut sut = InputOffset::new(Radix::Hexadecimal, 10, None);
|
||||
assert_eq!("00000A", &sut.format_byte_offset());
|
||||
sut.increase_position(10);
|
||||
assert_eq!("000014", &sut.format_byte_offset());
|
||||
|
||||
// note normally the radix will not change after initialisation
|
||||
sut.set_radix(Radix::Decimal);
|
||||
assert_eq!("0000020", &sut.format_byte_offset());
|
||||
|
||||
sut.set_radix(Radix::Hexadecimal);
|
||||
assert_eq!("000014", &sut.format_byte_offset());
|
||||
|
||||
sut.set_radix(Radix::Octal);
|
||||
assert_eq!("0000024", &sut.format_byte_offset());
|
||||
|
||||
sut.set_radix(Radix::NoPrefix);
|
||||
assert_eq!("", &sut.format_byte_offset());
|
||||
|
||||
sut.increase_position(10);
|
||||
sut.set_radix(Radix::Octal);
|
||||
assert_eq!("0000036", &sut.format_byte_offset());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_input_offset_with_label() {
|
||||
let mut sut = InputOffset::new(Radix::Hexadecimal, 10, Some(20));
|
||||
assert_eq!("00000A (000014)", &sut.format_byte_offset());
|
||||
sut.increase_position(10);
|
||||
assert_eq!("000014 (00001E)", &sut.format_byte_offset());
|
||||
|
||||
// note normally the radix will not change after initialisation
|
||||
sut.set_radix(Radix::Decimal);
|
||||
assert_eq!("0000020 (0000030)", &sut.format_byte_offset());
|
||||
|
||||
sut.set_radix(Radix::Hexadecimal);
|
||||
assert_eq!("000014 (00001E)", &sut.format_byte_offset());
|
||||
|
||||
sut.set_radix(Radix::Octal);
|
||||
assert_eq!("0000024 (0000036)", &sut.format_byte_offset());
|
||||
|
||||
sut.set_radix(Radix::NoPrefix);
|
||||
assert_eq!("(0000036)", &sut.format_byte_offset());
|
||||
|
||||
sut.increase_position(10);
|
||||
sut.set_radix(Radix::Octal);
|
||||
assert_eq!("0000036 (0000050)", &sut.format_byte_offset());
|
||||
}
|
101
src/od/mockstream.rs
Normal file
101
src/od/mockstream.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
// https://github.com/lazy-bitfield/rust-mockstream/pull/2
|
||||
|
||||
use std::io::{Cursor, Read, Result, Error, ErrorKind};
|
||||
use std::error::Error as errorError;
|
||||
|
||||
/// `FailingMockStream` mocks a stream which will fail upon read or write
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::io::{Cursor, Read};
|
||||
///
|
||||
/// struct CountIo {}
|
||||
///
|
||||
/// impl CountIo {
|
||||
/// fn read_data(&self, r: &mut Read) -> usize {
|
||||
/// let mut count: usize = 0;
|
||||
/// let mut retries = 3;
|
||||
///
|
||||
/// loop {
|
||||
/// let mut buffer = [0; 5];
|
||||
/// match r.read(&mut buffer) {
|
||||
/// Err(_) => {
|
||||
/// if retries == 0 { break; }
|
||||
/// retries -= 1;
|
||||
/// },
|
||||
/// Ok(0) => break,
|
||||
/// Ok(n) => count += n,
|
||||
/// }
|
||||
/// }
|
||||
/// count
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[test]
|
||||
/// fn test_io_retries() {
|
||||
/// let mut c = Cursor::new(&b"1234"[..])
|
||||
/// .chain(FailingMockStream::new(ErrorKind::Other, "Failing", 3))
|
||||
/// .chain(Cursor::new(&b"5678"[..]));
|
||||
///
|
||||
/// let sut = CountIo {};
|
||||
/// // this will fail unless read_data performs at least 3 retries on I/O errors
|
||||
/// assert_eq!(8, sut.read_data(&mut c));
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Clone)]
|
||||
pub struct FailingMockStream {
|
||||
kind: ErrorKind,
|
||||
message: &'static str,
|
||||
repeat_count: i32,
|
||||
}
|
||||
|
||||
impl FailingMockStream {
|
||||
/// Creates a FailingMockStream
|
||||
///
|
||||
/// When `read` or `write` is called, it will return an error `repeat_count` times.
|
||||
/// `kind` and `message` can be specified to define the exact error.
|
||||
pub fn new(kind: ErrorKind, message: &'static str, repeat_count: i32) -> FailingMockStream {
|
||||
FailingMockStream { kind: kind, message: message, repeat_count: repeat_count, }
|
||||
}
|
||||
|
||||
fn error(&mut self) -> Result<usize> {
|
||||
if self.repeat_count == 0 {
|
||||
return Ok(0)
|
||||
} else {
|
||||
if self.repeat_count > 0 {
|
||||
self.repeat_count -= 1;
|
||||
}
|
||||
Err(Error::new(self.kind, self.message))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for FailingMockStream {
|
||||
fn read(&mut self, _: &mut [u8]) -> Result<usize> {
|
||||
self.error()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_failing_mock_stream_read() {
|
||||
let mut s = FailingMockStream::new(ErrorKind::BrokenPipe, "The dog ate the ethernet cable", 1);
|
||||
let mut v = [0; 4];
|
||||
let error = s.read(v.as_mut()).unwrap_err();
|
||||
assert_eq!(error.kind(), ErrorKind::BrokenPipe);
|
||||
assert_eq!(error.description(), "The dog ate the ethernet cable");
|
||||
// after a single error, it will return Ok(0)
|
||||
assert_eq!(s.read(v.as_mut()).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_failing_mock_stream_chain_interrupted() {
|
||||
let mut c = Cursor::new(&b"abcd"[..])
|
||||
.chain(FailingMockStream::new(ErrorKind::Interrupted, "Interrupted", 5))
|
||||
.chain(Cursor::new(&b"ABCD"[..]));
|
||||
|
||||
let mut v = [0; 8];
|
||||
c.read_exact(v.as_mut()).unwrap();
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x41, 0x42, 0x43, 0x44]);
|
||||
assert_eq!(c.read(v.as_mut()).unwrap(), 0);
|
||||
}
|
194
src/od/multifilereader.rs
Normal file
194
src/od/multifilereader.rs
Normal file
|
@ -0,0 +1,194 @@
|
|||
use std;
|
||||
use std::io;
|
||||
use std::io::BufReader;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::vec::Vec;
|
||||
|
||||
pub enum InputSource<'a> {
|
||||
FileName(&'a str),
|
||||
Stdin,
|
||||
#[allow(dead_code)]
|
||||
Stream(Box<io::Read>),
|
||||
}
|
||||
|
||||
// MultifileReader - concatenate all our input, file or stdin.
|
||||
pub struct MultifileReader<'a> {
|
||||
ni: Vec<InputSource<'a>>,
|
||||
curr_file: Option<Box<io::Read>>,
|
||||
any_err: bool,
|
||||
}
|
||||
|
||||
pub trait HasError {
|
||||
fn has_error(&self) -> bool;
|
||||
}
|
||||
|
||||
impl<'b> MultifileReader<'b> {
|
||||
pub fn new<'a>(fnames: Vec<InputSource<'a>>) -> MultifileReader<'a> {
|
||||
let mut mf = MultifileReader {
|
||||
ni: fnames,
|
||||
curr_file: None, // normally this means done; call next_file()
|
||||
any_err: false,
|
||||
};
|
||||
mf.next_file();
|
||||
mf
|
||||
}
|
||||
|
||||
fn next_file(&mut self) {
|
||||
// loop retries with subsequent files if err - normally 'loops' once
|
||||
loop {
|
||||
if self.ni.len() == 0 {
|
||||
self.curr_file = None;
|
||||
break;
|
||||
}
|
||||
match self.ni.remove(0) {
|
||||
InputSource::Stdin => {
|
||||
self.curr_file = Some(Box::new(BufReader::new(std::io::stdin())));
|
||||
break;
|
||||
}
|
||||
InputSource::FileName(fname) => {
|
||||
match File::open(fname) {
|
||||
Ok(f) => {
|
||||
self.curr_file = Some(Box::new(BufReader::new(f)));
|
||||
break;
|
||||
}
|
||||
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`
|
||||
eprintln!("{}: '{}': {}",
|
||||
executable!().split("::").next().unwrap(), // remove module
|
||||
fname, e);
|
||||
self.any_err = true
|
||||
}
|
||||
}
|
||||
}
|
||||
InputSource::Stream(s) => {
|
||||
self.curr_file = Some(s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> io::Read for MultifileReader<'b> {
|
||||
// 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>
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let mut xfrd = 0;
|
||||
// while buffer we are filling is not full.. May go thru several files.
|
||||
'fillloop: while xfrd < buf.len() {
|
||||
match self.curr_file {
|
||||
None => break,
|
||||
Some(ref mut curr_file) => {
|
||||
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) => {
|
||||
eprintln!("{}: I/O: {}",
|
||||
executable!().split("::").next().unwrap(), // remove module
|
||||
e);
|
||||
self.any_err = true;
|
||||
break;
|
||||
},
|
||||
};
|
||||
if xfrd == buf.len() {
|
||||
// transferred all that was asked for.
|
||||
break 'fillloop;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.next_file();
|
||||
}
|
||||
Ok(xfrd)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> HasError for MultifileReader<'b> {
|
||||
fn has_error(&self) -> bool {
|
||||
self.any_err
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::{Cursor, Read, ErrorKind};
|
||||
use mockstream::*;
|
||||
|
||||
#[test]
|
||||
fn test_multi_file_reader_one_read() {
|
||||
let mut inputs = Vec::new();
|
||||
inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"abcd"[..]))));
|
||||
inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..]))));
|
||||
let mut v = [0; 10];
|
||||
|
||||
let mut sut = MultifileReader::new(inputs);
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 8);
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x41, 0x42, 0x43, 0x44, 0, 0]);
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_file_reader_two_reads() {
|
||||
let mut inputs = Vec::new();
|
||||
inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"abcd"[..]))));
|
||||
inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..]))));
|
||||
let mut v = [0; 5];
|
||||
|
||||
let mut sut = MultifileReader::new(inputs);
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 5);
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x41]);
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 3);
|
||||
assert_eq!(v, [0x42, 0x43, 0x44, 0x64, 0x41]); // last two bytes are not overwritten
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_file_reader_read_error() {
|
||||
let c = Cursor::new(&b"1234"[..])
|
||||
.chain(FailingMockStream::new(ErrorKind::Other, "Failing", 1))
|
||||
.chain(Cursor::new(&b"5678"[..]));
|
||||
let mut inputs = Vec::new();
|
||||
inputs.push(InputSource::Stream(Box::new(c)));
|
||||
inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..]))));
|
||||
let mut v = [0; 5];
|
||||
|
||||
let mut sut = MultifileReader::new(inputs);
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 5);
|
||||
assert_eq!(v, [49, 50, 51, 52, 65]);
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 3);
|
||||
assert_eq!(v, [66, 67, 68, 52, 65]); // last two bytes are not overwritten
|
||||
|
||||
// note: no retry on i/o error, so 5678 is missing
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_file_reader_read_error_at_start() {
|
||||
let mut inputs = Vec::new();
|
||||
inputs.push(InputSource::Stream(Box::new(FailingMockStream::new(ErrorKind::Other, "Failing", 1))));
|
||||
inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"abcd"[..]))));
|
||||
inputs.push(InputSource::Stream(Box::new(FailingMockStream::new(ErrorKind::Other, "Failing", 1))));
|
||||
inputs.push(InputSource::Stream(Box::new(Cursor::new(&b"ABCD"[..]))));
|
||||
inputs.push(InputSource::Stream(Box::new(FailingMockStream::new(ErrorKind::Other, "Failing", 1))));
|
||||
let mut v = [0; 5];
|
||||
|
||||
let mut sut = MultifileReader::new(inputs);
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 5);
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x41]);
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 3);
|
||||
assert_eq!(v, [0x42, 0x43, 0x44, 0x64, 0x41]); // last two bytes are not overwritten
|
||||
}
|
||||
}
|
777
src/od/od.rs
777
src/od/od.rs
|
@ -10,34 +10,92 @@
|
|||
*/
|
||||
|
||||
extern crate getopts;
|
||||
extern crate byteorder;
|
||||
extern crate half;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::mem;
|
||||
use std::io::BufReader;
|
||||
#[macro_use]
|
||||
extern crate uucore;
|
||||
|
||||
mod multifilereader;
|
||||
mod partialreader;
|
||||
mod peekreader;
|
||||
mod byteorder_io;
|
||||
mod formatteriteminfo;
|
||||
mod prn_int;
|
||||
mod prn_char;
|
||||
mod prn_float;
|
||||
mod parse_nrofbytes;
|
||||
mod parse_formats;
|
||||
mod parse_inputs;
|
||||
mod inputoffset;
|
||||
mod inputdecoder;
|
||||
mod output_info;
|
||||
#[cfg(test)]
|
||||
mod mockstream;
|
||||
|
||||
use std::cmp;
|
||||
use std::io::Write;
|
||||
use std::io;
|
||||
use byteorder_io::*;
|
||||
use multifilereader::*;
|
||||
use partialreader::*;
|
||||
use peekreader::*;
|
||||
use formatteriteminfo::*;
|
||||
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};
|
||||
use inputoffset::{InputOffset, Radix};
|
||||
use inputdecoder::{InputDecoder,MemoryDecoder};
|
||||
use output_info::OutputInfo;
|
||||
|
||||
//This is available in some versions of std, but not all that we target.
|
||||
macro_rules! hashmap {
|
||||
($( $key: expr => $val: expr ),*) => {{
|
||||
let mut map = ::std::collections::HashMap::new();
|
||||
$( map.insert($key, $val); )*
|
||||
map
|
||||
}}
|
||||
}
|
||||
static VERSION: &'static str = env!("CARGO_PKG_VERSION");
|
||||
const PEEK_BUFFER_SIZE: usize = 4; // utf-8 can be 4 bytes
|
||||
|
||||
static USAGE: &'static str =
|
||||
r#"Usage:
|
||||
od [OPTION]... [--] [FILENAME]...
|
||||
od [-abcdDefFhHiIlLoOsxX] [FILENAME] [[+][0x]OFFSET[.][b]]
|
||||
od --traditional [OPTION]... [FILENAME] [[+][0x]OFFSET[.][b] [[+][0x]LABEL[.][b]]]
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Radix { Decimal, Hexadecimal, Octal, Binary }
|
||||
Displays data in various human-readable formats. If multiple formats are
|
||||
specified, the output will contain all formats in the order they appear on the
|
||||
commandline. Each format will be printed on a new line. Only the line
|
||||
containing the first format will be prefixed with the offset.
|
||||
|
||||
#[derive(Debug)]
|
||||
enum InputSource<'a> {
|
||||
FileName(&'a str ),
|
||||
Stdin
|
||||
}
|
||||
If no filename is specified, or it is "-", stdin will be used. After a "--", no
|
||||
more options will be recognised. This allows for filenames starting with a "-".
|
||||
|
||||
pub fn uumain(args: Vec<String>) -> i32 {
|
||||
If a filename is a valid number which can be used as an offset in the second
|
||||
form, you can force it to be recognised as a filename if you include an option
|
||||
like "-j0", which is only valid in the first form.
|
||||
|
||||
RADIX is one of o,d,x,n for octal, decimal, hexadecimal or none.
|
||||
|
||||
BYTES is decimal by default, octal if prefixed with a "0", or hexadecimal if
|
||||
prefixed with "0x". The suffixes b, KB, K, MB, M, GB, G, will multiply the
|
||||
number with 512, 1000, 1024, 1000^2, 1024^2, 1000^3, 1024^3, 1000^2, 1024^2.
|
||||
|
||||
OFFSET and LABEL are octal by default, hexadecimal if prefixed with "0x" or
|
||||
decimal if a "." suffix is added. The "b" suffix will multiply with 512.
|
||||
|
||||
TYPE contains one or more format specifications consisting of:
|
||||
a for printable 7-bits ASCII
|
||||
c for utf-8 characters or octal for undefined characters
|
||||
d[SIZE] for signed decimal
|
||||
f[SIZE] for floating point
|
||||
o[SIZE] for octal
|
||||
u[SIZE] for unsigned decimal
|
||||
x[SIZE] for hexadecimal
|
||||
SIZE is the number of bytes which can be the number 1, 2, 4, 8 or 16,
|
||||
or C, I, S, L for 1, 2, 4, 8 bytes for integer types,
|
||||
or F, D, L for 4, 8, 16 bytes for floating point.
|
||||
Any type specification can have a "z" suffic, which will add a ASCII dump at
|
||||
the end of the line.
|
||||
|
||||
If an error occurred, a diagnostic message will be printed to stderr, and the
|
||||
exitcode will be non-zero."#;
|
||||
|
||||
fn create_getopts_options() -> getopts::Options {
|
||||
let mut opts = getopts::Options::new();
|
||||
|
||||
opts.optopt("A", "address-radix",
|
||||
|
@ -46,446 +104,343 @@ pub fn uumain(args: Vec<String>) -> i32 {
|
|||
"Skip bytes input bytes before formatting and writing.", "BYTES");
|
||||
opts.optopt("N", "read-bytes",
|
||||
"limit dump to BYTES input bytes", "BYTES");
|
||||
opts.optopt("", "endian", "byte order to use for multi-byte formats", "big|little");
|
||||
opts.optopt("S", "strings",
|
||||
("output strings of at least BYTES graphic chars. 3 is assumed when \
|
||||
BYTES is not specified."),
|
||||
"BYTES");
|
||||
opts.optflag("a", "", "named characters, ignoring high-order bit");
|
||||
opts.optflag("b", "", "octal bytes");
|
||||
opts.optflag("c", "", "ASCII characters or backslash escapes");
|
||||
opts.optflag("d", "", "unsigned decimal 2-byte units");
|
||||
opts.optflag("o", "", "unsigned decimal 2-byte units");
|
||||
opts.optflagmulti("a", "", "named characters, ignoring high-order bit");
|
||||
opts.optflagmulti("b", "", "octal bytes");
|
||||
opts.optflagmulti("c", "", "ASCII characters or backslash escapes");
|
||||
opts.optflagmulti("d", "", "unsigned decimal 2-byte units");
|
||||
opts.optflagmulti("D", "", "unsigned decimal 4-byte units");
|
||||
opts.optflagmulti("o", "", "octal 2-byte units");
|
||||
|
||||
opts.optflag("I", "", "decimal 2-byte units");
|
||||
opts.optflag("L", "", "decimal 2-byte units");
|
||||
opts.optflag("i", "", "decimal 2-byte units");
|
||||
opts.optflagmulti("I", "", "decimal 8-byte units");
|
||||
opts.optflagmulti("L", "", "decimal 8-byte units");
|
||||
opts.optflagmulti("i", "", "decimal 4-byte units");
|
||||
opts.optflagmulti("l", "", "decimal 8-byte units");
|
||||
opts.optflagmulti("x", "", "hexadecimal 2-byte units");
|
||||
opts.optflagmulti("h", "", "hexadecimal 2-byte units");
|
||||
|
||||
opts.optflag("O", "", "octal 4-byte units");
|
||||
opts.optflag("s", "", "decimal 4-byte units");
|
||||
opts.optflagmulti("O", "", "octal 4-byte units");
|
||||
opts.optflagmulti("s", "", "decimal 2-byte units");
|
||||
opts.optflagmulti("X", "", "hexadecimal 4-byte units");
|
||||
opts.optflagmulti("H", "", "hexadecimal 4-byte units");
|
||||
|
||||
opts.optopt("t", "format", "select output format or formats", "TYPE");
|
||||
opts.optflagmulti("e", "", "floating point double precision (64-bit) units");
|
||||
opts.optflagmulti("f", "", "floating point single precision (32-bit) units");
|
||||
opts.optflagmulti("F", "", "floating point double precision (64-bit) units");
|
||||
|
||||
opts.optmulti("t", "format", "select output format or formats", "TYPE");
|
||||
opts.optflag("v", "output-duplicates", "do not use * to mark line suppression");
|
||||
opts.optopt("w", "width",
|
||||
opts.optflagopt("w", "width",
|
||||
("output BYTES bytes per output line. 32 is implied when BYTES is not \
|
||||
specified."),
|
||||
"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.");
|
||||
|
||||
opts
|
||||
}
|
||||
|
||||
struct OdOptions {
|
||||
byte_order: ByteOrder,
|
||||
skip_bytes: usize,
|
||||
read_bytes: Option<usize>,
|
||||
label: Option<usize>,
|
||||
input_strings: Vec<String>,
|
||||
formats: Vec<ParsedFormatterItemInfo>,
|
||||
line_bytes: usize,
|
||||
output_duplicates: bool,
|
||||
radix: Radix,
|
||||
}
|
||||
|
||||
impl OdOptions {
|
||||
fn new(matches: getopts::Matches, args: Vec<String>) -> Result<OdOptions, String> {
|
||||
let byte_order = match matches.opt_str("endian").as_ref().map(String::as_ref) {
|
||||
None => { ByteOrder::Native },
|
||||
Some("little") => { ByteOrder::Little },
|
||||
Some("big") => { ByteOrder::Big },
|
||||
Some(s) => {
|
||||
return Err(format!("Invalid argument --endian={}", s));
|
||||
}
|
||||
};
|
||||
|
||||
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(_) => {
|
||||
return Err(format!("Invalid argument --skip-bytes={}", s));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut label: Option<usize> = None;
|
||||
|
||||
let input_strings = match parse_inputs(&matches) {
|
||||
Ok(CommandLineInputs::FileNames(v)) => v,
|
||||
Ok(CommandLineInputs::FileAndOffset((f, s, l))) => {
|
||||
skip_bytes = s;
|
||||
label = l;
|
||||
vec![f]
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(format!("Invalid inputs: {}", e));
|
||||
}
|
||||
};
|
||||
|
||||
let formats = match parse_format_flags(&args) {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
return Err(format!("{}", e));
|
||||
}
|
||||
};
|
||||
|
||||
let mut line_bytes = match matches.opt_default("w", "32") {
|
||||
None => 16,
|
||||
Some(s) => {
|
||||
match s.parse::<usize>() {
|
||||
Ok(i) => { i }
|
||||
Err(_) => { 0 }
|
||||
}
|
||||
}
|
||||
};
|
||||
let min_bytes = formats.iter().fold(1, |max, next| cmp::max(max, next.formatter_item_info.byte_size));
|
||||
if line_bytes == 0 || line_bytes % min_bytes != 0 {
|
||||
show_warning!("invalid width {}; using {} instead", line_bytes, min_bytes);
|
||||
line_bytes = min_bytes;
|
||||
}
|
||||
|
||||
let output_duplicates = matches.opt_present("v");
|
||||
|
||||
let read_bytes = match matches.opt_str("read-bytes") {
|
||||
None => None,
|
||||
Some(s) => {
|
||||
match parse_number_of_bytes(&s) {
|
||||
Ok(i) => { Some(i) }
|
||||
Err(_) => {
|
||||
return Err(format!("Invalid argument --read-bytes={}", s));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let radix = match matches.opt_str("A") {
|
||||
None => Radix::Octal,
|
||||
Some(s) => {
|
||||
let st = s.into_bytes();
|
||||
if st.len() != 1 {
|
||||
return Err(format!("Radix must be one of [d, o, n, x]"))
|
||||
} else {
|
||||
let radix: char = *(st.get(0)
|
||||
.expect("byte string of length 1 lacks a 0th elem")) as char;
|
||||
match radix {
|
||||
'd' => Radix::Decimal,
|
||||
'x' => Radix::Hexadecimal,
|
||||
'o' => Radix::Octal,
|
||||
'n' => Radix::NoPrefix,
|
||||
_ => return Err(format!("Radix must be one of [d, o, n, x]"))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(OdOptions {
|
||||
byte_order: byte_order,
|
||||
skip_bytes: skip_bytes,
|
||||
read_bytes: read_bytes,
|
||||
label: label,
|
||||
input_strings: input_strings,
|
||||
formats: formats,
|
||||
line_bytes: line_bytes,
|
||||
output_duplicates: output_duplicates,
|
||||
radix: radix,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// parses and validates commandline parameters, prepares data structures,
|
||||
/// opens the input and calls `odfunc` to process the input.
|
||||
pub fn uumain(args: Vec<String>) -> i32 {
|
||||
let opts = create_getopts_options();
|
||||
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => m,
|
||||
Err(f) => panic!("Invalid options\n{}", f)
|
||||
Err(f) => {
|
||||
disp_err!("{}", f);
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
let input_offset_base = match parse_radix(matches.opt_str("A")) {
|
||||
Ok(r) => r,
|
||||
Err(f) => { panic!("Invalid -A/--address-radix\n{}", f) }
|
||||
if matches.opt_present("h") {
|
||||
println!("{}", opts.usage(&USAGE));
|
||||
return 0;
|
||||
}
|
||||
if matches.opt_present("version") {
|
||||
println!("{} {}", executable!(), VERSION);
|
||||
return 0;
|
||||
}
|
||||
|
||||
let od_options = match OdOptions::new(matches, args) {
|
||||
Err(s) => {
|
||||
disp_err!("{}", s);
|
||||
return 1;
|
||||
},
|
||||
Ok(o) => o,
|
||||
};
|
||||
|
||||
// Gather up file names - args which don't start with '-'
|
||||
let stdnionly = [InputSource::Stdin];
|
||||
let inputs = args[1..]
|
||||
.iter()
|
||||
.filter_map(|w| match w as &str {
|
||||
"--" => Some(InputSource::Stdin),
|
||||
o if o.starts_with("-") => None,
|
||||
x => Some(InputSource::FileName(x)),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// If no input files named, use stdin.
|
||||
let inputs = if inputs.len() == 0 {
|
||||
&stdnionly[..]
|
||||
} else {
|
||||
&inputs[..]
|
||||
};
|
||||
// Gather up format flags, we don't use getopts becase we need keep them in order.
|
||||
let flags = args[1..]
|
||||
.iter()
|
||||
.filter_map(|w| match w as &str {
|
||||
"--" => None,
|
||||
o if o.starts_with("-") => Some(&o[1..]),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut input_offset = InputOffset::new(od_options.radix, od_options.skip_bytes,
|
||||
od_options.label);
|
||||
|
||||
// At the moment, char (-a & -c)formats need the driver to set up a
|
||||
// line by inserting a different # of of spaces at the start.
|
||||
struct OdFormater {
|
||||
writer: fn(p: u64, itembytes: usize),
|
||||
offmarg: usize,
|
||||
};
|
||||
let oct = OdFormater {
|
||||
writer: print_item_oct, offmarg: 2
|
||||
};
|
||||
let hex = OdFormater {
|
||||
writer: print_item_hex, offmarg: 2
|
||||
};
|
||||
let dec_u = OdFormater {
|
||||
writer: print_item_dec_u, offmarg: 2
|
||||
};
|
||||
let dec_s = OdFormater {
|
||||
writer: print_item_dec_s, offmarg: 2
|
||||
};
|
||||
let a_char = OdFormater {
|
||||
writer: print_item_a, offmarg: 1
|
||||
};
|
||||
let c_char = OdFormater {
|
||||
writer: print_item_c, offmarg: 1
|
||||
};
|
||||
let mut input = open_input_peek_reader(&od_options.input_strings,
|
||||
od_options.skip_bytes, od_options.read_bytes);
|
||||
let mut input_decoder = InputDecoder::new(&mut input, od_options.line_bytes,
|
||||
PEEK_BUFFER_SIZE, od_options.byte_order);
|
||||
|
||||
fn mkfmt(itembytes: usize, fmtspec: &OdFormater) -> OdFormat {
|
||||
OdFormat {
|
||||
itembytes: itembytes,
|
||||
writer: fmtspec.writer,
|
||||
offmarg: fmtspec.offmarg,
|
||||
}
|
||||
}
|
||||
let output_info = OutputInfo::new(od_options.line_bytes, &od_options.formats[..],
|
||||
od_options.output_duplicates);
|
||||
|
||||
// TODO: -t fmts
|
||||
let known_formats = hashmap![
|
||||
"a" => (1, &a_char),
|
||||
"B" => (2, &oct) ,
|
||||
"b" => (1, &oct),
|
||||
"c" => (1, &c_char),
|
||||
"D" => (4, &dec_u),
|
||||
// TODO: support floats
|
||||
// "e" => (8, &flo64),
|
||||
// "F" => (8, &flo64),
|
||||
// "F" => (4, &flo32),
|
||||
"H" => (4, &hex),
|
||||
"X" => (4, &hex) ,
|
||||
"o" => (2, &oct),
|
||||
"x" => (2, &hex),
|
||||
"h" => (2, &hex),
|
||||
|
||||
"I" => (2, &dec_s),
|
||||
"L" => (2, &dec_s),
|
||||
"i" => (2, &dec_s),
|
||||
|
||||
"O" => (4, &oct),
|
||||
"s" => (2, &dec_u)
|
||||
];
|
||||
|
||||
let mut formats = Vec::new();
|
||||
|
||||
for flag in flags.iter() {
|
||||
match known_formats.get(flag) {
|
||||
None => {} // not every option is a format
|
||||
Some(r) => {
|
||||
let (itembytes, fmtspec) = *r;
|
||||
formats.push(mkfmt(itembytes, fmtspec))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if formats.is_empty() {
|
||||
formats.push(mkfmt(2, &oct)); // 2 byte octal is the default
|
||||
}
|
||||
|
||||
odfunc(&input_offset_base, &inputs, &formats[..])
|
||||
odfunc(&mut input_offset, &mut input_decoder, &output_info)
|
||||
}
|
||||
|
||||
const LINEBYTES:usize = 16;
|
||||
const WORDBYTES:usize = 2;
|
||||
/// Loops through the input line by line, calling print_bytes to take care of the output.
|
||||
fn odfunc<I>(input_offset: &mut InputOffset, input_decoder: &mut InputDecoder<I>,
|
||||
output_info: &OutputInfo) -> i32
|
||||
where I: PeekRead + HasError {
|
||||
let mut duplicate_line = false;
|
||||
let mut previous_bytes: Vec<u8> = Vec::new();
|
||||
let line_bytes = output_info.byte_size_line;
|
||||
|
||||
fn odfunc(input_offset_base: &Radix, fnames: &[InputSource], formats: &[OdFormat]) -> i32 {
|
||||
|
||||
let mut mf = MultifileReader::new(fnames);
|
||||
let mut addr = 0;
|
||||
let bytes = &mut [b'\x00'; LINEBYTES];
|
||||
loop {
|
||||
// print each line data (or multi-format raster of several lines describing the same data).
|
||||
|
||||
print_with_radix(input_offset_base, addr); // print offset
|
||||
// if printing in multiple formats offset is printed only once
|
||||
match input_decoder.peek_read() {
|
||||
Ok(mut memory_decoder) => {
|
||||
let length = memory_decoder.length();
|
||||
|
||||
match mf.f_read(bytes) {
|
||||
Ok(0) => {
|
||||
print!("\n");
|
||||
break;
|
||||
}
|
||||
Ok(n) => {
|
||||
let mut first = true; // First line of a multi-format raster.
|
||||
for f in formats {
|
||||
if !first {
|
||||
// this takes the space of the file offset on subsequent
|
||||
// lines of multi-format rasters.
|
||||
print!(" ");
|
||||
}
|
||||
first = false;
|
||||
print!("{:>width$}", "", width = f.offmarg);// 4 spaces after offset - we print 2 more before each word
|
||||
|
||||
for b in 0..n / f.itembytes {
|
||||
let mut p: u64 = 0;
|
||||
for i in 0..f.itembytes {
|
||||
p |= (bytes[(f.itembytes * b) + i] as u64) << (8 * i);
|
||||
}
|
||||
(f.writer)(p, f.itembytes);
|
||||
}
|
||||
// not enough byte for a whole element, this should only happen on the last line.
|
||||
if n % f.itembytes != 0 {
|
||||
let b = n / f.itembytes;
|
||||
let mut p2: u64 = 0;
|
||||
for i in 0..(n % f.itembytes) {
|
||||
p2 |= (bytes[(f.itembytes * b) + i] as u64) << (8 * i);
|
||||
}
|
||||
(f.writer)(p2, f.itembytes);
|
||||
}
|
||||
// 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;
|
||||
// XXX this is running short for -c & -a
|
||||
print!("{:>width$}", "", width = (words_short) * (6 + 2));
|
||||
}
|
||||
print!("\n");
|
||||
if length == 0 {
|
||||
input_offset.print_final_offset();
|
||||
break;
|
||||
}
|
||||
addr += n;
|
||||
|
||||
// not enough byte for a whole element, this should only happen on the last line.
|
||||
if length != line_bytes {
|
||||
// set zero bytes in the part of the buffer that will be used, but is not filled.
|
||||
let mut max_used = length + output_info.byte_size_block;
|
||||
if max_used > line_bytes {
|
||||
max_used = line_bytes;
|
||||
}
|
||||
|
||||
memory_decoder.zero_out_buffer(length, max_used);
|
||||
}
|
||||
|
||||
if !output_info.output_duplicates
|
||||
&& length == line_bytes
|
||||
&& memory_decoder.get_buffer(0) == &previous_bytes[..] {
|
||||
if !duplicate_line {
|
||||
duplicate_line = true;
|
||||
println!("*");
|
||||
}
|
||||
} else {
|
||||
duplicate_line = false;
|
||||
if length == line_bytes {
|
||||
// save a copy of the input unless it is the last line
|
||||
memory_decoder.clone_buffer(&mut previous_bytes);
|
||||
}
|
||||
|
||||
print_bytes(&input_offset.format_byte_offset(), &memory_decoder,
|
||||
&output_info);
|
||||
}
|
||||
|
||||
input_offset.increase_position(length);
|
||||
}
|
||||
Err(_) => {
|
||||
break;
|
||||
Err(e) => {
|
||||
show_error!("{}", e);
|
||||
input_offset.print_final_offset();
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
if mf.any_err {
|
||||
|
||||
if input_decoder.has_error() {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
// For file byte offset printed at left margin.
|
||||
fn parse_radix(radix_str: Option<String>) -> Result<Radix, &'static str> {
|
||||
match radix_str {
|
||||
None => Ok(Radix::Octal),
|
||||
Some(s) => {
|
||||
let st = s.into_bytes();
|
||||
if st.len() != 1 {
|
||||
Err("Radix must be one of [d, o, b, x]\n")
|
||||
} else {
|
||||
let radix: char = *(st.get(0)
|
||||
.expect("byte string of length 1 lacks a 0th elem")) as char;
|
||||
match radix {
|
||||
'd' => Ok(Radix::Decimal),
|
||||
'x' => Ok(Radix::Hexadecimal),
|
||||
'o' => Ok(Radix::Octal),
|
||||
'b' => Ok(Radix::Binary),
|
||||
_ => Err("Radix must be one of [d, o, b, x]\n")
|
||||
/// Outputs a single line of input, into one or more lines human readable output.
|
||||
fn print_bytes(prefix: &str, input_decoder: &MemoryDecoder, output_info: &OutputInfo) {
|
||||
let mut first = true; // First line of a multi-format raster.
|
||||
for f in output_info.spaced_formatters_iter() {
|
||||
let mut output_text = String::new();
|
||||
|
||||
let mut b = 0;
|
||||
while b < input_decoder.length() {
|
||||
output_text.push_str(&format!("{:>width$}",
|
||||
"",
|
||||
width = f.spacing[b % output_info.byte_size_block]));
|
||||
|
||||
match f.formatter_item_info.formatter {
|
||||
FormatWriter::IntWriter(func) => {
|
||||
let p = input_decoder.read_uint(b, f.formatter_item_info.byte_size);
|
||||
output_text.push_str(&func(p));
|
||||
}
|
||||
FormatWriter::FloatWriter(func) => {
|
||||
let p = input_decoder.read_float(b, f.formatter_item_info.byte_size);
|
||||
output_text.push_str(&func(p));
|
||||
}
|
||||
FormatWriter::MultibyteWriter(func) => {
|
||||
output_text.push_str(&func(input_decoder.get_full_buffer(b)));
|
||||
}
|
||||
}
|
||||
|
||||
b += f.formatter_item_info.byte_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_with_radix(r: &Radix, x: usize) {
|
||||
// TODO(keunwoo): field widths should be based on sizeof(x), or chosen dynamically based on the
|
||||
// expected range of address values. Binary in particular is not great here.
|
||||
match *r {
|
||||
Radix::Decimal => print!("{:07}", x),
|
||||
Radix::Hexadecimal => print!("{:07X}", x),
|
||||
Radix::Octal => print!("{:07o}", x),
|
||||
Radix::Binary => print!("{:07b}", x)
|
||||
}
|
||||
}
|
||||
|
||||
// MultifileReader - concatenate all our input, file or stdin.
|
||||
struct MultifileReader<'a> {
|
||||
ni: std::slice::Iter<'a, InputSource<'a>>,
|
||||
curr_file: Option<Box<io::Read>>,
|
||||
any_err: bool,
|
||||
}
|
||||
impl<'b> MultifileReader<'b> {
|
||||
fn new<'a>(fnames: &'a [InputSource]) -> MultifileReader<'a> {
|
||||
let mut mf = MultifileReader {
|
||||
ni: fnames.iter(),
|
||||
curr_file: None, // normally this means done; call next_file()
|
||||
any_err: false,
|
||||
};
|
||||
mf.next_file();
|
||||
return mf;
|
||||
}
|
||||
|
||||
fn next_file(&mut self) {
|
||||
// loop retries with subsequent files if err - normally 'loops' once
|
||||
loop {
|
||||
match self.ni.next() {
|
||||
None => {
|
||||
self.curr_file = None;
|
||||
return;
|
||||
}
|
||||
Some(input) => {
|
||||
match *input {
|
||||
InputSource::Stdin => {
|
||||
self.curr_file = Some(Box::new(BufReader::new(std::io::stdin())));
|
||||
return;
|
||||
}
|
||||
InputSource::FileName(fname) => {
|
||||
match File::open(fname) {
|
||||
Ok(f) => {
|
||||
self.curr_file = Some(Box::new(BufReader::new(f)));
|
||||
return;
|
||||
}
|
||||
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);
|
||||
self.any_err = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if f.add_ascii_dump {
|
||||
let missing_spacing = output_info.print_width_line.saturating_sub(output_text.chars().count());
|
||||
output_text.push_str(&format!("{:>width$} {}",
|
||||
"",
|
||||
format_ascii_dump(input_decoder.get_buffer(0)),
|
||||
width = missing_spacing));
|
||||
}
|
||||
}
|
||||
|
||||
// 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>
|
||||
fn f_read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let mut xfrd = 0;
|
||||
// while buffer we are filling is not full.. May go thru several files.
|
||||
'fillloop: while xfrd < buf.len() {
|
||||
match self.curr_file {
|
||||
None => break,
|
||||
Some(ref mut curr_file) => {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.next_file();
|
||||
if first {
|
||||
print!("{}", prefix); // print offset
|
||||
// if printing in multiple formats offset is printed only once
|
||||
first = false;
|
||||
} else {
|
||||
// this takes the space of the file offset on subsequent
|
||||
// lines of multi-format rasters.
|
||||
print!("{:>width$}", "", width=prefix.chars().count());
|
||||
}
|
||||
Ok(xfrd)
|
||||
print!("{}\n", output_text);
|
||||
}
|
||||
}
|
||||
|
||||
/// returns a reader implementing `PeekRead + Read + HasError` providing the combined input
|
||||
///
|
||||
/// `skip_bytes` is the number of bytes skipped from the input
|
||||
/// `read_bytes` is an optinal limit to the number of bytes to read
|
||||
fn open_input_peek_reader<'a>(input_strings: &'a Vec<String>, skip_bytes: usize,
|
||||
read_bytes: Option<usize>) -> PeekReader<PartialReader<MultifileReader<'a>>> {
|
||||
// should return "impl PeekRead + Read + HasError" when supported in (stable) rust
|
||||
let inputs = input_strings
|
||||
.iter()
|
||||
.map(|w| match w as &str {
|
||||
"-" => InputSource::Stdin,
|
||||
x => InputSource::FileName(x),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
struct OdFormat {
|
||||
itembytes: usize,
|
||||
writer: fn(u64, usize),
|
||||
offmarg: usize,
|
||||
}
|
||||
|
||||
// TODO: use some sort of byte iterator, instead of passing bytes in u64
|
||||
fn print_item_oct(p: u64, itembytes: usize) {
|
||||
let itemwidth = 3 * itembytes;
|
||||
let itemspace = 4 * itembytes - itemwidth;
|
||||
|
||||
print!("{:>itemspace$}{:0width$o}",
|
||||
"",
|
||||
p,
|
||||
width = itemwidth,
|
||||
itemspace = itemspace);
|
||||
}
|
||||
|
||||
fn print_item_hex(p: u64, itembytes: usize) {
|
||||
let itemwidth = 2 * itembytes;
|
||||
let itemspace = 4 * itembytes - itemwidth;
|
||||
|
||||
print!("{:>itemspace$}{:0width$x}",
|
||||
"",
|
||||
p,
|
||||
width = itemwidth,
|
||||
itemspace = itemspace);
|
||||
}
|
||||
|
||||
|
||||
fn sign_extend(item: u64, itembytes: usize) -> i64{
|
||||
// https://graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend
|
||||
unsafe{
|
||||
let b = 8 * itembytes; // number of bits representing the number in p
|
||||
let m = mem::transmute::<u64,i64>(1u64 << (b - 1));
|
||||
let x = mem::transmute::<u64,i64>(item) & (mem::transmute::<u64,i64>(1u64 << b) - 1);
|
||||
let r = (x ^ m) - m;
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn print_item_dec_s(p: u64, itembytes: usize) {
|
||||
// sign extend
|
||||
let s = sign_extend(p,itembytes);
|
||||
print!("{:totalwidth$}", s, totalwidth = 4 * itembytes);
|
||||
}
|
||||
fn print_item_dec_u(p: u64, itembytes: usize) {
|
||||
print!("{:totalwidth$}", p, totalwidth = 4 * itembytes);
|
||||
}
|
||||
|
||||
// TODO: multi-byte chars
|
||||
// Quoth the man page: Multi-byte characters are displayed in the area corresponding to the first byte of the character. The remaining bytes are shown as `**'.
|
||||
|
||||
static A_CHRS : [&'static str; 160] =
|
||||
["nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel",
|
||||
"bs", "ht", "nl", "vt", "ff", "cr", "so", "si",
|
||||
"dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb",
|
||||
"can", "em", "sub", "esc", "fs", "gs", "rs", "us",
|
||||
"sp", "!", "\"", "#", "$", "%", "&", "'",
|
||||
"(", ")", "*", "+", ",", "-", ".", "/",
|
||||
"0", "1", "2", "3", "4", "5", "6", "7",
|
||||
"8", "9", ":", ";", "<", "=", ">", "?",
|
||||
"@", "A", "B", "C", "D", "E", "F", "G",
|
||||
"H", "I", "J", "K", "L", "M", "N", "O",
|
||||
"P", "Q", "R", "S", "T", "U", "V", "W",
|
||||
"X", "Y", "Z", "[", "\\", "]", "^", "_",
|
||||
"`", "a", "b", "c", "d", "e", "f", "g",
|
||||
"h", "i", "j", "k", "l", "m", "n", "o",
|
||||
"p", "q", "r", "s", "t", "u", "v", "w",
|
||||
"x", "y", "z", "{", "|", "}", "~", "del",
|
||||
"80", "81", "82", "83", "84", "85", "86", "87",
|
||||
"88", "89", "8a", "8b", "8c", "8d", "8e", "8f",
|
||||
"90", "91", "92", "93", "94", "95", "96", "97",
|
||||
"98", "99", "9a", "9b", "9c", "9d", "9e", "9f"];
|
||||
|
||||
fn print_item_a(p: u64, _: usize) {
|
||||
// itembytes == 1
|
||||
let b = (p & 0xff) as u8;
|
||||
print!("{:>4}", A_CHRS.get(b as usize).unwrap_or(&"?") // XXX od dose not actually do this, it just prints the byte
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
static C_CHRS : [&'static str; 127] = [
|
||||
"\\0", "001", "002", "003", "004", "005", "006", "\\a",
|
||||
"\\b", "\\t", "\\n", "\\v", "\\f", "\\r", "016", "017",
|
||||
"020", "021", "022", "023", "024", "025", "026", "027",
|
||||
"030", "031", "032", "033", "034", "035", "036", "037",
|
||||
" ", "!", "\"", "#", "$", "%", "&", "'",
|
||||
"(", ")", "*", "+", ",", "-", ".", "/",
|
||||
"0", "1", "2", "3", "4", "5", "6", "7",
|
||||
"8", "9", ":", ";", "<", "=", ">", "?",
|
||||
"@", "A", "B", "C", "D", "E", "F", "G",
|
||||
"H", "I", "J", "K", "L", "M", "N", "O",
|
||||
"P", "Q", "R", "S", "T", "U", "V", "W",
|
||||
"X", "Y", "Z", "[", "\\", "]", "^", "_",
|
||||
"`", "a", "b", "c", "d", "e", "f", "g",
|
||||
"h", "i", "j", "k", "l", "m", "n", "o",
|
||||
"p", "q", "r", "s", "t", "u", "v", "w",
|
||||
"x", "y", "z", "{", "|", "}", "~" ];
|
||||
|
||||
|
||||
fn print_item_c(p: u64, _: usize) {
|
||||
// itembytes == 1
|
||||
let b = (p & 0xff) as usize;
|
||||
|
||||
if b < C_CHRS.len() {
|
||||
match C_CHRS.get(b as usize) {
|
||||
Some(s) => print!("{:>4}", s),
|
||||
None => print!("{:>4}", b),
|
||||
}
|
||||
}
|
||||
let mf = MultifileReader::new(inputs);
|
||||
let pr = PartialReader::new(mf, skip_bytes, read_bytes);
|
||||
let input = PeekReader::new(pr);
|
||||
input
|
||||
}
|
||||
|
|
241
src/od/output_info.rs
Normal file
241
src/od/output_info.rs
Normal file
|
@ -0,0 +1,241 @@
|
|||
use std::cmp;
|
||||
use std::slice::Iter;
|
||||
use parse_formats::ParsedFormatterItemInfo;
|
||||
use formatteriteminfo::FormatterItemInfo;
|
||||
|
||||
/// Size in bytes of the max datatype. ie set to 16 for 128-bit numbers.
|
||||
const MAX_BYTES_PER_UNIT: usize = 8;
|
||||
|
||||
/// Contains information to output single output line in human readable form
|
||||
pub struct SpacedFormatterItemInfo {
|
||||
/// Contains a function pointer to output data, and information about the output format.
|
||||
pub formatter_item_info: FormatterItemInfo,
|
||||
/// Contains the number of spaces to add to align data with other output formats.
|
||||
///
|
||||
/// If the corresponding data is a single byte, each entry in this array contains
|
||||
/// the number of spaces to insert when outputting each byte. If the corresponding
|
||||
/// data is multi-byte, only the fist byte position is used. For example a 32-bit
|
||||
/// datatype, could use positions 0, 4, 8, 12, ....
|
||||
/// As each block is formatted identically, only the spacing for a single block is set.
|
||||
pub spacing: [usize; MAX_BYTES_PER_UNIT],
|
||||
/// if set adds a ascii dump at the end of the line
|
||||
pub add_ascii_dump: bool,
|
||||
}
|
||||
|
||||
/// Contains information about all output lines.
|
||||
pub struct OutputInfo {
|
||||
/// The number of bytes of a line.
|
||||
pub byte_size_line: usize,
|
||||
/// The width of a line in human readable format.
|
||||
pub print_width_line: usize,
|
||||
|
||||
/// The number of bytes in a block. (This is the size of the largest datatype in `spaced_formatters`.)
|
||||
pub byte_size_block: usize,
|
||||
/// The width of a block in human readable format. (The size of the largest format.)
|
||||
pub print_width_block: usize,
|
||||
/// All formats.
|
||||
spaced_formatters: Vec<SpacedFormatterItemInfo>,
|
||||
/// determines if duplicate output lines should be printed, or
|
||||
/// skipped with a "*" showing one or more skipped lines.
|
||||
pub output_duplicates: bool,
|
||||
}
|
||||
|
||||
|
||||
impl OutputInfo {
|
||||
/// Returns an iterator over the `SpacedFormatterItemInfo` vector.
|
||||
pub fn spaced_formatters_iter(&self) -> Iter<SpacedFormatterItemInfo> {
|
||||
self.spaced_formatters.iter()
|
||||
}
|
||||
|
||||
/// Creates a new `OutputInfo` based on the parameters
|
||||
pub fn new(line_bytes: usize, formats: &[ParsedFormatterItemInfo], output_duplicates: bool) -> OutputInfo {
|
||||
let byte_size_block = formats.iter().fold(1, |max, next| cmp::max(max, next.formatter_item_info.byte_size));
|
||||
let print_width_block = formats
|
||||
.iter()
|
||||
.fold(1, |max, next| {
|
||||
cmp::max(max, next.formatter_item_info.print_width * (byte_size_block / next.formatter_item_info.byte_size))
|
||||
});
|
||||
let print_width_line = print_width_block * (line_bytes / byte_size_block);
|
||||
|
||||
let spaced_formatters = OutputInfo::create_spaced_formatter_info(&formats, byte_size_block, print_width_block);
|
||||
|
||||
OutputInfo {
|
||||
byte_size_line: line_bytes,
|
||||
print_width_line: print_width_line,
|
||||
byte_size_block: byte_size_block,
|
||||
print_width_block: print_width_block,
|
||||
spaced_formatters: spaced_formatters,
|
||||
output_duplicates: output_duplicates,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_spaced_formatter_info(formats: &[ParsedFormatterItemInfo],
|
||||
byte_size_block: usize, print_width_block: usize) -> Vec<SpacedFormatterItemInfo> {
|
||||
formats
|
||||
.iter()
|
||||
.map(|f| SpacedFormatterItemInfo {
|
||||
formatter_item_info: f.formatter_item_info,
|
||||
add_ascii_dump: f.add_ascii_dump,
|
||||
spacing: OutputInfo::calculate_alignment(f, byte_size_block, print_width_block)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// calculates proper alignment for a single line of output
|
||||
///
|
||||
/// Multiple representations of the same data, will be right-aligned for easy reading.
|
||||
/// For example a 64 bit octal and a 32-bit decimal with a 16-bit hexadecimal looks like this:
|
||||
/// ```
|
||||
/// 1777777777777777777777 1777777777777777777777
|
||||
/// 4294967295 4294967295 4294967295 4294967295
|
||||
/// ffff ffff ffff ffff ffff ffff ffff ffff
|
||||
/// ```
|
||||
/// In this example is additional spacing before the first and third decimal number,
|
||||
/// and there is additional spacing before the 1st, 3rd, 5th and 7th hexadecimal number.
|
||||
/// This way both the octal and decimal, aswell the decimal and hexadecimal numbers
|
||||
/// left align. Note that the alignment below both octal numbers is identical.
|
||||
///
|
||||
/// This function calculates the required spacing for a single line, given the size
|
||||
/// of a block, and the width of a block. The size of a block is the largest type
|
||||
/// and the width is width of the the type which needs the most space to print that
|
||||
/// number of bytes. So both numbers might refer to different types. All widths
|
||||
/// include a space at the front. For example the width of a 8-bit hexadecimal,
|
||||
/// is 3 characters, for example " FF".
|
||||
///
|
||||
/// This algorithm first calculates how many spaces needs to be added, based the
|
||||
/// block size and the size of the type, and the widths of the block and the type.
|
||||
/// The required spaces are spread across the available positions.
|
||||
/// If the blocksize is 8, and the size of the type is 8 too, there will be just
|
||||
/// one value in a block, so all spacing will be assigned to position 0.
|
||||
/// If the blocksize is 8, and the size of the type is 2, the spacing will be
|
||||
/// spread across position 0, 2, 4, 6. All 4 positions will get an additional
|
||||
/// space as long as there are more then 4 spaces available. If there are 2
|
||||
/// spaces available, they will be assigend to position 0 and 4. If there is
|
||||
/// 1 space available, it will be assigned to position 0. This will be combined,
|
||||
/// For example 7 spaces will be assigned to position 0, 2, 4, 6 like: 3, 1, 2, 1.
|
||||
/// And 7 spaces with 2 positions will be assigned to position 0 and 4 like 4, 3.
|
||||
///
|
||||
/// Here is another example showing the alignment of 64-bit unsigned decimal numbers,
|
||||
/// 32-bit hexadecimal number, 16-bit octal numbers and 8-bit hexadecimal numbers:
|
||||
/// ```
|
||||
/// 18446744073709551615 18446744073709551615
|
||||
/// ffffffff ffffffff ffffffff ffffffff
|
||||
/// 177777 177777 177777 177777 177777 177777 177777 177777
|
||||
/// ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
|
||||
/// ```
|
||||
///
|
||||
/// This algorithm assumes the size of all types is a power of 2 (1, 2, 4, 8, 16, ...)
|
||||
/// Increase MAX_BYTES_PER_UNIT to allow larger types.
|
||||
fn calculate_alignment(sf: &TypeSizeInfo, byte_size_block: usize,
|
||||
print_width_block: usize) -> [usize; MAX_BYTES_PER_UNIT] {
|
||||
if byte_size_block > MAX_BYTES_PER_UNIT {
|
||||
panic!("{}-bits types are unsupported. Current max={}-bits.",
|
||||
8 * byte_size_block,
|
||||
8 * MAX_BYTES_PER_UNIT);
|
||||
}
|
||||
let mut spacing = [0; MAX_BYTES_PER_UNIT];
|
||||
|
||||
let mut byte_size = sf.byte_size();
|
||||
let mut items_in_block = byte_size_block / byte_size;
|
||||
let thisblock_width = sf.print_width() * items_in_block;
|
||||
let mut missing_spacing = print_width_block - thisblock_width;
|
||||
|
||||
while items_in_block > 0 {
|
||||
let avg_spacing: usize = missing_spacing / items_in_block;
|
||||
for i in 0..items_in_block {
|
||||
spacing[i * byte_size] += avg_spacing;
|
||||
missing_spacing -= avg_spacing;
|
||||
}
|
||||
|
||||
items_in_block /= 2;
|
||||
byte_size *= 2;
|
||||
}
|
||||
|
||||
spacing
|
||||
}
|
||||
}
|
||||
|
||||
trait TypeSizeInfo {
|
||||
fn byte_size(&self) -> usize;
|
||||
fn print_width(&self) -> usize;
|
||||
}
|
||||
|
||||
impl TypeSizeInfo for ParsedFormatterItemInfo {
|
||||
fn byte_size(&self) -> usize { self.formatter_item_info.byte_size }
|
||||
fn print_width(&self) -> usize { self.formatter_item_info.print_width }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
struct TypeInfo {
|
||||
byte_size: usize,
|
||||
print_width: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl TypeSizeInfo for TypeInfo {
|
||||
fn byte_size(&self) -> usize { self.byte_size }
|
||||
fn print_width(&self) -> usize { self.print_width }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_alignment() {
|
||||
// For this example `byte_size_block` is 8 and 'print_width_block' is 23:
|
||||
// 1777777777777777777777 1777777777777777777777
|
||||
// 4294967295 4294967295 4294967295 4294967295
|
||||
// ffff ffff ffff ffff ffff ffff ffff ffff
|
||||
|
||||
// the first line has no additional spacing:
|
||||
assert_eq!([0, 0, 0, 0, 0, 0, 0, 0],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:8, print_width:23}, 8, 23));
|
||||
// the second line a single space at the start of the block:
|
||||
assert_eq!([1, 0, 0, 0, 0, 0, 0, 0],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:4, print_width:11}, 8, 23));
|
||||
// the third line two spaces at pos 0, and 1 space at pos 4:
|
||||
assert_eq!([2, 0, 0, 0, 1, 0, 0, 0],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:2, print_width:5}, 8, 23));
|
||||
|
||||
// For this example `byte_size_block` is 8 and 'print_width_block' is 28:
|
||||
// 18446744073709551615 18446744073709551615
|
||||
// ffffffff ffffffff ffffffff ffffffff
|
||||
// 177777 177777 177777 177777 177777 177777 177777 177777
|
||||
// ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff
|
||||
|
||||
assert_eq!([7, 0, 0, 0, 0, 0, 0, 0],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:8, print_width:21}, 8, 28));
|
||||
assert_eq!([5, 0, 0, 0, 5, 0, 0, 0],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:4, print_width:9}, 8, 28));
|
||||
assert_eq!([0, 0, 0, 0, 0, 0, 0, 0],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:2, print_width:7}, 8, 28));
|
||||
assert_eq!([1, 0, 1, 0, 1, 0, 1, 0],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:3}, 8, 28));
|
||||
|
||||
// 9 tests where 8 .. 16 spaces are spread across 8 positions
|
||||
assert_eq!([1, 1, 1, 1, 1, 1, 1, 1],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 8));
|
||||
assert_eq!([2, 1, 1, 1, 1, 1, 1, 1],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 9));
|
||||
assert_eq!([2, 1, 1, 1, 2, 1, 1, 1],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 10));
|
||||
assert_eq!([3, 1, 1, 1, 2, 1, 1, 1],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 11));
|
||||
assert_eq!([2, 1, 2, 1, 2, 1, 2, 1],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 12));
|
||||
assert_eq!([3, 1, 2, 1, 2, 1, 2, 1],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 13));
|
||||
assert_eq!([3, 1, 2, 1, 3, 1, 2, 1],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 14));
|
||||
assert_eq!([4, 1, 2, 1, 3, 1, 2, 1],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 15));
|
||||
assert_eq!([2, 2, 2, 2, 2, 2, 2, 2],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 16));
|
||||
|
||||
// 4 tests where 15 spaces are spread across 8, 4, 2 or 1 position(s)
|
||||
assert_eq!([4, 1, 2, 1, 3, 1, 2, 1],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:1, print_width:2}, 8, 16 + 15));
|
||||
assert_eq!([5, 0, 3, 0, 4, 0, 3, 0],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:2, print_width:4}, 8, 16 + 15));
|
||||
assert_eq!([8, 0, 0, 0, 7, 0, 0, 0],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:4, print_width:8}, 8, 16 + 15));
|
||||
assert_eq!([15, 0, 0, 0, 0, 0, 0, 0],
|
||||
OutputInfo::calculate_alignment(&TypeInfo{byte_size:8, print_width:16}, 8, 16 + 15));
|
||||
}
|
516
src/od/parse_formats.rs
Normal file
516
src/od/parse_formats.rs
Normal file
|
@ -0,0 +1,516 @@
|
|||
use formatteriteminfo::FormatterItemInfo;
|
||||
use prn_int::*;
|
||||
use prn_char::*;
|
||||
use prn_float::*;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct ParsedFormatterItemInfo {
|
||||
pub formatter_item_info: FormatterItemInfo,
|
||||
pub add_ascii_dump: bool,
|
||||
}
|
||||
|
||||
impl ParsedFormatterItemInfo {
|
||||
pub fn new(formatter_item_info: FormatterItemInfo, add_ascii_dump: bool) -> ParsedFormatterItemInfo {
|
||||
ParsedFormatterItemInfo {
|
||||
formatter_item_info: formatter_item_info,
|
||||
add_ascii_dump: add_ascii_dump,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn od_argument_traditional_format(ch: char) -> Option<FormatterItemInfo> {
|
||||
match ch {
|
||||
'a' => Some(FORMAT_ITEM_A),
|
||||
'B' => Some(FORMAT_ITEM_OCT16),
|
||||
'b' => Some(FORMAT_ITEM_OCT8),
|
||||
'c' => Some(FORMAT_ITEM_C),
|
||||
'D' => Some(FORMAT_ITEM_DEC32U),
|
||||
'd' => Some(FORMAT_ITEM_DEC16U),
|
||||
'e' => Some(FORMAT_ITEM_F64),
|
||||
'F' => Some(FORMAT_ITEM_F64),
|
||||
'f' => Some(FORMAT_ITEM_F32),
|
||||
'H' => Some(FORMAT_ITEM_HEX32),
|
||||
'h' => Some(FORMAT_ITEM_HEX16),
|
||||
'i' => Some(FORMAT_ITEM_DEC32S),
|
||||
'I' => Some(FORMAT_ITEM_DEC64S),
|
||||
'L' => Some(FORMAT_ITEM_DEC64S),
|
||||
'l' => Some(FORMAT_ITEM_DEC64S),
|
||||
'O' => Some(FORMAT_ITEM_OCT32),
|
||||
'o' => Some(FORMAT_ITEM_OCT16),
|
||||
's' => Some(FORMAT_ITEM_DEC16S),
|
||||
'X' => Some(FORMAT_ITEM_HEX32),
|
||||
'x' => Some(FORMAT_ITEM_HEX16),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn od_format_type(type_char: FormatType, byte_size: u8) -> Option<FormatterItemInfo> {
|
||||
match (type_char, byte_size) {
|
||||
(FormatType::Ascii, _) => Some(FORMAT_ITEM_A),
|
||||
(FormatType::Char, _) => Some(FORMAT_ITEM_C),
|
||||
|
||||
(FormatType::DecimalInt, 1) => Some(FORMAT_ITEM_DEC8S),
|
||||
(FormatType::DecimalInt, 2) => Some(FORMAT_ITEM_DEC16S),
|
||||
(FormatType::DecimalInt, 0) |
|
||||
(FormatType::DecimalInt, 4) => Some(FORMAT_ITEM_DEC32S),
|
||||
(FormatType::DecimalInt, 8) => Some(FORMAT_ITEM_DEC64S),
|
||||
|
||||
(FormatType::OctalInt, 1) => Some(FORMAT_ITEM_OCT8),
|
||||
(FormatType::OctalInt, 2) => Some(FORMAT_ITEM_OCT16),
|
||||
(FormatType::OctalInt, 0) |
|
||||
(FormatType::OctalInt, 4) => Some(FORMAT_ITEM_OCT32),
|
||||
(FormatType::OctalInt, 8) => Some(FORMAT_ITEM_OCT64),
|
||||
|
||||
(FormatType::UnsignedInt, 1) => Some(FORMAT_ITEM_DEC8U),
|
||||
(FormatType::UnsignedInt, 2) => Some(FORMAT_ITEM_DEC16U),
|
||||
(FormatType::UnsignedInt, 0) |
|
||||
(FormatType::UnsignedInt, 4) => Some(FORMAT_ITEM_DEC32U),
|
||||
(FormatType::UnsignedInt, 8) => Some(FORMAT_ITEM_DEC64U),
|
||||
|
||||
(FormatType::HexadecimalInt, 1) => Some(FORMAT_ITEM_HEX8),
|
||||
(FormatType::HexadecimalInt, 2) => Some(FORMAT_ITEM_HEX16),
|
||||
(FormatType::HexadecimalInt, 0) |
|
||||
(FormatType::HexadecimalInt, 4) => Some(FORMAT_ITEM_HEX32),
|
||||
(FormatType::HexadecimalInt, 8) => Some(FORMAT_ITEM_HEX64),
|
||||
|
||||
(FormatType::Float, 2) => Some(FORMAT_ITEM_F16),
|
||||
(FormatType::Float, 0) |
|
||||
(FormatType::Float, 4) => Some(FORMAT_ITEM_F32),
|
||||
(FormatType::Float, 8) => Some(FORMAT_ITEM_F64),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn od_argument_with_option(ch:char) -> bool {
|
||||
match ch {
|
||||
'A' | 'j' | 'N' | 'S' | 'w' => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Parses format flags from commandline
|
||||
///
|
||||
/// getopts, docopt, clap don't seem suitable to parse the commandline
|
||||
/// arguments used for formats. In particular arguments can appear
|
||||
/// multiple times and the order they appear in, is significant.
|
||||
///
|
||||
/// arguments like -f, -o, -x can appear separate or combined: -fox
|
||||
/// it can also be mixed with non format related flags like -v: -fvox
|
||||
/// arguments with parameters like -w16 can only appear at the end: -fvoxw16
|
||||
/// parameters of -t/--format specify 1 or more formats.
|
||||
/// if -- appears on the commandline, parsing should stop.
|
||||
pub fn parse_format_flags(args: &Vec<String>) -> Result<Vec<ParsedFormatterItemInfo>, String> {
|
||||
let mut formats = Vec::new();
|
||||
|
||||
// args[0] is the name of the binary
|
||||
let mut arg_iter = args.iter().skip(1);
|
||||
let mut expect_type_string = false;
|
||||
|
||||
while let Some(arg) = arg_iter.next() {
|
||||
if expect_type_string {
|
||||
match parse_type_string(arg) {
|
||||
Ok(v) => formats.extend(v.into_iter()),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
expect_type_string = false;
|
||||
} else if arg.starts_with("--") {
|
||||
if arg.len() == 2 {
|
||||
break;
|
||||
}
|
||||
if arg.starts_with("--format=") {
|
||||
let params: String = arg.chars().skip_while(|c| *c != '=').skip(1).collect();
|
||||
match parse_type_string(¶ms) {
|
||||
Ok(v) => formats.extend(v.into_iter()),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
if arg == "--format" {
|
||||
expect_type_string = true;
|
||||
}
|
||||
} else if arg.starts_with("-") {
|
||||
let mut flags = arg.chars().skip(1);
|
||||
let mut format_spec = String::new();
|
||||
while let Some(c) = flags.next() {
|
||||
if expect_type_string {
|
||||
format_spec.push(c);
|
||||
} else if od_argument_with_option(c) {
|
||||
break;
|
||||
} else if c == 't' {
|
||||
expect_type_string = true;
|
||||
} else {
|
||||
// not every option is a format
|
||||
if let Some(r) = od_argument_traditional_format(c) {
|
||||
formats.push(ParsedFormatterItemInfo::new(r, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
if !format_spec.is_empty() {
|
||||
match parse_type_string(&format_spec) {
|
||||
Ok(v) => formats.extend(v.into_iter()),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
expect_type_string = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if expect_type_string {
|
||||
return Err(format!("missing format specification after '--format' / '-t'"));
|
||||
}
|
||||
|
||||
if formats.is_empty() {
|
||||
formats.push(ParsedFormatterItemInfo::new(FORMAT_ITEM_OCT16, false)); // 2 byte octal is the default
|
||||
}
|
||||
|
||||
Ok(formats)
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
|
||||
enum FormatType {
|
||||
Ascii,
|
||||
Char,
|
||||
DecimalInt,
|
||||
OctalInt,
|
||||
UnsignedInt,
|
||||
HexadecimalInt,
|
||||
Float,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
|
||||
enum FormatTypeCategory {
|
||||
Char,
|
||||
Integer,
|
||||
Float,
|
||||
}
|
||||
|
||||
fn format_type(ch: char) -> Option<FormatType> {
|
||||
match ch {
|
||||
'a' => Some(FormatType::Ascii),
|
||||
'c' => Some(FormatType::Char),
|
||||
'd' => Some(FormatType::DecimalInt),
|
||||
'o' => Some(FormatType::OctalInt),
|
||||
'u' => Some(FormatType::UnsignedInt),
|
||||
'x' => Some(FormatType::HexadecimalInt),
|
||||
'f' => Some(FormatType::Float),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn format_type_category(t: FormatType) -> FormatTypeCategory {
|
||||
match t {
|
||||
FormatType::Ascii | FormatType::Char
|
||||
=> FormatTypeCategory::Char,
|
||||
FormatType::DecimalInt | FormatType::OctalInt | FormatType::UnsignedInt | FormatType::HexadecimalInt
|
||||
=> FormatTypeCategory::Integer,
|
||||
FormatType::Float
|
||||
=> FormatTypeCategory::Float,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_format_size_char(ch: Option<char>, format_type: FormatTypeCategory, byte_size: &mut u8) -> bool {
|
||||
match (format_type, ch) {
|
||||
(FormatTypeCategory::Integer, Some('C')) => {
|
||||
*byte_size = 1;
|
||||
true
|
||||
},
|
||||
(FormatTypeCategory::Integer, Some('S')) => {
|
||||
*byte_size = 2;
|
||||
true
|
||||
},
|
||||
(FormatTypeCategory::Integer, Some('I')) => {
|
||||
*byte_size = 4;
|
||||
true
|
||||
},
|
||||
(FormatTypeCategory::Integer, Some('L')) => {
|
||||
*byte_size = 8;
|
||||
true
|
||||
},
|
||||
|
||||
(FormatTypeCategory::Float, Some('F')) => {
|
||||
*byte_size = 4;
|
||||
true
|
||||
},
|
||||
(FormatTypeCategory::Float, Some('D')) => {
|
||||
*byte_size = 8;
|
||||
true
|
||||
},
|
||||
// FormatTypeCategory::Float, 'L' => *byte_size = 16, // TODO support f128
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_format_size_decimal(ch: Option<char>, format_type: FormatTypeCategory, decimal_size: &mut String) -> bool {
|
||||
if format_type == FormatTypeCategory::Char { return false; }
|
||||
match ch {
|
||||
Some(d) if d.is_digit(10) => {
|
||||
decimal_size.push(d);
|
||||
return true;
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_format_dump_char(ch: Option<char>, show_ascii_dump: &mut bool) -> bool {
|
||||
match ch {
|
||||
Some('z') => {
|
||||
*show_ascii_dump = true;
|
||||
return true;
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_type_string(params: &String) -> Result<Vec<ParsedFormatterItemInfo>, String> {
|
||||
let mut formats = Vec::new();
|
||||
|
||||
let mut chars = params.chars();
|
||||
let mut ch = chars.next();
|
||||
|
||||
while ch.is_some() {
|
||||
let type_char = ch.unwrap();
|
||||
let type_char = match format_type(type_char) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
return Err(format!("unexpected char '{}' in format specification '{}'", type_char, params));
|
||||
}
|
||||
};
|
||||
|
||||
let type_cat = format_type_category(type_char);
|
||||
|
||||
ch = chars.next();
|
||||
|
||||
let mut byte_size = 0u8;
|
||||
let mut show_ascii_dump = false;
|
||||
if is_format_size_char(ch, type_cat, &mut byte_size) {
|
||||
ch = chars.next();
|
||||
} else {
|
||||
let mut decimal_size = String::new();
|
||||
while is_format_size_decimal(ch, type_cat, &mut decimal_size) {
|
||||
ch = chars.next();
|
||||
}
|
||||
if !decimal_size.is_empty() {
|
||||
byte_size = match decimal_size.parse() {
|
||||
Err(_) => return Err(format!("invalid number '{}' in format specification '{}'", decimal_size, params)),
|
||||
Ok(n) => n,
|
||||
}
|
||||
}
|
||||
}
|
||||
if is_format_dump_char(ch, &mut show_ascii_dump) {
|
||||
ch = chars.next();
|
||||
}
|
||||
|
||||
match od_format_type(type_char, byte_size) {
|
||||
Some(ft) => formats.push(ParsedFormatterItemInfo::new(ft, show_ascii_dump)),
|
||||
None => return Err(format!("invalid size '{}' in format specification '{}'", byte_size, params)),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(formats)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn parse_format_flags_str(args_str: &Vec<&'static str>) -> Result<Vec<FormatterItemInfo>, String> {
|
||||
let args = args_str.iter().map(|s| s.to_string()).collect();
|
||||
match parse_format_flags(&args) {
|
||||
Err(e) => Err(e),
|
||||
Ok(v) => {
|
||||
// tests using this function asume add_ascii_dump is not set
|
||||
Ok(v.into_iter()
|
||||
.inspect(|f| assert!(!f.add_ascii_dump))
|
||||
.map(|f| f.formatter_item_info)
|
||||
.collect())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_no_options() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od")).unwrap(),
|
||||
vec!(FORMAT_ITEM_OCT16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_one_option() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "-F")).unwrap(),
|
||||
vec!(FORMAT_ITEM_F64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_two_separate_options() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "-F", "-x")).unwrap(),
|
||||
vec!(FORMAT_ITEM_F64, FORMAT_ITEM_HEX16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_two_combined_options() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "-Fx")).unwrap(),
|
||||
vec!(FORMAT_ITEM_F64, FORMAT_ITEM_HEX16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignore_non_format_parameters() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "-d", "-Ax")).unwrap(),
|
||||
vec!(FORMAT_ITEM_DEC16U));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignore_separate_parameters() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "-I", "-A", "x")).unwrap(),
|
||||
vec!(FORMAT_ITEM_DEC64S));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ignore_trailing_vals() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "-D", "--", "-x")).unwrap(),
|
||||
vec!(FORMAT_ITEM_DEC32U));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_long_format() {
|
||||
parse_format_flags_str(&vec!("od", "--format=X")).unwrap_err();
|
||||
parse_format_flags_str(&vec!("od", "--format=xX")).unwrap_err();
|
||||
parse_format_flags_str(&vec!("od", "--format=aC")).unwrap_err();
|
||||
parse_format_flags_str(&vec!("od", "--format=fI")).unwrap_err();
|
||||
parse_format_flags_str(&vec!("od", "--format=xD")).unwrap_err();
|
||||
|
||||
parse_format_flags_str(&vec!("od", "--format=xC1")).unwrap_err();
|
||||
parse_format_flags_str(&vec!("od", "--format=x1C")).unwrap_err();
|
||||
parse_format_flags_str(&vec!("od", "--format=xz1")).unwrap_err();
|
||||
parse_format_flags_str(&vec!("od", "--format=xzC")).unwrap_err();
|
||||
parse_format_flags_str(&vec!("od", "--format=xzz")).unwrap_err();
|
||||
parse_format_flags_str(&vec!("od", "--format=xCC")).unwrap_err();
|
||||
|
||||
parse_format_flags_str(&vec!("od", "--format=c1")).unwrap_err();
|
||||
parse_format_flags_str(&vec!("od", "--format=x256")).unwrap_err();
|
||||
parse_format_flags_str(&vec!("od", "--format=d5")).unwrap_err();
|
||||
parse_format_flags_str(&vec!("od", "--format=f1")).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_format_a() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "--format=a")).unwrap(),
|
||||
vec!(FORMAT_ITEM_A));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_format_cz() {
|
||||
assert_eq!(parse_format_flags(
|
||||
&vec!("od".to_string(), "--format=cz".to_string())).unwrap(),
|
||||
vec!(ParsedFormatterItemInfo::new(FORMAT_ITEM_C, true)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_format_d() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "--format=d8")).unwrap(),
|
||||
vec!(FORMAT_ITEM_DEC64S));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_format_d_default() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "--format=d")).unwrap(),
|
||||
vec!(FORMAT_ITEM_DEC32S));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_format_o_default() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "--format=o")).unwrap(),
|
||||
vec!(FORMAT_ITEM_OCT32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_format_u_default() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "--format=u")).unwrap(),
|
||||
vec!(FORMAT_ITEM_DEC32U));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_format_x_default() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "--format=x")).unwrap(),
|
||||
vec!(FORMAT_ITEM_HEX32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_format_f_default() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "--format=f")).unwrap(),
|
||||
vec!(FORMAT_ITEM_F32));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_long_format_next_arg() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "--format", "f8")).unwrap(),
|
||||
vec!(FORMAT_ITEM_F64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_short_format_next_arg() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "-t", "x8")).unwrap(),
|
||||
vec!(FORMAT_ITEM_HEX64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_short_format_combined_arg() {
|
||||
assert_eq!(parse_format_flags_str(
|
||||
&vec!("od", "-tu8")).unwrap(),
|
||||
vec!(FORMAT_ITEM_DEC64U));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_next_arg_invalid() {
|
||||
parse_format_flags_str(&vec!("od", "--format", "-v")).unwrap_err();
|
||||
parse_format_flags_str(&vec!("od", "--format")).unwrap_err();
|
||||
parse_format_flags_str(&vec!("od", "-t", "-v")).unwrap_err();
|
||||
parse_format_flags_str(&vec!("od", "-t")).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mixed_formats() {
|
||||
assert_eq!(parse_format_flags(
|
||||
&vec!(
|
||||
"od".to_string(),
|
||||
"--skip-bytes=2".to_string(),
|
||||
"-vItu1z".to_string(),
|
||||
"-N".to_string(),
|
||||
"1000".to_string(),
|
||||
"-xt".to_string(),
|
||||
"acdx1".to_string(),
|
||||
"--format=u2c".to_string(),
|
||||
"--format".to_string(),
|
||||
"f".to_string(),
|
||||
"-xAx".to_string(),
|
||||
"--".to_string(),
|
||||
"-h".to_string(),
|
||||
"--format=f8".to_string())).unwrap(),
|
||||
vec!(
|
||||
ParsedFormatterItemInfo::new(FORMAT_ITEM_DEC64S, false), // I
|
||||
ParsedFormatterItemInfo::new(FORMAT_ITEM_DEC8U, true), // tu1z
|
||||
ParsedFormatterItemInfo::new(FORMAT_ITEM_HEX16, false), // x
|
||||
ParsedFormatterItemInfo::new(FORMAT_ITEM_A, false), // ta
|
||||
ParsedFormatterItemInfo::new(FORMAT_ITEM_C, false), // tc
|
||||
ParsedFormatterItemInfo::new(FORMAT_ITEM_DEC32S, false), // td
|
||||
ParsedFormatterItemInfo::new(FORMAT_ITEM_HEX8, false), // tx1
|
||||
ParsedFormatterItemInfo::new(FORMAT_ITEM_DEC16U, false), // tu2
|
||||
ParsedFormatterItemInfo::new(FORMAT_ITEM_C, false), // tc
|
||||
ParsedFormatterItemInfo::new(FORMAT_ITEM_F32, false), // tf
|
||||
ParsedFormatterItemInfo::new(FORMAT_ITEM_HEX16, false), // x
|
||||
));
|
||||
}
|
362
src/od/parse_inputs.rs
Normal file
362
src/od/parse_inputs.rs
Normal file
|
@ -0,0 +1,362 @@
|
|||
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) -> 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"]) {
|
||||
// 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 Ok(CommandLineInputs::FileAndOffset(("-".to_string(), n, None)));
|
||||
}
|
||||
if input_strings.len() == 2 {
|
||||
return Ok(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());
|
||||
}
|
||||
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
|
||||
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![])).unwrap());
|
||||
|
||||
assert_eq!(CommandLineInputs::FileNames(vec!["-".to_string()]),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!["-"],
|
||||
vec![])).unwrap());
|
||||
|
||||
assert_eq!(CommandLineInputs::FileNames(vec!["file1".to_string()]),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!["file1"],
|
||||
vec![])).unwrap());
|
||||
|
||||
assert_eq!(CommandLineInputs::FileNames(vec!["file1".to_string(), "file2".to_string()]),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!["file1", "file2"],
|
||||
vec![])).unwrap());
|
||||
|
||||
assert_eq!(CommandLineInputs::FileNames(vec!["-".to_string(), "file1".to_string(), "file2".to_string()]),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!["-", "file1", "file2"],
|
||||
vec![])).unwrap());
|
||||
}
|
||||
|
||||
#[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![])).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![""])).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![""])).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"])).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"])).unwrap());
|
||||
|
||||
assert_eq!(CommandLineInputs::FileAndOffset(("file1".to_string(), 8, None)),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!["file1", "+10"],
|
||||
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![])).unwrap());
|
||||
|
||||
assert_eq!(CommandLineInputs::FileNames(vec!["file1".to_string(), "+10a".to_string()]),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!["file1", "+10a"],
|
||||
vec![""])).unwrap());
|
||||
|
||||
assert_eq!(CommandLineInputs::FileNames(vec!["file1".to_string(), "+10".to_string()]),
|
||||
parse_inputs(&MockOptions::new(
|
||||
vec!["file1", "+10"],
|
||||
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![""])).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> {
|
||||
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
|
||||
}
|
||||
}
|
128
src/od/parse_nrofbytes.rs
Normal file
128
src/od/parse_nrofbytes.rs
Normal file
|
@ -0,0 +1,128 @@
|
|||
|
||||
pub fn parse_number_of_bytes(s: &String) -> Result<usize, &'static str> {
|
||||
let mut start = 0;
|
||||
let mut len = s.len();
|
||||
let mut radix = 10;
|
||||
let mut multiply = 1;
|
||||
|
||||
if s.starts_with("0x") || s.starts_with("0X") {
|
||||
start = 2;
|
||||
radix = 16;
|
||||
} else if s.starts_with("0") {
|
||||
radix = 8;
|
||||
}
|
||||
|
||||
let mut ends_with = s.chars().rev();
|
||||
match ends_with.next() {
|
||||
Some('b') if radix != 16 => {
|
||||
multiply = 512;
|
||||
len -= 1;
|
||||
},
|
||||
Some('k') | Some('K') => {
|
||||
multiply = 1024;
|
||||
len -= 1;
|
||||
}
|
||||
Some('m') | Some('M') => {
|
||||
multiply = 1024 * 1024;
|
||||
len -= 1;
|
||||
}
|
||||
Some('G') => {
|
||||
multiply = 1024 * 1024 * 1024;
|
||||
len -= 1;
|
||||
}
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
Some('T') => {
|
||||
multiply = 1024 * 1024 * 1024 * 1024;
|
||||
len -= 1;
|
||||
}
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
Some('P') => {
|
||||
multiply = 1024 * 1024 * 1024 * 1024 * 1024;
|
||||
len -= 1;
|
||||
}
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
Some('E') => {
|
||||
multiply = 1024 * 1024 * 1024 * 1024 * 1024 * 1024;
|
||||
len -= 1;
|
||||
}
|
||||
Some('B') if radix != 16 => {
|
||||
len -= 2;
|
||||
multiply = match ends_with.next() {
|
||||
Some('k') | Some('K') => 1000,
|
||||
Some('m') | Some('M') => 1000 * 1000,
|
||||
Some('G') => 1000 * 1000 * 1000,
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
Some('T') => 1000 * 1000 * 1000 * 1000,
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
Some('P') => 1000 * 1000 * 1000 * 1000 * 1000,
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
Some('E') => 1000 * 1000 * 1000 * 1000 * 1000 * 1000,
|
||||
_ => return Err("parse failed"),
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
match usize::from_str_radix(&s[start..len], radix) {
|
||||
Ok(i) => Ok(i * multiply),
|
||||
Err(_) => Err("parse failed"),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn parse_number_of_bytes_str(s: &str) -> Result<usize, &'static str> {
|
||||
parse_number_of_bytes(&String::from(s))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_number_of_bytes() {
|
||||
// normal decimal numbers
|
||||
assert_eq!(0, parse_number_of_bytes_str("0").unwrap());
|
||||
assert_eq!(5, parse_number_of_bytes_str("5").unwrap());
|
||||
assert_eq!(999, parse_number_of_bytes_str("999").unwrap());
|
||||
assert_eq!(2 * 512, parse_number_of_bytes_str("2b").unwrap());
|
||||
assert_eq!(2 * 1024, parse_number_of_bytes_str("2k").unwrap());
|
||||
assert_eq!(4 * 1024, parse_number_of_bytes_str("4K").unwrap());
|
||||
assert_eq!(2 * 1048576, parse_number_of_bytes_str("2m").unwrap());
|
||||
assert_eq!(4 * 1048576, parse_number_of_bytes_str("4M").unwrap());
|
||||
assert_eq!(1073741824, parse_number_of_bytes_str("1G").unwrap());
|
||||
assert_eq!(2000, parse_number_of_bytes_str("2kB").unwrap());
|
||||
assert_eq!(4000, parse_number_of_bytes_str("4KB").unwrap());
|
||||
assert_eq!(2000000, parse_number_of_bytes_str("2mB").unwrap());
|
||||
assert_eq!(4000000, parse_number_of_bytes_str("4MB").unwrap());
|
||||
assert_eq!(2000000000, parse_number_of_bytes_str("2GB").unwrap());
|
||||
|
||||
// octal input
|
||||
assert_eq!(8, parse_number_of_bytes_str("010").unwrap());
|
||||
assert_eq!(8 * 512, parse_number_of_bytes_str("010b").unwrap());
|
||||
assert_eq!(8 * 1024, parse_number_of_bytes_str("010k").unwrap());
|
||||
assert_eq!(8 * 1048576, parse_number_of_bytes_str("010m").unwrap());
|
||||
|
||||
// hex input
|
||||
assert_eq!(15, parse_number_of_bytes_str("0xf").unwrap());
|
||||
assert_eq!(15, parse_number_of_bytes_str("0XF").unwrap());
|
||||
assert_eq!(27, parse_number_of_bytes_str("0x1b").unwrap());
|
||||
assert_eq!(16 * 1024, parse_number_of_bytes_str("0x10k").unwrap());
|
||||
assert_eq!(16 * 1048576, parse_number_of_bytes_str("0x10m").unwrap());
|
||||
|
||||
// invalid input
|
||||
parse_number_of_bytes_str("").unwrap_err();
|
||||
parse_number_of_bytes_str("-1").unwrap_err();
|
||||
parse_number_of_bytes_str("1e2").unwrap_err();
|
||||
parse_number_of_bytes_str("xyz").unwrap_err();
|
||||
parse_number_of_bytes_str("b").unwrap_err();
|
||||
parse_number_of_bytes_str("1Y").unwrap_err();
|
||||
parse_number_of_bytes_str("∞").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
fn test_parse_number_of_bytes_64bits() {
|
||||
assert_eq!(1099511627776, parse_number_of_bytes_str("1T").unwrap());
|
||||
assert_eq!(1125899906842624, parse_number_of_bytes_str("1P").unwrap());
|
||||
assert_eq!(1152921504606846976, parse_number_of_bytes_str("1E").unwrap());
|
||||
|
||||
assert_eq!(2000000000000, parse_number_of_bytes_str("2TB").unwrap());
|
||||
assert_eq!(2000000000000000, parse_number_of_bytes_str("2PB").unwrap());
|
||||
assert_eq!(2000000000000000000, parse_number_of_bytes_str("2EB").unwrap());
|
||||
}
|
204
src/od/partialreader.rs
Normal file
204
src/od/partialreader.rs
Normal file
|
@ -0,0 +1,204 @@
|
|||
use std::cmp;
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
use multifilereader::HasError;
|
||||
|
||||
/// When a large number of bytes must be skipped, it will be read into a
|
||||
/// dynamically allocated buffer. The buffer will be limited to this size.
|
||||
const MAX_SKIP_BUFFER: usize = 64 * 1024;
|
||||
|
||||
/// Wrapper for `std::io::Read` which can skip bytes at the beginning
|
||||
/// of the input, and it can limit the returned bytes to a particular
|
||||
/// number of bytes.
|
||||
pub struct PartialReader<R> {
|
||||
inner: R,
|
||||
skip: usize,
|
||||
limit: Option<usize>,
|
||||
}
|
||||
|
||||
impl<R> PartialReader<R> {
|
||||
/// Create a new `PartialReader` wrapping `inner`, which will skip
|
||||
/// `skip` bytes, and limits the output to `limit` bytes. Set `limit`
|
||||
/// to `None` if there should be no limit.
|
||||
pub fn new(inner: R, skip: usize, limit: Option<usize>) -> Self {
|
||||
PartialReader {
|
||||
inner: inner,
|
||||
skip: skip,
|
||||
limit: limit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> Read for PartialReader<R> {
|
||||
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
|
||||
if self.skip > 0 {
|
||||
let buf_size = cmp::min(self.skip, MAX_SKIP_BUFFER);
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(buf_size);
|
||||
unsafe { bytes.set_len(buf_size); }
|
||||
|
||||
while self.skip > 0 {
|
||||
let skip_count = cmp::min(self.skip, buf_size);
|
||||
|
||||
match self.inner.read_exact(&mut bytes[..skip_count]) {
|
||||
Err(e) => return Err(e),
|
||||
Ok(()) => self.skip -= skip_count,
|
||||
}
|
||||
}
|
||||
}
|
||||
match self.limit {
|
||||
None => self.inner.read(out),
|
||||
Some(0) => Ok(0),
|
||||
Some(ref mut limit) => {
|
||||
let slice = if *limit > out.len() { out } else { &mut out[0..*limit] };
|
||||
match self.inner.read(slice) {
|
||||
Err(e) => Err(e),
|
||||
Ok(r) => {
|
||||
*limit -= r;
|
||||
Ok(r)
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: HasError> HasError for PartialReader<R> {
|
||||
fn has_error(&self) -> bool {
|
||||
self.inner.has_error()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::{Cursor, Read, ErrorKind};
|
||||
use std::error::Error;
|
||||
use mockstream::*;
|
||||
|
||||
#[test]
|
||||
fn test_read_without_limits() {
|
||||
let mut v = [0; 10];
|
||||
let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 0, None);
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 8);
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_without_limits_with_error() {
|
||||
let mut v = [0; 10];
|
||||
let f = FailingMockStream::new(ErrorKind::PermissionDenied, "No access", 3);
|
||||
let mut sut = PartialReader::new(f, 0, None);
|
||||
|
||||
let error = sut.read(v.as_mut()).unwrap_err();
|
||||
assert_eq!(error.kind(), ErrorKind::PermissionDenied);
|
||||
assert_eq!(error.description(), "No access");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_skipping_bytes() {
|
||||
let mut v = [0; 10];
|
||||
let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 2, None);
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 6);
|
||||
assert_eq!(v, [0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_skipping_all() {
|
||||
let mut v = [0; 10];
|
||||
let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 20, None);
|
||||
|
||||
let error = sut.read(v.as_mut()).unwrap_err();
|
||||
assert_eq!(error.kind(), ErrorKind::UnexpectedEof);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_skipping_with_error() {
|
||||
let mut v = [0; 10];
|
||||
let f = FailingMockStream::new(ErrorKind::PermissionDenied, "No access", 3);
|
||||
let mut sut = PartialReader::new(f, 2, None);
|
||||
|
||||
let error = sut.read(v.as_mut()).unwrap_err();
|
||||
assert_eq!(error.kind(), ErrorKind::PermissionDenied);
|
||||
assert_eq!(error.description(), "No access");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_skipping_with_two_reads_during_skip() {
|
||||
let mut v = [0; 10];
|
||||
let c = Cursor::new(&b"a"[..])
|
||||
.chain(Cursor::new(&b"bcdefgh"[..]));
|
||||
let mut sut = PartialReader::new(c, 2, None);
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 6);
|
||||
assert_eq!(v, [0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_skipping_huge_number() {
|
||||
let mut v = [0; 10];
|
||||
// test if it does not eat all memory....
|
||||
let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), usize::max_value(), None);
|
||||
|
||||
sut.read(v.as_mut()).unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_limitting_all() {
|
||||
let mut v = [0; 10];
|
||||
let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 0, Some(0));
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_limitting() {
|
||||
let mut v = [0; 10];
|
||||
let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 0, Some(6));
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 6);
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_limitting_with_error() {
|
||||
let mut v = [0; 10];
|
||||
let f = FailingMockStream::new(ErrorKind::PermissionDenied, "No access", 3);
|
||||
let mut sut = PartialReader::new(f, 0, Some(6));
|
||||
|
||||
let error = sut.read(v.as_mut()).unwrap_err();
|
||||
assert_eq!(error.kind(), ErrorKind::PermissionDenied);
|
||||
assert_eq!(error.description(), "No access");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_limitting_with_large_limit() {
|
||||
let mut v = [0; 10];
|
||||
let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 0, Some(20));
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 8);
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_limitting_with_multiple_reads() {
|
||||
let mut v = [0; 3];
|
||||
let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 0, Some(6));
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 3);
|
||||
assert_eq!(v, [0x61, 0x62, 0x63]);
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 3);
|
||||
assert_eq!(v, [0x64, 0x65, 0x66]);
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_skipping_and_limitting() {
|
||||
let mut v = [0; 10];
|
||||
let mut sut = PartialReader::new(Cursor::new(&b"abcdefgh"[..]), 2, Some(4));
|
||||
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 4);
|
||||
assert_eq!(v, [0x63, 0x64, 0x65, 0x66, 0, 0, 0, 0, 0, 0]);
|
||||
}
|
||||
}
|
212
src/od/peekreader.rs
Normal file
212
src/od/peekreader.rs
Normal file
|
@ -0,0 +1,212 @@
|
|||
//! Contains the trait `PeekRead` and type `PeekReader` implementing it.
|
||||
|
||||
use std::io;
|
||||
use std::io::{Read, Write};
|
||||
use multifilereader::HasError;
|
||||
|
||||
/// A trait which supplies a function to peek into a stream without
|
||||
/// actually reading it.
|
||||
///
|
||||
/// Like `std::io::Read`, it allows to read data from a stream, with
|
||||
/// the additional possibility to reserve a part of the returned data
|
||||
/// with the data which will be read in subsequent calls.
|
||||
///
|
||||
pub trait PeekRead {
|
||||
/// Reads data into a buffer.
|
||||
///
|
||||
/// Fills `out` with data. The last `peek_size` bytes of `out` are
|
||||
/// used for data which keeps available on subsequent calls.
|
||||
/// `peek_size` must be smaller or equal to the size of `out`.
|
||||
///
|
||||
/// Returns a tuple where the first number is the number of bytes
|
||||
/// read from the stream, and the second number is the number of
|
||||
/// bytes additionally read. Any of the numbers might be zero.
|
||||
/// It can also return an error.
|
||||
///
|
||||
/// A type implementing this trait, will typically also implement
|
||||
/// `std::io::Read`.
|
||||
///
|
||||
/// # Panics
|
||||
/// Might panic if `peek_size` is larger then the size of `out`
|
||||
fn peek_read(&mut self, out: &mut [u8], peek_size: usize) -> io::Result<(usize,usize)>;
|
||||
}
|
||||
|
||||
/// Wrapper for `std::io::Read` allowing to peek into the data to be read.
|
||||
pub struct PeekReader<R> {
|
||||
inner: R,
|
||||
temp_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl<R> PeekReader<R> {
|
||||
/// Create a new `PeekReader` wrapping `inner`
|
||||
pub fn new(inner: R) -> Self {
|
||||
PeekReader {
|
||||
inner: inner,
|
||||
temp_buffer: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> PeekReader<R> {
|
||||
fn read_from_tempbuffer(&mut self, mut out: &mut [u8]) -> usize {
|
||||
match out.write(self.temp_buffer.as_mut_slice()) {
|
||||
Ok(n) => {
|
||||
self.temp_buffer.drain(..n);
|
||||
n
|
||||
},
|
||||
Err(_) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to_tempbuffer(&mut self, bytes: &[u8]) {
|
||||
// if temp_buffer is not empty, data has to be inserted in front
|
||||
let org_buffer: Vec<_> = self.temp_buffer.drain(..).collect();
|
||||
self.temp_buffer.write(bytes).unwrap();
|
||||
self.temp_buffer.extend(org_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> Read for PeekReader<R> {
|
||||
fn read(&mut self, out: &mut [u8]) -> io::Result<usize> {
|
||||
let start_pos = self.read_from_tempbuffer(out);
|
||||
match self.inner.read(&mut out[start_pos..]) {
|
||||
Err(e) => Err(e),
|
||||
Ok(n) => Ok(n + start_pos),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read> PeekRead for PeekReader<R> {
|
||||
/// Reads data into a buffer.
|
||||
///
|
||||
/// See `PeekRead::peek_read`.
|
||||
///
|
||||
/// # Panics
|
||||
/// If `peek_size` is larger then the size of `out`
|
||||
fn peek_read(&mut self, out: &mut [u8], peek_size: usize) -> io::Result<(usize,usize)> {
|
||||
assert!(out.len() >= peek_size);
|
||||
match self.read(out) {
|
||||
Err(e) => Err(e),
|
||||
Ok(bytes_in_buffer) => {
|
||||
let unused = out.len() - bytes_in_buffer;
|
||||
if peek_size <= unused {
|
||||
Ok((bytes_in_buffer, 0))
|
||||
} else {
|
||||
let actual_peek_size = peek_size - unused;
|
||||
let real_size = bytes_in_buffer - actual_peek_size;
|
||||
self.write_to_tempbuffer(&out[real_size..bytes_in_buffer]);
|
||||
Ok((real_size, actual_peek_size))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: HasError> HasError for PeekReader<R> {
|
||||
fn has_error(&self) -> bool {
|
||||
self.inner.has_error()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::io::{Cursor, Read};
|
||||
|
||||
#[test]
|
||||
fn test_read_normal() {
|
||||
let mut sut = PeekReader::new(Cursor::new(&b"abcdefgh"[..]));
|
||||
|
||||
let mut v = [0; 10];
|
||||
assert_eq!(sut.read(v.as_mut()).unwrap(), 8);
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peek_read_without_buffer() {
|
||||
let mut sut = PeekReader::new(Cursor::new(&b"abcdefgh"[..]));
|
||||
|
||||
let mut v = [0; 10];
|
||||
assert_eq!(sut.peek_read(v.as_mut(), 0).unwrap(), (8,0));
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peek_read_and_read() {
|
||||
let mut sut = PeekReader::new(Cursor::new(&b"abcdefghij"[..]));
|
||||
|
||||
let mut v = [0; 8];
|
||||
assert_eq!(sut.peek_read(v.as_mut(), 4).unwrap(), (4, 4));
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68]);
|
||||
|
||||
let mut v2 = [0; 8];
|
||||
assert_eq!(sut.read(v2.as_mut()).unwrap(), 6);
|
||||
assert_eq!(v2, [0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peek_read_multiple_times() {
|
||||
let mut sut = PeekReader::new(Cursor::new(&b"abcdefghij"[..]));
|
||||
|
||||
let mut s1 = [0; 8];
|
||||
assert_eq!(sut.peek_read(s1.as_mut(), 4).unwrap(), (4, 4));
|
||||
assert_eq!(s1, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68]);
|
||||
|
||||
let mut s2 = [0; 8];
|
||||
assert_eq!(sut.peek_read(s2.as_mut(), 4).unwrap(), (4, 2));
|
||||
assert_eq!(s2, [0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0, 0]);
|
||||
|
||||
let mut s3 = [0; 8];
|
||||
assert_eq!(sut.peek_read(s3.as_mut(), 4).unwrap(), (2, 0));
|
||||
assert_eq!(s3, [0x69, 0x6a, 0, 0, 0, 0, 0, 0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peek_read_and_read_with_small_buffer() {
|
||||
let mut sut = PeekReader::new(Cursor::new(&b"abcdefghij"[..]));
|
||||
|
||||
let mut v = [0; 8];
|
||||
assert_eq!(sut.peek_read(v.as_mut(), 4).unwrap(), (4, 4));
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68]);
|
||||
|
||||
let mut v2 = [0; 2];
|
||||
assert_eq!(sut.read(v2.as_mut()).unwrap(), 2);
|
||||
assert_eq!(v2, [0x65, 0x66]);
|
||||
assert_eq!(sut.read(v2.as_mut()).unwrap(), 2);
|
||||
assert_eq!(v2, [0x67, 0x68]);
|
||||
assert_eq!(sut.read(v2.as_mut()).unwrap(), 2);
|
||||
assert_eq!(v2, [0x69, 0x6a]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peek_read_with_smaller_buffer() {
|
||||
let mut sut = PeekReader::new(Cursor::new(&b"abcdefghij"[..]));
|
||||
|
||||
let mut v = [0; 8];
|
||||
assert_eq!(sut.peek_read(v.as_mut(), 4).unwrap(), (4, 4));
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68]);
|
||||
|
||||
let mut v2 = [0; 2];
|
||||
assert_eq!(sut.peek_read(v2.as_mut(), 2).unwrap(), (0, 2));
|
||||
assert_eq!(v2, [0x65, 0x66]);
|
||||
assert_eq!(sut.peek_read(v2.as_mut(), 0).unwrap(), (2, 0));
|
||||
assert_eq!(v2, [0x65, 0x66]);
|
||||
assert_eq!(sut.peek_read(v2.as_mut(), 0).unwrap(), (2, 0));
|
||||
assert_eq!(v2, [0x67, 0x68]);
|
||||
assert_eq!(sut.peek_read(v2.as_mut(), 0).unwrap(), (2, 0));
|
||||
assert_eq!(v2, [0x69, 0x6a]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peek_read_peek_with_larger_peek_buffer() {
|
||||
let mut sut = PeekReader::new(Cursor::new(&b"abcdefghij"[..]));
|
||||
|
||||
let mut v = [0; 8];
|
||||
assert_eq!(sut.peek_read(v.as_mut(), 4).unwrap(), (4, 4));
|
||||
assert_eq!(v, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68]);
|
||||
|
||||
let mut v2 = [0; 8];
|
||||
assert_eq!(sut.peek_read(v2.as_mut(), 8).unwrap(), (0, 6));
|
||||
assert_eq!(v2, [0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0, 0]);
|
||||
}
|
||||
}
|
166
src/od/prn_char.rs
Normal file
166
src/od/prn_char.rs
Normal file
|
@ -0,0 +1,166 @@
|
|||
use std::str::from_utf8;
|
||||
use formatteriteminfo::*;
|
||||
|
||||
pub static FORMAT_ITEM_A: FormatterItemInfo = FormatterItemInfo {
|
||||
byte_size: 1,
|
||||
print_width: 4,
|
||||
formatter: FormatWriter::IntWriter(format_item_a),
|
||||
};
|
||||
|
||||
pub static FORMAT_ITEM_C: FormatterItemInfo = FormatterItemInfo {
|
||||
byte_size: 1,
|
||||
print_width: 4,
|
||||
formatter: FormatWriter::MultibyteWriter(format_item_c),
|
||||
};
|
||||
|
||||
|
||||
static A_CHRS: [&'static str; 128] =
|
||||
["nul", "soh", "stx", "etx", "eot", "enq", "ack", "bel",
|
||||
"bs", "ht", "nl", "vt", "ff", "cr", "so", "si",
|
||||
"dle", "dc1", "dc2", "dc3", "dc4", "nak", "syn", "etb",
|
||||
"can", "em", "sub", "esc", "fs", "gs", "rs", "us",
|
||||
"sp", "!", "\"", "#", "$", "%", "&", "'",
|
||||
"(", ")", "*", "+", ",", "-", ".", "/",
|
||||
"0", "1", "2", "3", "4", "5", "6", "7",
|
||||
"8", "9", ":", ";", "<", "=", ">", "?",
|
||||
"@", "A", "B", "C", "D", "E", "F", "G",
|
||||
"H", "I", "J", "K", "L", "M", "N", "O",
|
||||
"P", "Q", "R", "S", "T", "U", "V", "W",
|
||||
"X", "Y", "Z", "[", "\\", "]", "^", "_",
|
||||
"`", "a", "b", "c", "d", "e", "f", "g",
|
||||
"h", "i", "j", "k", "l", "m", "n", "o",
|
||||
"p", "q", "r", "s", "t", "u", "v", "w",
|
||||
"x", "y", "z", "{", "|", "}", "~", "del"];
|
||||
|
||||
fn format_item_a(p: u64) -> String {
|
||||
// itembytes == 1
|
||||
let b = (p & 0x7f) as u8;
|
||||
format!("{:>4}", A_CHRS.get(b as usize).unwrap_or(&"??")
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
static C_CHRS: [&'static str; 128] = [
|
||||
"\\0", "001", "002", "003", "004", "005", "006", "\\a",
|
||||
"\\b", "\\t", "\\n", "\\v", "\\f", "\\r", "016", "017",
|
||||
"020", "021", "022", "023", "024", "025", "026", "027",
|
||||
"030", "031", "032", "033", "034", "035", "036", "037",
|
||||
" ", "!", "\"", "#", "$", "%", "&", "'",
|
||||
"(", ")", "*", "+", ",", "-", ".", "/",
|
||||
"0", "1", "2", "3", "4", "5", "6", "7",
|
||||
"8", "9", ":", ";", "<", "=", ">", "?",
|
||||
"@", "A", "B", "C", "D", "E", "F", "G",
|
||||
"H", "I", "J", "K", "L", "M", "N", "O",
|
||||
"P", "Q", "R", "S", "T", "U", "V", "W",
|
||||
"X", "Y", "Z", "[", "\\", "]", "^", "_",
|
||||
"`", "a", "b", "c", "d", "e", "f", "g",
|
||||
"h", "i", "j", "k", "l", "m", "n", "o",
|
||||
"p", "q", "r", "s", "t", "u", "v", "w",
|
||||
"x", "y", "z", "{", "|", "}", "~", "177"];
|
||||
|
||||
|
||||
fn format_item_c(bytes: &[u8]) -> String {
|
||||
// itembytes == 1
|
||||
let b = bytes[0];
|
||||
|
||||
if b & 0x80 == 0x00 {
|
||||
match C_CHRS.get(b as usize) {
|
||||
Some(s) => format!("{:>4}", s),
|
||||
None => format!("{:>4}", b),
|
||||
}
|
||||
} else if (b & 0xc0) == 0x80 {
|
||||
// second or subsequent octet of an utf-8 sequence
|
||||
String::from(" **")
|
||||
} else if ((b & 0xe0) == 0xc0) && (bytes.len() >= 2) {
|
||||
// start of a 2 octet utf-8 sequence
|
||||
match from_utf8(&bytes[0..2]) {
|
||||
Ok(s) => { format!("{:>4}", s) },
|
||||
Err(_) => { format!(" {:03o}", b) },
|
||||
}
|
||||
} else if ((b & 0xf0) == 0xe0) && (bytes.len() >= 3) {
|
||||
// start of a 3 octet utf-8 sequence
|
||||
match from_utf8(&bytes[0..3]) {
|
||||
Ok(s) => { format!("{:>4}", s) },
|
||||
Err(_) => { format!(" {:03o}", b) },
|
||||
}
|
||||
} else if ((b & 0xf8) == 0xf0) && (bytes.len() >= 4) {
|
||||
// start of a 4 octet utf-8 sequence
|
||||
match from_utf8(&bytes[0..4]) {
|
||||
Ok(s) => { format!("{:>4}", s) },
|
||||
Err(_) => { format!(" {:03o}", b) },
|
||||
}
|
||||
} else {
|
||||
// invalid utf-8
|
||||
format!(" {:03o}", b)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format_ascii_dump(bytes: &[u8]) -> String {
|
||||
let mut result = String::new();
|
||||
|
||||
result.push('>');
|
||||
for c in bytes.iter() {
|
||||
if *c >= 0x20 && *c <= 0x7e {
|
||||
result.push_str(C_CHRS[*c as usize]);
|
||||
} else {
|
||||
result.push('.');
|
||||
}
|
||||
}
|
||||
result.push('<');
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_item_a() {
|
||||
assert_eq!(" nul", format_item_a(0x00));
|
||||
assert_eq!(" soh", format_item_a(0x01));
|
||||
assert_eq!(" sp", format_item_a(0x20));
|
||||
assert_eq!(" A", format_item_a(0x41));
|
||||
assert_eq!(" ~", format_item_a(0x7e));
|
||||
assert_eq!(" del", format_item_a(0x7f));
|
||||
|
||||
assert_eq!(" nul", format_item_a(0x80));
|
||||
assert_eq!(" A", format_item_a(0xc1));
|
||||
assert_eq!(" ~", format_item_a(0xfe));
|
||||
assert_eq!(" del", format_item_a(0xff));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_item_c() {
|
||||
assert_eq!(" \\0", format_item_c(&[0x00]));
|
||||
assert_eq!(" 001", format_item_c(&[0x01]));
|
||||
assert_eq!(" ", format_item_c(&[0x20]));
|
||||
assert_eq!(" A", format_item_c(&[0x41]));
|
||||
assert_eq!(" ~", format_item_c(&[0x7e]));
|
||||
assert_eq!(" 177", format_item_c(&[0x7f]));
|
||||
assert_eq!(" A", format_item_c(&[0x41, 0x21]));
|
||||
|
||||
assert_eq!(" **", format_item_c(&[0x80]));
|
||||
assert_eq!(" **", format_item_c(&[0x9f]));
|
||||
|
||||
assert_eq!(" ß", format_item_c(&[0xc3, 0x9f]));
|
||||
assert_eq!(" ß", format_item_c(&[0xc3, 0x9f, 0x21]));
|
||||
|
||||
assert_eq!(" \u{1000}", format_item_c(&[0xe1, 0x80, 0x80]));
|
||||
assert_eq!(" \u{1000}", format_item_c(&[0xe1, 0x80, 0x80, 0x21]));
|
||||
|
||||
assert_eq!(" \u{1f496}", format_item_c(&[0xf0, 0x9f, 0x92, 0x96]));
|
||||
assert_eq!(" \u{1f496}", format_item_c(&[0xf0, 0x9f, 0x92, 0x96, 0x21]));
|
||||
|
||||
assert_eq!(" 300", format_item_c(&[0xc0, 0x80])); // invalid utf-8 (MUTF-8 null)
|
||||
assert_eq!(" 301", format_item_c(&[0xc1, 0xa1])); // invalid utf-8
|
||||
assert_eq!(" 303", format_item_c(&[0xc3, 0xc3])); // invalid utf-8
|
||||
assert_eq!(" 360", format_item_c(&[0xf0, 0x82, 0x82, 0xac])); // invalid utf-8 (overlong)
|
||||
assert_eq!(" 360", format_item_c(&[0xf0, 0x9f, 0x92])); // invalid utf-8 (missing octet)
|
||||
assert_eq!(" \u{10FFFD}", format_item_c(&[0xf4, 0x8f, 0xbf, 0xbd])); // largest valid utf-8
|
||||
assert_eq!(" 364", format_item_c(&[0xf4, 0x90, 0x00, 0x00])); // invalid utf-8
|
||||
assert_eq!(" 365", format_item_c(&[0xf5, 0x80, 0x80, 0x80])); // invalid utf-8
|
||||
assert_eq!(" 377", format_item_c(&[0xff])); // invalid utf-8
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_ascii_dump() {
|
||||
assert_eq!(">.<", format_ascii_dump(&[0x00]));
|
||||
assert_eq!(">. A~.<", format_ascii_dump(&[0x1f, 0x20, 0x41, 0x7e, 0x7f]));
|
||||
}
|
210
src/od/prn_float.rs
Normal file
210
src/od/prn_float.rs
Normal file
|
@ -0,0 +1,210 @@
|
|||
use std::num::FpCategory;
|
||||
use half::f16;
|
||||
use std::f32;
|
||||
use std::f64;
|
||||
use formatteriteminfo::*;
|
||||
|
||||
pub static FORMAT_ITEM_F16: FormatterItemInfo = FormatterItemInfo {
|
||||
byte_size: 2,
|
||||
print_width: 10,
|
||||
formatter: FormatWriter::FloatWriter(format_item_flo16),
|
||||
};
|
||||
|
||||
pub static FORMAT_ITEM_F32: FormatterItemInfo = FormatterItemInfo {
|
||||
byte_size: 4,
|
||||
print_width: 15,
|
||||
formatter: FormatWriter::FloatWriter(format_item_flo32),
|
||||
};
|
||||
|
||||
pub static FORMAT_ITEM_F64: FormatterItemInfo = FormatterItemInfo {
|
||||
byte_size: 8,
|
||||
print_width: 25,
|
||||
formatter: FormatWriter::FloatWriter(format_item_flo64),
|
||||
};
|
||||
|
||||
pub fn format_item_flo16(f: f64) -> String {
|
||||
format!(" {}", format_flo16(f16::from_f64(f)))
|
||||
}
|
||||
|
||||
pub fn format_item_flo32(f: f64) -> String {
|
||||
format!(" {}", format_flo32(f as f32))
|
||||
}
|
||||
|
||||
pub fn format_item_flo64(f: f64) -> String {
|
||||
format!(" {}", format_flo64(f))
|
||||
}
|
||||
|
||||
fn format_flo16(f: f16) -> String {
|
||||
format_float(f64::from(f), 9, 4)
|
||||
}
|
||||
|
||||
// formats float with 8 significant digits, eg 12345678 or -1.2345678e+12
|
||||
// always retuns a string of 14 characters
|
||||
fn format_flo32(f: f32) -> String {
|
||||
let width: usize = 14;
|
||||
let precision: usize = 8;
|
||||
|
||||
if f.classify() == FpCategory::Subnormal {
|
||||
// subnormal numbers will be normal as f64, so will print with a wrong precision
|
||||
format!("{:width$e}", f, width = width) // subnormal numbers
|
||||
} else {
|
||||
format_float(f as f64, width, precision)
|
||||
}
|
||||
}
|
||||
|
||||
fn format_flo64(f: f64) -> String {
|
||||
format_float(f, 24, 17)
|
||||
}
|
||||
|
||||
fn format_float(f: f64, width: usize, precision: usize) -> String {
|
||||
if !f.is_normal() {
|
||||
if f == -0.0 && f.is_sign_negative() { return format!("{:>width$}", "-0", width = width) }
|
||||
if f == 0.0 || !f.is_finite() { return format!("{:width$}", f, width = width) }
|
||||
return format!("{:width$e}", f, width = width) // subnormal numbers
|
||||
}
|
||||
|
||||
let mut l = f.abs().log10().floor() as i32;
|
||||
|
||||
let r = 10f64.powi(l);
|
||||
if (f > 0.0 && r > f) || (f < 0.0 && -r < f) {
|
||||
// fix precision error
|
||||
l = l - 1;
|
||||
}
|
||||
|
||||
if l >= 0 && l <= (precision as i32 - 1) {
|
||||
format!("{:width$.dec$}", f,
|
||||
width = width,
|
||||
dec = (precision-1) - l as usize)
|
||||
} else if l == -1 {
|
||||
format!("{:width$.dec$}", f,
|
||||
width = width,
|
||||
dec = precision)
|
||||
} else {
|
||||
format!("{:width$.dec$e}", f,
|
||||
width = width,
|
||||
dec = precision - 1)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_flo32() {
|
||||
assert_eq!(format_flo32(1.0), " 1.0000000");
|
||||
assert_eq!(format_flo32(9.9999990), " 9.9999990");
|
||||
assert_eq!(format_flo32(10.0), " 10.000000");
|
||||
assert_eq!(format_flo32(99.999977), " 99.999977");
|
||||
assert_eq!(format_flo32(99.999992), " 99.999992");
|
||||
assert_eq!(format_flo32(100.0), " 100.00000");
|
||||
assert_eq!(format_flo32(999.99994), " 999.99994");
|
||||
assert_eq!(format_flo32(1000.0), " 1000.0000");
|
||||
assert_eq!(format_flo32(9999.9990), " 9999.9990");
|
||||
assert_eq!(format_flo32(10000.0), " 10000.000");
|
||||
assert_eq!(format_flo32(99999.992), " 99999.992");
|
||||
assert_eq!(format_flo32(100000.0), " 100000.00");
|
||||
assert_eq!(format_flo32(999999.94), " 999999.94");
|
||||
assert_eq!(format_flo32(1000000.0), " 1000000.0");
|
||||
assert_eq!(format_flo32(9999999.0), " 9999999.0");
|
||||
assert_eq!(format_flo32(10000000.0), " 10000000");
|
||||
assert_eq!(format_flo32(99999992.0), " 99999992");
|
||||
assert_eq!(format_flo32(100000000.0), " 1.0000000e8");
|
||||
assert_eq!(format_flo32(9.9999994e8), " 9.9999994e8");
|
||||
assert_eq!(format_flo32(1.0e9), " 1.0000000e9");
|
||||
assert_eq!(format_flo32(9.9999990e9), " 9.9999990e9");
|
||||
assert_eq!(format_flo32(1.0e10), " 1.0000000e10");
|
||||
|
||||
assert_eq!(format_flo32(0.1), " 0.10000000");
|
||||
assert_eq!(format_flo32(0.99999994), " 0.99999994");
|
||||
assert_eq!(format_flo32(0.010000001), " 1.0000001e-2");
|
||||
assert_eq!(format_flo32(0.099999994), " 9.9999994e-2");
|
||||
assert_eq!(format_flo32(0.001), " 1.0000000e-3");
|
||||
assert_eq!(format_flo32(0.0099999998), " 9.9999998e-3");
|
||||
|
||||
assert_eq!(format_flo32(-1.0), " -1.0000000");
|
||||
assert_eq!(format_flo32(-9.9999990), " -9.9999990");
|
||||
assert_eq!(format_flo32(-10.0), " -10.000000");
|
||||
assert_eq!(format_flo32(-99.999977), " -99.999977");
|
||||
assert_eq!(format_flo32(-99.999992), " -99.999992");
|
||||
assert_eq!(format_flo32(-100.0), " -100.00000");
|
||||
assert_eq!(format_flo32(-999.99994), " -999.99994");
|
||||
assert_eq!(format_flo32(-1000.0), " -1000.0000");
|
||||
assert_eq!(format_flo32(-9999.9990), " -9999.9990");
|
||||
assert_eq!(format_flo32(-10000.0), " -10000.000");
|
||||
assert_eq!(format_flo32(-99999.992), " -99999.992");
|
||||
assert_eq!(format_flo32(-100000.0), " -100000.00");
|
||||
assert_eq!(format_flo32(-999999.94), " -999999.94");
|
||||
assert_eq!(format_flo32(-1000000.0), " -1000000.0");
|
||||
assert_eq!(format_flo32(-9999999.0), " -9999999.0");
|
||||
assert_eq!(format_flo32(-10000000.0), " -10000000");
|
||||
assert_eq!(format_flo32(-99999992.0), " -99999992");
|
||||
assert_eq!(format_flo32(-100000000.0), " -1.0000000e8");
|
||||
assert_eq!(format_flo32(-9.9999994e8), " -9.9999994e8");
|
||||
assert_eq!(format_flo32(-1.0e9), " -1.0000000e9");
|
||||
assert_eq!(format_flo32(-9.9999990e9), " -9.9999990e9");
|
||||
assert_eq!(format_flo32(-1.0e10), " -1.0000000e10");
|
||||
|
||||
assert_eq!(format_flo32(-0.1), " -0.10000000");
|
||||
assert_eq!(format_flo32(-0.99999994), " -0.99999994");
|
||||
assert_eq!(format_flo32(-0.010000001), " -1.0000001e-2");
|
||||
assert_eq!(format_flo32(-0.099999994), " -9.9999994e-2");
|
||||
assert_eq!(format_flo32(-0.001), " -1.0000000e-3");
|
||||
assert_eq!(format_flo32(-0.0099999998), " -9.9999998e-3");
|
||||
|
||||
assert_eq!(format_flo32(3.4028233e38), " 3.4028233e38");
|
||||
assert_eq!(format_flo32(-3.4028233e38), " -3.4028233e38");
|
||||
assert_eq!(format_flo32(-1.1663108e-38),"-1.1663108e-38");
|
||||
assert_eq!(format_flo32(-4.7019771e-38),"-4.7019771e-38");
|
||||
assert_eq!(format_flo32(1e-45), " 1e-45");
|
||||
|
||||
assert_eq!(format_flo32(-3.402823466e+38), " -3.4028235e38");
|
||||
assert_eq!(format_flo32(f32::NAN), " NaN");
|
||||
assert_eq!(format_flo32(f32::INFINITY), " inf");
|
||||
assert_eq!(format_flo32(f32::NEG_INFINITY), " -inf");
|
||||
assert_eq!(format_flo32(-0.0), " -0");
|
||||
assert_eq!(format_flo32(0.0), " 0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_flo64() {
|
||||
assert_eq!(format_flo64(1.0), " 1.0000000000000000");
|
||||
assert_eq!(format_flo64(10.0), " 10.000000000000000");
|
||||
assert_eq!(format_flo64(1000000000000000.0), " 1000000000000000.0");
|
||||
assert_eq!(format_flo64(10000000000000000.0), " 10000000000000000");
|
||||
assert_eq!(format_flo64(100000000000000000.0), " 1.0000000000000000e17");
|
||||
|
||||
assert_eq!(format_flo64(-0.1), " -0.10000000000000001");
|
||||
assert_eq!(format_flo64(-0.01), " -1.0000000000000000e-2");
|
||||
|
||||
assert_eq!(format_flo64(-2.2250738585072014e-308),"-2.2250738585072014e-308");
|
||||
assert_eq!(format_flo64(4e-320), " 4e-320");
|
||||
assert_eq!(format_flo64(f64::NAN), " NaN");
|
||||
assert_eq!(format_flo64(f64::INFINITY), " inf");
|
||||
assert_eq!(format_flo64(f64::NEG_INFINITY), " -inf");
|
||||
assert_eq!(format_flo64(-0.0), " -0");
|
||||
assert_eq!(format_flo64(0.0), " 0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_flo16() {
|
||||
use half::consts::*;
|
||||
|
||||
assert_eq!(format_flo16(f16::from_bits(0x8400u16)), "-6.104e-5");
|
||||
assert_eq!(format_flo16(f16::from_bits(0x8401u16)), "-6.109e-5");
|
||||
assert_eq!(format_flo16(f16::from_bits(0x8402u16)), "-6.115e-5");
|
||||
assert_eq!(format_flo16(f16::from_bits(0x8403u16)), "-6.121e-5");
|
||||
|
||||
assert_eq!(format_flo16(f16::from_f32(1.0)), " 1.000");
|
||||
assert_eq!(format_flo16(f16::from_f32(10.0)), " 10.00");
|
||||
assert_eq!(format_flo16(f16::from_f32(100.0)), " 100.0");
|
||||
assert_eq!(format_flo16(f16::from_f32(1000.0)), " 1000");
|
||||
assert_eq!(format_flo16(f16::from_f32(10000.0)), " 1.000e4");
|
||||
|
||||
assert_eq!(format_flo16(f16::from_f32(-0.2)), " -0.2000");
|
||||
assert_eq!(format_flo16(f16::from_f32(-0.02)), "-2.000e-2");
|
||||
|
||||
assert_eq!(format_flo16(MIN_POSITIVE_SUBNORMAL), " 5.966e-8");
|
||||
assert_eq!(format_flo16(MIN), " -6.550e4");
|
||||
assert_eq!(format_flo16(NAN), " NaN");
|
||||
assert_eq!(format_flo16(INFINITY), " inf");
|
||||
assert_eq!(format_flo16(NEG_INFINITY), " -inf");
|
||||
assert_eq!(format_flo16(NEG_ZERO), " -0");
|
||||
assert_eq!(format_flo16(ZERO), " 0");
|
||||
}
|
151
src/od/prn_int.rs
Normal file
151
src/od/prn_int.rs
Normal file
|
@ -0,0 +1,151 @@
|
|||
use formatteriteminfo::*;
|
||||
|
||||
/// format string to print octal using `int_writer_unsigned`
|
||||
macro_rules! OCT { () => { " {:0width$o}" }}
|
||||
/// format string to print hexadecimal using `int_writer_unsigned`
|
||||
macro_rules! HEX { () => { " {:0width$x}" }}
|
||||
/// format string to print decimal using `int_writer_unsigned` or `int_writer_signed`
|
||||
macro_rules! DEC { () => { " {:width$}" }}
|
||||
|
||||
/// defines a static struct of type `FormatterItemInfo` called `$NAME`
|
||||
///
|
||||
/// Used to format unsigned integer types with help of a function called `$function`
|
||||
/// `$byte_size` is the size of the type, `$print_width` is the maximum width in
|
||||
/// human-readable format. `$format_str` is one of OCT, HEX or DEC
|
||||
macro_rules! int_writer_unsigned {
|
||||
($NAME:ident, $byte_size:expr, $print_width:expr, $function:ident, $format_str:expr) => {
|
||||
fn $function(p: u64) -> String {
|
||||
format!($format_str,
|
||||
p,
|
||||
width = $print_width - 1)
|
||||
}
|
||||
|
||||
pub static $NAME: FormatterItemInfo = FormatterItemInfo {
|
||||
byte_size: $byte_size,
|
||||
print_width: $print_width,
|
||||
formatter: FormatWriter::IntWriter($function),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// defines a static struct of type `FormatterItemInfo` called `$NAME`
|
||||
///
|
||||
/// Used to format signed integer types with help of a function called `$function`
|
||||
/// `$byte_size` is the size of the type, `$print_width` is the maximum width in
|
||||
/// human-readable format. `$format_str` should be DEC
|
||||
macro_rules! int_writer_signed {
|
||||
($NAME:ident, $byte_size:expr, $print_width:expr, $function:ident, $format_str:expr) => {
|
||||
fn $function(p: u64) -> String {
|
||||
let s = sign_extend(p, $byte_size);
|
||||
format!($format_str,
|
||||
s,
|
||||
width = $print_width - 1)
|
||||
}
|
||||
|
||||
pub static $NAME: FormatterItemInfo = FormatterItemInfo {
|
||||
byte_size: $byte_size,
|
||||
print_width: $print_width,
|
||||
formatter: FormatWriter::IntWriter($function),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Extends a signed number in `item` of `itembytes` bytes into a (signed) i64
|
||||
fn sign_extend(item: u64, itembytes: usize) -> i64{
|
||||
let shift = 64 - itembytes * 8;
|
||||
(item << shift) as i64 >> shift
|
||||
}
|
||||
|
||||
|
||||
int_writer_unsigned!(FORMAT_ITEM_OCT8, 1, 4, format_item_oct8, OCT!()); // max: 377
|
||||
int_writer_unsigned!(FORMAT_ITEM_OCT16, 2, 7, format_item_oct16, OCT!()); // max: 177777
|
||||
int_writer_unsigned!(FORMAT_ITEM_OCT32, 4, 12, format_item_oct32, OCT!()); // max: 37777777777
|
||||
int_writer_unsigned!(FORMAT_ITEM_OCT64, 8, 23, format_item_oct64, OCT!()); // max: 1777777777777777777777
|
||||
|
||||
int_writer_unsigned!(FORMAT_ITEM_HEX8, 1, 3, format_item_hex8, HEX!()); // max: ff
|
||||
int_writer_unsigned!(FORMAT_ITEM_HEX16, 2, 5, format_item_hex16, HEX!()); // max: ffff
|
||||
int_writer_unsigned!(FORMAT_ITEM_HEX32, 4, 9, format_item_hex32, HEX!()); // max: ffffffff
|
||||
int_writer_unsigned!(FORMAT_ITEM_HEX64, 8, 17, format_item_hex64, HEX!()); // max: ffffffffffffffff
|
||||
|
||||
int_writer_unsigned!(FORMAT_ITEM_DEC8U, 1, 4, format_item_dec_u8, DEC!()); // max: 255
|
||||
int_writer_unsigned!(FORMAT_ITEM_DEC16U, 2, 6, format_item_dec_u16, DEC!()); // max: 65535
|
||||
int_writer_unsigned!(FORMAT_ITEM_DEC32U, 4, 11, format_item_dec_u32, DEC!()); // max: 4294967295
|
||||
int_writer_unsigned!(FORMAT_ITEM_DEC64U, 8, 21, format_item_dec_u64, DEC!()); // max: 18446744073709551615
|
||||
|
||||
int_writer_signed!(FORMAT_ITEM_DEC8S, 1, 5, format_item_dec_s8, DEC!()); // max: -128
|
||||
int_writer_signed!(FORMAT_ITEM_DEC16S, 2, 7, format_item_dec_s16, DEC!()); // max: -32768
|
||||
int_writer_signed!(FORMAT_ITEM_DEC32S, 4, 12, format_item_dec_s32, DEC!()); // max: -2147483648
|
||||
int_writer_signed!(FORMAT_ITEM_DEC64S, 8, 21, format_item_dec_s64, DEC!()); // max: -9223372036854775808
|
||||
|
||||
#[test]
|
||||
fn test_sign_extend() {
|
||||
assert_eq!(0xffffffffffffff80u64 as i64, sign_extend(0x0000000000000080, 1));
|
||||
assert_eq!(0xffffffffffff8000u64 as i64, sign_extend(0x0000000000008000, 2));
|
||||
assert_eq!(0xffffffffff800000u64 as i64, sign_extend(0x0000000000800000, 3));
|
||||
assert_eq!(0xffffffff80000000u64 as i64, sign_extend(0x0000000080000000, 4));
|
||||
assert_eq!(0xffffff8000000000u64 as i64, sign_extend(0x0000008000000000, 5));
|
||||
assert_eq!(0xffff800000000000u64 as i64, sign_extend(0x0000800000000000, 6));
|
||||
assert_eq!(0xff80000000000000u64 as i64, sign_extend(0x0080000000000000, 7));
|
||||
assert_eq!(0x8000000000000000u64 as i64, sign_extend(0x8000000000000000, 8));
|
||||
|
||||
assert_eq!(0x000000000000007f, sign_extend(0x000000000000007f, 1));
|
||||
assert_eq!(0x0000000000007fff, sign_extend(0x0000000000007fff, 2));
|
||||
assert_eq!(0x00000000007fffff, sign_extend(0x00000000007fffff, 3));
|
||||
assert_eq!(0x000000007fffffff, sign_extend(0x000000007fffffff, 4));
|
||||
assert_eq!(0x0000007fffffffff, sign_extend(0x0000007fffffffff, 5));
|
||||
assert_eq!(0x00007fffffffffff, sign_extend(0x00007fffffffffff, 6));
|
||||
assert_eq!(0x007fffffffffffff, sign_extend(0x007fffffffffffff, 7));
|
||||
assert_eq!(0x7fffffffffffffff, sign_extend(0x7fffffffffffffff, 8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_item_oct() {
|
||||
assert_eq!(" 000", format_item_oct8(0));
|
||||
assert_eq!(" 377", format_item_oct8(0xff));
|
||||
assert_eq!(" 000000", format_item_oct16(0));
|
||||
assert_eq!(" 177777", format_item_oct16(0xffff));
|
||||
assert_eq!(" 00000000000", format_item_oct32(0));
|
||||
assert_eq!(" 37777777777", format_item_oct32(0xffffffff));
|
||||
assert_eq!(" 0000000000000000000000", format_item_oct64(0));
|
||||
assert_eq!(" 1777777777777777777777", format_item_oct64(0xffffffffffffffff));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_item_hex() {
|
||||
assert_eq!(" 00", format_item_hex8(0));
|
||||
assert_eq!(" ff", format_item_hex8(0xff));
|
||||
assert_eq!(" 0000", format_item_hex16(0));
|
||||
assert_eq!(" ffff", format_item_hex16(0xffff));
|
||||
assert_eq!(" 00000000", format_item_hex32(0));
|
||||
assert_eq!(" ffffffff", format_item_hex32(0xffffffff));
|
||||
assert_eq!(" 0000000000000000", format_item_hex64(0));
|
||||
assert_eq!(" ffffffffffffffff", format_item_hex64(0xffffffffffffffff));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_item_dec_u() {
|
||||
assert_eq!(" 0", format_item_dec_u8(0));
|
||||
assert_eq!(" 255", format_item_dec_u8(0xff));
|
||||
assert_eq!(" 0", format_item_dec_u16(0));
|
||||
assert_eq!(" 65535", format_item_dec_u16(0xffff));
|
||||
assert_eq!(" 0", format_item_dec_u32(0));
|
||||
assert_eq!(" 4294967295", format_item_dec_u32(0xffffffff));
|
||||
assert_eq!(" 0", format_item_dec_u64(0));
|
||||
assert_eq!(" 18446744073709551615", format_item_dec_u64(0xffffffffffffffff));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_item_dec_s() {
|
||||
assert_eq!(" 0", format_item_dec_s8(0));
|
||||
assert_eq!(" 127", format_item_dec_s8(0x7f));
|
||||
assert_eq!(" -128", format_item_dec_s8(0x80));
|
||||
assert_eq!(" 0", format_item_dec_s16(0));
|
||||
assert_eq!(" 32767", format_item_dec_s16(0x7fff));
|
||||
assert_eq!(" -32768", format_item_dec_s16(0x8000));
|
||||
assert_eq!(" 0", format_item_dec_s32(0));
|
||||
assert_eq!(" 2147483647", format_item_dec_s32(0x7fffffff));
|
||||
assert_eq!(" -2147483648", format_item_dec_s32(0x80000000));
|
||||
assert_eq!(" 0", format_item_dec_s64(0));
|
||||
assert_eq!(" 9223372036854775807", format_item_dec_s64(0x7fffffffffffffff));
|
||||
assert_eq!(" -9223372036854775808", format_item_dec_s64(0x8000000000000000));
|
||||
}
|
1
tests/fixtures/od/-f
vendored
Normal file
1
tests/fixtures/od/-f
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
minus lowercase f
|
1
tests/fixtures/od/0
vendored
Normal file
1
tests/fixtures/od/0
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
zero
|
1
tests/fixtures/od/c
vendored
Normal file
1
tests/fixtures/od/c
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
lowercase c
|
1
tests/fixtures/od/x
vendored
Normal file
1
tests/fixtures/od/x
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
lowercase x
|
559
tests/test_od.rs
559
tests/test_od.rs
|
@ -1,13 +1,20 @@
|
|||
extern crate unindent;
|
||||
|
||||
use common::util::*;
|
||||
use std::path::Path;
|
||||
use std::env;
|
||||
use std::io::Write;
|
||||
use std::fs::File;
|
||||
use std::fs::remove_file;
|
||||
use self::unindent::*;
|
||||
|
||||
|
||||
// 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";
|
||||
static ALPHA_OUT: &'static str = "
|
||||
0000000 061141 062143 063145 064147 065151 066153 067155 070157
|
||||
0000020 071161 072163 073165 074167 075171 000012
|
||||
0000033
|
||||
";
|
||||
|
||||
// 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.
|
||||
|
@ -28,11 +35,11 @@ fn test_file() {
|
|||
}
|
||||
}
|
||||
|
||||
let result = new_ucmd!().arg(file.as_os_str()).run();
|
||||
let result = new_ucmd!().arg("--endian=little").arg(file.as_os_str()).run();
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, ALPHA_OUT);
|
||||
assert_eq!(result.stdout, unindent(ALPHA_OUT));
|
||||
|
||||
let _ = remove_file(file);
|
||||
}
|
||||
|
@ -57,11 +64,11 @@ fn test_2files() {
|
|||
}
|
||||
}
|
||||
|
||||
let result = new_ucmd!().arg(file1.as_os_str()).arg(file2.as_os_str()).run();
|
||||
let result = new_ucmd!().arg("--endian=little").arg(file1.as_os_str()).arg(file2.as_os_str()).run();
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, ALPHA_OUT);
|
||||
assert_eq!(result.stdout, unindent(ALPHA_OUT));
|
||||
|
||||
let _ = remove_file(file1);
|
||||
let _ = remove_file(file2);
|
||||
|
@ -82,20 +89,17 @@ fn test_no_file() {
|
|||
// Test that od reads from stdin instead of a file
|
||||
#[test]
|
||||
fn test_from_stdin() {
|
||||
|
||||
let input = "abcdefghijklmnopqrstuvwxyz\n";
|
||||
let result = new_ucmd!().run_piped_stdin(input.as_bytes());
|
||||
let result = new_ucmd!().arg("--endian=little").run_piped_stdin(input.as_bytes());
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, ALPHA_OUT);
|
||||
|
||||
assert_eq!(result.stdout, unindent(ALPHA_OUT));
|
||||
}
|
||||
|
||||
// Test that od reads from stdin and also from files
|
||||
#[test]
|
||||
fn test_from_mixed() {
|
||||
|
||||
let temp = env::temp_dir();
|
||||
let tmpdir = Path::new(&temp);
|
||||
let file1 = tmpdir.join("test-1");
|
||||
|
@ -110,30 +114,31 @@ fn test_from_mixed() {
|
|||
}
|
||||
}
|
||||
|
||||
let result = new_ucmd!().arg(file1.as_os_str()).arg("--").arg(file3.as_os_str()).run_piped_stdin(data2.as_bytes());
|
||||
let result = new_ucmd!().arg("--endian=little").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);
|
||||
|
||||
assert_eq!(result.stdout, unindent(ALPHA_OUT));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_formats() {
|
||||
|
||||
let input = "abcdefghijklmnopqrstuvwxyz\n";
|
||||
let result = new_ucmd!().arg("-c").arg("-b").run_piped_stdin(input.as_bytes());
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, "0000000 a b c d e f g h i j k l m n o p\n 141 142 143 144 145 146 147 150 151 152 153 154 155 156 157 160\n0000020 q r s t u v w x y z \\n \n 161 162 163 164 165 166 167 170 171 172 012 \n0000033\n");
|
||||
|
||||
assert_eq!(result.stdout, unindent("
|
||||
0000000 a b c d e f g h i j k l m n o p
|
||||
141 142 143 144 145 146 147 150 151 152 153 154 155 156 157 160
|
||||
0000020 q r s t u v w x y z \\n
|
||||
161 162 163 164 165 166 167 170 171 172 012
|
||||
0000033
|
||||
"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dec() {
|
||||
|
||||
|
||||
let input = [
|
||||
0u8, 0u8,
|
||||
1u8, 0u8,
|
||||
|
@ -142,25 +147,519 @@ fn test_dec() {
|
|||
0xffu8,0x7fu8,
|
||||
0x00u8,0x80u8,
|
||||
0x01u8,0x80u8,];
|
||||
let expected_output = "0000000 0 1 2 3 32767 -32768 -32767 \n0000016\n";
|
||||
let result = new_ucmd!().arg("-i").run_piped_stdin(&input[..]);
|
||||
let expected_output = unindent("
|
||||
0000000 0 1 2 3 32767 -32768 -32767
|
||||
0000016
|
||||
");
|
||||
let result = new_ucmd!().arg("--endian=little").arg("-s").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// We don't support multibyte chars, so big NEIN to this
|
||||
/*
|
||||
#[test]
|
||||
fn mit_die_umlauten_getesten() {
|
||||
let result = new_ucmd!()
|
||||
.run_piped_stdin("Universität Tübingen".as_bytes());
|
||||
fn test_hex16(){
|
||||
let input: [u8; 9] = [
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xff];
|
||||
let expected_output = unindent("
|
||||
0000000 2301 6745 ab89 efcd 00ff
|
||||
0000011
|
||||
");
|
||||
let result = new_ucmd!().arg("--endian=little").arg("-x").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout,
|
||||
"0000000 U n i v e r s i t ä ** t T ü **\n0000020 b i n g e n\n0000026")
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hex32(){
|
||||
let input: [u8; 9] = [
|
||||
0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0xff];
|
||||
let expected_output = unindent("
|
||||
0000000 67452301 efcdab89 000000ff
|
||||
0000011
|
||||
");
|
||||
let result = new_ucmd!().arg("--endian=little").arg("-X").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_f16(){
|
||||
let input: [u8; 14] = [
|
||||
0x00, 0x3c, // 0x3C00 1.0
|
||||
0x00, 0x00, // 0x0000 0.0
|
||||
0x00, 0x80, // 0x8000 -0.0
|
||||
0x00, 0x7c, // 0x7C00 Inf
|
||||
0x00, 0xfc, // 0xFC00 -Inf
|
||||
0x00, 0xfe, // 0xFE00 NaN
|
||||
0x00, 0x84];// 0x8400 -6.104e-5
|
||||
let expected_output = unindent("
|
||||
0000000 1.000 0 -0 inf
|
||||
0000010 -inf NaN -6.104e-5
|
||||
0000016
|
||||
");
|
||||
let result = new_ucmd!().arg("--endian=little").arg("-tf2").arg("-w8").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_f32(){
|
||||
let input: [u8; 28] = [
|
||||
0x52, 0x06, 0x9e, 0xbf, // 0xbf9e0652 -1.2345679
|
||||
0x4e, 0x61, 0x3c, 0x4b, // 0x4b3c614e 12345678
|
||||
0x0f, 0x9b, 0x94, 0xfe, // 0xfe949b0f -9.876543E37
|
||||
0x00, 0x00, 0x00, 0x80, // 0x80000000 -0.0
|
||||
0xff, 0xff, 0xff, 0x7f, // 0x7fffffff NaN
|
||||
0xc2, 0x16, 0x01, 0x00, // 0x000116c2 1e-40
|
||||
0x00, 0x00, 0x7f, 0x80];// 0x807f0000 -1.1663108E-38
|
||||
let expected_output = unindent("
|
||||
0000000 -1.2345679 12345678 -9.8765427e37 -0
|
||||
0000020 NaN 1e-40 -1.1663108e-38
|
||||
0000034
|
||||
");
|
||||
let result = new_ucmd!().arg("--endian=little").arg("-f").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_f64(){
|
||||
let input: [u8; 40] = [
|
||||
0x27, 0x6b, 0x0a, 0x2f, 0x2a, 0xee, 0x45, 0x43, // 0x4345EE2A2F0A6B27 12345678912345678
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x0000000000000000 0
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x80, // 0x8010000000000000 -2.2250738585072014e-308
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x0000000000000001 5e-324 (subnormal)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0];// 0xc000000000000000 -2
|
||||
let expected_output = unindent("
|
||||
0000000 12345678912345678 0
|
||||
0000020 -2.2250738585072014e-308 5e-324
|
||||
0000040 -2.0000000000000000
|
||||
0000050
|
||||
");
|
||||
let result = new_ucmd!().arg("--endian=little").arg("-F").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multibyte() {
|
||||
let result = new_ucmd!().arg("-c").arg("-w12").run_piped_stdin("Universität Tübingen \u{1B000}".as_bytes());
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, unindent("
|
||||
0000000 U n i v e r s i t ä ** t
|
||||
0000014 T ü ** b i n g e n \u{1B000}
|
||||
0000030 ** ** **
|
||||
0000033
|
||||
"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_width(){
|
||||
let input: [u8; 8] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
|
||||
let expected_output = unindent("
|
||||
0000000 000000 000000
|
||||
0000004 000000 000000
|
||||
0000010
|
||||
");
|
||||
|
||||
let result = new_ucmd!().arg("-w4").arg("-v").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_width(){
|
||||
let input: [u8; 4] = [0x00, 0x00, 0x00, 0x00];
|
||||
let expected_output = unindent("
|
||||
0000000 000000
|
||||
0000002 000000
|
||||
0000004
|
||||
");
|
||||
|
||||
let result = new_ucmd!().arg("-w5").arg("-v").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_eq!(result.stderr, "od: warning: invalid width 5; using 2 instead\n");
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_width(){
|
||||
let input: [u8; 4] = [0x00, 0x00, 0x00, 0x00];
|
||||
let expected_output = unindent("
|
||||
0000000 000000
|
||||
0000002 000000
|
||||
0000004
|
||||
");
|
||||
|
||||
let result = new_ucmd!().arg("-w0").arg("-v").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_eq!(result.stderr, "od: warning: invalid width 0; using 2 instead\n");
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_width_without_value(){
|
||||
let input: [u8; 40] = [0 ; 40];
|
||||
let expected_output = unindent("
|
||||
0000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000 000000
|
||||
0000040 000000 000000 000000 000000
|
||||
0000050
|
||||
");
|
||||
|
||||
let result = new_ucmd!().arg("-w").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_suppress_duplicates(){
|
||||
let input: [u8; 41] = [
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
1, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0, 0, 0, 0,
|
||||
0];
|
||||
let expected_output = unindent("
|
||||
0000000 00000000000
|
||||
0000 0000
|
||||
*
|
||||
0000020 00000000001
|
||||
0001 0000
|
||||
0000024 00000000000
|
||||
0000 0000
|
||||
*
|
||||
0000050 00000000000
|
||||
0000
|
||||
0000051
|
||||
");
|
||||
|
||||
let result = new_ucmd!().arg("-w4").arg("-O").arg("-x").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_big_endian() {
|
||||
let input: [u8; 8] = [
|
||||
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];// 0xc000000000000000 -2
|
||||
|
||||
let expected_output = unindent("
|
||||
0000000 -2.0000000000000000
|
||||
-2.0000000 0
|
||||
c0000000 00000000
|
||||
c000 0000 0000 0000
|
||||
0000010
|
||||
");
|
||||
|
||||
let result = new_ucmd!().arg("--endian=big").arg("-F").arg("-f").arg("-X").arg("-x").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_alignment_Xxa() {
|
||||
let input: [u8; 8] = [
|
||||
0x0A, 0x0D, 0x65, 0x66, 0x67, 0x00, 0x9e, 0x9f];
|
||||
|
||||
let expected_output = unindent("
|
||||
0000000 66650d0a 9f9e0067
|
||||
0d0a 6665 0067 9f9e
|
||||
nl cr e f g nul rs us
|
||||
0000010
|
||||
");
|
||||
|
||||
// in this case the width of the -a (8-bit) determines the alignment for the other fields
|
||||
let result = new_ucmd!().arg("--endian=little").arg("-X").arg("-x").arg("-a").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn test_alignment_Fx() {
|
||||
let input: [u8; 8] = [
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0];// 0xc000000000000000 -2
|
||||
|
||||
let expected_output = unindent("
|
||||
0000000 -2.0000000000000000
|
||||
0000 0000 0000 c000
|
||||
0000010
|
||||
");
|
||||
|
||||
// in this case the width of the -F (64-bit) determines the alignment for the other field
|
||||
let result = new_ucmd!().arg("--endian=little").arg("-F").arg("-x").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_maxuint(){
|
||||
let input = [0xFFu8 ; 8];
|
||||
let expected_output = unindent("
|
||||
0000000 1777777777777777777777
|
||||
37777777777 37777777777
|
||||
177777 177777 177777 177777
|
||||
377 377 377 377 377 377 377 377
|
||||
18446744073709551615
|
||||
4294967295 4294967295
|
||||
65535 65535 65535 65535
|
||||
255 255 255 255 255 255 255 255
|
||||
0000010
|
||||
");
|
||||
|
||||
let result = new_ucmd!().arg("--format=o8").arg("-Oobtu8").arg("-Dd").arg("--format=u1").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hex_offset(){
|
||||
let input = [0u8 ; 0x1F];
|
||||
let expected_output = unindent("
|
||||
000000 00000000 00000000 00000000 00000000
|
||||
00000000 00000000 00000000 00000000
|
||||
000010 00000000 00000000 00000000 00000000
|
||||
00000000 00000000 00000000 00000000
|
||||
00001F
|
||||
");
|
||||
|
||||
let result = new_ucmd!().arg("-Ax").arg("-X").arg("-X").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dec_offset(){
|
||||
let input = [0u8 ; 19];
|
||||
let expected_output = unindent("
|
||||
0000000 00000000 00000000 00000000 00000000
|
||||
00000000 00000000 00000000 00000000
|
||||
0000016 00000000
|
||||
00000000
|
||||
0000019
|
||||
");
|
||||
|
||||
let result = new_ucmd!().arg("-Ad").arg("-X").arg("-X").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_offset(){
|
||||
let input = [0u8 ; 31];
|
||||
const LINE: &'static str = " 00000000 00000000 00000000 00000000\n";
|
||||
let expected_output = [LINE, LINE, LINE, LINE].join("");
|
||||
|
||||
let result = new_ucmd!().arg("-An").arg("-X").arg("-X").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_offset(){
|
||||
let result = new_ucmd!().arg("-Ab").run();
|
||||
|
||||
assert!(!result.success);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_skip_bytes(){
|
||||
let input = "abcdefghijklmnopq";
|
||||
let result = new_ucmd!().arg("-c").arg("--skip-bytes=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_skip_bytes_error(){
|
||||
let input = "12345";
|
||||
let result = new_ucmd!().arg("--skip-bytes=10").run_piped_stdin(input.as_bytes());
|
||||
|
||||
assert!(!result.success);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_bytes(){
|
||||
let input = "abcdefghijklmnopqrstuvwxyz\n12345678";
|
||||
let result = new_ucmd!().arg("--endian=little").arg("--read-bytes=27").run_piped_stdin(input.as_bytes());
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, unindent(ALPHA_OUT));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ascii_dump(){
|
||||
let input: [u8; 22] = [
|
||||
0x00, 0x01, 0x0a, 0x0d, 0x10, 0x1f, 0x20, 0x61, 0x62, 0x63, 0x7d,
|
||||
0x7e, 0x7f, 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0, 0xff];
|
||||
let result = new_ucmd!().arg("-tx1zacz").run_piped_stdin(&input[..]);
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, unindent(r"
|
||||
0000000 00 01 0a 0d 10 1f 20 61 62 63 7d 7e 7f 80 90 a0 >...... abc}~....<
|
||||
nul soh nl cr dle us sp a b c } ~ del nul dle sp
|
||||
\0 001 \n \r 020 037 a b c } ~ 177 ** ** ** >...... abc}~....<
|
||||
0000020 b0 c0 d0 e0 f0 ff >......<
|
||||
0 @ P ` p del
|
||||
** 300 320 340 360 377 >......<
|
||||
0000026
|
||||
"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filename_parsing(){
|
||||
// files "a" and "x" both exists, but are no filenames in the commandline below
|
||||
// "-f" must be treated as a filename, it contains the text: minus lowercase f
|
||||
// so "-f" should not be interpreted as a formatting option.
|
||||
let result = new_ucmd!().arg("--format").arg("a").arg("-A").arg("x").arg("--").arg("-f").run();
|
||||
|
||||
assert_empty_stderr!(result);
|
||||
assert!(result.success);
|
||||
assert_eq!(result.stdout, unindent("
|
||||
000000 m i n u s sp l o w e r c a s e sp
|
||||
000010 f nl
|
||||
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
|
||||
"));
|
||||
}
|
||||
|
||||
#[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 result = new_ucmd!().arg("--traditional").arg("0").arg("0").arg("0").arg("0").run();
|
||||
|
||||
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…
Add table
Add a link
Reference in a new issue