• Jump To … +
    api.js attributes.js node-exports.js parser.js rule-attributes.js rule-dependencies.js sabnf-grammar.js scanner-callbacks.js scanner-grammar.js scanner.js semantic-callbacks.js show-rules.js syntax-callbacks.js web-exports.js converter.js node-exports.js transformers.js web-exports.js apg-conv.js help.js apg-exp.js exec.js flags.js parse-replacement.js replace-grammar.js replace.js result.js sabnf-generator.js split.js web-exports.js ast.js circular-buffer.js emitcss.js identifiers.js node-exports.js parser.js stats.js style.js trace.js utilities.js web-exports.js apg.js command-line.js LICENSE.md README.md index.md
  • command-line.js

  • §
    /*  *************************************************************************************
     *   copyright: Copyright (c) 2021 Lowell D. Thomas, all rights reserved
     *     license: BSD-2-Clause (https://opensource.org/licenses/BSD-2-Clause)
     *   ********************************************************************************* */
  • §

    This module processes the command line arguments into a completed configuration and returns a config object. It takes the node command line arguments less the first two. e.g. process.argv.slice(2) and can be a free mix of keys and key/value pairs.

    Run
    npm run apg -- --help
    or
    ./bin/apg.sh -- help
    to see all the options.

    module.exports = function commandLine(args) {
      const fs = require('fs');
      const path = require('path');
      const { Buffer } = require('buffer');
      const converter = require('../apg-conv-api/converter');
      const helpScreen = function helpScreen(helpArgs) {
        let help = 'Usage: apg options\n';
        let options = '';
        helpArgs.forEach((arg) => {
          options += `${arg} `;
        });
        help += `options: ${options}\n`;
        help += '-h, --help                 : print this help screen\n';
        help += '-v, --version              : display version information\n';
        help += '-s, --strict               : only ABNF grammar (RFC 5234 & 7405) allowed, no Superset features\n';
        help += '-l, --lite                 : generate an apg-lite ESM grammar object\n';
        help += '-i <path>[,<path>[,...]]   : input file(s)*\n';
        help += '--in=<path>[,<path>[,...]] : input file(s)*\n';
        help += '-o <path>                  : output filename**\n';
        help += '--out=<path>               : output filename**\n';
        help += '-n <function name>         : the grammar function name***\n';
        help += '--name=<function name>     : the grammar function name***\n';
        help += '--display-rules            : display the rule names\n';
        help += '--display-rule-dependencies: display => rules referenced <= rules referring to this rule\n';
        help += '--display-attributes       : display the attributes\n';
        help += '\n';
        help += 'Options are case insensitive.\n';
        help += '*   Multiple input files allowed.\n';
        help += '    Multiple file names must be comma separated with no spaces.\n';
        help += '    File names from multiple input options are concatenated.\n';
        help += '    Content of all resulting input files is concatenated.\n';
        help += '**  Output file name is optional.\n';
        help += '    If no output file name is given, no parser is generated.\n';
        help += '    If the output file name does not have a ".js" extension,\n';
        help += '    the existing extension, if any, is stripped and ".js" is added.\n';
        help += '*** Grammar function name is optional.\n';
        help += '    If present, must be a valid JavaScript function name\n';
        help += '    If absent, uses "module.exports" for apg-js application\n';
        help += '    or "export default" for apg-lite application.\n';
        return help;
      };
      const version = function version() {
        return 'JavaScript APG, version 4.3.0\nCopyright (C) 2023 Lowell D. Thomas, all rights reserved\n';
      };
      const STRICTL = '--strict';
      const STRICTS = '-s';
      const LITEL = '--lite';
      const LITES = '-l';
      const HELPL = '--help';
      const HELPS = '-h';
      const VERSIONL = '--version';
      const VERSIONS = '-v';
      const DISPLAY_RULES = '--display-rules';
      const DISPLAY_RULE_DEPENDENCIES = '--display-rule-dependencies';
      const DISPLAY_ATTRIBUTES = '--display-attributes';
      const INL = '--in';
      const INS = '-i';
      const OUTL = '--out';
      const OUTS = '-o';
      const NAMEL = '--name';
      const NAMES = '-n';
      let inFilenames = [];
      const config = {
        help: '',
        version: '',
        error: '',
        strict: false,
        lite: false,
        noAttrs: false,
        displayRules: false,
        displayRuleDependencies: false,
        displayAttributes: false,
        src: null,
        outFilename: '',
        outfd: process.stdout.fd,
        funcName: null,
      };
      let key;
      let value;
      let i = 0;
      try {
        while (i < args.length) {
          const kv = args[i].split('=');
          if (kv.length === 2) {
            key = kv[0].toLowerCase();
  • §

    eslint-disable-next-line prefer-destructuring

            value = kv[1];
          } else if (kv.length === 1) {
            key = kv[0].toLowerCase();
            value = i + 1 < args.length ? args[i + 1] : '';
          } else {
            throw new Error(`command line error: ill-formed option: ${args[i]}`);
          }
          switch (key) {
            case HELPL:
            case HELPS:
              config.help = helpScreen(args);
              return config;
            case VERSIONL:
            case VERSIONS:
              config.version = version();
              return config;
            case DISPLAY_RULES:
              config.displayRules = true;
              i += 1;
              break;
            case DISPLAY_RULE_DEPENDENCIES:
              config.displayRuleDependencies = true;
              i += 1;
              break;
            case DISPLAY_ATTRIBUTES:
              config.displayAttributes = true;
              i += 1;
              break;
            case STRICTL:
            case STRICTS:
              config.strict = true;
              i += 1;
              break;
            case LITEL:
            case LITES:
              config.lite = true;
              i += 1;
              break;
            case INL:
            case INS:
              if (!value) {
                throw new Error(`command line error: input file name has no value: ${args[i]}`);
              }
              inFilenames = inFilenames.concat(value.split(','));
              i += key === INL ? 1 : 2;
              break;
            case OUTL:
            case OUTS:
              if (!value) {
                throw new Error(`command line error: output file name has no valu: ${args[i]}`);
              }
              config.outFilename = value;
              i += key === OUTL ? 1 : 2;
              break;
            case NAMEL:
            case NAMES:
              if (!value) {
                throw new Error(`command line error: output file name has no value: ${args[i]}`);
              }
              config.funcName = value;
              i += key === NAMEL ? 1 : 2;
              break;
            default:
              throw new Error(`command line error: unrecognized arg: ${args[i]}`);
          }
        }
    
        /* get the SABNF input */
        if (inFilenames.length === 0) {
          throw new Error('command line error: no input file(s)');
        }
    
        let buf = Buffer.alloc(0);
        inFilenames.forEach((name) => {
          buf = Buffer.concat([buf, fs.readFileSync(name)]);
        });
        config.src = converter.decode('BINARY', buf);
    
        /* validate & open the output file, if any */
        config.outfd = null;
        if (config.outFilename) {
          const info = path.parse(config.outFilename);
          if (info.ext !== 'js') {
            /* strip the extension and add .js */
            if (info.dir) {
              config.outFilename = `${info.dir}/${info.name}.js`;
            } else {
              config.outFilename = `${info.name}.js`;
            }
          }
          config.outfd = fs.openSync(config.outFilename, 'w');
        }
      } catch (e) {
        config.error = `CONFIG EXCEPTION: ${e.message}`;
        config.help = helpScreen(args);
      }
      return config;
    };