elog-gen.py : read multiple error yaml files

This change enables the elog-gen script to look at more than one error
yaml file (and corresponding metadata yaml file). The input to the
script had to be changed to a yaml directory, containing
error yaml files, instead of a single error yaml file.

The reason to support reading multiple error yaml files is that,
without this, applications have to all dump their errors in a single big
error yaml file. Now it's possible to write application/domain specific
error yaml files; they just need to be exported to the same location,
from where elog-gen.py can pick them.

Change-Id: I9418bf0e0b54a7b7f7701b337649cb8eb4c54913
Signed-off-by: Deepak Kodihalli <dkodihal@in.ibm.com>
diff --git a/tools/elog-gen.py b/tools/elog-gen.py
index c72aa98..d10c026 100755
--- a/tools/elog-gen.py
+++ b/tools/elog-gen.py
@@ -1,7 +1,7 @@
 #!/usr/bin/env python
 
 r"""
-This script will parse the input error log yaml file and generate
+This script will parse error log yaml file(s) and generate
 a header file which will then be used by the error logging client and
 server to collect and validate the error information generated by the
 openbmc software components.
@@ -9,7 +9,6 @@
 This code uses a mako template to provide the basic template of the header
 file we're going to generate.  We then call it with information from the
 yaml to generate the header file.
-
 """
 
 from mako.template import Template
@@ -19,6 +18,20 @@
 import os
 
 
