hexdump 0.3.0 → 1.0.0

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.
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