hexdump 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ChangeLog.md CHANGED
@@ -1,3 +1,17 @@
1
+ ### 0.2.1 / 2011-06-11
2
+
3
+ * Fixed a major bug in {Hexdump::Dumper#dump}, where the line buffers
4
+ were not being cleared.
5
+
6
+ ### 0.2.0 / 2011-06-11
7
+
8
+ * Added {Hexdump::Dumper}.
9
+ * Added support for hexdumping 1, 2, 4, 8 byte words.
10
+ * Added support for hexdumping Little and Big Endian words.
11
+ * Optimized the hexdump algorithm to not use Arrays, use a lookup table
12
+ of printable characters and cache formatted numbers.
13
+ * Opted into [test.rubygems.org](http://test.rubygems.org/).
14
+
1
15
  ### 0.1.0 / 2011-03-05
2
16
 
3
17
  * Initial release:
data/README.md CHANGED
@@ -15,53 +15,11 @@ Simple and Fast hexdumping for Ruby.
15
15
  * Can send the hexdump output to any Object supporting the `<<` method.
16
16
  * Can yield each line of hexdump, instead of printing the output.
17
17
  * Supports printing ASCII, hexadecimal, decimal, octal and binary bytes.
18
+ * Supports hexdumping 1, 2, 4, 8 byte words.
19
+ * Supports hexdumping Little and Big Endian words.
18
20
  * Makes {String}, {StringIO}, {IO}, {File} objects hexdumpable.
19
21
  * Fast-ish.
20
22
 
21
- ## Benchmarks
22
-
23
- Benchmarks show {Hexdump.dump} processing 2.4M of data.
24
-
25
- ### Ruby 1.9.2-p180
26
-
27
- user system total real
28
- hexdump (block) 7.740000 0.030000 7.770000 ( 8.138029)
29
- hexdump 9.590000 0.050000 9.640000 ( 10.178203)
30
- hexdump width=256 (block) 7.280000 0.020000 7.300000 ( 7.534507)
31
- hexdump width=256 8.130000 0.030000 8.160000 ( 8.342448)
32
- hexdump ascii=true (block) 7.740000 0.030000 7.770000 ( 7.958550)
33
- hexdump ascii=true 9.550000 0.050000 9.600000 ( 9.803758)
34
-
35
- ### Ruby 1.8.7-p334
36
-
37
- user system total real
38
- hexdump (block) 10.520000 0.010000 10.530000 ( 10.692901)
39
- hexdump 11.580000 0.010000 11.590000 ( 11.873978)
40
- hexdump width=256 (block) 9.960000 0.110000 10.070000 ( 11.592033)
41
- hexdump width=256 10.660000 0.010000 10.670000 ( 10.987417)
42
- hexdump ascii=true (block) 10.620000 0.010000 10.630000 ( 10.899925)
43
- hexdump ascii=true 11.590000 0.030000 11.620000 ( 12.765259)
44
-
45
- ### Jruby 1.5.6
46
-
47
- user system total real
48
- hexdump (block) 6.690000 0.000000 6.690000 ( 6.517000)
49
- hexdump 8.234000 0.000000 8.234000 ( 8.234000)
50
- hexdump width=256 (block) 4.488000 0.000000 4.488000 ( 4.488000)
51
- hexdump width=256 5.462000 0.000000 5.462000 ( 5.462000)
52
- hexdump ascii=true (block) 4.456000 0.000000 4.456000 ( 4.456000)
53
- hexdump ascii=true 5.039000 0.000000 5.039000 ( 5.039000)
54
-
55
- ### Rubinius 1.2.3
56
-
57
- user system total real
58
- hexdump (block) 10.013478 0.018997 10.032475 ( 11.148450)
59
- hexdump 13.153001 0.015997 13.168998 ( 13.740888)
60
- hexdump width=256 (block) 8.845656 0.008999 8.854655 ( 9.022673)
61
- hexdump width=256 9.894496 0.008999 9.903495 ( 10.121070)
62
- hexdump ascii=true (block) 9.576544 0.021996 9.598540 ( 9.810846)
63
- hexdump ascii=true 13.088011 0.015998 13.104009 ( 13.390532)
64
-
65
23
  ## Examples
66
24
 
67
25
  require 'hexdump'
@@ -74,6 +32,10 @@ Benchmarks show {Hexdump.dump} processing 2.4M of data.
74
32
  data.hexdump
75
33
  # 00000000 68 65 6c 6c 6f 00 |hello.|
76
34
 
35
+ File.open('dump.txt','w') do |file|
36
+ data.hexdump(:output => file)
37
+ end
38
+
77
39
  # iterate over the hexdump lines
78
40
  data.hexdump do |index,hex,printable|
79
41
  index # => 0
@@ -102,10 +64,82 @@ Benchmarks show {Hexdump.dump} processing 2.4M of data.
102
64
  Hexdump.dump(data, :base => :binary)
103
65
  # 00000000 01101000 01100101 01101100 01101100 01101111 00000000 |hello.|
104
66
 
67
+ ("ABC" * 10).hexdump(:word_size => 2)
68
+ # 00000000 4241 4143 4342 4241 4143 4342 4241 4143 |䉁䅃䍂䉁䅃䍂䉁䅃|
69
+ # 00000010 4342 4241 4143 4342 4241 4143 4342 |䍂䉁䅃䍂䉁䅃䍂|
70
+
105
71
  ## Install
106
72
 
107
73
  $ gem install hexdump
108
74
 
75
+ ## Benchmarks
76
+
77
+ Benchmarks show {Hexdump.dump} processing 2.4M of data.
78
+
79
+ ### Ruby 1.9.2-p180
80
+
81
+ user system total real
82
+ hexdump (block) 3.010000 0.010000 3.020000 ( 3.529396)
83
+ hexdump 5.430000 0.030000 5.460000 ( 6.216174)
84
+ hexdump width=256 (block) 3.010000 0.020000 3.030000 ( 3.308961)
85
+ hexdump width=256 4.700000 0.040000 4.740000 ( 5.520189)
86
+ hexdump ascii=true (block) 3.050000 0.010000 3.060000 ( 3.501436)
87
+ hexdump ascii=true 5.450000 0.040000 5.490000 ( 6.352144)
88
+ hexdump word_size=2 (block) 7.420000 0.050000 7.470000 ( 9.174734)
89
+ hexdump word_size=2 9.500000 0.070000 9.570000 ( 11.228204)
90
+ hexdump word_size=4 (block) 4.110000 0.030000 4.140000 ( 4.849785)
91
+ hexdump word_size=4 5.380000 0.060000 5.440000 ( 6.209022)
92
+ hexdump word_size=8 (block) 3.350000 0.070000 3.420000 ( 4.147304)
93
+ hexdump word_size=8 4.430000 0.040000 4.470000 ( 5.930758)
94
+
95
+ ### Ruby 1.8.7-p334
96
+
97
+ user system total real
98
+ hexdump (block) 8.470000 0.020000 8.490000 ( 9.585524)
99
+ hexdump 11.080000 0.050000 11.130000 ( 12.542401)
100
+ hexdump width=256 (block) 8.360000 0.030000 8.390000 ( 9.431877)
101
+ hexdump width=256 10.310000 0.050000 10.360000 ( 12.278973)
102
+ hexdump ascii=true (block) 8.550000 0.030000 8.580000 ( 10.502437)
103
+ hexdump ascii=true 11.140000 0.040000 11.180000 ( 12.752712)
104
+ hexdump word_size=2 (block) 12.680000 0.060000 12.740000 ( 14.657269)
105
+ hexdump word_size=2 13.560000 0.080000 13.640000 ( 16.368675)
106
+ hexdump word_size=4 (block) 8.500000 0.040000 8.540000 ( 9.687623)
107
+ hexdump word_size=4 9.340000 0.040000 9.380000 ( 10.657158)
108
+ hexdump word_size=8 (block) 7.520000 0.040000 7.560000 ( 8.565246)
109
+ hexdump word_size=8 8.240000 0.040000 8.280000 ( 9.475693)
110
+
111
+ ### JRuby 1.6.0
112
+
113
+ user system total real
114
+ hexdump (block) 6.742000 0.000000 6.742000 ( 6.495000)
115
+ hexdump 7.498000 0.000000 7.498000 ( 7.498000)
116
+ hexdump width=256 (block) 4.601000 0.000000 4.601000 ( 4.601000)
117
+ hexdump width=256 5.569000 0.000000 5.569000 ( 5.569000)
118
+ hexdump ascii=true (block) 5.198000 0.000000 5.198000 ( 5.198000)
119
+ hexdump ascii=true 5.799000 0.000000 5.799000 ( 5.798000)
120
+ hexdump word_size=2 (block) 8.440000 0.000000 8.440000 ( 8.440000)
121
+ hexdump word_size=2 8.698000 0.000000 8.698000 ( 8.698000)
122
+ hexdump word_size=4 (block) 5.603000 0.000000 5.603000 ( 5.602000)
123
+ hexdump word_size=4 5.999000 0.000000 5.999000 ( 5.999000)
124
+ hexdump word_size=8 (block) 7.975000 0.000000 7.975000 ( 7.975000)
125
+ hexdump word_size=8 5.255000 0.000000 5.255000 ( 5.255000)
126
+
127
+ ### Rubinius 1.2.4
128
+
129
+ user system total real
130
+ hexdump (block) 5.064230 0.029996 5.094226 ( 6.236865)
131
+ hexdump 7.401875 0.039993 7.441868 ( 10.154394)
132
+ hexdump width=256 (block) 4.149369 0.054992 4.204361 ( 6.518306)
133
+ hexdump width=256 4.960246 0.089986 5.050232 ( 8.647516)
134
+ hexdump ascii=true (block) 4.458322 0.026996 4.485318 ( 5.570982)
135
+ hexdump ascii=true 6.961941 0.056992 7.018933 ( 9.895088)
136
+ hexdump word_size=2 (block) 8.856653 0.078988 8.935641 ( 11.226360)
137
+ hexdump word_size=2 10.489405 0.083988 10.573393 ( 12.980509)
138
+ hexdump word_size=4 (block) 4.848263 0.047992 4.896255 ( 6.526478)
139
+ hexdump word_size=4 6.649989 0.053992 6.703981 ( 8.245247)
140
+ hexdump word_size=8 (block) 5.638143 0.047993 5.686136 ( 12.530454)
141
+ hexdump word_size=8 7.598844 0.066990 7.665834 ( 16.881667)
142
+
109
143
  ## Copyright
110
144
 
111
145
  Copyright (c) 2011 Hal Brodigan
@@ -8,7 +8,7 @@ require 'benchmark'
8
8
  DATA = ((0..255).map { |b| b.chr }.join) * 10000
9
9
  OUTPUT = Class.new { def <<(data); end }.new
10
10
 
11
- Benchmark.bm(26) do |b|
11
+ Benchmark.bm(27) do |b|
12
12
  b.report('hexdump (block)') do
13
13
  Hexdump.dump(DATA) { |index,hex,print| }
14
14
  end
@@ -32,4 +32,14 @@ Benchmark.bm(26) do |b|
32
32
  b.report('hexdump ascii=true') do
33
33
  Hexdump.dump(DATA, :ascci => true, :output => OUTPUT)
34
34
  end
35
+
36
+ [2, 4, 8].each do |word_size|
37
+ b.report("hexdump word_size=#{word_size} (block)") do
38
+ Hexdump.dump(DATA, :word_size => word_size) { |index,hex,print| }
39
+ end
40
+
41
+ b.report("hexdump word_size=#{word_size}") do
42
+ Hexdump.dump(DATA, :word_size => word_size, :output => OUTPUT)
43
+ end
44
+ end
35
45
  end
data/gemspec.yml CHANGED
@@ -1,13 +1,15 @@
1
1
  name: hexdump
2
- version: 0.1.0
3
- summary: Simple and Fast hexdumping for Ruby.
2
+ version: 0.2.1
3
+ summary: Hexdump Strings and IO objects.
4
4
  description: Simple and Fast hexdumping for Ruby.
5
5
  license: MIT
6
- authors: hal
6
+ authors: Postmodern
7
7
  email: postmodern.mod3@gmail.com
8
8
  homepage: http://github.com/postmodern/hexdump
9
9
  has_yard: true
10
10
 
11
+ required_ruby_version: ">= 1.8.7"
12
+
11
13
  development_dependencies:
12
14
  ore-tasks: ~> 0.4
13
15
  rspec: ~> 2.4
@@ -0,0 +1,441 @@
1
+ module Hexdump
2
+ #
3
+ # Handles the parsing of data and formatting of the hexdump.
4
+ #
5
+ # @since 0.2.0
6
+ #
7
+ class Dumper
8
+
9
+ # Widths for formatted numbers
10
+ WIDTHS = {
11
+ :hexadecimal => proc { |word_size| word_size * 2 },
12
+ :decimal => {
13
+ 1 => 3,
14
+ 2 => 5,
15
+ 4 => 10,
16
+ 8 => 20
17
+ },
18
+ :octal => {
19
+ 1 => 3,
20
+ 2 => 6,
21
+ 4 => 11,
22
+ 8 => 22
23
+ },
24
+ :binary => proc { |word_size| word_size * 8 }
25
+ }
26
+
27
+ # Format Strings for the various bases
28
+ FORMATS = {
29
+ :hexadecimal => proc { |width| "%.#{width}x" },
30
+ :decimal => proc { |width| "%#{width}.d" },
31
+ :octal => proc { |width| "%.#{width}o" },
32
+ :binary => proc { |width| "%.#{width}b" }
33
+ }
34
+
35
+ # Character to represent unprintable characters
36
+ UNPRINTABLE = '.'
37
+
38
+ # ASCII printable bytes and characters
39
+ PRINTABLE = {
40
+ 0x20 => " ",
41
+ 0x21 => "!",
42
+ 0x22 => "\"",
43
+ 0x23 => "#",
44
+ 0x24 => "$",
45
+ 0x25 => "%",
46
+ 0x26 => "&",
47
+ 0x27 => "'",
48
+ 0x28 => "(",
49
+ 0x29 => ")",
50
+ 0x2a => "*",
51
+ 0x2b => "+",
52
+ 0x2c => ",",
53
+ 0x2d => "-",
54
+ 0x2e => ".",
55
+ 0x2f => "/",
56
+ 0x30 => "0",
57
+ 0x31 => "1",
58
+ 0x32 => "2",
59
+ 0x33 => "3",
60
+ 0x34 => "4",
61
+ 0x35 => "5",
62
+ 0x36 => "6",
63
+ 0x37 => "7",
64
+ 0x38 => "8",
65
+ 0x39 => "9",
66
+ 0x3a => ":",
67
+ 0x3b => ";",
68
+ 0x3c => "<",
69
+ 0x3d => "=",
70
+ 0x3e => ">",
71
+ 0x3f => "?",
72
+ 0x40 => "@",
73
+ 0x41 => "A",
74
+ 0x42 => "B",
75
+ 0x43 => "C",
76
+ 0x44 => "D",
77
+ 0x45 => "E",
78
+ 0x46 => "F",
79
+ 0x47 => "G",
80
+ 0x48 => "H",
81
+ 0x49 => "I",
82
+ 0x4a => "J",
83
+ 0x4b => "K",
84
+ 0x4c => "L",
85
+ 0x4d => "M",
86
+ 0x4e => "N",
87
+ 0x4f => "O",
88
+ 0x50 => "P",
89
+ 0x51 => "Q",
90
+ 0x52 => "R",
91
+ 0x53 => "S",
92
+ 0x54 => "T",
93
+ 0x55 => "U",
94
+ 0x56 => "V",
95
+ 0x57 => "W",
96
+ 0x58 => "X",
97
+ 0x59 => "Y",
98
+ 0x5a => "Z",
99
+ 0x5b => "[",
100
+ 0x5c => "\\",
101
+ 0x5d => "]",
102
+ 0x5e => "^",
103
+ 0x5f => "_",
104
+ 0x60 => "`",
105
+ 0x61 => "a",
106
+ 0x62 => "b",
107
+ 0x63 => "c",
108
+ 0x64 => "d",
109
+ 0x65 => "e",
110
+ 0x66 => "f",
111
+ 0x67 => "g",
112
+ 0x68 => "h",
113
+ 0x69 => "i",
114
+ 0x6a => "j",
115
+ 0x6b => "k",
116
+ 0x6c => "l",
117
+ 0x6d => "m",
118
+ 0x6e => "n",
119
+ 0x6f => "o",
120
+ 0x70 => "p",
121
+ 0x71 => "q",
122
+ 0x72 => "r",
123
+ 0x73 => "s",
124
+ 0x74 => "t",
125
+ 0x75 => "u",
126
+ 0x76 => "v",
127
+ 0x77 => "w",
128
+ 0x78 => "x",
129
+ 0x79 => "y",
130
+ 0x7a => "z",
131
+ 0x7b => "{",
132
+ 0x7c => "|",
133
+ 0x7d => "}",
134
+ 0x7e => "~"
135
+ }
136
+
137
+ PRINTABLE.default = UNPRINTABLE
138
+
139
+ # The base to dump words as.
140
+ attr_reader :base
141
+
142
+ # The size of the words parse from the data.
143
+ attr_reader :word_size
144
+
145
+ # The endianness of the words parsed from the data.
146
+ attr_reader :endian
147
+
148
+ # The width in words of each hexdump line.
149
+ attr_reader :width
150
+
151
+ # Whether to display ASCII characters alongside numeric values.
152
+ attr_reader :ascii
153
+
154
+ #
155
+ # Creates a new Hexdump dumper.
156
+ #
157
+ # @param [Hash] options
158
+ # Additional options.
159
+ #
160
+ # @option options [Integer] :width (16)
161
+ # The number of bytes to dump for each line.
162
+ #
163
+ # @option options [Integer] :word_size (1)
164
+ # The number of bytes within a word.
165
+ #
166
+ # @option options [Symbol, Integer] :base (:hexadecimal)
167
+ # The base to print bytes in. Supported bases include, `:hexadecimal`,
168
+ # `:hex`, `16, `:decimal`, `:dec`, `10, `:octal`, `:oct`, `8`,
169
+ # `:binary`, `:bin` and `2`.
170
+ #
171
+ # @option options [Boolean] :ascii (false)
172
+ # Print ascii characters when possible.
173
+ #
174
+ # @raise [ArgumentError]
175
+ # The values for `:base` or `:endian` were unknown.
176
+ #
177
+ # @since 0.2.0
178
+ #
179
+ def initialize(options={})
180
+ @base = case options[:base]
181
+ when :hexadecimal, :hex, 16
182
+ :hexadecimal
183
+ when :decimal, :dec, 10
184
+ :decimal
185
+ when :octal, :oct, 8
186
+ :octal
187
+ when :binary, :bin, 2
188
+ :binary
189
+ when nil
190
+ :hexadecimal
191
+ else
192
+ raise(ArgumentError,"unknown base #{options[:base].inspect}")
193
+ end
194
+
195
+ @word_size = options.fetch(:word_size,1)
196
+ @endian = case options[:endian]
197
+ when 'little', :little
198
+ :little
199
+ when 'big', :big
200
+ :big
201
+ when nil
202
+ :little
203
+ else
204
+ raise(ArgumentError,"unknown endian: #{options[:endian].inspect}")
205
+ end
206
+
207
+ @width = (options.fetch(:width,16) / @word_size)
208
+ @ascii = options.fetch(:ascii,false)
209
+
210
+ @format_width = (WIDTHS[@base][@word_size] || 1)
211
+ @format = FORMATS[@base][@format_width]
212
+
213
+ if @word_size == 1
214
+ @format_cache = Hash.new do |hash,key|
215
+ hash[key] = sprintf(@format,key)
216
+ end
217
+ end
218
+ end
219
+
220
+ #
221
+ # Iterates over every word within the data.
222
+ #
223
+ # @param [#each_byte] data
224
+ # The data containing bytes.
225
+ #
226
+ # @yield [word]
227
+ # The given block will be passed each word within the data.
228
+ #
229
+ # @yieldparam [Integer]
230
+ # An unpacked word from the data.
231
+ #
232
+ # @return [Enumerator]
233
+ # If no block is given, an Enumerator will be returned.
234
+ #
235
+ # @raise [ArgumentError]
236
+ # The given data does not define the `#each_byte` method.
237
+ #
238
+ # @since 0.2.0
239
+ #
240
+ def each_word(data,&block)
241
+ return enum_for(:each_word,data) unless block
242
+
243
+ unless data.respond_to?(:each_byte)
244
+ raise(ArgumentError,"the data to hexdump must define #each_byte")
245
+ end
246
+
247
+ if @word_size > 1
248
+ word = 0
249
+ count = 0
250
+
251
+ init_shift = if @endian == :big
252
+ ((@word_size - 1) * 8)
253
+ else
254
+ 0
255
+ end
256
+ shift = init_shift
257
+
258
+ data.each_byte do |b|
259
+ word |= (b << shift)
260
+
261
+ if @endian == :big
262
+ shift -= 8
263
+ else
264
+ shift += 8
265
+ end
266
+
267
+ count += 1
268
+
269
+ if count >= @word_size
270
+ yield word
271
+
272
+ word = 0
273
+ count = 0
274
+ shift = init_shift
275
+ end
276
+ end
277
+
278
+ # yield the remaining word
279
+ yield word if count > 0
280
+ else
281
+ data.each_byte(&block)
282
+ end
283
+ end
284
+
285
+ #
286
+ # Iterates over the hexdump.
287
+ #
288
+ # @param [#each_byte] data
289
+ # The data to be hexdumped.
290
+ #
291
+ # @yield [index,numeric,printable]
292
+ # The given block will be passed the hexdump break-down of each
293
+ # segment.
294
+ #
295
+ # @yieldparam [Integer] index
296
+ # The index of the hexdumped segment.
297
+ #
298
+ # @yieldparam [Array<String>] numeric
299
+ # The numeric representation of the segment.
300
+ #
301
+ # @yieldparam [Array<String>] printable
302
+ # The printable representation of the segment.
303
+ #
304
+ # @return [Enumerator]
305
+ # If no block is given, an Enumerator will be returned.
306
+ #
307
+ # @since 0.2.0
308
+ #
309
+ def each(data)
310
+ return enum_for(:each,data) unless block_given?
311
+
312
+ index = 0
313
+ count = 0
314
+
315
+ numeric = []
316
+ printable = []
317
+
318
+ each_word(data) do |word|
319
+ numeric << format_numeric(word)
320
+ printable << format_printable(word)
321
+
322
+ count += 1
323
+
324
+ if count >= @width
325
+ yield(index,numeric,printable)
326
+
327
+ numeric.clear
328
+ printable.clear
329
+
330
+ index += (@width * @word_size)
331
+ count = 0
332
+ end
333
+ end
334
+
335
+ if count > 0
336
+ # yield the remaining data
337
+ yield(index,numeric,printable)
338
+ end
339
+ end
340
+
341
+ #
342
+ # Dumps the hexdump.
343
+ #
344
+ # @param [#each_byte] data
345
+ # The data to be hexdumped.
346
+ #
347
+ # @param [#<<] output
348
+ # The output to dump the hexdump to.
349
+ #
350
+ # @return [nil]
351
+ #
352
+ # @raise [ArgumentError]
353
+ # The output value does not support the `#<<` method.
354
+ #
355
+ # @since 0.2.0
356
+ #
357
+ def dump(data,output=STDOUT)
358
+ unless output.respond_to?(:<<)
359
+ raise(ArgumentError,"output must support the #<< method")
360
+ end
361
+
362
+ bytes_segment_width = ((@width * @format_width) + @width)
363
+ line_format = "%.8x %-#{bytes_segment_width}s |%s|\n"
364
+
365
+ index = 0
366
+ count = 0
367
+
368
+ numeric = ''
369
+ printable = ''
370
+
371
+ each_word(data) do |word|
372
+ numeric << format_numeric(word) << ' '
373
+ printable << format_printable(word)
374
+
375
+ count += 1
376
+
377
+ if count >= @width
378
+ output << sprintf(line_format,index,numeric,printable)
379
+
380
+ numeric = ''
381
+ printable = ''
382
+
383
+ index += (@width * @word_size)
384
+ count = 0
385
+ end
386
+ end
387
+
388
+ if count > 0
389
+ # output the remaining line
390
+ output << sprintf(line_format,index,numeric,printable)
391
+ end
392
+ end
393
+
394
+ protected
395
+
396
+ #
397
+ # Converts the word into a numeric String.
398
+ #
399
+ # @param [Integer] word
400
+ # The word to convert.
401
+ #
402
+ # @return [String]
403
+ # The numeric representation of the word.
404
+ #
405
+ # @since 0.2.0
406
+ #
407
+ def format_numeric(word)
408
+ if @word_size == 1
409
+ if (@ascii && (word >= 0x20 && word <= 0x7e))
410
+ PRINTABLE[word]
411
+ else
412
+ @format_cache[word]
413
+ end
414
+ else
415
+ sprintf(@format,word)
416
+ end
417
+ end
418
+
419
+ #
420
+ # Converts a word into a printable String.
421
+ #
422
+ # @param [Integer] word
423
+ # The word to convert.
424
+ #
425
+ # @return [String]
426
+ # The printable representation of the word.
427
+ #
428
+ # @since 0.2.0
429
+ #
430
+ def format_printable(word)
431
+ if @word_size == 1
432
+ PRINTABLE[word]
433
+ elsif (RUBY_VERSION > '1.9.' && (word >= -2 && word <= 0x7fffffff))
434
+ word.chr(Encoding::UTF_8)
435
+ else
436
+ UNPRINTABLE
437
+ end
438
+ end
439
+
440
+ end
441
+ end
@@ -1,3 +1,5 @@
1
+ require 'hexdump/dumper'
2
+
1
3
  #
2
4
  # Provides the {Hexdump.dump} method and can add hexdumping to other classes
3
5
  # by including the {Hexdump} module.
@@ -28,6 +30,9 @@ module Hexdump
28
30
  # @option options [Integer] :width (16)
29
31
  # The number of bytes to dump for each line.
30
32
  #
33
+ # @option options [Integer] :word_size (1)
34
+ # The number of bytes within a word.
35
+ #
31
36
  # @option options [Symbol, Integer] :base (:hexadecimal)
32
37
  # The base to print bytes in. Supported bases include, `:hexadecimal`,
33
38
  # `:hex`, `16, `:decimal`, `:dec`, `10, `:octal`, `:oct`, `8`,
@@ -39,89 +44,36 @@ module Hexdump
39
44
  # @option options [#<<] :output (STDOUT)
40
45
  # The output to print the hexdump to.
41
46
  #
42
- # @yield [index,hex_segment,print_segment]
43
- # The given block will be passed the hexdump break-down of each segment.
47
+ # @yield [index,numeric,printable]
48
+ # The given block will be passed the hexdump break-down of each
49
+ # segment.
44
50
  #
45
51
  # @yieldparam [Integer] index
46
52
  # The index of the hexdumped segment.
47
53
  #
48
- # @yieldparam [Array<String>] hex_segment
49
- # The hexadecimal-byte representation of the segment.
54
+ # @yieldparam [Array<String>] numeric
55
+ # The numeric representation of the segment.
50
56
  #
51
- # @yieldparam [Array<String>] print_segment
52
- # The print-character representation of the segment.
57
+ # @yieldparam [Array<String>] printable
58
+ # The printable representation of the segment.
53
59
  #
54
60
  # @return [nil]
55
61
  #
56
62
  # @raise [ArgumentError]
57
- # The given data does not define the `#each_byte` method, or
58
- # the `:output` value does not support the `#<<` method.
63
+ # The given data does not define the `#each_byte` method,
64
+ # the `:output` value does not support the `#<<` method or
65
+ # the `:base` value was unknown.
59
66
  #
60
- def Hexdump.dump(data,options={})
61
- unless data.respond_to?(:each_byte)
62
- raise(ArgumentError,"the data to hexdump must define #each_byte")
63
- end
64
-
65
- output = options.fetch(:output,STDOUT)
66
-
67
- unless output.respond_to?(:<<)
68
- raise(ArgumentError,":output must support the #<< method")
69
- end
70
-
71
- width = options.fetch(:width,16)
72
- base = options.fetch(:base,:hexadecimal)
73
- ascii = options.fetch(:ascii,false)
74
- byte_width, byte_format = case base
75
- when :hexadecimal, :hex, 16
76
- [2, "%.2x"]
77
- when :decimal, :dec, 10
78
- [3, "%3.d"]
79
- when :octal, :oct, 8
80
- [4, "0%.3o"]
81
- when :binary, :bin, 2
82
- [8, "%.8b"]
83
- end
84
-
85
- hex_byte = lambda { |byte|
86
- if (ascii && (byte >= 0x20 && byte <= 0x7e))
87
- byte.chr
88
- else
89
- byte_format % byte
90
- end
91
- }
92
-
93
- print_byte = lambda { |byte|
94
- if (byte >= 0x20 && byte <= 0x7e)
95
- byte.chr
96
- else
97
- '.'
98
- end
99
- }
100
-
101
- index = 0
102
-
103
- hex_segment_width = ((width * byte_width) + (width - 1))
104
- line_format = "%.8x %-#{hex_segment_width}s |%s|\n"
105
-
106
- data.each_byte.each_slice(width) do |bytes|
107
- hex_segment = bytes.map(&hex_byte)
108
- print_segment = bytes.map(&print_byte)
109
-
110
- if block_given?
111
- yield(index,hex_segment,print_segment)
112
- else
113
- output << sprintf(
114
- line_format,
115
- index,
116
- hex_segment.join(' '),
117
- print_segment.join
118
- )
119
- end
67
+ def Hexdump.dump(data,options={},&block)
68
+ output = (options.delete(:output) || STDOUT)
69
+ dumper = Dumper.new(options)
120
70
 
121
- index += width
71
+ if block
72
+ dumper.each(data,&block)
73
+ else
74
+ dumper.dump(data,output)
122
75
  end
123
76
 
124
- # flush the hexdump buffer
125
77
  return nil
126
78
  end
127
79
 
@@ -0,0 +1,260 @@
1
+ require 'spec_helper'
2
+
3
+ require 'hexdump/dumper'
4
+
5
+ describe Hexdump::Dumper do
6
+ let(:bytes) { [104, 101, 108, 108, 111] }
7
+ let(:hex_chars) { ['68', '65', '6c', '6c', '6f'] }
8
+ let(:decimal_chars) { ['104', '101', '108', '108', '111'] }
9
+ let(:octal_chars) { ['150', '145', '154', '154', '157'] }
10
+ let(:binary_chars) { ['01101000', '01100101', '01101100', '01101100', '01101111'] }
11
+ let(:print_chars) { ['h', 'e', 'l', 'l', 'o'] }
12
+ let(:data) { print_chars.join }
13
+
14
+ it "should only accept known :base values" do
15
+ lambda {
16
+ described_class.new(data, :base => :foo)
17
+ }.should raise_error(ArgumentError)
18
+ end
19
+
20
+ it "should only accept known :endian values" do
21
+ lambda {
22
+ described_class.new(data, :endian => :foo)
23
+ }.should raise_error(ArgumentError)
24
+ end
25
+
26
+ describe "each_word" do
27
+ let(:data) { 'ABAB' }
28
+ let(:bytes) { [0x41, 0x42, 0x41, 0x42] }
29
+ let(:shorts_le) { [0x4241, 0x4241] }
30
+ let(:shorts_be) { [0x4142, 0x4142] }
31
+ let(:custom_words) { [0x414241, 0x42] }
32
+
33
+ it "should check if the data defines '#each_byte'" do
34
+ lambda {
35
+ subject.each_word(Object.new).to_a
36
+ }.should raise_error(ArgumentError)
37
+ end
38
+
39
+ it "should iterate over each byte by default" do
40
+ subject.each_word(data).to_a.should == bytes
41
+ end
42
+
43
+ it "should allow iterating over custom word-sizes" do
44
+ dumper = described_class.new(:word_size => 3)
45
+
46
+ dumper.each_word(data).to_a.should == custom_words
47
+ end
48
+
49
+ it "should iterate over little-endian words by default" do
50
+ dumper = described_class.new(:word_size => 2)
51
+
52
+ dumper.each_word(data).to_a.should == shorts_le
53
+ end
54
+
55
+ it "should iterate over big-endian words" do
56
+ dumper = described_class.new(:word_size => 2, :endian => :big)
57
+
58
+ dumper.each_word(data).to_a.should == shorts_be
59
+ end
60
+ end
61
+
62
+ describe "#each" do
63
+ it "should yield the parts of each hexdump line to the given block" do
64
+ lines = []
65
+
66
+ subject.each(data) do |index,hex,print|
67
+ lines << [index, hex, print]
68
+ end
69
+
70
+ lines.length.should == 1
71
+ lines[0][0].should == 0
72
+ lines[0][1].should == hex_chars
73
+ lines[0][2].should == print_chars
74
+ end
75
+
76
+ it "should provide the index within the data for each line" do
77
+ dumper = described_class.new(:width => 10)
78
+ indices = []
79
+
80
+ dumper.each('A' * 100) do |index,hex,print|
81
+ indices << index
82
+ end
83
+
84
+ indices.should == [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
85
+ end
86
+
87
+ it "should allow configuring the width, in bytes, of each line" do
88
+ dumper = described_class.new(:width => 10)
89
+ widths = []
90
+
91
+ dumper.each('A' * 100) do |index,hex,print|
92
+ widths << hex.length
93
+ end
94
+
95
+ widths.should == ([10] * 10)
96
+ end
97
+
98
+ it "should hexdump the remaining bytes" do
99
+ dumper = described_class.new(:width => 10)
100
+ chars = (['B'] * 4)
101
+ string = chars.join
102
+ leading = ('A' * 100)
103
+ remainder = nil
104
+
105
+ dumper.each(leading + string) do |index,hex,print|
106
+ remainder = print
107
+ end
108
+
109
+ remainder.should == chars
110
+ end
111
+
112
+ it "should provide the hexadecimal characters for each line" do
113
+ dumper = described_class.new(:width => 10)
114
+ chars = []
115
+
116
+ dumper.each(data * 100) do |index,hex,print|
117
+ chars += hex
118
+ end
119
+
120
+ chars.should == (hex_chars * 100)
121
+ end
122
+
123
+ it "should allow printing ASCII characters in place of hex characters" do
124
+ dumper = described_class.new(:ascii => true)
125
+ chars = []
126
+
127
+ dumper.each(data) do |index,hex,print|
128
+ chars += hex
129
+ end
130
+
131
+ chars.should == print_chars
132
+ end
133
+
134
+ it "should provide the print characters for each line" do
135
+ dumper = described_class.new(:width => 10)
136
+ chars = []
137
+
138
+ dumper.each(data * 100) do |index,hex,print|
139
+ chars += print
140
+ end
141
+
142
+ chars.should == (print_chars * 100)
143
+ end
144
+
145
+ it "should map unprintable characters to '.'" do
146
+ unprintable = ((0x00..0x1f).map(&:chr) + (0x7f..0xff).map(&:chr)).join
147
+ chars = []
148
+
149
+ subject.each(unprintable) do |index,hex,print|
150
+ chars += print
151
+ end
152
+
153
+ chars.should == (['.'] * unprintable.length)
154
+ end
155
+
156
+ it "should support dumping bytes in decimal format" do
157
+ dumper = described_class.new(:base => :decimal)
158
+ chars = []
159
+
160
+ dumper.each(data) do |index,hex,print|
161
+ chars += hex
162
+ end
163
+
164
+ chars.should == decimal_chars
165
+ end
166
+
167
+ it "should support dumping bytes in octal format" do
168
+ dumper = described_class.new(:base => :octal)
169
+ chars = []
170
+
171
+ dumper.each(data) do |index,hex,print|
172
+ chars += hex
173
+ end
174
+
175
+ chars.should == octal_chars
176
+ end
177
+
178
+ it "should support dumping bytes in binary format" do
179
+ dumper = described_class.new(:base => :binary)
180
+ chars = []
181
+
182
+ dumper.each(data) do |index,hex,print|
183
+ chars += hex
184
+ end
185
+
186
+ chars.should == binary_chars
187
+ end
188
+
189
+ context ":word_size" do
190
+ let(:options) { {:word_size => 2, :endian => :little} }
191
+
192
+ let(:hex_words) { ['6568', '6c6c', '006f'] }
193
+ let(:decimal_words) { ['25960', '27756', ' 111'] }
194
+ let(:octal_words) { ['062550', '066154', '000157'] }
195
+ let(:binary_words) { ['0110010101101000', '0110110001101100', '0000000001101111'] }
196
+
197
+ it "should dump words in hexadecimal" do
198
+ dumper = described_class.new(options)
199
+ words = []
200
+
201
+ dumper.each(data) do |index,hex,print|
202
+ words += hex
203
+ end
204
+
205
+ words.should == hex_words
206
+ end
207
+
208
+ it "should dump words in decimal" do
209
+ dumper = described_class.new(options.merge(:base => :decimal))
210
+ words = []
211
+
212
+ dumper.each(data) do |index,dec,print|
213
+ words += dec
214
+ end
215
+
216
+ words.should == decimal_words
217
+ end
218
+
219
+ it "should dump words in octal" do
220
+ dumper = described_class.new(options.merge(:base => :octal))
221
+ words = []
222
+
223
+ dumper.each(data) do |index,oct,print|
224
+ words += oct
225
+ end
226
+
227
+ words.should == octal_words
228
+ end
229
+
230
+ it "should dump words in binary" do
231
+ dumper = described_class.new(options.merge(:base => :binary))
232
+ words = []
233
+
234
+ dumper.each(data) do |index,bin,print|
235
+ words += bin
236
+ end
237
+
238
+ words.should == binary_words
239
+ end
240
+ end
241
+ end
242
+
243
+ describe "#dump" do
244
+ it "should check if the :output supports the '#<<' method" do
245
+ lambda {
246
+ subject.dump(data,Object.new)
247
+ }.should raise_error(ArgumentError)
248
+ end
249
+
250
+ it "should append each line of the hexdump to the output" do
251
+ lines = []
252
+
253
+ subject.dump(data,lines)
254
+
255
+ lines.length.should == 1
256
+ lines[0].should include(hex_chars.join(' '))
257
+ lines[0].should include(print_chars.join)
258
+ end
259
+ end
260
+ end
data/spec/hexdump_spec.rb CHANGED
@@ -1,159 +1,17 @@
1
1
  require 'spec_helper'
2
+
2
3
  require 'hexdump'
3
4
 
4
5
  describe Hexdump do
5
- let(:bytes) { [104, 101, 108, 108, 111] }
6
- let(:hex_chars) { ['68', '65', '6c', '6c', '6f'] }
7
- let(:decimal_chars) { ['104', '101', '108', '108', '111'] }
8
- let(:octal_chars) { ['0150', '0145', '0154', '0154', '0157'] }
9
- let(:binary_chars) { ['01101000', '01100101', '01101100', '01101100', '01101111'] }
10
- let(:print_chars) { ['h', 'e', 'l', 'l', 'o'] }
11
- let(:data) { print_chars.join }
12
-
13
- describe "dump" do
14
- it "should check if the data defines '#each_byte'" do
15
- lambda {
16
- subject.dump(Object.new)
17
- }.should raise_error(ArgumentError)
18
- end
19
-
20
- it "should check if the :output supports the '#<<' method" do
21
- lambda {
22
- subject.dump(data, :output => Object.new)
23
- }.should raise_error(ArgumentError)
24
- end
25
-
26
- it "should append each line of the hexdump to the output" do
27
- lines = []
28
- subject.dump(data, :output => lines)
29
-
30
- lines.length.should == 1
31
- lines[0].should include(hex_chars.join(' '))
32
- lines[0].should include(print_chars.join)
33
- end
34
-
35
- it "should yield the parts of each hexdump line to the given block" do
36
- lines = []
37
-
38
- subject.dump(data) do |index,hex,print|
39
- lines << [index, hex, print]
40
- end
41
-
42
- lines.length.should == 1
43
- lines[0][0].should == 0
44
- lines[0][1].should == hex_chars
45
- lines[0][2].should == print_chars
46
- end
47
-
48
- it "should provide the index within the data for each line" do
49
- indices = []
50
-
51
- subject.dump('A' * 100, :width => 10) do |index,hex,print|
52
- indices << index
53
- end
54
-
55
- indices.should == [0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
56
- end
57
-
58
- it "should allow configuring the width, in bytes, of each line" do
59
- widths = []
60
-
61
- subject.dump('A' * 100, :width => 10) do |index,hex,print|
62
- widths << hex.length
63
- end
64
-
65
- widths.should == ([10] * 10)
66
- end
67
-
68
- it "should hexdump the remaining bytes" do
69
- chars = (['B'] * 4)
70
- string = chars.join
71
- leading = ('A' * 100)
72
- remainder = nil
73
-
74
- subject.dump(leading + string, :width => 10) do |index,hex,print|
75
- remainder = print
76
- end
77
-
78
- remainder.should == chars
79
- end
80
-
81
- it "should provide the hexadecimal characters for each line" do
82
- chars = []
83
-
84
- subject.dump(data * 100, :width => 10) do |index,hex,print|
85
- chars += hex
86
- end
87
-
88
- chars.should == (hex_chars * 100)
89
- end
90
-
91
- it "should allow printing ASCII characters in place of hex characters" do
92
- chars = []
93
-
94
- subject.dump(data, :ascii => true) do |index,hex,print|
95
- chars += hex
96
- end
97
-
98
- chars.should == print_chars
99
- end
100
-
101
- it "should provide the print characters for each line" do
102
- chars = []
103
-
104
- subject.dump(data * 100, :width => 10) do |index,hex,print|
105
- chars += print
106
- end
107
-
108
- chars.should == (print_chars * 100)
109
- end
110
-
111
- it "should map unprintable characters to '.'" do
112
- unprintable = ((0x00..0x1f).map(&:chr) + (0x7f..0xff).map(&:chr)).join
113
- chars = []
114
-
115
- subject.dump(unprintable) do |index,hex,print|
116
- chars += print
117
- end
118
-
119
- chars.should == (['.'] * unprintable.length)
120
- end
121
-
122
- it "should support dumping bytes in decimal format" do
123
- chars = []
124
-
125
- subject.dump(data, :base => :decimal) do |index,hex,print|
126
- chars += hex
127
- end
128
-
129
- chars.should == decimal_chars
130
- end
131
-
132
- it "should support dumping bytes in octal format" do
133
- chars = []
134
-
135
- subject.dump(data, :base => :octal) do |index,hex,print|
136
- chars += hex
137
- end
138
-
139
- chars.should == octal_chars
140
- end
141
-
142
- it "should support dumping bytes in binary format" do
143
- chars = []
144
-
145
- subject.dump(data, :base => :binary) do |index,hex,print|
146
- chars += hex
147
- end
148
-
149
- chars.should == binary_chars
150
- end
151
- end
152
-
153
6
  describe "#hexdump" do
7
+ let(:bytes) { [104, 101, 108, 108, 111] }
8
+ let(:hex_chars) { ['68', '65', '6c', '6c', '6f'] }
9
+
154
10
  subject do
155
11
  obj = Object.new.extend(Hexdump)
156
- obj.stub!(:each_byte).and_return(bytes.enum_for(:each))
12
+
13
+ stub = obj.stub!(:each_byte)
14
+ bytes.each { |b| stub = stub.and_yield(b) }
157
15
 
158
16
  obj
159
17
  end
metadata CHANGED
@@ -2,16 +2,15 @@
2
2
  name: hexdump
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.0
5
+ version: 0.2.1
6
6
  platform: ruby
7
7
  authors:
8
- - hal
8
+ - Postmodern
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-03-05 00:00:00 -08:00
14
- default_executable:
13
+ date: 2011-06-11 00:00:00 Z
15
14
  dependencies:
16
15
  - !ruby/object:Gem::Dependency
17
16
  name: ore-tasks
@@ -70,16 +69,17 @@ files:
70
69
  - gemspec.yml
71
70
  - hexdump.gemspec
72
71
  - lib/hexdump.rb
72
+ - lib/hexdump/dumper.rb
73
73
  - lib/hexdump/extensions.rb
74
74
  - lib/hexdump/extensions/file.rb
75
75
  - lib/hexdump/extensions/io.rb
76
76
  - lib/hexdump/extensions/string.rb
77
77
  - lib/hexdump/extensions/string_io.rb
78
78
  - lib/hexdump/hexdump.rb
79
+ - spec/dumper_spec.rb
79
80
  - spec/extensions_spec.rb
80
81
  - spec/hexdump_spec.rb
81
82
  - spec/spec_helper.rb
82
- has_rdoc: yard
83
83
  homepage: http://github.com/postmodern/hexdump
84
84
  licenses:
85
85
  - MIT
@@ -93,7 +93,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: "0"
96
+ version: 1.8.7
97
97
  required_rubygems_version: !ruby/object:Gem::Requirement
98
98
  none: false
99
99
  requirements:
@@ -103,10 +103,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
103
  requirements: []
104
104
 
105
105
  rubyforge_project: hexdump
106
- rubygems_version: 1.5.2
106
+ rubygems_version: 1.8.1
107
107
  signing_key:
108
108
  specification_version: 3
109
- summary: Simple and Fast hexdumping for Ruby.
109
+ summary: Hexdump Strings and IO objects.
110
110
  test_files:
111
+ - spec/dumper_spec.rb
111
112
  - spec/hexdump_spec.rb
112
113
  - spec/extensions_spec.rb