+def get_error_yaml_files(i_yaml_dir):
+    yaml_files = filter(
+        lambda file: file.endswith('.errors.yaml'),
+        os.listdir(i_yaml_dir))
+    return yaml_files
+
+
+def get_meta_yaml_file(i_error_yaml_file):
+    # the meta data will be defined in file name where we replace
+    # <Interface>.errors.yaml with <Interface>.metadata.yaml
+    meta_yaml = i_error_yaml_file.replace("errors", "metadata")
+    return meta_yaml
+
+
 def get_cpp_type(i_type):
     typeMap = {
         'int16': 'int16_t',
@@ -34,31 +47,83 @@
     return typeMap[i_type]
 
 
-def gen_elog_hpp(i_rootdir, i_elog_yaml, i_elog_meta_yaml,
-                 i_input_mako, i_output_hpp):
+def gen_elog_hpp(i_yaml_dir, i_output_hpp,
+                 i_template_dir, i_elog_mako, i_error_namespace):
     r"""
-    Read the input yaml file, grab the relevant data and call the mako
-    template to generate the header file.
+    Read  yaml file(s) under input yaml dir, grab the relevant data and call
+    the mako template to generate the output header file.
 
     Description of arguments:
-    i_rootdir                    base directory to search for yaml files
-    i_elog_yaml                  yaml file describing the error logs
-    i_elog_meta_yaml             yaml file describing the meta data for elogs
-    i_input_mako                 input mako template file to use
-    i_output_hpp                 header file to output the generated code to
+    i_yaml_dir                  directory containing error yaml files
+    i_output_hpp                name of the to be generated output hpp
+    i_template_dir              directory containing error mako templates
+    i_elog_mako                 error mako template to render
     """
 
     # Input parameters to mako template
-    errors = dict()  # Main error codes
+    errors = list()  # Main error codes
     error_msg = dict()  # Error msg that corresponds to error code
     error_lvl = dict()  # Error code log level (debug, info, error, ...)
     meta = list()  # The meta data names associated (ERRNO, FILE_NAME, ...)
     meta_data = dict()  # The meta data info (type, format)
 
-    # see elog.yaml for reference
-    ifile = yaml.safe_load(open("/".join((i_rootdir, i_elog_yaml))))
+    error_yamls = get_error_yaml_files(i_yaml_dir)
+
+    for error_yaml in error_yamls:
+        # Verify the error yaml file
+        error_yaml = "/".join((i_yaml_dir, error_yaml))
+        if (not (os.path.isfile(error_yaml))):
+            print "Can not find input yaml file " + error_yaml
+            exit(1)
+
+        # Verify the metadata yaml file
+        meta_yaml = get_meta_yaml_file(error_yaml)
+        if (not (os.path.isfile(meta_yaml))):
+            print "Can not find meta yaml file " + meta_yaml
+            exit(1)
+
+        # Verify the input mako file
+        template_path = "/".join((i_template_dir, i_elog_mako))
+        if (not (os.path.isfile(template_path))):
+            print "Can not find input template file " + template_path
+            exit(1)
+
+        get_elog_data(error_yaml,
+                      meta_yaml,
+                      # 3rd arg is a tuple
+                      (errors,
+                       error_msg,
+                       error_lvl,
+                       meta,
+                       meta_data))
+
+    # Load the mako template and call it with the required data
+    yaml_dir = i_yaml_dir.strip("./")
+    yaml_dir = yaml_dir.strip("../")
+    template = Template(filename=template_path)
+    f = open(i_output_hpp, 'w')
+    f.write(template.render(
+            errors=errors, error_msg=error_msg,
+            error_lvl=error_lvl, meta=meta,
+            meta_data=meta_data, error_namespace=i_error_namespace))
+    f.close()
+
+
+def get_elog_data(i_elog_yaml,
+                  i_elog_meta_yaml,
+                  o_elog_data):
+    r"""
+    Parse the error and metadata yaml files in order to pull out
+    error metadata.
+
+    Description of arguments:
+    i_elog_yaml                 error yaml file
+    i_elog_meta_yaml            metadata yaml file
+    o_elog_data                 error metadata
+    """
+    errors, error_msg, error_lvl, meta, meta_data = o_elog_data
+    ifile = yaml.safe_load(open(i_elog_yaml))
     mfile = yaml.safe_load(open(i_elog_meta_yaml))
-    err_count = 0
     for i in ifile:
         match = None
         # Find the corresponding meta data for this entry
@@ -70,7 +135,7 @@
             print "Error - Did not find meta data for " + i['name']
             exit(1)
         # Grab the main error and it's info
-        errors[err_count] = i['name']
+        errors.append(i['name'])
         error_msg[i['name']] = i['description']
         error_lvl[i['name']] = match['level']
         tmp_meta = []
@@ -83,7 +148,6 @@
             meta_data[str_short]['str_short'] = str_short
             meta_data[str_short]['type'] = get_cpp_type(j['type'])
         meta.append(tmp_meta)
-        err_count += 1
 
     # Debug
     # for i in errors:
@@ -93,22 +157,10 @@
     #   print " META: "
     #   print meta[i]
 
-    # Load the mako template and call it with the required data
-    mytemplate = Template(filename=i_input_mako)
-    f = open(i_output_hpp, 'w')
-    f.write(mytemplate.render(errors=errors, error_msg=error_msg,
-                              error_lvl=error_lvl, meta=meta,
-                              meta_data=meta_data, elog_yaml=i_elog_yaml))
-    f.close()
-
 
 def main(i_args):
     parser = OptionParser()
 
-    parser.add_option("-e", "--elog", dest="elog_yaml",
-                      default="xyz/openbmc_project/Example/Elog.errors.yaml",
-                      help="input error yaml file to parse")
-
     parser.add_option("-m", "--mako", dest="elog_mako",
                       default="elog-gen-template.mako.hpp",
                       help="input mako template file to use")
@@ -117,41 +169,25 @@
                       default="elog-gen.hpp",
                       help="output hpp to generate, elog-gen.hpp is default")
 
