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