Build the required error log header file for elogs

Take the elog.yaml file and generate the required hpp
file required for build time validation and error log
processing.

A future commit will delete elog-gen.hpp and will
start dynamically generating it during build.

Change-Id: I21a87d959096a2541de69fde47f57f02482a00cc
Signed-off-by: Andrew Geissler <andrewg@us.ibm.com>
diff --git a/elog-gen-template.mako.hpp b/elog-gen-template.mako.hpp
new file mode 100644
index 0000000..8461f70
--- /dev/null
+++ b/elog-gen-template.mako.hpp
@@ -0,0 +1,48 @@
+## Note that this file is not auto generated, it is what generates the
+## elog-gen.hpp file
+// This file was autogenerated.  Do not edit!
+// See elog-gen.py for more details
+#pragma once
+
+#include <tuple>
+#include <type_traits>
+#include "log.hpp"
+
+namespace phosphor
+{
+
+namespace logging
+{
+
+    % for a in errors:
+namespace _${errors[a]}
+{
+    % for b in meta[a]:
+struct ${b}
+{
+    static constexpr auto str = "${meta_data[b]['str']}";
+    static constexpr auto str_short = "${meta_data[b]['str_short']}";
+    using type = std::tuple<std::decay_t<decltype(str)>,${meta_data[b]['type']}>;
+    explicit constexpr ${b}(${meta_data[b]['type']} a) : _entry(entry(str, a)) {};
+    type _entry;
+};
+    % endfor
+
+}  // namespace _${errors[a]}
+<% meta_string = ', '.join(meta[a]) %>
+struct ${errors[a]}
+{
+    static constexpr auto err_code = "xyz.openbmc_project.logging.${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]:
+    using ${b} = _${errors[a]}::${b};
+    % endfor
+    using metadata_types = std::tuple<${meta_string}>;
+};
+
+    % endfor
+
+} // namespace logging
+
+} // namespace phosphor
diff --git a/elog-gen.hpp b/elog-gen.hpp
index 3aba8fb..07d0757 100644
--- a/elog-gen.hpp
+++ b/elog-gen.hpp
@@ -1,4 +1,5 @@
-// this file was autogenerated.  do not edit.
+// This file was autogenerated.  Do not edit!
+// See elog-gen.py for more details
 #pragma once
 
 #include <tuple>
