1.. _module-pw_cli: 2 3------ 4pw_cli 5------ 6This directory contains the ``pw`` command line interface (CLI) that facilitates 7working with Pigweed. The CLI module adds several subcommands prefixed with 8``pw``, and provides a mechanism for other Pigweed modules to behave as 9"plugins" and register themselves as ``pw`` commands as well. After activating 10the Pigweed environment, these commands will be available for use. 11 12``pw`` includes the following commands by default: 13 14.. code-block:: text 15 16 doctor Check that the environment is set up correctly for Pigweed. 17 format Check and fix formatting for source files. 18 help Display detailed information about pw commands. 19 ide Configure editors and IDEs to work best with Pigweed. 20 logdemo Show how logs look at various levels. 21 module Utilities for managing modules. 22 test Run Pigweed unit tests built using GN. 23 watch Watch files for changes and rebuild. 24 25To see an up-to-date list of ``pw`` subcommands, run ``pw --help``. 26 27Invoking ``pw`` 28================ 29``pw`` subcommands are invoked by providing the command name. Arguments prior to 30the command are interpreted by ``pw`` itself; all arguments after the command 31name are interpreted by the command. 32 33Here are some example invocations of ``pw``: 34 35.. code-block:: text 36 37 # Run the doctor command 38 $ pw doctor 39 40 # Run format --fix with debug-level logs 41 $ pw --loglevel debug format --fix 42 43 # Display help for the pw command 44 $ pw -h watch 45 46 # Display help for the watch command 47 $ pw watch -h 48 49Registering ``pw`` plugins 50========================== 51Projects can register their own Python scripts as ``pw`` commands. ``pw`` 52plugins are registered by providing the command name, module, and function in a 53``PW_PLUGINS`` file. ``PW_PLUGINS`` files can add new commands or override 54built-in commands. Since they are accessed by module name, plugins must be 55defined in Python packages that are installed in the Pigweed virtual 56environment. 57 58Plugin registrations in a ``PW_PLUGINS`` file apply to the their directory and 59all subdirectories, similarly to configuration files like ``.clang-format``. 60Registered plugins appear as commands in the ``pw`` tool when ``pw`` is run from 61those directories. 62 63Projects that wish to register commands might place a ``PW_PLUGINS`` file in the 64root of their repo. Multiple ``PW_PLUGINS`` files may be applied, but the ``pw`` 65tool gives precedence to a ``PW_PLUGINS`` file in the current working directory 66or the nearest parent directory. 67 68PW_PLUGINS file format 69---------------------- 70``PW_PLUGINS`` contains one plugin entry per line in the following format: 71 72.. code-block:: python 73 74 # Lines that start with a # are ignored. 75 <command name> <Python module> <function> 76 77The following example registers three commands: 78 79.. code-block:: python 80 81 # Register the presubmit script as pw presubmit 82 presubmit my_cool_project.tools run_presubmit 83 84 # Override the pw test command with a custom version 85 test my_cool_project.testing run_test 86 87 # Add a custom command 88 flash my_cool_project.flash main 89 90Defining a plugin function 91-------------------------- 92Any function without required arguments may be used as a plugin function. The 93function should return an int, which the ``pw`` uses as the exit code. The 94``pw`` tool uses the function docstring as the help string for the command. 95 96Typically, ``pw`` commands parse their arguments with the ``argparse`` module. 97``pw`` sets ``sys.argv`` so it contains only the arguments for the plugin, 98so plugins can behave the same whether they are executed independently or 99through ``pw``. 100 101Example 102^^^^^^^ 103This example shows a function that is registered as a ``pw`` plugin. 104 105.. code-block:: python 106 107 # my_package/my_module.py 108 109 def _do_something(device): 110 ... 111 112 def main() -> int: 113 """Do something to a connected device.""" 114 115 parser = argparse.ArgumentParser(description=__doc__) 116 parser.add_argument('--device', help='Set which device to target') 117 return _do_something(**vars(parser.parse_args())) 118 119 120 if __name__ == '__main__': 121 logging.basicConfig(format='%(message)s', level=logging.INFO) 122 sys.exit(main()) 123 124This plugin is registered in a ``PW_PLUGINS`` file in the current working 125directory or a parent of it. 126 127.. code-block:: python 128 129 # Register my_commmand 130 my_command my_package.my_module main 131 132The function is now available through the ``pw`` command, and will be listed in 133``pw``'s help. Arguments after the command name are passed to the plugin. 134 135.. code-block:: text 136 137 $ pw 138 139 ▒█████▄ █▓ ▄███▒ ▒█ ▒█ ░▓████▒ ░▓████▒ ▒▓████▄ 140 ▒█░ █░ ░█▒ ██▒ ▀█▒ ▒█░ █ ▒█ ▒█ ▀ ▒█ ▀ ▒█ ▀█▌ 141 ▒█▄▄▄█░ ░█▒ █▓░ ▄▄░ ▒█░ █ ▒█ ▒███ ▒███ ░█ █▌ 142 ▒█▀ ░█░ ▓█ █▓ ░█░ █ ▒█ ▒█ ▄ ▒█ ▄ ░█ ▄█▌ 143 ▒█ ░█░ ░▓███▀ ▒█▓▀▓█░ ░▓████▒ ░▓████▒ ▒▓████▀ 144 145 usage: pw [-h] [-C DIRECTORY] [-l LOGLEVEL] [--no-banner] [command] ... 146 147 The Pigweed command line interface (CLI). 148 149 ... 150 151 supported commands: 152 doctor Check that the environment is set up correctly for Pigweed. 153 format Check and fix formatting for source files. 154 help Display detailed information about pw commands. 155 ... 156 my_command Do something to a connected device. 157 158 $ pw my_command -h 159 160 ▒█████▄ █▓ ▄███▒ ▒█ ▒█ ░▓████▒ ░▓████▒ ▒▓████▄ 161 ▒█░ █░ ░█▒ ██▒ ▀█▒ ▒█░ █ ▒█ ▒█ ▀ ▒█ ▀ ▒█ ▀█▌ 162 ▒█▄▄▄█░ ░█▒ █▓░ ▄▄░ ▒█░ █ ▒█ ▒███ ▒███ ░█ █▌ 163 ▒█▀ ░█░ ▓█ █▓ ░█░ █ ▒█ ▒█ ▄ ▒█ ▄ ░█ ▄█▌ 164 ▒█ ░█░ ░▓███▀ ▒█▓▀▓█░ ░▓████▒ ░▓████▒ ▒▓████▀ 165 166 usage: pw my_command [-h] [--device DEVICE] 167 168 Do something to a connected device. 169 170 optional arguments: 171 -h, --help show this help message and exit 172 --device DEVICE Set which device to target 173 174Branding Pigweed's tooling 175========================== 176An important part of starting a new project is picking a name, and in the case 177of Pigweed, designing a banner for the project. Pigweed supports configuring 178the banners by setting environment variables: 179 180* ``PW_BRANDING_BANNER`` - Absolute path to a filename containing a banner to 181 display when running the ``pw`` commands. See the example below. 182* ``PW_BRANDING_BANNER_COLOR`` - Color of the banner. Possible values include: 183 ``red``, ``bold_red``, ``yellow``, ``bold_yellow``, ``green``, 184 ``bold_green``, ``blue``, ``cyan``, ``magenta``, ``bold_white``, 185 ``black_on_white``. See ``pw_cli.colors`` for details. 186 187The below example shows how to manually change the branding at the command 188line. However, these environment variables should be set in the project root's 189``bootstrap.sh`` before delegating to Pigweed's upstream ``bootstrap.sh``. 190 191.. code-block:: text 192 193 $ cat foo-banner.txt 194 195 ▒██████ ░▓██▓░ ░▓██▓░ 196 ▒█░ ▒█ ▒█ ▒█ ▒█ 197 ▒█▄▄▄▄ ▒█ █ ▒█ ▒█ █ ▒█ 198 ▒█▀ ▒█ ▒█ ▒█ ▒█ 199 ▒█ ░▓██▓░ ░▓██▓░ 200 201 $ export PW_BRANDING_BANNER="$(pwd)/foo-banner.txt" 202 $ export PW_BRANDING_BANNER_COLOR="bold_red" 203 $ pw logdemo 204 205 ▒██████ ░▓██▓░ ░▓██▓░ 206 ▒█░ ▒█ ▒█ ▒█ ▒█ 207 ▒█▄▄▄▄ ▒█ █ ▒█ ▒█ █ ▒█ 208 ▒█▀ ▒█ ▒█ ▒█ ▒█ 209 ▒█ ░▓██▓░ ░▓██▓░ 210 211 20200610 12:03:44 CRT This is a critical message 212 20200610 12:03:44 ERR There was an error on our last operation 213 20200610 12:03:44 WRN Looks like something is amiss; consider investigating 214 20200610 12:03:44 INF The operation went as expected 215 20200610 12:03:44 OUT Standard output of subprocess 216 217The branding is not purely visual; it serves to make it clear which project an 218engineer is working with. 219 220Making the ASCII / ANSI art 221--------------------------- 222The most direct way to make the ASCII art is to create it with a text editor. 223However, there are some tools to make the process faster and easier. 224 225* `Patorjk's ASCII art generator <http://patorjk.com/software/taag/>`_ - A 226 great starting place, since you can copy and paste straight from the browser 227 into a file, and then point ``PW_BRANDING_BANNER`` at it. Most of the fonts 228 use normal ASCII characters; and fonts with extended ASCII characters use the 229 Unicode versions of them (needed for modern terminals). 230 231There are other options, but these require additional work to put into Pigweed 232since they only export in the traditional ANS or ICE formats. The old ANS 233formats do not have a converter (contributions welcome!). Here are some of the 234options as of mid-2020: 235 236* `Playscii <http://vectorpoem.com/playscii/>`_ - Actively maintained. 237* `Moebius <https://github.com/blocktronics/moebius>`_ - Actively maintained. 238* `SyncDraw <http://syncdraw.bbsdev.net/>`_ - Actively maintained, in 2020, in 239 a CVS repository. 240* `PabloDraw <http://picoe.ca/products/pablodraw/>`_ - Works on most desktop 241 machines thanks to being written in .NET. Not maintained, but works well. Has 242 an impresive brush system for organic style drawing. 243* `TheDraw <https://en.wikipedia.org/wiki/TheDraw>`_ - One of the most popular 244 ANSI art editors back in the 90s. Requires DOSBox to run on modern machines, 245 but otherwise works. It has some of the most impressive capabilities, 246 including supporting full-color multi-character fonts. 247 248Future branding improvements 249---------------------------- 250Branding the ``pw`` tool is a great start, but more changes are planned: 251 252- Supporting branding the ``bootstrap/activate`` banner, which for technical 253 reasons is not the same code as the banner printing from the Python tooling. 254 These will use the same ``PW_BRANDING_BANNER`` and 255 ``PW_BRANDING_BANNER_COLOR`` environment variables. 256- Supporting renaming the ``pw`` command to something project specific, like 257 ``foo`` in this case. 258- Re-coloring the log headers from the ``pw`` tool. 259 260pw_cli Python package 261===================== 262The ``pw_cli`` Pigweed module includes the ``pw_cli`` Python package, which 263provides utilities for creating command line tools with Pigweed. 264 265pw_cli.log 266---------- 267.. automodule:: pw_cli.log 268 :members: 269 270pw_cli.plugins 271-------------- 272:py:mod:`pw_cli.plugins` provides general purpose plugin functionality. The 273module can be used to create plugins for command line tools, interactive 274consoles, or anything else. Pigweed's ``pw`` command uses this module for its 275plugins. 276 277To use plugins, create a :py:class:`pw_cli.plugins.Registry`. The registry may 278have an optional validator function that checks plugins before they are 279registered (see :py:meth:`pw_cli.plugins.Registry.__init__`). 280 281Plugins may be registered in a few different ways. 282 283 * **Direct function call.** Register plugins by calling 284 :py:meth:`pw_cli.plugins.Registry.register` or 285 :py:meth:`pw_cli.plugins.Registry.register_by_name`. 286 287 .. code-block:: python 288 289 registry = pw_cli.plugins.Registry() 290 291 registry.register('plugin_name', my_plugin) 292 registry.register_by_name('plugin_name', 'module_name', 'function_name') 293 294 * **Decorator.** Register using the :py:meth:`pw_cli.plugins.Registry.plugin` 295 decorator. 296 297 .. code-block:: python 298 299 _REGISTRY = pw_cli.plugins.Registry() 300 301 # This function is registered as the "my_plugin" plugin. 302 @_REGISTRY.plugin 303 def my_plugin(): 304 pass 305 306 # This function is registered as the "input" plugin. 307 @_REGISTRY.plugin(name='input') 308 def read_something(): 309 pass 310 311 The decorator may be aliased to give a cleaner syntax (e.g. ``register = 312 my_registry.plugin``). 313 314 * **Plugins files.** Plugins files use a simple format: 315 316 .. code-block:: 317 318 # Comments start with "#". Blank lines are ignored. 319 name_of_the_plugin module.name module_member 320 321 another_plugin some_module some_function 322 323 These files are placed in the file system and apply similarly to Git's 324 ``.gitignore`` files. From Python, these files are registered using 325 :py:meth:`pw_cli.plugins.Registry.register_file` and 326 :py:meth:`pw_cli.plugins.Registry.register_directory`. 327 328pw_cli.plugins module reference 329^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 330.. automodule:: pw_cli.plugins 331 :members: 332