kaitai-struct-visualizer 0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +57 -0
- data/bin/ksv +20 -0
- data/kaitai-struct-visualizer.gemspec +37 -0
- data/lib/kaitai/struct/visualizer.rb +2 -0
- data/lib/kaitai/struct/visualizer/hex_viewer.rb +294 -0
- data/lib/kaitai/struct/visualizer/node.rb +242 -0
- data/lib/kaitai/struct/visualizer/tree.rb +253 -0
- data/lib/kaitai/struct/visualizer/version.rb +7 -0
- data/lib/kaitai/struct/visualizer/visualizer.rb +47 -0
- data/lib/kaitai/struct/visualizer/visualizer_main.rb +24 -0
- data/lib/kaitai/struct/visualizer/visualizer_ruby.rb +18 -0
- data/lib/kaitai/tui.rb +169 -0
- metadata +105 -0
data/README.md
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# Kaitai Struct: visualizer
|
2
|
+
|
3
|
+
This is a simple visualizer for [Kaitai Struct](https://github.com/kaitai-io/kaitai_struct) project.
|
4
|
+
|
5
|
+
Kaitai Struct is a declarative language used for describe various
|
6
|
+
binary data structures, laid out in files or in memory: i.e. binary
|
7
|
+
file formats, network stream packet formats, etc.
|
8
|
+
|
9
|
+
The main idea is that a particular format is described in Kaitai
|
10
|
+
Struct language (`.ksy` files) only once and then can be compiled with
|
11
|
+
this compiler into source files in one of the supported programming
|
12
|
+
languages. These modules will include a generated code for a parser
|
13
|
+
that can read described data structure from a file / stream and give
|
14
|
+
access to it in a nice, easy-to-comprehend API.
|
15
|
+
|
16
|
+
Please refer to [documentation in Kaitai Struct project](https://github.com/kaitai-io/kaitai_struct)
|
17
|
+
for details on `.ksy` files and general usage patterns.
|
18
|
+
|
19
|
+
## Downloading and installing
|
20
|
+
|
21
|
+
### Generic
|
22
|
+
|
23
|
+
KS visualizer is written in Ruby and is available as .gem package.
|
24
|
+
|
25
|
+
### Source code
|
26
|
+
|
27
|
+
If you're interested in developing the visualizer itself, you can check
|
28
|
+
out source code in repository:
|
29
|
+
|
30
|
+
git clone https://github.com/kaitai-io/kaitai_struct_visualizer
|
31
|
+
|
32
|
+
## Usage
|
33
|
+
|
34
|
+
`ksv <binary-file> <ksy-file>...`
|
35
|
+
|
36
|
+
## Licensing
|
37
|
+
|
38
|
+
Kaitai Struct visualizer itself is copyright (C) 2015-2016 Kaitai
|
39
|
+
Project.
|
40
|
+
|
41
|
+
This program is free software: you can redistribute it and/or modify
|
42
|
+
it under the terms of the GNU General Public License as published by
|
43
|
+
the Free Software Foundation, either version 3 of the License, or (at
|
44
|
+
your option) any later version.
|
45
|
+
|
46
|
+
This program is distributed in the hope that it will be useful, but
|
47
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of
|
48
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
49
|
+
General Public License for more details.
|
50
|
+
|
51
|
+
You should have received a copy of the GNU General Public License
|
52
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
53
|
+
|
54
|
+
Note that it applies only to compiler itself, not `.ksy` input files
|
55
|
+
that one supplies in normal process of compilation, nor to compiler's
|
56
|
+
output files — that consitutes normal usage process and you obviously
|
57
|
+
keep copyright to both.
|
data/bin/ksv
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Some additional magic to make it work right after repo checkout,
|
4
|
+
# without installation to proper Ruby library dirs
|
5
|
+
|
6
|
+
full_bin_path = File.realpath($PROGRAM_NAME)
|
7
|
+
dist_path = File.expand_path(File.dirname(full_bin_path) + '/..')
|
8
|
+
|
9
|
+
$LOAD_PATH << "#{dist_path}/lib"
|
10
|
+
$LOAD_PATH << "#{dist_path}/../runtime/ruby/lib"
|
11
|
+
|
12
|
+
require 'kaitai/struct/visualizer'
|
13
|
+
|
14
|
+
if ARGV.size < 2
|
15
|
+
puts "Usage: #{File.basename($PROGRAM_NAME)} <file_to_parse.bin> <format.yaml>..."
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
|
19
|
+
v = Kaitai::Struct::Visualizer::ExternalCompilerVisualizer.new(ARGV[0], ARGV[1..-1])
|
20
|
+
v.run
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require File.expand_path("../lib/kaitai/struct/visualizer/version", __FILE__)
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
Gem::Specification.new { |s|
|
7
|
+
s.name = 'kaitai-struct-visualizer'
|
8
|
+
s.version = Kaitai::Struct::Visualizer::VERSION
|
9
|
+
s.date = Date.today.to_s
|
10
|
+
|
11
|
+
s.authors = ['Mikhail Yakshin']
|
12
|
+
s.email = 'greycat@kaitai.io'
|
13
|
+
|
14
|
+
s.homepage = 'http://kaitai.io'
|
15
|
+
s.summary = 'Advanced hex viewer and binary structure exploration tool (visualizer) using Kaitai Struct ksy files'
|
16
|
+
s.license = 'GPL-3.0+'
|
17
|
+
s.description = <<-EOF
|
18
|
+
Kaitai Struct is a declarative language used for describe various binary data structures, laid out in files or in memory: i.e. binary file formats, network stream packet formats, etc.
|
19
|
+
|
20
|
+
The main idea is that a particular format is described in Kaitai Struct language (.ksy file) and then can be compiled with ksc into source files in one of the supported programming languages. These modules will include a generated code for a parser that can read described data structure from a file / stream and give access to it in a nice, easy-to-comprehend API.
|
21
|
+
|
22
|
+
This package is a visualizer tool for .ksy files. Given a particular binary file and .ksy file(s) that describe its format, it can visualize internal data structures in a tree form and a multi-level highlight hex viewer.
|
23
|
+
EOF
|
24
|
+
|
25
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
26
|
+
s.require_paths = ['lib']
|
27
|
+
|
28
|
+
s.files = `git ls-files`.split("\n")
|
29
|
+
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
30
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
31
|
+
|
32
|
+
s.add_development_dependency "bundler", "~> 1.3"
|
33
|
+
s.add_development_dependency 'rake', '~> 10'
|
34
|
+
# s.add_development_dependency 'rspec', '~> 3'
|
35
|
+
|
36
|
+
s.add_dependency 'kaitai-struct', "~> #{s.version}"
|
37
|
+
}
|
@@ -0,0 +1,294 @@
|
|
1
|
+
require 'kaitai/struct/visualizer/version'
|
2
|
+
|
3
|
+
module Kaitai::Struct::Visualizer
|
4
|
+
class HexViewer
|
5
|
+
def initialize(ui, buf, shift_x = 0, tree = nil)
|
6
|
+
@ui = ui
|
7
|
+
@buf = buf
|
8
|
+
@shift_x = shift_x
|
9
|
+
@tree = tree
|
10
|
+
|
11
|
+
@embedded = not(tree.nil?)
|
12
|
+
@max_scr_ln = @ui.rows - 3
|
13
|
+
|
14
|
+
@addr = 0
|
15
|
+
@scroll_y = 0
|
16
|
+
reset_cur
|
17
|
+
raise if @cur_x.nil?
|
18
|
+
end
|
19
|
+
|
20
|
+
def buf=(buf)
|
21
|
+
@buf = buf
|
22
|
+
end
|
23
|
+
|
24
|
+
def addr; @addr; end
|
25
|
+
def addr=(a)
|
26
|
+
@addr = a
|
27
|
+
reset_cur
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset_cur
|
31
|
+
@cur_y = addr_to_row(@addr)
|
32
|
+
@cur_x = addr_to_col(@addr)
|
33
|
+
end
|
34
|
+
|
35
|
+
def highlight(regions)
|
36
|
+
highlight_hide
|
37
|
+
@hl_regions = regions
|
38
|
+
highlight_show
|
39
|
+
end
|
40
|
+
|
41
|
+
def ensure_visible
|
42
|
+
scr_y = row_to_scr(@cur_y)
|
43
|
+
if scr_y < 0
|
44
|
+
@scroll_y = @cur_y
|
45
|
+
redraw
|
46
|
+
highlight_show
|
47
|
+
elsif scr_y > @max_scr_ln
|
48
|
+
@scroll_y = @cur_y - @max_scr_ln
|
49
|
+
redraw
|
50
|
+
highlight_show
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def run
|
55
|
+
c = nil
|
56
|
+
loop {
|
57
|
+
@ui.goto(0, @max_scr_ln + 1)
|
58
|
+
printf "%06x (%d, %d)", @addr, @cur_x, @cur_y
|
59
|
+
|
60
|
+
@ui.goto(col_to_col_char(@cur_x), row_to_scr(@cur_y))
|
61
|
+
c = @ui.read_char_mapped
|
62
|
+
case c
|
63
|
+
when :tab
|
64
|
+
return if @embedded
|
65
|
+
when :left_arrow
|
66
|
+
if @addr > 0
|
67
|
+
@addr -= 1
|
68
|
+
@cur_x -= 1
|
69
|
+
if @cur_x < 0
|
70
|
+
@cur_y -= 1
|
71
|
+
@cur_x = PER_LINE - 1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
when :right_arrow
|
75
|
+
if @addr < @buf.size
|
76
|
+
@addr += 1
|
77
|
+
@cur_x += 1
|
78
|
+
if @cur_x >= PER_LINE
|
79
|
+
@cur_y += 1
|
80
|
+
@cur_x = 0
|
81
|
+
end
|
82
|
+
end
|
83
|
+
when :up_arrow
|
84
|
+
@addr -= PER_LINE
|
85
|
+
@cur_y -= 1
|
86
|
+
clamp_cursor
|
87
|
+
when :down_arrow
|
88
|
+
@addr += PER_LINE
|
89
|
+
@cur_y += 1
|
90
|
+
clamp_cursor
|
91
|
+
when :pg_dn
|
92
|
+
@addr += PER_LINE * PAGE_ROWS
|
93
|
+
@cur_y += PAGE_ROWS
|
94
|
+
clamp_cursor
|
95
|
+
when :pg_up
|
96
|
+
@addr -= PER_LINE * PAGE_ROWS
|
97
|
+
@cur_y -= PAGE_ROWS
|
98
|
+
clamp_cursor
|
99
|
+
when :home
|
100
|
+
if @cur_x == 0
|
101
|
+
@addr = 0
|
102
|
+
@cur_y = 0
|
103
|
+
else
|
104
|
+
@addr -= @cur_x
|
105
|
+
@cur_x = 0
|
106
|
+
end
|
107
|
+
clamp_cursor
|
108
|
+
when :end
|
109
|
+
if @cur_x == PER_LINE - 1
|
110
|
+
@addr = @buf.size - 1
|
111
|
+
reset_cur
|
112
|
+
else
|
113
|
+
@addr = @addr - @cur_x + PER_LINE - 1
|
114
|
+
@cur_x = PER_LINE - 1
|
115
|
+
end
|
116
|
+
clamp_cursor
|
117
|
+
when 'q'
|
118
|
+
@tree.do_exit if @tree
|
119
|
+
return
|
120
|
+
end
|
121
|
+
|
122
|
+
ensure_visible
|
123
|
+
}
|
124
|
+
end
|
125
|
+
|
126
|
+
def clamp_cursor
|
127
|
+
if @addr < 0
|
128
|
+
@addr = 0
|
129
|
+
@cur_x = 0
|
130
|
+
@cur_y = 0
|
131
|
+
elsif @addr >= @buf.size
|
132
|
+
@addr = @buf.size - 1
|
133
|
+
reset_cur
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
PER_LINE = 16
|
138
|
+
PER_GROUP = 4
|
139
|
+
PAGE_ROWS = 20
|
140
|
+
FMT = "%06x: %-#{PER_LINE * 3}s| %-#{PER_LINE}s\n"
|
141
|
+
|
142
|
+
def self.line_width
|
143
|
+
#6 + 2 + 3 * PER_LINE + 2 + PER_LINE
|
144
|
+
10 + 4 * PER_LINE
|
145
|
+
end
|
146
|
+
|
147
|
+
def col_to_col_hex(c)
|
148
|
+
#6 + 2 + 3 * c
|
149
|
+
@shift_x + 8 + 3 * c
|
150
|
+
end
|
151
|
+
|
152
|
+
def col_to_col_char(c)
|
153
|
+
#6 + 2 + 3 * PER_LINE + 2
|
154
|
+
@shift_x + 10 + 3 * PER_LINE + c
|
155
|
+
end
|
156
|
+
|
157
|
+
def row_to_scr(r)
|
158
|
+
r - @scroll_y
|
159
|
+
end
|
160
|
+
|
161
|
+
def redraw
|
162
|
+
i = row_col_to_addr(@scroll_y, 0)
|
163
|
+
row = 0
|
164
|
+
|
165
|
+
while row <= @max_scr_ln do
|
166
|
+
line = @buf[i, PER_LINE]
|
167
|
+
return unless line
|
168
|
+
|
169
|
+
@ui.goto(@shift_x, row)
|
170
|
+
|
171
|
+
hex = line.bytes.map { |x| sprintf('%02x', x) }.join(' ')
|
172
|
+
char = line.bytes.map { |x| byte_to_display_char(x) }.join
|
173
|
+
|
174
|
+
printf FMT, i, hex, char
|
175
|
+
i += PER_LINE
|
176
|
+
row += 1
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def each_highlight_region
|
181
|
+
return if @hl_regions.nil?
|
182
|
+
n = @hl_regions.size
|
183
|
+
(n - 1).downto(0).each { |i|
|
184
|
+
p1 = @hl_regions[i][0]
|
185
|
+
p2 = @hl_regions[i][1]
|
186
|
+
yield i, p1, p2 unless p1.nil?
|
187
|
+
}
|
188
|
+
end
|
189
|
+
|
190
|
+
def highlight_hide
|
191
|
+
each_highlight_region { |i, p1, p2|
|
192
|
+
highlight_draw(p1, p2)
|
193
|
+
}
|
194
|
+
end
|
195
|
+
|
196
|
+
HIGHLIGHT_COLOR = [
|
197
|
+
:gray14,
|
198
|
+
:gray11,
|
199
|
+
:gray8,
|
200
|
+
:gray5,
|
201
|
+
:gray2,
|
202
|
+
]
|
203
|
+
|
204
|
+
def highlight_show
|
205
|
+
each_highlight_region { |i, p1, p2|
|
206
|
+
@ui.bg_color = HIGHLIGHT_COLOR[i]
|
207
|
+
@ui.fg_color = :black
|
208
|
+
highlight_draw(p1, p2)
|
209
|
+
}
|
210
|
+
@ui.reset_colors
|
211
|
+
end
|
212
|
+
|
213
|
+
def highlight_draw(p1, p2)
|
214
|
+
r = row_to_scr(addr_to_row(p1))
|
215
|
+
return if r > @max_scr_ln
|
216
|
+
if r < 0
|
217
|
+
c = 0
|
218
|
+
r = 0
|
219
|
+
i = row_col_to_addr(@scroll_y, 0)
|
220
|
+
return if i >= p2
|
221
|
+
else
|
222
|
+
c = addr_to_col(p1)
|
223
|
+
i = p1
|
224
|
+
end
|
225
|
+
|
226
|
+
highlight_draw_hex(r, c, i, p2)
|
227
|
+
highlight_draw_char(r, c, i, p2)
|
228
|
+
end
|
229
|
+
|
230
|
+
def highlight_draw_hex(r, c, i, p2)
|
231
|
+
@ui.goto(col_to_col_hex(c), r)
|
232
|
+
while i < p2
|
233
|
+
v = byte_at(i)
|
234
|
+
return if v.nil?
|
235
|
+
printf('%02x ', v)
|
236
|
+
c += 1
|
237
|
+
if c >= PER_LINE
|
238
|
+
c = 0
|
239
|
+
r += 1
|
240
|
+
return if r > @max_scr_ln
|
241
|
+
@ui.goto(col_to_col_hex(c), r)
|
242
|
+
end
|
243
|
+
i += 1
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def highlight_draw_char(r, c, i, p2)
|
248
|
+
@ui.goto(col_to_col_char(c), r)
|
249
|
+
|
250
|
+
while i < p2
|
251
|
+
v = byte_at(i)
|
252
|
+
return if v.nil?
|
253
|
+
print byte_to_display_char(v)
|
254
|
+
c += 1
|
255
|
+
if c >= PER_LINE
|
256
|
+
c = 0
|
257
|
+
r += 1
|
258
|
+
return if r > @max_scr_ln
|
259
|
+
@ui.goto(col_to_col_char(c), r)
|
260
|
+
end
|
261
|
+
i += 1
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def byte_at(i)
|
266
|
+
v = @buf[i]
|
267
|
+
if v.nil?
|
268
|
+
nil
|
269
|
+
else
|
270
|
+
v.ord
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def byte_to_display_char(x)
|
275
|
+
if x < 0x20 or x >= 0x7f
|
276
|
+
'.'
|
277
|
+
else
|
278
|
+
x.chr
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
def addr_to_row(addr)
|
283
|
+
addr / PER_LINE
|
284
|
+
end
|
285
|
+
|
286
|
+
def addr_to_col(addr)
|
287
|
+
addr % PER_LINE
|
288
|
+
end
|
289
|
+
|
290
|
+
def row_col_to_addr(row, col)
|
291
|
+
row * PER_LINE + col
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
@@ -0,0 +1,242 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
require 'kaitai/struct/visualizer/version'
|
5
|
+
|
6
|
+
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
|
+
|
16
|
+
def initialize(tree, value, level, value_method = nil, pos1 = nil, pos2 = nil)
|
17
|
+
@tree = tree
|
18
|
+
@value = value
|
19
|
+
@level = level
|
20
|
+
@value_method = value_method
|
21
|
+
|
22
|
+
unless pos1.nil? or pos2.nil?
|
23
|
+
@pos1 = pos1
|
24
|
+
@pos2 = pos2
|
25
|
+
end
|
26
|
+
|
27
|
+
@open = false
|
28
|
+
@explored = false
|
29
|
+
|
30
|
+
@children = []
|
31
|
+
end
|
32
|
+
|
33
|
+
def add(child)
|
34
|
+
@children << child
|
35
|
+
child.parent = self
|
36
|
+
end
|
37
|
+
|
38
|
+
def open?; @open; end
|
39
|
+
|
40
|
+
def openable?
|
41
|
+
not (
|
42
|
+
@value.is_a?(Fixnum) or
|
43
|
+
@value.is_a?(String) or
|
44
|
+
@value.is_a?(Symbol) or
|
45
|
+
@value === true or
|
46
|
+
@value === false
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
def hex?
|
51
|
+
@value.is_a?(String)
|
52
|
+
end
|
53
|
+
|
54
|
+
def toggle
|
55
|
+
if @open
|
56
|
+
close
|
57
|
+
else
|
58
|
+
open
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def open
|
63
|
+
return unless openable?
|
64
|
+
explore
|
65
|
+
@open = true if @explored
|
66
|
+
end
|
67
|
+
|
68
|
+
def close
|
69
|
+
@open = false
|
70
|
+
end
|
71
|
+
|
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 @value.is_a?(Fixnum)
|
88
|
+
print " = #{@value}"
|
89
|
+
elsif @value.is_a?(Symbol)
|
90
|
+
print " = #{@value}"
|
91
|
+
elsif @value.is_a?(String)
|
92
|
+
print ' = '
|
93
|
+
pos += 3
|
94
|
+
@str_mode = detect_str_mode unless @str_mode
|
95
|
+
max_len = @tree.tree_width - pos
|
96
|
+
case @str_mode
|
97
|
+
when :str
|
98
|
+
s = @value[0, max_len]
|
99
|
+
when :str_esc
|
100
|
+
s = @value.inspect[0, max_len]
|
101
|
+
when :hex
|
102
|
+
s = first_n_bytes_dump(@value, max_len / 3 + 1)
|
103
|
+
else
|
104
|
+
raise "Invalid str_mode: #{@str_mode.inspect}"
|
105
|
+
end
|
106
|
+
if s.length > max_len
|
107
|
+
s = s[0, max_len - 1]
|
108
|
+
s += '…'
|
109
|
+
end
|
110
|
+
print s
|
111
|
+
elsif @value === true or @value === false
|
112
|
+
print " = #{@value}"
|
113
|
+
elsif @value.is_a?(Array)
|
114
|
+
printf ' (%d = 0x%x entries)', @value.size, @value.size
|
115
|
+
end
|
116
|
+
|
117
|
+
puts
|
118
|
+
end
|
119
|
+
|
120
|
+
def first_n_bytes_dump(s, n)
|
121
|
+
i = 0
|
122
|
+
r = ''
|
123
|
+
s.each_byte { |x|
|
124
|
+
r << sprintf('%02x ', x)
|
125
|
+
i += 1
|
126
|
+
break if i >= n
|
127
|
+
}
|
128
|
+
r
|
129
|
+
end
|
130
|
+
|
131
|
+
##
|
132
|
+
# Empirically detects a mode that would be best to show a designated string
|
133
|
+
def detect_str_mode
|
134
|
+
if @value.encoding == Encoding::ASCII_8BIT
|
135
|
+
:hex
|
136
|
+
else
|
137
|
+
:str_esc
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def io
|
142
|
+
return @io if @io
|
143
|
+
|
144
|
+
if @parent.nil?
|
145
|
+
@io = @value._io
|
146
|
+
else
|
147
|
+
obj = @parent
|
148
|
+
while not obj.value.respond_to?(:_io)
|
149
|
+
obj = obj.parent
|
150
|
+
end
|
151
|
+
@io = obj.value._io
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def explore
|
156
|
+
return if @explored
|
157
|
+
|
158
|
+
if @value.nil?
|
159
|
+
@value = @parent.value.send(@value_method)
|
160
|
+
end
|
161
|
+
|
162
|
+
if @value.is_a?(Fixnum) or @value.is_a?(String) or @value.is_a?(Symbol)
|
163
|
+
# do nothing else
|
164
|
+
elsif @value.is_a?(Array)
|
165
|
+
clean_id = @id[0] == '@' ? @id[1..-1] : @id
|
166
|
+
debug_el = @parent.value._debug[clean_id]
|
167
|
+
raise "Unable to get debugging aid for array: #{@parent.value._debug.inspect} using ID '#{clean_id}'" unless debug_el
|
168
|
+
aid = debug_el[:arr]
|
169
|
+
raise "Unable to get debugging aid for array: #{debug_el.inspect}" unless aid
|
170
|
+
|
171
|
+
max_val_digits = @value.size.to_s.size
|
172
|
+
fmt = "%#{max_val_digits}d"
|
173
|
+
|
174
|
+
@value.each_with_index { |el, i|
|
175
|
+
n = Node.new(@tree, el, level + 1, nil, aid[i][:start], aid[i][:end])
|
176
|
+
n.id = sprintf(fmt, i)
|
177
|
+
add(n)
|
178
|
+
}
|
179
|
+
else
|
180
|
+
# Gather seq attributes
|
181
|
+
attrs = Set.new
|
182
|
+
@value.instance_variables.each { |k|
|
183
|
+
k = k.to_s
|
184
|
+
next if k =~ /^@_/
|
185
|
+
el = @value.instance_eval(k)
|
186
|
+
aid = @value._debug[k[1..-1]]
|
187
|
+
if aid
|
188
|
+
aid_s = aid[:start]
|
189
|
+
aid_e = aid[:end]
|
190
|
+
else
|
191
|
+
#raise "Unable to get debugging aid for '#{k}'"
|
192
|
+
aid_s = nil
|
193
|
+
aid_e = nil
|
194
|
+
end
|
195
|
+
n = Node.new(@tree, el, level + 1, nil, aid_s, aid_e)
|
196
|
+
n.id = k
|
197
|
+
add(n)
|
198
|
+
attrs << k.gsub(/^@/, '')
|
199
|
+
}
|
200
|
+
|
201
|
+
# Gather instances
|
202
|
+
common_meths = Set.new
|
203
|
+
@value.class.ancestors.each { |cl|
|
204
|
+
next if cl == @value.class
|
205
|
+
common_meths.merge(cl.instance_methods)
|
206
|
+
}
|
207
|
+
inst_meths = Set.new(@value.public_methods) - common_meths
|
208
|
+
inst_meths.each { |meth|
|
209
|
+
k = meth.to_s
|
210
|
+
next if k =~ /^_/ or attrs.include?(k)
|
211
|
+
n = Node.new(@tree, nil, level + 1, meth)
|
212
|
+
n.id = k
|
213
|
+
add(n)
|
214
|
+
}
|
215
|
+
end
|
216
|
+
@explored = true
|
217
|
+
end
|
218
|
+
|
219
|
+
##
|
220
|
+
# Determine total height of an element, including all children if
|
221
|
+
# it's open and visible
|
222
|
+
def height
|
223
|
+
if @open
|
224
|
+
r = 1
|
225
|
+
@children.each { |n| r += n.height }
|
226
|
+
r
|
227
|
+
else
|
228
|
+
1
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
##
|
233
|
+
# Find out last (deepest) descendant of current node
|
234
|
+
def last_descendant
|
235
|
+
n = self
|
236
|
+
while n.open?
|
237
|
+
n = n.children.last
|
238
|
+
end
|
239
|
+
n
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|