Add filter hwid_tool command, to filter data for just one board.

This is for use in producing bundles/releases.  The filter
command will produce data files containing only the necessary
data for a given kind of build (filtering by component and bom
supportedness status), and will remove all of the unrelated
component probe names and data from the component_db.

BUG=chrome-os-partner:6003
TEST=run hwid_tool filter_database

Change-Id: Ie5292fbedebe2e150ee6e2f86de1741c1935f84f
Reviewed-on: https://gerrit.chromium.org/gerrit/17579
Commit-Ready: Tammo Spalink <tammo@chromium.org>
Reviewed-by: Tammo Spalink <tammo@chromium.org>
Tested-by: Tammo Spalink <tammo@chromium.org>
diff --git a/common.py b/common.py
index 7a63131..5d24190 100644
--- a/common.py
+++ b/common.py
@@ -18,9 +18,13 @@
 
 class Obj(object):
   """Generic wrapper allowing dot-notation dict access."""
+
   def __init__(self, **field_dict):
     self.__dict__.update(field_dict)
 
+  def __repr__(self):
+    return repr(self.__dict__)
+
 
 # TODO(tammo): Combine this with gft_common.ShellExecution.
 def RunShellCmd(cmd):
diff --git a/hwid_database.py b/hwid_database.py
index bc01eed..fff7b59 100644
--- a/hwid_database.py
+++ b/hwid_database.py
@@ -43,6 +43,12 @@
     return yaml_data
 
   @classmethod
+  def New(c):
+    return c(**dict((elt_key, '' if not isinstance(elt_type, tuple)
+                     else ({} if elt_type[0] is dict else []))
+                    for elt_key, elt_type in c._schema.items()))
+
+  @classmethod
   def Decode(c, data):
     """Given YAML string, creates corresponding object and check its schema."""
     def NestedDecode(elt_type, elt_data):
diff --git a/hwid_tool.py b/hwid_tool.py
index 5d2b4d3..23267b4 100755
--- a/hwid_tool.py
+++ b/hwid_tool.py
@@ -791,21 +791,68 @@
   if board_name in data.device_db:
     print 'ERROR: Board %s already exists.' % board_name
     return
-  device = Device(
-      bitmap_file_path='',
-      hash_map={},
-      hwid_list_deprecated=[],
-      hwid_list_eol=[],
-      hwid_list_qualified=[],
-      hwid_list_supported=[],
-      hwid_map={},
-      initial_config_map={},
-      initial_config_use_map={},
-      release_map={},
-      variant_map={},
-      volatile_map={},
-      vpd_ro_field_list=[])
-  data.device_db[board_name] = device
+  data.device_db[board_name] = Device.New()
+  print data.device_db[board_name].__dict__
+
+
+@Command('filter_database',
+         CmdArg('-b', '--board', required=True),
+         CmdArg('-d', '--dest_dir', required=True),
+         CmdArg('-s', '--by_status', nargs='*', default=['supported']))
+def FilterDatabase(config, data):
+  """Generate trimmed down board data file and corresponding component_db.
+
+  Generate a board data file containing only those boms matching the
+  specified status, and only that portion of the related board data
+  that is used by those boms.  Also produce a component_db which
+  contains entries only for those components used by the selected
+  boms.
+  """
+  # TODO(tammo): Validate inputs -- board name, status, etc.
+  device = data.device_db[config.board]
+  target_hwid_map = {}
+  target_volatile_set = set()
+  target_variant_set = set()
+  for bom, hwid in device.hwid_map.items():
+    for variant in hwid.variant_list:
+      for volatile in device.volatile_map:
+        status = LookupHwidStatus(device, bom, volatile, variant)
+        if status in config.by_status:
+          variant_map = target_hwid_map.setdefault(bom, {})
+          volatile_list = variant_map.setdefault(variant, [])
+          volatile_list.append(volatile)
+          target_volatile_set.add(volatile)
+          target_variant_set.add(variant)
+  filtered_comp_db = CompDb.New()
+  filtered_device = Device.New()
+  for bom in target_hwid_map:
+    hwid = device.hwid_map[bom]
+    filtered_hwid = Hwid.New()
+    filtered_hwid.component_map = hwid.component_map
+    filtered_hwid.variant_list = list(set(hwid.variant_list) &
+                                      target_variant_set)
+    filtered_device.hwid_map[bom] = filtered_hwid
+    for comp_class, comp_name in hwid.component_map.items():
+      filtered_comp_db.component_registry[comp_class] = \
+          data.comp_db.component_registry[comp_class]
+  for volatile_index in target_volatile_set:
+    volatile_details = device.volatile_map[volatile_index]
+    filtered_device.volatile_map[volatile_index] = volatile_details
+    for volatile_class, volatile_name in volatile_details.items():
+      volatile_value = device.hash_map[volatile_name]
+      filtered_device.hash_map[volatile_name] = volatile_value
+  for variant_index in target_variant_set:
+    variant_details = device.variant_map[variant_index]
+    filtered_device.variant_map[variant_index] = variant_details
+  filtered_device.bitmap_file_path = device.bitmap_file_path
+  filtered_device.vpd_ro_field_list = device.vpd_ro_field_list
+  WriteDatastore(config.dest_dir,
+                 Obj(comp_db=filtered_comp_db,
+                     device_db={config.board: filtered_device}))
+  # TODO(tammo): Also filter initial_config once the schema for that
+  # has been refactored to be cleaner.
+  # TODO(tammo): Also filter status for both boms and components once
+  # the schema for that has been refactored to be cleaner.
 
 
 class HackedArgumentParser(ArgumentParser):