kaitai-struct-visualizer 0.7 → 0.11

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.
@@ -1,272 +1,284 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  require 'set'
3
4
 
4
5
  require 'kaitai/struct/visualizer/version'
5
6
 
6
7
  module Kaitai::Struct::Visualizer
7
- class Node
8
- attr_accessor :id
9
- attr_reader :value
10
- attr_reader :level
11
- attr_reader :pos1
12
- attr_reader :pos2
13
- attr_reader :children
14
- attr_accessor :parent
15
- attr_accessor :type
16
-
17
- def initialize(tree, value, level, value_method = nil, pos1 = nil, pos2 = nil)
18
- @tree = tree
19
- @value = value
20
- @level = level
21
- @value_method = value_method
22
-
23
- unless pos1.nil? or pos2.nil?
24
- @pos1 = pos1
25
- @pos2 = pos2
26
- end
8
+ class Node
9
+ attr_accessor :id, :parent, :type
10
+ attr_reader :value, :level, :pos1, :pos2, :children
11
+
12
+ def initialize(tree, value, level, value_method = nil, pos1 = nil, pos2 = nil)
13
+ @tree = tree
14
+ @value = value
15
+ @level = level
16
+ @value_method = value_method
17
+
18
+ unless pos1.nil? || pos2.nil?
19
+ @pos1 = pos1
20
+ @pos2 = pos2
21
+ end
27
22
 
28
- @open = false
29
- @explored = false
23
+ @open = false
24
+ @explored = false
30
25
 
31
- @children = []
32
- end
26
+ @children = []
27
+ end
33
28
 
34
- def add(child)
35
- @children << child
36
- child.parent = self
37
- end
29
+ def add(child)
30
+ @children << child
31
+ child.parent = self
32
+ end
38
33
 
39
- def open?; @open; end
40
-
41
- def openable?
42
- not (
43
- @value.is_a?(Fixnum) or
44
- @value.is_a?(Bignum) or
45
- @value.is_a?(Float) or
46
- @value.is_a?(String) or
47
- @value.is_a?(Symbol) or
48
- @value === true or
49
- @value === false
50
- )
51
- end
34
+ def open?
35
+ @open
36
+ end
52
37
 
53
- def hex?
54
- @value.is_a?(String)
55
- end
38
+ def openable?
39
+ !(
40
+ @value.is_a?(Float) or
41
+ @value.is_a?(Integer) or
42
+ @value.is_a?(String) or
43
+ @value.is_a?(Symbol) or
44
+ @value == true or
45
+ @value == false
46
+ )
47
+ end
56
48
 
57
- def toggle
58
- if @open
59
- close
60
- else
61
- open
49
+ def hex?
50
+ @value.is_a?(String)
62
51
  end
63
- end
64
52
 
65
- def open
66
- return unless openable?
67
- explore
68
- @open = true if @explored
69
- end
53
+ def toggle
54
+ if @open
55
+ close
56
+ else
57
+ open
58
+ end
59
+ end
70
60
 
71
- def close
72
- @open = false
73
- end
61
+ def open
62
+ return unless openable?
74
63
 
75
- def draw(ui)
76
- print ' ' * level
77
- print(if @value.nil?
78
- '[?]'
79
- elsif open?
80
- '[-]'
81
- elsif openable?
82
- '[+]'
83
- else
84
- '[.]'
85
- end)
86
- print " #{@id}"
87
-
88
- pos = 2 * level + 4 + @id.length
89
-
90
- if open? or not openable?
91
- if @value.is_a?(Fixnum) or @value.is_a?(Bignum) or @value.is_a?(Float)
92
- print " = #{@value}"
93
- elsif @value.is_a?(Symbol)
94
- print " = #{@value}"
95
- elsif @value.is_a?(String)
96
- print ' = '
97
- pos += 3
98
- @str_mode = detect_str_mode unless @str_mode
99
- max_len = @tree.tree_width - pos
100
- case @str_mode
101
- when :str
102
- v = @value.encode('UTF-8')
103
- s = v[0, max_len]
104
- when :str_esc
105
- v = @value.encode('UTF-8')
106
- s = v.inspect[0, max_len]
107
- when :hex
108
- s = first_n_bytes_dump(@value, max_len / 3 + 1)
109
- else
110
- raise "Invalid str_mode: #{@str_mode.inspect}"
111
- end
112
- if s.length > max_len
113
- s = s[0, max_len - 1]
114
- s += '…'
115
- end
116
- print s
117
- elsif @value === true or @value === false
118
- print " = #{@value}"
119
- elsif @value.nil?
120
- print " = null"
121
- elsif @value.is_a?(Array)
122
- printf ' (%d = 0x%x entries)', @value.size, @value.size
123
- end
64
+ explore
65
+ @open = true if @explored
124
66
  end
