#!/usr/bin/env python # (c) 2012, Jan-Piet Mens # # 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 . # import os import sys import yaml import json import ast import textwrap import re import optparse import time import datetime from ansible import utils from ansible import errors from ansible.utils import module_docs import ansible.constants as C from ansible.utils import version import traceback MODULEDIR = C.DEFAULT_MODULE_PATH BLACKLIST_EXTS = ('.swp', '.bak', '~', '.rpm') _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 tty_ify(text): t = _ITALIC.sub("`" + r"\1" + "'", text) # I(word) => `word' t = _BOLD.sub("*" + r"\1" + "*", t) # B(word) => *word* t = _MODULE.sub("[" + r"\1" + "]", t) # M(word) => [word] t = _URL.sub(r"\1", t) # U(word) => word t = _CONST.sub("`" + r"\1" + "'", t) # C(word) => `word' return t def print_man(doc): opt_indent=" " print "> %s\n" % doc['module'].upper() desc = "".join(doc['description']) print "%s\n" % textwrap.fill(tty_ify(desc), initial_indent=" ", subsequent_indent=" ") if 'option_keys' in doc and len(doc['option_keys']) > 0: print "Options (= is mandatory):\n" for o in doc['option_keys']: opt = doc['options'][o] if opt.get('required', False): opt_leadin = "=" else: opt_leadin = "-" print "%s %s" % (opt_leadin, o) desc = "".join(opt['description']) if 'choices' in opt: choices = ", ".join(str(i) for i in opt['choices']) desc = desc + " (Choices: " + choices + ")" print "%s\n" % textwrap.fill(tty_ify(desc), initial_indent=opt_indent, subsequent_indent=opt_indent) if 'notes' in doc and len(doc['notes']) > 0: notes = "".join(doc['notes']) print "Notes:%s\n" % textwrap.fill(tty_ify(notes), initial_indent=" ", subsequent_indent=opt_indent) if 'examples' in doc and len(doc['examples']) > 0: print "Example%s:\n" % ('' if len(doc['examples']) < 2 else 's') for ex in doc['examples']: print "%s\n" % (ex['code']) def print_snippet(doc): desc = tty_ify("".join(doc['short_description'])) print "- name: %s" % (desc) print " action: %s" % (doc['module']) for o in doc['options']: opt = doc['options'][o] desc = tty_ify("".join(opt['description'])) s = o + "=" print " %-20s # %s" % (s, desc) def main(): p = optparse.OptionParser( version=version("%prog"), usage='usage: %prog [options] [module...]', description='Show Ansible module documentation', ) p.add_option("-M", "--module-path", action="store", dest="module_path", default=MODULEDIR, help="Ansible modules/ directory") p.add_option("-l", "--list", action="store_true", default=False, dest='list_dir', help='List available modules') p.add_option("-s", "--snippet", action="store_true", default=False, dest='show_snippet', help='Show playbook snippet for specified module(s)') p.add_option('-v', action='version', help='Show version number and exit') (options, args) = p.parse_args() if options.module_path is not None: for i in options.module_path.split(os.pathsep): utils.plugins.module_finder.add_directory(i) if options.list_dir: # list all modules paths = utils.plugins.module_finder._get_paths() module_list = [] for path in paths: # os.system("ls -C %s" % (path)) if os.path.isdir(path): for module in os.listdir(path): if any(module.endswith(x) for x in BLACKLIST_EXTS): continue module_list.append(module) for module in sorted(set(module_list)): if module in module_docs.BLACKLIST_MODULES: continue filename = utils.plugins.module_finder.find_plugin(module) try: doc = module_docs.get_docstring(filename) desc = tty_ify(doc.get('short_description', '?')) if len(desc) > 55: desc = desc + '...' print "%-20s %-60.60s" % (module, desc) except: traceback.print_exc() sys.stderr.write("ERROR: module %s has a documentation error formatting or is missing documentation\n" % module) pass sys.exit() if len(args) == 0: p.print_help() for module in args: filename = utils.plugins.module_finder.find_plugin(module) if filename is None: sys.stderr.write("module %s not found in %s\n" % (module, utils.plugins.module_finder.print_paths())) continue if any(filename.endswith(x) for x in BLACKLIST_EXTS): continue try: doc = module_docs.get_docstring(filename) except: traceback.print_exc() sys.stderr.write("ERROR: module %s has a documentation error formatting or is missing documentation\n" % module) continue if doc is not None: all_keys = [] for (k,v) in doc['options'].iteritems(): all_keys.append(k) all_keys = sorted(all_keys) doc['option_keys'] = all_keys doc['filename'] = filename doc['docuri'] = doc['module'].replace('_', '-') doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d') if options.show_snippet: print_snippet(doc) else: print_man(doc) else: # this typically means we couldn't even parse the docstring, not just that the YAML is busted, # probably a quoting issue. sys.stderr.write("ERROR: module %s missing documentation (or could not parse documentation)\n" % module) if __name__ == '__main__': main()