Class: OpenC3::ConfigParser
- Defined in:
- lib/openc3/config/config_parser.rb,
ext/openc3/ext/config_parser/config_parser.c
Overview
Reads OpenC3 style configuration data which consists of keywords followed by 0 or more comma delimited parameters. Parameters with spaces must be enclosed in quotes. Quotes should also be used to indicate a parameter is a string. Keywords are case-insensitive and will be returned in uppercase.
Defined Under Namespace
Classes: Error
Constant Summary collapse
- PARSING_REGEX =
Regular expression used to break up an individual line into a keyword and comma delimited parameters. Handles parameters in single or double quotes.
%r{ (?:"(?:[^\\"]|\\.)*") | (?:'(?:[^\\']|\\.)*') | \S+ }x
- @@message_callback =
nil- @@progress_callback =
nil- @@splash =
Holds the current splash screen
nil
Instance Attribute Summary collapse
-
#filename ⇒ String
The name of the configuration file being parsed.
-
#keyword ⇒ String
The current keyword being parsed.
-
#line ⇒ String
The current line being parsed.
-
#line_number ⇒ Integer
The current line number being parsed.
-
#parameters ⇒ Array<String>
The parameters found after the keyword.
-
#url ⇒ String
The default URL to use in errors.
Class Method Summary collapse
- .calculate_range_value(type, data_type, bit_size) ⇒ Object
-
.handle_defined_constants(value, data_type = nil, bit_size = nil) ⇒ Numeric
Converts a string representing a defined constant into its value.
-
.handle_nil(value) ⇒ nil|Object
Converts a String containing '', 'NIL', 'NULL', or 'NONE' to nil Ruby primitive.
-
.handle_true_false(value) ⇒ true|false|Object
Converts a String containing 'TRUE' or 'FALSE' to true or false Ruby primitive.
-
.handle_true_false_nil(value) ⇒ true|false|nil|Object
Converts a String containing '', 'NIL', 'NULL', 'TRUE' or 'FALSE' to nil, true or false Ruby primitives.
-
.handle_true_false_strict(value, description: 'value', default: false) ⇒ true|false
Converts a String containing 'TRUE', '1', 'FALSE', '0' or '' to a true or false Ruby primitive.
- .message_callback=(message_callback) ⇒ Object
- .progress_callback=(progress_callback) ⇒ Object
-
.splash ⇒ Object
Returns the current splash screen if present.
- .splash=(splash) ⇒ Object
Instance Method Summary collapse
-
#error(message, usage = "", url = @url) ⇒ Error
Creates an Error.
-
#initialize(url = "https://docs.openc3.com/docs") ⇒ ConfigParser
constructor
A new instance of ConfigParser.
-
#parse_file(filename, yield_non_keyword_lines = false, remove_quotes = true, run_erb = true, variables = {}) {|keyword, parameters| ... } ⇒ Object
Processes a file and yields |config| to the given block.
-
#parse_loop(io, yield_non_keyword_lines, remove_quotes, size, rx) ⇒ Object
Iterates over each line of the io object and yields the keyword and parameters.
-
#read_file(filename) ⇒ Object
Can be called during parsing to read a referenced file.
-
#render(template_name, options = {}) ⇒ Object
Called by the ERB template to render a partial.
- #set_preserve_lines(state) ⇒ Object
-
#verify_num_parameters(min_num_params, max_num_params, usage = "") ⇒ Object
Verifies the parameters in the config parameter have the specified number of parameter and raises an Error if not.
-
#verify_parameter_naming(index, usage = "") ⇒ Object
Verifies the indicated parameter in the config doesn't start or end with an underscore, doesn't contain a double underscore or double bracket, doesn't contain spaces, quotes or brackets.
Constructor Details
#initialize(url = "https://docs.openc3.com/docs") ⇒ ConfigParser
Returns a new instance of ConfigParser.
143 144 145 |
# File 'lib/openc3/config/config_parser.rb', line 143 def initialize(url = "https://docs.openc3.com/docs") @url = url end |
Instance Attribute Details
#filename ⇒ String
Returns The name of the configuration file being parsed. This will be an empty string if the parse_string class method is used.
38 39 40 |
# File 'lib/openc3/config/config_parser.rb', line 38 def filename @filename end |
#keyword ⇒ String
Returns The current keyword being parsed.
31 32 33 |
# File 'lib/openc3/config/config_parser.rb', line 31 def keyword @keyword end |
#line ⇒ String
Returns The current line being parsed. This is the raw string which is useful when printing errors.
42 43 44 |
# File 'lib/openc3/config/config_parser.rb', line 42 def line @line end |
#line_number ⇒ Integer
Returns The current line number being parsed. This will still be populated when using parse_string because lines still must be delimited by newline characters.
47 48 49 |
# File 'lib/openc3/config/config_parser.rb', line 47 def line_number @line_number end |
#parameters ⇒ Array<String>
Returns The parameters found after the keyword.
34 35 36 |
# File 'lib/openc3/config/config_parser.rb', line 34 def parameters @parameters end |
#url ⇒ String
Returns The default URL to use in errors. The URL can still be overridden by directly passing it to the error method.
51 52 53 |
# File 'lib/openc3/config/config_parser.rb', line 51 def url @url end |
Class Method Details
.calculate_range_value(type, data_type, bit_size) ⇒ Object
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 |
# File 'lib/openc3/config/config_parser.rb', line 490 def self.calculate_range_value(type, data_type, bit_size) value = 0 # Default for UINT minimum case data_type when :INT if type == 'MIN' value = -2**(bit_size - 1) else # 'MAX' value = (2**(bit_size - 1)) - 1 end when :UINT # Default is 0 for 'MIN' if type == 'MAX' value = (2**bit_size) - 1 end when :FLOAT case bit_size when 32 value = 3.402823e38 value *= -1 if type == 'MIN' when 64 value = Float::MAX value *= -1 if type == 'MIN' else raise ArgumentError, "Invalid bit size #{bit_size} for FLOAT type." end else raise ArgumentError, "Invalid data type #{data_type} when calculating range." end value end |
.handle_defined_constants(value, data_type = nil, bit_size = nil) ⇒ Numeric
Converts a string representing a defined constant into its value. The defined constants are the minimum and maximum values for all the allowable data types. [MIN/MAX]_[U]INT and [MIN/MAX]_FLOAT. Thus MIN_UINT8, MAX_INT32, and MIN_FLOAT64 are all allowable values. Any other strings raise ArgumentError but all other types are simply returned.
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 |
# File 'lib/openc3/config/config_parser.rb', line 399 def self.handle_defined_constants(value, data_type = nil, bit_size = nil) if value.class == String case value.upcase when 'MIN', 'MAX' return self.calculate_range_value(value.upcase, data_type, bit_size) when 'MIN_INT8' return -128 when 'MAX_INT8' return 127 when 'MIN_INT16' return -32768 when 'MAX_INT16' return 32767 when 'MIN_INT32' return -2147483648 when 'MAX_INT32' return 2147483647 when 'MIN_INT64' return -9223372036854775808 when 'MAX_INT64' return 9223372036854775807 when 'MIN_UINT8', 'MIN_UINT16', 'MIN_UINT32', 'MIN_UINT64' return 0 when 'MAX_UINT8' return 255 when 'MAX_UINT16' return 65535 when 'MAX_UINT32' return 4294967295 when 'MAX_UINT64' return 18446744073709551615 when 'MIN_FLOAT64' return -Float::MAX when 'MAX_FLOAT64' return Float::MAX when 'MIN_FLOAT32' return -3.402823e38 when 'MAX_FLOAT32' return 3.402823e38 when 'POS_INFINITY' return Float::INFINITY when 'NEG_INFINITY' return -Float::INFINITY else # NOTE: The else case does not raise because of the following scenario: # If the value type is a UINT but they have a WRITE_CONVERSION that takes a string # then the default value will be a string. In that case we just want to return the string. # For example, the IP_ADDRESS parameter in the TIME_OFFSET command in the Demo plugin. end end return value end |
.handle_nil(value) ⇒ nil|Object
Converts a String containing '', 'NIL', 'NULL', or 'NONE' to nil Ruby primitive. All other arguments are simply returned.
306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/openc3/config/config_parser.rb', line 306 def self.handle_nil(value) if String === value case value.upcase when '', 'NIL', 'NULL', 'NONE' return nil else return value end end return value end |
.handle_true_false(value) ⇒ true|false|Object
Converts a String containing 'TRUE' or 'FALSE' to true or false Ruby primitive. All other values are simply returned.
323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/openc3/config/config_parser.rb', line 323 def self.handle_true_false(value) if String === value case value.upcase when 'TRUE' return true when 'FALSE' return false else return value end end return value end |
.handle_true_false_nil(value) ⇒ true|false|nil|Object
Converts a String containing '', 'NIL', 'NULL', 'TRUE' or 'FALSE' to nil, true or false Ruby primitives. All other values are simply returned.
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
# File 'lib/openc3/config/config_parser.rb', line 374 def self.handle_true_false_nil(value) if String === value case value.upcase when 'TRUE' return true when 'FALSE' return false when '', 'NIL', 'NULL' return nil else # All other strings are returned unmodified below end end return value end |
.handle_true_false_strict(value, description: 'value', default: false) ⇒ true|false
Converts a String containing 'TRUE', '1', 'FALSE', '0' or '' to a true or false Ruby primitive. Unlike handle_true_false, which returns anything it doesn't recognize unchanged, an unrecognized value raises.
Use this for a flag where guessing is worse than failing - notably an environment variable that is enabled by presence elsewhere in COSMOS, so that 'VAR=false' (or 'VAR=0') means off here rather than the surprising on.
358 359 360 361 362 363 364 365 366 367 |
# File 'lib/openc3/config/config_parser.rb', line 358 def self.handle_true_false_strict(value, description: 'value', default: false) return default if value.nil? normalized = value.to_s.strip.upcase return true if STRICT_TRUE_VALUES.include?(normalized) return false if STRICT_FALSE_VALUES.include?(normalized) # Name empty explicitly - it is accepted (as false) but isn't a value # anyone can read off the list raise ArgumentError, "Invalid value #{value.to_s.strip.inspect} for #{description}. " \ "Must be one of: #{(STRICT_TRUE_VALUES + STRICT_FALSE_VALUES - ['']).join(', ')}, or empty" end |
.message_callback=(message_callback) ⇒ Object
58 59 60 |
# File 'lib/openc3/config/config_parser.rb', line 58 def self.=() = end |
.progress_callback=(progress_callback) ⇒ Object
68 69 70 |
# File 'lib/openc3/config/config_parser.rb', line 68 def self.progress_callback=(progress_callback) @@progress_callback = progress_callback end |
.splash ⇒ Object
Returns the current splash screen if present
90 91 92 |
# File 'lib/openc3/config/config_parser.rb', line 90 def self.splash @@splash end |
.splash=(splash) ⇒ Object
77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/openc3/config/config_parser.rb', line 77 def self.splash=(splash) if splash @@splash = splash @@progress_callback = splash.progress_callback = splash. else @@splash = nil @@progress_callback = nil = nil end end |
Instance Method Details
#error(message, usage = "", url = @url) ⇒ Error
Creates an Error
153 154 155 |
# File 'lib/openc3/config/config_parser.rb', line 153 def error(, usage = "", url = @url) return Error.new(self, , usage, url) end |
#parse_file(filename, yield_non_keyword_lines = false, remove_quotes = true, run_erb = true, variables = {}) {|keyword, parameters| ... } ⇒ Object
Processes a file and yields |config| to the given block
221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'lib/openc3/config/config_parser.rb', line 221 def parse_file(filename, yield_non_keyword_lines = false, remove_quotes = true, run_erb = true, variables = {}, &) raise Error.new(self, "Configuration file #{filename} does not exist.") unless filename && File.exist?(filename) @filename = filename # Create a temp file where we write the ERB parsed output file = create_parsed_output_file(filename, run_erb, variables) size = file.stat.size.to_f # Callbacks for beginning of parsing .call("Parsing #{size} bytes of #{filename}") if @@progress_callback.call(0.0) if @@progress_callback begin # Loop through each line of the data parse_loop(file, yield_non_keyword_lines, remove_quotes, size, PARSING_REGEX, &) ensure file.close unless file.closed? file.unlink end end |
#parse_loop(io, yield_non_keyword_lines, remove_quotes, size, rx) ⇒ Object
Iterates over each line of the io object and yields the keyword and parameters
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 |
# File 'lib/openc3/config/config_parser.rb', line 548 def parse_loop(io, yield_non_keyword_lines, remove_quotes, size, rx) string_concat = false @line_number = 0 @keyword = nil @parameters = [] @line = '' errors = [] while true @line_number += 1 if @@progress_callback && ((@line_number % 10) == 0) && size > 0.0 @@progress_callback.call(io.pos / size) end begin line = io.readline line.chomp! rescue Exception break end if not @preserve_lines line.strip! # Ensure the line length is not 0 if line.length == 0 if yield_non_keyword_lines begin yield(nil, []) rescue => e errors << e end end next end if string_concat # Skip comment lines after a string concatenation if (line[0] == '#') next end # Remove the opening quote if we're continuing the line line = line[1..-1] end # Check for string continuation case line[-1] when '+', '\\' # String concatenation newline = line[-1] == '+' # Trim off the concat character plus any spaces, e.g. "line" trim = line[0..-2].strip() # Now trim off the last quote so it will flow into the next line @line += trim[0..-2] @line += "\n" if newline string_concat = true next when '&' # Line continuation @line += line[0..-2] next else @line += line end else @line = line end string_concat = false data = @line.scan(rx) first_item = '' if data.length > 0 first_item += data[0] end if (first_item.length == 0) || (first_item[0] == '#') @keyword = nil else @keyword = first_item.upcase end @parameters = [] # Ignore lines without keywords: comments and blank lines if @keyword.nil? if yield_non_keyword_lines begin yield(@keyword, @parameters) rescue => e errors << e end end @line = '' next end length = data.length if length > 1 (1..(length - 1)).each do |index| string = data[index] # Don't process trailing comments such as: # KEYWORD PARAM #This is a comment # But still process Ruby string interpolations such as: # KEYWORD PARAM #{var} if (string.length > 0) && (string[0] == '#') && !((string.length > 1) && (string[1] == '{')) break end if remove_quotes @parameters << string.remove_quotes else @parameters << string end end end begin yield(@keyword, @parameters) rescue => e errors << e end @line = '' end parse_errors(errors) @@progress_callback.call(1.0) if @@progress_callback return nil end |
#read_file(filename) ⇒ Object
Can be called during parsing to read a referenced file. The filename must be relative to the configuration file being parsed and must resolve to a location inside that file's directory.
172 173 174 175 176 177 |
# File 'lib/openc3/config/config_parser.rb', line 172 def read_file(filename) path = resolve_config_path(filename) OpenC3.set_working_dir(File.dirname(path)) do return File.read(path).force_encoding("UTF-8") end end |
#render(template_name, options = {}) ⇒ Object
Called by the ERB template to render a partial
158 159 160 161 162 163 164 165 166 167 |
# File 'lib/openc3/config/config_parser.rb', line 158 def render(template_name, = {}) raise Error.new(self, "Partial name '#{template_name}' must begin with an underscore.") if File.basename(template_name)[0] != '_' b = binding if [:locals] [:locals].each { |key, value| b.local_variable_set(key, value) } end return ERB.new(read_file(template_name).comment_erb(), trim_mode: "-").result(b) end |
#set_preserve_lines(state) ⇒ Object
452 453 454 |
# File 'lib/openc3/config/config_parser.rb', line 452 def set_preserve_lines(state) @preserve_lines = state end |
#verify_num_parameters(min_num_params, max_num_params, usage = "") ⇒ Object
Verifies the parameters in the config parameter have the specified number of parameter and raises an Error if not.
259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/openc3/config/config_parser.rb', line 259 def verify_num_parameters(min_num_params, max_num_params, usage = "") # This syntax works with 0 because each doesn't return any values # for a backwards range (1..min_num_params).each do |index| # If the parameter is nil (0 based) then we have a problem if @parameters[index - 1].nil? raise Error.new(self, "Not enough parameters for #{@keyword}.", usage, @url) end end # If they pass nil for max_params we don't check for a maximum number if max_num_params && !@parameters[max_num_params].nil? raise Error.new(self, "Too many parameters for #{@keyword}.", usage, @url) end end |
#verify_parameter_naming(index, usage = "") ⇒ Object
Verifies the indicated parameter in the config doesn't start or end with an underscore, doesn't contain a double underscore or double bracket, doesn't contain spaces, quotes or brackets.
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 |
# File 'lib/openc3/config/config_parser.rb', line 279 def verify_parameter_naming(index, usage = "") param = @parameters[index - 1] if param.end_with? '_' raise Error.new(self, "Parameter #{index} (#{param}) for #{@keyword} cannot end with an underscore ('_').", usage, @url) end if param.include? '__' raise Error.new(self, "Parameter #{index} (#{param}) for #{@keyword} cannot contain a double underscore ('__').", usage, @url) end if param.include? '[[' or param.include? ']]' raise Error.new(self, "Parameter #{index} (#{param}) for #{@keyword} cannot contain double brackets ('[[' or ']]').", usage, @url) end if param.include? ' ' raise Error.new(self, "Parameter #{index} (#{param}) for #{@keyword} cannot contain a space (' ').", usage, @url) end if param.include?('"') or param.include?("'") raise Error.new(self, "Parameter #{index} (#{param}) for #{@keyword} cannot contain a quote (' or \").", usage, @url) end if param.include?('{') or param.include?('}') raise Error.new(self, "Parameter #{index} (#{param}) for #{@keyword} cannot contain a curly bracket ('{' or '}').", usage, @url) end end |