125
67
 
126
- puts
127
- end
68
+ def close
69
+ @open = false
70
+ end
128
71
 
129
- def first_n_bytes_dump(s, n)
130
- i = 0
131
- r = ''
132
- s.each_byte { |x|
133
- r << sprintf('%02x ', x)
134
- i += 1
135
- break if i >= n
136
- }
137
- r
138
- end
72
+ def draw(_ui)
73
+ print ' ' * level
74
+ print(if @value.nil?
75
+ '[?]'
76
+ elsif open?
77
+ '[-]'
78
+ elsif openable?
79
+ '[+]'
80
+ else
81
+ '[.]'
82
+ end)
83
+ print " #{@id}"
84
+
85
+ pos = 2 * level + 4 + @id.length
86
+
87
+ if open? || !openable?
88
+ if @value.is_a?(Float) || @value.is_a?(Integer)
89
+ print " = #{@value}"
90
+ elsif @value.is_a?(Symbol)
91
+ print " = #{@value}"
92
+ elsif @value.is_a?(String)
93
+ print ' = '
94
+ pos += 3
95
+ @str_mode ||= detect_str_mode
96
+ max_len = @tree.tree_width - pos
97
+
98
+ case @str_mode
99
+ when :str
100
+ v = @value.encode('UTF-8')
101
+ s = v[0, max_len]
102
+ when :str_esc
103
+ v = @value.encode('UTF-8')
104
+ s = v.inspect[0, max_len]
105
+ when :hex
106
+ s = first_n_bytes_dump(@value, max_len / 3 + 1)
107
+ else
108
+ raise "Invalid str_mode: #{@str_mode.inspect}"
109
+ end
110
+
111
+ s = clamp_string(s, max_len)
112
+ print s
113
+ elsif (@value == true) || (@value == false)
114
+ print " = #{@value}"
115
+ elsif @value.nil?
116
+ print ' = null'
117
+ elsif @value.is_a?(Array)
118
+ printf ' (%d = 0x%x entries)', @value.size, @value.size
119
+ elsif @value.public_methods(false).include?(:to_s)
120
+ s = @value.to_s
121
+ pos += 2
122
+ max_len = @tree.tree_width - pos
123
+ if s.is_a?(String)
124
+ print ": #{clamp_string(s, max_len)}"
125
+ else
126
+ print ": #{clamp_string(s.class.to_s, max_len)}"
127
+ end
128
+ end
129
+ end
139
130
 
140
- ##
141
- # Empirically detects a mode that would be best to show a designated string
142
- def detect_str_mode
143
- if @value.encoding == Encoding::ASCII_8BIT
144
- :hex
145
- else
146
- :str_esc
131
+ puts
132
+ end
133
+
134
+ def clamp_string(s, max_len)
135
+ s ||= ''
136
+ if s.length > max_len
137
+ s = s[0, max_len - 1] || ''
138
+ s += '…'
139
+ end
140
+ s
147
141
  end
148
- end
149
142
 
150
- def io
151
- return @io if @io
143
+ def first_n_bytes_dump(s, n)
144
+ i = 0
145
+ r = +''
146
+ s.each_byte do |x|
147
+ r << format('%02x ', x)
148
+ i += 1
149
+ break if i >= n
150
+ end
151
+ r
152
+ end
152
153
 
