Camera: Generate tag string info from XML

Change-Id: I5109a3c46a065fb2dce31482d4377c33aeb84176
diff --git a/camera/docs/camera_metadata_tag_info.mako b/camera/docs/camera_metadata_tag_info.mako
new file mode 100644
index 0000000..ab6c404
--- /dev/null
+++ b/camera/docs/camera_metadata_tag_info.mako
@@ -0,0 +1,62 @@
+## -*- coding: utf-8 -*-
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * 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
+ *
+ *      http://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.
+ */
+
+/**
+ * !! Do not reference this file directly !!
+ *
+ * It is logically a part of camera_metadata.c.  It is broken out for ease of
+ * maintaining the tag info.
+ *
+ * Array assignments are done using specified-index syntax to keep things in
+ * sync with camera_metadata_tags.h
+ */
+
+/**
+ * ! Do not edit this file directly !
+ *
+ * Generated automatically from camera_metadata_tag_info.mako
+ */
+
+const char *camera_metadata_section_names[ANDROID_SECTION_COUNT] = {
+  % for i in find_all_sections(metadata):
+    ${"[%s]" %(path_name(i)) | csym,pad(36)} = "${path_name(i)}",
+  % endfor
+};
+
+unsigned int camera_metadata_section_bounds[ANDROID_SECTION_COUNT][2] = {
+  % for i in find_all_sections(metadata):
+    ${"[%s]" %(path_name(i)) | csym,pad(36)} = { ${path_name(i) | csym}_START,
+                                       ${path_name(i) | csym}_END },
+  % endfor
+};
+
+% for sec in find_all_sections(metadata):
+static tag_info_t ${path_name(sec) | csyml}[${path_name(sec) | csym}_END -
+        ${path_name(sec) | csym}_START] = {
+  % for entry in find_unique_entries(sec):
+    [ ${entry.name | csym} - ${path_name(sec) | csym}_START ] =
+    { ${'"%s",' %(entry.name_short) | pad(40)} ${entry.type | ctype_enum,ljust(11)} },
+  % endfor
+};
+
+% endfor
+
+tag_info_t *tag_info[ANDROID_SECTION_COUNT] = {
+  % for i in find_all_sections(metadata):
+    ${path_name(i) | csyml},
+  % endfor
+};
diff --git a/camera/docs/camera_metadata_tags.mako b/camera/docs/camera_metadata_tags.mako
index 184bc01..d4a5e59 100644
--- a/camera/docs/camera_metadata_tags.mako
+++ b/camera/docs/camera_metadata_tags.mako
@@ -27,75 +27,6 @@
  * Generated automatically from camera_metadata_tags.mako
  */
 
-<%!
-  import metadata_model
-
-  def _is_sec_or_ins(x):
-    return isinstance(x, metadata_model.Section) or    \
-           isinstance(x, metadata_model.InnerNamespace)
-
-  ##
-  ## Metadata Helpers
-  ##
-
-  def find_all_sections(root):
-    return root.find_all(_is_sec_or_ins)
-
-  def find_parent_section(entry):
-    return entry.find_parent_first(_is_sec_or_ins)
-
-  # find uniquely named entries (w/o recursing through inner namespaces)
-  def find_unique_entries(node):
-    if not isinstance(node, metadata_model.Section) and    \
-       not isinstance(node, metadata_model.InnerNamespace):
-      raise TypeError("expected node to be a Section or InnerNamespace")
-
-    d = {}
-    # remove the 'kinds' from the path between sec and the closest entries
-    # then search the immediate children of the search path
-    search_path = isinstance(node, metadata_model.Section) and node.kinds \
-                  or [node]
-    for i in search_path:
-        for entry in i.entries:
-            d[entry.name] = entry
-
-    for k,v in d.iteritems():
-        yield v
-
-  def path_name(node):
-    isa = lambda x,y: isinstance(x, y)
-    fltr = lambda x: not isa(x, metadata_model.Metadata) and \
-                     not isa(x, metadata_model.Kind)
-
-    path = node.find_parents(fltr)
-    path = list(path)
-    path.reverse()
-    path.append(node)
-
-    return ".".join((i.name for i in path))
-
-  ##
-  ## Filters
-  ##
-
-  # abcDef.xyz -> ABC_DEF_XYZ
-  def csym(name):
-    newstr = name
-    newstr = "".join([i.isupper() and ("_" + i) or i for i in newstr]).upper()
-    newstr = newstr.replace(".", "_")
-    return newstr
-
-  # pad with spaces to make string len == size. add new line if too big
-  def ljust(size, indent=4):
-    def inner(what):
-        newstr = what.ljust(size)
-        if len(newstr) > size:
-            return what + "\n" + "".ljust(indent + size)
-        else:
-            return newstr
-    return inner
-%>
-
 /** TODO: Nearly every enum in this file needs a description */
 
 /**
@@ -119,7 +50,7 @@
  */
 typedef enum camera_metadata_section_start {
   % for i in find_all_sections(metadata):
-    ${path_name(i) + '.start' | csym,ljust(30)} = ${path_name(i) | csym,ljust(25)} << 16,
+    ${path_name(i) + '.start' | csym,ljust(30)} = ${path_name(i) | csym,pad(64)} << 16,
   % endfor
     VENDOR_SECTION_START           = VENDOR_SECTION            << 16
 } camera_metadata_section_start_t;
