Merge pull request #9451 from bcoca/load_aliases

Load aliases
This commit is contained in:
Brian Coca 2014-11-04 10:24:11 -05:00
commit afd8cca345
5 changed files with 212 additions and 50 deletions

View file

@ -34,6 +34,7 @@ import traceback
MODULEDIR = C.DEFAULT_MODULE_PATH
BLACKLIST_EXTS = ('.pyc', '.swp', '.bak', '~', '.rpm')
IGNORE_FILES = [ "COPYING", "CONTRIBUTING", "LICENSE", "README" ]
_ITALIC = re.compile(r"I\(([^)]+)\)")
_BOLD = re.compile(r"B\(([^)]+)\)")
@ -94,7 +95,7 @@ def get_man_text(doc):
desc = " ".join(doc['description'])
text.append("%s\n" % textwrap.fill(tty_ify(desc), initial_indent=" ", subsequent_indent=" "))
if 'option_keys' in doc and len(doc['option_keys']) > 0:
text.append("Options (= is mandatory):\n")
@ -164,7 +165,11 @@ def get_snippet_text(doc):
return "\n".join(text)
def get_module_list_text(module_list):
columns = max(60, int(os.popen('stty size', 'r').read().split()[1]))
displace = max(len(x) for x in module_list)
linelimit = columns - displace - 5
text = []
deprecated = []
for module in sorted(set(module_list)):
if module in module_docs.BLACKLIST_MODULES:
@ -181,15 +186,45 @@ def get_module_list_text(module_list):
try:
doc, plainexamples = module_docs.get_docstring(filename)
desc = tty_ify(doc.get('short_description', '?'))
if len(desc) > 55:
desc = desc + '...'
text.append("%-20s %-60.60s" % (module, desc))
desc = tty_ify(doc.get('short_description', '?')).strip()
if len(desc) > linelimit:
desc = desc[:linelimit] + '...'
if module.startswith('_'): # Handle deprecated
deprecated.append("%-*s %-*.*s" % (displace, module[1:], linelimit, len(desc), desc))
else:
text.append("%-*s %-*.*s" % (displace, module, linelimit, len(desc), desc))
except:
traceback.print_exc()
sys.stderr.write("ERROR: module %s has a documentation error formatting or is missing documentation\n" % module)
if len(deprecated) > 0:
text.append("\nDEPRECATED:")
text.extend(deprecated)
return "\n".join(text)
def find_modules(path, module_list):
if os.path.isdir(path):
for module in os.listdir(path):
if module.startswith('.'):
continue
elif os.path.isdir(module):
find_modules(module, module_list)
elif any(module.endswith(x) for x in BLACKLIST_EXTS):
continue
elif module.startswith('__'):
continue
elif module in IGNORE_FILES:
continue
elif module.startswith('_'):
fullpath = '/'.join([path,module])
if os.path.islink(fullpath): # avoids aliases
continue
module = os.path.splitext(module)[0] # removes the extension
module_list.append(module)
def main():
p = optparse.OptionParser(
@ -222,23 +257,18 @@ def main():
utils.plugins.module_finder.add_directory(i)
if options.list_dir:
# list all modules
# list 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)
find_modules(path, module_list)
pager(get_module_list_text(module_list))
sys.exit()
if len(args) == 0:
p.print_help()
def print_paths(finder):
''' Returns a string suitable for printing of the search path '''
@ -248,14 +278,13 @@ def main():
if i not in ret:
ret.append(i)
return os.pathsep.join(ret)
text = ''
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,
print_paths(utils.plugins.module_finder)))
sys.stderr.write("module %s not found in %s\n" % (module, print_paths(utils.plugins.module_finder)))
continue
if any(filename.endswith(x) for x in BLACKLIST_EXTS):

View file

