Parse the command line arguments, return an argparse namespace object.

    Other projects can call this function and pass in their own ArgumentParser
    object (which should have a losser ArgumentParser from make_parser() above
    as parent) to do the argument parsing and get the result (this does some
    custom post-processing, beyond what argparse's parse_args() does). For
    example::

        parent_parser = losser.cli.make_parser(...)
        parser = argparse.ArgumentParser(parents=[parent_parser])
        parser.add_argument(...)
        try:
            parsed_args = losser.cli.parse(parser=parser)
        except losser.cli.CommandLineError as err:
            ...

    :raises CommandLineError: If something went wrong during command-line
        parsing. If the exception has a non-empty .message attribute it
        contains an error message that hasn't been printed to stdout yet,
        otherwise any error message has already been printed.

    :raises CommandLineExit: If the result of command-line parsing means that
        the command should exit without continuing, but this is not because of
        an error (for example if the user passed --help). Any help text will
        already have been written to stdout, the exit code that the process
        should exit with is in the exception's .code attribute.
        CommandLineExit is a subclass of CommandLineError above.

def parse(parser=None, args=None):
    """Parse the command line arguments, return an argparse namespace object.

    Other projects can call this function and pass in their own ArgumentParser
    object (which should have a losser ArgumentParser from make_parser() above
    as parent) to do the argument parsing and get the result (this does some
    custom post-processing, beyond what argparse's parse_args() does). For
    example::

        parent_parser = losser.cli.make_parser(...)
        parser = argparse.ArgumentParser(parents=[parent_parser])
        parser.add_argument(...)
        try:
            parsed_args = losser.cli.parse(parser=parser)
        except losser.cli.CommandLineError as err:
            ...

    :raises CommandLineError: If something went wrong during command-line
        parsing. If the exception has a non-empty .message attribute it
        contains an error message that hasn't been printed to stdout yet,
        otherwise any error message has already been printed.

    :raises CommandLineExit: If the result of command-line parsing means that
        the command should exit without continuing, but this is not because of
        an error (for example if the user passed --help). Any help text will
        already have been written to stdout, the exit code that the process
        should exit with is in the exception's .code attribute.
        CommandLineExit is a subclass of CommandLineError above.

    """
    if not parser:
        parser = make_parser()

    try:
        parsed_args = parser.parse_args(args)
    except SystemExit as err:
        raise CommandLineExit(err.code)

    try:
        columns = parsed_args.columns
    except AttributeError:
        columns = collections.OrderedDict()
        parsed_args.columns = columns

    for title, spec in columns.items():
        if "pattern" not in spec:
            raise ColumnWithoutPatternError(
                'Column "{0}" needs a pattern'.format(title))

        # Change length-1 patterns into strings (not lists of one string).
        if len(spec["pattern"]) == 1:
            spec["pattern"] = spec["pattern"][0]

    if columns and parsed_args.columns_file:
        raise ColumnsAndColumnsFileError(
            "You can't use the --column and --columns options together (yet)")
    elif parsed_args.columns_file and not columns:
        parsed_args.columns = parsed_args.columns_file
    elif (not columns) and (not parsed_args.columns_file):
        # Crash if no columns specified.
        # In the future we'll support simply converting all JSON fields to CSV
        # columns if no columns are specified, and this will be removed.
        raise NoColumnsError(
            "You must give either a --columns or at least one -c/--column "
            "argument")
    else:
        assert columns

    return parsed_args