********* Tutorials ********* Run a command based application with craft-cli ============================================== This tutorial will explain how to use Craft CLI to run an application that is based on commands. Along the way you will define a simple command (named ``unlink``, with the functionality of removing files), and call the appropriate library mechanisms for that command to be executed when running the application. Prerequisites ------------- Craft CLI is a standard Python library, so the best way to have it available is installed in a virtual environment. The first step, then, is to create a virtual environment (you may skip this test if you already have one):: $ python3 -m venv env Note that Python 3.8 or 3.9 are the supported versions. Then enable the virtual environment and install Craft CLI:: $ source env/bin/activate $ pip install craft-cli Define the command and run it using the Dispatcher -------------------------------------------------- First start with a class sub-classing ``BaseCommand`` with the appropriate attributes to name it and have automatic help texts, then provide a ``fill_parser`` method to declare what arguments are possible for this command, and finally a ``run`` method where the "real" functionality is implemented:: import pathlib import textwrap import sys from craft_cli import ( ArgumentParsingError, BaseCommand, CommandGroup, CraftError, Dispatcher, EmitterMode, ProvideHelpException, emit, ) class RemoveFileCommand(BaseCommand): """Remove the indicated file.""" name = "unlink" help_msg = "Remove the indicated file." overview = textwrap.dedent(""" Remove the indicated file. A file needs to be indicated. It is an argument error if the path does not exist or it's a directory. It will return successfully if the file was properly removed. """) def fill_parser(self, parser): """Add own parameters to the general parser.""" parser.add_argument("filepath", type=pathlib.Path, help="The file to be removed") def run(self, parsed_args): """Run the command.""" if not parsed_args.filepath.exists() or parsed_args.filepath.is_dir(): raise ArgumentParsingError("The indicated path is not a file or does not exist.") try: parsed_args.filepath.unlink() except Exception as exc: raise CraftError(f"Problem removing the file: {exc}.") emit.message("File removed successfully.") Then initiate the ``emit`` object and call the ``Dispatcher`` functionality:: emit.init(EmitterMode.BRIEF, "example-app", "Starting example app v1.") command_groups = [CommandGroup("Basic", [RemoveFileCommand])] summary = "Example application for the craft-cli tutorial." try: dispatcher = Dispatcher("example-app", command_groups, summary=summary) dispatcher.pre_parse_args(sys.argv[1:]) dispatcher.load_command(None) dispatcher.run() except (ArgumentParsingError, ProvideHelpException) as err: print(err, file=sys.stderr) # to stderr, as argparse normally does emit.ended_ok() except CraftError as err: emit.error(err) except KeyboardInterrupt as exc: error = CraftError("Interrupted.") error.__cause__ = exc emit.error(error) except Exception as exc: error = CraftError(f"Application internal error: {exc!r}") error.__cause__ = exc emit.error(error) else: emit.ended_ok() Finally, put both chunks of code in a ``example-app.py`` file, and (having the virtual environment you prepared at the beginning still activated), run it. You should see the help message for the whole application (as a command is missing, which would be the same output if you pass the ``help``, ``-h`` or ``--help`` parameters):: $ python example-app.py Usage: example-app [help] Summary: Example application for the craft-cli tutorial. Global options: -h, --help: Show this help message and exit -v, --verbose: Show debug information and be more verbose -q, --quiet: Only show warnings and errors, not progress --verbosity: Set the verbosity level to 'quiet', 'brief', 'verbose', 'debug' or 'trace'", Starter commands: Commands can be classified as follows: Example: unlink For more information about a command, run 'example-app help '. For a summary of all commands, run 'example-app help --all'. Ask help for specifically for the command:: $ python example-app.py help unlink Usage: example-app unlink [options] Summary: Remove the indicated file. A file needs to be indicated. It is an argument error if the path does not exist or it's a directory. It will return successfully if the file was properly removed. Positional arguments: filepath: The file to be removed Options: -h, --help: Show this help message and exit -v, --verbose: Show debug information and be more verbose -q, --quiet: Only show warnings and errors, not progress --verbosity: Set the verbosity level to 'quiet', 'brief', 'verbose', 'debug' or 'trace'", For a summary of all commands, run 'example-app help --all'. Time to run the command on a file, you should see the successful message:: $ touch testfile $ ls testfile testfile $ env/bin/python example-app.py unlink testfile File removed successfully. $ ls testfile ls: cannot access 'testfile': No such file or directory Explore different error situations, first trying to remove a directory, then trying to remove a file but with "unexpected" problems:: $ mkdir testdir $ python example-app.py unlink testdir The indicated path is not a file or does not exist. $ touch /tmp/testfile $ sudo chown root /tmp/testfile $ python example-app.py unlink /tmp/testfile Problem removing the file: [Errno 1] Operation not permitted: '/tmp/testfile'. Full execution log: '/home/user/.cache/example-app/log/example-app-20220114-120745.861866.log' Congratulations! You have built a complete application with good UX by using Craft CLI and implementing the functionality in one command.