hexdump 0.1.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
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