@@ -157,7 +88,7 @@
         % if val.id is None:
     ${entry.name | csym}_${val.name},
         % else:
-    ${'%s_%s'%(csym(entry.name), val.name) | ljust(30)} = ${val.id},
+    ${'%s_%s'%(csym(entry.name), val.name) | pad(65)} = ${val.id},
         % endif
       % endfor
 } camera_metadata_enum_${csym(entry.name).lower()}_t;
@@ -166,3 +97,38 @@
   % endfor
 
 %endfor
+
+int camera_metadata_enum_snprint(uint32_t tag,
+                                 uint32_t value,
+                                 char *dst,
+                                 size_t size) {
+    const char *msg = "error: not an enum";
+    int ret = -1;
+
+    switch(tag) {
+    % for sec in find_all_sections(metadata):
+      % for idx,entry in enumerate(find_unique_entries(sec)):
+        case ${entry.name | csym}: {
+          % if entry.type == 'enum':
+            switch (value) {
+              % for val in entry.enum.values:
+                case ${entry.name | csym}_${val.name}:
+                    msg = "${val.name}";
+                    ret = 0;
+                    break;
+              % endfor
+                default:
+                    msg = "error: enum value out of range";
+            }
+          % endif
+            break;
+        }
+      % endfor
+
+    %endfor
+    }
+
+    snprintf(dst, size, msg);
+    return ret;
+}
+
diff --git a/camera/docs/metadata_helpers.py b/camera/docs/metadata_helpers.py
new file mode 100644
index 0000000..bcfa29e
--- /dev/null
+++ b/camera/docs/metadata_helpers.py
@@ -0,0 +1,245 @@
+#
+# Copyright (C) 2012 The Android Open Source Project
+#
+# 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
+#
+#      http://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.
+#
+
+"""
+A set of helpers for rendering Mako templates with a Metadata model.
+"""
+
+import metadata_model
+
+_context_buf = None
+
+def _is_sec_or_ins(x):
+  return isinstance(x, metadata_model.Section) or    \
+         isinstance(x, metadata_model.InnerNamespace)
+
+##
+## Metadata Helpers
+##
+
+def find_all_sections(root):
+  """
+  Find all descendants that are Section or InnerNamespace instances.
+
+  Args:
+    root: a Metadata instance
+
+  Returns:
+    A list of Section/InnerNamespace instances
+
+  Remarks:
+    These are known as "sections" in the generated C code.
+  """
+  return root.find_all(_is_sec_or_ins)
+
+def find_parent_section(entry):
+  """
+  Find the closest ancestor that is either a Section or InnerNamespace.
+
+  Args:
+    entry: an Entry or Clone node
+
+  Returns:
+    An instance of Section or InnerNamespace
+  """
+  return entry.find_parent_first(_is_sec_or_ins)
+
+# find uniquely named entries (w/o recursing through inner namespaces)
+def find_unique_entries(node):
+  """
+  Find all uniquely named entries, without recursing through inner namespaces.
+
+  Args:
+    node: a Section or InnerNamespace instance
+
+  Yields:
+    A sequence of MergedEntry nodes representing an entry
+
+  Remarks:
+    This collapses multiple entries with the same fully qualified name into
+    one entry (e.g. if there are multiple entries in different kinds).
+  """
+  if not isinstance(node, metadata_model.Section) and    \
+     not isinstance(node, metadata_model.InnerNamespace):
+      raise TypeError("expected node to be a Section or InnerNamespace")
+
+  d = {}
+  # remove the 'kinds' from the path between sec and the closest entries
+  # then search the immediate children of the search path
+  search_path = isinstance(node, metadata_model.Section) and node.kinds \
+                or [node]
+  for i in search_path:
+      for entry in i.entries:
+          d[entry.name] = entry
+
+  for k,v in d.iteritems():
+      yield v.merge()
+
+def path_name(node):
+  """
+  Calculate a period-separated string path from the root to this element,
+  by joining the names of each node and excluding the Metadata/Kind nodes
+  from the path.
+
+  Args:
+    node: a Node instance
+
+  Returns:
+    A string path
+  """
+
+  isa = lambda x,y: isinstance(x, y)
+  fltr = lambda x: not isa(x, metadata_model.Metadata) and \
+                   not isa(x, metadata_model.Kind)
+
+  path = node.find_parents(fltr)
+  path = list(path)
+  path.reverse()
+  path.append(node)
+
+  return ".".join((i.name for i in path))
+
+##
+## Filters
+##
+
+# abcDef.xyz -> ABC_DEF_XYZ
+def csym(name):
+  """
+  Convert an entry name string into an uppercase C symbol.
+
+  Returns:
+    A string
+
+  Example:
+    csym('abcDef.xyz') == 'ABC_DEF_XYZ'
+  """
+  newstr = name
+  newstr = "".join([i.isupper() and ("_" + i) or i for i in newstr]).upper()
+  newstr = newstr.replace(".", "_")
+  return newstr
+
+# abcDef.xyz -> abc_def_xyz
+def csyml(name):
+  """
+  Convert an entry name string into a lowercase C symbol.
+
+  Returns:
+    A string
+
+  Example:
+    csyml('abcDef.xyz') == 'abc_def_xyz'
+  """
+  return csym(name).lower()
+
+# pad with spaces to make string len == size. add new line if too big
+def ljust(size, indent=4):
+  """
+  Creates a function that given a string will pad it with spaces to make
+  the string length == size. Adds a new line if the string was too big.
+
+  Args:
+    size: an integer representing how much spacing should be added
+    indent: an integer representing the initial indendation level
+
+  Returns:
+    A function that takes a string and returns a string.
+
+  Example:
+    ljust(8)("hello") == 'hello   '
+
+  Remarks:
+    Deprecated. Use pad instead since it works for non-first items in a
+    Mako template.
+  """
+  def inner(what):
+    newstr = what.ljust(size)
+    if len(newstr) > size:
+      return what + "\n" + "".ljust(indent + size)
+    else:
+      return newstr
+  return inner
+
+def _find_new_line():
+
+  if _context_buf is None:
+    raise ValueError("Context buffer was not set")
+
+  buf = _context_buf
+  x = -1 # since the first read is always ''
+  cur_pos = buf.tell()
+  while buf.tell() > 0 and buf.read(1) != '\n':
+    buf.seek(cur_pos - x)
+    x = x + 1
+
+  buf.seek(cur_pos)
+
+  return int(x)
+
+# Pad the string until the buffer reaches the desired column.
+# If string is too long, insert a new line with 'col' spaces instead
+def pad(col):
+  """
+  Create a function that given a string will pad it to the specified column col.
+  If the string overflows the column, put the string on a new line and pad it.
+
+  Args:
+    col: an integer specifying the column number
+
+  Returns:
+    A function that given a string will produce a padded string.
+
+  Example:
+    pad(8)("hello") == 'hello   '
+
+  Remarks:
+    This keeps track of the line written by Mako so far, so it will always
+    align to the column number correctly.
+  """
+  def inner(what):
+    wut = int(col)
+    current_col = _find_new_line()
+
+    if len(what) > wut - current_col:
+      return what + "\n".ljust(col)
+    else:
+      return what.ljust(wut - current_col)
+  return inner
+
+# int32 -> TYPE_INT32, byte -> TYPE_BYTE, etc. note that enum -> TYPE_INT32
+def ctype_enum(what):
+  """
+  Generate a camera_metadata_type_t symbol from a type string.
+
+  Args:
+    what: a type string
+
+  Returns:
+    A string representing the camera_metadata_type_t
+
+  Example:
+    ctype_enum('int32') == 'TYPE_INT32'
+    ctype_enum('int64') == 'TYPE_INT64'
+    ctype_enum('float') == 'TYPE_FLOAT'
+    ctype_enum('enum')  == 'TYPE_INT32'
+
+  Remarks:
+    An enum is coerced to an int32 since the rest of the camera_metadata
+    code doesn't support enums directly yet.
+  """
+  if what == 'enum':
+      return 'TYPE_INT32'
+  return 'TYPE_%s' %(what.upper())
diff --git a/camera/docs/metadata_parser_xml.py b/camera/docs/metadata_parser_xml.py
index 05953d4..a9ee25f 100755
--- a/camera/docs/metadata_parser_xml.py
+++ b/camera/docs/metadata_parser_xml.py
@@ -36,15 +36,20 @@
 """
 
 import sys
+import os
+import StringIO
 
 from bs4 import BeautifulSoup
 from bs4 import NavigableString
 
 from mako.template import Template
+from mako.lookup import TemplateLookup
+from mako.runtime import Context
 
 from metadata_model import *
 import metadata_model
 from metadata_validate import *
+import metadata_helpers
 
 class MetadataParserXml:
   """
@@ -212,12 +217,29 @@
     """
     Render the metadata model using a Mako template as the view.
 
+    The template gets the metadata as an argument, as well as all
+    public attributes from the metadata_helpers module.
+
     Args:
       template: path to a Mako template file
       output_name: path to the output file, or None to use stdout
     """
-    tpl = Template(filename=template)
-    tpl_data = tpl.render(metadata=self.metadata, metadata_model=metadata_model)
+    buf = StringIO.StringIO()
+    metadata_helpers._context_buf = buf
+
+    helpers = [(i, getattr(metadata_helpers, i))
+                for i in dir(metadata_helpers) if not i.startswith('_')]
+    helpers = dict(helpers)
+
+    lookup = TemplateLookup(directories=[os.getcwd()])
+    tpl = Template(filename=template, lookup=lookup)
+
+    ctx = Context(buf, metadata=self.metadata, **helpers)
+    tpl.render_context(ctx)
+
+    tpl_data = buf.getvalue()
+    metadata_helpers._context_buf = None
+    buf.close()
 
     if output_name is None:
       print tpl_data