hacking/module_formatter.py
d47e15a1
 #!/usr/bin/env python
 # (c) 2012, Jan-Piet Mens <jpmens () gmail.com>
 #
 # This file is part of Ansible
 #
 # Ansible is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
 #
 # Ansible is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
 #
 # You should have received a copy of the GNU General Public License
 # along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
 #
 
 import os
391fb98e
 import glob
d47e15a1
 import sys
 import yaml
 import codecs
 import json
 import ast
 from jinja2 import Environment, FileSystemLoader
 import re
2786149b
 import optparse
d47e15a1
 import time
 import datetime
 import subprocess
7681b1ce
 import cgi
e32f4a05
 import ansible.utils
0ae7f996
 import ansible.utils.module_docs as module_docs
31a4fe41
 
60f06c36
 # Get parent directory of the directory this script lives in
 MODULEDIR=os.path.abspath(os.path.join(
     os.path.dirname(os.path.realpath(__file__)), os.pardir, 'library'
     ))
 EXAMPLE_YAML=os.path.abspath(os.path.join(
     os.path.dirname(os.path.realpath(__file__)), os.pardir, 'examples', 'DOCUMENTATION.yaml'
     ))
d47e15a1
 
 # There is a better way of doing this!
 # TODO: somebody add U(text, http://foo.bar/) as described by Tim in #991
 
 _ITALIC = re.compile(r"I\(([^)]+)\)")
 _BOLD   = re.compile(r"B\(([^)]+)\)")
 _MODULE = re.compile(r"M\(([^)]+)\)")
 _URL    = re.compile(r"U\(([^)]+)\)")
 _CONST  = re.compile(r"C\(([^)]+)\)")
 
 def latex_ify(text):
 
     t = _ITALIC.sub("\\I{" + r"\1" + "}", text)
     t = _BOLD.sub("\\B{" + r"\1" + "}", t)
     t = _MODULE.sub("\\M{" + r"\1" + "}", t)
     t = _URL.sub("\\url{" + r"\1" + "}", t)
     t = _CONST.sub("\\C{" + r"\1" + "}", t)
 
     return t
 
 def html_ify(text):
 
fa963547
     #print "DEBUG: text=%s" % text
3f8aa8ae
     
7681b1ce
     t = cgi.escape(text)
     t = _ITALIC.sub("<em>" + r"\1" + "</em>", t)
d47e15a1
     t = _BOLD.sub("<b>" + r"\1" + "</b>", t)
     t = _MODULE.sub("<span class='module'>" + r"\1" + "</span>", t)
     t = _URL.sub("<a href='" + r"\1" + "'>" + r"\1" + "</a>", t)
068ef0e9
     t = _CONST.sub("<code>" + r"\1" + "</code>", t)
7681b1ce
 
d47e15a1
     return t
 
94de4db9
 def json_ify(text):
 
     t = _ITALIC.sub("<em>" + r"\1" + "</em>", text)
     t = _BOLD.sub("<b>" + r"\1" + "</b>", t)
     t = _MODULE.sub("<span class='module'>" + r"\1" + "</span>", t)
     t = _URL.sub("<a href='" + r"\1" + "'>" + r"\1" + "</a>", t)
     t = _CONST.sub("<code>" + r"\1" + "</code>", t)
 
     return t
 
ee679c01
 
 def js_ify(text):
 
     return text
 
 
d47e15a1
 def man_ify(text):
 
     t = _ITALIC.sub(r'\\fI' + r"\1" + r"\\fR", text)
     t = _BOLD.sub(r'\\fB' + r"\1" + r"\\fR", t)
     t = _MODULE.sub(r'\\fI' + r"\1" + r"\\fR", t)
     t = _URL.sub(r'\\fI' + r"\1" + r"\\fR", t)
     t = _CONST.sub(r'\\fC' + r"\1" + r"\\fR", t)
 
     return t
 
 def rst_ify(text):
 
     t = _ITALIC.sub(r'*' + r"\1" + r"*", text)
     t = _BOLD.sub(r'**' + r"\1" + r"**", t)
caf003c8
     t = _MODULE.sub(r'``' + r"\1" + r"``", t)
d47e15a1
     t = _URL.sub(r"\1", t)
caf003c8
     t = _CONST.sub(r'``' + r"\1" + r"``", t)
d47e15a1
 
     return t
 
7681b1ce
 _MARKDOWN = re.compile(r"[*_`]")
 
d4f89122
 def markdown_ify(text):
 
7681b1ce
     t = cgi.escape(text)
     t = _MARKDOWN.sub(r"\\\g<0>", t)
     t = _ITALIC.sub("_" + r"\1" + "_", t)
d4f89122
     t = _BOLD.sub("**" + r"\1" + "**", t)
     t = _MODULE.sub("*" + r"\1" + "*", t)
     t = _URL.sub("[" + r"\1" + "](" + r"\1" + ")", t)
     t = _CONST.sub("`" + r"\1" + "`", t)
 
     return t
 
d47e15a1
 # Helper for Jinja2 (format() doesn't work here...)
 def rst_fmt(text, fmt):
     return fmt % (text)
 
 def rst_xline(width, char="="):
     return char * width
 
