1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
/**
 * File: src/lib.rs
 * Author: Anicka Burova <anicka.burova@gmail.com>
 * Date: 03.10.2017
 * Last Modified Date: 03.10.2017
 * Last Modified By: Anicka Burova <anicka.burova@gmail.com>
 */
use std::io::{Read,Bytes};
use std::fmt::Write;
#[cfg(test)]
use std::fs::File;
use std::iter::Iterator;

/// Format configuration of xxd output
#[derive(Clone)]
pub struct Format {
    /// How many bytes per line, the default is 16.
    pub size: usize,
    /// How to pack xx bytes next to each other. For each multiple of the value, space will be
    /// inserted.
    /// The default is [2,4,8]. (Every two bytes one space, every 4 bytes a space and every 8 bytes
    /// a space).
    pub pack: Vec<usize>,
    /// A character to print in case of unprintable characters for ASCII output, the default is '.'.
    pub ascii_none: char,
    /// True to output ASCII. The default is true.
    pub ascii: bool,
    /// Gaps in formatting: offset{gaps.0}hex{gaps.1}ascii. The default is 2 spaces for both.
    pub gaps: (usize, usize),
}

impl Format {
    /// Create default format
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate hex_utils;
    ///
    /// let format = hex_utils::Format::default().unwrap();
    ///
    /// assert_eq!(16, format.size);
    ///
    /// ```
    pub fn default() -> Option<Format> {
        Some(
            Format {
                size: 16,
                pack: vec![2,4,8],
                ascii_none: '.',
                ascii: true,
                gaps: (2,2),
            }
            )
    }

    /// Get the format out of Option or get the default one.
    ///
    /// # Examples
    ///
    /// ```
    /// let format = Some(hex_utils::Format {
    ///                                 size: 9,
    ///                                 pack: vec![3,5],
    ///                                 ascii_none: '#',
    ///                                 ascii: true,
    ///                                 gaps: (2,3),
    ///                                 });
    ///
    /// let opt = hex_utils::Format::or_default(format);
    /// assert_eq!(9, opt.size);
    /// assert_eq!(vec![3,5], opt.pack);
    /// assert_eq!('#', opt.ascii_none);
    ///
    /// let opt = hex_utils::Format::or_default(None);
    /// assert_eq!(16, opt.size);
    /// assert_eq!(vec![2,4,8], opt.pack);
    /// assert_eq!('.', opt.ascii_none);
    ///
    /// ```
    pub fn or_default(format: Option<Format>) -> Format {
        match format {
            Some(o) => o,
            None    => Format::default().unwrap(),
        }
    }

    /// Create formatter function from this formatting configuration.
    pub fn formatter(&self) -> Box<Fn( (usize, String, Option<String>)) -> String> {
            let hex_size =
                    self.size * 2
                    + self.pack.iter().fold(0, | sum, val| sum + if *val == 0 { 0 } else { (self.size - 1) / val } );
            let gap0 = self.gaps.0;
            let gap1 = self.gaps.1;
            Box::new(move |line| {
                let (i,h,t) = line;
                let mut result = String::new();
                match t {
                    Some(t) => {
                        let _ = write!(&mut result, "{:0index$x}:{:gap0$}{:hex$}{:gap1$}{}", i,"", h.trim(),"", t, index=6, hex = hex_size, gap0 = gap0, gap1 = gap1);
                    }
                    None => {
                        let _ = write!(&mut result, "{:0index$x}:{:gap0$}{:hex$}", i, "", h.trim(), index=6, hex = hex_size, gap0 = gap0);
                    }
                }
                result
            })
    }
}

/// Iterator over lines returned by xxd.
///
/// Iterators returns (usize, String, Option<String>)
/// where ther first is the offset of the data,
/// the second is data formatted to hex output
/// and the third is Option to return data formatted as normal text.
///
pub struct XxdLines<T: Read> {
    iter: Bytes<T>,
    format: Format,
    offset: usize,
}

impl<T: Read> Iterator for XxdLines<T> {
    type Item = (usize, String, Option<String>);
    fn next(&mut self) -> Option<Self::Item> {
        match self.iter {
            ref mut iter => {
                match iter.take(self.format.size) {
                    ref mut iter => {
                        let mut any = false;
                        let mut hexed = String::new();
                        let mut ascii = if self.format.ascii { Some(String::new()) } else { None };
                        let mut pack = vec![0;self.format.pack.len()];
                        for c in iter {
                            any = true;
                            let c = c.unwrap();
                            let _ = write!(&mut hexed, "{:02x}", c);
                            for i in 0..pack.len() {
                                pack[i] += 1;
                                if pack[i] == self.format.pack[i] {
                                    let _ = write!(&mut hexed, " ");
                                    pack[i] = 0;
                                }
                            }
                            match ascii {
                                Some(ref mut ascii) => {
                                    if 32<= c && c < 127 {
                                        let _ = write!(ascii, "{}", c as char);
                                    } else {
                                        let _ = write!(ascii, "{}", self.format.ascii_none);
                                    }
                                }
                                None => (),
                            }
                        }
                        let offset = self.offset;
                        self.offset += self.format.size;
                        if any {
                            Some((offset,hexed,ascii))
                        } else {
                            None
                        }
                    }
                }
            }
        }
    }
}


