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,108 @@
1
+ require 'hexdump/type'
2
+
3
+ module Hexdump
4
+ #
5
+ # @api private
6
+ #
7
+ # @since 1.0.0
8
+ #
9
+ TYPES = {
10
+ char: Type::Char.new,
11
+ uchar: Type::UChar.new,
12
+
13
+ int8: Type::Int8.new,
14
+ uint8: Type::UInt8.new,
15
+
16
+ int16: Type::Int16.new,
17
+ int16_le: Type::Int16.new(endian: :little),
18
+ int16_be: Type::Int16.new(endian: :big),
19
+ int16_ne: Type::Int16.new(endian: :big),
20
+
21
+ uint16: Type::UInt16.new,
22
+ uint16_le: Type::UInt16.new(endian: :little),
23
+ uint16_be: Type::UInt16.new(endian: :big),
24
+ uint16_ne: Type::UInt16.new(endian: :big),
25
+
26
+ int32: Type::Int32.new,
27
+ int32_le: Type::Int32.new(endian: :little),
28
+ int32_be: Type::Int32.new(endian: :big),
29
+ int32_ne: Type::Int32.new(endian: :big),
30
+
31
+ uint32: Type::UInt32.new,
32
+ uint32_le: Type::UInt32.new(endian: :little),
33
+ uint32_be: Type::UInt32.new(endian: :big),
34
+ uint32_ne: Type::UInt32.new(endian: :big),
35
+
36
+ int64: Type::Int64.new,
37
+ int64_le: Type::Int64.new(endian: :little),
38
+ int64_be: Type::Int64.new(endian: :big),
39
+ int64_ne: Type::Int64.new(endian: :big),
40
+
41
+ uint64: Type::UInt64.new,
42
+ uint64_le: Type::UInt64.new(endian: :little),
43
+ uint64_be: Type::UInt64.new(endian: :big),
44
+ uint64_ne: Type::UInt64.new(endian: :big),
45
+
46
+ float32: Type::Float32.new,
47
+ float32_le: Type::Float32.new(endian: :little),
48
+ float32_be: Type::Float32.new(endian: :big),
49
+ float32_ne: Type::Float32.new(endian: :big),
50
+
51
+ float64: Type::Float64.new,
52
+ float64_le: Type::Float64.new(endian: :little),
53
+ float64_be: Type::Float64.new(endian: :big),
54
+ float64_ne: Type::Float64.new(endian: :big),
55
+ }
56
+
57
+ TYPES[:byte] = TYPES[:uint8]
58
+
59
+ TYPES[:short] = TYPES[:int16]
60
+ TYPES[:short_le] = TYPES[:int16_le]
61
+ TYPES[:short_be] = TYPES[:int16_be]
62
+ TYPES[:short_ne] = TYPES[:int16_ne]
63
+
64
+ TYPES[:ushort] = TYPES[:uint16]
65
+ TYPES[:ushort_le] = TYPES[:uint16_le]
66
+ TYPES[:ushort_be] = TYPES[:uint16_be]
67
+ TYPES[:ushort_ne] = TYPES[:uint16_ne]
68
+
69
+ TYPES[:int] = TYPES[:int32]
70
+ TYPES[:int_le] = TYPES[:int32_le]
71
+ TYPES[:int_be] = TYPES[:int32_be]
72
+ TYPES[:int_ne] = TYPES[:int32_ne]
73
+
74
+ TYPES[:uint] = TYPES[:uint32]
75
+ TYPES[:uint_le] = TYPES[:uint32_le]
76
+ TYPES[:uint_be] = TYPES[:uint32_be]
77
+ TYPES[:uint_ne] = TYPES[:uint32_ne]
78
+
79
+ TYPES[:long] = TYPES[:int32]
80
+ TYPES[:long_le] = TYPES[:int32_le]
81
+ TYPES[:long_be] = TYPES[:int32_be]
82
+ TYPES[:long_ne] = TYPES[:int32_ne]
83
+
84
+ TYPES[:ulong] = TYPES[:uint32]
85
+ TYPES[:ulong_le] = TYPES[:uint32_le]
86
+ TYPES[:ulong_be] = TYPES[:uint32_be]
87
+ TYPES[:ulong_ne] = TYPES[:uint32_ne]
88
+
89
+ TYPES[:long_long] = TYPES[:int64]
90
+ TYPES[:long_long_le] = TYPES[:int64_le]
91
+ TYPES[:long_long_be] = TYPES[:int64_be]
92
+ TYPES[:long_long_ne] = TYPES[:int64_ne]
93
+
94
+ TYPES[:ulong_long] = TYPES[:uint64]
95
+ TYPES[:ulong_long_le] = TYPES[:uint64_le]
96
+ TYPES[:ulong_long_be] = TYPES[:uint64_be]
97
+ TYPES[:ulong_long_ne] = TYPES[:uint64_ne]
98
+
99
+ TYPES[:float] = TYPES[:float32]
100
+ TYPES[:float_le] = TYPES[:float32_le]
101
+ TYPES[:float_be] = TYPES[:float32_be]
102
+ TYPES[:float_ne] = TYPES[:float32_ne]
103
+
104
+ TYPES[:double] = TYPES[:float64]
105
+ TYPES[:double_le] = TYPES[:float64_le]
106
+ TYPES[:double_be] = TYPES[:float64_be]
107
+ TYPES[:double_ne] = TYPES[:float64_ne]
108
+ end
@@ -1,4 +1,4 @@
1
1
  module Hexdump
2
2
  # hexdump version
3
- VERSION = '0.3.0'
3
+ VERSION = '1.0.0'
4
4
  end
data/lib/hexdump.rb CHANGED
@@ -1,3 +1,14 @@
1
- require 'hexdump/hexdump'
1
+ require 'hexdump/module_methods'
2
2
  require 'hexdump/core_ext'
3
3
  require 'hexdump/version'
4
+
5
+ module Hexdump
6
+ extend ModuleMethods
7
+
8
+ #
9
+ # @see hexdump
10
+ #
11
+ def self.dump(data,**kwargs)
12
+ hexdump(data,**kwargs)
13
+ end
14
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+ require 'hexdump/chars'
3
+
4
+ describe Hexdump::Chars do
5
+ describe "#initialize" do
6
+ it "must default #encoding to nil" do
7
+ expect(subject.encoding).to be(nil)
8
+ end
9
+
10
+ context "when given nil" do
11
+ subject { described_class.new(nil) }
12
+
13
+ it "must set #encoding to nil" do
14
+ expect(subject.encoding).to be(nil)
15
+ end
16
+ end
17
+
18
+ context "when given an Encoding object" do
19
+ let(:encoding) { Encoding::UTF_8 }
20
+
21
+ subject { described_class.new(encoding) }
22
+
23
+ it "must set #encoding to the Encoding object" do
24
+ expect(subject.encoding).to be(encoding)
25
+ end
26
+ end
27
+ end
28
+
29
+ describe "#scrub" do
30
+ context "when the string only contains printable ASCII characters" do
31
+ let(:string) { "hello" }
32
+
33
+ it "must return the string unchanged" do
34
+ expect(subject.scrub(string)).to eq(string)
35
+ end
36
+ end
37
+
38
+ context "when the string contains unprintable ASCII characters" do
39
+ let(:ascii) { (0..255).map(&:chr).join }
40
+
41
+ it "must replace non-printable ASCII characters with a '.'" do
42
+ expect(subject.scrub(ascii)).to eq(ascii.gsub(/[^\x20-\x7e]/,'.'))
43
+ end
44
+ end
45
+
46
+ context "#encoding is set" do
47
+ let(:encoding) { Encoding::UTF_8 }
48
+ let(:string) { "hello" }
49
+
50
+ subject { described_class.new(encoding) }
51
+
52
+ it "must convert the string to the #encoding" do
53
+ expect((subject.scrub(string)).encoding).to eq(encoding)
54
+ end
55
+
56
+ context "when the string contains an invalid byte-sequence" do
57
+ let(:invalid_bytes) { "\x80\x81" }
58
+ let(:string) { "A#{invalid_bytes}B" }
59
+
60
+ it "must replace any invalid byte sequencnes with a '.'" do
61
+ expect(subject.scrub(string)).to eq("A..B")
62
+ end
63
+ end
64
+
65
+ context "when the string contains unprintable characters" do
66
+ let(:codepoint) { 888 }
67
+ let(:char) { codepoint.chr(encoding) }
68
+ let(:string) { "A#{char}B" }
69
+
70
+ it "must replace unprintable characters with a '.'" do
71
+ expect(subject.scrub(string)).to eq("A.B")
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -2,19 +2,23 @@ require 'spec_helper'
2
2
  require 'hexdump/core_ext'