-    parser.add_option("-r", "--rootdir", dest="rootdir",
-                      default="example",
+    parser.add_option("-y", "--yamldir", dest="yamldir",
+                      default="./example/xyz/openbmc_project/Example",
                       help="Base directory of yaml files to process")
 
     parser.add_option("-t", "--templatedir", dest="templatedir",
                       default="phosphor-logging/templates/",
                       help="Base directory of files to process")
 
+    parser.add_option("-n", "--namespace", dest="error_namespace",
+                      default="example/xyz/openbmc_project/Example",
+                      help="Error d-bus namespace")
+
     (options, args) = parser.parse_args(i_args)
 
-    # Verify the input yaml file
-    yaml_path = "/".join((options.rootdir, options.elog_yaml))
-    if (not (os.path.isfile(yaml_path))):
-        print "Can not find input yaml file " + yaml_path
-        exit(1)
-
-    # the meta data will be defined in a similar file name where we replace
-    # <Interface>.errors.yaml with <Interface>.metadata.yaml
-    meta_file = options.elog_yaml.replace("errors", "metadata")
-    meta_file = "/".join((options.rootdir, meta_file))
-    if (not (os.path.isfile(meta_file))):
-        print "Can not find meta yaml file " + meta_file
-        exit(1)
-
-    # Verify the input mako file
-    template_path = "/".join((options.templatedir, options.elog_mako))
-    if (not (os.path.isfile(template_path))):
-        print "Can not find input template file " + template_path
-        exit(1)
-
-    gen_elog_hpp(options.rootdir,
-                 options.elog_yaml,
-                 meta_file,
-                 template_path,
-                 options.output_hpp)
+    gen_elog_hpp(options.yamldir,
+                 options.output_hpp,
+                 options.templatedir,
+                 options.elog_mako,
+                 options.error_namespace)
 
 # Only run if it's a script
 if __name__ == '__main__':
diff --git a/tools/example/xyz/openbmc_project/Example/Foo.errors.yaml b/tools/example/xyz/openbmc_project/Example/Foo.errors.yaml
new file mode 100644
index 0000000..73fe62b
--- /dev/null
+++ b/tools/example/xyz/openbmc_project/Example/Foo.errors.yaml
@@ -0,0 +1,2 @@
+- name: Foo
+  description: this is test error Foo
diff --git a/tools/example/xyz/openbmc_project/Example/Foo.metadata.yaml b/tools/example/xyz/openbmc_project/Example/Foo.metadata.yaml
new file mode 100644
index 0000000..2ac0532
--- /dev/null
+++ b/tools/example/xyz/openbmc_project/Example/Foo.metadata.yaml
@@ -0,0 +1,5 @@
+- name: Foo
+  level: INFO
+  meta:
+    - str: "FOO_DATA=%s"
+      type: string
diff --git a/tools/phosphor-logging/templates/elog-gen-template.mako.hpp b/tools/phosphor-logging/templates/elog-gen-template.mako.hpp
index 5c11c1b..1ff9150 100644
--- a/tools/phosphor-logging/templates/elog-gen-template.mako.hpp
+++ b/tools/phosphor-logging/templates/elog-gen-template.mako.hpp
@@ -15,21 +15,22 @@
 namespace logging
 {
 
-    % for a in errors:
+    % for index, name in enumerate(errors):
 <%
-    namespaces = elog_yaml.split('/')
-    namespaces.pop()
-    classname = errors[a]
+    namespaces = error_namespace.split('/')
+    ## In case someone provided a error_namespace ending with '/', remove the
+    ## last split string, which would be an empty string.
+    if not namespaces[-1]:
+        namespaces = namespaces[:-1]
+    classname = name
 %>\
     % for s in namespaces:
 namespace ${s}
 {
     % endfor
-namespace Error
-{
 namespace _${classname}
 {
-    % for b in meta[a]:
+    % for b in meta[index]:
 struct ${b}
 {
     static constexpr auto str = "${meta_data[b]['str']}";
@@ -41,18 +42,17 @@
     % endfor
 
 }  // namespace _${classname}
-<% meta_string = ', '.join(meta[a]) %>
+<% meta_string = ', '.join(meta[index]) %>
 struct ${classname}
 {
-    static constexpr auto err_code = "${errors[a]}";
-    static constexpr auto err_msg = "${error_msg[errors[a]]}";
-    static constexpr auto L = level::${error_lvl[errors[a]]};
-    % for b in meta[a]:
+    static constexpr auto err_code = "${name}";
+    static constexpr auto err_msg = "${error_msg[name]}";
+    static constexpr auto L = level::${error_lvl[name]};
+    % for b in meta[index]:
     using ${b} = _${classname}::${b};
     % endfor
     using metadata_types = std::tuple<${meta_string}>;
 };
-} // namespace Error
 % for s in reversed(namespaces):
 } // namespace ${s}
 % endfor
diff --git a/tools/phosphor-logging/templates/elog-lookup-template.mako.cpp b/tools/phosphor-logging/templates/elog-lookup-template.mako.cpp
index a96760e..be4ff96 100644
--- a/tools/phosphor-logging/templates/elog-lookup-template.mako.cpp
+++ b/tools/phosphor-logging/templates/elog-lookup-template.mako.cpp
@@ -16,7 +16,7 @@
 std::map<std::string,std::vector<std::string>> g_errMetaMap = {
     % for a in errors:
     <% meta_string = '\",\"'.join(meta[a]) %> \
-    {"${errors[a]}",{"${meta_string}"}},
+    {"${a}",{"${meta_string}"}},
     % endfor
 };