/// Returns xxd iterator over the data configured by format.
/// Passing None to format will use defaults.
///
/// # Examples
/// ```
/// extern crate hex_utils;
///
///
/// let text = "The quick brown fox jumps over the lazy dog";
///
/// for (offset, hex, txt) in hex_utils::xxd(text.as_bytes(), None) {
///     println!("offset = {:03x} hex = {:60} txt = {}", offset, hex, txt.unwrap());
/// }
///
/// ```
/// ```
/// extern crate hex_utils;
///
///
/// let text = "The quick brown fox jumps over the lazy dog";
/// let format = hex_utils::Format {
///     size: 18,
///     pack: vec![3,6],
///     ascii_none: '-',
///     ascii: true,
///     gaps:(4,2),
/// };
///
/// let fmt = format.formatter();
///
/// for line in hex_utils::xxd(text.as_bytes(), Some(format)) {
///     println!("{}", fmt(line));
/// }
/// ```
pub fn xxd<T: Read>( data: T, format: Option<Format>) -> XxdLines<T> {
    XxdLines {
        iter: data.bytes(),
        format: Format::or_default(format),
        offset: 0
    }
}

/// Returns the whole xxd output as one string.
///
/// # Examples
///
/// ```
/// extern crate hex_utils;
///
///
/// let text = "The quick brown fox jumps over the lazy dog";
///
/// println!("{}", hex_utils::xxd_str(text.as_bytes(), None));
///
/// ```
pub fn xxd_str<T: Read>( data: T, format: Option<Format>) -> String {
    let format = Format::or_default(format);
    let fmt = format.formatter();
    xxd(data, Some(format)).map(|line| fmt(line)).fold(String::new(), |mut res, line| {let _ = writeln!(&mut res, "{}", line);res})
}


#[test]
fn xxd_test() {
    let text = "[package]
name = \"hex-utils\"
version = \"0.1.5\"
authors = [\"Anicka Burova <anicka.burova@gmail.com>\"]

[dependencies]
itertools = \"*\"";
    let output = "000000: 5b70 6163  6b61 6765  5d0a 6e61  6d65 203d  [package].name =\n\
000010: 2022 6865  782d 7574  696c 7322  0a76 6572   \"hex-utils\".ver\n\
000020: 7369 6f6e  203d 2022  302e 312e  3522 0a61  sion = \"0.1.5\".a\n\
000030: 7574 686f  7273 203d  205b 2241  6e69 636b  uthors = [\"Anick\n\
000040: 6120 4275  726f 7661  203c 616e  6963 6b61  a Burova <anicka\n\
000050: 2e62 7572  6f76 6140  676d 6169  6c2e 636f  .burova@gmail.co\n\
000060: 6d3e 225d  0a0a 5b64  6570 656e  6465 6e63  m>\"]..[dependenc\n\
000070: 6965 735d  0a69 7465  7274 6f6f  6c73 203d  ies].itertools =\n\
000080: 2022 2a22                                    \"*\"\n";
    let format = Format {
        size: 16,
        pack: vec![2,4],
        ascii_none: '.',
        ascii: true,
        gaps:(1,2),
    };
    let xxd_output = xxd_str(text.as_bytes(), Some(format));
    assert_eq!(output.to_string(), xxd_output);
}
#[test]
fn print_test() {
    let mut file = File::open("Cargo.toml").unwrap();
    let mut content = String::new();
    let _ = file.read_to_string(&mut content);
    let format = Format {
        size: 16,
        pack: vec![2,4],
        ascii_none: '.',
        ascii: true,
        gaps: (1,4)
    };
    let _ = format.clone();

    //let fmt = format.formatter();

    //let res = xxd(content.as_bytes(), Some(format)).map(|line| fmt(line)).fold(String::new(), |mut res, line| { let _ = writeln!(&mut res, "{}", line);res});
    //let res = xxd_str(content.as_bytes(), Some(format));
    let res = xxd_str(content.as_bytes(), Some(format));
    println!("{}", res);

    //for line in xxd(content.as_bytes(), Some(format)).map(|line| fmt(line)) {
        //println!("{}", line);
    //}
}