kaitai-struct-visualizer 0.5 → 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,268 +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
27
11
 
28
- @open = false
29
- @explored = false
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
30
17
 
31
- @children = []
32
- end
18
+ unless pos1.nil? || pos2.nil?
19
+ @pos1 = pos1
20
+ @pos2 = pos2
21
+ end
33
22
 
34
- def add(child)
35
- @children << child
36
- child.parent = self
37
- end
23
+ @open = false
24
+ @explored = false
38
25
 
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
26
+ @children = []
27
+ end
52
28
 
53
- def hex?
54
- @value.is_a?(String)
55
- end
29
+ def add(child)
30
+ @children << child
31
+ child.parent = self
32
+ end
56
33
 
57
- def toggle
58
- if @open
59
- close
60
- else
61
- open
34
+ def open?
35
+ @open
62
36
  end
63
- end
64
37
 
65
- def open
66
- return unless openable?
67
- explore
68
- @open = true if @explored
69
- 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
70
48
 
71
- def close
72
- @open = false
73
- end
49
+ def hex?
50
+ @value.is_a?(String)
51
+ end
74
52
 
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
53
+ def toggle
54
+ if @open
55
+ close
56
+ else
57
+ open
123
58
  end
124
59
  end
125
60
 
126
- puts
127
- end
61
+ def open
62
+ return unless openable?
128
63
 
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
64
+ explore
65
+ @open = true if @explored
66
+ end
139
67
 
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
68
+ def close
69
+ @open = false
147
70
  end
148
- end
149
71
 
150
- def io
151
- return @io if @io
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
152
110
 
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
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
130
+
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
141
+ end
142
+
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
153
+
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
- clean_id = @id[0] == '@' ? @id[1..-1] : @id
190
- debug_el = @parent.value._debug[clean_id]
191
- raise "Unable to get debugging aid for array: #{@parent.value._debug.inspect} using ID '#{clean_id}'" unless debug_el
192
- aid = debug_el[:arr]
193
- raise "Unable to get debugging aid for array: #{debug_el.inspect}" unless aid
194
-
195
- max_val_digits = @value.size.to_s.size
196
- fmt = "%#{max_val_digits}d"
197
-
198
- @value.each_with_index { |el, i|
199
- aid_el = aid[i] || {}
200
- n = Node.new(@tree, el, level + 1, nil, aid_el[:start], aid_el[:end])
201
- n.id = sprintf(fmt, i)
202
- add(n)
203
- }
204
- else
205
- # Gather seq attributes
206
- @value.class::SEQ_FIELDS.each { |k|
207
- el = @value.instance_eval("@#{k}")
208
- aid = @value._debug[k]
209
- if aid
210
- aid_s = aid[:start]
211
- aid_e = aid[:end]
212
- else
213
- #raise "Unable to get debugging aid for '#{k}'"
214
- aid_s = nil
215
- 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]
216
204
  end
217
- unless el.nil?
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)
225
+ end
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
+
218
241
  n = Node.new(@tree, el, level + 1, nil, aid_s, aid_e)
219
242
  n.id = k
220
243
  n.type = :seq
221
244
  add(n)
222
245
  end
223
- }
224
-
225
- attrs = Set.new(@value.class::SEQ_FIELDS)
226
-
227
- # Gather instances
228
- common_meths = Set.new
229
- @value.class.ancestors.each { |cl|
230
- next if cl == @value.class
231
- common_meths.merge(cl.instance_methods)
232
- }
233
- inst_meths = Set.new(@value.public_methods) - common_meths
234
- inst_meths.each { |meth|
235
- k = meth.to_s
236
- next if k =~ /^_/ or attrs.include?(k)
237
- n = Node.new(@tree, nil, level + 1, meth)
238
- n.id = k
239
- n.type = :instance
240
- add(n)
241
- }
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
242
264
  end
243
- end
244
265
 
245
- ##
246
- # Determine total height of an element, including all children if
247
- # it's open and visible
248
- def height
249
- if @open
250
- r = 1
251
- @children.each { |n| r += n.height }
252
- r
253
- else
254
- 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
255
275
  end
256
- end
257
276
 
258
- ##
259
- # Find out last (deepest) descendant of current node
260
- def last_descendant
261
- n = self
262
- while n.open?
263
- 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
264
282
  end
265
- n
266
283
  end
267
284
  end
268
- 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
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kaitai/struct/visualizer/version'
4
+ require 'kaitai/tui'
5
+ require 'kaitai/struct/visualizer/tree'
6
+ require 'kaitai/struct/visualizer/ks_error_matcher'
7
+
8
+ # TODO: should be inside compiled files
9
+ require 'zlib'
10
+ require 'stringio'
11
+
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
23
+
24
+ def load
25
+ main_class_name = @compiler.compile_formats_if(@formats_fn)
26
+
27
+ main_class = Kernel.const_get(main_class_name)
28
+ @data = main_class.from_file(@bin_fn)
29
+
30
+ load_exc = nil
31
+ begin
32
+ @data._read
33
+ rescue Kaitai::Struct::Visualizer::KSErrorMatcher => e
34
+ load_exc = e
35
+ end
36
+
37
+ load_exc
38
+ end
39
+ end
40
+ end