pw_cmd: Update code from incorrect rebase

Change-Id: Ie9f002728a87b2e11e59859e71477e2dc3d1f209
diff --git a/pw_cmd/pw b/pw_cmd/pw
index bbcc117..2312598 100644
--- a/pw_cmd/pw
+++ b/pw_cmd/pw
@@ -1,12 +1,12 @@
 #!/usr/bin/env python3
-# Pip requirements: watchdog, coloredlogs
 
-import sys
+import argparse
+import enum
 import logging
 import pathlib
 import subprocess
+import sys
 import time
-import enum
 
 import coloredlogs
 
@@ -26,29 +26,34 @@
   ╚═╝     ╚═╝  ╚═╝╚══════╝╚══════╝╚═╝
 """
 
+# Pick a visually-distinct font from "PASS" to ensure that readers can't
+# possibly mistake the difference between the two states.
 FAIL_MESSAGE = """
-  ██████▒░▄▄▄       ██▓  ░██▓                  
-▓██     ░▒████▄    ▓██▒  ░▓██▒                  
-▒█████   ░▒█▀  ▀█▄  ▒██▒ ▒██░                  
-░▓█▒    ░░██▄▄▄▄██ ░██░  ▒██░                  
-░▒█░      ▓█   ▓██▒░██░░ ████████▒              
- ▒ ░      ▒▒   ▓▒█░░▓  ░  ▒░▓  ░              
- ░         ▒   ▒▒ ░ ▒ ░░  ░ ▒  ░              
- ░ ░       ░   ▒    ▒ ░   ░ ░                 
+  ██████▒░▄▄▄       ██▓  ░██▓
+▓██     ░▒████▄    ▓██▒  ░▓██▒
+▒█████   ░▒█▀  ▀█▄  ▒██▒ ▒██░
+░▓█▒    ░░██▄▄▄▄██ ░██░  ▒██░
+░▒█░      ▓█   ▓██▒░██░░ ████████▒
+ ▒ ░      ▒▒   ▓▒█░░▓  ░  ▒░▓  ░
+ ░         ▒   ▒▒ ░ ▒ ░░  ░ ▒  ░
+ ░ ░       ░   ▒    ▒ ░   ░ ░
                ░  ░ ░       ░  ░
-"""                            
+"""
 
 
 class State(enum.Enum):
     WAITING_FOR_FILE_CHANGE_EVENT = 1
     COOLDOWN_IGNORING_EVENTS = 2
 
+
 def print_red(x):
     print('\u001b[31m' + x + '\u001b[0m')
 
+
 def print_green(x):
     print('\u001b[32m' + x + '\u001b[0m')
 
