hexdump 0.3.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/ChangeLog.md +68 -2
  3. data/Gemfile +1 -0
  4. data/README.md +486 -135
  5. data/benchmark.rb +29 -22
  6. data/lib/hexdump/chars.rb +46 -0
  7. data/lib/hexdump/core_ext/file.rb +68 -6
  8. data/lib/hexdump/core_ext/io.rb +2 -2
  9. data/lib/hexdump/core_ext/kernel.rb +7 -0
  10. data/lib/hexdump/core_ext/string.rb +2 -2
  11. data/lib/hexdump/core_ext/string_io.rb +2 -2
  12. data/lib/hexdump/core_ext.rb +1 -0
  13. data/lib/hexdump/format_string.rb +43 -0
  14. data/lib/hexdump/hexdump.rb +768 -75
  15. data/lib/hexdump/mixin.rb +198 -0
  16. data/lib/hexdump/module_methods.rb +132 -0
  17. data/lib/hexdump/numeric/binary.rb +55 -0
  18. data/lib/hexdump/numeric/char_or_int.rb +90 -0
  19. data/lib/hexdump/numeric/decimal.rb +56 -0
  20. data/lib/hexdump/numeric/exceptions.rb +11 -0
  21. data/lib/hexdump/numeric/hexadecimal.rb +59 -0
  22. data/lib/hexdump/numeric/octal.rb +55 -0
  23. data/lib/hexdump/numeric.rb +5 -0
  24. data/lib/hexdump/reader.rb +314 -0
  25. data/lib/hexdump/theme/ansi.rb +81 -0
  26. data/lib/hexdump/theme/rule.rb +159 -0
  27. data/lib/hexdump/theme.rb +61 -0
  28. data/lib/hexdump/type.rb +232 -0
  29. data/lib/hexdump/types.rb +108 -0
  30. data/lib/hexdump/version.rb +1 -1
  31. data/lib/hexdump.rb +12 -1
  32. data/spec/chars_spec.rb +76 -0
  33. data/spec/core_ext_spec.rb +10 -6
  34. data/spec/hexdump_class_spec.rb +1708 -0
  35. data/spec/hexdump_module_spec.rb +23 -0
  36. data/spec/mixin_spec.rb +37 -0
  37. data/spec/numeric/binary_spec.rb +239 -0
  38. data/spec/numeric/char_or_int_spec.rb +210 -0
  39. data/spec/numeric/decimal_spec.rb +317 -0
  40. data/spec/numeric/hexadecimal_spec.rb +320 -0
  41. data/spec/numeric/octal_spec.rb +239 -0
  42. data/spec/reader_spec.rb +863 -0
  43. data/spec/theme/ansi_spec.rb +242 -0
  44. data/spec/theme/rule_spec.rb +199 -0
  45. data/spec/theme_spec.rb +94 -0
  46. data/spec/type_spec.rb +317 -0
  47. data/spec/types_spec.rb +904 -0
  48. metadata +39 -10
  49. data/.gemtest +0 -0
  50. data/lib/hexdump/dumper.rb +0 -419
  51. data/spec/dumper_spec.rb +0 -329
  52. data/spec/hexdump_spec.rb +0 -30
