Class: Kaitai::Struct::Visualizer::KSYCompiler

Inherits:
Object
  • Object
show all
Defined in:
lib/kaitai/struct/visualizer/ksy_compiler.rb

Instance Method Summary collapse

Constructor Details

#initialize(opts, prog_name = 'ksv', out = $stderr) ⇒ KSYCompiler

Initializes a new instance of the KSYCompiler class that is used to compile Kaitai Struct formats into Ruby classes by invoking the command line kaitai-struct-compiler.

Parameters:

  • opts (Hash)

    Options

  • prog_name (String) (defaults to: 'ksv')

    Program name to be used as a prefix in error messages

  • out (IO) (defaults to: $stderr)

    IO stream to write error/warning messages to

Options Hash (opts):

  • :outdir (String)

    Output directory for compiled code; if not specified, a temporary directory will be used that will be deleted after the compilation is done

  • :import_path (String)

    Additional import paths

  • :opaque_types (String)

    “true” or “false” to enable or disable opaque types



29
30
31
32
33
34
35
# File 'lib/kaitai/struct/visualizer/ksy_compiler.rb', line 29

def initialize(opts, prog_name = 'ksv', out = $stderr)
  @opts = opts
  @prog_name = prog_name
  @out = out

  @outdir = opts[:outdir]
end

Instance Method Details

#compile_and_load(fns, code_dir) ⇒ String

Compiles Kaitai Struct formats into Ruby classes by invoking the command line kaitai-struct-compiler, and loads the generated Ruby files into current Ruby interpreter by running ‘require` on them.

Parameters:

  • fns (Array<String>)

    List of Kaitai Struct format files to compile

  • code_dir (String)

    Directory to store the compiled code in

Returns:

  • (String)

    Main class name, or nil if were errors



87
88
89
90
# File 'lib/kaitai/struct/visualizer/ksy_compiler.rb', line 87

def compile_and_load(fns, code_dir)
  log = compile_formats_to_output(fns, code_dir)
  load_ruby_files(fns, code_dir, log)
end

#compile_formats(fns) ⇒ String

Compiles Kaitai Struct formats into Ruby classes by invoking the command line kaitai-struct-compiler, and loads the generated Ruby files into current Ruby interpreter by running ‘require` on them.

If the :outdir option was specified, the compiled code will be stored in that directory. Otherwise, a temporary directory will be used that will be deleted after the compilation and loading is done.

Parameters:

  • fns (Array<String>)

    List of Kaitai Struct format files to compile

Returns:

  • (String)

    Main class name, or nil if were errors



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/kaitai/struct/visualizer/ksy_compiler.rb', line 63

def compile_formats(fns)
  if @outdir.nil?
    main_class_name = nil
    Dir.mktmpdir { |code_dir| main_class_name = compile_and_load(fns, code_dir) }
  else
    main_class_name = compile_and_load(fns, @outdir)
  end

  if main_class_name.nil?
    @out.puts 'Fatal errors encountered, cannot continue'
    exit 1
  end

  main_class_name
end

#compile_formats_if(fns) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/kaitai/struct/visualizer/ksy_compiler.rb', line 37

def compile_formats_if(fns)
  return compile_formats(fns) if (fns.length > 1) || fns[0].end_with?('.ksy')

  fname = File.basename(fns[0], '.rb')
  dname = File.dirname(fns[0])
  gpath = File.expand_path('*.rb', dname)

  Dir.glob(gpath) do |fn|
    require File.expand_path(fn, dname)
  end

  # The name of the main class is that of the given file by convention.
  fname.split('_').map(&:capitalize).join
end

#compile_formats_to_output(fns, code_dir) ⇒ Hash

Compiles Kaitai Struct formats into Ruby classes by invoking the command line kaitai-struct-compiler.

Parameters:

  • fns (Array<String>)

    List of Kaitai Struct format files to compile

  • code_dir (String)

    Directory to store the compiled code in

Returns:

  • (Hash)

    Structured output of kaitai-struct-compiler



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/kaitai/struct/visualizer/ksy_compiler.rb', line 99