3
3
 
4
4
  describe "Hexdump core_ext" do
5
- it "should include Hexdump into String" do
6
- expect(String).to include(Hexdump)
5
+ it "should include Hexdump::Mixin into String" do
6
+ expect(String).to include(Hexdump::Mixin)
7
7
  end
8
8
 
9
- it "should include Hexdump into StringIO" do
10
- expect(StringIO).to include(Hexdump)
9
+ it "should include Hexdump::Mixin into StringIO" do
10
+ expect(StringIO).to include(Hexdump::Mixin)
11
11
  end
12
12
 
13
- it "should include Hexdump into IO" do
14
- expect(IO).to include(Hexdump)
13
+ it "should include Hexdump::Mixin into IO" do
14
+ expect(IO).to include(Hexdump::Mixin)
15
15
  end
16
16
 
17
17
  it "should define File.hexdump" do
18
18
  expect(File).to respond_to(:hexdump)
19
19
  end
20
+
21
+ it "should include Hexdump::ModuleMethods into ::Kernel" do
22
+ expect(::Kernel).to include(Hexdump::ModuleMethods)
23
+ end
20
24
  end
@@ -0,0 +1,1708 @@
1
+ require 'spec_helper'
2
+ require 'hexdump/hexdump'
3
+
4
+ describe Hexdump::Hexdump do
5
+ describe "#initialize" do
6
+ it "must default type to :byte" do
7
+ expect(subject.type).to be(Hexdump::TYPES[:byte])
8
+ end
9
+
10
+ it "must default #columns to 16" do
11
+ expect(subject.columns).to eq(16)
12
+ end
13
+
14
+ it "must default #group_columns to nil" do
15
+ expect(subject.group_columns).to be(nil)
16
+ end
17
+
18
+ it "must default #base to 16" do
19
+ expect(subject.base).to eq(16)
20
+ end
21
+
22
+ it "must initialize #index to Hexdump::Numeric::Hexdecimal by default" do
23
+ expect(subject.index).to be_kind_of(Hexdump::Numeric::Hexadecimal)
24
+ end
25
+
26
+ it "must default #index_offset to nil" do
27
+ expect(subject.index_offset).to be(nil)
28
+ end
29
+
30
+ it "must initialize #numeric to a Hexdump::Numeric::Hexadecimal" do
31
+ expect(subject.numeric).to be_kind_of(Hexdump::Numeric::Hexadecimal)
32
+ end
33
+
34
+ it "must initialize the #reader with zero-padding disabled by default" do
35
+ expect(subject.reader.zero_pad?).to be(false)
36
+ end
37
+
38
+ it "must initialize #chars by default" do
39
+ expect(subject.chars).to be_kind_of(Hexdump::Chars)
40
+ end
41
+
42
+ context "when given the chars_column: true" do
43
+ subject { described_class.new(chars_column: true) }
44
+
45
+ it "must set #chars_column to true" do
46
+ expect(subject.chars_column).to be(true)
47
+ end
48
+
49
+ it "must initialize #chars with #encoding" do
50
+ expect(subject.chars).to be_kind_of(Hexdump::Chars)
51
+ end
52
+
53
+ context "and encoding: keyword" do
54
+ let(:encoding) { :utf8 }
55
+
56
+ subject { described_class.new(chars_column: true, encoding: encoding) }
57
+
58
+ it "must set #chars.encoding to the given encoding: value" do
59
+ expect(subject.chars.encoding).to be(subject.encoding)
60
+ end
61
+ end
62
+ end
63
+
64
+ context "when given chars_column: false" do
65
+ subject { described_class.new(chars_column: false) }
66
+
67
+ it "must set #chars_column to false" do
68
+ expect(subject.chars_column).to be(false)
69
+ end
70
+
71
+ it "must set #chars to nil" do
72
+ expect(subject.chars).to be(nil)
73
+ end
74
+ end
75
+
76
+ context "when given the encoding: keyword" do
77
+ subject { described_class.new(encoding: :utf8) }
78
+
79
+ it "must set #encoding" do
80
+ expect(subject.encoding).to eq(Encoding::UTF_8)
81
+ end
82
+ end
83
+
84
+ context "when given the offset: keyword" do
85
+ let(:offset) { 4 }
86
+
87
+ subject { described_class.new(offset: offset) }
88
+
89
+ it "must initialize the #reader with the offset: keyword" do
90
+ expect(subject.reader.offset).to eq(offset)
91
+ end
92
+
93
+ it "must also set #index_offset" do
94
+ expect(subject.index_offset).to eq(offset)
95
+ end
96
+ end
97
+
98
+ context "when given the length: keyword" do
99
+ let(:length) { 1024 }
100
+
101
+ subject { described_class.new(length: length) }
102
+
103
+ it "must initialize the #reader with the length: keyword" do
104
+ expect(subject.reader.length).to eq(length)
105
+ end
106
+ end
107
+
108
+ context "when given zero_pad: true" do
109
+ subject { described_class.new(zero_pad: true) }
110
+
111
+ it "must initialize the #reader with zero-padding enabled" do
112
+ expect(subject.reader.zero_pad?).to be(true)
113
+ end
114
+ end
115
+
116
+ context "when given a type: keyword" do
117
+ let(:type) { :uint16_le }
118
+
119
+ subject { described_class.new(type: type) }
120
+
121
+ it "must look up the given type in Hexdump::TYPES" do
122
+ expect(subject.type).to be(Hexdump::TYPES[type])
123
+ end
124
+
125
+ it "must divide the number of columns by the size of the type" do
126
+ expect(subject.columns).to eq(16 / Hexdump::TYPES[type].size)
127
+ end
128
+
129
+ context "when initialized with a type: :char" do
130
+ subject { described_class.new(type: :char) }
131
+
132
+ it "must set #type to Hexdump::Type::Char" do
133
+ expect(subject.type).to be_kind_of(Hexdump::Type::Char)
134
+ end
135
+
136
+ it "must initialize #numeric to Hexdump::Numeric::CharOrInt" do
137
+ expect(subject.numeric).to be_kind_of(Hexdump::Numeric::CharOrInt)
138
+ end
139
+
140
+ it "must disable #chars" do
141
+ expect(subject.chars).to be(nil)
142
+ end
143
+ end
144
+
145
+ context "when with type: is :uchar" do
146
+ let(:type) { :uchar }
147
+
148
+ it "must initialize #numeric to Hexdump::Numeric::CharOrInt" do
149
+ expect(subject.numeric).to be_kind_of(Hexdump::Numeric::CharOrInt)
150
+ end
151
+ end
152
+
153
+ context "when the type: is :byte" do
154
+ let(:type) { :byte }
155
+
156
+ it "must set #type to Hexdump::Type::UInt8" do
157
+ expect(subject.type).to be_kind_of(Hexdump::Type::UInt8)
158
+ end
159
+ end
160
+
161
+ context "when the type: is :uint8" do
162
+ let(:type) { :uint8 }
163
+
164
+ it "must set #type to Hexdump::Type::UInt8" do
165
+ expect(subject.type).to be_kind_of(Hexdump::Type::UInt8)
166
+ end
167
+ end
168
+
169
+ context "when the type: is :int8" do
170
+ let(:type) { :int8 }
171
+
172
+ it "must set #type to Hexdump::Type::Int8" do
173
+ expect(subject.type).to be_kind_of(Hexdump::Type::Int8)
174
+ end
175
+ end
176
+
177
+ context "when the type: is :uint16" do
178
+ let(:type) { :uint16 }
179
+
180
+ it "must set #type to Hexdump::Type::UInt16" do
181
+ expect(subject.type).to be_kind_of(Hexdump::Type::UInt16)
182
+ end
183
+ end
184
+
185
+ context "when the type: is :int16" do
186
+ let(:type) { :int16 }
187
+
188
+ it "must set #type to Hexdump::Type::Int16" do
189
+ expect(subject.type).to be_kind_of(Hexdump::Type::Int16)
190
+ end
191
+ end
192
+
193
+ context "when the type: is :uint32" do
194
+ let(:type) { :uint32 }
195
+
196
+ it "must set #type to Hexdump::Type::UInt32" do
197
+ expect(subject.type).to be_kind_of(Hexdump::Type::UInt32)
198
+ end
199
+ end
200
+
201
+ context "when the type: is :int32" do
202
+ let(:type) { :int32 }
203
+
204
+ it "must set #type to Hexdump::Type::Int32" do
205
+ expect(subject.type).to be_kind_of(Hexdump::Type::Int32)
206
+ end
207
+ end
208
+
209
+ context "when the type: is :uint64" do
210
+ let(:type) { :uint64 }
211
+
212
+ it "must set #type to Hexdump::Type::UInt64" do
213
+ expect(subject.type).to be_kind_of(Hexdump::Type::UInt64)
214
+ end
215
+ end
216
+
217
+ context "when the type: is :int64" do
218
+ let(:type) { :int64 }
219
+
220
+ it "must set #type to Hexdump::Type::Int64" do
221
+ expect(subject.type).to be_kind_of(Hexdump::Type::Int64)
222
+ end
223
+ end
224
+
225
+ context "and the type is :float, :float32, :float64, or :double" do
226
+ let(:type) { :float }
227
+
228
+ it "must set #type to Hexdump::Type::Float" do
229
+ expect(subject.type).to be_kind_of(Hexdump::Type::Float)
230
+ end
231
+
232
+ it "must default the #base to 10" do
233
+ expect(subject.base).to eq(10)
234
+ end
235
+
236
+ context "but the base: value isn't 10 or 16" do
237
+ it do
238
+ expect {
239
+ described_class.new(type: type, base: 8)
240
+ }.to raise_error(Hexdump::Numeric::IncompatibleTypeError)
241
+ end
242
+ end
243
+ end
244
+
245
+ context "when given an unsupported type" do
246
+ it do
247
+ expect {
248
+ described_class.new(type: :foo)
249
+ }.to raise_error(ArgumentError,"unsupported type: :foo")
250
+ end
251
+ end
252
+ end
253
+
254
+ context "when given base: keyword" do
255
+ subject { described_class.new(base: base) }
256
+
257
+ context "when the base: is 16" do
258
+ let(:base) { 16 }
259
+
260
+ it "must initialize #numeric to Hexdump::Numeric::Hexdecimal" do
261
+ expect(subject.numeric).to be_kind_of(Hexdump::Numeric::Hexadecimal)
262
+ end
263
+ end
264
+
265
+ context "when the base: is 10" do
266
+ let(:base) { 10 }
267
+
268
+ it "must initialize #numeric to Hexdump::Numeric::Decimal" do
269
+ expect(subject.numeric).to be_kind_of(Hexdump::Numeric::Decimal)
270
+ end
271
+ end
272
+
273
+ context "when given base: 8" do
274
+ let(:base) { 8 }
275
+
276
+ it "must initialize #numeric to Hexdump::Numeric::Octal" do
277
+ expect(subject.numeric).to be_kind_of(Hexdump::Numeric::Octal)
278
+ end
279
+ end
280
+
281
+ context "when given base: 2" do
282
+ let(:base) { 2 }
283
+
284
+ it "must initialize #numeric to Hexdump::Numeric::Binary" do
285
+ expect(subject.numeric).to be_kind_of(Hexdump::Numeric::Binary)
286
+ end
287
+ end
288
+ end
289
+
290
+ context "when given index_base: keyword" do
291
+ subject { described_class.new(index_base: index_base) }
292
+
293
+ context "when the index: is 16" do
294
+ let(:index_base) { 16 }
295
+
296
+ it "must initialize #index to Hexdump::Numeric::Hexdecimal" do
297
+ expect(subject.index).to be_kind_of(Hexdump::Numeric::Hexadecimal)
298
+ end
299
+ end
300
+
301
+ context "when the index: is 10" do
302
+ let(:index_base) { 10 }
303
+
304
+ it "must initialize #index to Hexdump::Numeric::Decimal" do
305
+ expect(subject.index).to be_kind_of(Hexdump::Numeric::Decimal)
306
+ end
307
+ end
308
+
309
+ context "when given index: 8" do
310
+ let(:index_base) { 8 }
311
+
312
+ it "must initialize #index to Hexdump::Numeric::Octal" do
313
+ expect(subject.index).to be_kind_of(Hexdump::Numeric::Octal)
314
+ end
315
+ end
316
+
317
+ context "when given index: 2" do
318
+ let(:index_base) { 2 }
319
+
320
+ it "must initialize #index to Hexdump::Numeric::Binary" do
321
+ expect(subject.index).to be_kind_of(Hexdump::Numeric::Binary)
322
+ end
323
+ end
324
+ end
325
+
326
+ context "when given the index_offset: keyword" do
327
+ let(:index_offset) { 1024 }
328
+
329
+ subject { described_class.new(index_offset: index_offset) }
330
+
331
+ it "must set #index_offset" do
332
+ expect(subject.index_offset).to eq(index_offset)
333
+ end
334
+
335
+ context "but the offset: keyword is also given" do
336
+ let(:offset) { 4 }
337
+
338
+ subject do
339
+ described_class.new(
340
+ offset: offset,
341
+ index_offset: index_offset
342
+ )
343
+ end
344
+
345
+ it "must set #index_offset to the index_offset: value" do
346
+ expect(subject.index_offset).to eq(index_offset)
347
+ end
348
+ end
349
+ end
350
+
351
+ context "when given the columns: keyword" do
352
+ let(:columns) { 7 }
353
+
354
+ subject { described_class.new(columns: columns) }
355
+
356
+ it "must set #columns" do
357
+ expect(subject.columns).to eq(columns)
358
+ end
359
+ end
360
+
361
+ context "when given the group_columns: keyword" do
362
+ let(:group_columns) { 4 }
363
+
364
+ subject { described_class.new(group_columns: group_columns) }
365
+
366
+ it "must set #group_columns" do
367
+ expect(subject.group_columns).to eq(group_columns)
368
+ end
369
+ end
370
+
371
+ context "when given the group_chars: keyword" do
372
+ let(:group_chars) { 4 }
373
+
374
+ subject { described_class.new(group_chars: group_chars) }
375
+
376
+ it "must set #group_chars" do
377
+ expect(subject.group_chars).to eq(group_chars)
378
+ end
379
+
380
+ context "when group_chars: :type is given" do
381
+ let(:type) { :uint16 }
382
+
383
+ subject { described_class.new(type: type, group_chars: :type) }
384
+
385
+ it "must set #group_chars to #type.size" do
386
+ expect(subject.group_chars).to eq(subject.type.size)
387
+ end
388
+ end
389
+ end
390
+
391
+ context "when given an unsupported base: value" do
392
+ it do
393
+ expect {
394
+ described_class.new(base: 3)
395
+ }.to raise_error(ArgumentError,"unsupported base: 3")
396
+ end
397
+ end
398
+
399
+ it "must default #theme to nil" do
400
+ expect(subject.theme).to be(nil)
401
+ end
402
+
403
+ context "when given the style: keyword" do
404
+ context "and is a Hash" do
405
+ let(:index_style) { :white }
406
+ let(:numeric_style) { :cyan }
407
+ let(:chars_style) { :blue }
408
+
409
+ subject do
410
+ described_class.new(
411
+ style: {
412
+ index: index_style,
413
+ numeric: numeric_style,
414
+ chars: chars_style
415
+ }
416
+ )
417
+ end
418
+
419
+ it "must initialize #style" do
420
+ expect(subject.theme).to be_kind_of(Hexdump::Theme)
421
+ end
422
+
423
+ it "must initialize #style.index" do
424
+ expect(subject.theme.index).to be_kind_of(Hexdump::Theme::Rule)
425
+ end
426
+
427
+ it "must populate #style.index.theme" do
428
+ expect(subject.theme.index.style.parameters).to eq(index_style)
429
+ end
430
+
431
+ it "must initialize #style.numeric" do
432
+ expect(subject.theme.numeric).to be_kind_of(Hexdump::Theme::Rule)
433
+ end
434
+
435
+ it "must populate #style.numeric.theme" do
436
+ expect(subject.theme.numeric.style.parameters).to eq(numeric_style)
437
+ end
438
+
439
+ it "must initialize #style.chars" do
440
+ expect(subject.theme.chars).to be_kind_of(Hexdump::Theme::Rule)
441
+ end
442
+
443
+ it "must populate #style.chars.theme" do
444
+ expect(subject.theme.chars.style.parameters).to eq(chars_style)
445
+ end
446
+ end
447
+ end
448
+
449
+ context "when given the highlights: keyword" do
450
+ context "and is a Hash" do
451
+ let(:index_pattern) { /00$/ }
452
+ let(:index_highlight) { :white }
453
+ let(:index_highlights) { {index_pattern => index_highlight} }
454
+
455
+ let(:numeric_pattern) { '00' }
456
+ let(:numeric_highlight) { :faint }
457
+ let(:numeric_highlights) { {numeric_pattern => numeric_highlight} }
458
+
459
+ let(:chars_pattern) { /A-Z/ }
460
+ let(:chars_highlight) { :bold }
461
+ let(:chars_highlights) { {chars_pattern => chars_highlight} }
462
+
463
+ subject do
464
+ described_class.new(
465
+ highlights: {
466
+ index: index_highlights,
467
+ numeric: numeric_highlights,
468
+ chars: chars_highlights
469
+ }
470
+ )
471
+ end
472
+
473
+ it "must initialize #style" do
474
+ expect(subject.theme).to be_kind_of(Hexdump::Theme)
475
+ end
476
+
477
+ it "must initialize #style.index" do
478
+ expect(subject.theme.index).to be_kind_of(Hexdump::Theme::Rule)
479
+ end
480
+
481
+ it "must populate #style.index.highlights" do
482
+ expect(subject.theme.index.highlights[index_pattern].parameters).to eq(index_highlight)
483
+ end
484
+
485
+ it "must initialize #style.numeric" do
486
+ expect(subject.theme.numeric).to be_kind_of(Hexdump::Theme::Rule)
487
+ end
488
+
489
+ it "must populate #style.numeric.highlights" do
490
+ expect(subject.theme.numeric.highlights[numeric_pattern].parameters).to eq(numeric_highlight)
491
+ end
492
+
493
+ it "must initialize #style.chars" do
494
+ expect(subject.theme.chars).to be_kind_of(Hexdump::Theme::Rule)
495
+ end
496
+
497
+ it "must populate #style.chars.highlights" do
498
+ expect(subject.theme.chars.highlights[chars_pattern].parameters).to eq(chars_highlight)
499
+ end
500
+ end
501
+ end
502
+
503
+ context "when a block is given" do
504
+ it "must yield the newly initialized hexdump object" do
505
+ yielded_hexdump = nil
506
+
507
+ described_class.new do |hexdump|
508
+ yielded_hexdump = hexdump
509
+ end
510
+
511
+ expect(yielded_hexdump).to be_kind_of(described_class)
512
+ end
513
+ end
514
+ end
515
+
516
+ describe "#type=" do
517
+ context "when given a type name" do
518
+ let(:type_name) { :uint32_le }
519
+
520
+ before { subject.type = type_name }
521
+
522
+ it "must set the #type to a Type object" do
523
+ expect(subject.type).to eq(Hexdump::TYPES.fetch(type_name))
524
+ end
525
+ end
526
+ end
527
+
528
+ describe "#base=" do
529
+ context "when given 16" do
530
+ let(:base) { 16 }
531
+
532
+ before { subject.base = base }
533
+
534
+ it "must set #base" do
535
+ expect(subject.base).to eq(base)
536
+ end
537
+ end
538
+
539
+ context "when given 10" do
540
+ let(:base) { 10 }
541
+
542
+ before { subject.base = base }
543
+
544
+ it "must set #base" do
545
+ expect(subject.base).to eq(base)
546
+ end
547
+ end
548
+
549
+ context "when given 8" do
550
+ let(:base) { 8 }
551
+
552
+ before { subject.base = base }
553
+
554
+ it "must set #base" do
555
+ expect(subject.base).to eq(base)
556
+ end
557
+ end
558
+
559
+ context "when given 2" do
560
+ let(:base) { 2 }
561
+
562
+ before { subject.base = base }
563
+
564
+ it "must set #base" do
565
+ expect(subject.base).to eq(base)
566
+ end
567
+ end
568
+ end
569
+
570
+ describe "#index_base=" do
571
+ context "when given 16" do
572
+ let(:value) { 16 }
573
+
574
+ before { subject.index_base = value }
575
+
576
+ it "must set #index_base" do
577
+ expect(subject.index_base).to eq(value)
578
+ end
579
+ end
580
+
581
+ context "when given 10" do
582
+ let(:value) { 10 }
583
+
584
+ before { subject.index_base = value }
585
+
586
+ it "must set #index_base" do
587
+ expect(subject.index_base).to eq(value)
588
+ end
589
+ end
590
+
591
+ context "when given 8" do
592
+ let(:value) { 8 }
593
+
594
+ before { subject.index_base = value }
595
+
596
+ it "must set #index_base" do
597
+ expect(subject.index_base).to eq(value)
598
+ end
599
+ end
600
+
601
+ context "when given 2" do
602
+ let(:value) { 2 }
603
+
604
+ before { subject.index_base = value }
605
+
606
+ it "must set #index_base" do
607
+ expect(subject.index_base).to eq(value)
608
+ end
609
+ end
610
+ end
611
+
612
+ describe "#group_chars=" do
613
+ context "when given an Integer" do
614
+ let(:value) { 4 }
615
+
616
+ before { subject.group_chars = value }
617
+
618
+ it "must set #group_chars" do
619
+ expect(subject.group_chars).to eq(value)
620
+ end
621
+ end
622
+
623
+ context "when given :type" do
624
+ subject { described_class.new(type: :uint16) }
625
+
626
+ before { subject.group_chars = :type }
627
+
628
+ it "must set #group_chars to #type.size" do
629
+ expect(subject.group_chars).to eq(subject.type.size)
630
+ end
631
+ end
632
+ end
633
+
634
+ describe "#encoding=" do
635
+ context "when given nil" do
636
+ before { subject.encoding = nil }
637
+
638
+ it "must set #encoding to nil" do
639
+ expect(subject.encoding).to be(nil)
640
+ end
641
+ end
642
+
643
+ context "when given :ascii" do
644
+ before { subject.encoding = :ascii }
645
+
646
+ it "must set #encoding to nil" do
647
+ expect(subject.encoding).to be(nil)
648
+ end
649
+ end
650
+
651
+ context "when given :utf8" do
652
+ before { subject.encoding = :utf8 }
653
+
654
+ it "must set #encoding to Encoding::UTF_8" do
655
+ expect(subject.encoding).to eq(Encoding::UTF_8)
656
+ end
657
+ end
658
+
659
+ context "when given an Encoding object" do
660
+ let(:encoding) { Encoding::UTF_7 }
661
+
662
+ before { subject.encoding = encoding }
663
+
664
+ it "must set #encoding" do
665
+ expect(subject.encoding).to eq(encoding)
666
+ end
667
+ end
668
+ end
669
+
670
+ describe "#theme" do
671
+ context "when a block is given" do
672
+ context "when not initialized with style: or highlights: keywords" do
673
+ it "must yield a new Theme object" do
674
+ yielded_theme = nil
675
+
676
+ subject.theme { |theme| yielded_theme = theme }
677
+
678
+ initialized_theme = subject.instance_variable_get("@theme")
679
+
680
+ expect(yielded_theme).to be(initialized_theme)
681
+ end
682
+
683
+ context "when called multiple times" do
684
+ it "must yield the same Theme object" do
685
+ theme_id1 = nil
686
+ theme_id2 = nil
687
+
688
+ subject.theme { |theme| theme_id1 = theme.object_id }
689
+ subject.theme { |theme| theme_id2 = theme.object_id }
690
+
691
+ expect(theme_id1).to eq(theme_id2)
692
+ end
693
+ end
694
+ end
695
+
696
+ context "when initialized with style: or highlights: keywords" do
697
+ subject do
698
+ described_class.new(
699
+ style: {},
700
+ highlights: {}
701
+ )
702
+ end
703
+
704
+ it "must yield the initialized theme object" do
705
+ initialized_theme = subject.instance_variable_get("@theme")
706
+
707
+ yielded_theme = nil
708
+
709
+ subject.theme { |theme| yielded_theme = theme }
710
+
711
+ expect(yielded_theme).to be(initialized_theme)
712
+ end
713
+ end
714
+ end
715
+
716
+ context "when no block is given" do
717
+ context "when not initialized with style: or highlights: keywords" do
718
+ it "must return nil" do
719
+ expect(subject.theme).to be(nil)
720
+ end
721
+ end
722
+
723
+ context "when initialized with style: or highlights: keywords" do
724
+ subject do
725
+ described_class.new(
726
+ style: {},
727
+ highlights: {}
728
+ )
729
+ end
730
+
731
+ it "must return the initialized theme object" do
732
+ initialized_theme = subject.instance_variable_get("@theme")
733
+
734
+ expect(subject.theme).to be(initialized_theme)
735
+ end
736
+ end
737
+ end
738
+ end
739
+
740
+ let(:data) do
741
+ 'A' * subject.columns + \
742
+ 'B' * subject.columns + \
743
+ 'C' * subject.columns
744
+ end
745
+
746
+ let(:columns) { subject.columns }
747
+
748
+ describe "#each_row" do
749
+ let(:rows) do
750
+ [
751
+ [columns * 0, [0x41] * columns, ['A'] * columns],
752
+ [columns * 1, [0x42] * columns, ['B'] * columns],
753
+ [columns * 2, [0x43] * columns, ['C'] * columns],
754
+ ]
755
+ end
756
+
757
+ it "must yield each row of index, numeric values, and raw characters" do
758
+ yielded_rows = []
759
+
760
+ subject.each_row(data) do |*args|
761
+ yielded_rows << args
762
+ end
763
+
764
+ expect(yielded_rows).to eq(rows)
765
+ end
766
+
767
+ it "must return the total number of bytes read" do
768
+ index = subject.each_row(data) do |*args|
769
+ end
770
+
771
+ expect(index).to eq(data.length)
772
+ end
773
+
774
+ context "when #reader.offset is > 0" do
775
+ let(:offset) { 8 }
776
+
777
+ subject { described_class.new(offset: offset) }
778
+
779
+ let(:indexes) do
780
+ [
781
+ offset + (columns * 0),
782
+ offset + (columns * 1),
783
+ offset + (columns * 2),
784
+ ]
785
+ end
786
+
787
+ it "must start the index at #offset" do
788
+ yielded_indexes = []
789
+
790
+ subject.each_row(data) do |index,*args|
791
+ yielded_indexes << index
792
+ end
793
+
794
+ expect(yielded_indexes).to eq(indexes)
795
+ end
796
+
797
+ context "and #index_offset is > 0" do
798
+ let(:index_offset) { 0 }
799
+
800
+ subject do
801
+ described_class.new(
802
+ offset: offset,
803
+ index_offset: index_offset
804
+ )
805
+ end
806
+
807
+ let(:indexes) do
808
+ [
809
+ index_offset + (columns * 0),
810
+ index_offset + (columns * 1),
811
+ index_offset + (columns * 2),
812
+ ]
813
+ end
814
+
815
+ it "must override :index and start the index at #index_offset" do
816
+ yielded_indexes = []
817
+
818
+ subject.each_row(data) do |index,*args|
819
+ yielded_indexes << index
820
+ end
821
+
822
+ expect(yielded_indexes).to eq(indexes)
823
+ end
824
+ end
825
+ end
826
+
827
+ context "when the data's length is not evenly divisble by the columns" do
828
+ let(:rows) do
829
+ [
830
+ [columns * 0, [0x41] * columns, ['A'] * columns ],
831
+ [columns * 1, [0x42] * columns, ['B'] * columns ],
832
+ [columns * 2, [0x43] * (columns / 2), ['C'] * (columns / 2)]
833
+ ]
834
+ end
835
+
836
+ let(:data) do
837
+ 'A' * columns + \
838
+ 'B' * columns + \
839
+ 'C' * (columns / 2)
840
+ end
841
+
842
+ it "must yield a partial row of read values last" do
843
+ yielded_rows = []
844
+
845
+ subject.each_row(data) do |*args|
846
+ yielded_rows << args
847
+ end
848
+
849
+ expect(yielded_rows).to eq(rows)
850
+ end
851
+ end
852
+
853
+ context "when #columns is not 16" do
854
+ let(:columns) { 7 }
855
+
856
+ subject { described_class.new(columns: columns) }
857
+
858
+ it "must yield rows with the same number of columns" do
859
+ yielded_row_lengths = []
860
+
861
+ subject.each_row(data) do |index,numeric,chars|
862
+ yielded_row_lengths << numeric.length
863
+ end
864
+
865
+ expect(yielded_row_lengths).to all(eq(columns))
866
+ end
867
+ end
868
+
869
+ context "when there are not enough bytes to decode the last value" do
870
+ subject { described_class.new(type: :uint32) }
871
+
872
+ let(:size) { subject.type.size }
873
+
874
+ let(:data) do
875
+ "\x41\x41\x41\x41" * columns + \
876
+ "ABC"
877
+ end
878
+
879
+ let(:rows) do
880
+ [
881
+ [size * columns * 0, [0x41414141] * columns, ["AAAA"] * columns],
882
+ [size * columns * 1, [nil], ["ABC"]]
883
+ ]
884
+ end
885
+
886
+ it "must yield the read characters, but not the partially decoded value" do
887
+ yielded_rows = []
888
+
889
+ subject.each_row(data) do |*row|
890
+ yielded_rows << row
891
+ end
892
+
893
+ expect(yielded_rows).to eq(rows)
894
+ end
895
+ end
896
+
897
+ context "when no block is given" do
898
+ it "must return an Enumerator" do
899
+ expect(subject.each_row(data)).to be_kind_of(Enumerator)
900
+ end
901
+ end
902
+ end
903
+
904
+ describe "#each_non_repeating_row" do
905
+ let(:rows) do
906
+ [
907
+ [columns * 0, [0x41] * columns, ['A'] * columns],
908
+ [columns * 1, [0x42] * columns, ['B'] * columns],
909
+ ['*'],
910
+ [columns * 4, [0x43] * columns, ['C'] * columns]
911
+ ]
912
+ end
913
+
914
+ let(:data) do
915
+ 'A' * subject.columns + \
916
+ 'B' * subject.columns + \
917
+ 'B' * subject.columns + \
918
+ 'B' * subject.columns + \
919
+ 'C' * subject.columns
920
+ end
921
+
922
+ it "must omit repeating rows, instead yielding an index of '*'" do
923
+ yielded_rows = []
924
+
925
+ subject.each_non_repeating_row(data) do |*args|
926
+ yielded_rows << args
927
+ end
928
+
929
+ expect(yielded_rows).to eq(rows)
930
+ end
931
+
932
+ it "must return the total number of bytes read" do
933
+ index = subject.each_non_repeating_row(data) do |*args|
934
+ end
935
+
936
+ expect(index).to eq(data.length)
937
+ end
938
+
939
+ context "when the repeating rows is at the end of the data" do
940
+ let(:rows) do
941
+ [
942
+ [columns * 0, [0x41] * columns, ['A'] * columns],
943
+ [columns * 1, [0x42] * columns, ['B'] * columns],
944
+ [columns * 2, [0x43] * columns, ['C'] * columns],
945
+ ['*']
946
+ ]
947
+ end
948
+
949
+ let(:data) do
950
+ 'A' * columns + \
951
+ 'B' * columns + \
952
+ 'C' * columns + \
953
+ 'C' * columns + \
954
+ 'C' * columns
955
+ end
956
+
957
+ it "must yield an index of '*' at the end" do
958
+ yielded_rows = []
959
+
960
+ subject.each_non_repeating_row(data) do |*args|
961
+ yielded_rows << args
962
+ end
963
+
964
+ expect(yielded_rows).to eq(rows)
965
+ end
966
+ end
967
+
968
+ context "when no block is given" do
969
+ it "must return an Enumerator" do
970
+ expect(subject.each_non_repeating_row(data)).to be_kind_of(Enumerator)
971
+ end
972
+ end
973
+ end
974
+
975
+ describe "#each_formatted_row" do
976
+ let(:formatted_rows) do
977
+ [
978
+ ["%.8x" % (columns * 0), ["41"] * columns, "A" * columns],
979
+ ["%.8x" % (columns * 1), ["42"] * columns, "B" * columns],
980
+ ["%.8x" % (columns * 2), ["43"] * columns, "C" * columns],
981
+ ]
982
+ end
983
+
984
+ it "should yield the formatted rows to the given block" do
985
+ yielded_rows = []
986
+
987
+ subject.each_formatted_row(data) do |*row|
988
+ yielded_rows << row
989
+ end
990
+
991
+ expect(yielded_rows).to eq(formatted_rows)
992
+ end
993
+
994
+ it "must return the formatted total number of bytes read" do
995
+ index = subject.each_formatted_row(data) do |*row|
996
+ end
997
+
998
+ expect(index).to eq(subject.index % data.length)
999
+ end
1000
+
1001
+ [*(0x00..0x1f), *(0x7f..0xff)].each do |byte|
1002
+ context "when the row contains an ASCII #{byte.chr.dump} characters" do
1003
+ let(:data) { "ABC#{byte.chr}" }
1004
+
1005
+ let(:formatted_rows) do
1006
+ [
1007
+ ["00000000", ["41", "42", "43", "%.2x" % byte], "ABC."]
1008
+ ]
1009
+ end
1010
+
1011
+ it "must replace unprintable characters with a '.'" do
1012
+ yielded_rows = []
1013
+
1014
+ subject.each_formatted_row(data) do |*row|
1015
+ yielded_rows << row
1016
+ end
1017
+
1018
+ expect(yielded_rows).to eq(formatted_rows)
1019
+ end
1020
+ end
1021
+ end
1022
+
1023
+ context "when the data's length is not evenly divisble by the columns" do
1024
+ let(:data) do
1025
+ 'A' * columns + \
1026
+ 'B' * columns + \
1027
+ 'C' * (columns / 2)
1028
+ end
1029
+
1030
+ let(:formatted_rows) do
1031
+ [
1032
+ ["%.8x" % (columns * 0), ["41"] * columns, "A" * columns],
1033
+ ["%.8x" % (columns * 1), ["42"] * columns, "B" * columns],
1034
+ [
1035
+ "%.8x" % (columns * 2), ["43"] * (columns / 2), "C" * (columns / 2)
1036
+ ]
1037
+ ]
1038
+ end
1039
+
1040
+ it "must yield the last formatted partial row" do
1041
+ yielded_rows = []
1042
+
1043
+ subject.each_formatted_row(data) do |*row|
1044
+ yielded_rows << row
1045
+ end
1046
+
1047
+ expect(yielded_rows).to eq(formatted_rows)
1048
+ end
1049
+ end
1050
+
1051
+ context "when there are not enough bytes to decode the last value" do
1052
+ subject { described_class.new(type: :uint32) }
1053
+
1054
+ let(:size) { subject.type.size }
1055
+
1056
+ let(:data) do
1057
+ "\x41\x41\x41\x41" * columns + \
1058
+ "ABC"
1059
+ end
1060
+
1061
+ let(:formatted_rows) do
1062
+ [
1063
+ [
1064
+ "%.8x" % (size * columns * 0),
1065
+ ["41414141"] * columns,
1066
+ "AAAA" * columns
1067
+ ],
1068
+
1069
+ [
1070
+ "%.8x" % (size * columns * 1),
1071
+ [" "],
1072
+ "ABC"
1073
+ ]
1074
+ ]
1075
+ end
1076
+
1077
+ it "must yield the read characters and a place-holder blank String" do
1078
+ yielded_rows = []
1079
+
1080
+ subject.each_formatted_row(data) do |*row|
1081
+ yielded_rows << row
1082
+ end
1083
+
1084
+ expect(yielded_rows).to eq(formatted_rows)
1085
+ end
1086
+ end
1087
+
1088
+ context "when there is repeating rows of data" do
1089
+ let(:data) do
1090
+ 'A' * columns + \
1091
+ 'B' * columns + \
1092
+ 'B' * columns + \
1093
+ 'B' * columns + \
1094
+ 'C' * columns
1095
+ end
1096
+
1097
+ let(:formatted_rows) do
1098
+ [
1099
+ ["%.8x" % (columns * 0), ["41"] * columns, "A" * columns],
1100
+ ["%.8x" % (columns * 1), ["42"] * columns, "B" * columns],
1101
+ ["*"],
1102
+ ["%.8x" % (columns * 4), ["43"] * columns, "C" * columns]
1103
+ ]
1104
+ end
1105
+
1106
+ it "must yield the formatted rows and a '*' row to denote the beginning of repeating rows" do
1107
+ yielded_rows = []
1108
+
1109
+ subject.each_formatted_row(data) do |*row|
1110
+ yielded_rows << row
1111
+ end
1112
+
1113
+ expect(yielded_rows).to eq(formatted_rows)
1114
+ end
1115
+
1116
+ context "and #repeating is true" do
1117
+ subject { described_class.new(repeating: true) }
1118
+
1119
+ let(:formatted_rows) do
1120
+ [
1121
+ ["%.8x" % (columns * 0), ["41"] * columns, 'A' * columns],
1122
+ ["%.8x" % (columns * 1), ["42"] * columns, 'B' * columns],
1123
+ ["%.8x" % (columns * 2), ["42"] * columns, 'B' * columns],
1124
+ ["%.8x" % (columns * 3), ["42"] * columns, 'B' * columns],
1125
+ ["%.8x" % (columns * 4), ["43"] * columns, 'C' * columns]
1126
+ ]
1127
+ end
1128
+
1129
+ it "must yield the formatted repeating rows" do
1130
+ yielded_rows = []
1131
+
1132
+ subject.each_formatted_row(data) do |*row|
1133
+ yielded_rows << row
1134
+ end
1135
+
1136
+ expect(yielded_rows).to eq(formatted_rows)
1137
+ end
1138
+ end
1139
+ end
1140
+
1141
+ context "when #columns is not 16" do
1142
+ let(:columns) { 7 }
1143
+
1144
+ subject { described_class.new(columns: columns) }
1145
+
1146
+ it "must yield rows with the same number of formatted columns" do
1147
+ yielded_numeric_lengths = []
1148
+ yielded_chars_lengths = []
1149
+
1150
+ subject.each_formatted_row(data) do |index,numeric,chars|
1151
+ yielded_numeric_lengths << numeric.length
1152
+ yielded_chars_lengths << chars.length
1153
+ end
1154
+
1155
+ expect(yielded_numeric_lengths).to all(eq(columns))
1156
+ expect(yielded_chars_lengths).to all(eq(columns))
1157
+ end
1158
+ end
1159
+
1160
+ context "when #chars.encoding is set" do
1161
+ let(:encoding) { Encoding::UTF_8 }
1162
+
1163
+ subject { described_class.new(encoding: encoding) }
1164
+
1165
+ it "must encode the characters to the given Encoding" do
1166
+ yielded_char_encodings = []
1167
+
1168
+ subject.each_formatted_row(data) do |index,numeric,chars|
1169
+ yielded_char_encodings << chars.encoding
1170
+ end
1171
+
1172
+ expect(yielded_char_encodings).to all(eq(encoding))
1173
+ end
1174
+
1175
+ context "and the data contains unprintable characters" do
1176
+ let(:codepoint) { 888 }
1177
+ let(:char) { codepoint.chr(encoding) }
1178
+ let(:data) { "A#{char}B" }
1179
+ let(:bytes) { data.bytes }
1180
+
1181
+ let(:formatted_rows) do
1182
+ [
1183
+ [subject.index % 0, bytes.map { |b| subject.numeric % b }, "A.B"]
1184
+ ]
1185
+ end
1186
+
1187
+ it "must replace any unprintable characters with a '.'" do
1188
+ yielded_rows = []
1189
+
1190
+ subject.each_formatted_row(data) do |*row|
1191
+ yielded_rows << row
1192
+ end
1193
+
1194
+ expect(yielded_rows).to eq(formatted_rows)
1195
+ end
1196
+ end
1197
+
1198
+ context "and the data contains invalid byte sequences" do
1199
+ let(:invalid_bytes) { "\x80\x81" }
1200
+ let(:data) { "A#{invalid_bytes}B" }
1201
+ let(:bytes) { data.bytes }
1202
+
1203
+ let(:formatted_rows) do
1204
+ [
1205
+ [subject.index % 0, bytes.map { |b| subject.numeric % b }, "A..B"]
1206
+ ]
1207
+ end
1208
+
1209
+ it "must replace any invalid byte sequences with a '.'" do
1210
+ yielded_rows = []
1211
+
1212
+ subject.each_formatted_row(data) do |*row|
1213
+ yielded_rows << row
1214
+ end
1215
+
1216
+ expect(yielded_rows).to eq(formatted_rows)
1217
+ end
1218
+ end
1219
+ end
1220
+
1221
+ context "when #theme is initialized" do
1222
+ let(:ansi_reset) { Hexdump::Theme::ANSI::RESET }
1223
+ let(:ansi_green) { Hexdump::Theme::ANSI::PARAMETERS[:green] }
1224
+ let(:ansi_blue) { Hexdump::Theme::ANSI::PARAMETERS[:blue] }
1225
+
1226
+ let(:ansi_reset) { Hexdump::Theme::ANSI::RESET }
1227
+
1228
+ context "and #style.index.theme is set" do
1229
+ subject { described_class.new(style: {index: :cyan}) }
1230
+
1231
+ let(:ansi_style) { Hexdump::Theme::ANSI::PARAMETERS[:cyan] }
1232
+
1233
+ let(:index_format) { "#{ansi_style}%.8x#{ansi_reset}" }
1234
+ let(:formatted_rows) do
1235
+ [
1236
+ [index_format % (columns * 0), ["41"] * columns, "A" * columns],
1237
+ [index_format % (columns * 1), ["42"] * columns, "B" * columns],
1238
+ [index_format % (columns * 2), ["43"] * columns, "C" * columns],
1239
+ ]
1240
+ end
1241
+
1242
+ it "must apply ANSI control sequences around the index column" do
1243
+ yielded_rows = []
1244
+
1245
+ subject.each_formatted_row(data) do |*row|
1246
+ yielded_rows << row
1247
+ end
1248
+
1249
+ expect(yielded_rows).to eq(formatted_rows)
1250
+ end
1251
+
1252
+ let(:formatted_index) do
1253
+ "#{ansi_style}%.8x#{ansi_reset}" % data.length
1254
+ end
1255
+
1256
+ it "must apply ANSI control sequences around the returned index" do
1257
+ index = subject.each_formatted_row(data) do |*row|
1258
+ end
1259
+
1260
+ expect(index).to eq(formatted_index)
1261
+ end
1262
+ end
1263
+
1264
+ context "and #style.index.highlights is populated" do
1265
+ subject do
1266
+ described_class.new(
1267
+ highlights: {
1268
+ index: {/00$/ => :bold}
1269
+ }
1270
+ )
1271
+ end
1272
+
1273
+ let(:ansi_style) { Hexdump::Theme::ANSI::PARAMETERS[:bold] }
1274
+
1275
+ let(:highlighted_index) { "000000#{ansi_style}00#{ansi_reset}" }
1276
+ let(:formatted_rows) do
1277
+ [
1278
+ [highlighted_index, ["41"] * columns, "A" * columns],
1279
+ ["%.8x" % (columns * 1), ["42"] * columns, "B" * columns],
1280
+ ["%.8x" % (columns * 2), ["43"] * columns, "C" * columns],
1281
+ ]
1282
+ end
1283
+
1284
+ it "must selectively insert ANSI control sequences to the index column" do
1285
+ yielded_rows = []
1286
+
1287
+ subject.each_formatted_row(data) do |*row|
1288
+ yielded_rows << row
1289
+ end
1290
+
1291
+ expect(yielded_rows).to eq(formatted_rows)
1292
+ end
1293
+ end
1294
+
1295
+ context "and #style.numeric.theme is set" do
1296
+ subject { described_class.new(style: {numeric: :blue}) }
1297
+
1298
+ let(:ansi_style) { Hexdump::Theme::ANSI::PARAMETERS[:blue] }
1299
+
1300
+ let(:formatted_rows) do
1301
+ [
1302
+ ["%.8x" % (columns * 0), ["#{ansi_style}41#{ansi_reset}"] * columns, "A" * columns],
1303
+ ["%.8x" % (columns * 1), ["#{ansi_style}42#{ansi_reset}"] * columns, "B" * columns],
1304
+ ["%.8x" % (columns * 2), ["#{ansi_style}43#{ansi_reset}"] * columns, "C" * columns],
1305
+ ]
1306
+ end
1307
+
1308
+ it "must apply ANSI control sequences around each numeric column" do
1309
+ yielded_rows = []
1310
+
1311
+ subject.each_formatted_row(data) do |*row|
1312
+ yielded_rows << row
1313
+ end
1314
+
1315
+ expect(yielded_rows).to eq(formatted_rows)
1316
+ end
1317
+ end
1318
+
1319
+ context "and #style.numeric.highlights is populated" do
1320
+ subject do
1321
+ described_class.new(
1322
+ highlights: {
1323
+ numeric: {/^4/ => :green}
1324
+ }
1325
+ )
1326
+ end
1327
+
1328
+ let(:ansi_style) { Hexdump::Theme::ANSI::PARAMETERS[:green] }
1329
+
1330
+ let(:formatted_rows) do
1331
+ [
1332
+ ["%.8x" % (columns * 0), ["#{ansi_style}4#{ansi_reset}1"] * columns, "A" * columns],
1333
+ ["%.8x" % (columns * 1), ["#{ansi_style}4#{ansi_reset}2"] * columns, "B" * columns],
1334
+ ["%.8x" % (columns * 2), ["#{ansi_style}4#{ansi_reset}3"] * columns, "C" * columns],
1335
+ ]
1336
+ end
1337
+
1338
+ it "must selectively insert ANSI control sequences to the numeric columns" do
1339
+ yielded_rows = []
1340
+
1341
+ subject.each_formatted_row(data) do |*row|
1342
+ yielded_rows << row
1343
+ end
1344
+
1345
+ expect(yielded_rows).to eq(formatted_rows)
1346
+ end
1347
+ end
1348
+
1349
+ context "and #style.chars.theme is set" do
1350
+ subject { described_class.new(style: {chars: :green}) }
1351
+
1352
+ let(:ansi_style) { Hexdump::Theme::ANSI::PARAMETERS[:green] }
1353
+
1354
+ let(:formatted_rows) do
1355
+ [
1356
+ ["%.8x" % (columns * 0), ["41"] * columns, "#{ansi_style}#{"A" * columns}#{ansi_reset}"],
1357
+ ["%.8x" % (columns * 1), ["42"] * columns, "#{ansi_style}#{"B" * columns}#{ansi_reset}"],
1358
+ ["%.8x" % (columns * 2), ["43"] * columns, "#{ansi_style}#{"C" * columns}#{ansi_reset}"],
1359
+ ]
1360
+ end
1361
+
1362
+ it "must apply ANSI control sequences around the chars column" do
1363
+ yielded_rows = []
1364
+
1365
+ subject.each_formatted_row(data) do |*row|
1366
+ yielded_rows << row
1367
+ end
1368
+
1369
+ expect(yielded_rows).to eq(formatted_rows)
1370
+ end
1371
+ end
1372
+
1373
+ context "and #style.chars.highlights is populated" do
1374
+ subject do
1375
+ described_class.new(
1376
+ highlights: {
1377
+ chars: {/[AC]/ => :red}
1378
+ }
1379
+ )
1380
+ end
1381
+
1382
+ let(:ansi_style) { Hexdump::Theme::ANSI::PARAMETERS[:red] }
1383
+
1384
+ let(:formatted_rows) do
1385
+ [
1386
+ ["%.8x" % (columns * 0), ["41"] * columns, "#{ansi_style}A#{ansi_reset}" * columns],
1387
+ ["%.8x" % (columns * 1), ["42"] * columns, "B" * columns],
1388
+ ["%.8x" % (columns * 2), ["43"] * columns, "#{ansi_style}C#{ansi_reset}" * columns],
1389
+ ]
1390
+ end
1391
+
1392
+ it "must selectively insert ANSI control sequences to the chars column" do
1393
+ yielded_rows = []
1394
+
1395
+ subject.each_formatted_row(data) do |*row|
1396
+ yielded_rows << row
1397
+ end
1398
+
1399
+ expect(yielded_rows).to eq(formatted_rows)
1400
+ end
1401
+ end
1402
+
1403
+ context "but the ansi: keyword is false" do
1404
+ subject do
1405
+ described_class.new(
1406
+ style: {
1407
+ index: :cyan,
1408
+ numeric: :blue,
1409
+ chars: :green
1410
+ },
1411
+ highlights: {
1412
+ index: {/00$/ => :bold},
1413
+ numeric: {/^4/ => :green},
1414
+ chars: {/[AC]/ => :red}
1415
+ }
1416
+ )
1417
+ end
1418
+
1419
+ it "must not apply any ANSI control sequences" do
1420
+ yielded_rows = []
1421
+
1422
+ subject.each_formatted_row(data, ansi: false) do |*row|
1423
+ yielded_rows << row
1424
+ end
1425
+
1426
+ expect(yielded_rows).to eq(formatted_rows)
1427
+ end
1428
+ end
1429
+
1430
+ context "when #group_chars is set" do
1431
+ let(:group_chars) { 4 }
1432
+
1433
+ subject { described_class.new(group_chars: group_chars) }
1434
+
1435
+ let(:formatted_chars) do
1436
+ [
1437
+ ["A" * group_chars] * (columns / group_chars),
1438
+ ["B" * group_chars] * (columns / group_chars),
1439
+ ["C" * group_chars] * (columns / group_chars)
1440
+ ]
1441
+ end
1442
+
1443
+ it "must group the characters into fixed length strings" do
1444
+ yielded_chars= []
1445
+
1446
+ subject.each_formatted_row(data) do |*row,chars|
1447
+ yielded_chars << chars
1448
+ end
1449
+
1450
+ expect(yielded_chars).to eq(formatted_chars)
1451
+ end
1452
+
1453
+ context "but #chars.encoding is Encoding::UTF_8" do
1454
+ let(:encoding) { Encoding::UTF_8 }
1455
+
1456
+ subject do
1457
+ described_class.new(group_chars: group_chars, encoding: encoding)
1458
+ end
1459
+
1460
+ let(:char) { "\u8000" }
1461
+ let(:length) { (columns * 2) / char.bytes.length }
1462
+ let(:data) { char * length }
1463
+
1464
+ let(:formatted_chars) do
1465
+ [
1466
+ ["耀.", "...", ".耀", "耀."],
1467
+ ["...", ".耀", "耀.", ".."]
1468
+ ]
1469
+ end
1470
+
1471
+ it "must split the characters along byte boundaries" do
1472
+ yielded_chars= []
1473
+
1474
+ subject.each_formatted_row(data) do |*row,chars|
1475
+ yielded_chars << chars
1476
+ end
1477
+
1478
+ expect(yielded_chars).to eq(formatted_chars)
1479
+ end
1480
+ end
1481
+ end
1482
+ end
1483
+
1484
+ context "when no block is given" do
1485
+ it "must return an Enumerator" do
1486
+ expect(subject.each_formatted_row(data)).to be_kind_of(Enumerator)
1487
+ end
1488
+ end
1489
+ end
1490
+
1491
+ describe "#each_line" do
1492
+ let(:lines) do
1493
+ [
1494
+ "00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|#{$/}",
1495
+ "00000010 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 |BBBBBBBBBBBBBBBB|#{$/}",
1496
+ "00000020 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 |CCCCCCCCCCCCCCCC|#{$/}",
1497
+ "00000030#{$/}"
1498
+ ]
1499
+ end
1500
+
1501
+ it "must yield each line" do
1502
+ yielded_lines = []
1503
+
1504
+ subject.each_line(data) do |line|
1505
+ yielded_lines << line
1506
+ end
1507
+
1508
+ expect(yielded_lines).to eq(lines)
1509
+ end
1510
+
1511
+ context "when the data's length is not evenly divisble by the columns" do
1512
+ let(:data) do
1513
+ 'A' * columns + \
1514
+ 'B' * columns + \
1515
+ 'C' * (columns / 2)
1516
+ end
1517
+
1518
+ let(:lines) do
1519
+ [
1520
+ "00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|#{$/}",
1521
+ "00000010 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 |BBBBBBBBBBBBBBBB|#{$/}",
1522
+ "00000020 43 43 43 43 43 43 43 43 |CCCCCCCC|#{$/}",
1523
+ "00000028#{$/}"
1524
+ ]
1525
+ end
1526
+
1527
+ it "must yield the last left-adjusted partial row" do
1528
+ yielded_lines = []
1529
+
1530
+ subject.each_line(data) do |line|
1531
+ yielded_lines << line
1532
+ end
1533
+
1534
+ expect(yielded_lines).to eq(lines)
1535
+ end
1536
+ end
1537
+
1538
+ context "when the data contains repeating rows" do
1539
+ let(:data) do
1540
+ 'A' * columns + \
1541
+ 'B' * columns + \
1542
+ 'B' * columns + \
1543
+ 'B' * columns + \
1544
+ 'C' * columns
1545
+ end
1546
+
1547
+ let(:lines) do
1548
+ [
1549
+ "00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|#{$/}",
1550
+ "00000010 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 |BBBBBBBBBBBBBBBB|#{$/}",
1551
+ "*#{$/}",
1552
+ "00000040 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 |CCCCCCCCCCCCCCCC|#{$/}",
1553
+ "00000050#{$/}"
1554
+ ]
1555
+ end
1556
+
1557
+ it "must yield the formatted lines and a '*' row to denote the beginning of repeating lines" do
1558
+ yielded_lines = []
1559
+
1560
+ subject.each_line(data) do |line|
1561
+ yielded_lines << line
1562
+ end
1563
+
1564
+ expect(yielded_lines).to eq(lines)
1565
+ end
1566
+ end
1567
+
1568
+ context "when #repeating is true" do
1569
+ subject { described_class.new(repeating: true) }
1570
+
1571
+ let(:data) do
1572
+ 'A' * columns + \
1573
+ 'B' * columns + \
1574
+ 'B' * columns + \
1575
+ 'B' * columns + \
1576
+ 'C' * columns
1577
+ end
1578
+
1579
+ let(:lines) do
1580
+ [
1581
+ "00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|#{$/}",
1582
+ "00000010 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 |BBBBBBBBBBBBBBBB|#{$/}",
1583
+ "00000020 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 |BBBBBBBBBBBBBBBB|#{$/}",
1584
+ "00000030 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 |BBBBBBBBBBBBBBBB|#{$/}",
1585
+ "00000040 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 |CCCCCCCCCCCCCCCC|#{$/}",
1586
+ "00000050#{$/}"
1587
+ ]
1588
+ end
1589
+
1590
+ it "must yield repeating lines" do
1591
+ yielded_lines = []
1592
+
1593
+ subject.each_line(data) do |line|
1594
+ yielded_lines << line
1595
+ end
1596
+
1597
+ expect(yielded_lines).to eq(lines)
1598
+ end
1599
+ end
1600
+
1601
+ context "when #chars_column is false" do
1602
+ subject { described_class.new(chars_column: false) }
1603
+
1604
+ let(:lines) do
1605
+ [
1606
+ "00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41#{$/}",
1607
+ "00000010 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42#{$/}",
1608
+ "00000020 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43#{$/}",
1609
+ "00000030#{$/}"
1610
+ ]
1611
+ end
1612
+
1613
+ it "must omit the characters column" do
1614
+ yielded_lines = []
1615
+
1616
+ subject.each_line(data) do |line|
1617
+ yielded_lines << line
1618
+ end
1619
+
1620
+ expect(yielded_lines).to eq(lines)
1621
+ end
1622
+ end
1623
+
1624
+ context "when #group_columns is set" do
1625
+ subject { described_class.new(group_columns: 4) }
1626
+
1627
+ let(:lines) do
1628
+ [
1629
+ "00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAAAAAAAAAAAAAA|#{$/}",
1630
+ "00000010 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 |BBBBBBBBBBBBBBBB|#{$/}",
1631
+ "00000020 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 |CCCCCCCCCCCCCCCC|#{$/}",
1632
+ "00000030#{$/}"
1633
+ ]
1634
+ end
1635
+
1636
+ it "must group columns together with an extra space" do
1637
+ yielded_lines = []
1638
+
1639
+ subject.each_line(data) do |line|
1640
+ yielded_lines << line
1641
+ end
1642
+
1643
+ expect(yielded_lines).to eq(lines)
1644
+ end
1645
+ end
1646
+
1647
+ context "when #group_chars is set" do
1648
+ let(:group_chars) { 4 }
1649
+
1650
+ subject { described_class.new(group_chars: group_chars) }
1651
+
1652
+ let(:lines) do
1653
+ [
1654
+ "00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 |AAAA|AAAA|AAAA|AAAA|#{$/}",
1655
+ "00000010 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 42 |BBBB|BBBB|BBBB|BBBB|#{$/}",
1656
+ "00000020 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 |CCCC|CCCC|CCCC|CCCC|#{$/}",
1657
+ "00000030#{$/}"
1658
+ ]
1659
+ end
1660
+
1661
+ it "must group the characters into fixed length strings" do
1662
+ yielded_lines = []
1663
+
1664
+ subject.each_line(data) do |line|
1665
+ yielded_lines << line
1666
+ end
1667
+
1668
+ expect(yielded_lines).to eq(lines)
1669
+ end
1670
+ end
1671
+ end
1672
+
1673
+ let(:lines) { subject.each_line(data).to_a }
1674
+
1675
+ describe "#hexdump" do
1676
+ it "must print each line of the hexdump to stdout" do
1677
+ expect {
1678
+ subject.hexdump(data)
1679
+ }.to output(lines.join).to_stdout
1680
+ end
1681
+
1682
+ context "when the output: keyword argument is given" do
1683
+ let(:output) { StringIO.new }
1684
+
1685
+ it "must print each line of hte hexdump to the given output" do
1686
+ subject.hexdump(data, output: output)
1687
+
1688
+ expect(output.string).to eq(lines.join)
1689
+ end
1690
+
1691
+ context "but the output does not support #<<" do
1692
+ let(:output) { Object.new }
1693
+
1694
+ it do
1695
+ expect {
1696
+ subject.hexdump(data, output: output)
1697
+ }.to raise_error(ArgumentError,"output must support the #<< method")
1698
+ end
1699
+ end
1700
+ end
1701
+ end
1702
+
1703
+ describe "#dump" do
1704
+ it "must append each line of the hexdump to the given String" do
1705
+ expect(subject.dump(data)).to eq(lines.join)
1706
+ end
1707
+ end
1708
+ end