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.
- checksums.yaml +4 -4
- data/ChangeLog.md +68 -2
- data/Gemfile +1 -0
- data/README.md +486 -135
- data/benchmark.rb +29 -22
- data/lib/hexdump/chars.rb +46 -0
- data/lib/hexdump/core_ext/file.rb +68 -6
- data/lib/hexdump/core_ext/io.rb +2 -2
- data/lib/hexdump/core_ext/kernel.rb +7 -0
- data/lib/hexdump/core_ext/string.rb +2 -2
- data/lib/hexdump/core_ext/string_io.rb +2 -2
- data/lib/hexdump/core_ext.rb +1 -0
- data/lib/hexdump/format_string.rb +43 -0
- data/lib/hexdump/hexdump.rb +768 -75
- data/lib/hexdump/mixin.rb +198 -0
- data/lib/hexdump/module_methods.rb +132 -0
- data/lib/hexdump/numeric/binary.rb +55 -0
- data/lib/hexdump/numeric/char_or_int.rb +90 -0
- data/lib/hexdump/numeric/decimal.rb +56 -0
- data/lib/hexdump/numeric/exceptions.rb +11 -0
- data/lib/hexdump/numeric/hexadecimal.rb +59 -0
- data/lib/hexdump/numeric/octal.rb +55 -0
- data/lib/hexdump/numeric.rb +5 -0
- data/lib/hexdump/reader.rb +314 -0
- data/lib/hexdump/theme/ansi.rb +81 -0
- data/lib/hexdump/theme/rule.rb +159 -0
- data/lib/hexdump/theme.rb +61 -0
- data/lib/hexdump/type.rb +232 -0
- data/lib/hexdump/types.rb +108 -0
- data/lib/hexdump/version.rb +1 -1
- data/lib/hexdump.rb +12 -1
- data/spec/chars_spec.rb +76 -0
- data/spec/core_ext_spec.rb +10 -6
- data/spec/hexdump_class_spec.rb +1708 -0
- data/spec/hexdump_module_spec.rb +23 -0
- data/spec/mixin_spec.rb +37 -0
- data/spec/numeric/binary_spec.rb +239 -0
- data/spec/numeric/char_or_int_spec.rb +210 -0
- data/spec/numeric/decimal_spec.rb +317 -0
- data/spec/numeric/hexadecimal_spec.rb +320 -0
- data/spec/numeric/octal_spec.rb +239 -0
- data/spec/reader_spec.rb +863 -0
- data/spec/theme/ansi_spec.rb +242 -0
- data/spec/theme/rule_spec.rb +199 -0
- data/spec/theme_spec.rb +94 -0
- data/spec/type_spec.rb +317 -0
- data/spec/types_spec.rb +904 -0
- metadata +39 -10
- data/.gemtest +0 -0
- data/lib/hexdump/dumper.rb +0 -419
- data/spec/dumper_spec.rb +0 -329
- 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,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,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
|