1/*
2 * Jake JavaScript build tool
3 * Copyright 2112 Matthew Eernisse (mde@fleegix.org)
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *         http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17*/
18
19let parseargs = {};
20let isOpt = function (arg) { return arg.indexOf('-') === 0; };
21let removeOptPrefix = function (opt) { return opt.replace(/^--/, '').replace(/^-/, ''); };
22
23/**
24 * @constructor
25 * Parses a list of command-line args into a key/value object of
26 * options and an array of positional commands.
27 * @ param {Array} opts A list of options in the following format:
28 * [{full: 'foo', abbr: 'f'}, {full: 'bar', abbr: 'b'}]]
29 */
30parseargs.Parser = function (opts) {
31  // A key/value object of matching options parsed out of the args
32  this.opts = {};
33  this.taskNames = null;
34  this.envVars = null;
35
36  // Data structures used for parsing
37  this.reg = opts;
38  this.shortOpts = {};
39  this.longOpts = {};
40
41  let self = this;
42  [].forEach.call(opts, function (item) {
43    self.shortOpts[item.abbr] = item;
44    self.longOpts[item.full] = item;
45  });
46};
47
48parseargs.Parser.prototype = new function () {
49
50  let _trueOrNextVal = function (argParts, args) {
51    if (argParts[1]) {
52      return argParts[1];
53    }
54    else {
55      return (!args[0] || isOpt(args[0])) ?
56        true : args.shift();
57    }
58  };
59
60  /**
61   * Parses an array of arguments into options and positional commands
62   * @param {Array} args The command-line args to parse
63   */
64  this.parse = function (args) {
65    let cmds = [];
66    let cmd;
67    let envVars = {};
68    let opts = {};
69    let arg;
70    let argItem;
71    let argParts;
72    let cmdItems;
73    let taskNames = [];
74    let preempt;
75
76    while (args.length) {
77      arg = args.shift();
78
79      if (isOpt(arg)) {
80        arg = removeOptPrefix(arg);
81        argParts = arg.split('=');
82        argItem = this.longOpts[argParts[0]] || this.shortOpts[argParts[0]];
83        if (argItem) {
84          // First-encountered preemptive opt takes precedence -- no further opts
85          // or possibility of ambiguity, so just look for a value, or set to
86          // true and then bail
87          if (argItem.preempts) {
88            opts[argItem.full] = _trueOrNextVal(argParts, args);
89            preempt = true;
90            break;
91          }
92          // If the opt requires a value, see if we can get a value from the
93          // next arg, or infer true from no-arg -- if it's followed by another
94          // opt, throw an error
95          if (argItem.expectValue || argItem.allowValue) {
96            opts[argItem.full] = _trueOrNextVal(argParts, args);
97            if (argItem.expectValue && !opts[argItem.full]) {
98              throw new Error(argItem.full + ' option expects a value.');
99            }
100          }
101          else {
102            opts[argItem.full] = true;
103          }
104        }
105      }
106      else {
107        cmds.unshift(arg);
108      }
109    }
110
111    if (!preempt) {
112      // Parse out any env-vars and task-name
113      while ((cmd = cmds.pop())) {
114        cmdItems = cmd.split('=');
115        if (cmdItems.length > 1) {
116          envVars[cmdItems[0]] = cmdItems[1];
117        }
118        else {
119          taskNames.push(cmd);
120        }
121      }
122
123    }
124
125    return {
126      opts: opts,
127      envVars: envVars,
128      taskNames: taskNames
129    };
130  };
131
132};
133
134module.exports = parseargs;
135