153
- if @parent.nil?
154
- @io = @value._io
155
- else
156
- obj = @parent
157
- while not obj.value.respond_to?(:_io)
158
- obj = obj.parent
154
+ # Empirically detects a mode that would be best to show a designated string
155
+ def detect_str_mode
156
+ if @value.encoding == Encoding::ASCII_8BIT
157
+ :hex
158
+ else
159
+ :str_esc
159
160
  end
160
- @io = obj.value._io
161
161
  end
162
- end
163
162
 
164
- def explore
165
- return if @explored
163
+ def io
164
+ return @io if @io
166
165
 
167
- if @value.nil?
168
- @value = @parent.value.send(@value_method)
166
+ if @parent.nil?
167
+ @io = @value._io
168
+ else
169
+ obj = @parent
170
+ obj = obj.parent until obj.value.respond_to?(:_io)
171
+ @io = obj.value._io
172
+ end
169
173
  end
170
174
 
171
- @explored = true
172
-
173
- if @value.is_a?(Fixnum) or
174
- @value.is_a?(Bignum) or
175
- @value.is_a?(Float) or
176
- @value.is_a?(String) or
177
- @value == true or
178
- @value == false or
179
- @value.nil? or
180
- @value.is_a?(Symbol)
181
- clean_id = @id[0] == '@' ? @id[1..-1] : @id
182
- debug_el = @parent.value._debug[clean_id]
183
- #raise "Unable to get debugging aid for: #{@parent.value._debug.inspect} using ID '#{clean_id}'" unless debug_el
184
- if debug_el
185
- @pos1 = debug_el[:start]
186
- @pos2 = debug_el[:end]
175
+ def explore
176
+ return if @explored
177
+
178
+ if @value.nil?
179
+ @value = @parent.value.send(@value_method)
180
+ clean_id = @id[0] == '@' ? @id[1..-1] : @id
181
+ debug_el = @parent.value._debug[clean_id]
182
+ # raise "Unable to get debugging aid for: #{@parent.value._debug.inspect} using ID '#{clean_id}'" unless debug_el
183
+ if debug_el
184
+ @pos1 = debug_el[:start]
185
+ @pos2 = debug_el[:end]
186
+ end
187
187
  end
