pw_cli: Add support for branding

This adds the ability to change the banner (including color) that's
displayed across the "pw" tooling; such as in "pw watch".

Change-Id: I4483e0674020365f5da7974248446e8325ba8389
diff --git a/pw_cli/docs.rst b/pw_cli/docs.rst
index fa5da59..fca0fa0 100644
--- a/pw_cli/docs.rst
+++ b/pw_cli/docs.rst
@@ -173,3 +173,99 @@
   optional arguments:
     -h, --help       show this help message and exit
     --device DEVICE  Set which device to target
+
+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.
+
+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``.
+
+.. code-block:: text
+
+  $ 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 <http://patorjk.com/software/taag/>`_ - 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).
+* `Online ANSII Edit by Andy Herbert
+  <http://andyherbert.github.io/ansiedit/public/index.html>`_ - Browser based
+  editor that can export to mixed UTF-8 and ANSII color. It's also `open source
+  <https://github.com/andyherbert/ansiedit>`_. What's nice about this editor is
+  that you can create a multi-color banner, and save it with the ``File`` -->
+  ``Export as ANSi (UTF-8)`` option, and use it directly as a Pigweed banner.
+  One caveat is that the editor uses UTF-8 box drawing characters, which don't
+  work well with all terminals. However, the box drawing characters look so
+  slick on terminals that support them that we feel this is a worthwhile
+  tradeoff.
+
+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 <http://vectorpoem.com/playscii/>`_ - Actively maintained.
+* `Moebius <https://github.com/blocktronics/moebius>`_ - Actively maintained.
+* `SyncDraw <http://syncdraw.bbsdev.net/>`_ - Actively maintained, in 2020, in
+  a CVS repository.
+* `PabloDraw <http://picoe.ca/products/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 <https://en.wikipedia.org/wiki/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.
diff --git a/pw_cli/py/pw_cli/arguments.py b/pw_cli/py/pw_cli/arguments.py
index baac649..7a0f1b2 100644
--- a/pw_cli/py/pw_cli/arguments.py
+++ b/pw_cli/py/pw_cli/arguments.py
@@ -20,15 +20,7 @@
 from typing import NoReturn
 
 from pw_cli import plugins
-from pw_cli.color import colors
-
-_PIGWEED_BANNER = '''
- ▒█████▄   █▓  ▄███▒  ▒█    ▒█ ░▓████▒ ░▓████▒ ▒▓████▄
-  ▒█░  █░ ░█▒ ██▒ ▀█▒ ▒█░ █ ▒█  ▒█   ▀  ▒█   ▀  ▒█  ▀█▌
-  ▒█▄▄▄█░ ░█▒ █▓░ ▄▄░ ▒█░ █ ▒█  ▒███    ▒███    ░█   █▌
-  ▒█▀     ░█░ ▓█   █▓ ░█░ █ ▒█  ▒█   ▄  ▒█   ▄  ░█  ▄█▌
-  ▒█      ░█░ ░▓███▀   ▒█▓▀▓█░ ░▓████▒ ░▓████▒ ▒▓████▀
-'''
+from pw_cli.branding import banner
 
 _HELP_HEADER = '''The Pigweed command line interface (CLI).
 
@@ -43,8 +35,8 @@
 
 
 def print_banner() -> None:
-    """Prints the colorful PIGWEED banner to stderr."""
-    print(colors().magenta(_PIGWEED_BANNER), file=sys.stderr)
+    """Prints the PIGWEED (or project specific) banner to stderr."""
+    print(banner(), file=sys.stderr)
 
 
 def format_help() -> str:
diff --git a/pw_cli/py/pw_cli/branding.py b/pw_cli/py/pw_cli/branding.py
new file mode 100644
index 0000000..87a424cc
--- /dev/null
+++ b/pw_cli/py/pw_cli/branding.py
@@ -0,0 +1,55 @@
+# Copyright 2020 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+"""Facilities for accessing the current Pigweed branding"""
+
+from typing import Optional
+from pathlib import Path
+
+import pw_cli.env
+import pw_cli.color
+
+_memoized_banner: Optional[str] = None
+
+# This is the default banner for Pigweed.
+_PIGWEED_BANNER = '''
+ ▒█████▄   █▓  ▄███▒  ▒█    ▒█ ░▓████▒ ░▓████▒ ▒▓████▄
+  ▒█░  █░ ░█▒ ██▒ ▀█▒ ▒█░ █ ▒█  ▒█   ▀  ▒█   ▀  ▒█  ▀█▌
+  ▒█▄▄▄█░ ░█▒ █▓░ ▄▄░ ▒█░ █ ▒█  ▒███    ▒███    ░█   █▌
+  ▒█▀     ░█░ ▓█   █▓ ░█░ █ ▒█  ▒█   ▄  ▒█   ▄  ░█  ▄█▌
+  ▒█      ░█░ ░▓███▀   ▒█▓▀▓█░ ░▓████▒ ░▓████▒ ▒▓████▀
+'''
+
+
+def banner():
+    global _memoized_banner  # pylint: disable=global-statement
+    if _memoized_banner is not None:
+        return _memoized_banner
+
+    parsed_env = pw_cli.env.pigweed_environment()
+
+    # Take the banner from the file PW_BRANDING_BANNER; or use the default.
+    banner_filename = parsed_env.PW_BRANDING_BANNER
+    _memoized_banner = (Path(banner_filename).read_text()
+                        if banner_filename else _PIGWEED_BANNER)
+
+    # Color the banner if requested.
+    banner_color = parsed_env.PW_BRANDING_BANNER_COLOR
+    if banner_color != '':
+        _memoized_banner = getattr(
+            pw_cli.color.colors(),
+            banner_color,
+            str,
+        )(_memoized_banner)
+
+    return _memoized_banner
diff --git a/pw_cli/py/pw_cli/color.py b/pw_cli/py/pw_cli/color.py
index 6559def..63c58e2 100644
--- a/pw_cli/py/pw_cli/color.py
+++ b/pw_cli/py/pw_cli/color.py
@@ -30,11 +30,13 @@
     return lambda msg: f'{start}{msg}{reset}'
 
 
-# TODO(keir): Totally replace this object with something more complete like the
-# 'colorful' module.
-class _Color:  # pylint: disable=too-few-public-methods
+# TODO(keir): Replace this with something like the 'colorful' module.
+class _Color:
+    # pylint: disable=too-few-public-methods
+    # pylint: disable=too-many-instance-attributes
     """Helpers to surround text with ASCII color escapes"""
     def __init__(self):
+        self.none = str
         self.red = _make_color(31, 1)
         self.bold_red = _make_color(30, 41)
         self.yellow = _make_color(33, 1)
diff --git a/pw_cli/py/pw_cli/env.py b/pw_cli/py/pw_cli/env.py
index 3611588..41f16f3 100644
--- a/pw_cli/py/pw_cli/env.py
+++ b/pw_cli/py/pw_cli/env.py
@@ -49,6 +49,9 @@
     parser.add_var('PW_VIRTUALENV_SETUP_PY_ROOTS')
     parser.add_var('PW_CARGO_PACKAGE_FILES')
 
+    parser.add_var('PW_BRANDING_BANNER')
+    parser.add_var('PW_BRANDING_BANNER_COLOR', default='magenta')
+
     return parser
 
 
diff --git a/pw_watch/py/pw_watch/watch.py b/pw_watch/py/pw_watch/watch.py
index e1f8485..5674b0e 100755
--- a/pw_watch/py/pw_watch/watch.py
+++ b/pw_watch/py/pw_watch/watch.py
@@ -30,6 +30,7 @@
 from watchdog.utils import has_attribute
 from watchdog.utils import unicode_paths
 
+import pw_cli.branding
 import pw_cli.color
 import pw_cli.env
 import pw_cli.plugins
@@ -40,14 +41,6 @@
 _LOG = logging.getLogger(__name__)
 _ERRNO_INOTIFY_LIMIT_REACHED = 28
 
-_BUILD_MESSAGE = """
- ▒█████▄   █▓  ▄███▒  ▒█    ▒█ ░▓████▒ ░▓████▒ ▒▓████▄
-  ▒█░  █░ ░█▒ ██▒ ▀█▒ ▒█░ █ ▒█  ▒█   ▀  ▒█   ▀  ▒█  ▀█▌
-  ▒█▄▄▄█░ ░█▒ █▓░ ▄▄░ ▒█░ █ ▒█  ▒███    ▒███    ░█   █▌
-  ▒█▀     ░█░ ▓█   █▓ ░█░ █ ▒█  ▒█   ▄  ▒█   ▄  ░█  ▄█▌
-  ▒█      ░█░ ░▓███▀   ▒█▓▀▓█░ ░▓████▒ ░▓████▒ ▒▓████▀
-"""
-
 _PASS_MESSAGE = """
   ██████╗  █████╗ ███████╗███████╗██╗
   ██╔══██╗██╔══██╗██╔════╝██╔════╝██║
@@ -213,7 +206,7 @@
 
         # Clear the screen and show a banner indicating the build is starting.
         print('\033c', end='')  # TODO(pwbug/38): Not Windows compatible.
-        print(_COLOR.magenta(_BUILD_MESSAGE))
+        print(pw_cli.branding.banner())
         print(
             _COLOR.green(
                 '  Watching for changes. Ctrl-C to exit; enter to rebuild'))