Module: Ibex::CLIErrorMessages

Defined in:
lib/ibex/cli/error_messages.rb,
sig/ibex/cli/error_messages.rbs

Overview

CLI subcommand and generation-file handling for example-keyed errors.

Instance Method Summary collapse

Instance Method Details

#automaton_for_error_messages(input_path) ⇒ IR::Automaton

RBS:

  • (String input_path) -> IR::Automaton

Parameters:

  • input_path (String)

Returns:



78
79
80
81
82
83
84
85
# File 'lib/ibex/cli/error_messages.rb', line 78

def automaton_for_error_messages(input_path)
  return automaton_from_ir_for_messages(input_path) if @options[:from]

  report_status("reading #{input_path}")
  grammar = normalize_grammar_path(input_path)
  handle_grammar_warnings(grammar, input_path)
  build_automaton(grammar, input_path)
end

#automaton_from_ir_for_messages(input_path) ⇒ IR::Automaton

RBS:

  • (String input_path) -> IR::Automaton

Parameters:

  • input_path (String)

Returns:



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/ibex/cli/error_messages.rb', line 88

def automaton_from_ir_for_messages(input_path)
  report_status("reading #{input_path}")
  value = IR::Validator.validate(File.read(input_path))
  expected = @options[:from] == "grammar-ir" ? IR::Grammar : IR::Automaton
  raise Ibex::Error, "#{input_path}:1:1: expected #{@options[:from]} input" unless value.is_a?(expected)
  if value.is_a?(IR::Automaton) && @options[:messages_algorithm_explicit]
    raise Ibex::Error, "(cli):1:1: --algorithm cannot be combined with --from=automaton-ir"
  end

  grammar = if value.is_a?(IR::Grammar)
              value
            elsif value.is_a?(IR::Automaton)
              value.grammar
            else
              raise Ibex::Error, "#{input_path}:1:1: expected #{@options[:from]} input"
            end
  handle_grammar_warnings(grammar, input_path)
  return build_automaton(value, input_path) if value.is_a?(IR::Grammar)

  if value.is_a?(IR::Automaton)
    prepare_loaded_automaton(value, input_path)
    return value
  end

  raise Ibex::Error, "#{input_path}:1:1: expected #{@options[:from]} input"
end

#error_message_search_limits{ max_tokens: Integer, max_configurations: Integer }

RBS:

  • () -> { max_tokens: Integer, max_configurations: Integer }

Returns:

  • ({ max_tokens: Integer, max_configurations: Integer })


144
145
146
147
148
149
150
151
# File 'lib/ibex/cli/error_messages.rb', line 144

def error_message_search_limits
  {
    max_tokens: @options.fetch(:messages_max_tokens, ErrorMessages::SentenceSearch::DEFAULT_MAX_TOKENS),
    max_configurations: @options.fetch(
      :messages_max_configurations, ErrorMessages::SentenceSearch::DEFAULT_MAX_CONFIGURATIONS
    )
  }
end

#error_messages_option_parserOptionParser

RBS:

  • () -> OptionParser

Returns:

  • (OptionParser)


48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/ibex/cli/error_messages.rb', line 48

def error_messages_option_parser
  OptionParser.new do |options|
    options.banner = "Usage: ibex errors (--list | --update[=FILE]) [options] grammarfile"
    options.on("--list", "list shortest error sentences on stdout") { @options[:messages_list] = true }
    options.on("--update[=FILE]", "update messages (defaults to grammar.messages)") do |value|
      @options[:messages_update] = value || true
    end
    options.on("--max-tokens=N", Integer, "maximum error-sentence token budget") do |value|
      @options[:messages_max_tokens] = positive_messages_limit(value, "--max-tokens")
    end
    options.on("--max-configurations=N", Integer, "maximum error-sentence configuration budget") do |value|
      @options[:messages_max_configurations] = positive_messages_limit(value, "--max-configurations")
    end
    options.on("--from=FORMAT", %w[grammar-ir automaton-ir], "resume from IR JSON") do |value|
      @options[:from] = value
    end
    options.on("--mode=MODE", %w[default extended], "grammar mode") { |value| @options[:mode] = value.to_sym }
    options.on("--algorithm=NAME", %w[slr lalr ielr lr1], "parser construction algorithm") do |value|
      @options[:algorithm] = value.to_sym
      @options[:messages_algorithm_explicit] = true
    end
    options.on("--warnings=CATEGORIES", "all, error, all,error, or none") do |value|
      @options[:warnings] = warning_categories(value)
    end
    options.on("-S", "--output-status", "show pipeline status") { @options[:status] = true }
    options.on("--help", "show help") { @options[:help] = true }
  end