@@ -11,52 +12,87 @@
 namespace logging
 {
 
-namespace _file_not_found
+namespace _GETSCOM
 {
-struct errnum
+struct DEV_ADDR
 {
-    static constexpr auto str = "ERRNO=%d";
-    static constexpr auto str_short = "ERRNO";
+    static constexpr auto str = "DEV_ADDR=0x%.8X";
+    static constexpr auto str_short = "DEV_ADDR";
     using type = std::tuple<std::decay_t<decltype(str)>,int>;
-    explicit constexpr errnum(int a) : _entry(entry(str, a)) {};
+    explicit constexpr DEV_ADDR(int a) : _entry(entry(str, a)) {};
+    type _entry;
+};
+struct DEV_ID
+{
+    static constexpr auto str = "DEV_ID=%u";
+    static constexpr auto str_short = "DEV_ID";
+    using type = std::tuple<std::decay_t<decltype(str)>,int>;
+    explicit constexpr DEV_ID(int a) : _entry(entry(str, a)) {};
+    type _entry;
+};
+struct DEV_NAME
+{
+    static constexpr auto str = "DEV_NAME=%s";
+    static constexpr auto str_short = "DEV_NAME";
+    using type = std::tuple<std::decay_t<decltype(str)>,const char *>;
+    explicit constexpr DEV_NAME(const char * a) : _entry(entry(str, a)) {};
     type _entry;
 };
 
-struct file_path
+}  // namespace _GETSCOM
+
+struct GETSCOM
+{
+    static constexpr auto err_code = "xyz.openbmc_project.logging.GETSCOM";
+    static constexpr auto err_msg = "Getscom call failed";
+    static constexpr auto L = level::ERR;
+    using DEV_ADDR = _GETSCOM::DEV_ADDR;
+    using DEV_ID = _GETSCOM::DEV_ID;
+    using DEV_NAME = _GETSCOM::DEV_NAME;
+    using metadata_types = std::tuple<DEV_ADDR, DEV_ID, DEV_NAME>;
+};
+
+namespace _FILE_NOT_FOUND
+{
+struct ERRNUM
+{
+    static constexpr auto str = "ERRNUM=0x%.4X";
+    static constexpr auto str_short = "ERRNUM";
+    using type = std::tuple<std::decay_t<decltype(str)>,int>;
+    explicit constexpr ERRNUM(int a) : _entry(entry(str, a)) {};
+    type _entry;
+};
+struct FILE_PATH
 {
     static constexpr auto str = "FILE_PATH=%s";
     static constexpr auto str_short = "FILE_PATH";
-    using type = std::tuple<std::decay_t<decltype(str)>,const char*>;
-    explicit constexpr file_path(const char *a) : _entry(entry(str,a)) {};
+    using type = std::tuple<std::decay_t<decltype(str)>,const char *>;
+    explicit constexpr FILE_PATH(const char * a) : _entry(entry(str, a)) {};
+    type _entry;
+};
+struct FILE_NAME
+{
+    static constexpr auto str = "FILE_NAME=%s";
+    static constexpr auto str_short = "FILE_NAME";
+    using type = std::tuple<std::decay_t<decltype(str)>,const char *>;
+    explicit constexpr FILE_NAME(const char * a) : _entry(entry(str, a)) {};
     type _entry;
 };
 
-struct file_name
-{
-   static constexpr auto str = "FILE_NAME=%s";
-   static constexpr auto str_short = "FILE_NAME";
-   using type = std::tuple<std::decay_t<decltype(str)>,const char*>;
-   explicit constexpr file_name(const char *a) : _entry(entry(str,a)) {};
-   type _entry;
-};
+}  // namespace _FILE_NOT_FOUND
 
-} // namespace _file_not_found
-
-struct file_not_found
+struct FILE_NOT_FOUND
 {
-    static constexpr auto err_code = "xyz.openbmc_project.logging.FILE_NOT_FOUND_ERROR";
+    static constexpr auto err_code = "xyz.openbmc_project.logging.FILE_NOT_FOUND";
     static constexpr auto err_msg = "A required file was not found";
     static constexpr auto L = level::INFO;
-
-    using errnum = _file_not_found::errnum;
-    using file_path = _file_not_found::file_path;
-    using file_name = _file_not_found::file_name;
-
-    using metadata_types = std::tuple<errnum, file_path, file_name>;
+    using ERRNUM = _FILE_NOT_FOUND::ERRNUM;
+    using FILE_PATH = _FILE_NOT_FOUND::FILE_PATH;
+    using FILE_NAME = _FILE_NOT_FOUND::FILE_NAME;
+    using metadata_types = std::tuple<ERRNUM, FILE_PATH, FILE_NAME>;
 };
 
 
-
 } // namespace logging
 
 } // namespace phosphor