+
 class PigweedBuildWatcher(FileSystemEventHandler):
     def __init__(self,
                  patterns=None,
@@ -64,9 +69,8 @@
     def path_matches(self, path):
         """Returns true if path matches according to the watcher patterns"""
         pure_path = pathlib.PurePath(path)
-        return ((not any(map(pure_path.match, self.ignore_patterns))) and 
-                any(map(pure_path.match, self.patterns)))
-
+        return ((not any(pure_path.match(x) for x in self.ignore_patterns)) and
+                any(pure_path.match(x) for x in self.patterns))
 
     def dispatch(self, event):
         # There isn't any point in triggering builds on new directory creation.
@@ -88,7 +92,6 @@
                 log.debug('Match for path: %s', path)
                 self.on_any_event()
 
-
     def on_any_event(self):
         if self.state == State.WAITING_FOR_FILE_CHANGE_EVENT:
             log.info('Starting the build...🏗️...')
@@ -114,41 +117,96 @@
                 self.state = State.WAITING_FOR_FILE_CHANGE_EVENT
                 self.on_any_event()  # Retrigger.
 
-
     def on_success(self):
         log.debug('Build and tests passed')
         print_green(PASS_MESSAGE)
 
-
     def on_fail(self):
         log.debug('Build and tests failed')
         print_red(FAIL_MESSAGE)
 
 
-if __name__ == '__main__':
+class WatchCommand:
+    # A single command object can handle multiple commands.
+    def commands(self):
+        return [('watch', 'Trigger recompile & retest on source changes')]
+
+    def run(self, subcommand_argv):
+        parser = argparse.ArgumentParser()
+        parser.add_argument('--patterns',
+                            default='*.cc;*.h;*.rst;*.gni;*.gn;*.py')
+        parser.add_argument('--ignore_patterns',
+                            default='out/*')
+        args = parser.parse_args(subcommand_argv)
+
+        log.info('Starting Pigweed build watcher')
+        # TODO(keir): This will need to be made more general; and needs to be
+        # more granular. Currently this will recieve events from Ninja in the
+        # 'out/' directory, which is not ideal. The proper way to handle this
+        # is to create a list of directories that are not Ninja and not Bazel,
+        # and watch those.
+        path = '.'
+        event_handler = PigweedBuildWatcher(
+                patterns=args.patterns.split(';'),
+                ignore_patterns=args.ignore_patterns.split(';'))
+
+        observer = Observer()
+        observer.schedule(event_handler, path, recursive=True)
+        observer.start()
+        log.info('Watching for file modifications')
+        try:
+            while observer.isAlive():
+                observer.join(1)
+        except KeyboardInterrupt:
+            log.info('Got Ctrl-C; exiting...')
+            observer.stop()
+        observer.join()
+
+
+def main():
     coloredlogs.install(level='INFO',
-                        fmt='WATCH - %(asctime)s - %(levelname)s - %(message)s')
-    log = logging.getLogger('pigweed.autobuilder')
+                        fmt='PW - %(asctime)s - %(levelname)s - %(message)s')
+    # TODO(keir): Figure out a better logging policy for subcommands.
+    global log
+    log = logging.getLogger('pw')
 
-    log.info('Starting Pigweed build watcher')
 
-    # TODO(keir): This will need to be made more general; and needs to be more
-    # granular. Currently this will recieve events from Ninja in the 'out/'
-    # directory, which is not ideal. The proper way to handle this is to create
-    # a list of directories that are not Ninja and not Bazel, and watch those.
-    path = '.'
-    event_handler = PigweedBuildWatcher(
-            patterns='*.cc;*.h;*.rst;*.gni'.split(';'),
-            ignore_patterns=['out/*'])
+    # All subcommands go into this list.
+    # TODO(keir): Switch this to upstream argparse nested command handling,
+    # which it turns out can work with a plugin-architecture.
+    subcommands = [
+        WatchCommand()
+    ]
+    # Create help string that lists the subcommands.
+    command_list = '\n'.join([
+        '\n'.join([f'    {cmd:<10} {hlp}'
+                   for (cmd, hlp) in subcommand.commands()])
+        for subcommand in subcommands])
 
-    observer = Observer()
-    observer.schedule(event_handler, path, recursive=True)
-    observer.start()
-    log.info('Watching for file modifications')
-    try:
-        while observer.isAlive():
-            observer.join(1)
-    except KeyboardInterrupt:
-        log.info('Got Ctrl-C; exiting...')
-        observer.stop()
-    observer.join()
+    # Parse the top-level command.
+    parser = argparse.ArgumentParser(
+        description='Pigweed development utility tool',
+        usage='pw <command> [<args>]' + '\n\nSubcommands:\n' + command_list)
+    parser.add_argument('command', help='Subcommand to run')
+    args = parser.parse_args(sys.argv[1:2])
+
+    # Search for subcommand.
+    # TODO(keir): When we have more experience with the subcommand structure
+    # decide if this should move to a dictionary or something similar.
+    matched_command = False
+    for subcommand in subcommands:
+        for (cmd, hlp) in subcommand.commands():
+            if cmd == args.command:
+                matched_command = True
+
+    if not matched_command:
+        log.error('Unknown command: %s', args.command)
+        sys.exit(1)
+
+    # Run the discovered command.
+    subcommand.run(sys.argv[2:])
+
+
+if __name__ == '__main__':
+    main()
+