Skip to content

cli ¤

Module that contains the command line application.

Functions:

get_duty_parser ¤

get_duty_parser(duty: Duty) -> ArgParser

Get a duty-specific options parser.

Parameters:

  • duty (Duty) –

    The duty to parse for.

Returns:

Source code in src/duty/cli.py
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
def get_duty_parser(duty: Duty) -> ArgParser:
    """Get a duty-specific options parser.

    Parameters:
        duty: The duty to parse for.

    Returns:
        A duty-specific parser.
    """
    parser = ArgParser(
        prog=f"duty {duty.name}",
        add_help=False,
        description=duty.description,
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    add_flags(parser, set_defaults=False)
    return parser

get_parser ¤

get_parser() -> ArgParser

Return the CLI argument parser.

Returns:

Source code in src/duty/cli.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def get_parser() -> ArgParser:
    """Return the CLI argument parser.

    Returns:
        An argparse parser.
    """
    usage = "duty [GLOBAL_OPTS...] [DUTY [DUTY_OPTS...] [DUTY_PARAMS...]...]"
    description = "A simple task runner."
    parser = ArgParser(add_help=False, usage=usage, description=description)

    parser.add_argument(
        "-d",
        "--duties-file",
        default="duties.py",
        help="Python file where the duties are defined.",
    )
    parser.add_argument(
        "-l",
        "--list",
        action="store_true",
        dest="list",
        help="List the available duties.",
    )
    parser.add_argument(
        "-h",
        "--help",
        dest="help",
        nargs="*",
        metavar="DUTY",
        help="Show this help message and exit. Pass duties names to print their help.",
    )
    parser.add_argument("-V", "--version", action="version", version=f"%(prog)s {debug.get_version()}")
    parser.add_argument("--debug-info", action=_DebugInfo, help="Print debug information.")

    add_flags(parser, set_defaults=False)
    parser.add_argument("remainder", nargs=argparse.REMAINDER)

    parser._optionals.title = "Global options"

    return parser

main ¤

main(args: list[str] | None = None) -> int

Run the main program.

This function is executed when you type duty or python -m duty.

Parameters:

  • args (list[str] | None, default: None ) –

    Arguments passed from the command line.

Returns:

  • int

    An exit code.

Source code in src/duty/cli.py
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
def main(args: list[str] | None = None) -> int:
    """Run the main program.

    This function is executed when you type `duty` or `python -m duty`.

    Parameters:
        args: Arguments passed from the command line.

    Returns:
        An exit code.
    """
    parser = get_parser()
    opts = parser.parse_args(args=args)
    remainder = opts.remainder

    collection = Collection(opts.duties_file)
    collection.load()

    if opts.help is not None:
        print_help(parser, opts, collection)
        return 0

    if opts.list:
        print(textwrap.indent(collection.format_help(), prefix="  "))
        return 0

    try:
        arg_lists = split_args(remainder, collection.names())
    except ValueError as error:
        print(error, file=sys.stderr)
        return 1

    if not arg_lists:
        print_help(parser, opts, collection)
        return 1

    global_opts = specified_options(opts, exclude={"duties_file", "list", "help", "remainder"})
    try:
        commands = parse_commands(arg_lists, global_opts, collection)
    except TypeError as error:
        print(f"> {error}", file=sys.stderr)
        return 1

    for duty, posargs, kwargs in commands:
        try:
            duty.run(*posargs, **kwargs)
        except DutyFailure as failure:
            return failure.code

    return 0

parse_args ¤

parse_args(duty: Duty, args: list[str]) -> tuple

Parse the positional and keyword arguments of a duty.

Parameters:

  • duty (Duty) –

    The duty to parse for.

  • args (list[str]) –

    The list of arguments.

Returns:

  • tuple

    The positional and keyword arguments.

Source code in src/duty/cli.py
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
def parse_args(duty: Duty, args: list[str]) -> tuple:
    """Parse the positional and keyword arguments of a duty.

    Parameters:
        duty: The duty to parse for.
        args: The list of arguments.

    Returns:
        The positional and keyword arguments.
    """
    posargs = []
    kwargs = {}

    for arg in args:
        if "=" in arg:
            # we found a keyword argument
            arg_name, arg_value = arg.split("=", 1)
            kwargs[arg_name] = arg_value
        else:
            # we found a positional argument
            posargs.append(arg)

    return validate(duty.function, *posargs, **kwargs)

parse_commands ¤

parse_commands(
    arg_lists: list[list[str]],
    global_opts: dict[str, Any],
    collection: Collection,
) -> list[tuple]

Parse argument lists into ready-to-run duties.

Parameters:

  • arg_lists (list[list[str]]) –

    Lists of arguments lists.

  • global_opts (dict[str, Any]) –

    The global options.

  • collection (Collection) –

    The duties collection.

Returns:

  • list[tuple]

    A list of tuples composed of:

    • a duty
    • its positional arguments
    • its keyword arguments
Source code in src/duty/cli.py
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
def parse_commands(arg_lists: list[list[str]], global_opts: dict[str, Any], collection: Collection) -> list[tuple]:
    """Parse argument lists into ready-to-run duties.

    Parameters:
        arg_lists: Lists of arguments lists.
        global_opts: The global options.
        collection: The duties collection.

    Returns:
        A list of tuples composed of:

            - a duty
            - its positional arguments
            - its keyword arguments
    """
    commands = []
    for arg_list in arg_lists:
        duty = collection.get(arg_list[0])
        opts, remainder = parse_options(duty, arg_list[1:])
        duty.options_override = {**global_opts, **opts}
        commands.append((duty, *parse_args(duty, remainder)))
    return commands

parse_options ¤

parse_options(
    duty: Duty, args: list[str]
) -> tuple[dict, list[str]]

Parse options for a duty.

Parameters:

  • duty (Duty) –

    The duty to parse for.

  • args (list[str]) –

    The CLI args passed for this duty.

Returns:

Source code in src/duty/cli.py
158
159
160
161
162
163
164
165
166
167
168
169
170
def parse_options(duty: Duty, args: list[str]) -> tuple[dict, list[str]]:
    """Parse options for a duty.

    Parameters:
        duty: The duty to parse for.
        args: The CLI args passed for this duty.

    Returns:
        The parsed opts, and the remaining arguments.
    """
    parser = get_duty_parser(duty)
    opts, remainder = parser.parse_known_args(args)
    return specified_options(opts), remainder

print_help ¤

print_help(
    parser: ArgParser,
    opts: Namespace,
    collection: Collection,
) -> None

Print general help or duties help.

Parameters:

Source code in src/duty/cli.py
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
def print_help(parser: ArgParser, opts: argparse.Namespace, collection: Collection) -> None:
    """Print general help or duties help.

    Parameters:
        parser: The main parser.
        opts: The main parsed options.
        collection: A collection of duties.
    """
    if opts.help:
        for duty_name in opts.help:
            try:
                duty = collection.get(duty_name)
            except KeyError:
                print(f"> Unknown duty '{duty_name}'")
            else:
                print(get_duty_parser(duty).format_help())
    else:
        print(parser.format_help())
        print("Available duties:")
        print(textwrap.indent(collection.format_help(), prefix="  "))

specified_options ¤

specified_options(
    opts: Namespace, exclude: set[str] | None = None
) -> dict

Cast an argparse Namespace into a dictionary of options.

Remove all options that were not specified (equal to None).

Parameters:

  • opts (Namespace) –

    The namespace to cast.

  • exclude (set[str] | None, default: None ) –

    Names of options to exclude from the result.

Returns:

  • dict

    A dictionary of specified-only options.

Source code in src/duty/cli.py
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
def specified_options(opts: argparse.Namespace, exclude: set[str] | None = None) -> dict:
    """Cast an argparse Namespace into a dictionary of options.

    Remove all options that were not specified (equal to None).

    Parameters:
        opts: The namespace to cast.
        exclude: Names of options to exclude from the result.

    Returns:
        A dictionary of specified-only options.
    """
    exclude = exclude or set()
    options = opts.__dict__.items()
    return {opt: value for opt, value in options if value is not None and opt not in exclude}

split_args ¤

split_args(
    args: list[str], names: list[str]
) -> list[list[str]]

Split command line arguments into duty commands.

Parameters:

  • args (list[str]) –

    The CLI arguments.

  • names (list[str]) –

    The known duty names.

Raises:

  • ValueError

    When a duty name is missing before an argument, or when the duty name is unknown.

Returns:

Source code in src/duty/cli.py
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def split_args(args: list[str], names: list[str]) -> list[list[str]]:
    """Split command line arguments into duty commands.

    Parameters:
        args: The CLI arguments.
        names: The known duty names.

    Raises:
        ValueError: When a duty name is missing before an argument,
            or when the duty name is unknown.

    Returns:
        The split commands.
    """
    arg_lists = []
    current_arg_list: list[str] = []

    for arg in args:
        if arg in names:
            # We found a duty name.
            if current_arg_list:
                # Append the previous arg list to the result and reset it.
                arg_lists.append(current_arg_list)
                current_arg_list = []
            current_arg_list.append(arg)
        elif current_arg_list:
            # We found an argument.
            current_arg_list.append(arg)
        else:
            # We found an argument but no duty name.
            raise ValueError(f"> Missing duty name before argument '{arg}', or unknown duty name")

    # Don't forget the last arg list.
    if current_arg_list:
        arg_lists.append(current_arg_list)

    return arg_lists