Class: SpitewasteCLI

Inherits:
Thor
  • Object
show all
Defined in:
lib/spitewaste/cli.rb,
lib/spitewaste/cli/asm.rb,
lib/spitewaste/cli/docs.rb,
lib/spitewaste/cli/exec.rb,
lib/spitewaste/cli/image.rb,
lib/spitewaste/cli/compile.rb,
lib/spitewaste/cli/convert.rb,
lib/spitewaste/cli/version.rb

Constant Summary collapse

VALID_FORMATS =
%i[spw spitewaste ws whitespace wsa wsassembly asm assembly]
CACHE_DIR =
File.expand_path '~/.cache/spitewaste'

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.exit_on_failure?Boolean

Returns:

  • (Boolean)


10
11
12
# File 'lib/spitewaste/cli.rb', line 10

def self.exit_on_failure?
  exit 1
end

.handle_no_command_error(*args) ⇒ Object

TODO: Figure out how to invoke a command from this class method.



36
37
38
# File 'lib/spitewaste/cli.rb', line 36

def self.handle_no_command_error *args
  exec $0, 'exec', *args
end

.make_cache_path(name) ⇒ Object



48
49
50
51
52
# File 'lib/spitewaste/cli.rb', line 48

def self.make_cache_path name
  FileUtils.mkpath CACHE_DIR
  base = File.basename File.expand_path(name).tr(?/, ?-), '.*'
  File.join(CACHE_DIR, base[1..]) + '.json'
end

.shared_optionsObject



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/spitewaste/cli.rb', line 14

def self.shared_options
  option :format,
    desc: 'input format (in case auto-detection misguesses)',
    aliases: '-f'

  option :aliases,
    desc: 'augment or override one or more of the default instruction mnemonics [WIP]',
    banner: 'pop:drop...',
    type: :array,
    aliases: '-a'

  option :coexist,
    desc: <<DESC,
allow multiple mnemonics to refer to the same instruction where possible [WIP]

\e[1mNOTE: \e[0mIf --no-coexist (the default), aliases take precedence and render the original mnemonics invalid.
DESC
    type: :boolean,
    aliases: '-x'
end

.validate_format(options) ⇒ Object



40
41
42
43
44
45
46
# File 'lib/spitewaste/cli.rb', line 40

def self.validate_format options
  if fmt = options[:format]&.to_sym and !VALID_FORMATS.include?(fmt)
    raise ArgumentError, "invalid format '#{fmt}'", []
  end

  Hash[*VALID_FORMATS][fmt] || fmt # expand short names
end

Instance Method Details

#asm(input, output = '/dev/stdout') ⇒ Object



8
9
10
11
# File 'lib/spitewaste/cli/asm.rb', line 8

def asm input, output = '/dev/stdout'
  as = Spitewaste::Assembler.new File.read input
  File.open(output, ?w) { |of| as.assemble! format: :assembly, io: of }
end

#compile(file = nil) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/spitewaste/cli/compile.rb', line 44

def compile file = nil
  fmt = SpitewasteCLI.validate_format options

  out = options[:output]
  out ||= file ? File.basename(file, '.*') : 'a.out'
  file ||= '/dev/stdin'

  as = Spitewaste::Assembler.new File.read(file), format: fmt
  as.assemble! format: :codegen, io: cpp = StringIO.new

  cmd = "#{options[:compiler]} -x c++ -o #{out} #{options[:argv]} -"
  Open3.capture2 cmd, stdin_data: cpp.string

  if options[:run]
    print `./#{out}`
    File.delete out unless options[:keep]
  end
end

#convert(input = '/dev/stdin', output = '/dev/stdout') ⇒ Object

Raises:

  • (ArgumentError)


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/spitewaste/cli/convert.rb', line 26

def convert input = '/dev/stdin', output = '/dev/stdout'
  ext = File.extname output
  Kernel.exec 'spw', 'image', input, output if ext == '.png'
  fmt = SpitewasteCLI.validate_format options

  valid_outputs = %i[ws whitespace
                     wsa wsassembly
                     asm assembly
                     cpp codegen]
  if out_fmt = options[:output_format]&.to_sym and !valid_outputs.include?(out_fmt)
    raise ArgumentError, "invalid output format '#{out_fmt}'", []
  end

  ext_map = {
    '.ws'  => :whitespace,
    '.wsa' => :wsassembly,
    '.asm' => :assembly,
    '.cpp' => :codegen}
  out_fmt = Hash[*valid_outputs][out_fmt] || ext_map[ext]
  raise ArgumentError, "can't determine output format", [] unless out_fmt

  opts = options.dup # options is frozen
  if opts[:symbol_file] == '%'
    opts[:symbol_file] = SpitewasteCLI.make_cache_path input
  end

  as = Spitewaste::Assembler.new File.read(input), format: fmt, **opts
  File.open(output, ?w) { |of| as.assemble! format: out_fmt, io: of }
end

#docs(*terms) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/spitewaste/cli/docs.rb', line 23

def docs *terms
  abort "need at least one search term" if terms.empty?

  docs = JSON.load File.read DOCS
  found = []
  min = options[:match_all] ? terms.size : 1

  docs.each do |lib, fns|
    fns.each do |fn, data|
      full = "#{lib}/#{bold under fn} #{data['full']}"
      ms = terms.count { |t| full[/#{t}/i] }
      found << [lib, fn, full, ms] if ms >= min
    end
  end

  abort "no matches for terms: #{terms}" if found.empty?

  found.sort_by! { |v| -v[-1] }

  IO.popen('less -R', 'w') do |io|
    found.each do |lib, fn, full|
      full.gsub! /#{terms * ?|}/i, &method(:hi)
      desc, specs = full.split "\n\n"
      io.puts "#{?- * 10}\n#{desc}\n\n"
      io.puts specs if options[:show_specs]
    end
  end
end

#exec(input = '/dev/stdin') ⇒ Object

Raises:

  • (LoadError)


21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/spitewaste/cli/exec.rb', line 21

def exec input = '/dev/stdin'
  fmt = SpitewasteCLI.validate_format options

  raise LoadError, "No such file '#{input}'", [] unless File.exists? input

  opts = options.dup # options is frozen
  if opts[:symbol_file] == '%'
    opts[:symbol_file] = SpitewasteCLI.make_cache_path input
  end

  if File.extname(input) != '.ws'
    io = Tempfile.new
    as = Spitewaste::Assembler.new File.read(input), format: fmt, **opts
    as.assemble! format: :whitespace, io: io
    input = io.tap(&:close).path
  end

  cmd = options[:interpreter].split
  cmd.map! { |c| c == '%' ? input : c }
  Kernel.exec *cmd
end

#image(input, output = nil) ⇒ Object



46
47
48
49
50
51
52
53
54
# File 'lib/spitewaste/cli/image.rb', line 46

def image input, output = nil
  fmt = SpitewasteCLI.validate_format options
  output ||= Pathname.new(input).sub_ext '.png'

  as = Spitewaste::Assembler.new File.read(input), format: fmt
  File.open(output, ?w) { |of|
    as.assemble! format: :image, io: of, **options.transform_keys(&:to_sym)
  }
end

#versionObject



6
7
8
# File 'lib/spitewaste/cli/version.rb', line 6

def version
  puts "spw version #{Spitewaste::VERSION}"
end