188
- elsif @value.is_a?(Array)
189
- # Bail out early for empty array: it doesn't have proper
190
- # debugging aids structure anyway
191
- return if @value.empty?
192
-
193
- clean_id = @id[0] == '@' ? @id[1..-1] : @id
194
- debug_el = @parent.value._debug[clean_id]
195
- raise "Unable to get debugging aid for array: #{@parent.value._debug.inspect} using ID '#{clean_id}'" unless debug_el
196
- aid = debug_el[:arr]
197
- raise "Unable to get debugging aid for array: #{debug_el.inspect}" unless aid
198
-
199
- max_val_digits = @value.size.to_s.size
200
- fmt = "%#{max_val_digits}d"
201
-
202
- @value.each_with_index { |el, i|
203
- aid_el = aid[i] || {}
204
- n = Node.new(@tree, el, level + 1, nil, aid_el[:start], aid_el[:end])
205
- n.id = sprintf(fmt, i)
206
- add(n)
207
- }
208
- else
209
- # Gather seq attributes
210
- @value.class::SEQ_FIELDS.each { |k|
211
- el = @value.instance_eval("@#{k}")
212
- aid = @value._debug[k]
213
- if aid
214
- aid_s = aid[:start]
215
- aid_e = aid[:end]
216
- else
217
- #raise "Unable to get debugging aid for '#{k}'"
218
- aid_s = nil
219
- aid_e = nil
188
+
189
+ @explored = true
190
+
191
+ if @value.is_a?(Float) ||
192
+ @value.is_a?(Integer) ||
193
+ @value.is_a?(String) ||
194
+ (@value == true) ||
195
+ (@value == false) ||
196
+ @value.nil? ||
197
+ @value.is_a?(Symbol)
198
+ clean_id = @id[0] == '@' ? @id[1..-1] : @id
199
+ debug_el = @parent.value._debug[clean_id]
200
+ # raise "Unable to get debugging aid for: #{@parent.value._debug.inspect} using ID '#{clean_id}'" unless debug_el
201
+ if debug_el
202
+ @pos1 = debug_el[:start]
203
+ @pos2 = debug_el[:end]
204
+ end
205
+ elsif @value.is_a?(Array)
206
+ # Bail out early for empty array: it doesn't have proper
207
+ # debugging aids structure anyway
208
+ return if @value.empty?
209
+
210
+ clean_id = @id[0] == '@' ? @id[1..-1] : @id
211
+ debug_el = @parent.value._debug[clean_id]
212
+ # raise "Unable to get debugging aid for array: #{@parent.value._debug.inspect} using ID '#{clean_id}'" unless debug_el
213
+
214
+ aid = (debug_el && debug_el[:arr]) || {}
215
+ # raise "Unable to get debugging aid for array: #{debug_el.inspect}" unless aid
216
+
217
+ max_val_digits = @value.size.to_s.size
218
+ fmt = "%#{max_val_digits}d"
219
+
220
+ @value.each_with_index do |el, i|
221
+ aid_el = aid[i] || {}
222
+ n = Node.new(@tree, el, level + 1, nil, aid_el[:start], aid_el[:end])
223
+ n.id = format(fmt, i)
224
+ add(n)
220
225
  end
221
- unless el.nil?
226
+ else
227
+ # Gather seq attributes
228
+ @value.class::SEQ_FIELDS.each do |k|
229
+ el = @value.instance_eval("@#{k}", __FILE__, __LINE__)
230
+ aid = @value._debug[k]
231
+ if aid
232
+ aid_s = aid[:start]
233
+ aid_e = aid[:end]
234
+ else
235
+ # raise "Unable to get debugging aid for '#{k}'"
236
+ aid_s = nil
237
+ aid_e = nil
238
+ end
239
+ next if el.nil?
240
+
222
241
  n = Node.new(@tree, el, level + 1, nil, aid_s, aid_e)
223
242
  n.id = k
224
243
  n.type = :seq
225
244
  add(n)
226
245
  end
227
- }
228
-
229
- attrs = Set.new(@value.class::SEQ_FIELDS)
230
-
231
- # Gather instances
232
- common_meths = Set.new
233
- @value.class.ancestors.each { |cl|
234
- next if cl == @value.class
235
- common_meths.merge(cl.instance_methods)
236
- }
237
- inst_meths = Set.new(@value.public_methods) - common_meths
238
- inst_meths.each { |meth|
239
- k = meth.to_s
240
- next if k =~ /^_/ or attrs.include?(k)
241
- n = Node.new(@tree, nil, level + 1, meth)
242
- n.id = k
243
- n.type = :instance
244
- add(n)
245
- }
246
+
247
+ attrs = Set.new(@value.class::SEQ_FIELDS)
248
+
249
+ # Gather instances
250
+ prop_meths = @value.public_methods(false)
251
+ prop_meths.each do |meth|
252
+ k = meth.to_s
253
+ # NB: we don't need to consider `_unnamed*` attributes here
254
+ # (https://github.com/kaitai-io/kaitai_struct/issues/1064) because
255
+ # only `seq` fields can be unnamed, not `instances`
256
+ next if k.start_with?('_') || attrs.include?(k) || meth == :to_s
257
+
258
+ n = Node.new(@tree, nil, level + 1, meth)
259
+ n.id = k
260
+ n.type = :instance
261
+ add(n)
262
+ end
263
+ end
246
264
  end
247
- end
248
265
 
249
- ##
250
- # Determine total height of an element, including all children if
251
- # it's open and visible
252
- def height
253
- if @open
254
- r = 1
255
- @children.each { |n| r += n.height }
256
- r
257
- else
258
- 1
266
+ # Determine total height of an element, including all children if it's open and visible
267
+ def height
268
+ if @open
269
+ r = 1
270
+ @children.each { |n| r += n.height }
271
+ r
272
+ else
273
+ 1
274
+ end
259
275
  end
260
- end
261
276
 
