pw_cli#

Enhance and accelerate custom command-line tooling

Stable Python

  • Add some flair: Build out consistent experiences with building blocks for command-line utilities.

  • Accelerate workflows: Create custom pw commands to streamline common developer workflows.

API reference

Reference details about the pw_cli Python API

This directory contains the pw command line interface (CLI) that facilitates working with Pigweed. The CLI module adds several subcommands prefixed with pw, and provides a mechanism for other Pigweed modules to behave as “plugins” and register themselves as pw commands as well. After activating the Pigweed environment, these commands will be available for use.

pw includes the following commands by default:

doctor   Check that the environment is set up correctly for Pigweed.
format   Check and fix formatting for source files.
help     Display detailed information about pw commands.
ide      Configure editors and IDEs to work best with Pigweed.
logdemo  Show how logs look at various levels.
module   Utilities for managing modules.
test     Run Pigweed unit tests built using GN.
watch    Watch files for changes and rebuild.

To see an up-to-date list of pw subcommands, run pw --help.

Invoking pw#

pw subcommands are invoked by providing the command name. Arguments prior to the command are interpreted by pw itself; all arguments after the command name are interpreted by the command.

Here are some example invocations of pw:

# Run the doctor command
$ pw doctor

# Run format --fix with debug-level logs
$ pw --loglevel debug format --fix

# Display help for the pw command
$ pw -h watch

# Display help for the watch command
$ pw watch -h

Registering pw plugins#

Projects can register their own Python scripts as pw commands. pw plugins are registered by providing the command name, module, and function in the pigweed.json file. pigweed.json files can add new commands or override built-in commands. Since they are accessed by module name, plugins must be defined in Python packages that are installed in the Pigweed virtual environment.

pigweed.json file format#

pigweed.json contains plugin entries in the following format:

{
  "pw": {
    "pw_cli": {
      "plugins": {
        "<plugin name>": {
          "module": "<module containing plugin>",
          "function": "<entry point for plugin>"
        },
        ...
      }
    }
  }
}

The following example registers three commands:

{
  "pw": {
    "pw_cli": {
      "plugins": {
        "presubmit": {
          "module": "my_cool_project.tools",
          "function": "run_presubmit"
        },
        "test": {
          "module": "my_cool_project.testing",
          "function": "run_test"
        },
        "flash": {
          "module": "my_cool_project.flash",
          "function": "main"
        }
      }
    }
  }
}

Defining a plugin function#

Any function without required arguments may be used as a plugin function. The function should return an int, which the pw uses as the exit code. The pw tool uses the function docstring as the help string for the command.

Typically, pw commands parse their arguments with the argparse module. pw sets sys.argv so it contains only the arguments for the plugin, so plugins can behave the same whether they are executed independently or through pw.

Example#

This example shows a function that is registered as a pw plugin.

# my_package/my_module.py

def _do_something(device):
    ...

def main() -> int:
    """Do something to a connected device."""

    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--device', help='Set which device to target')
    return _do_something(**vars(parser.parse_args()))


if __name__ == '__main__':
    logging.basicConfig(format='%(message)s', level=logging.INFO)
    sys.exit(main())

This plugin is registered in a PW_PLUGINS file in the current working directory or a parent of it.

# Register my_commmand
my_command my_package.my_module main

The function is now available through the pw command, and will be listed in pw’s help. Arguments after the command name are passed to the plugin.

$ pw

 ▒█████▄   █▓  ▄███▒  ▒█    ▒█ ░▓████▒ ░▓████▒ ▒▓████▄
  ▒█░  █░ ░█▒ ██▒ ▀█▒ ▒█░ █ ▒█  ▒█   ▀  ▒█   ▀  ▒█  ▀█▌
  ▒█▄▄▄█░ ░█▒ █▓░ ▄▄░ ▒█░ █ ▒█  ▒███    ▒███    ░█   █▌
  ▒█▀     ░█░ ▓█   █▓ ░█░ █ ▒█  ▒█   ▄  ▒█   ▄  ░█  ▄█▌
  ▒█      ░█░ ░▓███▀   ▒█▓▀▓█░ ░▓████▒ ░▓████▒ ▒▓████▀

usage: pw [-h] [-C DIRECTORY] [-l LOGLEVEL] [--no-banner] [command] ...

The Pigweed command line interface (CLI).

...

supported commands:
  doctor        Check that the environment is set up correctly for Pigweed.
  format        Check and fix formatting for source files.
  help          Display detailed information about pw commands.
  ...
  my_command    Do something to a connected device.