405c097c
 def load_examples_section(text):
     return text.split('***BREAK***')
d47e15a1
 
2786149b
 def return_data(text, options, outputname, module):
     if options.output_dir is not None:
         f = open(os.path.join(options.output_dir, outputname % module), 'w')
29aaa5e6
         f.write(text.encode('utf-8'))
ee679c01
         f.close()
     else:
         print text
 
60f06c36
 def boilerplate():
     if not os.path.exists(EXAMPLE_YAML):
af40b19a
         print >>sys.stderr, "Missing example boiler plate: %s" % EXAMPLE_YAML
9ca0289d
     print "DOCUMENTATION = '''"
60f06c36
     print file(EXAMPLE_YAML).read()
9ca0289d
     print "'''"
     print ""
60f06c36
 
391fb98e
 def list_modules(module_dir):
     categories = {}
     files = glob.glob("%s/*" % module_dir)
     for d in files:
         if os.path.isdir(d):
             files2 = glob.glob("%s/*" % d)
             for f in files2:
                 tokens = f.split("/")
                 module = tokens[-1]
                 category = tokens[-2]
                 if not category in categories:
                     categories[category] = {}
                 categories[category][module] = f
     return categories
ee679c01
 
d47e15a1
 def main():
 
2786149b
     p = optparse.OptionParser(
         version='%prog 1.0',
         usage='usage: %prog [options] arg1 arg2',
         description='Convert Ansible module DOCUMENTATION strings to other formats',
     )
 
     p.add_option("-A", "--ansible-version",
             action="store",
             dest="ansible_version",
             default="unknown",
             help="Ansible version number")
     p.add_option("-M", "--module-dir",
             action="store",
             dest="module_dir",
             default=MODULEDIR,
             help="Ansible modules/ directory")
     p.add_option("-T", "--template-dir",
             action="store",
             dest="template_dir",
             default="hacking/templates",
             help="directory containing Jinja2 templates")
     p.add_option("-t", "--type",
             action='store',
             dest='type',
a53259a7
             choices=['html', 'latex', 'man', 'rst', 'json', 'markdown', 'js'],
2786149b
             default='latex',
             help="Output type")
     p.add_option("-m", "--module",
             action='append',
             default=[],
             dest='module_list',
             help="Add modules to process in module_dir")
     p.add_option("-v", "--verbose",
             action='store_true',
             default=False,
             help="Verbose")
     p.add_option("-o", "--output-dir",
             action="store",
             dest="output_dir",
             default=None,
             help="Output directory for module files")
     p.add_option("-I", "--includes-file",
             action="store",
             dest="includes_file",
             default=None,
             help="Create a file containing list of processed modules")
     p.add_option("-G", "--generate",
             action="store_true",
             dest="do_boilerplate",
             default=False,
             help="generate boilerplate DOCUMENTATION to stdout")
60f06c36
     p.add_option('-V', action='version', help='Show version number and exit')
2786149b
 
     (options, args) = p.parse_args()
 
 #    print "M: %s" % options.module_dir
 #    print "t: %s" % options.type
 #    print "m: %s" % options.module_list
 #    print "v: %s" % options.verbose
 
     if options.do_boilerplate:
d47e15a1
         boilerplate()
9ca0289d
 
         print ""
         print "EXAMPLES = '''"
         print "# example of doing ___ from a playbook"
         print "your_module: some_arg=1 other_arg=2"
         print "'''"
         print ""
 
d47e15a1
         sys.exit(0)
 
2786149b
     if not options.module_dir:
d47e15a1
         print "Need module_dir"
         sys.exit(1)
60f06c36
     if not os.path.exists(options.module_dir):
         print >>sys.stderr, "Module directory does not exist: %s" % options.module_dir
         sys.exit(1)
 
d47e15a1
 
2786149b
     if not options.template_dir:
62d038dc
         print "Need template_dir"
         sys.exit(1)
 
2786149b
     env = Environment(loader=FileSystemLoader(options.template_dir),
62d038dc
         variable_start_string="@{",
         variable_end_string="}@",
e4338d0c
         trim_blocks=True,
626203a7
     )
62d038dc
 
     env.globals['xline'] = rst_xline
83f277cf
 
2786149b
     if options.type == 'latex':
d47e15a1
         env.filters['jpfunc'] = latex_ify
         template = env.get_template('latex.j2')
         outputname = "%s.tex"
afa467e9
         includecmt = ""
         includefmt = "%s\n"
2786149b
     if options.type == 'html':
d47e15a1
         env.filters['jpfunc'] = html_ify
         template = env.get_template('html.j2')
         outputname = "%s.html"
eb8a1123
         includecmt = ""
         includefmt = ""
2786149b
     if options.type == 'man':
d47e15a1
         env.filters['jpfunc'] = man_ify
         template = env.get_template('man.j2')
85fb7c6d
         outputname = "ansible.%s.3"
eb8a1123
         includecmt = ""
         includefmt = ""
2786149b
     if options.type == 'rst':
d47e15a1
         env.filters['jpfunc'] = rst_ify
