pw_env_setup: Initial pass at prettier Windows UI

This change improves the Windows env_setup UI to mirror that of POSIX.
Not everything matches yet, and there is still no distinction between
bootstrap and activate.

Change-Id: I8680400340c7af03dbce234a248a65839287e6eb
diff --git a/pw_env_setup/env_setup.bat b/pw_env_setup/env_setup.bat
index 6da9997..7a81b72 100644
--- a/pw_env_setup/env_setup.bat
+++ b/pw_env_setup/env_setup.bat
@@ -30,10 +30,35 @@
   set PW_CHECKOUT_ROOT=
 )
 
+:: Allow forcing a specifc Python version through the environment variable
+:: PW_BOOTSTRAP_PYTHON. Otherwise, use the system Python if one exists.
+if not "%PW_BOOTSTRAP_PYTHON%" == "" (
+  set python="%PW_BOOTSTRAP_PYTHON%"
+) else (
+  where python >NUL 2>&1
+  if %ERRORLEVEL% EQU 0 (
+    set python=python
+  ) else (
+    echo.
+    echo Error: no system Python present
+    echo.
+    echo   Pigweed's bootstrap process requires a local system Python.
+    echo   Please install Python on your system, add it to your PATH
+    echo   and re-try running bootstrap.
+    goto finish
+  )
+)
+
+call %python% %PW_ROOT%\pw_env_setup\py\pw_env_setup\windows_env_start.py
+
 set shell_file="%PW_ROOT%\pw_env_setup\.env_setup.bat"
 
 if not exist %shell_file% (
-  call python %PW_ROOT%\pw_env_setup\py\pw_env_setup\env_setup.py --pw-root %PW_ROOT% --shell-file %shell_file%
+  call %python% %PW_ROOT%\pw_env_setup\py\pw_env_setup\env_setup.py^
+    --pw-root %PW_ROOT%^
+    --shell-file %shell_file%
 )
 
 call %shell_file%
+
+:finish
diff --git a/pw_env_setup/py/pw_env_setup/colors.py b/pw_env_setup/py/pw_env_setup/colors.py
new file mode 100644
index 0000000..63cb834
--- /dev/null
+++ b/pw_env_setup/py/pw_env_setup/colors.py
@@ -0,0 +1,39 @@
+# 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.
+"""Defines ANSI color codes."""
+
+import ctypes
+import os
+
+
+def _make_color(*codes):
+    # Apply all the requested ANSI color codes. Note that this is unbalanced
+    # with respect to the reset, which only requires a '0' to erase all codes.
+    start = ''.join('\033[{}m'.format(code) for code in codes)
+    reset = '\033[0m'
+
+    return staticmethod(lambda msg: u'{}{}{}'.format(start, msg, reset))
+
+
+class Color:  # pylint: disable=too-few-public-methods
+    """Helpers to surround text with ASCII color escapes"""
+    bold = _make_color(1)
+    green = _make_color(32)
+    magenta = _make_color(35, 1)
+
+
+def enable_colors():
+    if os.name == 'nt':
+        kernel32 = ctypes.windll.kernel32
+        kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
diff --git a/pw_env_setup/py/pw_env_setup/env_setup.py b/pw_env_setup/py/pw_env_setup/env_setup.py
index 7afdaa4..b425b08 100755
--- a/pw_env_setup/py/pw_env_setup/env_setup.py
+++ b/pw_env_setup/py/pw_env_setup/env_setup.py
@@ -69,25 +69,12 @@
 # pylint: disable=wrong-import-position
 from pw_env_setup.cipd_setup import update as cipd_update
 from pw_env_setup.cipd_setup import wrapper as cipd_wrapper
+from pw_env_setup.colors import Color, enable_colors
 from pw_env_setup import cargo_setup
 from pw_env_setup import environment
 from pw_env_setup import virtualenv_setup
 
 
-def _make_color(*codes):
-    # Apply all the requested ANSI color codes. Note that this is unbalanced
-    # with respect to the reset, which only requires a '0' to erase all codes.
-    start = ''.join('\033[{}m'.format(code) for code in codes)
-    reset = '\033[0m'
-
-    return staticmethod(lambda msg: '{}{}{}'.format(start, msg, reset))
-
-
-class _Color:  # pylint: disable=too-few-public-methods
-    """Helpers to surround text with ASCII color escapes"""
-    bold = _make_color(1)
-
-
 class _Result:
     class Status:  # pylint: disable=too-few-public-methods
         DONE = 'done'
@@ -140,6 +127,8 @@
     def setup(self):
         """Runs each of the env_setup steps."""
 
