1from __future__ import unicode_literals
2
3import argparse
4
5import dvc.logger as logger
6from dvc.command.base import CmdBase
7from dvc.exceptions import DvcException
8
9
10class CmdRun(CmdBase):
11    def run(self):
12        overwrite = self.args.yes or self.args.overwrite_dvcfile
13
14        if not any(
15            [
16                self.args.deps,
17                self.args.outs,
18                self.args.outs_no_cache,
19                self.args.metrics,
20                self.args.metrics_no_cache,
21                self.args.command,
22            ]
23        ):  # pragma: no cover
24            logger.error(
25                "too few arguments. Specify at least one: '-d', "
26                "'-o', '-O', '-m', '-M', 'command'."
27            )
28            return 1
29
30        try:
31            self.repo.run(
32                cmd=self._parsed_cmd(),
33                outs=self.args.outs,
34                outs_no_cache=self.args.outs_no_cache,
35                metrics=self.args.metrics,
36                metrics_no_cache=self.args.metrics_no_cache,
37                deps=self.args.deps,
38                fname=self.args.file,
39                cwd=self.args.cwd,
40                wdir=self.args.wdir,
41                no_exec=self.args.no_exec,
42                overwrite=overwrite,
43                ignore_build_cache=self.args.ignore_build_cache,
44                remove_outs=self.args.remove_outs,
45                no_commit=self.args.no_commit,
46            )
47        except DvcException:
48            logger.error("failed to run command")
49            return 1
50
51        return 0
52
53    def _parsed_cmd(self):
54        """
55        We need to take into account two cases:
56
57        - ['python code.py foo bar']: Used mainly with dvc as a library
58        - ['echo', 'foo bar']: List of arguments received from the CLI
59
60        The second case would need quoting, as it was passed through:
61                dvc run echo "foo bar"
62        """
63        if len(self.args.command) < 2:
64            return " ".join(self.args.command)
65
66        return " ".join(self._quote_argument(arg) for arg in self.args.command)
67
68    def _quote_argument(self, argument):
69        if " " not in argument or '"' in argument:
70            return argument
71
72        return '"{}"'.format(argument)
73
74
75def add_parser(subparsers, parent_parser):
76    RUN_HELP = (
77        "Generate a stage file from a given "
78        "command and execute the command."
79    )
80    run_parser = subparsers.add_parser(
81        "run", parents=[parent_parser], description=RUN_HELP, help=RUN_HELP
82    )
83    run_parser.add_argument(
84        "-d",
85        "--deps",
86        action="append",
87        default=[],
88        help="Declare dependencies for reproducible cmd.",
89    )
90    run_parser.add_argument(
91        "-o",
92        "--outs",
93        action="append",
94        default=[],
95        help="Declare output file or directory.",
96    )
97    run_parser.add_argument(
98        "-O",
99        "--outs-no-cache",
100        action="append",
101        default=[],
102        help="Declare output file or directory "
103        "(do not put into DVC cache).",
104    )
105    run_parser.add_argument(
106        "-m",
107        "--metrics",
108        action="append",
109        default=[],
110        help="Declare output metric file or directory.",
111    )
112    run_parser.add_argument(
113        "-M",
114        "--metrics-no-cache",
115        action="append",
116        default=[],
117        help="Declare output metric file or directory "
118        "(do not put into DVC cache).",
119    )
120    run_parser.add_argument(
121        "-f",
122        "--file",
123        help="Specify name of the stage file. It should be "
124        "either 'Dvcfile' or have a '.dvc' suffix (e.g. "
125        "'prepare.dvc', 'clean.dvc', etc) in order for "
126        "dvc to be able to find it later. By default "
127        "the first output basename + .dvc is used as "
128        "a stage filename.",
129    )
130    run_parser.add_argument(
131        "-c", "--cwd", default=None, help="Deprecated, use -w and -f instead."
132    )
133    run_parser.add_argument(
134        "-w",
135        "--wdir",
136        default=None,
137        help="Directory within your repo to run your command in.",
138    )
139    run_parser.add_argument(
140        "--no-exec",
141        action="store_true",
142        default=False,
143        help="Only create stage file without actually running it.",
144    )
145    run_parser.add_argument(
146        "-y",
147        "--yes",
148        action="store_true",
149        default=False,
150        help="Deprecated, use --overwrite-dvcfile instead",
151    )
152    run_parser.add_argument(
153        "--overwrite-dvcfile",
154        action="store_true",
155        default=False,
156        help="Overwrite existing dvc file without asking for confirmation.",
157    )
158    run_parser.add_argument(
159        "--ignore-build-cache",
160        action="store_true",
161        default=False,
162        help="Run this stage even if it has been already ran with the same "
163        "command/dependencies/outputs/etc before.",
164    )
165    run_parser.add_argument(
166        "--remove-outs",
167        action="store_true",
168        default=False,
169        help="Remove outputs before running the command.",
170    )
171    run_parser.add_argument(
172        "--no-commit",
173        action="store_true",
174        default=False,
175        help="Don't put files/directories into cache.",
176    )
177    run_parser.add_argument(
178        "command", nargs=argparse.REMAINDER, help="Command to execute."
179    )
180    run_parser.set_defaults(func=CmdRun)
181