83f277cf
         env.filters['html_ify'] = html_ify
d47e15a1
         env.filters['fmt'] = rst_fmt
         env.filters['xline'] = rst_xline
         template = env.get_template('rst.j2')
         outputname = "%s.rst"
eb8a1123
         includecmt = ".. Generated by module_formatter\n"
761330b1
         includefmt = ".. include:: modules/%s.rst\n"
2786149b
     if options.type == 'json':
94de4db9
         env.filters['jpfunc'] = json_ify
         outputname = "%s.json"
eb8a1123
         includecmt = ""
         includefmt = ""
2786149b
     if options.type == 'js':
ee679c01
         env.filters['jpfunc'] = js_ify
         template = env.get_template('js.j2')
         outputname = "%s.js"
d4f89122
     if options.type == 'markdown':
         env.filters['jpfunc'] = markdown_ify
         env.filters['html_ify'] = html_ify
         template = env.get_template('markdown.j2')
         outputname = "%s.md"
         includecmt = ""
         includefmt = ""
eb8a1123
 
2786149b
     if options.includes_file is not None and includefmt != "":
         incfile = open(options.includes_file, "w")
eb8a1123
         incfile.write(includecmt)
d47e15a1
 
ee679c01
     # Temporary variable required to genrate aggregated content in 'js' format.
     js_data = []
391fb98e
 
     categories = list_modules(options.module_dir)
     last_category = None
     category_names = categories.keys()
     category_names.sort()
  
     for category in category_names:
         module_map = categories[category]
  
         category = category.replace("_"," ")
         category = category.title()
 
         modules = module_map.keys()
         modules.sort()
 
         for module in modules:
5f18a535
 
             print "rendering: %s" % module
 
391fb98e
             fname = module_map[module]
 
             if len(options.module_list):
                 if not module in options.module_list:
                     continue
 
             # fname = os.path.join(options.module_dir, module)
 
             extra = os.path.join("inc", "%s.tex" % module)
 
             # probably could just throw out everything with extensions
             if fname.endswith(".swp") or fname.endswith(".orig") or fname.endswith(".rej"):
d47e15a1
                 continue
 
391fb98e
             # print " processing module source ---> %s" % fname
d47e15a1
 
391fb98e
             if options.type == 'js':
                 if fname.endswith(".json"):
                     f = open(fname)
                     j = json.load(f)
                     f.close()
                     js_data.append(j)
                 continue
0c855a85
 
391fb98e
             doc, examples = ansible.utils.module_docs.get_docstring(fname, verbose=options.verbose)
d47e15a1
 
391fb98e
             if doc is None and module not in ansible.utils.module_docs.BLACKLIST_MODULES:
                 print " while processing module source ---> %s" % fname
                 sys.stderr.write("*** ERROR: CORE MODULE MISSING DOCUMENTATION: %s ***\n" % module)
                 #sys.exit(1)
ee679c01
 
391fb98e
             if not doc is None:
  
                 all_keys = []
f7c3975f
 
                 if not 'version_added' in doc:
                     sys.stderr.write("*** ERROR: missing version_added in: %s ***\n" % module)
                     sys.exit(1)
                     if doc['version_added'] == 'historical':
                        del doc['version_added']
 
391fb98e
                 for (k,v) in doc['options'].iteritems():
                     all_keys.append(k)
                 all_keys = sorted(all_keys)
                 doc['option_keys'] = all_keys 
31a4fe41
 
391fb98e
                 doc['filename']         = fname
                 doc['docuri']           = doc['module'].replace('_', '-')
                 doc['now_date']         = datetime.date.today().strftime('%Y-%m-%d')
                 doc['ansible_version']  = options.ansible_version
                 doc['plainexamples']    = examples  #plain text
31a4fe41
 
391fb98e
                 # BOOKMARK: here is where we build the table of contents...
405c097c
 
391fb98e
                 if options.includes_file is not None and includefmt != "":
626203a7
 
391fb98e
                     if last_category != category:
                          incfile.write("\n\n")
                          incfile.write(category)
                          incfile.write("\n")
627b6a04
                          incfile.write('`' * len(category))
391fb98e
                          incfile.write("\n\n")
                          last_category = category
d47e15a1
 
391fb98e
                     incfile.write(includefmt % module)
eb8a1123
 
391fb98e
                 if options.verbose:
                     print json.dumps(doc, indent=4)
d47e15a1
 
 
391fb98e
                 if options.type == 'latex':
                     if os.path.exists(extra):
                         f = open(extra)
                         extradata = f.read()
                         f.close()
                         doc['extradata'] = extradata
d47e15a1
 
391fb98e
                 if options.type == 'json':
                     text = json.dumps(doc, indent=2)
                 else:
                     text = template.render(doc)
94de4db9
 
391fb98e
                 return_data(text, options, outputname, module)
ee679c01
 
391fb98e
         if options.type == 'js':
             docs = {}
             docs['json'] = json.dumps(js_data, indent=2)
             text = template.render(docs)
             return_data(text, options, outputname, 'modules')
d47e15a1
 
 if __name__ == '__main__':
     main()