[CORD-1569]
Autogenerate documentation of build system variables

Change-Id: I839f46d681e8f6954316f0ea4e9a79395501459f
diff --git a/docs/scripts/defaultsdoc.py b/docs/scripts/defaultsdoc.py
new file mode 100644
index 0000000..f75fa85
--- /dev/null
+++ b/docs/scripts/defaultsdoc.py
@@ -0,0 +1,191 @@
+#!/usr/bin/env python
+# defaultsdoc.py - documentation for ansible default vaules
+
+# Copyright 2017-present Open Networking Foundation
+#
+# 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.
+
+import argparse
+import fnmatch
+import jinja2
+import logging
+import os
+import pprint
+import re
+import sys
+import xml.etree.ElementTree as ET
+import yaml
+import markedyaml
+
+# logging setup
+sh = logging.StreamHandler(sys.stderr)
+sh.setFormatter(logging.Formatter(logging.BASIC_FORMAT))
+
+LOG = logging.getLogger("defaultsdoc.py")
+LOG.addHandler(sh)
+
+# parse args
+parser = argparse.ArgumentParser()
+
+parser.add_argument('-p', '--playbook_dir', default='../platform-install/',
+                    action='append', required=False,
+                    help="path to base playbook directory")
+
+parser.add_argument('-d', '--descriptions', default='scripts/descriptions.md',
+                    action='store', required=False,
+                    help="markdown file with descriptions")
+
+parser.add_argument('-t', '--template', default='scripts/defaults.md.j2',
+                    action='store', required=False,
+                    help="jinja2 template to fill with defaults")
+
+parser.add_argument('-o', '--output', default='defaults.md',
+                    action='store', required=False,
+                    help="output file")
+
+args = parser.parse_args()
+
+# find the branch we're on via the repo manifest
+manifest_path =  os.path.abspath("../../.repo/manifest.xml")
+try:
+    tree = ET.parse(manifest_path)
+    manifest_xml = tree.getroot()
+    repo_default = manifest_xml.find('default')
+    repo_branch = repo_default.attrib['revision']
+except Exception:
+    LOG.exception("Error loading repo manifest")
+    sys.exit(1)
+
+role_defs = []
+profile_defs = []
+group_defs = []
+
+# frontmatter section is any text at the top of the descriptions.md file, and
+# comes before all other sections
+def_docs = {'frontmatter':{'description':''}}
+
+# find all the files to be processed
+for dirpath, dirnames, filenames in os.walk(args.playbook_dir):
+    basepath = re.sub(args.playbook_dir, '', dirpath)
+    for filename in filenames :
+        filepath = os.path.join(basepath, filename)
+
+        if fnmatch.fnmatch(filepath, "roles/*/defaults/*.yml"):
+            role_defs.append(filepath)
+
+        if fnmatch.fnmatch(filepath, "profile_manifests/*.yml"):
+            profile_defs.append(filepath)
+
+        if fnmatch.fnmatch(filepath, "group_vars/*.yml"):
+            group_defs.append(filepath)
+
+
+
+for rd in role_defs:
+    rd_vars = {}
+    # trim slash so basename grabs the final directory name
+    rd_basedir = os.path.basename(args.playbook_dir[:-1])
+    try:
+        rd_fullpath = os.path.abspath(os.path.join(args.playbook_dir, rd))
+        rd_partialpath = os.path.join(rd_basedir, rd)
+
+        # partial URL, without line nums
+        rd_url = "https://github.com/opencord/platform-install/tree/%s/%s" % (repo_branch, rd)
+
+        
+        rd_fh= open(rd_fullpath, 'r')
+
+        # markedloader is for line #'s
+        loader = markedyaml.MarkedLoader(rd_fh.read())
+        marked_vars = loader.get_data()
+
+        rd_fh.seek(0)  # go to front of file
+
+        # yaml.safe_load is for vars in a better format
+        rd_vars = yaml.safe_load(rd_fh)
+
+        rd_fh.close()
+
+    except yaml.YAMLError:
+        LOG.exception("Problem loading file: %s" % rd)
+        sys.exit(1)
+
+    if rd_vars:
+
+        for key, val in rd_vars.iteritems():
+
+           # build full URL to lines. Lines numbered from zero, so +1 on them to match github
+           if marked_vars[key].start_mark.line == marked_vars[key].end_mark.line:
+               full_url = "%s#L%d" % (rd_url, marked_vars[key].start_mark.line+1)
+           else:
+               full_url = "%s#L%d-L%d" % (rd_url, marked_vars[key].start_mark.line, marked_vars[key].end_mark.line)
+
+           if key in def_docs:
+                if def_docs[key]['defval'] == val:
+                    def_docs[key]['reflist'].append({'path':rd_partialpath, 'link':full_url})
+                else:
+                    LOG.error(" %s has different default > %s : %s" % (rd, key, val))
+           else:
+                to_print = { str(key): val }
+                pp = yaml.dump(to_print, indent=4, allow_unicode=False, default_flow_style=False)
+
+                def_docs[key] = {
+                        'defval': val,
+                        'defval_pp': pp,
+                        'description': "",
+                        'reflist': [{'path':rd_partialpath, 'link':full_url}],
+                        }
+
+# read in descriptions file
+descriptions = {}
+with open(args.descriptions, 'r') as descfile:
+    desc_name = 'frontmatter'
+    desc_lines = ''
+
+    for d_l in descfile:
+        # see if this is a header line at beginning of docs
+        desc_header = re.match(r"##\s+([\w_]+)", d_l)
+
+        if desc_header:
+            # add previous description to dict
+            descriptions[desc_name] = desc_lines
+
+            # set this as the next name, wipe out lines
+            desc_name = desc_header.group(1)
+            desc_lines = ''
+        else:
+            desc_lines += d_l
+
+    descriptions[desc_name] = desc_lines
+
+# add descriptions to def_docs
+for d_name, d_text in descriptions.iteritems():
+    if d_name in def_docs:
+        def_docs[d_name]['description'] = d_text
+    else:
+        LOG.error("Description exists for '%s' but doesn't exist in defaults" % d_name)
+
+# check for missing descriptions
+for key in sorted(def_docs):
+    if not def_docs[key]['description']:
+        LOG.error("No description found for '%s'" % key)
+
+# Add to template and write to output file
+j2env = jinja2.Environment(
+    loader = jinja2.FileSystemLoader('.')
+)
+
+template = j2env.get_template(args.template)
+
+with open(args.output, 'w') as f:
+    f.write(template.render(def_docs=def_docs))