Add fan presence detection definition parser

The parser creates a source file from a given yaml file that defines
each fan's presence detection method, which is accessible using the
non-generated header file.

Change-Id: I8110c031abc23c0a621cdb984b2a315892d9a1ac
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/example/fan-detect.yaml b/example/fan-detect.yaml
new file mode 100644
index 0000000..ad64e91
--- /dev/null
+++ b/example/fan-detect.yaml
@@ -0,0 +1,24 @@
+# Example fan presence detection definitions

+

+# List each fan requiring presence detection and creation of an inventory object

+# within a system with the following parameters. The 'Detection' method must

+# have an associated fan presence detection application to properly handle

+# detecting fans using that type.

+

+#- [Detection method]:

+#  - Fan: [A fan enclosure instance]

+#    Sensors: [List of sensors associated with this fan enclosure]

+#        - i.e) For tach feedback detection:

+#                   The hwmon name for a detected fan rotor's tach feedback

+#               For gpio detection:

+#                   The gpio pin name for the fan's presence line

+#  Inventory: [The system inventory location for the fan]

+#  Description: (Optional)

+

+# Example entry for a single fan's presence detected by 'Tach' feedback

+#- Tach:

+#  - Fan:

+#    Sensors:

+#        - fan0

+#    Inventory: /system/chassis/fan0

+#    Description: Chassis location A1

diff --git a/fan_detect_defs.hpp b/fan_detect_defs.hpp
new file mode 100644
index 0000000..6c49366
--- /dev/null
+++ b/fan_detect_defs.hpp
@@ -0,0 +1,9 @@
+#pragma once
+
+#include <map>
+#include <set>
+#include <tuple>
+#include "fan_properties.hpp"
+
+extern const std::map<std::string,
+                      std::set<phosphor::fan::Properties>> fanDetectMap;
diff --git a/gen-fan-detect-defs.py b/gen-fan-detect-defs.py
new file mode 100755
index 0000000..1b95193
--- /dev/null
+++ b/gen-fan-detect-defs.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+
+"""
+This script parses the given fan presence definition yaml file and generates
+a header file based on the defined methods for determining when a fan is
+present.
+"""
+
+import os
+import sys
+import yaml
+from argparse import ArgumentParser
+# TODO Convert to using a mako template
+
+
+def get_filename():
+    """
+    Constructs and returns the fully qualified header filename.
+    """
+    script_dir = os.path.dirname(os.path.abspath(__file__))
+    header_file = os.path.join(script_dir, "fan_detect_defs.cpp")
+
+    return header_file
+
+
+def create_and_add_header(header_file):
+    """
+    Creates the header file based on the header filename value given within.
+    The associated fan presence detection application includes this header.
+
+    Parameter descriptions:
+    header_file           Header filename to create
+    """
+    with open(header_file, 'w') as ofile:
+        ofile.write("/* WARNING: This header contains code generated ")
+        ofile.write("by " + __file__ + " */\n")
+        ofile.write("/* !!! DO NOT EDIT THIS FILE BY HAND !!! */\n")
+        ofile.write("#include \"fan_detect_defs.hpp\"\n\n")
+        ofile.write("const std::map<std::string, ")
+        ofile.write("std::set<phosphor::fan::Properties>>")
+        ofile.write("\nfanDetectMap = {\n")
+
+
+def add_detect(header_fd, dmethod):
+    """
+    Adds the detection method map entry for listing the fan(s) that should be
+    detected using the supported method.
+
+    Parameter descriptions:
+    header_fd           Header file to add the detection method to
+    dmethod             The detection method to be added
+    """
+    header_fd.write("    {\"" + dmethod + "\",{\n")
+
+
+def add_fan(header_fd, fan_info):
+    """
+    Adds each fan's yaml entry presence detection information to the
+    corresponding header file for the defined detection method
+
+    Parameter descriptions:
+    header_fd           Header file to add fan information to
+    fan_info            Fan presence detection information
+    """
+    tab = ' ' * 4
+    header_fd.write(tab * 2 + "std::make_tuple(\"" +
+                    fan_info['Inventory'] + "\",\n")
+    header_fd.write(tab * 6 + "\"" +fan_info['Description'] + "\",\n")
+    header_fd.write(tab * 6 + "std::vector<std::string>({")
+    for sensors in fan_info['Sensors']:
+        if sensors != fan_info['Sensors'][-1]:
+            header_fd.write("\"" + sensors + "\",")
+        else:
+            header_fd.write("\"" + sensors + "\"})),\n")
+
+
+def close_detect(header_fd):
+    """
+    Closes the current detection method map entry.
+
+    Parameter descriptions:
+    header_fd           Header file to add detection method closing to
+    """
+    header_fd.write("    }},\n")
+
+
+def add_closing(header_fd):
+    """
+    Appends final closing tags to the header file given. This essentially
+    ends writing to the header file generated.
+
+    Parameter descriptions:
+    header_fd           Header file to add closing tags to
+    """
+    header_fd.write("};\n")
+
+
+def parse_yaml(yaml_file):
+    """
+    Parse the given yaml file, creating a header file for each 'Detection'
+    types found to be included within the app supporting presence detection
+    by that type.
+
+    Parameter descriptions:
+    yaml_file           File to be parsed for fan presences definitions
+    """
+    with open(yaml_file, 'r') as input_file:
+        yaml_input = yaml.safe_load(input_file)
+        header_file = get_filename()
+        create_and_add_header(header_file)
+        header_fd = open(header_file, 'a')
+        if yaml_input:
+            for detect in yaml_input:
+                for dmethod in detect:
+                    add_detect(header_fd, dmethod.replace(" ", "-").lower())
+                    for fan in detect[dmethod]:
+                        add_fan(header_fd, fan)
+                    close_detect(header_fd)
+        add_closing(header_fd)
+        header_fd.close()
+
+
+if __name__ == '__main__':
+    parser = ArgumentParser()
+    # Input yaml containing how each fan's presence detection should be done
+    parser.add_argument("-y", "--yaml", dest="pres_yaml",
+                        default=
+                        "example/fan-detect.yaml",
+                        help=
+                        "Input fan presences definition yaml file to parse")
+    args = parser.parse_args(sys.argv[1:])
+
+    # Verify given yaml file exists
+    yaml_file = os.path.abspath(args.pres_yaml)
+    if not os.path.isfile(yaml_file):
+        print "Unable to find input yaml file " + yaml_file
+        exit(1)
+
+    parse_yaml(yaml_file)