Skip to content

cli ¤

Module that contains the command line application.

Functions:

  • get_parser

    Return the CLI argument parser.

  • main

    Run the main program.

get_parser ¤

get_parser() -> ArgumentParser

Return the CLI argument parser.

Returns:

Source code in src/shellman/cli.py
 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
 81
 82
 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
def get_parser() -> argparse.ArgumentParser:
    """Return the CLI argument parser.

    Returns:
        An argparse parser.
    """
    parser = argparse.ArgumentParser(prog="shellman")

    parser.add_argument(
        "-c",
        "--context",
        dest="context",
        nargs="+",
        help="context to inject when rendering the template. "
        "You can pass JSON strings or key=value pairs. "
        "Example: `--context project=hello '{\"version\": [0, 3, 1]}'`.",
    )

    parser.add_argument(
        "--context-file",
        dest="context_file",
        help="JSON file to read context from. "
        "By default shellman will try to read the file '%s' "
        "in the current directory." % DEFAULT_JSON_FILE,
    )

    parser.add_argument(
        "-t",
        "--template",
        metavar="TEMPLATE",
        choices=templates._parser_choices(),
        default="helptext",
        dest="template",
        help="the Jinja2 template to use. "
        'Prefix with "path:" to specify the path '
        "to a custom template. "
        "Available templates: %s" % ", ".join(templates._names()),
    )

    parser.add_argument(
        "-m",
        "--merge",
        dest="merge",
        action="store_true",
        help="with multiple input files, merge their contents in the output "
        "instead of appending (default: %(default)s). ",
    )

    parser.add_argument(
        "-o",
        "--output",
        action="store",
        dest="output",
        default=None,
        help="file to write to (default: stdout). "
        "You can use the following variables in the output name: "
        "{basename}, {ext}, {filename} (equal to {basename}.{ext}), "
        "{filepath}, {dirname}, {dirpath}, and {vcsroot} "
        "(git and mercurial supported). "
        "They will be populated from each input file.",
    )

    parser.add_argument(
        "FILE",
        type=_valid_file,
        nargs="*",
        help="path to the file(s) to read. Use - to read on standard input.",
    )
    return parser

main ¤

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

Run the main program.

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

Get the file to parse, construct a Doc object, get file's doc, get the according formatter class, instantiate it with acquired doc and write on specified file (stdout by default).

Parameters:

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

    Arguments passed from the command line.

Returns:

  • int

    An exit code.

Source code in src/shellman/cli.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
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
def main(args: list[str] | None = None) -> int:
    """Run the main program.

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

    Get the file to parse, construct a Doc object, get file's doc,
    get the according formatter class, instantiate it
    with acquired doc and write on specified file (stdout by default).

    Parameters:
        args: Arguments passed from the command line.

    Returns:
        An exit code.
    """
    templates._load_plugin_templates()

    parser = get_parser()
    opts = parser.parse_args(args)

    # Catch errors as early as possible
    if opts.merge and len(opts.FILE) < 2:  # noqa: PLR2004
        print(
            "shellman: warning: --merge option is ignored with less than 2 inputs",
            file=sys.stderr,
        )

    if not opts.FILE and opts.output and _is_format_string(opts.output):
        parser.print_usage(file=sys.stderr)
        print(
            "shellman: error: cannot format output name without file inputs. "
            "Please remove variables from output name, or provide file inputs",
            file=sys.stderr,
        )
        return 2

    # Immediately get the template to throw error if not found
    if opts.template.startswith("path:"):
        template = templates._get_custom_template(opts.template[5:])
    else:
        template = templates.templates[opts.template]

    context = _get_context(opts)

    # Render template with context only
    if not opts.FILE:
        if not context:
            parser.print_usage(file=sys.stderr)
            print("shellman: error: please specify input file(s) or context", file=sys.stderr)
            return 1
        contents = _render(template, None, **context)
        if opts.output:
            _write(contents, opts.output)
        else:
            print(contents)
        return 0

    # Parse input files
    docs: list[DocFile | DocStream] = []
    for file in opts.FILE:
        if file == "-":
            docs.append(DocStream(sys.stdin, filename=_guess_filename(opts.output)))
        else:
            docs.append(DocFile(file))

    # Optionally merge the parsed contents
    if opts.merge:
        new_filename = _guess_filename(opts.output, docs)
        docs = [_merge(docs, new_filename)]

    # If opts.output contains variables, each input has its own output
    if opts.output and _is_format_string(opts.output):
        for doc in docs:
            _write(
                _render(template, doc, **context),
                opts.output.format(**_output_name_variables(doc)),
            )
    # Else, concatenate contents (no effect if already merged), then output to file or stdout
    else:
        contents = "\n\n\n".join(_render(template, doc, **context) for doc in docs)
        if opts.output:
            _write(contents, opts.output)
        else:
            print(contents)

    return 0