hexdump 0.3.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +5 -6
- data/.gitignore +1 -0
- data/.yardopts +1 -1
- data/ChangeLog.md +79 -6
- data/Gemfile +3 -0
- data/LICENSE.txt +1 -1
- data/README.md +500 -137
- data/benchmark.rb +29 -22
- data/gemspec.yml +2 -1
- data/hexdump.gemspec +1 -4
- data/lib/hexdump/chars.rb +46 -0
- data/lib/hexdump/core_ext/file.rb +68 -6
- data/lib/hexdump/core_ext/io.rb +2 -2
- data/lib/hexdump/core_ext/kernel.rb +5 -0
- data/lib/hexdump/core_ext/string.rb +2 -2
- data/lib/hexdump/core_ext/string_io.rb +2 -2
- data/lib/hexdump/core_ext.rb +5 -4
- data/lib/hexdump/format_string.rb +43 -0
- data/lib/hexdump/hexdump.rb +766 -75
- data/lib/hexdump/mixin.rb +192 -0
- data/lib/hexdump/module_methods.rb +132 -0
- data/lib/hexdump/numeric/binary.rb +55 -0
- data/lib/hexdump/numeric/char_or_int.rb +95 -0
- data/lib/hexdump/numeric/decimal.rb +56 -0
- data/lib/hexdump/numeric/exceptions.rb +11 -0
- data/lib/hexdump/numeric/hexadecimal.rb +59 -0
- data/lib/hexdump/numeric/octal.rb +55 -0
- data/lib/hexdump/numeric.rb +5 -0
- data/lib/hexdump/reader.rb +313 -0
- data/lib/hexdump/theme/ansi.rb +82 -0
- data/lib/hexdump/theme/rule.rb +159 -0
- data/lib/hexdump/theme.rb +61 -0
- data/lib/hexdump/type.rb +233 -0
- data/lib/hexdump/types.rb +108 -0
- data/lib/hexdump/version.rb +1 -1
- data/lib/hexdump.rb +14 -3
- data/spec/chars_spec.rb +76 -0
- data/spec/core_ext_spec.rb +10 -6
- data/spec/format_string_spec.rb +22 -0
- data/spec/hexdump_class_spec.rb +1708 -0
- data/spec/hexdump_module_spec.rb +23 -0
- data/spec/mixin_spec.rb +37 -0
- data/spec/numeric/binary_spec.rb +239 -0
- data/spec/numeric/char_or_int_spec.rb +210 -0
- data/spec/numeric/decimal_spec.rb +317 -0
- data/spec/numeric/hexadecimal_spec.rb +320 -0
- data/spec/numeric/octal_spec.rb +239 -0
- data/spec/reader_spec.rb +866 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/theme/ansi_spec.rb +242 -0
- data/spec/theme/rule_spec.rb +199 -0
- data/spec/theme_spec.rb +94 -0
- data/spec/type_spec.rb +317 -0
- data/spec/types_spec.rb +904 -0
- metadata +42 -12
- data/.gemtest +0 -0
- data/lib/hexdump/dumper.rb +0 -419
- data/lib/hexdump/extensions.rb +0 -2
- data/spec/dumper_spec.rb +0 -329
- data/spec/hexdump_spec.rb +0 -30
@@ -0,0 +1,313 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'hexdump/type'
|
4
|
+
|
5
|
+
module Hexdump
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
#
|
9
|
+
# @since 1.0.0
|
10
|
+
#
|
11
|
+
class Reader
|
12
|
+
|
13
|
+
# The type to decode the data as.
|
14
|
+
#
|
15
|
+
# @return [Type]
|
16
|
+
attr_reader :type
|
17
|
+
|
18
|
+
# Controls whether to offset N number of bytes before starting to read data.
|
19
|
+
#
|
20
|
+
# @return [Integer, nil]
|
21
|
+
attr_reader :offset
|
22
|
+
|
23
|
+
# Controls control many bytes to read.
|
24
|
+
#
|
25
|
+
# @return [Integer, nil]
|
26
|
+
attr_reader :length
|
27
|
+
|
28
|
+
#
|
29
|
+
# Initializes the reader.
|
30
|
+
#
|
31
|
+
# @param [Type] type
|
32
|
+
# The type to decode the data as.
|
33
|
+
#
|
34
|
+
# @param [Integer, nil] offset
|
35
|
+
# Controls whether to offset N number of bytes before starting to read
|
36
|
+
# data.
|
37
|
+
#
|
38
|
+
# @param [Integer, nil] length
|
39
|
+
# Controls control many bytes to read.
|
40
|
+
#
|
41
|
+
# @param [Boolean] zero_pad
|
42
|
+
# Controls whether the remaining data will be padded with zeros.
|
43
|
+
#
|
44
|
+
def initialize(type, offset: nil, length: nil, zero_pad: false)
|
45
|
+
@type = type
|
46
|
+
@offset = offset
|
47
|
+
@length = length
|
48
|
+
@zero_pad = zero_pad
|
49
|
+
end
|
50
|
+
|
51
|
+
def zero_pad?
|
52
|
+
@zero_pad
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Reads each byte from the given data.
|
57
|
+
#
|
58
|
+
# @yield [byte]
|
59
|
+
#
|
60
|
+
# @yieldparam [Integer] byte
|
61
|
+
#
|
62
|
+
# @return [Enumerator]
|
63
|
+
#
|
64
|
+
# @raise [ArgumentError]
|
65
|
+
#
|
66
|
+
def each_byte(data)
|
67
|
+
return enum_for(__method__,data) unless block_given?
|
68
|
+
|
69
|
+
unless data.respond_to?(:each_byte)
|
70
|
+
raise(ArgumentError,"the given data must respond to #each_byte")
|
71
|
+
end
|
72
|
+
|
73
|
+
count = 0
|
74
|
+
|
75
|
+
data.each_byte do |b|
|
76
|
+
count += 1
|
77
|
+
|
78
|
+
# offset the first @offset number of bytes
|
79
|
+
if @offset.nil? || count > @offset
|
80
|
+
yield b
|
81
|
+
end
|
82
|
+
|
83
|
+
# stop reading after @length number of bytes
|
84
|
+
break if @length && count >= @length
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Reads each string of the same number of bytes as the {#type}'s
|
90
|
+
# {Type#size size}.
|
91
|
+
#
|
92
|
+
# @param [#each_byte] data
|
93
|
+
#
|
94
|
+
# @yield [slice]
|
95
|
+
#
|
96
|
+
# @yieldparam [String] slice
|
97
|
+
#
|
98
|
+
# @raise [ArgumentError]
|
99
|
+
#
|
100
|
+
def each_slice(data)
|
101
|
+
return enum_for(__method__,data) unless block_given?
|
102
|
+
|
103
|
+
unless data.respond_to?(:each_byte)
|
104
|
+
raise(ArgumentError,"the given data must respond to #each_byte")
|
105
|
+
end
|
106
|
+
|
107
|
+
if @type.size == 1
|
108
|
+
each_byte(data) do |b|
|
109
|
+
yield b.chr
|
110
|
+
end
|
111
|
+
else
|
112
|
+
buffer = String.new("\0" * @type.size, capacity: @type.size,
|
113
|
+
encoding: Encoding::BINARY)
|
114
|
+
index = 0
|
115
|
+
|
116
|
+
each_byte(data) do |b|
|
117
|
+
buffer[index] = b.chr(Encoding::BINARY)
|
118
|
+
index += 1
|
119
|
+
|
120
|
+
if index >= @type.size
|
121
|
+
yield buffer.dup
|
122
|
+
index = 0
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
if index > 0
|
127
|
+
if @zero_pad
|
128
|
+
# zero pad the rest of the buffer
|
129
|
+
(index..(@type.size - 1)).each do |i|
|
130
|
+
buffer[i] = "\0"
|
131
|
+
end
|
132
|
+
|
133
|
+
yield buffer
|
134
|
+
else
|
135
|
+
# yield the remaining partial buffer
|
136
|
+
yield buffer[0,index]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
#
|
143
|
+
# @param [#each_byte] data
|
144
|
+
#
|
145
|
+
# @yield [raw, uint]
|
146
|
+
#
|
147
|
+
# @yieldparam [String] raw
|
148
|
+
#
|
149
|
+
# @yieldparam [Integer, nil] uint
|
150
|
+
#
|
151
|
+
# @return [Enumerator]
|
152
|
+
#
|
153
|
+
# @raise [ArgumentError]
|
154
|
+
#
|
155
|
+
def each_uint(data,&block)
|
156
|
+
return enum_for(__method__,data) unless block_given?
|
157
|
+
|
158
|
+
unless data.respond_to?(:each_byte)
|
159
|
+
raise(ArgumentError,"the given data must respond to #each_byte")
|
160
|
+
end
|
161
|
+
|
162
|
+
if @type.size == 1
|
163
|
+
each_byte(data) do |b|
|
164
|
+
yield b.chr, b
|
165
|
+
end
|
166
|
+
else
|
167
|
+
pack_format = case @type.size
|
168
|
+
when 1
|
169
|
+
'c'
|
170
|
+
when 2
|
171
|
+
case @type.endian
|
172
|
+
when :little then 'S<'
|
173
|
+
when :big then 'S>'
|
174
|
+
else
|
175
|
+
raise(TypeError,"unsupported endian #{@type.endian} for #{@type.inspect}")
|
176
|
+
end
|
177
|
+
when 4
|
178
|
+
case @type.endian
|
179
|
+
when :little then 'L<'
|
180
|
+
when :big then 'L>'
|
181
|
+
else
|
182
|
+
raise(TypeError,"unsupported endian #{@type.endian} for #{@type.inspect}")
|
183
|
+
end
|
184
|
+
when 8
|
185
|
+
case @type.endian
|
186
|
+
when :little then 'Q<'
|
187
|
+
when :big then 'Q>'
|
188
|
+
else
|
189
|
+
raise(TypeError,"unsupported endian #{@type.endian} for #{@type.inspect}")
|
190
|
+
end
|
191
|
+
else
|
192
|
+
raise(TypeError,"unsupported type size #{@type.size} for #{@type.inspect}")
|
193
|
+
end
|
194
|
+
|
195
|
+
each_slice(data) do |slice|
|
196
|
+
yield slice, slice.unpack1(pack_format)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
#
|
202
|
+
# @param [#each_byte] data
|
203
|
+
#
|
204
|
+
# @yield [raw, int]
|
205
|
+
#
|
206
|
+
# @yieldparam [String] raw
|
207
|
+
#
|
208
|
+
# @yieldparam [Integer, nil] int
|
209
|
+
#
|
210
|
+
# @return [Enumerator]
|
211
|
+
#
|
212
|
+
def each_int(data)
|
213
|
+
return enum_for(__method__,data) unless block_given?
|
214
|
+
|
215
|
+
pack_format = case @type.size
|
216
|
+
when 1
|
217
|
+
'c'
|
218
|
+
when 2
|
219
|
+
case @type.endian
|
220
|
+
when :little then 's<'
|
221
|
+
when :big then 's>'
|
222
|
+
else
|
223
|
+
raise(TypeError,"unsupported endian #{@type.endian} for #{@type.inspect}")
|
224
|
+
end
|
225
|
+
when 4
|
226
|
+
case @type.endian
|
227
|
+
when :little then 'l<'
|
228
|
+
when :big then 'l>'
|
229
|
+
else
|
230
|
+
raise(TypeError,"unsupported endian #{@type.endian} for #{@type.inspect}")
|
231
|
+
end
|
232
|
+
when 8
|
233
|
+
case @type.endian
|
234
|
+
when :little then 'q<'
|
235
|
+
when :big then 'q>'
|
236
|
+
else
|
237
|
+
raise(TypeError,"unsupported endian #{@type.endian} for #{@type.inspect}")
|
238
|
+
end
|
239
|
+
else
|
240
|
+
raise(TypeError,"unsupported type size #{@type.size} for #{@type.inspect}")
|
241
|
+
end
|
242
|
+
|
243
|
+
each_slice(data) do |slice|
|
244
|
+
yield slice, slice.unpack1(pack_format)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
#
|
249
|
+
# @param [#each_byte] data
|
250
|
+
#
|
251
|
+
# @yield [raw, float]
|
252
|
+
#
|
253
|
+
# @yieldparam [String] raw
|
254
|
+
#
|
255
|
+
# @yieldparam [Float, nil] float
|
256
|
+
#
|
257
|
+
# @return [Enumerator]
|
258
|
+
#
|
259
|
+
def each_float(data)
|
260
|
+
return enum_for(__method__,data) unless block_given?
|
261
|
+
|
262
|
+
pack_format = case @type.endian
|
263
|
+
when :little
|
264
|
+
case @type.size
|
265
|
+
when 4 then 'e'
|
266
|
+
when 8 then 'E'
|
267
|
+
else
|
268
|
+
raise(TypeError,"unsupported type size #{@type.size} for #{@type.inspect}")
|
269
|
+
end
|
270
|
+
when :big
|
271
|
+
case @type.size
|
272
|
+
when 4 then 'g'
|
273
|
+
when 8 then 'G'
|
274
|
+
else
|
275
|
+
raise(TypeError,"unsupported type size #{@type.size} for #{@type.inspect}")
|
276
|
+
end
|
277
|
+
else
|
278
|
+
raise(TypeError,"unsupported endian #{@type.endian} for #{@type.inspect}")
|
279
|
+
end
|
280
|
+
|
281
|
+
each_slice(data) do |slice|
|
282
|
+
yield slice, slice.unpack1(pack_format)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
#
|
287
|
+
# @param [#each_byte] data
|
288
|
+
#
|
289
|
+
# @yield [raw, value]
|
290
|
+
#
|
291
|
+
# @yieldparam [String] raw
|
292
|
+
#
|
293
|
+
# @yieldparam [Integer, Float, nil] value
|
294
|
+
#
|
295
|
+
# @return [Enumerator]
|
296
|
+
#
|
297
|
+
def each(data,&block)
|
298
|
+
return enum_for(__method__,data) unless block
|
299
|
+
|
300
|
+
case @type
|
301
|
+
when Type::UInt
|
302
|
+
each_uint(data,&block)
|
303
|
+
when Type::Float
|
304
|
+
each_float(data,&block)
|
305
|
+
when Type::Int
|
306
|
+
each_int(data,&block)
|
307
|
+
else
|
308
|
+
raise(TypeError,"unsupported type: #{@type.inspect}")
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
end
|
313
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Hexdump
|
2
|
+
class Theme
|
3
|
+
#
|
4
|
+
# Represents an ANSI control sequence.
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
#
|
8
|
+
# @since 1.0.0
|
9
|
+
#
|
10
|
+
class ANSI
|
11
|
+
# ANSI reset control sequence
|
12
|
+
RESET = "\e[0m"
|
13
|
+
|
14
|
+
PARAMETERS = {
|
15
|
+
reset: RESET,
|
16
|
+
|
17
|
+
bold: "\e[1m",
|
18
|
+
faint: "\e[2m",
|
19
|
+
italic: "\e[3m",
|
20
|
+
underline: "\e[4m",
|
21
|
+
invert: "\e[7m",
|
22
|
+
strike: "\e[9m",
|
23
|
+
black: "\e[30m",
|
24
|
+
red: "\e[31m",
|
25
|
+
green: "\e[32m",
|
26
|
+
yellow: "\e[33m",
|
27
|
+
blue: "\e[34m",
|
28
|
+
magenta: "\e[35m",
|
29
|
+
cyan: "\e[36m",
|
30
|
+
white: "\e[37m",
|
31
|
+
|
32
|
+
on_black: "\e[40m",
|
33
|
+
on_red: "\e[41m",
|
34
|
+
on_green: "\e[42m",
|
35
|
+
on_yellow: "\e[43m",
|
36
|
+
on_blue: "\e[44m",
|
37
|
+
on_magenta: "\e[45m",
|
38
|
+
on_cyan: "\e[46m",
|
39
|
+
on_white: "\e[47m"
|
40
|
+
}
|
41
|
+
|
42
|
+
# The style name(s).
|
43
|
+
#
|
44
|
+
# @return [Symbol, Array<Symbol>] style
|
45
|
+
attr_reader :parameters
|
46
|
+
|
47
|
+
# The ANSI string.
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
attr_reader :string
|
51
|
+
|
52
|
+
#
|
53
|
+
# Initializes an ANSI control sequence.
|
54
|
+
#
|
55
|
+
# @param [Symbol, Array<Symbol>] parameters
|
56
|
+
# The ANSI styling parameters. See {PARAMETERS}.
|
57
|
+
#
|
58
|
+
def initialize(parameters)
|
59
|
+
@parameters = parameters
|
60
|
+
|
61
|
+
@string = String.new
|
62
|
+
|
63
|
+
Array(parameters).each do |name|
|
64
|
+
@string << PARAMETERS.fetch(name) do
|
65
|
+
raise(ArgumentError,"unknown ANSI parameter: #{name}")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#
|
71
|
+
# Returns the ANSI string.
|
72
|
+
#
|
73
|
+
# @return [String]
|
74
|
+
#
|
75
|
+
def to_s
|
76
|
+
@string
|
77
|
+
end
|
78
|
+
|
79
|
+
alias to_str to_s
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require_relative 'ansi'
|
2
|
+
|
3
|
+
require 'strscan'
|
4
|
+
|
5
|
+
module Hexdump
|
6
|
+
class Theme
|
7
|
+
#
|
8
|
+
# Represents a color highlighting rule.
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
#
|
12
|
+
# @since 1.0.0
|
13
|
+
#
|
14
|
+
class Rule
|
15
|
+
|
16
|
+
# The default style to apply to strings.
|
17
|
+
#
|
18
|
+
# @return [ANSI, nil]
|
19
|
+
attr_reader :style
|
20
|
+
|
21
|
+
# Highlighting rules for exact strings.
|
22
|
+
#
|
23
|
+
# @return [Hash{String => ANSI}]
|
24
|
+
attr_reader :highlight_strings
|
25
|
+
|
26
|
+
# Highlighting rules for matching substrings.
|
27
|
+
#
|
28
|
+
# @return [Hash{String => ANSI}]
|
29
|
+
attr_reader :highlight_regexps
|
30
|
+
|
31
|
+
#
|
32
|
+
# Initializes the color.
|
33
|
+
#
|
34
|
+
# @param [Symbol, Array<Symbol>] style
|
35
|
+
# The default style name(s). See {ANSI::PARAMETERS}.
|
36
|
+
#
|
37
|
+
# @param [Hash{String,Regexp => Symbol,Array<Symbol>}, nil] highlights
|
38
|
+
# Optional highlighting rules.
|
39
|
+
#
|
40
|
+
def initialize(style: nil, highlights: nil)
|
41
|
+
@reset = ANSI::RESET
|
42
|
+
|
43
|
+
@style = if style
|
44
|
+
ANSI.new(style)
|
45
|
+
end
|
46
|
+
|
47
|
+
@highlight_strings = {}
|
48
|
+
@highlight_regexps = {}
|
49
|
+
|
50
|
+
if highlights
|
51
|
+
highlights.each do |pattern,style|
|
52
|
+
highlight(pattern,style)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# The highlighting rules.
|
59
|
+
#
|
60
|
+
# @return [Hash{String,Regexp => ANSI}]
|
61
|
+
# The combination of {#highlight_strings} and {#highlight_regexps}.
|
62
|
+
#
|
63
|
+
def highlights
|
64
|
+
@highlight_strings.merge(@highlight_regexps)
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Adds a highlighting rule.
|
69
|
+
#
|
70
|
+
# @param [String, Regexp] pattern
|
71
|
+
# The exact String to highlight or regular expression to highlight.
|
72
|
+
#
|
73
|
+
# @param [Symbol, Array<Symbol>] style
|
74
|
+
# The style name(s). See {ANSI::PARAMETERS}.
|
75
|
+
#
|
76
|
+
# @raise [ArgumentError]
|
77
|
+
# The given pattern was not a String or Regexp.
|
78
|
+
#
|
79
|
+
# @example
|
80
|
+
# hexdump.style.numeric.highlight('00', :faint)
|
81
|
+
#
|
82
|
+
# @example
|
83
|
+
# hexdump.style.index.highlight(/00$/, [:white, :bold])
|
84
|
+
#
|
85
|
+
# @api public
|
86
|
+
#
|
87
|
+
def highlight(pattern,style)
|
88
|
+
ansi = ANSI.new(style)
|
89
|
+
|
90
|
+
case pattern
|
91
|
+
when String
|
92
|
+
@highlight_strings[pattern] = ansi
|
93
|
+
when Regexp
|
94
|
+
@highlight_regexps[pattern] = ansi
|
95
|
+
else
|
96
|
+
raise(ArgumentError,"pattern must be a String or Regexp: #{pattern.inspect}")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Applies coloring/highlighting to a string.
|
102
|
+
#
|
103
|
+
# @param [String] string
|
104
|
+
# The string to color/highlight.
|
105
|
+
#
|
106
|
+
# @return [String]
|
107
|
+
# The colored the string.
|
108
|
+
#
|
109
|
+
def apply(string)
|
110
|
+
if (!@highlight_strings.empty? || !@highlight_regexps.empty?)
|
111
|
+
apply_highlight(string)
|
112
|
+
elsif @style
|
113
|
+
"#{@style}#{string}#{@reset}"
|
114
|
+
else
|
115
|
+
string
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def apply_highlight(string)
|
122
|
+
if (ansi = @highlight_strings[string])
|
123
|
+
# highlight the whole string
|
124
|
+
"#{ansi}#{string}#{@reset}"
|
125
|
+
else
|
126
|
+
scanner = StringScanner.new(string)
|
127
|
+
new_string = String.new
|
128
|
+
|
129
|
+
until scanner.eos?
|
130
|
+
matched = false
|
131
|
+
|
132
|
+
@highlight_regexps.each do |regexp,ansi|
|
133
|
+
if (match = scanner.scan(regexp))
|
134
|
+
# highlight the match
|
135
|
+
new_string << "#{ansi}#{match}#{@reset}"
|
136
|
+
new_string << "#{@style}" unless scanner.eos?
|
137
|
+
matched = true
|
138
|
+
break
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
unless matched
|
143
|
+
# next char
|
144
|
+
new_string << scanner.getch
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
if @style
|
149
|
+
new_string.prepend(@style) unless new_string.start_with?("\e")
|
150
|
+
new_string << @reset unless new_string.end_with?(@reset)
|
151
|
+
end
|
152
|
+
|
153
|
+
new_string
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require_relative 'theme/rule'
|
2
|
+
|
3
|
+
module Hexdump
|
4
|
+
#
|
5
|
+
# Represents a hexdump theme (styling + highlighting).
|
6
|
+
#
|
7
|
+
# @api semipublic
|
8
|
+
#
|
9
|
+
# @since 1.0.0
|
10
|
+
#
|
11
|
+
class Theme
|
12
|
+
|
13
|
+
# The index styling/highlights.
|
14
|
+
#
|
15
|
+
# @return [Rule, nil]
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
attr_reader :index
|
19
|
+
|
20
|
+
# The numeric styling/highlights.
|
21
|
+
#
|
22
|
+
# @return [Rule, nil]
|
23
|
+
#
|
24
|
+
# @api public
|
25
|
+
attr_reader :numeric
|
26
|
+
|
27
|
+
# The chars styling/highlights.
|
28
|
+
#
|
29
|
+
# @return [Rule, nil]
|
30
|
+
#
|
31
|
+
# @api public
|
32
|
+
attr_reader :chars
|
33
|
+
|
34
|
+
#
|
35
|
+
# Initializes the theme.
|
36
|
+
#
|
37
|
+
# @param [Hash{:index,:numeric,:chars => Symbol,Array<Symbol>,nil}] style
|
38
|
+
# The default style of the index, numeric, and/or chars columns.
|
39
|
+
#
|
40
|
+
# @param [Hash{:index,:numeric,:chars => Hash{String,Regexp => Symbol,Array<Symbol>},nil}] highlights
|
41
|
+
# The highlighting rules for the index, numeric, and/or chars columns.
|
42
|
+
#
|
43
|
+
def initialize(style: {}, highlights: {})
|
44
|
+
@index = Rule.new(
|
45
|
+
style: style[:index],
|
46
|
+
highlights: highlights[:index]
|
47
|
+
)
|
48
|
+
|
49
|
+
@numeric = Rule.new(
|
50
|
+
style: style[:numeric],
|
51
|
+
highlights: highlights[:numeric]
|
52
|
+
)
|
53
|
+
|
54
|
+
@chars = Rule.new(
|
55
|
+
style: style[:chars],
|
56
|
+
highlights: highlights[:chars]
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|