packages/pegjs/bin/options.js

Summary

Maintainability
C
7 hrs
Test Coverage
"use strict";

const fs = require( "fs" );
const path = require( "path" );
const peg = require( "../lib/peg" );
const util = peg.util;

// Options

let inputFile = null;
let outputFile = null;
let options = {
    "--": [],
    "cache": false,
    "dependencies": {},
    "exportVar": null,
    "format": "commonjs",
    "optimize": "speed",
    "output": "source",
    "parser": {},
    "plugins": [],
    "trace": false,
};

const EXPORT_VAR_FORMATS = [ "globals", "umd" ];
const DEPENDENCY_FORMATS = [ "amd", "commonjs", "es", "umd" ];
const MODULE_FORMATS = [ "amd", "bare", "commonjs", "es", "globals", "umd" ];
const OPTIMIZATION_GOALS = [ "size", "speed" ];

// Helpers

function abort( message ) {

    console.error( message );
    process.exit( 1 );

}

function addExtraOptions( config ) {

    if ( typeof config === "string" ) {

        try {

            config = JSON.parse( config );

        } catch ( e ) {

            if ( ! ( e instanceof SyntaxError ) ) throw e;
            abort( "Error parsing JSON: " + e.message );

        }

    }
    if ( typeof config !== "object" ) {

        abort( "The JSON with extra options has to represent an object." );

    }

    if ( config.input !== null || config.output !== null ) {

        // We don't want to touch the orignal config, just in case it comes from
        // a javascript file, in which case its possible the same object is used
        // for something else, somewhere else.
        config = util.clone( config );
        const { input, output } = config;

        if ( input !== null ) {

            if ( typeof input !== "string" )

                abort( "The option `input` must be a string." );

            if ( inputFile !== null )

                abort( `The input file is already set, cannot use: "${ input }".` );

            inputFile = input;
            delete config.input;

        }

        if ( output !== null ) {

            if ( typeof output !== "string" )

                abort( "The option `output` must be a string." );

            outputFile = output;
            delete config.output;

        }

    }

    options = util.processOptions( config, options );

}

function formatChoicesList( list ) {

    list = list.map( entry => `"${ entry }"` );
    const lastOption = list.pop();

    return list.length === 0
        ? lastOption
        : list.join( ", " ) + " or " + lastOption;

}

function updateList( list, string ) {

    string
        .split( "," )
        .forEach( entry => {

            entry = entry.trim();
            if ( list.indexOf( entry ) === -1 ) {

                list.push( entry );

            }

        } );

}

// Arguments

let args = process.argv.slice( 2 );

function nextArg( option ) {

    if ( args.length === 0 ) {

        abort( `Missing parameter of the ${ option } option.` );

    }
    return args.shift();

}

// Parse Arguments

while ( args.length > 0 ) {

    let config, mod;
    let argument = args.shift();

    if ( argument.indexOf( "-" ) === 0 && argument.indexOf( "=" ) > 1 ) {

        argument = argument.split( "=" );
        args.unshift( argument.length > 2 ? argument.slice( 1 ) : argument[ 1 ] );
        argument = argument[ 0 ];

    }

    switch ( argument ) {

        case "--":
            options[ "--" ] = args;
            args = [];
            break;

        case "-a":
        case "--allowed-start-rules":
            if ( ! options.allowedStartRules ) options.allowedStartRules = [];
            updateList( options.allowedStartRules, nextArg( "--allowed-start-rules" ) );
            break;

        case "--cache":
            options.cache = true;
            break;

        case "--no-cache":
            options.cache = false;
            break;

        case "-d":
        case "--dependency":
            argument = nextArg( "-d/--dependency" );
            mod = argument.split( ":" );

            if ( mod.length === 1 ) mod = [ argument, argument ];
            else if ( mod.length > 2 ) mod[ 1 ] = mod.slice( 1 );

            options.dependencies[ mod[ 0 ] ] = mod[ 1 ];
            break;

        case "-e":
        case "--export-var":
            options.exportVar = nextArg( "-e/--export-var" );
            break;

        case "--extra-options":
            addExtraOptions( nextArg( "--extra-options" ) );
            break;

        case "-c":
        case "--config":
        case "--extra-options-file":
            argument = nextArg( "-c/--config/--extra-options-file" );
            if ( path.extname( argument ) === ".js" ) {

                config = require( path.resolve( argument ) );

            } else {

                try {

                    config = fs.readFileSync( argument, "utf8" );

                } catch ( e ) {

                    abort( `Can't read from file "${ argument }".` );

                }

            }
            addExtraOptions( config );
            break;

        case "-f":
        case "--format":
            argument = nextArg( "-f/--format" );
            if ( MODULE_FORMATS.indexOf( argument ) === -1 ) {

                abort( `Module format must be either ${ formatChoicesList( MODULE_FORMATS ) }.` );

            }
            options.format = argument;
            break;

        case "-h":
        case "--help":
            console.log( require( "./usage" ) );
            process.exit();
            break;

        case "-O":
        case "--optimize":
            argument = nextArg( "-O/--optimize" );
            if ( OPTIMIZATION_GOALS.indexOf( argument ) === -1 ) {

                abort( `Optimization goal must be either ${ formatChoicesList( OPTIMIZATION_GOALS ) }.` );

            }
            options.optimize = argument;
            break;

        case "-o":
        case "--output":
            outputFile = nextArg( "-o/--output" );
            break;

        case "-p":
        case "--plugin":
            argument = nextArg( "-p/--plugin" );
            try {

                mod = require( argument );

            } catch ( ex1 ) {

                if ( ex1.code !== "MODULE_NOT_FOUND" ) throw ex1;
                try {

                    mod = require( path.resolve( argument ) );

                } catch ( ex2 ) {

                    if ( ex2.code !== "MODULE_NOT_FOUND" ) throw ex2;
                    abort( `Can't load module "${ argument }".` );

                }

            }
            options.plugins.push( mod );
            break;

        case "--trace":
            options.trace = true;
            break;

        case "--no-trace":
            options.trace = false;
            break;

        case "-v":
        case "--version":
            console.log( "PEG.js v" + peg.VERSION );
            process.exit();
            break;

        default:
            if ( inputFile !== null ) {

                abort( `Unknown option: "${ argument }".` );

            }
            inputFile = argument;

    }

}

// Validation and defaults

if ( Object.keys( options.dependencies ).length > 0 ) {

    if ( DEPENDENCY_FORMATS.indexOf( options.format ) === -1 ) {

        abort( `Can't use the -d/--dependency option with the "${ options.format }" module format.` );

    }

}

if ( options.exportVar !== null ) {

    if ( EXPORT_VAR_FORMATS.indexOf( options.format ) === -1 ) {

        abort( `Can't use the -e/--export-var option with the "${ options.format }" module format.` );

    }

}

if ( inputFile === null ) inputFile = "-";

if ( outputFile === null ) {

    if ( inputFile === "-" ) outputFile = "-";
    else if ( inputFile ) {

        outputFile = inputFile
            .substr( 0, inputFile.length - path.extname( inputFile ).length )
            + ".js";

    }

}

// Export

options.inputFile = inputFile;
options.outputFile = outputFile;

module.exports = options;