$ pw my_command -h

 ▒█████▄   █▓  ▄███▒  ▒█    ▒█ ░▓████▒ ░▓████▒ ▒▓████▄
  ▒█░  █░ ░█▒ ██▒ ▀█▒ ▒█░ █ ▒█  ▒█   ▀  ▒█   ▀  ▒█  ▀█▌
  ▒█▄▄▄█░ ░█▒ █▓░ ▄▄░ ▒█░ █ ▒█  ▒███    ▒███    ░█   █▌
  ▒█▀     ░█░ ▓█   █▓ ░█░ █ ▒█  ▒█   ▄  ▒█   ▄  ░█  ▄█▌
  ▒█      ░█░ ░▓███▀   ▒█▓▀▓█░ ░▓████▒ ░▓████▒ ▒▓████▀

usage: pw my_command [-h] [--device DEVICE]

Do something to a connected device.

optional arguments:
  -h, --help       show this help message and exit
  --device DEVICE  Set which device to target

Defining a simple alias#

For simpler pw subcommands that effectively only need to act as command line aliases, the pw_cli.alias python module can be reused for simplicity.

First, create a python module that defines the alias:

# my_package/aliases.py

from pw_cli.aliases import alias

foo = alias("bar", "baz")

The remaining step needed is to add a new plugin to pigweed.json:

{
  "pw": {
    "pw_cli": {
      "plugins": {
        "foo": {
          "module": "my_package.aliases",
          "function": "foo"
        },
        ...
      }
    }
  }
}

In this case, the command pw foo abc will effectively run bar baz abc.

Branding Pigweed’s tooling#

An important part of starting a new project is picking a name, and in the case of Pigweed, designing a banner for the project. Pigweed supports configuring the banners by setting environment variables:

  • PW_BRANDING_BANNER - Absolute path to a filename containing a banner to display when running the pw commands. See the example below.

  • PW_BRANDING_BANNER_COLOR - Color of the banner. Possible values include: red, bold_red, yellow, bold_yellow, green, bold_green, blue, cyan, magenta, bold_white, black_on_white. See pw_cli.colors for details.

  • PW_ENVSETUP_NO_BANNER - Suppress banner from being printed.

The below example shows how to manually change the branding at the command line. However, these environment variables should be set in the project root’s bootstrap.sh before delegating to Pigweed’s upstream bootstrap.sh.

$ cat foo-banner.txt

 ▒██████  ░▓██▓░  ░▓██▓░
  ▒█░    ▒█   ▒█ ▒█   ▒█
  ▒█▄▄▄▄ ▒█ █ ▒█ ▒█ █ ▒█
  ▒█▀    ▒█   ▒█ ▒█   ▒█
  ▒█      ░▓██▓░  ░▓██▓░

$ export PW_BRANDING_BANNER="$(pwd)/foo-banner.txt"
$ export PW_BRANDING_BANNER_COLOR="bold_red"
$ pw logdemo

 ▒██████  ░▓██▓░  ░▓██▓░
  ▒█░    ▒█   ▒█ ▒█   ▒█
  ▒█▄▄▄▄ ▒█ █ ▒█ ▒█ █ ▒█
  ▒█▀    ▒█   ▒█ ▒█   ▒█
  ▒█      ░▓██▓░  ░▓██▓░

20200610 12:03:44 CRT This is a critical message
20200610 12:03:44 ERR There was an error on our last operation
20200610 12:03:44 WRN Looks like something is amiss; consider investigating
20200610 12:03:44 INF The operation went as expected
20200610 12:03:44 OUT Standard output of subprocess

The branding is not purely visual; it serves to make it clear which project an engineer is working with.

Making the ASCII / ANSI art#

The most direct way to make the ASCII art is to create it with a text editor. However, there are some tools to make the process faster and easier.

  • Patorjk’s ASCII art generator - A great starting place, since you can copy and paste straight from the browser into a file, and then point PW_BRANDING_BANNER at it. Most of the fonts use normal ASCII characters; and fonts with extended ASCII characters use the Unicode versions of them (needed for modern terminals).

There are other options, but these require additional work to put into Pigweed since they only export in the traditional ANS or ICE formats. The old ANS formats do not have a converter (contributions welcome!). Here are some of the options as of mid-2020:

  • Playscii - Actively maintained.

  • Moebius - Actively maintained.

  • SyncDraw - Actively maintained, in 2020, in a CVS repository.

  • PabloDraw - Works on most desktop machines thanks to being written in .NET. Not maintained, but works well. Has an impresive brush system for organic style drawing.

  • TheDraw - One of the most popular ANSI art editors back in the 90s. Requires DOSBox to run on modern machines, but otherwise works. It has some of the most impressive capabilities, including supporting full-color multi-character fonts.

Future branding improvements#

Branding the pw tool is a great start, but more changes are planned:

  • Supporting branding the bootstrap/activate banner, which for technical reasons is not the same code as the banner printing from the Python tooling. These will use the same PW_BRANDING_BANNER and PW_BRANDING_BANNER_COLOR environment variables.

  • Supporting renaming the pw command to something project specific, like foo in this case.

  • Re-coloring the log headers from the pw tool.