def compile_formats_to_output(fns, code_dir)
  args = ['--ksc-json-output', '--debug', '-t', 'ruby', '-d', code_dir, *fns]

  # Extra arguments
  extra = []
  extra += ['--import-path', @opts[:import_path]] if @opts[:import_path]
  extra += ['--opaque-types', @opts[:opaque_types]] if @opts[:opaque_types]

  args = extra + args

  # UNIX-based systems run ksc via a shell wrapper that requires
  # extra '--' in invocation to disambiguate our '-d' from java runner
  # '-d' (which allows to pass defines to JVM). Windows-based systems
  # do not need and do not support this extra '--', so we don't add it
  # on Windows.
  args.unshift('--') unless Kaitai::TUI.windows?

  begin
    log_str, err_str, status = Open3.capture3('kaitai-struct-compiler', *args)
  rescue Errno::ENOENT
    @out.puts "#{@prog_name}: unable to find and execute kaitai-struct-compiler in your PATH"
    exit 1
  end
  unless status.success?
    if err_str =~ /Error: Unknown option --ksc-json-output/
      @out.puts "#{@prog_name}: your kaitai-struct-compiler is too old:"
      system('kaitai-struct-compiler', '--version')
      @out.puts "\nPlease use at least v0.7."
    else
      @out.puts "ksc crashed (exit status = #{status}):\n"
      @out.puts "== STDOUT\n"
      @out.puts log_str
      @out.puts
      @out.puts "== STDERR\n"
      @out.puts err_str
      @out.puts
    end
    exit status.exitstatus
  end

  JSON.parse(log_str)
end

#load_ruby_files(fns, code_dir, log) ⇒ String

Loads Ruby files generated by kaitai-struct-compiler into current Ruby interpreter by running ‘require` on them.

Parameters:

  • fns (Array<String>)

    List of Kaitai Struct format files that were compiled

  • code_dir (String)

    Directory where the compiled Ruby files are stored

  • log (Hash)

    Structured output of kaitai-struct-compiler

Returns:

  • (String)

    Main class name, or nil if were errors



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/kaitai/struct/visualizer/ksy_compiler.rb', line 149

def load_ruby_files(fns, code_dir, log)
  errs = false
  main_class_name = nil

  fns.each_with_index do |fn, idx|
    log_fn = log[fn]
    if log_fn['errors']
      report_err(log_fn['errors'])
      errs = true
    else
      log_classes = log_fn['output']['ruby']
      log_classes.each_pair do |_k, v|
        if v['errors']
          report_err(v['errors'])
          errs = true
        else
          compiled_name = v['files'][0]['fileName']
          compiled_path = File.join(code_dir, compiled_name)

          require compiled_path
        end
      end

      # Is it main ClassSpecs?
      if idx.zero?
        main = log_classes[log_fn['firstSpecName']]
        main_class_name = main['topLevelName']
      end
    end
  end

  errs ? nil : main_class_name
end

#psych_find(yaml, path_part) ⇒ Object



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/kaitai/struct/visualizer/ksy_compiler.rb', line 227

def psych_find(yaml, path_part)
  if yaml.is_a?(Psych::Nodes::Mapping)
    # mapping are key-values, which are represented as [k1, v1, k2, v2, ...]
    yaml.children.each_slice(2) do |map_key, map_value|
      return map_value if map_key.value == path_part
    end
    nil
  elsif yaml.is_a?(Psych::Nodes::Sequence)
    # sequences are just integer-indexed arrays - [a0, a1, a2, ...]
    idx = Integer(path_part)
    yaml.children[idx]
  else
    raise "Unknown Psych component encountered: #{yaml.class}"
  end
end

#report_err(errs) ⇒ Object



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/kaitai/struct/visualizer/ksy_compiler.rb', line 183

def report_err(errs)
  @out.puts((errs.length > 1 ? 'Errors' : 'Error') + ":\n\n")
  errs.each do |err|
    @out << err['file']

    row = err['line']
    col = err['col']

    if row.nil? && err['path']
      begin
        node = resolve_yaml_path(err['file'], err['path'])

        # Psych line numbers are 0-based, but we want 1-based
        row = node.start_line + 1

        # Psych column numbers are 0-based, but we want 1-based
        col = node.start_column + 1
      rescue StandardError
        row = '!'
        col = '!'
      end
    end

    if row
      @out << ':' << row
      @out << ':' << col if col
    end

    @out << ':/' << err['path'].join('/') if err['path']
    @out << ': ' << err['message'] << "\n"
  end
end

#resolve_yaml_path(file, path) ⇒ Object

Parses YAML file using Ruby’s mid-level Psych API and resolve YAML path reported by ksc to row & column.



218
219
220
221
222
223
224
225
# File 'lib/kaitai/struct/visualizer/ksy_compiler.rb', line 218

def resolve_yaml_path(file, path)
  doc = Psych.parse(File.read(file))
  yaml = doc.children[0]
  path.each do |path_part|
    yaml = psych_find(yaml, path_part)
  end
  yaml
end