Merge pull request #965 from wimh/od

od: implement remaining functionality
This commit is contained in:
Alex Lyon
2016-11-10 15:13:10 -08:00
committed by GitHub
26 changed files with 3809 additions and 442 deletions
+2
View File
@@ -1,2 +1,4 @@
CONFIG_FEATURE_FANCY_HEAD=y
CONFIG_UNICODE_SUPPORT=y
CONFIG_DESKTOP=y
CONFIG_LONG_OPTS=y
Generated
+19
View File
@@ -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"
+1
View File
@@ -203,6 +203,7 @@ libc = "*"
regex="*"
rand="*"
tempdir="*"
unindent="*"
[[bin]]
name = "uutils"
+1 -1
View File
@@ -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
+3
View File
@@ -10,6 +10,9 @@ path = "od.rs"
[dependencies]
getopts = "*"
libc = "*"
byteorder = "*"
half = "*"
uucore = { path="../uucore" }
[[bin]]
name = "od"
+50
View 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
View 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
View 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
View 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
View 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
View 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
}
}
+366 -411
View File
File diff suppressed because it is too large Load Diff
+241
View 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));
}
File diff suppressed because it is too large Load Diff
+362
View 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
View 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
View 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
View 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
View 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
View 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");
}

Some files were not shown because too many files have changed in this diff Show More