1"""Command line interface for dvc."""
2
3from __future__ import print_function
4from __future__ import unicode_literals
5
6import sys
7import argparse
8
9import dvc.logger as logger
10from dvc.command.base import fix_subparsers
11import dvc.command.init as init
12import dvc.command.destroy as destroy
13import dvc.command.remove as remove
14import dvc.command.move as move
15import dvc.command.unprotect as unprotect
16import dvc.command.run as run
17import dvc.command.repro as repro
18import dvc.command.data_sync as data_sync
19import dvc.command.gc as gc
20import dvc.command.add as add
21import dvc.command.imp as imp
22import dvc.command.config as config
23import dvc.command.checkout as checkout
24import dvc.command.remote as remote
25import dvc.command.cache as cache
26import dvc.command.metrics as metrics
27import dvc.command.install as install
28import dvc.command.root as root
29import dvc.command.lock as lock
30import dvc.command.pipeline as pipeline
31import dvc.command.daemon as daemon
32import dvc.command.commit as commit
33from dvc.exceptions import DvcParserError
34from dvc import VERSION
35
36
37COMMANDS = [
38    init,
39    destroy,
40    add,
41    remove,
42    move,
43    unprotect,
44    run,
45    repro,
46    data_sync,
47    gc,
48    add,
49    imp,
50    config,
51    checkout,
52    remote,
53    cache,
54    metrics,
55    install,
56    root,
57    lock,
58    pipeline,
59    daemon,
60    commit,
61]
62
63
64class DvcParser(argparse.ArgumentParser):
65    """Custom parser class for dvc CLI."""
66
67    def error(self, message):
68        """Custom error method.
69        Args:
70            message (str): error message.
71
72        Raises:
73            dvc.exceptions.DvcParser: dvc parser exception.
74        """
75        logger.error(message)
76        self.print_help()
77        raise DvcParserError()
78
79
80class VersionAction(argparse.Action):  # pragma: no cover
81    # pylint: disable=too-few-public-methods
82    """Shows dvc version and exits."""
83
84    def __call__(self, parser, namespace, values, option_string=None):
85        print(VERSION)
86        sys.exit(0)
87
88
89def get_parent_parser():
90    """Create instances of a parser containing common arguments shared among
91    all the commands.
92
93    When overwritting `-q` or `-v`, you need to instantiate a new object
94    in order to prevent some weird behavior.
95    """
96    parent_parser = argparse.ArgumentParser(add_help=False)
97
98    log_level_group = parent_parser.add_mutually_exclusive_group()
99    log_level_group.add_argument(
100        "-q", "--quiet", action="store_true", default=False, help="Be quiet."
101    )
102    log_level_group.add_argument(
103        "-v",
104        "--verbose",
105        action="store_true",
106        default=False,
107        help="Be verbose.",
108    )
109
110    return parent_parser
111
112
113def parse_args(argv=None):
114    """Parses CLI arguments.
115
116    Args:
117        argv: optional list of arguments to parse. sys.argv is used by default.
118
119    Raises:
120        dvc.exceptions.DvcParserError: raised for argument parsing errors.
121    """
122    parent_parser = get_parent_parser()
123
124    # Main parser
125    desc = "Data Version Control"
126    parser = DvcParser(
127        prog="dvc",
128        description=desc,
129        parents=[parent_parser],
130        formatter_class=argparse.RawTextHelpFormatter,
131    )
132
133    # NOTE: On some python versions action='version' prints to stderr
134    # instead of stdout https://bugs.python.org/issue18920
135    parser.add_argument(
136        "-V",
137        "--version",
138        action=VersionAction,
139        nargs=0,
140        help="Show program's version.",
141    )
142
143    # Sub commands
144    subparsers = parser.add_subparsers(
145        title="Available Commands",
146        metavar="COMMAND",
147        dest="cmd",
148        help="Use dvc COMMAND --help for command-specific help.",
149    )
150
151    fix_subparsers(subparsers)
152
153    for cmd in COMMANDS:
154        cmd.add_parser(subparsers, parent_parser)
155
156    args = parser.parse_args(argv)
157
158    return args
159