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