diff --git a/elog-gen.py b/elog-gen.py
new file mode 100644
index 0000000..61cceac
--- /dev/null
+++ b/elog-gen.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+
+r"""
+This script will parse the input error log yaml file 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.
+
+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
+from optparse import OptionParser
+import yaml
+import sys
+import os
+
+def gen_elog_hpp(i_elog_yaml, i_output_hpp):
+    r"""
+    Read the input yaml file, grab the relevant data and call the mako
+    template to generate the header file.
+
+    Description of arguments:
+    i_elog_yaml                  yaml file describing the error logs
+    i_output_hpp                 header file to output the generated code to
+    """
+
+    # Input parameters to mako template
+    errors = dict()     # 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(i_elog_yaml))
+    err_count = 0;
+    for i in ifile['SW'].keys():
+        # Grab the main error
+        errors[err_count] = i
+        # Grab it's sub-items
+        prop = ifile['SW'][i]
+        error_msg[i] = prop['msg']
+        error_lvl[i] = prop['level']
+        tmp_meta=[]
+        # grab all the meta data fields and info
+        for j in prop['meta']:
+            str_short = j['str'].split('=')[0]
+            tmp_meta.append(str_short)
+            meta_data[str_short] = {}
+            meta_data[str_short]['str'] = j['str']
+            meta_data[str_short]['str_short'] = str_short
+            meta_data[str_short]['type'] = j['type']
+        meta.append(tmp_meta)
+        err_count+=1
+
+    # Debug
+    #for i in errors:
+    #    print "ERROR: " + errors[i]
+    #    print " MSG:  " + error_msg[errors[i]]
+    #    print " LVL:  " + error_lvl[errors[i]]
+    #    print " META: "
+    #    print meta[i]
+
+    # Load the mako template and call it with the required data
+    mytemplate = Template(filename='elog-gen-template.mako.hpp')
+    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))
+    f.close()
+
+
+def main(i_args):
+    parser = OptionParser()
+
+    parser.add_option("-e","--elog",dest="elog_yaml",default="elog.yaml",
+                      help="input error yaml file to parse");
+
+    parser.add_option("-o","--output",dest="output_hpp", default="elog-gen.hpp",
+                      help="output hpp to generate, elog-gen.hpp is default");
+
+    (options, args) = parser.parse_args(i_args)
+
+    if (not (os.path.isfile(options.elog_yaml))):
+        print "Can not find input yaml file " + options.elog_yaml
+        exit(1);
+
+    gen_elog_hpp(options.elog_yaml,options.output_hpp)
+
+# Only run if it's a script
+if __name__ == '__main__':
+    main(sys.argv[1:])
\ No newline at end of file
diff --git a/elog.yaml b/elog.yaml
index 79307a3..0dd21e3 100644
--- a/elog.yaml
+++ b/elog.yaml
@@ -1,13 +1,22 @@
 ---
 SW:
-    FILE_NOT_FOUND_ERROR:
+    FILE_NOT_FOUND:
         msg: "A required file was not found"
         level: INFO
-        meta_i: [ ERRNUM ]
-        meta_s: [ FILE_PATH, FILE_NAME ]
-    GETSCOM_ERROR:
+        meta:
+            - str: "ERRNUM=0x%.4X"
+              type: int
+            - str: FILE_PATH=%s
+              type: const char*
+            - str: FILE_NAME=%s
+              type: const char*
+    GETSCOM:
         msg: "Getscom call failed"
         level: ERR
-        meta_i: [ DEV_ADDR,
-                  DEV_ID ]
-        meta_s: [ DEV_NAME ]
+        meta:
+            - str: DEV_ADDR=0x%.8X
+              type: int
+            - str: DEV_ID=%u
+              type: int
+            - str: DEV_NAME=%s
+              type: const char*
\ No newline at end of file
diff --git a/elog_parser.py b/elog_parser.py
deleted file mode 100755
index 4896700..0000000
--- a/elog_parser.py
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/python -u
-
-import uuid
-import yaml
-
-with open('elog.yaml') as f:
-    ifile = yaml.safe_load(f)
-
-with open('elog-gen.hpp', 'w') as ofile:
-    ofile.write('namespace phosphor\n{\n\n')
-    ofile.write('namespace logging\n{\n\n')
-
-    # TBD
-
-if __name__ == '__main__':
-     print "TODO\n"
diff --git a/logging_test.cpp b/logging_test.cpp
index 9a638c3..c5f622c 100644
--- a/logging_test.cpp
+++ b/logging_test.cpp
@@ -1,9 +1,11 @@
 // A basic unit test that runs on a BMC (qemu or hardware)
 
 #include <iostream>
-#include <elog.hpp>
-#include <log.hpp>
 #include <systemd/sd-journal.h>
+#include <sstream>
+#include "elog.hpp"
+#include "log.hpp"
+#include "elog-gen.hpp"
 
 using namespace phosphor;
 using namespace logging;
@@ -44,7 +46,8 @@
     rc = (validated) ? 0 : 1;
     if(rc)
     {
-        std::cerr << "Failed to find " << i_entry << " in journal!" << "\n";
+        std::cerr << "Failed to find " << i_entry << " with value " << i_value
+                  <<" in journal!" << "\n";
     }
 
     return rc;
@@ -72,56 +75,61 @@
     const char *test_string = "/tmp/test_string/";
     try
     {
-        elog<file_not_found>(file_not_found::errnum(number),
-                             file_not_found::file_path(test_string),
-                             file_not_found::file_name("elog_test_3.txt"));
+        elog<FILE_NOT_FOUND>(FILE_NOT_FOUND::ERRNUM(number),
+                             FILE_NOT_FOUND::FILE_PATH(test_string),
+                             FILE_NOT_FOUND::FILE_NAME("elog_test_3.txt"));
     }
-    catch (elogException<file_not_found>& e)
+    catch (elogException<FILE_NOT_FOUND>& e)
     {
         std::cout << "elog exception caught: " << e.what() << std::endl;
     }
 
     // Now read back and verify our data made it into the journal
-    rc = validate_journal(file_not_found::errnum::str_short,
-                          std::to_string(number).c_str());
+    std::stringstream stream;
+    stream << std::hex << number;
+    rc = validate_journal(FILE_NOT_FOUND::ERRNUM::str_short,
+                          std::string(stream.str()).c_str());
     if(rc)
         return(rc);
 
-    rc = validate_journal(file_not_found::file_path::str_short,
+    rc = validate_journal(FILE_NOT_FOUND::FILE_PATH::str_short,
                           test_string);
     if(rc)
         return(rc);
 
-    rc = validate_journal(file_not_found::file_name::str_short,
+    rc = validate_journal(FILE_NOT_FOUND::FILE_NAME::str_short,
                           "elog_test_3.txt");
     if(rc)
         return(rc);
 
     // TEST 4 - Create error log with previous entry use
-    number = 0xFEDC;
+    number = 0x9876;
     try
     {
-        elog<file_not_found>(file_not_found::errnum(number),
-                             prev_entry<file_not_found::file_path>(),
-                             file_not_found::file_name("elog_test_4.txt"));
+        elog<FILE_NOT_FOUND>(FILE_NOT_FOUND::ERRNUM(number),
+                             prev_entry<FILE_NOT_FOUND::FILE_PATH>(),
+                             FILE_NOT_FOUND::FILE_NAME("elog_test_4.txt"));
     }
     catch (elogExceptionBase& e)
     {
         std::cout << "elog exception caught: " << e.what() << std::endl;
     }
+
     // Now read back and verify our data made it into the journal
-    rc = validate_journal(file_not_found::errnum::str_short,
-                          std::to_string(number).c_str());
+    stream.str("");
+    stream << std::hex << number;
+    rc = validate_journal(FILE_NOT_FOUND::ERRNUM::str_short,
+                          std::string(stream.str()).c_str());
     if(rc)
         return(rc);
 
     // This should just be equal to what we put in test 3
-    rc = validate_journal(file_not_found::file_path::str_short,
+    rc = validate_journal(FILE_NOT_FOUND::FILE_PATH::str_short,
                           test_string);
     if(rc)
         return(rc);
 
-    rc = validate_journal(file_not_found::file_name::str_short,
+    rc = validate_journal(FILE_NOT_FOUND::FILE_NAME::str_short,
                           "elog_test_4.txt");
     if(rc)
         return(rc);
@@ -129,13 +137,13 @@
     // Compile fail tests
 
     // Simple test to prove we fail to compile due to missing param
-    //elog<file_not_found>(file_not_found::errnum(1),
-    //                     file_not_found::file_path("test"));
+    //elog<FILE_NOT_FOUND>(FILE_NOT_FOUND::ERRNUM(1),
+    //                     FILE_NOT_FOUND::FILE_PATH("test"));
 
     // Simple test to prove we fail to compile due to invalid param
-    //elog<file_not_found>(file_not_found::errnum(1),
-    //                     file_not_found::file_path("test"),
-    //                     file_not_found::file_name(1));
+    //elog<FILE_NOT_FOUND>(FILE_NOT_FOUND::ERRNUM(1),
+    //                     FILE_NOT_FOUND::FILE_PATH("test"),
+    //                     FILE_NOT_FOUND::FILE_NAME(1));
 
     return 0;
 }