Skip to content

How to define additional commands

idefix_cli can be extended to include arbitrary, user-defined commands, to be written as individual Python modules, later refered to as plugins.

Configuration

To enable plugins, select a directory that will contain their definitions through the configuration file, e.g.

# idefix.cfg
[idefix_cli]
plugins_directory = path/to/my/plugins/

A basic plugin example

Here's simple example illustrating all the requirements for a plugin file. Say that we want to define a idfx hello command

# hello.py
"Say hello n times"

from idefix_cli.lib import print_error


def add_arguments(parser) -> None:
    # Define arbitrary arguments
    # parser is a argparse.ArgumentParser object
    # https://docs.python.org/3/library/argparse.html#argparse.ArgumentParser
    #
    # this function is required, its signature is mandatory, but the body can be left empty
    parser.add_argument(
        "nrepeat",
        type=int,
        help="number of times to say 'hello'. Must be (>=1)",
    )


def command(nrepeat:int) -> int:
    # Define the actual script
    #
    # this function is required and its return type must be int
    # (return 0 is all goes well, otherwise return 1)
    # the arguments must match those defined in add_arguments
    if nrepeat <= 0:
        # illustrate how to deal with exceptions
        print_error(f"Cannot greet less than 1 time !")
        return 1

    print(nrepeat * "Hello " + "!")
    return 0

Note that the name of the file (here hello.py) defines the name of the command (idfx hello). The module-level docstring is also required and serves as the description of the command when idfx --help is invoked.

Public API

The idefix_cli.lib module contains some common utility functions that can be imported

Print a fatal error message to stderr. Normally followed by return 1.

Parameters:

Name Type Description Default
message str

the error message

required
hint str

hint at a potential solution (optional)

None

Returns:

Type Description
None

None

Examples:

>>> def my_command() -> int:
...    if "MYENVVAR" not in os.environ:
...        print_error("Missing MYENVVAR")
...        return 1
...    return 0

Print a non-fatal error message to stderr.

Parameters:

Name Type Description Default
message str

the error message

required

Returns:

Type Description
None

None

Examples:

>>> def my_command() -> int:
...    if "MYENVVAR" not in os.environ:
...        print_warning("Missing MYENVVAR")
...    return 0

Print some exciting news to stdout.

Parameters:

Name Type Description Default
message str

the message to be printed

required

Returns:

Type Description
None

None

Examples:

>>> def my_command() -> int:
...    print_success("Successfully did nothing !")
...    return 0

Print a command, which is to be executed as a subprocess.

Parameters:

Name Type Description Default
cmd list[str]

equivalent argument to be passed to, e.g., subprocess.run

required
loc PathLike[str] | None

the directory (other than cwd) from which the command is meant to be executed.

None

Returns:

Type Description
None

None

Examples:

>>> import os
>>> import subprocess
>>> from pathlib import Path
>>> def my_command() -> int:
...    cmd = ["which", "gcc"]
...    print_subcommand(cmd, loc=Path.home())
...    subprocess.run(cmd)
...    return 0
>>> my_command()
🚀 running which gcc (from ...)
...

run_subcommand

Convenience function to run subprocess while logging with print_subcommand

Parameters:

Name Type Description Default
cmd list[str]

equivalent argument to be passed to subprocess.run

required
loc PathLike[str] | None

the directory (other than cwd) from which the command is meant to be executed.

None
err str | None

error message to be printed in case the subprocess fails

None

Returns:

Name Type Description
retcode int

the return code of the subprocess

files_from_patterns

Parameters:

Name Type Description Default
source PathLike[str]

path to the directory to inspect

required
patterns str

file patterns (e.g. ".py", ".txt" ...)

()
recursive bool

set to True to recurse into the source directory

False
excludes list of str

file patterns to dismiss

None

Returns:

Name Type Description
files list[str]

files and directories sorted in alphabetical order. Directories names end with "/" (or "" on Windows) to make them visually distinct from extension-less files.

Examples:

>>> from pathlib import Path
>>> files_from_patterns(Path.home() / "myproject", "*.py", "*.txt")
["data.txt", "script1.py", "script2.py"]

make_file_tree

Build a simple file tree

Parameters:

Name Type Description Default
file_list (list[str])

...

required
parent_dir (str)

...

required
origin (str)

...

required

Returns:

Name Type Description
file_tree str

a string representation of the parent directory content

Examples:

>>> file_list = ["a.cpp", "a.hpp", "b.cpp", "b.hpp", "README.md"]
>>> print(make_file_tree(file_list, parent_dir=".", origin=".."))
 my_directory
 ├── a.cpp
 ├── a.hpp
 ├── b.cpp
 ├── b.hppp
 └── README.md

requires_idefix (decorator)

Decorate a function that requires Idefix to produce standardized error in case Idefix isn't installed properly.

Examples:

>>> @requires_idefix()
... def my_command():
...    pass

get_idefix_version

Attempt to retrieve Idefix's version. Default to Version("0")

Returns:

Name Type Description
version Version

a Version object that supports

Version

meaningful comparison

Examples:

>>> from packaging.version import Version
>>> if (version := get_idefix_version()) < Version("1.1"):
...     print_error(f"Idefix v{version} is too old (v1.1 or newer is required)")

get_config_file

Return absolute path to the active configuration file. If present, the local configuration is returned, otherwise the global one is to be considered active, even if non-existent.

get_configuration

Parse the whole configuration file (local if present, else global)

Returns:

Name Type Description
cf ConfigParser

parsed configuration object (may be empty).

Examples:

>>> cf = get_configuration()
>>> cf["compilation"]
<Section: compilation>
See also

get_option

get_option

Parse a specific option from the configuration file (local if present, else global)

Parameters:

Name Type Description Default
section_name str

the section where the option is expected

required
option_name str

the name of the option itself

required

Returns:

Name Type Description
val str

the raw string value (may be empty).

Examples:

>>> get_option("compilation", "compiler")
'g++'

prompt_ask

Repeatedly prompt a yes/no alternative until either 'y' or 'n' is received. Return the result as a boolean (yes=True). Case insensitive.