OpenC3/cosmos

View on GitHub
openc3/templates/tool_svelte/src/services/config-parser.js

Summary

Maintainability
F
1 wk
Test Coverage
/*
# Copyright 2022 Ball Aerospace & Technologies Corp.
# All Rights Reserved.
#
# This program is free software; you can modify and/or redistribute it
# under the terms of the GNU Affero General Public License
# as published by the Free Software Foundation; version 3 with
# attribution addendums as found in the LICENSE.txt
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# Modified by OpenC3, Inc.
# All changes Copyright 2024, OpenC3, Inc.
# All Rights Reserved
#
# This file may also be used under the terms of a commercial license
# if purchased from OpenC3, Inc.
*/

export class ConfigParserError {
  constructor(config_parser, message, usage = '', url = '') {
    this.keyword = config_parser.keyword
    this.parameters = config_parser.parameters
    this.filename = config_parser.filename
    this.line = config_parser.line
    this.lineNumber = config_parser.lineNumber
    this.message = message
    this.usage = usage
    this.url = url
  }
}

export class ConfigParserService {
  keyword = null
  parameters = []
  filename = ''
  line = ''
  lineNumber = 0
  url = 'https://docs.openc3.com/docs'
  constructor() {}

  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
    for (let index = 1; index <= min_num_params; index++) {
      // If the parameter is nil (0 based) then we have a problem
      if (this.parameters[index - 1] === undefined) {
        throw new ConfigParserError(
          this,
          `Not enough parameters for ${this.keyword}.`,
          usage,
          this.url,
        )
      }
    }
    // If they pass null for max_params we don't check for a maximum number
    if (max_num_params && this.parameters[max_num_params] !== undefined) {
      throw new ConfigParserError(
        this,
        `Too many parameters for ${this.keyword}.`,
        usage,
        this.url,
      )
    }
  }

  remove_quotes(string) {
    if (string.length < 2) {
      return string
    }
    let first_char = string.charAt(0)
    if (first_char !== '"' && first_char !== "'") {
      return string
    }
    let last_char = string.charAt(string.length - 1)
    if (first_char !== last_char) {
      return string
    }
    return string.substring(1, string.length - 1)
  }

  scan_string(string, rx) {
    if (!rx.global) throw "rx must have 'global' flag set"
    let r = []
    string.replace(rx, function (match) {
      r.push(match)
      return match
    })
    return r
  }

  parse_string(
    input_string,
    original_filename,
    yield_non_keyword_lines,
    remove_quotes,
    handler,
  ) {
    let string_concat = false
    this.line = ''
    this.keyword = null
    this.parameters = []
    this.filename = original_filename

    // Break string into lines
    let lines = input_string.split('\n')
    let numLines = lines.length

    for (let i = 0; i < numLines; i++) {
      this.lineNumber = i + 1
      let line = lines[i].trim()
      // Ensure the line length is not 0
      if (line.length === 0) {
        continue
      }

      if (string_concat === true) {
        // Skip comment lines after a string concatenation
        if (line[0] === '#') {
          continue
        }
        // Remove the opening quote if we're continuing the line
        line = line.substring(1, line.length)
      }

      // Check for string continuation
      let last_char = line.charAt(line.length - 1)
      let newline = false
      switch (last_char) {
        case '+': // String concatenation with newlines
          newline = true
        // Deliberate fall through
        case '\\': // String concatenation
          // Trim off the concat character plus any spaces, e.g. "line" \
          let trim = line.substring(0, line.length - 1).trim()
          // Now trim off the last quote so it will flow into the next line
          this.line += trim.substring(0, trim.length - 1)
          if (newline) {
            this.line += '\n'
          }
          string_concat = true
          continue
        case '&': // Line continuation
          this.line += line.substring(0, line.length - 1)
          continue
        default:
          this.line += line
      }
      string_concat = false

      let rx = /("([^\\"]|\\.)*")|('([^\\']|\\.)*')|\S+/g
      let data = this.scan_string(this.line, rx)
      let first_item = ''
      if (data.length > 0) {
        first_item = first_item + data[0]
      }

      if (first_item.length === 0 || first_item.charAt(0) === '#') {
        this.keyword = null
      } else {
        this.keyword = first_item.toUpperCase()
      }
      this.parameters = []

      // Ignore lines without keywords: comments and blank lines
      if (this.keyword === null) {
        if (yield_non_keyword_lines) {
          handler(this.keyword, this.parameters, this.line, this.lineNumber)
        }
        this.line = ''
        continue
      }

      let length = data.length
      if (length > 1) {
        for (let index = 1; index < length; index++) {
          let string = data[index]

          // Don't process trailing comments such as:
          // KEYWORD PARAM #This is a comment
          if (string.length > 0 && string.charAt(0) === '#') {
            break
          }
          if (remove_quotes) {
            this.parameters.push(this.remove_quotes(string))
          } else {
            this.parameters.push(string)
          }
        }
      }
      handler(this.keyword, this.parameters, this.line, this.lineNumber)
      this.line = ''
    } // for all the lines
  } // parse_string
} // class ConfigParserService