mirror of
https://github.com/uutils/coreutils.git
synced 2026-06-10 15:48:22 -07:00
Merge pull request #965 from wimh/od
od: implement remaining functionality
This commit is contained in:
@@ -1,2 +1,4 @@
|
||||
CONFIG_FEATURE_FANCY_HEAD=y
|
||||
CONFIG_UNICODE_SUPPORT=y
|
||||
CONFIG_DESKTOP=y
|
||||
CONFIG_LONG_OPTS=y
|
||||
|
||||
Generated
+19
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
+366
-411
File diff suppressed because it is too large
Load Diff
@@ -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));
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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]));
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user