Source code for lexor.command.document

"""Document

Routine to create an xml file with the documentation of a lexor
style.

"""
#pylint: disable=W0142

import os
import sys
import textwrap
import inspect
import lexor
from imp import load_source
from lexor.core import elements as core
from lexor.command import config, error, warn
from lexor.command.to import style_parameters


[docs]def xml_style(lang_str): """Parses a style string. """ input_style = style_parameters(lang_str) if input_style['name'] == '': input_style['name'] = '_' return input_style
DEFAULTS = { 'path': '.', } DESC = """ Generate an xml file with the documentation of a python file. In the case of a lexor style, the xml generated contains a special format. """
[docs]def add_parser(subp, fclass): """Add a parser to the main subparser. """ tmpp = subp.add_parser('document', help='document a style', formatter_class=fclass, description=textwrap.dedent(DESC)) tmpp.add_argument('style', type=xml_style, nargs='?', help='an xml style') tmpp.add_argument('--path', type=str, help='documentation directory')
[docs]def get_info_node(info): """Generate the info node. """ node_info = core.Element('info') for key in info: if info[key] is None or key == 'path': continue node = core.Element('entry') node['key'] = key if key == 'description': node.append_child(core.CData(str(info[key]))) else: node.append_child(core.Text(str(info[key]))) node_info.append_child(node) return node_info
[docs]def get_defaults_node(obj): """Obtain defaults node. """ defaults_node = core.Element('defaults') for ele in obj: node = core.Element('entry') node['key'] = ele node.append_child(core.CData(obj[ele])) defaults_node.append_child(node) return defaults_node
[docs]def get_mapping_node(mapping): """Generate the mapping node. """ modules = dict() mapping_node = core.Element('mapping') keys = mapping.keys() keys.sort() for ele in keys: if isinstance(mapping[ele], tuple): entry = core.Element('entry') entry['key'] = ele entry.append_child( core.Element('checker').append_child( core.CData(mapping[ele][0]) ) ) for mod in mapping[ele][1]: node = core.Element('processor') mod_name = mod.__module__ node['module'] = mod_name node['name'] = mod.__name__ mapping_node.append_child(node) if mod_name not in modules: modules[mod_name] = sys.modules[mod_name] entry.append_child(node) mapping_node.append_child(entry) else: node = core.Element('entry') node['key'] = ele mod_name = mapping[ele].__module__ node['module'] = mod_name node['name'] = mapping[ele].__name__ if mod_name not in modules: modules[mod_name] = sys.modules[mod_name] mapping_node.append_child(node) return modules, mapping_node
[docs]def separate_objects(mod, remove=None): """Given a module, it separates the objects into a dictionary. """ info = { 'class': [], 'function': [], 'module': [], 'data': [], 'other': [], } mod_dict = dict(mod.__dict__) if remove is None: remove = list() for ele in mod_dict: if ele in remove or ele[0] == '_': continue if inspect.isfunction(mod_dict[ele]): if mod_dict[ele].__module__ == mod.__name__: info['function'].append(mod_dict[ele]) else: info['other'].append([ele, mod_dict[ele]]) elif inspect.isclass(mod_dict[ele]): if mod_dict[ele].__module__ == mod.__name__: info['class'].append(mod_dict[ele]) else: info['other'].append([ele, mod_dict[ele]]) elif inspect.ismodule(mod_dict[ele]): info['module'].append([ele, mod_dict[ele]]) elif inspect.isbuiltin(mod_dict[ele]): info['other'].append([ele, mod_dict[ele]]) else: info['data'].append([ele, mod_dict[ele]]) return info
[docs]def get_function_node(func): """Return a function node. """ node = core.Element('function') node['name'] = func.__name__ argspec = inspect.getargspec(func) node_argspec = core.Element('argspec') node_argspec['varargs'] = str(argspec[1]) node_argspec['keywords'] = str(argspec[2]) node_args = core.Element('args') for item in argspec[0]: node_args.append_child(core.Void('arg', {'name': item})) if argspec[3] is not None: largs = len(argspec[0]) ldefs = len(argspec[3]) num = 0 for index in xrange(largs-ldefs, largs): node_args[index]['default'] = argspec[3][num] num += 1 node_argspec.append_child(node_args) node.append_child(node_argspec) doc = inspect.getdoc(func) if doc is not None: node.append_child( core.Element('doc').append_child( core.CData(doc) ) ) #return core.Text(func.__name__) return node
[docs]def get_property_node(prop): """Return a property node. """ node = core.Element('property') node['name'] = prop[0] doc = inspect.getdoc(prop[1]) if doc is not None: node.append_child( core.Element('doc').append_child( core.CData(doc) ) ) return node
[docs]def get_member_node(member): """Return a property node. """ node = core.Element('member') node['name'] = member.__name__ return node
[docs]def full_class_name(cls): """Obtain the full class name. """ if 'tmp' in cls.__module__: return cls.__name__ return cls.__module__ + "." + cls.__name__
def _update_class_tree(tree, cls, mro): """Helper function for get_class_node. It updates the tree. """ for name, val in inspect.getmembers(cls): for mod in mro: if name in mod.__dict__: if inspect.ismethod(val): if val.im_self is None: tree[mod]['method'].append(val) else: tree[mod]['cls_method'].append(val) elif inspect.isdatadescriptor(val): if val.__class__.__name__ == 'property': tree[mod]['property'].append([name, val]) else: tree[mod]['member'].append(val) def _update_node(node, tree, mro): """Helper function for get_class_node. Updates the node. """ for index in xrange(len(mro)): tmp = core.Element('cls_method_block') tmp['from'] = full_class_name(mro[index]) for func in tree[mro[index]]['cls_method']: tmp.append_child(get_function_node(func)) if len(tmp) > 0: node.append_child(tmp) tmp = core.Element('method_block') tmp['from'] = full_class_name(mro[index]) for func in tree[mro[index]]['method']: tmp.append_child(get_function_node(func)) if len(tmp) > 0: node.append_child(tmp) tmp = core.Element('property_block') tmp['from'] = full_class_name(mro[index]) for prop in tree[mro[index]]['property']: tmp.append_child(get_property_node(prop)) if len(tmp) > 0: node.append_child(tmp) tmp = core.Element('member_block') tmp['from'] = full_class_name(mro[index]) for member in tree[mro[index]]['member']: tmp.append_child(get_member_node(member)) if len(tmp) > 0: node.append_child(tmp)
[docs]def get_class_node(cls): """Return a class node. Assumes that cls is a valid class. """ node = core.Element('class') node['name'] = cls.__name__ tmp = core.Element('bases') for base in cls.__bases__: tmp.append_child( core.Void('base', {'name': full_class_name(base)}) ) node.append_child(tmp) mro = inspect.getmro(cls) tmp = core.Element('mro') for base in mro: tmp.append_child( core.Void('class', {'name': full_class_name(base)}) ) node.append_child(tmp) doc = inspect.getdoc(cls) if doc is not None: node.append_child( core.Element('doc').append_child( core.CData(doc) ) ) tree = dict() for mod in mro: tree[mod] = { 'cls_method': [], 'method': [], 'property': [], 'member': [], } _update_class_tree(tree, cls, mro) _update_node(node, tree, mro) return node
[docs]def append_main(doc, mod): """Append the main module. Return a dictionary containing all the modules used in the style. """ module = core.Element('module') module['name'] = 'main' module.append_child( core.Element('doc').append_child( core.CData(str(mod.__doc__)) ) ) module.append_child(get_info_node(mod.INFO)) if hasattr(mod, 'DEFAULTS'): module.append_child(get_defaults_node(mod.DEFAULTS)) modules, m_node = get_mapping_node(mod.MAPPING) module.append_child(m_node) info = separate_objects(mod, ['INFO', 'DEFAULTS', 'MAPPING', 'DESCRIPTION']) node = core.Element('classes') for cls in info['class']: node.append_child(get_class_node(cls)) module.append_child(node) node = core.Element('functions') for func in info['function']: node.append_child(get_function_node(func)) module.append_child(node) node = core.Element('data') for ele in info['data']: if ele[0][0] == '_': continue tmp = core.Element('data', {'name': ele[0]}) tmp.append_child(core.CData(repr(ele[1]))) node.append_child(tmp) if len(node) > 0: module.append_child(node) node = core.Element('imports') for ele in info['module']: node.append_child( core.Void('module', {'name': ele[0], 'fullname': ele[1].__name__}) ) for ele in info['other']: node.append_child( core.Void('other', {'name': ele[0], 'fullname': full_class_name(ele[1]), 'type': ele[1].__class__.__name__}) ) module.append_child(node) doc.append_child(module) return modules
[docs]def make_module_node(mod, name=None): """Create a module node documenation. """ module = core.Element('module') if name is None: name = mod.__name__ module['name'] = name module.append_child( core.Element('doc').append_child( core.CData(str(mod.__doc__)) ) ) info = separate_objects(mod) node = core.Element('classes') for cls in info['class']: node.append_child(get_class_node(cls)) module.append_child(node) node = core.Element('functions') for func in info['function']: node.append_child(get_function_node(func)) module.append_child(node) node = core.Element('data') for ele in info['data']: if ele[0][0] == '_': continue tmp = core.Element('data', {'name': ele[0]}) tmp.append_child(core.CData(repr(ele[1]))) node.append_child(tmp) if len(node) > 0: module.append_child(node) node = core.Element('imports') for ele in info['module']: node.append_child( core.Void('module', {'name': ele[0], 'fullname': ele[1].__name__}) ) for ele in info['other']: node.append_child( core.Void('other', {'name': ele[0], 'fullname': full_class_name(ele[1]), 'type': ele[1].__class__.__name__}) ) module.append_child(node) return module
[docs]def check_filename(arg): """Check if the inputfile exists. """ cfg = config.get_cfg('document', DEFAULTS) root = cfg['lexor']['root'] path = cfg['document']['path'] fname = arg.inputfile if path[0] in ['/', '.']: dirpath = path else: dirpath = '%s/%s' % (root, path) if '.py' not in fname: fname = '%s.py' % fname if not os.path.exists(fname): error("ERROR: No such file or directory.\n") return dirpath, fname
[docs]def run(): """Run the command. """ arg = config.CONFIG['arg'] dirpath, fname = check_filename(arg) moddir = os.path.splitext(fname)[0] base, _ = os.path.split(moddir) if base == '': base = '.' mod = load_source('tmp-module', fname) doc = core.Document() if not hasattr(mod, 'INFO') or 'lang' not in mod.INFO: filename = fname[:-3] + '.xml' doc.append_child(make_module_node(mod, "main")) else: info = mod.INFO if info['to_lang']: filename = '%s/lexor.%s.%s.%s.%s-%s.xml' filename = filename % (dirpath, info['lang'], info['type'], info['to_lang'], info['style'], info['ver']) else: filename = '%s/lexor.%s.%s.%s-%s.xml' filename = filename % (dirpath, info['lang'], info['type'], info['style'], info['ver']) modules = append_main(doc, mod) for mod_name in modules: doc.append_child(make_module_node(modules[mod_name])) warn('Writing %s ... ' % filename) try: if arg.style: doc.style = arg.style['name'] doc.defaults = arg.style['params'] lexor.write(doc, filename) except IOError: error("\nERROR: unable to write file.\n" "xml writer default style missing?\n") else: warn('done\n')