Parse the command-line options and arguments into an object.

{
'args': [...]       // arguments
'help': true,       // true if '-h' option given
// etc.
}

@return {Object} The parsed options. `.args` is the argument list.
@throws {Error} If there is an error parsing argv.
function parseArgv(argv) {
    var parsed = {
        args: [],
        help: false,
        color: null,
        paginate: null,
        outputMode: OM_LONG,
        jsonIndent: 2,
        level: null,
        strict: false,
        pids: null,
        pidsType: null,
        timeFormat: TIME_UTC  // one of the TIME_ constants
    };

    // Turn '-iH' into '-i -H', except for argument-accepting options.
    var args = argv.slice(2);  // drop ['node', 'scriptname']
    var newArgs = [];
    var optTakesArg = {'d': true, 'o': true, 'c': true, 'l': true, 'p': true};
    for (var i = 0; i < args.length; i++) {
        if (args[i].charAt(0) === '-' && args[i].charAt(1) !== '-' &&
            args[i].length > 2)
        {
            var splitOpts = args[i].slice(1).split('');
            for (var j = 0; j < splitOpts.length; j++) {
                newArgs.push('-' + splitOpts[j]);
                if (optTakesArg[splitOpts[j]]) {
                    var optArg = splitOpts.slice(j+1).join('');
                    if (optArg.length) {
                        newArgs.push(optArg);
                    }
                    break;
                }
            }
        } else {
            newArgs.push(args[i]);
        }
    }
    args = newArgs;

    // Expose level definitions to condition vm context
    var condDefines = [];
    Object.keys(upperNameFromLevel).forEach(function (lvl) {
        condDefines.push(
            format('Object.prototype.%s = %s;', upperNameFromLevel[lvl], lvl));
    });
    condDefines = condDefines.join('\n') + '\n';

    var endOfOptions = false;
    while (args.length > 0) {
        var arg = args.shift();
        switch (arg) {
            case '--':
                endOfOptions = true;
                break;
            case '-h': // display help and exit
            case '--help':
                parsed.help = true;
                break;
            case '--version':
                parsed.version = true;
                break;
            case '--strict':
                parsed.strict = true;
                break;
            case '--color':
                parsed.color = true;
                break;
            case '--no-color':
                parsed.color = false;
                break;
            case '--pager':
                parsed.paginate = true;
                break;
            case '--no-pager':
                parsed.paginate = false;
                break;
            case '-o':
            case '--output':
                var name = args.shift();
                var idx = name.lastIndexOf('-');
                if (idx !== -1) {
                    var indentation = Number(name.slice(idx+1));
                    if (! isNaN(indentation)) {
                        parsed.jsonIndent = indentation;
                        name = name.slice(0, idx);
                    }
                }
                parsed.outputMode = OM_FROM_NAME[name];
                if (parsed.outputMode === undefined) {
                    throw new Error('unknown output mode: "'+name+'"');
                }
                break;
            case '-j': // output with JSON.stringify
                parsed.outputMode = OM_JSON;
                break;
            case '-0':
                parsed.outputMode = OM_BUNYAN;
                break;
            case '-L':
                parsed.timeFormat = TIME_LOCAL;
                if (!moment) {
                    throw new Error(
                        'could not find moment package required for "-L"');
                }
                break;
            case '--time':
                var timeArg = args.shift();
                switch (timeArg) {
                case 'utc':
                    parsed.timeFormat = TIME_UTC;
                    break
                case 'local':
                    parsed.timeFormat = TIME_LOCAL;
                    if (!moment) {
                        throw new Error('could not find moment package '
                            + 'required for "--time=local"');
                    }
                    break
                case undefined:
                    throw new Error('missing argument to "--time"');
                default:
                    throw new Error(format('invalid time format: "%s"',
                        timeArg));
                }
                break;
            case '-p':
                if (!parsed.pids) {
                    parsed.pids = [];
                }
                var pidArg = args.shift();
                var pid = +(pidArg);
                if (!isNaN(pid) || pidArg === '*') {
                    if (parsed.pidsType && parsed.pidsType !== 'num') {
                        throw new Error(format('cannot mix PID name and '
                            + 'number arguments: "%s"', pidArg));
                    }
                    parsed.pidsType = 'num';
                    if (!parsed.pids) {
                        parsed.pids = [];
                    }
                    parsed.pids.push(isNaN(pid) ? pidArg : pid);
                } else {
                    if (parsed.pidsType && parsed.pidsType !== 'name') {
                        throw new Error(format('cannot mix PID name and '
                            + 'number arguments: "%s"', pidArg));
                    }
                    parsed.pidsType = 'name';
                    parsed.pids = pidArg;
                }
                break;
            case '-l':
            case '--level':
                var levelArg = args.shift();
                var level = +(levelArg);
                if (isNaN(level)) {
                    level = +levelFromName[levelArg.toLowerCase()];
                }
                if (isNaN(level)) {
                    throw new Error('unknown level value: "'+levelArg+'"');
                }
                parsed.level = level;
                break;
            case '-c':
            case '--condition':
                gUsingConditionOpts = true;
                var condition = args.shift();
                if (Boolean(process.env.BUNYAN_EXEC &&
                    process.env.BUNYAN_EXEC === 'vm'))
                {
                    parsed.condVm = parsed.condVm || [];
                    var scriptName = 'bunyan-condition-'+parsed.condVm.length;
                    var code = condDefines + condition;
                    var script;
                    try {
                        script = vm.createScript(code, scriptName);
                    } catch (complErr) {
                        throw new Error(format('illegal CONDITION code: %s\n'
                            + '  CONDITION script:\n'
                            + '%s\n'
                            + '  Error:\n'
                            + '%s',
                            complErr, indent(code), indent(complErr.stack)));
                    }

                    // Ensure this is a reasonably safe CONDITION.
                    try {
                        script.runInNewContext(minValidRecord);
                    } catch (condErr) {
                        throw new Error(format(
                            /* JSSTYLED */
                            'CONDITION code cannot safely filter a minimal Bunyan log record\n'
                            + '  CONDITION script:\n'
                            + '%s\n'
                            + '  Minimal Bunyan log record:\n'
                            + '%s\n'
                            + '  Filter error:\n'
                            + '%s',
                            indent(code),
                            indent(JSON.stringify(minValidRecord, null, 2)),
                            indent(condErr.stack)
                            ));
                    }
                    parsed.condVm.push(script);
                } else  {
                    parsed.condFuncs = parsed.condFuncs || [];
                    parsed.condFuncs.push(funcWithReturnFromSnippet(condition));
                }
                break;
            default: // arguments
                if (!endOfOptions && arg.length > 0 && arg[0] === '-') {
                    throw new Error('unknown option "'+arg+'"');
                }
                parsed.args.push(arg);
                break;
        }
    }
    //TODO: '--' handling and error on a first arg that looks like an option.

    return parsed;
}