hexplay-0.2.1/.gitignore000064400000000000000000000005221310215050400133300ustar0000000000000000############################################################################## ## Rust Cargo.lock target ############################################################################## ## Visual Studio Code /.vscode/ ############################################################################## ## Various *.bak *.bk hexplay-0.2.1/.travis.yml000064400000000000000000000001731310215050400134530ustar0000000000000000language: rust rust: - 1.13.0 - stable - beta - nightly matrix: allow_failures: - rust: nightly hexplay-0.2.1/benches/bench.rs000064400000000000000000000022541310270200500143770ustar0000000000000000#![feature(test)] extern crate hexplay; extern crate test; use test::Bencher; use hexplay::*; #[bench] fn bench_format_one_complete_line(b: &mut Bencher) { let data: Vec = (0x40..0x40 + 0xF + 1).collect(); let row_view = HexViewBuilder::new(&data) .row_width(data.len()) .finish(); b.iter(|| format!("{}", row_view)); } #[bench] fn bench_format_one_unaligned_incomplete_line(b: &mut Bencher) { let data: Vec = (0x45..0x45 + 8 + 1).collect(); let row_view = HexViewBuilder::new(&data) .address_offset(5) .row_width(16) .finish(); b.iter(|| format!("{}", row_view)); } #[bench] fn bench_format_a_medium_block_of_data(b: &mut Bencher) { let data: Vec = (0u16..256u16).map(|v| v as u8).collect(); let row_view = HexViewBuilder::new(&data) .finish(); b.iter(|| format!("{}", row_view)); } #[bench] fn bench_format_a_big_block_of_data(b: &mut Bencher) { let data: Vec = (0u16..10 * 1024u16).map(|v| (v % 256) as u8).collect(); let row_view = HexViewBuilder::new(&data) .finish(); b.iter(|| format!("{}", row_view)); } hexplay-0.2.1/Cargo.toml.orig000064400000000000000000000007561327464434600142660ustar0000000000000000[package] name = "hexplay" version = "0.2.1" authors = ["Tom Moers "] description = "Format u8 slices like an hex editor" license = "MIT" readme = "README.md" documentation = "https://docs.rs/crate/hexplay" repository = "https://github.com/tmoers/hexplay" categories = ["development-tools", "visualization"] keywords = ["display", "hex"] [badges] travis-ci = { repository = "tmoers/hexplay" } [dependencies] atty = "0.2" termcolor = "0.3" hexplay-0.2.1/Cargo.toml0000644000000017760000000000000105560ustar00# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g. crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "hexplay" version = "0.2.1" authors = ["Tom Moers "] description = "Format u8 slices like an hex editor" documentation = "https://docs.rs/crate/hexplay" readme = "README.md" keywords = ["display", "hex"] categories = ["development-tools", "visualization"] license = "MIT" repository = "https://github.com/tmoers/hexplay" [dependencies.atty] version = "0.2" [dependencies.termcolor] version = "0.3" [badges.travis-ci] repository = "tmoers/hexplay" hexplay-0.2.1/CHANGELOG.md000064400000000000000000000011711327464462500132000ustar0000000000000000# 0.2.1 (May 08, 2018) - Fix incorrect color highlighting (thanks [Erich Gubler](https://github.com/ErichDonGubler)!) # 0.2.0 (August 19, 2017) - Support coloring subranges (thanks [m4b](https://github.com/m4b)!) # 0.1.16 (May 08, 2017) - Use dot (`.`) as the fallback character - Make the replacement character configurable - Improve documentation # 0.1.15 (May 03, 2017) - Add ASCII codepage - Use rust's REPLACEMENT_CHARACTER as fallback character - Add small example # 0.1.14 (March 03, 2017) - Fix representation of 0x20 (space) in code page 850 # 0.1.13 (March 03, 2017) - Initial version hexplay-0.2.1/examples/example.rs000064400000000000000000000027371314611742600152070ustar0000000000000000extern crate hexplay; use hexplay::*; fn main() { let data: Vec = (0u16..256u16).map(|v| v as u8).collect(); let default_view = HexViewBuilder::new(&data) .finish(); println!("Default view of all data:\n{}\n\n", default_view); let ascii_view = HexViewBuilder::new(&data) .codepage(CODEPAGE_ASCII) .finish(); println!("Ascii codepage view of all data:\n{}\n\n", ascii_view); let partial_view = HexViewBuilder::new(&data[10..80]) .address_offset(10) .finish(); println!("Default view of a subslice:\n{}\n\n", partial_view); let narrowed_view = HexViewBuilder::new(&data) .row_width(10) .finish(); println!("Narrowed view of all data:\n{}\n\n", narrowed_view); let combined_view = HexViewBuilder::new(&data[10..180]) .address_offset(10) .codepage(CODEPAGE_1252) .row_width(14) .replacement_character(std::char::REPLACEMENT_CHARACTER) .finish(); println!("Custom view: \n{}\n\n", combined_view); let color_view = HexViewBuilder::new(&data) .force_color() .add_colors(vec![ (hexplay::color::red(), 42..72), (hexplay::color::yellow_bold(), 10..11), (hexplay::color::green(), 32..38), (hexplay::color::blue(), 200..226), ]) .finish(); println!("Coloured view: \n"); color_view.print().unwrap(); println!("\n\n"); } hexplay-0.2.1/LICENSE000064400000000000000000000020771310215050400123540ustar0000000000000000MIT License Copyright (c) 2017 Tom Moers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. hexplay-0.2.1/README.md000064400000000000000000000033351310215050400126240ustar0000000000000000Implements rust's Display trait to format a u8 slice as many hex editors do. This might be useful for dumping a binary blob for debugging purposes. [![Build Status][tr-img]][tr] ### Documentation The API documentation can be found here: [https://docs.rs/crate/hexplay/](https://docs.rs/crate/hexplay/). ### Example Here's an example that prints a hex view of a slice of some vector's data: ```rust extern crate hexplay; use hexplay::HexViewBuilder; fn main() { // The buffer we want to display let data : Vec = (0u8..200u8).collect(); // Build a new HexView using the provider builder let view = HexViewBuilder::new(&data[40..72]) .address_offset(40) .row_width(16) .finish(); println!("{}", view); } ``` This will result in the following output: ```text 00000020 28 29 2A 2B 2C 2D 2E 2F | ()*+,-./ | 00000030 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F | 0123456789:;<=>? | 00000040 40 41 42 43 44 45 46 47 | @ABCDEFG | ``` ### Installation `hexplay` is on [crates.io][crates], so you can include it in your project like so: ```toml [dependencies] hexplay = "*" ``` Because this crate uses the `?` operator, you need [rust v1.13.0][rust-v13] or higher. ### License Hexplay is licensed under the terms of the [MIT license][mit]. [crates]: https://crates.io/crates/hexplay [mit]: https://en.wikipedia.org/wiki/MIT_License [rust-v13]: https://github.com/rust-lang/rust/blob/master/RELEASES.md#version-1130-2016-11-10 [tr-img]: https://travis-ci.org/tmoers/hexplay.svg?branch=master [tr]: https://travis-ci.org/tmoers/hexplay hexplay-0.2.1/src/byte_mapping.rs000064400000000000000000000171651310414161400151720ustar0000000000000000use std; pub const NIL: char = std::char::REPLACEMENT_CHARACTER; /// The mapping for [ASCII](https://en.wikipedia.org/wiki/ASCII) /// /// This mapping uses the standard ascii character set which is composed of a /// 7-bit code (or 128 characters). The first 32 characters and the last one /// are non-printable control characters. pub const CODEPAGE_ASCII: &'static [char] = &[ // 0 1 2 3 4 5 6 7 8 9 A B C D E F NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, // 0 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, // 1 ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', // 2 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', // 3 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', // 4 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', // 5 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', // 6 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', NIL, // 7 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, // 8 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, // 9 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, // A NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, // B NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, // C NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, // D NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, // E NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, // F ]; /// The mapping for [code page 850](https://en.wikipedia.org/wiki/Code_page_850) /// /// This code page is also known as `DOS/IBM-ASCII` and is used as the default /// code page by this library. pub const CODEPAGE_0850: &'static [char] = &[ // 0 1 2 3 4 5 6 7 8 9 A B C D E F NIL, '☺', '☻', '♥', '♦', '♣', '♠', '•', '◘', '○', '◙', '♂', '♀', '♪', '♫', '☼', // 0 '►', '◄', '↕', '‼', '¶', '§', '▬', '↨', '↑', '↓', '→', '←', '∟', '↔', '▲', '▼', // 1 ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', // 2 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', // 3 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', // 4 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', // 5 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', // 6 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '⌂', // 7 'Ç', 'ü', 'é', 'â', 'ä', 'à', 'å', 'ç', 'ê', 'ë', 'è', 'ï', 'î', 'ì', 'Ä', 'Å', // 8 'É', 'æ', 'Æ', 'ô', 'ö', 'ò', 'û', 'ù', 'ÿ', 'Ö', 'Ü', 'ø', '£', 'Ø', '×', 'ƒ', // 9 'á', 'í', 'ó', 'ú', 'ñ', 'Ñ', 'ª', 'º', '¿', '⌐', '¬', '½', '¼', '¡', '«', '»', // A '░', '▒', '▓', '│', '┤', '╡', '╢', '╖', '╕', '╣', '║', '╗', '╝', '╜', '╛', '┐', // B '└', '┴', '┬', '├', '─', '┼', '╞', '╟', '╚', '╔', '╩', '╦', '╠', '═', '╬', '╧', // C '╨', '╤', '╥', '╙', '╘', '╒', '╕', '╫', '╪', '┘', '┌', '█', '▄', '▌', '▐', '▀', // D 'α', 'ß', 'Γ', '∏', '∑', 'σ', 'µ', 'τ', 'Φ', 'θ', 'Ω', 'δ', '∞', 'Ø', 'ε', '∩', // E '≡', '±', '≥', '≤', '⌠', '⌡', '÷', '≈', '▫', '¨', '·', '√', 'ⁿ', '²', '■', NIL, // F ]; /// The mapping for [code page 1252](https://en.wikipedia.org/wiki/Code_page_1252) /// /// This code page is also known as `Latin 1 Windows` or `ANSI`. pub const CODEPAGE_1252: &'static [char] = &[ // 0 1 2 3 4 5 6 7 8 9 A B C D E F NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, // 0 NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, NIL, // 1 ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', // 2 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', // 3 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', // 4 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', // 5 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', // 6 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', NIL, // 7 '€', NIL, '‚', 'ƒ', '„', '…', '†', '‡', 'ˆ', '‰', 'Š', '‹', 'Œ', NIL, 'Ž', NIL, // 8 NIL, '‘', '’', '“', '”', '•', '–', '—', '˜', '™', 'Š', '›', 'œ', NIL, 'ž', 'Ÿ', // 9 ' ', '¡', '¢', '£', '¤', '¥', '¦', '§', '¨', '©', 'ª', '«', '¬', NIL, '®', '¯', // A '°', '±', '²', '³', '´', 'µ', '¶', '·', '¸', '¹', 'º', '»', '¼', '½', '¾', '¿', // B 'À', 'Á', 'Â', 'Ã', 'Ä', 'Å', 'Æ', 'Ç', 'È', 'É', 'Ê', 'Ë', 'Ì', 'Í', 'Î', 'Ï', // C 'Ð', 'Ñ', 'Ò', 'Ó', 'Ô', 'Õ', 'Ö', '×', 'Ø', 'Ù', 'Ú', 'Û', 'Ü', 'Ý', 'Þ', 'ß', // D 'à', 'á', 'â', 'ã', 'ä', 'å', 'æ', 'ç', 'è', 'é', 'ê', 'ë', 'ì', 'í', 'î', 'ï', // E 'ð', 'ñ', 'ò', 'ó', 'ô', 'õ', 'ö', '÷', 'ø', 'ù', 'ú', 'û', 'ü', 'ý', 'þ', 'ÿ', // F ]; fn contains(byte: u8, codepage: &[char]) -> bool { (byte as usize) < codepage.len() } fn is_nil(byte: u8, codepage: &[char]) -> bool { codepage[byte as usize] == NIL } fn is_printable(byte: u8, codepage: &[char]) -> bool { contains(byte, codepage) && !is_nil(byte, codepage) } /// Returns a byte's character representation given a specific codepage pub fn as_char(byte: u8, codepage: &[char], repl_char: char) -> char { if !is_printable(byte, codepage) { return repl_char; } return codepage[byte as usize]; } #[cfg(test)] mod tests { use super::*; use std; #[test] fn test_hardcoded_ascii_table_matches_the_generated_one() { let codepage: Vec = std::iter::empty() .chain(std::iter::repeat(super::NIL).take(32)) // The first 32 control characters .chain((32..127).map(|c| std::char::from_u32(c).unwrap())) // The following 95 printable chars .chain(std::iter::once(super::NIL)) // The DEL character .chain(std::iter::repeat(super::NIL).take(128)) // The characters for the 8th bit .collect(); assert_eq!(CODEPAGE_ASCII, &*codepage); } } hexplay-0.2.1/src/color.rs000064400000000000000000000052311327464325000136340ustar0000000000000000//! Provides helpers for generating colors for use in HexViewBuilder printing, //! as well as some reexports of the underlying color crate, `termcolor` use std::io::{self, Write}; use std::ops::Range; use termcolor::WriteColor; pub use termcolor::Color as Color; pub use termcolor::ColorSpec as Spec; /// A vector of `(ColorSpec, Range)` values to print pub type Colors = Vec<(Spec, Range)>; pub struct ColorlessString(pub String); impl Write for ColorlessString { fn write(&mut self, buf: &[u8]) -> io::Result { use std::str; let string = str::from_utf8(buf).unwrap(); self.0 += string; Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } impl WriteColor for ColorlessString { fn supports_color(&self) -> bool { false } fn set_color(&mut self, _spec: &Spec) -> io::Result<()> { Ok(()) } fn reset (&mut self) -> io::Result<()> { Ok(()) } } pub struct ColorRange<'a> { colors: &'a Colors, offset: usize, } impl<'a> Clone for ColorRange<'a> { fn clone(&self) -> Self { ColorRange { colors: self.colors, offset: self.offset, } } } impl<'a> ColorRange<'a> { pub fn new(colors: &'a Colors) -> Self { ColorRange { colors: colors, offset: 0, } } pub fn update_offset(&mut self, offset: usize) { self.offset = offset; } pub fn get(&self, idx: usize) -> Option<&Spec> { let mut i = 0; let offset = self.offset + idx; while i < self.colors.len() { let (ref rgb, ref range) = self.colors[i]; if offset >= range.start && offset < range.end { return Some(rgb); } else { i += 1; } } None } } macro_rules! make_color { ($name:ident, $name_bold:ident, $color:ident) => { /// Creates the appropriate ColorSpec pub fn $name() -> Spec { Spec::new().set_bold(false).set_fg(Some(Color::$color)).clone() } /// Creates the appropriate ColorSpec, in bold pub fn $name_bold () -> Spec { Spec::new().set_bold(true).set_fg(Some(Color::$color)).clone() } } } make_color!(red, red_bold, Red); make_color!(blue, blue_bold, Blue); make_color!(green, green_bold, Green); make_color!(yellow, yellow_bold, Yellow); make_color!(magenta, magenta_bold, Magenta); make_color!(black, black_bold, Black); make_color!(cyan, cyan_bold, Cyan); make_color!(white, white_bold, White); hexplay-0.2.1/src/format.rs000064400000000000000000000362441327464325000140160ustar0000000000000000use 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; /// The HexView struct represents the configuration of how to display the data. 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> { /// Prints the hextable to stdout. If any colors were given during construction, the specified ranges will be printed in color. 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(()) } /// Constructs a new HexView for the given data without offset and using codepage 850, a row width /// of 16 and `.` as replacement character. 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(&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(()) } } /// A builder for the [HexView](struct.HexView.html) struct. pub struct HexViewBuilder<'a> { hex_view: HexView<'a>, } impl<'a> HexViewBuilder<'a> { /// Constructs a new HexViewBuilder for the given data. pub fn new(data: &[u8]) -> HexViewBuilder { HexViewBuilder { hex_view: HexView::new(&data) } } /// Configures the address offset of the HexView under construction. pub fn address_offset(mut self, offset: usize) -> HexViewBuilder<'a> { self.hex_view.address_offset = offset; self } /// Forces any color data to be printed in `print`, even if redirected to a file or pipe. pub fn force_color(mut self) -> Self { self.hex_view.force_color = true; self } /// Configures the codepage of the HexView under construction. pub fn codepage<'b: 'a>(mut self, codepage: &'b [char]) -> HexViewBuilder<'a> { self.hex_view.codepage = codepage; self } /// Configures the replacement character of the HexView under construction. /// /// The replacement character is the character that will be used for nonprintable /// characters in the codepage. pub fn replacement_character(mut self, ch: char) -> HexViewBuilder<'a> { self.hex_view.replacement_character = ch; self } /// Configures the row width of the HexView under construction. pub fn row_width(mut self, width: usize) -> HexViewBuilder<'a> { self.hex_view.row_width = width; self } /// Adds the vector of `colors` to the range color printer pub fn add_colors(mut self, colors: Colors) -> HexViewBuilder<'a> { self.hex_view.colors.extend(colors); self } /// Adds the `color` to the given `range`, using a more ergonomic API pub fn add_color(mut self, color: &str, range: Range) -> HexViewBuilder<'a> { use std::str::FromStr; self.hex_view.colors.push((Spec::new().set_fg(Some(Color::from_str(color).unwrap())).clone(), range)); self } /// Constructs the HexView. 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(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(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(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() { // Rust 1.13 needs the fully qualified name here 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 = (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 = (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 = (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()); } } hexplay-0.2.1/src/lib.rs000064400000000000000000000053441314611711400132620ustar0000000000000000//! This crate provides a way to [Display](https://doc.rust-lang.org/std/fmt/trait.Display.html) //! a byte slice as it is commonly done in a hex-editor. //! //! The configuration of the visualization are stored in the [HexView](struct.HexView.html), //! struct, which can be easily constructed using the [HexViewBuilder](struct.HexViewBuilder.html). //! //! # Examples //! //! Usage is very simple, just build a `HexView` and use it for formatting: //! //! ```rust //! use hexplay::HexViewBuilder; //! //! // The buffer we want to display //! let data : Vec = (0u8..200u8).collect(); //! //! // Build a new HexView using the provider builder //! let view = HexViewBuilder::new(&data[40..72]) //! .address_offset(40) //! .row_width(16) //! .finish(); //! //! println!("{}", view); //! //! # let result = format!("{}", view); //! # let mut lines = result.lines(); //! # assert_eq!("00000020 28 29 2A 2B 2C 2D 2E 2F | ()*+,-./ |", lines.next().unwrap()); //! # assert_eq!("00000030 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F | 0123456789:;<=>? |", lines.next().unwrap()); //! # assert_eq!("00000040 40 41 42 43 44 45 46 47 | @ABCDEFG |", lines.next().unwrap()); //! ``` //! //! This will result in the following output: //! //! ```text //! 00000020 28 29 2A 2B 2C 2D 2E 2F | ()*+,-./ | //! 00000030 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F | 0123456789:;<=>? | //! 00000040 40 41 42 43 44 45 46 47 | @ABCDEFG | //! ``` //! //! # Color //! //! You can add color to the hextable by specifying a [color::Spec](color/struct.Spec.html) and a range in the hextable to color, //! using HexViewBuilder's [add_colors](struct.HexViewBuilder.html#method.add_colors) method. //! //! **NB**: overlapping color ranges have unspecified behavior (not unsafe though, of course) //! //! ```rust //! use hexplay::HexViewBuilder; //! //! let data : Vec = (0u8..200u8).collect(); //! //! let view = HexViewBuilder::new(&data[40..72]) //! .address_offset(40) //! .row_width(16) //! .add_colors(vec![ //! (hexplay::color::red(), 6..15), //! (hexplay::color::blue(), 21..26), //! (hexplay::color::yellow_bold(), 15..21), //! (hexplay::color::green(), 0..6), //! ]) //! .finish(); //! //! // this will print to stdout //! view.print().unwrap(); //! ``` extern crate atty; extern crate termcolor; mod byte_mapping; mod format; pub mod color; pub use byte_mapping::CODEPAGE_0850; pub use byte_mapping::CODEPAGE_1252; pub use byte_mapping::CODEPAGE_ASCII; pub use format::HexView; pub use format::HexViewBuilder;