pw_cli#
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
#
Pigweed AI summary: The article explains how to invoke "pw" subcommands by providing the command name. Arguments before the command are interpreted by "pw" itself, while arguments after the command are interpreted by the command. The article provides examples of how to invoke "pw" commands, including running the doctor command, running format with debug-level logs, and displaying help for the pw and watch commands.
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#
Pigweed AI summary: This document explains how to register Python scripts as "pw" commands in Pigweed projects. The registration process involves providing the command name, module, and function in the "pigweed.json" file. The file format for plugin entries is also described. Any function without required arguments can be used as a plugin function, and the function should return an int, which the "pw" tool uses as the exit code. The document provides an example of a function registered as a "pw" plugin and how
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 AI summary: The pigweed.json file format contains plugin entries in a specific format, with a "pw" section and a "plugins" section that includes the name of the plugin, the module containing the plugin, and the entry point for the plugin. An example is given with three commands registered under the "pw_cli" plugin.
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#
Pigweed AI summary: This document explains how to define a plugin function for the Pigweed command line interface (CLI). Any function without required arguments can be used as a plugin function, and it should return an integer that the pw tool uses as the exit code. The function's docstring is used as the help string for the command. The pw command parses its arguments with the argparse module, and sets sys.argv so that plugins can behave the same whether they are executed independently or through pw. An example function is provided,
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#
Pigweed AI summary: This example demonstrates how to register a function as a plugin in the Pigweed command line interface (CLI). The function is registered in a PW_PLUGINS file and can be accessed through the "pw" command. The example includes a Python script with a function that interacts with a connected device and a command-line argument parser. The plugin is listed in the "pw" help menu and can be executed with additional arguments. The example also includes a sample output of the "pw" command and the plugin's
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
Branding Pigweed’s tooling#
Pigweed AI summary: This article discusses how to brand Pigweed's tooling by setting environment variables for the banner and banner color. It provides an example of how to manually change the branding at the command line and explains the importance of branding for identifying which project an engineer is working with. The article also suggests tools for creating ASCII/ANSI art and lists future branding improvements, such as supporting renaming the <literal>pw</literal> command to something project-specific.
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 thepw
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
. Seepw_cli.colors
for details.
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#
Pigweed AI summary: This section discusses various tools and methods for creating ASCII and ANSI art. The most direct way is to use a text editor, but there are also tools like Patorjk's ASCII art generator that can make the process faster and easier. Other options include Playscii, Moebius, SyncDraw, PabloDraw, and TheDraw, but some require additional work to put into Pigweed. TheDraw is one of the most popular ANSI art editors from the 90s and has impressive capabilities,
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#
Pigweed AI summary: The article discusses future branding improvements for the "pw" tool, including support for branding the "bootstrap/activate" banner, renaming the "pw" command to something project-specific, and re-coloring the log headers. These changes will use the same environment variables for consistency.
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 samePW_BRANDING_BANNER
andPW_BRANDING_BANNER_COLOR
environment variables.Supporting renaming the
pw
command to something project specific, likefoo
in this case.Re-coloring the log headers from the
pw
tool.
pw_cli Python package#
The pw_cli
Pigweed module includes the pw_cli
Python package, which
provides utilities for creating command line tools with Pigweed.
pw_cli.log#
Tools for configuring Python logging.
- pw_cli.log.all_loggers() Iterator[Logger] #
Iterates over all loggers known to Python logging.
- pw_cli.log.c_to_py_log_level(c_level: int) int #
Converts pw_log C log-level macros to Python logging levels.
- pw_cli.log.install(level: Union[str, int] = 20, use_color: Optional[bool] = None, hide_timestamp: bool = False, log_file: Optional[Union[str, Path]] = None, logger: Optional[Logger] = None, debug_log: Optional[Union[str, Path]] = None, time_format: str = '%Y%m%d %H:%M:%S', msec_format: str = '%s,%03d', include_msec: bool = False, message_format: str = '%(levelname)s %(message)s') None #
Configures the system logger for the default pw command log format.
If you have Python loggers separate from the root logger you can use pw_cli.log.install to get the Pigweed log formatting there too. For example:
import logging import pw_cli.log pw_cli.log.install( level=logging.INFO, use_color=True, hide_timestamp=False, log_file=(Path.home() / 'logs.txt'), logger=logging.getLogger(__package__), )
- Parameters:
level – The logging level to apply. Default: logging.INFO.
use_color – When True include ANSI escape sequences to colorize log messages.
hide_timestamp – When True omit timestamps from the log formatting.
log_file – File to send logs into instead of the terminal.
logger – Python Logger instance to install Pigweed formatting into. Defaults to the Python root logger: logging.getLogger().
debug_log – File to log to from all levels, regardless of chosen log level. Logs will go here in addition to the terminal.
time_format – Default time format string.
msec_format – Default millisecond format string. This should be a format string that accepts a both a string
%s
and an integer%d
. The default Python format for this string is%s,%03d
.include_msec – Whether or not to include the millisecond part of log timestamps.
message_format – The message format string. By default this includes levelname and message. The asctime field is prepended to this unless hide_timestamp=True.
- pw_cli.log.main() None #
Shows how logs look at various levels.
- pw_cli.log.set_all_loggers_minimum_level(level: int) None #
Increases the log level to the specified value for all known loggers.
pw_cli.plugins#
pw_cli.plugins
provides general purpose plugin functionality. The
module can be used to create plugins for command line tools, interactive
consoles, or anything else. Pigweed’s pw
command uses this module for its
plugins.
To use plugins, create a pw_cli.plugins.Registry
. The registry may
have an optional validator function that checks plugins before they are
registered (see pw_cli.plugins.Registry.__init__()
).
Plugins may be registered in a few different ways.
Direct function call. Register plugins by calling
pw_cli.plugins.Registry.register()
orpw_cli.plugins.Registry.register_by_name()
.registry = pw_cli.plugins.Registry() registry.register('plugin_name', my_plugin) registry.register_by_name('plugin_name', 'module_name', 'function_name')Decorator. Register using the
pw_cli.plugins.Registry.plugin()
decorator._REGISTRY = pw_cli.plugins.Registry() # This function is registered as the "my_plugin" plugin. @_REGISTRY.plugin def my_plugin(): pass # This function is registered as the "input" plugin. @_REGISTRY.plugin(name='input') def read_something(): passThe decorator may be aliased to give a cleaner syntax (e.g.
register = my_registry.plugin
).Plugins files. Plugins files use a simple format:
# Comments start with "#". Blank lines are ignored. name_of_the_plugin module.name module_member another_plugin some_module some_functionThese files are placed in the file system and apply similarly to Git’s
.gitignore
files. From Python, these files are registered usingpw_cli.plugins.Registry.register_file()
andpw_cli.plugins.Registry.register_directory()
.
pw_cli.plugins module reference#
Provides general purpose plugin functionality.
As used in this module, a plugin is a Python object associated with a name. Plugins are registered in a Registry. The plugin object is typically a function, but can be anything.
Plugins may be loaded in a variety of ways:
Listed in a plugins file in the file system (e.g. as “name module target”).
Registered in a Python file using a decorator (@my_registry.plugin).
Registered directly or by name with function calls on a registry object.
This functionality can be used to create plugins for command line tools, interactive consoles, or anything else. Pigweed’s pw command uses this module for its plugins.
- exception pw_cli.plugins.Error#
Indicates that a plugin is invalid or cannot be registered.
- class pw_cli.plugins.Plugin(name: str, target: Any, source: Optional[Path] = None)#
Represents a Python entity registered as a plugin.
Each plugin resolves to a Python object, typically a function.
- __init__(name: str, target: Any, source: Optional[Path] = None) None #
Creates a plugin for the provided target.
- classmethod from_name(name: str, module_name: str, member_name: str, source: Optional[Path]) Plugin #
Creates a plugin by module and attribute name.
- Parameters:
name – the name of the plugin
module_name – Python module name (e.g. ‘foo_pkg.bar’)
member_name – the name of the member in the module
source – path to the plugins file that declared this plugin, if any
- help(full: bool = False) str #
Returns a description of this plugin from its docstring.
- run_with_argv(argv: Iterable[str]) int #
Sets sys.argv and calls the plugin function.
This is used to call a plugin as if from the command line.
- class pw_cli.plugins.Registry(validator: ~typing.Callable[[~pw_cli.plugins.Plugin], ~typing.Any] = <function Registry.<lambda>>)#
Manages a set of plugins from Python modules or plugins files.
- __init__(validator: ~typing.Callable[[~pw_cli.plugins.Plugin], ~typing.Any] = <function Registry.<lambda>>) None #
Creates a new, empty plugins registry.
- Parameters:
validator – Function that checks whether a plugin is valid and should be registered. Must raise plugins.Error is the plugin is invalid.
- detailed_help(plugins: Iterable[str] = ()) Iterator[str] #
Yields lines of detailed information about commands.
- plugin(function: Optional[Callable] = None, *, name: Optional[str] = None) Callable[[Callable], Callable] #
Decorator that registers a function with this plugin registry.
- register_by_name(name: str, module_name: str, member_name: str, source: Optional[Path] = None) Optional[Plugin] #
Registers an object from its module and name as a plugin.
- register_config(config: Dict, path: Optional[Path] = None) None #
Registers plugins from a Pigweed config.
Any exceptions raised from parsing the file are caught and logged.
- register_directory(directory: Path, file_name: str, restrict_to: Optional[Path] = None) None #
Finds and registers plugins from plugins files in a directory.
- Parameters:
directory – The directory from which to start searching up.
file_name – The name of plugins files to look for.
restrict_to – If provided, do not search higher than this directory.
- register_file(path: Path) None #
Registers plugins from a plugins file.
Any exceptions raised from parsing the file are caught and logged.
- run_with_argv(name: str, argv: Iterable[str]) int #
Runs a plugin by name, setting sys.argv to the provided args.
This is used to run a command as if it were executed directly from the command line. The plugin is expected to return an int.
- Raises:
KeyError if plugin is not registered. –
- short_help() str #
Returns a help string for the registered plugins.
- pw_cli.plugins.callable_with_no_args(plugin: Plugin) None #
Checks that a plugin is callable without arguments.
May be used for the validator argument to Registry.
- pw_cli.plugins.find_all_in_parents(name: str, path: Path) Iterator[Path] #
Searches all parent directories of the path for files or directories.
- pw_cli.plugins.find_in_parents(name: str, path: Path) Optional[Path] #
Searches parent directories of the path for a file or directory.
- pw_cli.plugins.import_submodules(module: module, recursive: bool = False) None #
Imports the submodules of a package.
This can be used to collect plugins registered with a decorator from a directory.