cli_test.py

The main file that runs the program

This file is executable and can be called from the terminal like:

foo@bar:~$ cd [path to directory of this file] foo@bar:~[path to directory of this file]$ ./cli_test.py [cmd line]

or if installed in a global location:

foo@bar:~$ cli_test [cmd line]

Typical usage is show in the main() method.

CliTest

The main class, responsible for the operation of the program

Public methods

main: The main method of the program

This class does the most of the work of a typical CLI program. It parses command line options, loads/saves config files, and performs the operations required for the program.

Source code in src/cli_test.py
class CliTest:
    """
    The main class, responsible for the operation of the program

    Public methods:
        main: The main method of the program

    This class does the most of the work of a typical CLI program. It parses
    command line options, loads/saves config files, and performs the operations
    required for the program.
    """

    # --------------------------------------------------------------------------
    # Class constants
    # --------------------------------------------------------------------------

    # --------------------------------------------------------------------------
    # find path to project
    P_DIR_PRJ = Path(__file__).parents[1].resolve()

    # --------------------------------------------------------------------------

    # short description
    S_PP_SHORT_DESC = "Short description"

    # version string
    S_PP_VERSION = "Version 0.0.0"

    # config option strings
    S_ARG_CFG_OPTION = "-c"
    S_ARG_CFG_DEST = "CFG_DEST"
    # I18N: config file option help
    S_ARG_CFG_HELP = _("load configuration from file")
    # I18N: config file dest
    S_ARG_CFG_METAVAR = _("FILE")

    # debug option strings
    S_ARG_DBG_OPTION = "-d"
    S_ARG_DBG_ACTION = "store_true"
    S_ARG_DBG_DEST = "DBG_DEST"
    # I18N: debug mode help
    S_ARG_DBG_HELP = _("enable debugging mode")

    # config option strings
    S_ARG_HLP_OPTION = "-h"
    S_ARG_HLP_ACTION = "store_true"
    S_ARG_HLP_DEST = "HLP_DEST"
    # I18N: help option help
    S_ARG_HLP_HELP = _("show this help message and exit")

    # config option strings
    S_ARG_UNINST_OPTION = "--uninstall"
    S_ARG_UNINST_ACTION = "store_true"
    S_ARG_UNINST_DEST = "UNINST_DEST"
    # I18N: uninstall option help
    S_ARG_UNINST_HELP = _("uninstall this program")

    # about string
    S_ABOUT = (
        "\n"
        "CLI_Test\n"
        f"{S_PP_SHORT_DESC}\n"
        f"{S_PP_VERSION}\n"
        "https://github.com/cyclopticnerve/CLI_Test\n"
    )

    # I18N if using argparse, add help at end of about
    S_ABOUT_HELP = _("Use -h for help") + "\n"

    # path to default config file
    P_CFG_DEF = Path("conf/cli_test.json")

    # uninst not found
    # I18N: uninstall not found
    S_ERR_NO_UNINST = _("Uninstall files not found")

    # --------------------------------------------------------------------------
    # Instance methods
    # --------------------------------------------------------------------------

    # --------------------------------------------------------------------------
    # Initialize the new object
    # --------------------------------------------------------------------------
    def __init__(self):
        """
        Initialize the new object

        Initializes a new instance of the class, setting the default values
        of its properties, and any other code that needs to run to create a
        new object.
        """

        # set defaults

        # cmd line args
        self._dict_args = {}
        self._debug = False
        self._path_cfg_arg = None

        # the final cfg path and dict
        self._path_cfg = None
        self._dict_cfg = {}

    # --------------------------------------------------------------------------
    # Public methods
    # --------------------------------------------------------------------------

    # --------------------------------------------------------------------------
    # The main method of the program
    # --------------------------------------------------------------------------
    def main(self):
        """
        The main method of the program

        This method is the main entry point for the program, initializing the
        program, and performing its steps.
        """

        # ----------------------------------------------------------------------
        # setup

        # call boilerplate code
        self._setup()

        # ----------------------------------------------------------------------
        # main stuff

        # do the thing with the thing
        print(self._func())

        # ----------------------------------------------------------------------
        # teardown

        # call boilerplate code
        self._teardown()

    # --------------------------------------------------------------------------
    # Private methods
    # --------------------------------------------------------------------------

    # --------------------------------------------------------------------------
    # Short description
    # --------------------------------------------------------------------------
    def _func(self):
        """
        Short description

        Args:
            var_name: Short description

        Returns:
            Description

        Raises:
            exception_type(vars): Description

        Long description (including HTML).
        """

        # check for debug flag
        if self._debug:
            # I18N: context for this string
            return _("this is func (DEBUG)")

        # no debug, return normal result
        # I18N: context for this string
        return _("this is func")

    # --------------------------------------------------------------------------
    # Boilerplate to use at the start of main
    # --------------------------------------------------------------------------
    def _setup(self):
        """
        Boilerplate to use at the start of main

        Perform some mundane stuff like running the arg parser and loading
        config files.
        """

        # print default about text
        print(self.S_ABOUT)

        # ----------------------------------------------------------------------
        # use cmd line

        # create a parser object in case we need it
        parser = argparse.ArgumentParser(
            formatter_class=CNFormatter, add_help=False
        )

        # add help text to about block
        print(self.S_ABOUT_HELP)

        # add cfg option
        parser.add_argument(
            self.S_ARG_CFG_OPTION,
            dest=self.S_ARG_CFG_DEST,
            help=self.S_ARG_CFG_HELP,
            metavar=self.S_ARG_CFG_METAVAR,
        )

        # add debug option
        parser.add_argument(
            self.S_ARG_DBG_OPTION,
            action=self.S_ARG_DBG_ACTION,
            dest=self.S_ARG_DBG_DEST,
            help=self.S_ARG_DBG_HELP,
        )

        # add help option
        parser.add_argument(
            self.S_ARG_HLP_OPTION,
            action=self.S_ARG_HLP_ACTION,
            dest=self.S_ARG_HLP_DEST,
            help=self.S_ARG_HLP_HELP,
        )

        # add help option
        parser.add_argument(
            self.S_ARG_UNINST_OPTION,
            action=self.S_ARG_UNINST_ACTION,
            dest=self.S_ARG_UNINST_DEST,
            help=self.S_ARG_UNINST_HELP,
        )

        # run the parser
        args = parser.parse_args()
        self._dict_args = vars(args)

        # if -h passed, this will print and exit
        if self._dict_args.get(self.S_ARG_HLP_DEST, False):
            parser.print_help()
            print()
            sys.exit(-1)

        # set props from args
        self._debug = self._dict_args.get(self.S_ARG_DBG_DEST, self._debug)
        self._path_cfg_arg = self._dict_args.get(
            self.S_ARG_CFG_DEST, self._path_cfg_arg
        )

        # punt to sub func
        if self._dict_args.get(self.S_ARG_UNINST_DEST, False):
            self._do_uninstall()

        # ----------------------------------------------------------------------
        # use cfg

        # fix paths and get cfg to load
        self._get_path_cfg()

        # load config file (or not, if no param and not using -c)
        self._load_config()

    # --------------------------------------------------------------------------
    # Boilerplate to use at the end of main
    # --------------------------------------------------------------------------
    def _teardown(self):
        """
        Boilerplate to use at the end of main

        Perform some mundane stuff like saving config files.
        """

        # ----------------------------------------------------------------------
        # use cfg

        # call to save config
        self._save_config()

    # --------------------------------------------------------------------------
    # Get path to config file from cmd line option or default
    # --------------------------------------------------------------------------
    def _get_path_cfg(self):
        """
        Get path to config file from cmd line option or default

        This method figures the config path from either:
        1. the command line -c option (if present)
        or
        2. the self.P_CFG_DEF value (if present)

        If you use the -c option, and the file exists, it will used as the
        _path_cfg property, and processing stops.
        If you do not use the -c option, or it is not present on the command
        line, the path_def value will be used.
        If you use neither, nothing happens to the _path_cfg property.
        """

        # accept path or str
        path_def = self.P_CFG_DEF
        if path_def:
            path_def = Path(path_def)
            if not path_def.is_absolute():
                # make abs rel to self
                path_def = self.P_DIR_PRJ / path_def

        # accept path or str
        if self._path_cfg_arg:
            self._path_cfg_arg = Path(self._path_cfg_arg)
            if not self._path_cfg_arg.is_absolute():
                # make abs rel to self
                self._path_cfg_arg = self.P_DIR_PRJ / self._path_cfg_arg

        # ----------------------------------------------------------------------

        # assume def
        if path_def:
            self._path_cfg = path_def

        # arg supersedes def
        if self._path_cfg_arg:
            self._path_cfg = self._path_cfg_arg

    # --------------------------------------------------------------------------
    # Load config data from a file
    # --------------------------------------------------------------------------
    def _load_config(self):
        """
        Load config data from a file

        This method loads data from a config file. It is written to load a dict
        from a json file, but it can be used for other formats as well. It uses
        the values of self._dict_cfg and self._path_cfg to load the config
        data.
        """

        # if one or the other, load it
        if self._path_cfg and self._path_cfg.exists():
            self._dict_cfg = F.load_dicts([self._path_cfg], self._dict_cfg)

        # throw in a debug test
        if self._debug:
            print("load cfg from:", self._path_cfg)
            F.pp(self._dict_cfg, label="load cfg")

    # --------------------------------------------------------------------------
    # Save config data to a file
    # --------------------------------------------------------------------------
    def _save_config(self):
        """
        Save config data to a file

        This method saves the config data to the same file it was loaded from.
        It is written to save a dict to a json file, but it can be used for
        other formats as well. It uses the values of self._dict_cfg and
        self._path_cfg to save the config data.
        """

        # save dict to path
        if self._path_cfg:
            self._path_cfg.parent.mkdir(parents=True, exist_ok=True)
            F.save_dict(self._dict_cfg, [self._path_cfg])

        # throw in a debug test
        if self._debug:
            print("save cfg to:", self._path_cfg)
            F.pp(self._dict_cfg, label="save cfg")

    # --------------------------------------------------------------------------
    # Handle the --uninstall cmd line op
    # --------------------------------------------------------------------------
    def _do_uninstall(self):
        """
        Handle the -- uninstall cmd line op
        """

        # find uninstall file
        path_uninst = (
            Path.home() / ".local/share/cli_test/uninstall.py"
        )

        # if path exists
        if path_uninst.exists():

            # run uninstall and exit
            cmd = str(path_uninst)
            subprocess.run(cmd, shell=True, check=True)
            sys.exit(-1)
        else:
            print(self.S_ERR_NO_UNINST)
            sys.exit(-1)

__init__()

Initialize the new object

Initializes a new instance of the class, setting the default values of its properties, and any other code that needs to run to create a new object.

Source code in src/cli_test.py
def __init__(self):
    """
    Initialize the new object

    Initializes a new instance of the class, setting the default values
    of its properties, and any other code that needs to run to create a
    new object.
    """

    # set defaults

    # cmd line args
    self._dict_args = {}
    self._debug = False
    self._path_cfg_arg = None

    # the final cfg path and dict
    self._path_cfg = None
    self._dict_cfg = {}

main()

The main method of the program

This method is the main entry point for the program, initializing the program, and performing its steps.

Source code in src/cli_test.py
def main(self):
    """
    The main method of the program

    This method is the main entry point for the program, initializing the
    program, and performing its steps.
    """

    # ----------------------------------------------------------------------
    # setup

    # call boilerplate code
    self._setup()

    # ----------------------------------------------------------------------
    # main stuff

    # do the thing with the thing
    print(self._func())

    # ----------------------------------------------------------------------
    # teardown

    # call boilerplate code
    self._teardown()