text: "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  exports.parseArguments = exports.parseFreeFormArguments = exports.ParseArgumentException = void 0;
  /**
   * @license
   * Copyright Google Inc. All Rights Reserved.
   *
   * Use of this source code is governed by an MIT-style license that can be
   * found in the LICENSE file at https://angular.io/license
   *
   */
  const core_1 = require("@angular-devkit/core");
  const interface_1 = require("./interface");
  class ParseArgumentException extends core_1.BaseException {
      constructor(comments, parsed, ignored) {
          super(`One or more errors occurred while parsing arguments:\n  ${comments.join('\n  ')}`);
          this.comments = comments;
          this.parsed = parsed;
          this.ignored = ignored;
      }
  }
  exports.ParseArgumentException = ParseArgumentException;
  function _coerceType(str, type, v) {
      switch (type) {
          case interface_1.OptionType.Any:
              if (Array.isArray(v)) {
                  return v.concat(str || '');
              }
              return _coerceType(str, interface_1.OptionType.Boolean, v) !== undefined
                  ? _coerceType(str, interface_1.OptionType.Boolean, v)
                  : _coerceType(str, interface_1.OptionType.Number, v) !== undefined
                      ? _coerceType(str, interface_1.OptionType.Number, v)
                      : _coerceType(str, interface_1.OptionType.String, v);
          case interface_1.OptionType.String:
              return str || '';
          case interface_1.OptionType.Boolean:
              switch (str) {
                  case 'false':
                      return false;
                  case undefined:
                  case '':
                  case 'true':
                      return true;
                  default:
                      return undefined;
              }
          case interface_1.OptionType.Number:
              if (str === undefined) {
                  return 0;
              }
              else if (str === '') {
                  return undefined;
              }
              else if (Number.isFinite(+str)) {
                  return +str;
              }
              else {
                  return undefined;
              }
          case interface_1.OptionType.Array:
              return Array.isArray(v)
                  ? v.concat(str || '')
                  : v === undefined
                      ? [str || '']
                      : [v + '', str || ''];
          default:
              return undefined;
      }
  }
  function _coerce(str, o, v) {
      if (!o) {
          return _coerceType(str, interface_1.OptionType.Any, v);
      }
      else {
          const types = o.types || [o.type];
          // Try all the types one by one and pick the first one that returns a value contained in the
          // enum. If there's no enum, just return the first one that matches.
          for (const type of types) {
              const maybeResult = _coerceType(str, type, v);
              if (maybeResult !== undefined && (!o.enum || o.enum.includes(maybeResult))) {
                  return maybeResult;
              }
          }
          return undefined;
      }
  }
  function _getOptionFromName(name, options) {
      const camelName = /(-|_)/.test(name)
          ? core_1.strings.camelize(name)
          : name;
      for (const option of options) {
          if (option.name === name || option.name === camelName) {
              return option;
          }
          if (option.aliases.some(x => x === name || x === camelName)) {
              return option;
          }
      }
      return undefined;
  }
  function _removeLeadingDashes(key) {
      const from = key.startsWith('--') ? 2 : key.startsWith('-') ? 1 : 0;
      return key.substr(from);
  }
  function _assignOption(arg, nextArg, { options, parsedOptions, leftovers, ignored, errors, warnings }) {
      const from = arg.startsWith('--') ? 2 : 1;
      let consumedNextArg = false;
      let key = arg.substr(from);
      let option = null;
      let value = '';
      const i = arg.indexOf('=');
      // If flag is --no-abc AND there's no equal sign.
      if (i == -1) {
          if (key.startsWith('no')) {
              // Only use this key if the option matching the rest is a boolean.
              const from = key.startsWith('no-') ? 3 : 2;
              const maybeOption = _getOptionFromName(core_1.strings.camelize(key.substr(from)), options);
              if (maybeOption && maybeOption.type == 'boolean') {
                  value = 'false';
                  option = maybeOption;
              }
          }
          if (option === null) {
              // Set it to true if it's a boolean and the next argument doesn't match true/false.
              const maybeOption = _getOptionFromName(key, options);
              if (maybeOption) {
                  value = nextArg;
                  let shouldShift = true;
                  if (value && value.startsWith('-') && _coerce(undefined, maybeOption) !== undefined) {
                      // Verify if not having a value results in a correct parse, if so don't shift.
                      shouldShift = false;
                  }
                  // Only absorb it if it leads to a better value.
                  if (shouldShift && _coerce(value, maybeOption) !== undefined) {
                      consumedNextArg = true;
                  }
                  else {
                      value = '';
                  }
                  option = maybeOption;
              }
          }
      }
      else {
          key = arg.substring(0, i);
          option = _getOptionFromName(_removeLeadingDashes(key), options) || null;
          if (option) {
              value = arg.substring(i + 1);
          }
      }
      if (option === null) {
          if (nextArg && !nextArg.startsWith('-')) {
              leftovers.push(arg, nextArg);
              consumedNextArg = true;
          }
          else {
              leftovers.push(arg);
          }
      }
      else {
          const v = _coerce(value, option, parsedOptions[option.name]);
          if (v !== undefined) {
              if (parsedOptions[option.name] !== v) {
                  if (parsedOptions[option.name] !== undefined && option.type !== interface_1.OptionType.Array) {
                      warnings.push(`Option ${JSON.stringify(option.name)} was already specified with value `
                          + `${JSON.stringify(parsedOptions[option.name])}. The new value ${JSON.stringify(v)} `
                          + `will override it.`);
                  }
                  parsedOptions[option.name] = v;
              }
          }
          else {
              let error = `Argument ${key} could not be parsed using value ${JSON.stringify(value)}.`;
              if (option.enum) {
                  error += ` Valid values are: ${option.enum.map(x => JSON.stringify(x)).join(', ')}.`;
              }
              else {
                  error += `Valid type(s) is: ${(option.types || [option.type]).join(', ')}`;
              }
              errors.push(error);
              ignored.push(arg);
          }
      }
      return consumedNextArg;
  }
  /**
   * Parse the arguments in a consistent way, but without having any option definition. This tries
   * to assess what the user wants in a free form. For example, using `--name=false` will set the
   * name properties to a boolean type.
   * This should only be used when there's no schema available or if a schema is "true" (anything is
   * valid).
   *
   * @param args Argument list to parse.
   * @returns An object that contains a property per flags from the args.
   */
  function parseFreeFormArguments(args) {
      const parsedOptions = {};
      const leftovers = [];
      for (let arg = args.shift(); arg !== undefined; arg = args.shift()) {
          if (arg == '--') {
              leftovers.push(...args);
              break;
          }
          if (arg.startsWith('--')) {
              const eqSign = arg.indexOf('=');
              let name;
              let value;
              if (eqSign !== -1) {
                  name = arg.substring(2, eqSign);
                  value = arg.substring(eqSign + 1);
              }
              else {
                  name = arg.substr(2);
                  value = args.shift();
              }
              const v = _coerce(value, null, parsedOptions[name]);
              if (v !== undefined) {
                  parsedOptions[name] = v;
              }
          }
          else if (arg.startsWith('-')) {
              arg.split('').forEach(x => parsedOptions[x] = true);
          }
          else {
              leftovers.push(arg);
          }
      }
      if (leftovers.length) {
          parsedOptions['--'] = leftovers;
      }
      return parsedOptions;
  }
  exports.parseFreeFormArguments = parseFreeFormArguments;
  /**
   * Parse the arguments in a consistent way, from a list of standardized options.
   * The result object will have a key per option name, with the `_` key reserved for positional
   * arguments, and `--` will contain everything that did not match. Any key that don't have an
   * option will be pushed back in `--` and removed from the object. If you need to validate that
   * there's no additionalProperties, you need to check the `--` key.
   *
   * @param args The argument array to parse.
   * @param options List of supported options. {@see Option}.
   * @param logger Logger to use to warn users.
   * @returns An object that contains a property per option.
   */
  function parseArguments(args, options, logger) {
      if (options === null) {
          options = [];
      }
      const leftovers = [];
      const positionals = [];
      const parsedOptions = {};
      const ignored = [];
      const errors = [];
      const warnings = [];
      const state = { options, parsedOptions, positionals, leftovers, ignored, errors, warnings };
      for (let argIndex = 0; argIndex < args.length; argIndex++) {
          const arg = args[argIndex];
          let consumedNextArg = false;
          if (arg == '--') {
              // If we find a --, we're done.
              leftovers.push(...args.slice(argIndex + 1));
              break;
          }
          if (arg.startsWith('--')) {
              consumedNextArg = _assignOption(arg, args[argIndex + 1], state);
          }
          else if (arg.startsWith('-')) {
              // Argument is of form -abcdef.  Starts at 1 because we skip the `-`.
              for (let i = 1; i < arg.length; i++) {
                  const flag = arg[i];
                  // If the next character is an '=', treat it as a long flag.
                  if (arg[i + 1] == '=') {
                      const f = '-' + flag + arg.slice(i + 1);
                      consumedNextArg = _assignOption(f, args[argIndex + 1], state);
                      break;
                  }
                  // Treat the last flag as `--a` (as if full flag but just one letter). We do this in
                  // the loop because it saves us a check to see if the arg is just `-`.
                  if (i == arg.length - 1) {
                      const arg = '-' + flag;
                      consumedNextArg = _assignOption(arg, args[argIndex + 1], state);
                  }
                  else {
                      const maybeOption = _getOptionFromName(flag, options);
                      if (maybeOption) {
                          const v = _coerce(undefined, maybeOption, parsedOptions[maybeOption.name]);
                          if (v !== undefined) {
                              parsedOptions[maybeOption.name] = v;
                          }
                      }
                  }
              }
          }
          else {
              positionals.push(arg);
          }
          if (consumedNextArg) {
              argIndex++;
          }
      }
      // Deal with positionals.
      // TODO(hansl): this is by far the most complex piece of code in this file. Try to refactor it
      //   simpler.
      if (positionals.length > 0) {
          let pos = 0;
          for (let i = 0; i < positionals.length;) {
              let found = false;
              let incrementPos = false;
              let incrementI = true;
              // We do this with a found flag because more than 1 option could have the same positional.
              for (const option of options) {
                  // If any option has this positional and no value, AND fit the type, we need to remove it.
                  if (option.positional === pos) {
                      const coercedValue = _coerce(positionals[i], option, parsedOptions[option.name]);
                      if (parsedOptions[option.name] === undefined && coercedValue !== undefined) {
                          parsedOptions[option.name] = coercedValue;
                          found = true;
                      }
                      else {
                          incrementI = false;
                      }
                      incrementPos = true;
                  }
              }
              if (found) {
                  positionals.splice(i--, 1);
              }
              if (incrementPos) {
                  pos++;
              }
              if (incrementI) {
                  i++;
              }
          }
      }
      if (positionals.length > 0 || leftovers.length > 0) {
          parsedOptions['--'] = [...positionals, ...leftovers];
      }
      if (warnings.length > 0 && logger) {
          warnings.forEach(message => logger.warn(message));
      }
      if (errors.length > 0) {
          throw new ParseArgumentException(errors, parsedOptions, ignored);
      }
      return parsedOptions;
  }
  exports.parseArguments = parseArguments;
  