+        enable_colors()
+
         steps = [
             ('CIPD package manager', self.cipd),
             ('Python environment', self.virtualenv),
@@ -156,13 +145,13 @@
         #   steps.append(("Rust's cargo", self.cargo))
 
         self._log(
-            _Color.bold('Downloading and installing packages into local '
-                        'source directory:\n'))
+            Color.bold('Downloading and installing packages into local '
+                       'source directory:\n'))
 
         max_name_len = max(len(name) for name, _ in steps)
 
         self._env.echo(
-            _Color.bold(
+            Color.bold(
                 'Activating environment (setting environment variables):'))
         self._env.echo('')
 
@@ -271,7 +260,7 @@
 
         if not self._quiet:
             fd.write('echo "{}"\n'.format(
-                _Color.bold('Sanity checking the environment:')))
+                Color.bold('Sanity checking the environment:')))
             fd.write('{}\n'.format(echo_empty))
 
         log_level = 'warn' if 'PW_ENVSETUP_QUIET' in os.environ else 'info'
@@ -280,14 +269,14 @@
 
         if self._is_windows:
             fd.write('{}\n'.format(doctor))
-            fd.write('if %ERRORLEVEL% == 0 (\n')
+            fd.write('if %ERRORLEVEL% EQU 0 (\n')
         else:
             fd.write('if {}; then\n'.format(doctor))
 
         if not self._quiet:
             fd.write('  {}\n'.format(echo_empty))
             fd.write('  echo "{}"\n'.format(
-                _Color.bold('Environment looks good; you are ready to go!')))
+                Color.bold('Environment looks good; you are ready to go!')))
 
         if self._is_windows:
             fd.write(')\n')
diff --git a/pw_env_setup/py/pw_env_setup/environment.py b/pw_env_setup/py/pw_env_setup/environment.py
index fe14fc9..9154eef 100644
--- a/pw_env_setup/py/pw_env_setup/environment.py
+++ b/pw_env_setup/py/pw_env_setup/environment.py
@@ -54,10 +54,16 @@
         self._check()
 
     def _check(self):
-        if not isinstance(self.name, str):
+        try:
+            # In python2, unicode is a distinct type.
+            valid_types = (str, unicode)  # pylint: disable=undefined-variable
+        except NameError:
+            valid_types = (str, )
+
+        if not isinstance(self.name, valid_types):
             raise BadNameType('variable name {!r} not of type str'.format(
                 self.name))
-        if not isinstance(self.value, str):
+        if not isinstance(self.value, valid_types):
             raise BadValueType('{!r} value {!r} not of type str'.format(
                 self.name, self.value))
 
diff --git a/pw_env_setup/py/pw_env_setup/windows_env_start.py b/pw_env_setup/py/pw_env_setup/windows_env_start.py
new file mode 100644
index 0000000..dc81260
--- /dev/null
+++ b/pw_env_setup/py/pw_env_setup/windows_env_start.py
@@ -0,0 +1,67 @@
+# -*- coding: utf-8 -*-
+
+# 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.
+"""Prints the env_setup banner for cmd.exe.
+
+This is done from Python as activating colors and printing ASCII art are not
+easy to do in cmd.exe. Activated colors also don't persist in the parent
+process.
+"""
+
+from __future__ import print_function
+
+import os
+import sys
+
+from colors import Color, enable_colors
+
+_PIGWEED_BANNER = u'''
+ ▒█████▄   █▓  ▄███▒  ▒█    ▒█ ░▓████▒ ░▓████▒ ▒▓████▄
+  ▒█░  █░ ░█▒ ██▒ ▀█▒ ▒█░ █ ▒█  ▒█   ▀  ▒█   ▀  ▒█  ▀█▌
+  ▒█▄▄▄█░ ░█▒ █▓░ ▄▄░ ▒█░ █ ▒█  ▒███    ▒███    ░█   █▌
+  ▒█▀     ░█░ ▓█   █▓ ░█░ █ ▒█  ▒█   ▄  ▒█   ▄  ░█  ▄█▌
+  ▒█      ░█░ ░▓███▀   ▒█▓▀▓█░ ░▓████▒ ░▓████▒ ▒▓████▀
+'''
+
+
+def main():
+    if os.name != 'nt':
+        return 1
+
+    enable_colors()
+
+    print(Color.green('\n  WELCOME TO...'))
+    print(Color.magenta(_PIGWEED_BANNER))
+
+    bootstrap = True
+
+    if bootstrap:
+        print(
+            Color.green('\n  BOOTSTRAP! Bootstrap may take a few minutes; '
+                        'please be patient'))
+        print(
+            Color.green(
+                '  On Windows, this stage is extremely slow (~10 minutes).\n'))
+    else:
+        print(
+            Color.green(
+                '\n  ACTIVATOR! This sets your console environment variables.')
+        )
+
+    return 0
+
+
+if __name__ == '__main__':
+    sys.exit(main())
diff --git a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
index 710643d..8330cde 100755
--- a/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
+++ b/pw_presubmit/py/pw_presubmit/pigweed_presubmit.py
@@ -313,7 +313,7 @@
             # Skip shebang and blank lines
             line = file.readline()
             while line and (line.startswith(
-                ('#!', '/*', '@echo off')) or not line.strip()):
+                ('#!', '/*', '@echo off', '# -*-')) or not line.strip()):
                 line = file.readline()
 
             first_line = COPYRIGHT_FIRST_LINE.match(line)