• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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