@ -465,6 +465,23 @@ a github pull request to the `extras <https://github.com/ansible/ansible-modules
Included modules will ship with ansible, and also have a change to be promoted to 'core' status, which
gives them slightly higher development priority (though they'll work in exactly the same way).
Deprecating and making module aliases
``````````````````````````````````````
Starting in 1.8 you can deprecate modules by renaming them with a preceeding _, i.e. old_cloud.py to
_old_cloud.py, This will keep the module available but hide it from the primary docs and listing.
You can also rename modules and keep an alias to the old name by using a symlink that starts with _.
This example allows the stat module to be called with fileinfo, making the following examples equivalent
EXAMPLES = '''
ln -s stat.py _fileinfo.py
ansible -m stat -a "path=/tmp" localhost
ansible -m fileinfo -a "path=/tmp" localhost
'''
.. seealso::
:doc:`modules`

View file

@ -59,6 +59,8 @@ _MODULE = re.compile(r"M\(([^)]+)\)")
_URL = re.compile(r"U\(([^)]+)\)")
_CONST = re.compile(r"C\(([^)]+)\)")
DEPRECATED = " (D)"
NOTCORE = " (E)"
#####################################################################################
def rst_ify(text):
@ -118,28 +120,53 @@ def write_data(text, options, outputname, module):
#####################################################################################
def list_modules(module_dir):
def list_modules(module_dir, depth=0):
''' returns a hash of categories, each category being a hash of module names to file paths '''
categories = dict(all=dict())
files = glob.glob("%s/*/*" % module_dir)
for d in files:
if os.path.isdir(d):
files2 = glob.glob("%s/*" % d)
for f in files2:
categories = dict(all=dict(),_aliases=dict())
if depth <= 3: # limit # of subdirs
if not f.endswith(".py") or f.endswith('__init__.py'):
files = glob.glob("%s/*" % module_dir)
for d in files:
category = os.path.splitext(os.path.basename(d))[0]
if os.path.isdir(d):
res = list_modules(d, depth + 1)
for key in res.keys():
if key in categories:
categories[key].update(res[key])
res.pop(key, None)
if depth < 2:
categories.update(res)
else:
category = module_dir.split("/")[-1]
if not category in categories:
categories[category] = res
else:
categories[category].update(res)
else:
module = category
category = os.path.basename(module_dir)
if not d.endswith(".py") or d.endswith('__init__.py'):
# windows powershell modules have documentation stubs in python docstring
# format (they are not executed) so skip the ps1 format files
continue
elif module.startswith("_") and os.path.islink(d):
source = os.path.splitext(os.path.basename(os.path.realpath(d)))[0]
module = module.replace("_","",1)
if not d in categories['_aliases']:
categories['_aliases'][source] = [module]
else:
categories['_aliases'][source].update(module)
continue
tokens = f.split("/")
module = tokens[-1].replace(".py","")
category = tokens[-2]
if not category in categories:
categories[category] = {}
categories[category][module] = f
categories['all'][module] = f
categories[category][module] = d
categories['all'][module] = d
return categories
#####################################################################################
@ -188,33 +215,48 @@ def jinja2_environment(template_dir, typ):
#####################################################################################
def process_module(module, options, env, template, outputname, module_map):
print "rendering: %s" % module
def process_module(module, options, env, template, outputname, module_map, aliases):
fname = module_map[module]
if isinstance(fname, dict):
return "SKIPPED"
basename = os.path.basename(fname)
deprecated = False
# ignore files with extensions
if not os.path.basename(fname).endswith(".py"):
if not basename.endswith(".py"):
return
elif module.startswith("_"):
if os.path.islink(fname):
return # ignore, its an alias
deprecated = True
module = module.replace("_","",1)
print "rendering: %s" % module
# use ansible core library to parse out doc metadata YAML and plaintext examples
doc, examples = ansible.utils.module_docs.get_docstring(fname, verbose=options.verbose)
# crash if module is missing documentation and not explicitly hidden from docs index
if doc is None and module not in ansible.utils.module_docs.BLACKLIST_MODULES:
sys.stderr.write("*** ERROR: CORE MODULE MISSING DOCUMENTATION: %s, %s ***\n" % (fname, module))
sys.exit(1)
if doc is None:
return "SKIPPED"
if module in ansible.utils.module_docs.BLACKLIST_MODULES:
return "SKIPPED"
else:
sys.stderr.write("*** ERROR: MODULE MISSING DOCUMENTATION: %s, %s ***\n" % (fname, module))
sys.exit(1)
if deprecated and 'deprecated' not in doc:
sys.stderr.write("*** ERROR: DEPRECATED MODULE MISSING 'deprecated' DOCUMENTATION: %s, %s ***\n" % (fname, module))
sys.exit(1)
if "/core/" in fname:
doc['core'] = True
else:
doc['core'] = False
if module in aliases:
doc['aliases'] = aliases[module]
all_keys = []
@ -238,9 +280,10 @@ def process_module(module, options, env, template, outputname, module_map):
for (k,v) in doc['options'].iteritems():
all_keys.append(k)
all_keys = sorted(all_keys)
doc['option_keys'] = all_keys
all_keys = sorted(all_keys)
doc['option_keys'] = all_keys
doc['filename'] = fname
doc['docuri'] = doc['module'].replace('_', '-')
doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d')
@ -251,13 +294,32 @@ def process_module(module, options, env, template, outputname, module_map):
text = template.render(doc)
write_data(text, options, outputname, module)
return doc['short_description']
#####################################################################################
def print_modules(module, category_file, deprecated, core, options, env, template, outputname, module_map, aliases):
modstring = module
modname = module
if module in deprecated:
modstring = modstring + DEPRECATED
modname = "_" + module
elif module not in core:
modstring = modstring + NOTCORE
result = process_module(modname, options, env, template, outputname, module_map, aliases)
if result != "SKIPPED":
category_file.write(" %s - %s <%s_module>\n" % (modstring, result, module))
def process_category(category, categories, options, env, template, outputname):
module_map = categories[category]
aliases = {}
if '_aliases' in categories:
aliases = categories['_aliases']
category_file_path = os.path.join(options.output_dir, "list_of_%s_modules.rst" % category)
category_file = open(category_file_path, "w")
print "*** recording category %s in %s ***" % (category, category_file_path)
@ -267,7 +329,27 @@ def process_category(category, categories, options, env, template, outputname):
category = category.replace("_"," ")
category = category.title()
modules = module_map.keys()
modules = []
deprecated = []
core = []
for module in module_map.keys():
if isinstance(module_map[module], dict):
for mod in module_map[module].keys():
if mod.startswith("_"):
mod = mod.replace("_","",1)
deprecated.append(mod)
elif '/core/' in module_map[module][mod]:
core.append(mod)
else:
if module.startswith("_"):
module = module.replace("_","",1)
deprecated.append(module)
elif '/core/' in module_map[module]:
core.append(module)
modules.append(module)
modules.sort()
category_header = "%s Modules" % (category.title())
@ -277,17 +359,33 @@ def process_category(category, categories, options, env, template, outputname):
%s
%s
.. toctree::
:maxdepth: 1
.. toctree:: :maxdepth: 1
""" % (category_header, underscores))
sections = []
for module in modules:
result = process_module(module, options, env, template, outputname, module_map)
if result != "SKIPPED":
category_file.write(" %s_module\n" % module)
if module in module_map and isinstance(module_map[module], dict):
sections.append(module)
continue
else:
print_modules(module, category_file, deprecated, core, options, env, template, outputname, module_map, aliases)
sections.sort()
for section in sections:
category_file.write("%s\n%s\n\n" % (section.replace("_"," ").title(),'-' * len(section)))
category_file.write(".. toctree:: :maxdepth: 1\n\n")
section_modules = module_map[section].keys()
section_modules.sort()
#for module in module_map[section]:
for module in section_modules:
print_modules(module, category_file, deprecated, core, options, env, template, outputname, module_map[section], aliases)
category_file.write("""\n\n
.. note::
- %s: This marks a module as deprecated, kept for backwards compatibility but use is discouraged
- %s: Denotes that this module is not part of core, it can be found in the extras or some other external repo
""" % (DEPRECATED, NOTCORE))
category_file.close()
# TODO: end a new category file
@ -332,6 +430,8 @@ def main():
category_list_file.write(" :maxdepth: 1\n\n")
for category in category_names:
if category.startswith("_"):
continue
category_list_file.write(" list_of_%s_modules\n" % category)
process_category(category, categories, options, env, template, outputname)

View file

@ -21,6 +21,17 @@
#
--------------------------------------------#}
{% if aliases is defined -%}
Aliases: @{ ','.join(aliases) }@
{% endif %}
{% if deprecated is defined -%}
DEPRECATED
----------
@{ deprecated }@
{% endif %}
Synopsis
--------
@ -102,7 +113,8 @@ Examples
{% endif %}
{% if core %}
{% if not deprecated %}
{% if core %}
This is a Core Module
---------------------
@ -117,7 +129,7 @@ Documentation updates for this module can also be edited directly by submitting
This is a "core" ansible module, which means it will receive slightly higher priority for all requests than those in the "extras" repos.
{% else %}
{% else %}
This is an Extras Module
------------------------
@ -133,6 +145,7 @@ Documentation updates for this module can also be edited directly by submitting
Note that this module is designated a "extras" module. Non-core modules are still fully usable, but may receive slightly lower response rates for issues and pull requests.
Popular "extras" modules may be promoted to core modules over time.
{% endif %}
{% endif %}
For help in developing on modules, should you be so inclined, please read :doc:`community`, :doc:`developing_test_pr` and :doc:`developing_modules`.

View file

@ -178,6 +178,9 @@ class PluginLoader(object):
self._plugin_path_cache[full_name] = path
return path
if not name.startswith('_'):
return self.find_plugin('_' + name, suffixes, transport)
return None
def has_plugin(self, name):