hunk.code_window:     { name: 'helloBool', aliases: [], type: OptionType.Boolean, description: '' },
       { name: 'arr', aliases: [ 'a' ], type: OptionType.Array, description: '' },
       { name: 'p1', positional: 0, aliases: [], type: OptionType.String, description: '' },
       { name: 'p2', positional: 1, aliases: [], type: OptionType.String, description: '' },
       { name: 't1', aliases: [], type: OptionType.Boolean,
         types: [OptionType.Boolean, OptionType.String], description: '' },
       { name: 't2', aliases: [], type: OptionType.Boolean,
         types: [OptionType.Boolean, OptionType.Number], description: '' },
  
hunk.labels: keep keep keep add keep keep keep keep
hunk.after_edit:     { name: 'p3', positional: 2, aliases: [], type: OptionType.Number, description: '' },
  
hunk.file_path: packages/angular/cli/models/parser_spec.ts
hunk.type: add
file: /**
   * @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
   *
   */
  import { BaseException, strings } from '@angular-devkit/core';
  import { Arguments, Option, OptionType, Value } from './interface';
  
  
  export class ParseArgumentException extends BaseException {
    constructor(
      public readonly comments: string[],
      public readonly parsed: Arguments,
      public readonly ignored: string[],
    ) {
      super(`One or more errors occured while parsing arguments:\n  ${comments.join('\n  ')}`);
    }
  }
  
  
  function _coerceType(str: string | undefined, type: OptionType, v?: Value): Value | undefined {
    switch (type) {
      case OptionType.Any:
        if (Array.isArray(v)) {
          return v.concat(str || '');
        }
  
        return _coerceType(str, OptionType.Boolean, v) !== undefined
             ? _coerceType(str, OptionType.Boolean, v)
             : _coerceType(str, OptionType.Number, v) !== undefined
             ? _coerceType(str, OptionType.Number, v)
             : _coerceType(str, OptionType.String, v);
  
      case OptionType.String:
        return str || '';
  
      case OptionType.Boolean:
        switch (str) {
          case 'false':
            return false;
  
          case undefined:
          case '':
          case 'true':
            return true;
  
          default:
            return undefined;
        }
  
      case OptionType.Number:
        if (str === undefined) {
          return 0;
        } else if (Number.isFinite(+str)) {
          return +str;
        } else {
          return undefined;
        }
  
      case OptionType.Array:
        return Array.isArray(v) ? v.concat(str || '') : [str || ''];
  
      default:
        return undefined;
    }
  }
  
  function _coerce(str: string | undefined, o: Option | null, v?: Value): Value | undefined {
    if (!o) {
      return _coerceType(str, 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) {
          if (!o.enum || o.enum.includes(maybeResult)) {
            return maybeResult;
          }
        }
      }
  
      return undefined;
    }
  }
  
  
  function _getOptionFromName(name: string, options: Option[]): Option | undefined {
    const cName = strings.camelize(name);
  
    for (const option of options) {
      if (option.name == name || option.name == cName) {
        return option;
      }
  
      if (option.aliases.some(x => x == name || x == cName)) {
        return option;
      }
    }
  
    return undefined;
  }
  
  
  function _assignOption(
    arg: string,
    args: string[],
    options: Option[],
    parsedOptions: Arguments,
    _positionals: string[],
    leftovers: string[],
    ignored: string[],
    errors: string[],
  ) {
    let key = arg.substr(2);
    let option: Option | null = 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 maybeOption = _getOptionFromName(key.substr(3), options);
        if (maybeOption && maybeOption.type == 'boolean') {
          value = 'false';
          option = maybeOption;
        }
      } else if (key.startsWith('no')) {
        // Only use this key if the option matching the rest is a boolean.
        const maybeOption = _getOptionFromName(key.substr(2), 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 = args[0];
          let shouldShift = true;
  
          if (value && value.startsWith('-')) {
            // Verify if not having a value results in a correct parse, if so don't shift.
            if (_coerce(undefined, maybeOption) !== undefined) {
              shouldShift = false;
            }
          }
  
          // Only absorb it if it leads to a better value.
          if (shouldShift && _coerce(value, maybeOption) !== undefined) {
            args.shift();
          } else {
            value = '';
          }
          option = maybeOption;
        }
      }
    } else {
      key = arg.substring(0, i);
      option = _getOptionFromName(key, options) || null;
      if (option) {
        value = arg.substring(i + 1);
      }
    }
    if (option === null) {
      if (args[0] && !args[0].startsWith('--')) {
        leftovers.push(arg, args[0]);
        args.shift();
      } else {
        leftovers.push(arg);
      }
    } else {
      const v = _coerce(value, option, parsedOptions[option.name]);
      if (v !== undefined) {
        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);
      }
    }
  }
  
  
  /**
   * 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.
   */
  export function parseFreeFormArguments(args: string[]): Arguments {
    const parsedOptions: Arguments = {};
    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: string;
        let value: string | undefined;
        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);
      }
    }
  
    parsedOptions['--'] = leftovers;
  
    return parsedOptions;
  }
  
  
  /**
   * 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}.
   * @returns An object that contains a property per option.
   */
  export function parseArguments(args: string[], options: Option[] | null): Arguments {
    if (options === null) {
      options = [];
    }
  
    const leftovers: string[] = [];
    const positionals: string[] = [];
    const parsedOptions: Arguments = {};
  
    const ignored: string[] = [];
    const errors: string[] = [];
  
    for (let arg = args.shift(); arg !== undefined; arg = args.shift()) {
      if (arg == '--') {
        // If we find a --, we're done.
        leftovers.push(...args);
        break;
      }
  
      if (arg.startsWith('--')) {
        _assignOption(arg, args, options, parsedOptions, positionals, leftovers, ignored, errors);
      } 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);
            _assignOption(f, args, options, parsedOptions, positionals, leftovers, ignored, errors);
            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;
            _assignOption(arg, args, options, parsedOptions, positionals, leftovers, ignored, errors);
          } 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);
      }
    }
  
    // Deal with positionals.
    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, we need to remove it.
          if (option.positional === pos) {
            if (parsedOptions[option.name] === undefined) {
              parsedOptions[option.name] = positionals[i];
              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 (errors.length > 0) {
      throw new ParseArgumentException(errors, parsedOptions, ignored);
    }
  
    return parsedOptions;
  }
  