end

#messages_file_mode(path) ⇒ Integer

RBS:

  • (String path) -> Integer

Parameters:

  • path (String)

Returns:

  • (Integer)


171
172
173
174
175
# File 'lib/ibex/cli/error_messages.rb', line 171

def messages_file_mode(path)
  return File.stat(path).mode & 0o777 if File.exist?(path)

  0o666 & ~File.umask
end

#positive_messages_limit(value, option) ⇒ Integer

RBS:

  • (Integer value, String option) -> Integer

Parameters:

  • value (Integer)
  • option (String)

Returns:

  • (Integer)


154
155
156
157
158
# File 'lib/ibex/cli/error_messages.rb', line 154

def positive_messages_limit(value, option)
  return value if value.positive?

  raise Ibex::Error, "(cli):1:1: #{option} must be positive"
end

#run_error_messages_command(arguments) ⇒ Integer

RBS:

  • (Array[String] arguments) -> Integer

Parameters:

  • arguments (Array[String])

Returns:

  • (Integer)


26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/ibex/cli/error_messages.rb', line 26

def run_error_messages_command(arguments)
  parser = error_messages_option_parser
  remaining = parser.parse(arguments)
  return print_help(parser) if @options[:help]
  if @options[:messages_list] && @options[:messages_update]
    raise Ibex::Error, "(cli):1:1: errors command accepts only one of --list or --update"
  end
  unless @options[:messages_list] || @options[:messages_update]
    raise Ibex::Error, "(cli):1:1: errors command requires --list or --update[=FILE]"
  end

  input = input_path(remaining)
  automaton = automaton_for_error_messages(input)
  if @options[:messages_list]
    @stdout.write(ErrorMessages.render(automaton, **error_message_search_limits))
  else
    write_error_messages(automaton, input)
  end
  0
end

#write_error_message_update_report(update) ⇒ void

This method returns an undefined value.

RBS:

  • (ErrorMessages::Update update) -> void

Parameters:



161
162
163
164
165
166
167
168
# File 'lib/ibex/cli/error_messages.rb', line 161

def write_error_message_update_report(update)
  classifications = {
    "uncovered" => update.uncovered, "unreachable" => update.unreachable, "moved" => update.moved
  }
  classifications.each do |label, items|
    items.each { |item| @stdout.puts("#{label}: #{item}") }
  end
end

#write_error_messages(automaton, input_path) ⇒ void

This method returns an undefined value.

RBS:

  • (IR::Automaton automaton, String input_path) -> void

Parameters:



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
141
# File 'lib/ibex/cli/error_messages.rb', line 116

def write_error_messages(automaton, input_path)
  configured = @options[:messages_update]
  path = configured == true ? default_output_path(input_path, ".messages") : configured
  raise Ibex::Error, "(cli):1:1: messages update path must not be empty" unless path.is_a?(String) && !path.empty?
  if same_file_target?(path, input_path)
    raise Ibex::Error, "(cli):1:1: messages update path must differ from the input path"
  end

  existing = if File.exist?(path)
               ErrorMessages.load(path)
             else
               ErrorMessages::Document.new(entries: [])
             end
  update = ErrorMessages.update(automaton, existing: existing, **error_message_search_limits)
  target_path = File.symlink?(path) ? File.realpath(path) : path
  directory = File.dirname(File.expand_path(target_path))
  Tempfile.create([".ibex-messages-", ".tmp"], directory, encoding: "UTF-8") do |file|
    file.write(update.source)
    File.chmod(messages_file_mode(target_path), file.path)
    file.flush
    file.fsync
    File.rename(file.path, target_path)
  end
  write_error_message_update_report(update)
  report_status("wrote #{path}")
end