Completed
Push — master ( 82fa36...4edb63 )
by Klaus
29s
created

parse_args()   B

Complexity

Conditions 5

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
c 0
b 0
f 0
dl 0
loc 36
rs 8.0894

1 Method

Rating   Name   Duplication   Size   Complexity  
B get_config_updates() 0 30 5
1
#!/usr/bin/env python
2
# coding=utf-8
3
"""
4
This module contains the command-line parsing and help for experiments.
5
6
The command-line interface of sacred is built on top of ``docopt``, which
7
constructs a command-line parser from a usage text. Curiously in sacred we
8
first programmatically generate a usage text and then parse it with ``docopt``.
9
"""
10
from __future__ import division, print_function, unicode_literals
11
import ast
12
from collections import OrderedDict
13
import textwrap
14
import inspect
15
16
from sacred.serializer import restore
17
from sacred.settings import SETTINGS
18
from sacred.utils import set_by_dotted_path
19
20
try:
21
    from shlex import quote as cmd_quote
22
except ImportError:
23
    from pipes import quote as cmd_quote
24
25
26
__sacred__ = True  # marks files that should be filtered from stack traces
27
28
__all__ = ('get_config_updates', 'format_usage')
29
30
31
USAGE_TEMPLATE = """Usage:
32
  {program_name} [(with UPDATE...)] [options]
33
  {program_name} help [COMMAND]
34
  {program_name} (-h | --help)
35
  {program_name} COMMAND [(with UPDATE...)] [options]
36
37
{description}
38
39
Options:
40
{options}
41
42
Arguments:
43
  COMMAND   Name of command to run (see below for list of commands)
44
  UPDATE    Configuration assignments of the form foo.bar=17
45
{arguments}
46
{commands}"""
47
48
49
def get_config_updates(updates):
50
    """
51
    Parse the UPDATES given on the commandline.
52
53
    Parameters
54
    ----------
55
        updates (list[str]):
56
            list of update-strings of the form NAME=LITERAL or just NAME.
57
58
    Returns
59
    -------
60
        (dict, list):
61
            Config updates and named configs to use
62
63
    """
64
    config_updates = {}
65
    named_configs = []
66
    if not updates:
67
        return config_updates, named_configs
68
    for upd in updates:
69
        if upd == '':
70
            continue
71
        path, sep, value = upd.partition('=')
72
        if sep == '=':
73
            path = path.strip()    # get rid of surrounding whitespace
74
            value = value.strip()  # get rid of surrounding whitespace
75
            set_by_dotted_path(config_updates, path, _convert_value(value))
76
        else:
77
            named_configs.append(path)
78
    return config_updates, named_configs
79
80
81
def _format_options_usage(options):
82
    """
83
    Format the Options-part of the usage text.
84
85
    Parameters
86
    ----------
87
        options : list[sacred.commandline_options.CommandLineOption]
88
            A list of all supported commandline options.
89
90
    Returns
91
    -------
92
        str
93
            Text formatted as a description for the commandline options
94
95
    """
96
    options_usage = ""
97
    for op in options:
98
        short, long = op.get_flags()
99
        if op.arg:
100
            flag = "{short} {arg} {long}={arg}".format(
101
                short=short, long=long, arg=op.arg)
102
        else:
103
            flag = "{short} {long}".format(short=short, long=long)
104
105
        wrapped_description = textwrap.wrap(inspect.cleandoc(op.__doc__),
106
                                            width=79,
107
                                            initial_indent=' ' * 32,
108
                                            subsequent_indent=' ' * 32)
109
        wrapped_description = "\n".join(wrapped_description).strip()
110
111
        options_usage += "  {0:28}  {1}\n".format(flag, wrapped_description)
112
    return options_usage
113
114
115
def _format_arguments_usage(options):
116
    """
117
    Construct the Arguments-part of the usage text.
118
119
    Parameters
120
    ----------
121
        options : list[sacred.commandline_options.CommandLineOption]
122
            A list of all supported commandline options.
123
124
    Returns
125
    -------
126
        str
127
            Text formatted as a description of the arguments supported by the
128
            commandline options.
129
130
    """
131
    argument_usage = ""
132
    for op in options:
133
        if op.arg and op.arg_description:
134
            wrapped_description = textwrap.wrap(op.arg_description,
135
                                                width=79,
136
                                                initial_indent=' ' * 12,
137
                                                subsequent_indent=' ' * 12)
138
            wrapped_description = "\n".join(wrapped_description).strip()
139
            argument_usage += "  {0:8}  {1}\n".format(op.arg,
140
                                                      wrapped_description)
141
    return argument_usage
142
143
144
def _format_command_usage(commands):
145
    """
146
    Construct the Commands-part of the usage text.
147
148
    Parameters
149
    ----------
150
        commands : dict[str, func]
151
            dictionary of supported commands.
152
            Each entry should be a tuple of (name, function).
153
154
    Returns
155
    -------
156
        str
157
            Text formatted as a description of the commands.
158
159
    """
160
    if not commands:
161
        return ""
162
    command_usage = "\nCommands:\n"
163
    cmd_len = max([len(c) for c in commands] + [8])
164
    command_doc = OrderedDict(
165
        [(cmd_name, _get_first_line_of_docstring(cmd_doc))
166
         for cmd_name, cmd_doc in commands.items()])
167
    for cmd_name, cmd_doc in command_doc.items():
168
        command_usage += ("  {:%d}  {}\n" % cmd_len).format(cmd_name, cmd_doc)
169
    return command_usage
170
171
172
def format_usage(program_name, description, commands=None, options=()):
173
    """
174
    Construct the usage text.
175
176
    Parameters
177
    ----------
178
        program_name : str
179
            Usually the name of the python file that contains the experiment.
180
        description : str
181
            description of this experiment (usually the docstring).
182
        commands : dict[str, func]
183
            Dictionary of supported commands.
184
            Each entry should be a tuple of (name, function).
185
        options : list[sacred.commandline_options.CommandLineOption]
186
            A list of all supported commandline options.
187
188
    Returns
189
    -------
190
        str
191
            The complete formatted usage text for this experiment.
192
            It adheres to the structure required by ``docopt``.
193
194
    """
195
    usage = USAGE_TEMPLATE.format(
196
        program_name=cmd_quote(program_name),
197
        description=description.strip() if description else '',
198
        options=_format_options_usage(options),
199
        arguments=_format_arguments_usage(options),
200
        commands=_format_command_usage(commands)
201
    )
202
    return usage
203
204
205
def _get_first_line_of_docstring(func):
206
    return textwrap.dedent(func.__doc__ or "").strip().split('\n')[0]
207
208
209
def _convert_value(value):
210
    """Parse string as python literal if possible and fallback to string."""
211
    try:
212
        return restore(ast.literal_eval(value))
213
    except (ValueError, SyntaxError):
214
        if SETTINGS.COMMAND_LINE.STRICT_PARSING:
215
            raise
216
        # use as string if nothing else worked
217
        return value
218