use std::ops::Range;
use std;
use std::io;
use atty;
use termcolor::{BufferWriter, Buffer, Color, ColorChoice, WriteColor};
use color::{Spec, Colors, ColorRange, ColorlessString};
use byte_mapping;
pub struct HexView<'a> {
address_offset: usize,
codepage: &'a [char],
data: &'a [u8],
replacement_character: char,
row_width: usize,
colors: Colors,
force_color: bool,
}
macro_rules! color {
($fmt:ident, $color:ident, $str:expr) => ({
$fmt.set_color(&$color)?;
write!($fmt, "{}", $str)?;
$fmt.reset()
})
}
impl<'a> HexView<'a> {
pub fn print(&self) -> io::Result<()> {
let cc = if self.force_color || atty::is(atty::Stream::Stdout) { ColorChoice::Auto } else { ColorChoice::Never };
let writer = BufferWriter::stdout(cc);
let mut buffer: Buffer = writer.buffer();
self.fmt(&mut buffer)?;
writer.print(&buffer)?;
Ok(())
}
pub fn new(data: &[u8]) -> HexView {
HexView {
address_offset: 0,
codepage: &byte_mapping::CODEPAGE_0850,
data: data,
replacement_character: '.',
row_width: 16,
colors: Colors::new(),
force_color: false,
}
}
pub fn fmt<W: WriteColor>(&self, buffer: &mut W) -> io::Result<()> {
let begin_padding = calculate_begin_padding(self.address_offset, self.row_width);
let end_padding = calculate_end_padding(begin_padding + self.data.len(), self.row_width);
let mut address = self.address_offset - begin_padding;
let mut offset = 0;
let mut color_range = ColorRange::new(&self.colors);
let mut separator = "";
if self.data.len() + begin_padding + end_padding <= self.row_width {
fmt_line(buffer, address, &self.codepage, self.replacement_character, &self.data, &mut color_range, &Padding::new(begin_padding, end_padding))?;
return Ok(())
}
if begin_padding != 0 {
let slice = &self.data[offset..offset + self.row_width - begin_padding];
fmt_line(buffer, address, &self.codepage, self.replacement_character, &slice, &mut color_range, &Padding::from_left(begin_padding))?;
offset += self.row_width - begin_padding;
address += self.row_width;
separator = "\n";
color_range.update_offset(offset);
}
while offset + (self.row_width - 1) < self.data.len() {
let slice = &self.data[offset..offset + self.row_width];
write!(buffer, "{}", separator)?;
fmt_line(buffer, address, &self.codepage, self.replacement_character, &slice, &mut color_range, &Padding::default())?;
offset += self.row_width;
address += self.row_width;
separator = "\n";
color_range.update_offset(offset);
}
if end_padding != 0 {
let slice = &self.data[offset..];
writeln!(buffer, "")?;
fmt_line(buffer, address, &self.codepage, self.replacement_character, &slice, &mut color_range, &Padding::from_right(end_padding))?;
}
Ok(())
}
}
pub struct HexViewBuilder<'a> {
hex_view: HexView<'a>,
}
impl<'a> HexViewBuilder<'a> {
pub fn new(data: &[u8]) -> HexViewBuilder {
HexViewBuilder {
hex_view: HexView::new(&data)
}
}
pub fn address_offset(mut self, offset: usize) -> HexViewBuilder<'a> {
self.hex_view.address_offset = offset;
self
}
pub fn force_color(mut self) -> Self {
self.hex_view.force_color = true;
self
}
pub fn codepage<'b: 'a>(mut self, codepage: &'b [char]) -> HexViewBuilder<'a> {
self.hex_view.codepage = codepage;
self
}
pub fn replacement_character(mut self, ch: char) -> HexViewBuilder<'a> {
self.hex_view.replacement_character = ch;
self
}
pub fn row_width(mut self, width: usize) -> HexViewBuilder<'a> {
self.hex_view.row_width = width;
self
}
pub fn add_colors(mut self, colors: Colors) -> HexViewBuilder<'a> {
self.hex_view.colors.extend(colors);
self
}
pub fn add_color(mut self, color: &str, range: Range<usize>) -> HexViewBuilder<'a> {
use std::str::FromStr;
self.hex_view.colors.push((Spec::new().set_fg(Some(Color::from_str(color).unwrap())).clone(), range));
self
}
pub fn finish(mut self) -> HexView<'a> {
self.hex_view.colors.sort_by(|&(_, ref r1), &(_, ref r2)| r1.start.cmp(&r2.start));
self.hex_view
}
}
#[derive(Default)]
struct Padding {
left: usize,
right: usize,
}
impl Padding {
fn new(left_padding: usize, right_padding: usize) -> Padding {
Padding {
left: left_padding,
right: right_padding,
}
}
fn from_left(left_padding: usize) -> Padding {
Padding {
left: left_padding,
right: 0,
}
}
fn from_right(right_padding: usize) -> Padding {
Padding {
left: 0,
right: right_padding,
}
}
}
fn fmt_bytes_as_hex<W: WriteColor>(f: &mut W, bytes: &[u8], color_range: &ColorRange, padding: &Padding) -> io::Result<()> {
let mut separator = "";
for _ in 0..padding.left {
write!(f, "{} ", separator)?;
separator = " ";
}
for (i, byte) in bytes.iter().enumerate() {
match color_range.get(i) {
Some(rgb) => {
write!(f, "{}", separator)?;
color!(f, rgb, format!("{:02X}", byte))?;
},
None => write!(f, "{}{:02X}", separator, byte)?,
}
separator = " ";
}
for _ in 0..padding.right {
write!(f, "{} ", separator)?;
separator = " ";
}
Ok(())
}
fn fmt_bytes_as_char<W: WriteColor>(f: &mut W, cp: &[char], repl_char: char, bytes: &[u8], color_range: &ColorRange, padding: &Padding) -> io::Result<()> {
for _ in 0..padding.left {
write!(f, " ")?;
}
for (i, &byte) in bytes.iter().enumerate() {
let byte = byte_mapping::as_char(byte, cp, repl_char);
match color_range.get(i) {
Some(rgb) => {
color!(f, rgb, format!("{}", byte))?;
},
_ => write!(f, "{}", byte)?,
}
}
for _ in 0..padding.right {
write!(f, " ")?;
}
Ok(())
}
fn fmt_line<W: WriteColor>(f: &mut W, address: usize, cp: &[char], repl_char: char, bytes: &[u8], color_range: &mut ColorRange, padding: &Padding) -> io::Result<()> {
write!(f, "{:0width$X}", address, width = 8)?;
write!(f, " ")?;
fmt_bytes_as_hex(f, bytes, &color_range, &padding)?;
write!(f, " ")?;
write!(f, "| ")?;
fmt_bytes_as_char(f, cp, repl_char, bytes, &color_range, &padding)?;
write!(f, " |")?;
Ok(())
}
fn calculate_begin_padding(address_offset: usize, row_width: usize) -> usize {
debug_assert!(row_width != 0, "A zero row width is can not be used to calculate the begin padding");
address_offset % row_width
}
fn calculate_end_padding(data_size: usize, row_width: usize) -> usize {
debug_assert!(row_width != 0, "A zero row width is can not be used to calculate the end padding");
(row_width - data_size % row_width) % row_width
}
impl<'a> std::fmt::Display for HexView<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
if self.row_width == 0 {
write!(f, "Invalid HexView::width")?;
return Err(std::fmt::Error);
}
let mut string = ColorlessString(String::new());
match self.fmt(&mut string) {
Ok(()) => {
write!(f, "{}", string.0)
},
Err(e) => write!(f, "{}", e)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std;
#[test]
fn test_begin_padding() {
assert_eq!(super::calculate_begin_padding(0, 16), 0);
assert_eq!(super::calculate_begin_padding(16, 16), 0);
assert_eq!(super::calculate_begin_padding(54, 16), 6);
}
#[test]
fn a_full_line_is_formatted_as_expected() {
let data: Vec<u8> = (0x40..0x40 + 0xF + 1).collect();
let row_view = HexViewBuilder::new(&data)
.row_width(data.len())
.finish();
let result = format!("{}", row_view);
assert_eq!(result, "00000000 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F | @ABCDEFGHIJKLMNO |");
}
#[test]
fn an_incomplete_line_is_padded_on_the_right() {
let data = ['a' as u8; 10];
let row_view = HexViewBuilder::new(&data)
.row_width(16)
.finish();
let result = format!("{}", row_view);
assert_eq!(result, "00000000 61 61 61 61 61 61 61 61 61 61 | aaaaaaaaaa |");
}
#[test]
fn an_unaligned_address_causes_padded_on_the_left() {
let data = ['a' as u8; 11];
let row_view = HexViewBuilder::new(&data)
.address_offset(5)
.row_width(16)
.finish();
let result = format!("{}", row_view);
println!("{}", result);
assert_eq!(result, "00000000 61 61 61 61 61 61 61 61 61 61 61 | aaaaaaaaaaa |");
}
#[test]
fn an_unaligned_incomplete_line_causes_padding_on_both_sides() {
let data = ['a' as u8; 8];
let row_view = HexViewBuilder::new(&data)
.address_offset(5)
.row_width(16)
.finish();
let result = format!("{}", row_view);
println!("{}", result);
assert_eq!(result, "00000000 61 61 61 61 61 61 61 61 | aaaaaaaa |");
}
#[test]
fn decreasing_the_row_width_increases_the_total_character_count() {
let data: Vec<u8> = (0..64).collect();
let short_row_view = HexViewBuilder::new(&data).row_width(1).finish();
let long_row_view = HexViewBuilder::new(&data).row_width(16).finish();
let short_row_result = format!("{}", short_row_view);
let long_row_result = format!("{}", long_row_view);
assert!(long_row_result.len() < short_row_result.len());
}
#[test]
fn the_address_offset_is_zero_by_default() {
let data = [99; 16];
let row_view = HexViewBuilder::new(&data)
.row_width(16)
.finish();
let result = format!("{}", row_view);
let address_offset_str = format!("{:X}", 0);
assert!(result.contains(&address_offset_str));
}
#[test]
fn the_address_offset_is_used_when_given() {
let data = [0; 16];
let address_offset = data.len() * 10;
let row_view = HexViewBuilder::new(&data)
.row_width(16)
.address_offset(address_offset)
.finish();
let result = format!("{}", row_view);
let address_offset_str = format!("{:X}", address_offset);
assert!(result.contains(&address_offset_str));
}
#[test]
fn the_address_offset_increases_by_the_row_width_for_each_row() {
let data = [0; 16 * 5];
let address_offset = data.len() * 10;
let row_view = HexViewBuilder::new(&data)
.row_width(16)
.address_offset(address_offset)
.finish();
let result = format!("{}", row_view);
let row_2_address_offset_str = format!("{:X}", address_offset + 2 * row_view.row_width);
let row_4_address_offset_str = format!("{:X}", address_offset + 4 * row_view.row_width);
assert!(result.contains(&row_2_address_offset_str));
assert!(result.contains(&row_4_address_offset_str));
}
#[test]
fn there_is_no_superfluous_whitespace() {
let data = [0; 17];
let one_line_result = format!("{}", HexViewBuilder::new(&data[0..16]).finish());
let two_line_result = format!("{}", HexViewBuilder::new(&data[0..17]).finish());
println!("{}", one_line_result);
println!("{}", two_line_result);
assert_eq!(one_line_result, one_line_result.trim());
assert_eq!(two_line_result, two_line_result.trim());
}
#[test]
fn the_row_width_is_16_by_default() {
let data = [0; 17];
let one_line_result = format!("{}", HexViewBuilder::new(&data[0..16]).finish());
let two_line_result = format!("{}", HexViewBuilder::new(&data[0..17]).finish());
println!("{}", one_line_result);
println!("{}", two_line_result);
assert_eq!(1, one_line_result.lines().count());
assert_eq!(2, two_line_result.lines().count());
}
#[test]
fn the_replacement_character_is_dot_by_default() {
let data = [0; 1];
let empty_cp = [];
let result = format!("{}", HexViewBuilder::new(&data)
.codepage(&empty_cp)
.finish());
println!("{}", result);
assert!(result.contains('.'));
}
#[test]
fn the_replacement_character_can_be_changed() {
let data = [0; 1];
let empty_cp = [];
let result = format!("{}", HexViewBuilder::new(&data)
.codepage(&empty_cp)
.replacement_character(std::char::REPLACEMENT_CHARACTER)
.finish());
println!("{}", result);
assert!(result.contains(std::char::REPLACEMENT_CHARACTER));
}
#[test]
fn all_characters_can_be_printed() {
let data: Vec<u8> = (0u16..256u16).map(|v| v as u8).collect();
let dump_view = HexViewBuilder::new(&data)
.address_offset(20)
.row_width(8)
.finish();
let result = format!("{}", dump_view);
println!("{}", result);
assert!(!result.is_empty());
}
}