262
- ##
263
- # Find out last (deepest) descendant of current node
264
- def last_descendant
265
- n = self
266
- while n.open?
267
- n = n.children.last
277
+ # Find out last (deepest) descendant of current node
278
+ def last_descendant
279
+ n = self
280
+ n = n.children.last while n.open?
281
+ n
268
282
  end
269
- n
270
283
  end
271
284
  end
272
- end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kaitai::Struct::Visualizer
4
+ # Recursively convert object received from Kaitai Struct to a hash.
5
+ # Used by ksdump to prepare data for JSON/XML/YAML output.
6
+ def self.obj_to_h(obj)
7
+ if (obj == true) || (obj == false) || obj.is_a?(Numeric) || obj.nil?
8
+ obj
9
+ elsif obj.is_a?(Symbol)
10
+ obj.to_s
11
+ elsif obj.is_a?(String)
12
+ if obj.encoding == Encoding::ASCII_8BIT
13
+ r = +''
14
+ obj.each_byte { |x| r << format('%02X ', x) }
15
+ r.chop!
16
+ r
17
+ else
18
+ obj.encode('UTF-8')
19
+ end
20
+ elsif obj.is_a?(Array)
21
+ obj.map { |x| obj_to_h(x) }
22
+ else
23
+ return "OPAQUE (#{obj.class})" unless obj.is_a?(Kaitai::Struct::Struct)
24
+
25
+ root = {}
26
+
27
+ prop_meths = obj.public_methods(false)
28
+ prop_meths.sort.each do |meth|
29
+ k = meth.to_s
30
+ next if (k.start_with?('_') && !k.start_with?('_unnamed')) || meth == :to_s
31
+
32
+ el = obj.send(meth)
33
+ v = obj_to_h(el)
34
+ root[k] = v unless v.nil?
35
+ end
36
+
37
+ root
38
+ end
39
+ end
40
+ end
@@ -1,42 +1,40 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'kaitai/struct/visualizer/version'
2
4
  require 'kaitai/tui'
3
5
  require 'kaitai/struct/visualizer/tree'
6
+ require 'kaitai/struct/visualizer/ks_error_matcher'
4
7
 
5
8
  # TODO: should be inside compiled files
6
9
  require 'zlib'
7
10
  require 'stringio'
8
11
 
9
12
  module Kaitai::Struct::Visualizer
13
+ # Base class for everything that deals with compiling .ksy and parsing stuff as object tree.
14
+ class Parser
15
+ attr_reader :data
16
+
17
+ def initialize(compiler, bin_fn, formats_fn, opts)
18
+ @compiler = compiler
19
+ @bin_fn = bin_fn
20
+ @formats_fn = formats_fn
21
+ @opts = opts
22
+ end
10
23
 
11
- ##
12
- # Base class for everything that deals with compiling .ksy and parsing
13
- # stuff as object tree.
14
- class Parser
15
- attr_reader :data
16
-
17
- def initialize(compiler, bin_fn, formats_fn, opts)
18
- @compiler = compiler
19
- @bin_fn = bin_fn
20
- @formats_fn = formats_fn
21
- @opts = opts
22
- end
24
+ def load
25
+ main_class_name = @compiler.compile_formats_if(@formats_fn)
23
26
 
24
- def load
25
- main_class_name = @compiler.compile_formats(@formats_fn)
27
+ main_class = Kernel.const_get(main_class_name)
28
+ @data = main_class.from_file(@bin_fn)
26
29
 
27
- main_class = Kernel::const_get(main_class_name)
28
- @data = main_class.from_file(@bin_fn)
30
+ load_exc = nil
31
+ begin
32
+ @data._read
33
+ rescue Kaitai::Struct::Visualizer::KSErrorMatcher => e
34
+ load_exc = e
35
+ end
29
36
 
30
- load_exc = nil
31
- begin
32
- @data._read
33
- rescue EOFError => e
34
- load_exc = e
35
- rescue Kaitai::Struct::Stream::UnexpectedDataError => e
36
- load_exc = e
37
+ load_exc
37
38
  end
38
-
39
- return load_exc
40
39
  end
41
40
  end
42
- end