@@ -0,0 +1,56 @@
1
+ require 'hexdump/format_string'
2
+
3
+ module Hexdump
4
+ module Numeric
5
+ #
6
+ # @api private
7
+ #
8
+ # @since 1.0.0
9
+ #
10
+ class Decimal < FormatString
11
+
12
+ INT_SIZE_TO_WIDTH = {
13
+ 1 => 3, # 0xff.to_s(10).length
14
+ 2 => 5, # 0xffff.to_s(10).length
15
+ 4 => 10, # 0xffffffff.to_s(10).length
16
+ 8 => 20 # 0xffffffffffffffff.to_s(10).length
17
+ }
18
+
19
+ FLOAT_SIZE_TO_WIDTH = {
20
+ 4 => 15,
21
+ 8 => 24
22
+ }
23
+
24
+ # @return [Integer]
25
+ attr_reader :width
26
+
27
+ #
28
+ # Initializes the decimal format.
29
+ #
30
+ # @param [Type:Int, Type::UInt, Type::Float] type
31
+ #
32
+ def initialize(type)
33
+ widths = case type
34
+ when Type::Float then FLOAT_SIZE_TO_WIDTH
35
+ else INT_SIZE_TO_WIDTH
36
+ end
37
+
38
+ @width = widths.fetch(type.size) do
39
+ raise(NotImplementedError,"type #{type} with unsupported size #{type.size}")
40
+ end
41
+
42
+ case type
43
+ when Type::Float
44
+ super("% #{@width}g"); @width += 1
45
+ else
46
+ if type.signed?
47
+ super("% #{@width}d"); @width += 1
48
+ else
49
+ super("%#{@width}d")
50
+ end
51
+ end
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,11 @@
1
+ module Hexdump
2
+ module Numeric
3
+ #
4
+ # @api private
5
+ #
6
+ # @since 1.0.0
7
+ #
8
+ class IncompatibleTypeError < TypeError
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,59 @@
1
+ require 'hexdump/format_string'
2
+
3
+ module Hexdump
4
+ module Numeric
5
+ #
6
+ # @api private
7
+ #
8
+ # @since 1.0.0
9
+ #
10
+ class Hexadecimal < FormatString
11
+
12
+ INT_SIZE_TO_WIDTH = {
13
+ 1 => 2, # 0xff.to_s(16).length
14
+ 2 => 4, # 0xffff.to_s(16).length
15
+ 4 => 8, # 0xffffffff.to_s(16).length
16
+ 8 => 16 # 0xffffffffffffffff.to_s(16).length
17
+ }
18
+
19
+ FLOAT_WIDTH = 20
20
+
21
+ # @return [Integer]
22
+ attr_reader :width
23
+
24
+ #
25
+ # Initializes the hexadecimal format.
26
+ #
27
+ # @param [Type::Int, Type::UInt, Type::Float] type
28
+ #
29
+ def initialize(type)
30
+ case type
31
+ when Type::Float
32
+ if RUBY_ENGINE == 'jruby'
33
+ # XXX: https://github.com/jruby/jruby/issues/5122
34
+ begin
35
+ "%a" % 1.0
36
+ rescue ArgumentError
37
+ raise(NotImplementedError,"jruby #{RUBY_ENGINE_VERSION} does not support the \"%a\" format string")
38
+ end
39
+ end
40
+
41
+ # NOTE: jruby does not currently support the %a format string
42
+ @width = FLOAT_WIDTH
43
+ super("% #{@width}a"); @width += 1
44
+ else
45
+ @width = INT_SIZE_TO_WIDTH.fetch(type.size) do
46
+ raise(NotImplementedError,"type #{type} with unsupported size #{type.size}")
47
+ end
48
+
49
+ if type.signed?
50
+ super("% .#{@width}x"); @width += 1
51
+ else
52
+ super("%.#{@width}x")
53
+ end
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,55 @@
1
+ require 'hexdump/numeric/exceptions'
2
+ require 'hexdump/format_string'
3
+
4
+ module Hexdump
5
+ module Numeric
6
+ #
7
+ # @api private
8
+ #
9
+ # @since 1.0.0
10
+ #
11
+ class Octal < FormatString
12
+
13
+ SIZE_TO_WIDTH = {
14
+ 1 => 3, # 0xff.to_s(7).length
15
+ 2 => 6, # 0xffff.to_s(7).length
16
+ 4 => 11, # 0xffffffff.to_s(7).length
17
+ 8 => 22 # 0xffffffffffffffff.to_s(7).length
18
+ }
19
+
20
+ # @return [Integer]
21
+ attr_reader :width
22
+
23
+ #
24
+ # Initializes the octal format.
25
+ #
26
+ # @param [Type::Int, Type::UInt] type
27
+ #
28
+ # @raise [NotImplementedError]
29
+ #
30
+ # @raise [IncompatibleTypeError]
31
+ #
32
+ # @raise [TypeError]
33
+ #
34
+ def initialize(type)
35
+ case type
36
+ when Type::Int, Type::UInt
37
+ @width = SIZE_TO_WIDTH.fetch(type.size) do
38
+ raise(NotImplementedError,"type #{type} with unsupported size #{type.size}")
39
+ end
40
+
41
+ if type.signed?
42
+ super("% .#{@width}o"); @width += 1
43
+ else
44
+ super("%.#{@width}o")
45
+ end
46
+ when Type::Float
47
+ raise(IncompatibleTypeError,"cannot format floating-point numbers in octal")
48
+ else
49
+ raise(TypeError,"unsupported type: #{type.inspect}")
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,5 @@
1
+ require 'hexdump/numeric/binary'
2
+ require 'hexdump/numeric/octal'
3
+ require 'hexdump/numeric/decimal'
4
+ require 'hexdump/numeric/hexadecimal'
5
+ require 'hexdump/numeric/char_or_int'
@@ -0,0 +1,314 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'hexdump/type'
4
+
5
+ module Hexdump
6
+ #
7
+ # @api private
8
+ #
9
+ # @since 1.0.0
10
+ #
11
+ class Reader
12
+
13
+ # The type to decode the data as.
14
+ #
15
+ # @return [Type]
16
+ attr_reader :type
17
+
18
+ # Controls whether to offset N number of bytes before starting to read data.
19
+ #
20
+ # @return [Integer, nil]
21
+ attr_reader :offset
22
+
23
+ # Controls control many bytes to read.
24
+ #
25
+ # @return [Integer, nil]
26
+ attr_reader :length
27
+
28
+ #
29
+ # Initializes the reader.
30
+ #
31
+ # @param [Type] type
32
+ # The type to decode the data as.
33
+ #
34
+ # @param [Integer, nil] offset
35
+ # Controls whether to offset N number of bytes before starting to read data.
36
+ #
37
+ # @param [Integer, nil] length
38
+ # Controls control many bytes to read.
39
+ #
40
+ # @param [Boolean] zero_pad
41
+ # Controls whether the remaining data will be padded with zeros.
42
+ #
43
+ def initialize(type, offset: nil, length: nil, zero_pad: false)
44
+ @type = type
45
+ @offset = offset
46
+ @length = length
47
+ @zero_pad = zero_pad
48
+ end
49
+
50
+ def zero_pad?
51
+ @zero_pad
52
+ end
53
+
54
+ #
55
+ # Reads each byte from the given data.
56
+ #
57
+ # @yield [byte]
58
+ #
59
+ # @yieldparam [Integer] byte
60
+ #
61
+ # @return [Enumerator]
62
+ #
63
+ # @raise [ArgumentError]
64
+ #
65
+ def each_byte(data)
66
+ return enum_for(__method__,data) unless block_given?
67
+
68
+ unless data.respond_to?(:each_byte)
69
+ raise(ArgumentError,"the given data must respond to #each_byte")
70
+ end
71
+
72
+ count = 0
73
+
74
+ data.each_byte do |b|
75
+ count += 1
76
+
77
+ # offset the first @offset number of bytes
78
+ if @offset.nil? || count > @offset
79
+ yield b
80
+ end
81
+
82
+ # stop reading after @length number of bytes
83
+ break if @length && count >= @length
84
+ end
85
+ end
86
+
87
+ #
88
+ # Reads each string of the same number of bytes as the {#type}'s
89
+ # {Type#size size}.
90
+ #
91
+ # @param [#each_byte] data
92
+ #
93
+ # @yield [slice]
94
+ #
95
+ # @yieldparam [String] slice
96
+ #
97
+ # @raise [ArgumentError]
98
+ #
99
+ def each_slice(data)
100
+ return enum_for(__method__,data) unless block_given?
101
+
102
+ unless data.respond_to?(:each_byte)
103
+ raise(ArgumentError,"the given data must respond to #each_byte")
104
+ end
105
+
106
+ count = 0
107
+
108
+ if @type.size == 1
109
+ each_byte(data) do |b|
110
+ yield b.chr
111
+ end
112
+ else
113
+ buffer = String.new("\0" * @type.size, capacity: @type.size,
114
+ encoding: Encoding::BINARY)
115
+ index = 0
116
+
117
+ each_byte(data) do |b|
118
+ buffer[index] = b.chr(Encoding::BINARY)
119
+ index += 1
120
+
121
+ if index >= @type.size
122
+ yield buffer.dup
123
+ index = 0
124
+ end
125
+ end
126
+
127
+ if index > 0
128
+ if @zero_pad
129
+ # zero pad the rest of the buffer
130
+ (index..(@type.size - 1)).each do |i|
131
+ buffer[i] = "\0"
132
+ end
133
+
134
+ yield buffer
135
+ else
136
+ # yield the reamining partial buffer
137
+ yield buffer[0,index]
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ #
144
+ # @param [#each_byte] data
145
+ #
146
+ # @yield [raw, uint]
147
+ #
148
+ # @yieldparam [String] raw
149
+ #
150
+ # @yieldparam [Integer, nil] uint
151
+ #
152
+ # @return [Enumerator]
153
+ #
154
+ # @raise [ArgumentError]
155
+ #
156
+ def each_uint(data,&block)
157
+ return enum_for(__method__,data) unless block_given?
158
+
159
+ unless data.respond_to?(:each_byte)
160
+ raise(ArgumentError,"the given data must respond to #each_byte")
161
+ end
162
+
163
+ if @type.size == 1
164
+ each_byte(data) do |b|
165
+ yield b.chr, b
166
+ end
167
+ else
168
+ pack_format = case @type.size
169
+ when 1
170
+ 'c'
171
+ when 2
172
+ case @type.endian
173
+ when :little then 'S<'
174
+ when :big then 'S>'
175
+ else
176
+ raise(TypeError,"unsupported endian #{@type.endian} for #{@type.name}")
177
+ end
178
+ when 4
179
+ case @type.endian
180
+ when :little then 'L<'
181
+ when :big then 'L>'
182
+ else
183
+ raise(TypeError,"unsupported endian #{@type.endian} for #{@type.name}")
184
+ end
185
+ when 8
186
+ case @type.endian
187
+ when :little then 'Q<'
188
+ when :big then 'Q>'
189
+ else
190
+ raise(TypeError,"unsupported endian #{@type.endian} for #{@type.name}")
191
+ end
192
+ else
193
+ raise(TypeError,"unsupported type size #{@type.size} for #{@type.name}")
194
+ end
195
+
196
+ each_slice(data) do |slice|
197
+ yield slice, slice.unpack1(pack_format)
198
+ end
199
+ end
200
+ end
201
+
202
+ #
203
+ # @param [#each_byte] data
204
+ #
205
+ # @yield [raw, int]
206
+ #
207
+ # @yieldparam [String] raw
208
+ #
209
+ # @yieldparam [Integer, nil] int
210
+ #
211
+ # @return [Enumerator]
212
+ #
213
+ def each_int(data)
214
+ return enum_for(__method__,data) unless block_given?
215
+
216
+ pack_format = case @type.size
217
+ when 1
218
+ 'c'
219
+ when 2
220
+ case @type.endian
221
+ when :little then 's<'
222
+ when :big then 's>'
223
+ else
224
+ raise(TypeError,"unsupported endian #{@type.endian} for #{@type.name}")
225
+ end
226
+ when 4
227
+ case @type.endian
228
+ when :little then 'l<'
229
+ when :big then 'l>'
230
+ else
231
+ raise(TypeError,"unsupported endian #{@type.endian} for #{@type.name}")
232
+ end
233
+ when 8
234
+ case @type.endian
235
+ when :little then 'q<'
236
+ when :big then 'q>'
237
+ else
238
+ raise(TypeError,"unsupported endian #{@type.endian} for #{@type.name}")
239
+ end
240
+ else
241
+ raise(TypeError,"unsupported type size #{@type.size} for #{@type.name}")
242
+ end
243
+
244
+ each_slice(data) do |slice|
245
+ yield slice, slice.unpack1(pack_format)
246
+ end
247
+ end
248
+
249
+ #
250
+ # @param [#each_byte] data
251
+ #
252
+ # @yield [raw, float]
253
+ #
254
+ # @yieldparam [String] raw
255
+ #
256
+ # @yieldparam [Float, nil] float
257
+ #
258
+ # @return [Enumerator]
259
+ #
260
+ def each_float(data)
261
+ return enum_for(__method__,data) unless block_given?
262
+
263
+ pack_format = case @type.endian
264
+ when :little
265
+ case @type.size
266
+ when 4 then 'e'
267
+ when 8 then 'E'
268
+ else
269
+ raise(TypeError,"unsupported type size #{@type.size} for #{@type.name}")
270
+ end
271
+ when :big
272
+ case @type.size
273
+ when 4 then 'g'
274
+ when 8 then 'G'
275
+ else
276
+ raise(TypeError,"unsupported type size #{@type.size} for #{@type.name}")
277
+ end
278
+ else
279
+ raise(TypeError,"unsupported endian #{@type.endian} for #{@type.name}")
280
+ end
281
+
282
+ each_slice(data) do |slice|
283
+ yield slice, slice.unpack1(pack_format)
284
+ end
285
+ end
286
+
287
+ #
288
+ # @param [#each_byte] data
289
+ #
290
+ # @yield [raw, value]
291
+ #
292
+ # @yieldparam [String] raw
293
+ #
294
+ # @yieldparam [Integer, Float, nil] value
295
+ #
296
+ # @return [Enumerator]
297
+ #
298
+ def each(data,&block)
299
+ return enum_for(__method__,data) unless block
300
+
301
+ case @type
302
+ when Type::UInt
303
+ each_uint(data,&block)
304
+ when Type::Float
305
+ each_float(data,&block)
306
+ when Type::Int
307
+ each_int(data,&block)
308
+ else
309
+ raise(TypeError,"unsupported type: #{@type.inspect}")
310
+ end
311
+ end
312
+
313
+ end
314
+ end