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.
- checksums.yaml +5 -5
- data/LICENSE +4 -4
- data/README.md +88 -28
- data/bin/ksdump +96 -0
- data/bin/ksv +53 -7
- data/lib/kaitai/console_ansi.rb +103 -98
- data/lib/kaitai/console_windows.rb +175 -230
- data/lib/kaitai/struct/visualizer/hex_viewer.rb +250 -241
- data/lib/kaitai/struct/visualizer/ks_error_matcher.rb +44 -0
- data/lib/kaitai/struct/visualizer/ksy_compiler.rb +243 -0
- data/lib/kaitai/struct/visualizer/node.rb +241 -225
- data/lib/kaitai/struct/visualizer/obj_to_h.rb +40 -0
- data/lib/kaitai/struct/visualizer/parser.rb +40 -0
- data/lib/kaitai/struct/visualizer/tree.rb +179 -198
- data/lib/kaitai/struct/visualizer/version.rb +3 -1
- data/lib/kaitai/struct/visualizer/visualizer.rb +10 -31
- data/lib/kaitai/struct/visualizer.rb +4 -1
- data/lib/kaitai/tui.rb +85 -94
- metadata +58 -27
- data/kaitai-struct-visualizer.gemspec +0 -37
- data/lib/kaitai/struct/visualizer/visualizer_main.rb +0 -42
- data/lib/kaitai/struct/visualizer/visualizer_ruby.rb +0 -18
@@ -0,0 +1,243 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kaitai/struct/visualizer/version'
|
4
|
+
require 'kaitai/tui'
|
5
|
+
|
6
|
+
require 'open3'
|
7
|
+
require 'json'
|
8
|
+
require 'tmpdir'
|
9
|
+
|
10
|
+
require 'psych'
|
11
|
+
|
12
|
+
module Kaitai::Struct::Visualizer
|
13
|
+
class KSYCompiler
|
14
|
+
# Initializes a new instance of the KSYCompiler class that is used to
|
15
|
+
# compile Kaitai Struct formats into Ruby classes by invoking the
|
16
|
+
# command line kaitai-struct-compiler.
|
17
|
+
#
|
18
|
+
# @param [Hash] opts Options
|
19
|
+
# @option opts [String] :outdir Output directory for compiled code; if
|
20
|
+
# not specified, a temporary directory will be used that will be
|
21
|
+
# deleted after the compilation is done
|
22
|
+
# @option opts [String] :import_path Additional import paths
|
23
|
+
# @option opts [String] :opaque_types "true" or "false" to enable or
|
24
|
+
# disable opaque types
|
25
|
+
#
|
26
|
+
# @param [String] prog_name Program name to be used as a prefix in
|
27
|
+
# error messages
|
28
|
+
# @param [IO] out IO stream to write error/warning messages to
|
29
|
+
def initialize(opts, prog_name = 'ksv', out = $stderr)
|
30
|
+
@opts = opts
|
31
|
+
@prog_name = prog_name
|
32
|
+
@out = out
|
33
|
+
|
34
|
+
@outdir = opts[:outdir]
|
35
|
+
end
|
36
|
+
|
37
|
+
def compile_formats_if(fns)
|
38
|
+
return compile_formats(fns) if (fns.length > 1) || fns[0].end_with?('.ksy')
|
39
|
+
|
40
|
+
fname = File.basename(fns[0], '.rb')
|
41
|
+
dname = File.dirname(fns[0])
|
42
|
+
gpath = File.expand_path('*.rb', dname)
|
43
|
+
|
44
|
+
Dir.glob(gpath) do |fn|
|
45
|
+
require File.expand_path(fn, dname)
|
46
|
+
end
|
47
|
+
|
48
|
+
# The name of the main class is that of the given file by convention.
|
49
|
+
fname.split('_').map(&:capitalize).join
|
50
|
+
end
|
51
|
+
|
52
|
+
# Compiles Kaitai Struct formats into Ruby classes by invoking the
|
53
|
+
# command line kaitai-struct-compiler, and loads the generated Ruby
|
54
|
+
# files into current Ruby interpreter by running `require` on them.
|
55
|
+
#
|
56
|
+
# If the :outdir option was specified, the compiled code will be
|
57
|
+
# stored in that directory. Otherwise, a temporary directory will be
|
58
|
+
# used that will be deleted after the compilation and loading is done.
|
59
|
+
#
|
60
|
+
# @param [Array<String>] fns List of Kaitai Struct format files to
|
61
|
+
# compile
|
62
|
+
# @return [String] Main class name, or nil if were errors
|
63
|
+
def compile_formats(fns)
|
64
|
+
if @outdir.nil?
|
65
|
+
main_class_name = nil
|
66
|
+
Dir.mktmpdir { |code_dir| main_class_name = compile_and_load(fns, code_dir) }
|
67
|
+
else
|
68
|
+
main_class_name = compile_and_load(fns, @outdir)
|
69
|
+
end
|
70
|
+
|
71
|
+
if main_class_name.nil?
|
72
|
+
@out.puts 'Fatal errors encountered, cannot continue'
|
73
|
+
exit 1
|
74
|
+
end
|
75
|
+
|
76
|
+
main_class_name
|
77
|
+
end
|
78
|
+
|
79
|
+
# Compiles Kaitai Struct formats into Ruby classes by invoking the
|
80
|
+
# command line kaitai-struct-compiler, and loads the generated Ruby
|
81
|
+
# files into current Ruby interpreter by running `require` on them.
|
82
|
+
#
|
83
|
+
# @param [Array<String>] fns List of Kaitai Struct format files to
|
84
|
+
# compile
|
85
|
+
# @param [String] code_dir Directory to store the compiled code in
|
86
|
+
# @return [String] Main class name, or nil if were errors
|
87
|
+
def compile_and_load(fns, code_dir)
|
88
|
+
log = compile_formats_to_output(fns, code_dir)
|
89
|
+
load_ruby_files(fns, code_dir, log)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Compiles Kaitai Struct formats into Ruby classes by invoking the
|
93
|
+
# command line kaitai-struct-compiler.
|
94
|
+
#
|
95
|
+
# @param [Array<String>] fns List of Kaitai Struct format files to
|
96
|
+
# compile
|
97
|
+
# @param [String] code_dir Directory to store the compiled code in
|
98
|
+
# @return [Hash] Structured output of kaitai-struct-compiler
|
99
|
+
def compile_formats_to_output(fns, code_dir)
|
100
|
+
args = ['--ksc-json-output', '--debug', '-t', 'ruby', '-d', code_dir, *fns]
|
101
|
+
|
102
|
+
# Extra arguments
|
103
|
+
extra = []
|
104
|
+
extra += ['--import-path', @opts[:import_path]] if @opts[:import_path]
|
105
|
+
extra += ['--opaque-types', @opts[:opaque_types]] if @opts[:opaque_types]
|
106
|
+
|
107
|
+
args = extra + args
|
108
|
+
|
109
|
+
# UNIX-based systems run ksc via a shell wrapper that requires
|
110
|
+
# extra '--' in invocation to disambiguate our '-d' from java runner
|
111
|
+
# '-d' (which allows to pass defines to JVM). Windows-based systems
|
112
|
+
# do not need and do not support this extra '--', so we don't add it
|
113
|
+
# on Windows.
|
114
|
+
args.unshift('--') unless Kaitai::TUI.windows?
|
115
|
+
|
116
|
+
begin
|
117
|
+
log_str, err_str, status = Open3.capture3('kaitai-struct-compiler', *args)
|
118
|
+
rescue Errno::ENOENT
|
119
|
+
@out.puts "#{@prog_name}: unable to find and execute kaitai-struct-compiler in your PATH"
|
120
|
+
exit 1
|
121
|
+
end
|
122
|
+
unless status.success?
|
123
|
+
if err_str =~ /Error: Unknown option --ksc-json-output/
|
124
|
+
@out.puts "#{@prog_name}: your kaitai-struct-compiler is too old:"
|
125
|
+
system('kaitai-struct-compiler', '--version')
|
126
|
+
@out.puts "\nPlease use at least v0.7."
|
127
|
+
else
|
128
|
+
@out.puts "ksc crashed (exit status = #{status}):\n"
|
129
|
+
@out.puts "== STDOUT\n"
|
130
|
+
@out.puts log_str
|
131
|
+
@out.puts
|
132
|
+
@out.puts "== STDERR\n"
|
133
|
+
@out.puts err_str
|
134
|
+
@out.puts
|
135
|
+
end
|
136
|
+
exit status.exitstatus
|
137
|
+
end
|
138
|
+
|
139
|
+
JSON.parse(log_str)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Loads Ruby files generated by kaitai-struct-compiler into current Ruby interpreter
|
143
|
+
# by running `require` on them.
|
144
|
+
#
|
145
|
+
# @param [Array<String>] fns List of Kaitai Struct format files that were compiled
|
146
|
+
# @param [String] code_dir Directory where the compiled Ruby files are stored
|
147
|
+
# @param [Hash] log Structured output of kaitai-struct-compiler
|
148
|
+
# @return [String] Main class name, or nil if were errors
|
149
|
+
def load_ruby_files(fns, code_dir, log)
|
150
|
+
errs = false
|
151
|
+
main_class_name = nil
|
152
|
+
|
153
|
+
fns.each_with_index do |fn, idx|
|
154
|
+
log_fn = log[fn]
|
155
|
+
if log_fn['errors']
|
156
|
+
report_err(log_fn['errors'])
|
157
|
+
errs = true
|
158
|
+
else
|
159
|
+
log_classes = log_fn['output']['ruby']
|
160
|
+
log_classes.each_pair do |_k, v|
|
161
|
+
if v['errors']
|
162
|
+
report_err(v['errors'])
|
163
|
+
errs = true
|
164
|
+
else
|
165
|
+
compiled_name = v['files'][0]['fileName']
|
166
|
+
compiled_path = File.join(code_dir, compiled_name)
|
167
|
+
|
168
|
+
require compiled_path
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Is it main ClassSpecs?
|
173
|
+
if idx.zero?
|
174
|
+
main = log_classes[log_fn['firstSpecName']]
|
175
|
+
main_class_name = main['topLevelName']
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
errs ? nil : main_class_name
|
181
|
+
end
|
182
|
+
|
183
|
+
def report_err(errs)
|
184
|
+
@out.puts((errs.length > 1 ? 'Errors' : 'Error') + ":\n\n")
|
185
|
+
errs.each do |err|
|
186
|
+
@out << err['file']
|
187
|
+
|
188
|
+
row = err['line']
|
189
|
+
col = err['col']
|
190
|
+
|
191
|
+
if row.nil? && err['path']
|
192
|
+
begin
|
193
|
+
node = resolve_yaml_path(err['file'], err['path'])
|
194
|
+
|
195
|
+
# Psych line numbers are 0-based, but we want 1-based
|
196
|
+
row = node.start_line + 1
|
197
|
+
|
198
|
+
# Psych column numbers are 0-based, but we want 1-based
|
199
|
+
col = node.start_column + 1
|
200
|
+
rescue StandardError
|
201
|
+
row = '!'
|
202
|
+
col = '!'
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
if row
|
207
|
+
@out << ':' << row
|
208
|
+
@out << ':' << col if col
|
209
|
+
end
|
210
|
+
|
211
|
+
@out << ':/' << err['path'].join('/') if err['path']
|
212
|
+
@out << ': ' << err['message'] << "\n"
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Parses YAML file using Ruby's mid-level Psych API and resolve YAML
|
217
|
+
# path reported by ksc to row & column.
|
218
|
+
def resolve_yaml_path(file, path)
|
219
|
+
doc = Psych.parse(File.read(file))
|
220
|
+
yaml = doc.children[0]
|
221
|
+
path.each do |path_part|
|
222
|
+
yaml = psych_find(yaml, path_part)
|
223
|
+
end
|
224
|
+
yaml
|
225
|
+
end
|
226
|
+
|
227
|
+
def psych_find(yaml, path_part)
|
228
|
+
if yaml.is_a?(Psych::Nodes::Mapping)
|
229
|
+
# mapping are key-values, which are represented as [k1, v1, k2, v2, ...]
|
230
|
+
yaml.children.each_slice(2) do |map_key, map_value|
|
231
|
+
return map_value if map_key.value == path_part
|
232
|
+
end
|
233
|
+
nil
|
234
|
+
elsif yaml.is_a?(Psych::Nodes::Sequence)
|
235
|
+
# sequences are just integer-indexed arrays - [a0, a1, a2, ...]
|
236
|
+
idx = Integer(path_part)
|
237
|
+
yaml.children[idx]
|
238
|
+
else
|
239
|
+
raise "Unknown Psych component encountered: #{yaml.class}"
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|