From 259e65828cdc1596eefa750d3b4c757ec0fc5f5d Mon Sep 17 00:00:00 2001 From: Zhanna Tsitkov Date: Thu, 23 Jun 2011 19:03:34 +0000 Subject: [PATCH] doxy.py is a translator from Doxygen xml output into the restructuredText format. The generated output may be used in Sphinx documentation project for the complete API and data type reference. git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@24983 dc483132-0cff-0310-8789-dd5450dbe970 --- doc/rst_tools/docmodel.py | 222 ++++++++++++ doc/rst_tools/doxy.py | 64 ++++ doc/rst_tools/doxybuilder_funcs.py | 565 +++++++++++++++++++++++++++++ doc/rst_tools/doxybuilder_types.py | 286 +++++++++++++++ doc/rst_tools/func_document.tmpl | 105 ++++++ doc/rst_tools/type_document.tmpl | 62 ++++ 6 files changed, 1304 insertions(+) create mode 100644 doc/rst_tools/docmodel.py create mode 100644 doc/rst_tools/doxy.py create mode 100644 doc/rst_tools/doxybuilder_funcs.py create mode 100644 doc/rst_tools/doxybuilder_types.py create mode 100644 doc/rst_tools/func_document.tmpl create mode 100644 doc/rst_tools/type_document.tmpl diff --git a/doc/rst_tools/docmodel.py b/doc/rst_tools/docmodel.py new file mode 100644 index 000000000..6db5b1db7 --- /dev/null +++ b/doc/rst_tools/docmodel.py @@ -0,0 +1,222 @@ +''' +Created on Nov 9, 2010 + +@author: tsitkova +''' +import re + +from Cheetah.Template import Template + +class Attribute(object): + def __init__(self, **argkw): + self.name = argkw.get('name') + self.type = argkw.get('type') + self.typeId = argkw.get('typeId') + self.short_description = argkw.get('short_description') + self.long_description = argkw.get('long_description') + self.version = argkw.get('version') + + def __repr__(self): + result = list() + for (attr,value) in self.__dict__.iteritems(): + result.append('%s=%s' % (attr,value)) + return 'Attribute: %s' % ','.join(result) + + +class CompositeType(): + def __init__(self, **argkw): + self.category = 'composite' + self.name = argkw.get('name') + self.Id = argkw.get('Id') + self.active = argkw.get('active', False) + self.version = argkw.get('version') + self.return_type = argkw.get('return_type') + self.short_description = argkw.get('short_description') + self.long_description = argkw.get('long_description') + self.friends = argkw.get('friends') + self.type = argkw.get('type') + self.attributes = self._setAttributes(argkw.get('attributes')) + + def __repr__(self): + result = list() + for (attr,value) in self.__dict__.iteritems(): + if attr == 'attributes': + if value is not None: + attributes = ['%s' % a for a in value] + value = '\n %s' % '\n '.join(attributes) + + result.append('%s: %s' % (attr,value)) + result = '\n'.join(result) + + return result + + def _setAttributes(self, attributes): + result = None + if attributes is not None: + result = list() + for a in attributes: + result.append(Attribute(**a)) + + return result + + def struct_reference(self, name): + result = re.sub(r'_', '-', name) + result = '_%s-struct' % result + + return result + +class Parameter(object): + def __init__(self, **argkw): + self.seqno = argkw.get('seqno') + self.name = argkw.get('name') + self.direction = argkw.get('direction') + self.type = argkw.get('type') + self.typeId = argkw.get('typeId') + self.description = argkw.get('description') + self.version = argkw.get('version') + + def __repr__(self): + content = (self.name,self.direction,self.seqno,self.type,self.typeId,self.description) + return 'Parameter: name=%s,direction=%s,seqno=%s,type=%s,typeId=%s,descr=%s' % content + +class Function(object): + def __init__(self, **argkw): + self.category = 'function' + self.name = argkw.get('name') + self.Id = argkw.get('Id') + self.active = argkw.get('active', False) + self.version = argkw.get('version') + self.parameters = self._setParameters(argkw.get('parameters')) + self.return_type = argkw.get('return_type') + self.return_description = argkw.get('return_description') + self.retval_description = argkw.get('retval_description') + self.warn_description = argkw.get('warn_description') + self.sa_description = argkw.get('sa_description') + self.notes_description = argkw.get('notes_description') + self.short_description = argkw.get('short_description') + self.long_description = argkw.get('long_description') + self.deprecated_description = argkw.get('deprecated_description') + self.friends = argkw.get('friends') + + def _setParameters(self, parameters): + result = None + if parameters is not None: + result = list() + for p in parameters: + result.append(Parameter(**p)) + + return result + + def getObjectRow(self): + result = [str(self.Id), + self.name, + self.category] + + return ','.join(result) + + def getObjectDescriptionRow(self): + result = [self.Id, + self.active, + self.version, + self.short_description, + self.long_description] + + return ','.join(result) + + def getParameterRows(self): + result = list() + for p in self.parameters: + p_row = [self.Id, + p.name, + p.seqno, + p.type, + p.typeId, + p.description, + p.version] + result.append(','.join(p_row)) + + return '\n'.join(result) + + def __repr__(self): + lines = list() + lines.append('Category: %s' % self.category) + lines.append('Function name: %s' % self.name) + lines.append('Function Id: %s' % self.Id) + parameters = [' %s' % p for p in self.parameters] + lines.append('Parameters:\n%s' % '\n'.join(parameters)) + lines.append('Function return type: %s' % self.return_type) + lines.append('Function return type description:\n%s' % self.return_description) + lines.append('Function retval description:\n%s' % self.retval_description) + lines.append('Function short description:\n%s' % self.short_description) + lines.append('Function long description:\n%s' % self.long_description) + lines.append('Warning description:\n%s' % self.warn_description) + lines.append('See also description:\n%s' % self.sa_description) + lines.append('NOTE description:\n%s' % self.notes_description) + lines.append('Deprecated description:\n%s' % self.deprecated_description) + result = '\n'.join(lines) + + return result + + +class DocModel(object): + def __init__(self, **argkw): + if len(argkw): + self.name = argkw['name'] + if argkw['category'] == 'function': + self.category = 'function' + self.function = Function(**argkw) + elif argkw['category'] == 'composite': + self.category = 'composite' + self.composite = CompositeType(**argkw) + + def __repr__(self): + obj = getattr(self,self.category) + print type(obj) + return str(obj) + + def signature(self): + param_list = list() + for p in self.function.parameters: + if p.type is "... " : + param_list.append('%s %s' % (p.type,' ')) + else: + param_list.append('%s %s' % (p.type, p.name)) + param_list = ', '.join(param_list) + result = '%s %s(%s)' % (self.function.return_type, + self.function.name, param_list) + + return result + + def save(self, path, template_path): + f = open(template_path, 'r') + t = Template(f.read(),self) + out = open(path, 'w') + out.write(str(t)) + out.close() + f.close() + + +class DocModelTest(DocModel): + def __init__(self): + doc_path = '../docutil/example.yml' + argkw = yaml.load(open(doc_path,'r')) + super(DocModelTest,self).__init__(**argkw) + + def run_tests(self): + self.test_save() + + def test_print(self): + print 'testing' + print self + + + def test_save(self): + template_path = '../docutil/function2edit.html' + + path = '/var/tsitkova/Sources/v10/trunk/documentation/test_doc.html' + + self.save(path, template_path) + +if __name__ == '__main__': + tester = DocModelTest() + tester.run_tests() diff --git a/doc/rst_tools/doxy.py b/doc/rst_tools/doxy.py new file mode 100644 index 000000000..d343a49cb --- /dev/null +++ b/doc/rst_tools/doxy.py @@ -0,0 +1,64 @@ +''' + Copyright 2011 by the Massachusetts + Institute of Technology. All Rights Reserved. + + Export of this software from the United States of America may + require a specific license from the United States Government. + It is the responsibility of any person or organization contemplating + export to obtain such a license before exporting. + + WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + distribute this software and its documentation for any purpose and + without fee is hereby granted, provided that the above copyright + notice appear in all copies and that both that copyright notice and + this permission notice appear in supporting documentation, and that + the name of M.I.T. not be used in advertising or publicity pertaining + to distribution of the software without specific, written prior + permission. Furthermore if you modify this software you must label + your software as modified software and not distribute it in such a + fashion that it might be confused with the original M.I.T. software. + M.I.T. makes no representations about the suitability of + this software for any purpose. It is provided "as is" without express + or implied warranty. +''' +import sys +import os +import re +from optparse import OptionParser + + +from doxybuilder_types import * +from doxybuilder_funcs import * + + +def processOptions(): + usage = "\n\t\t%prog -t type -i in_dir -o out_dir" + description = "Description:\n\tProcess doxygen output for c-types and/or functions" + parser = OptionParser(usage=usage, description=description) + + parser.add_option("-t", "--type", type="string", dest="action_type", help="process typedef and/or function. Possible choices: typedef, func, all. Default: all.", default="all") + parser.add_option("-i", "--in", type="string", dest="in_dir", help="input directory") + parser.add_option("-o", "--out", type="string", dest= "out_dir", help="output directory. Note: The subdirectory ./types will be created for typedef") + + (options, args) = parser.parse_args() + action = options.action_type + in_dir = options.in_dir + out_dir = options.out_dir + + + if in_dir is None or out_dir is None: + parser.error("Input and output directories are required") + + if action == "all" or action == "typedef": + tester = DoxyTypesTest(in_dir, out_dir) + tester.run_tests() + + if action == "all" or action == "func" or action == "function": + tester = DoxyFuncsTest(in_dir, out_dir) + tester.run_tests() + + +if __name__ == '__main__': + parser = processOptions() + + diff --git a/doc/rst_tools/doxybuilder_funcs.py b/doc/rst_tools/doxybuilder_funcs.py new file mode 100644 index 000000000..3f0b9c00c --- /dev/null +++ b/doc/rst_tools/doxybuilder_funcs.py @@ -0,0 +1,565 @@ +''' + Copyright 2011 by the Massachusetts + Institute of Technology. All Rights Reserved. + + Export of this software from the United States of America may + require a specific license from the United States Government. + It is the responsibility of any person or organization contemplating + export to obtain such a license before exporting. + + WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + distribute this software and its documentation for any purpose and + without fee is hereby granted, provided that the above copyright + notice appear in all copies and that both that copyright notice and + this permission notice appear in supporting documentation, and that + the name of M.I.T. not be used in advertising or publicity pertaining + to distribution of the software without specific, written prior + permission. Furthermore if you modify this software you must label + your software as modified software and not distribute it in such a + fashion that it might be confused with the original M.I.T. software. + M.I.T. makes no representations about the suitability of + this software for any purpose. It is provided "as is" without express + or implied warranty. +''' +import sys +import re + +from collections import defaultdict +from xml.sax import make_parser +from xml.sax.handler import ContentHandler +from docmodel import * + +class DocNode(object): + """ + Represents the structure of xml node. + """ + def __init__(self, name): + """ + @param node: name - the name of a node. + @param attributes: a dictionary populated with attributes of a node + @param children: a dictionary with lists of children nodes. Nodes + in lists are ordered as they appear in a document. + @param content: a content of xml node represented as a list of + tuples [(type,value)] with type = ['char'|'element']. + If type is 'char' then the value is a character string otherwise + it is a reference to a child node. + """ + self.name = name + self.content = list() + self.attributes = dict() + self.children = defaultdict(list) + + def walk(self, decorators, sub_ws, stack=[]): + result = list() + decorator = decorators.get(self.name, decorators['default']) + stack.append(decorators['default']) + decorators['default'] = decorator + + for (obj_type,obj) in self.content: + if obj_type == 'char': + if obj != '': + result.append(obj) + else: + partial = obj.walk(decorators,1, stack) + if partial is not None: + result.append(' %s ' % partial) + decorators['default'] = stack.pop() + result = decorator(self, ''.join(result)) + if result is not None: + if sub_ws == 1: + result = re.sub(r'[ ]+', r' ', result) + else: + result = result.strip() + + return result + + def getContent(self): + decorators = {'default': lambda node,value: value} + result = self.walk(decorators, 1) + if len(result) == 0: + result = None + + return result + + def __repr__(self): + result = ['Content: %s' % self.content] + + for (key,value) in self.attributes.iteritems(): + result.append('Attr: %s = %s' % (key,value)) + for (key,value) in self.children.iteritems(): + result.append('Child: %s,%i' % (key,len(value))) + + return '\n'.join(result) + +class DoxyContenHandler(object, ContentHandler): + def __init__(self, builder): + self.builder = builder + self.counters = defaultdict(int) + self._nodes = None + self._current = None + + def startDocument(self): + pass + + def endDocument(self): + import sys + + def startElement(self, name, attrs): + if name == self.builder.toplevel: + self._nodes = [] + + if name == 'memberdef': + kind = attrs.get('kind') + if kind is None: + raise ValueError('Kind is not defined') + self.counters[kind] += 1 + + if self._nodes is None: + return + + node = DocNode(name) + for (key,value) in attrs.items(): + node.attributes[key] = value + if self._current is not None: + self._current.children[name].append(node) + self._nodes.append(self._current) + self._current = node + + def characters(self, content): + + if self._current is not None: + self._current.content.append(('char',content.strip())) + + def endElement(self, name): + if name == self.builder.toplevel: + assert(len(self._nodes) == 0) + self._nodes = None + self.builder.document.append(self._current) + self._current = None + else: + if self._nodes is not None: + node = self._current + self._current = self._nodes.pop() + self._current.content.append(('element',node)) + + +class XML2AST(object): + """ + Translates XML document into Abstract Syntax Tree like representation + The content of document is stored in self.document + """ + def __init__(self, xmlpath, toplevel='doxygen'): + self.document = list() + self.toplevel = toplevel + self.parser = make_parser() + handler = DoxyContenHandler(self) + self.parser.setContentHandler(handler) + filename = 'krb5_8hin.xml' + filepath = '%s/%s' % (xmlpath,filename) + self.parser.parse(open(filepath,'r')) + + +class DoxyFuncs(XML2AST): + def __init__(self, path): + super(DoxyFuncs, self).__init__(path,toplevel='memberdef') + self.objects = list() + + def run(self): + for node in self.document: + self.process(node) + print "\nnumber of functions processed ===> ",len(self.objects) + + def process(self, node): + node_type = node.attributes['kind'] + if node_type == 'function': + data = self._process_function_node(node) + else: + print 'not processing node: %s' % node_type + return + + self.objects.append(DocModel(**data)) + + def save(self, templates, target_dir): + for obj in self.objects: + template_path = templates[obj.category] + outpath = '%s/%s.rst' % (target_dir,obj.name) + obj.save(outpath, template_path) + + + def _process_function_node(self, node): + f_name = node.children['name'][0].getContent() + print f_name + f_Id = node.attributes['id'] + f_ret_type = self._process_type_node(node.children['type'][0]) + f_brief = node.children['briefdescription'][0].getContent() + f_detailed = node.children['detaileddescription'][0] + detailed_description = self._process_description_node(f_detailed) + return_value_description = self._process_return_value_description(f_detailed) + retval_description = self._process_retval_description(f_detailed) + warning_description = self._process_warning_description(f_detailed) + seealso_description = self._process_seealso_description(f_detailed) + notes_description = self._process_notes_description(f_detailed) + deprecated_description = self._process_deprecated_description(f_detailed) + param_description_map = self.process_parameter_description(f_detailed) + f_definition = node.children['definition'][0].getContent() + f_argsstring = node.children['argsstring'][0].getContent() + + function_descr = {'category': 'function', + 'name': f_name, + 'Id': f_Id, + 'return_type': f_ret_type[1], + 'return_description': return_value_description, + 'retval_description': retval_description, + 'sa_description': seealso_description, + 'warn_description': warning_description, + 'notes_description': notes_description, + 'short_description': f_brief, + 'long_description': detailed_description, + 'deprecated_description': deprecated_description, + 'parameters': list()} + + parameters = function_descr['parameters'] + for (i,p) in enumerate(node.children['param']): + type_node = p.children['type'][0] + p_type = self._process_type_node(type_node) + if p_type[1].find('...') > -1 : + p_name = '' + else: + p_name = None + p_name_node = p.children.get('declname') + if p_name_node is not None: + p_name = p_name_node[0].getContent() + (p_direction,p_descr) = param_description_map.get(p_name,(None,None)) + + param_descr = {'seqno': i, + 'name': p_name, + 'direction': p_direction, + 'type': p_type[1], + 'typeId': p_type[0], + 'description': p_descr} + parameters.append(param_descr) + result = Function(**function_descr) + print >> self.tmp, result + + return function_descr + + def _process_type_node(self, type_node): + """ + Type node has form + type_string + for build in types and + + + 'type_name' + + postfix (ex. *, **m, etc.) + + for user defined types. + """ + type_ref_node = type_node.children.get('ref') + if type_ref_node is not None: + p_type_id = type_ref_node[0].attributes['refid'] + else: + p_type_id = None + p_type = type_node.getContent() + # remove some macros + p_type = re.sub('KRB5_ATTR_DEPRECATED', '', p_type) + p_type = re.sub('KRB5_CALLCONV_C', '', p_type) + p_type = re.sub('KRB5_CALLCONV_WRONG', '', p_type) + p_type = re.sub('KRB5_CALLCONV', '', p_type) + p_type = p_type.strip() + + return (p_type_id, p_type) + + def _process_description_node(self, node): + """ + Description node is comprised of ... sections + """ + para = node.children.get('para') + result = list() + if para is not None: + decorators = {'default': self.paragraph_content_decorator} + for e in para: + result.append(str(e.walk(decorators, 1))) + result.append('\n') + result = '\n'.join(result) + + return result + + def return_value_description_decorator(self, node, value): + if node.name == 'simplesect': + if node.attributes['kind'] == 'return': + cont = set() + cont = node.getContent() + return value + else: + return None + + def paragraph_content_decorator(self, node, value): + if node.name == 'para': + return value + '\n' + elif node.name == 'simplesect': + if node.attributes['kind'] == 'return': + return None + elif node.name == 'ref': + return ':c:func:' + '`' + value + '`' + elif node.name == 'emphasis': + return '*' + value + '*' + elif node.name == 'itemizedlist': + return '\n' + value + elif node.name == 'listitem': + return '\n\t - ' + value + '\n' + elif node.name == 'computeroutput': + return '**' + value + '**' + else: + return None + + def parameter_name_decorator(self, node, value): + if node.name == 'parametername': + direction = node.attributes.get('direction') + if direction is not None: + value = '%s:%s' % (value,direction) + return value + + elif node.name == 'parameterdescription': + return None + else: + return value + + def parameter_description_decorator(self, node, value): + if node.name == 'parameterdescription': + return value + elif node.name == 'parametername': + return None + else: + return value + + def process_parameter_description(self, node): + """ + Parameter descriptions reside inside detailed description section. + """ + para = node.children.get('para') + result = dict() + if para is not None: + for e in para: + + param_list = e.children.get('parameterlist') + if param_list is None: + continue + param_items = param_list[0].children.get('parameteritem') + if param_items is None: + continue + for it in param_items: + decorators = {'default': self.parameter_name_decorator} + direction = None + name = it.walk(decorators,0).split(':') + if len(name) == 2: + direction = name[1] + + decorators = {'default': self.parameter_description_decorator, + 'para': self.paragraph_content_decorator} + description = it.walk(decorators, 0) + result[name[0]] = (direction,description) + return result + + + def _process_return_value_description(self, node): + result = None + ret = list() + + para = node.children.get('para') + if para is not None: + for p in para: + simplesect_list = p.children.get('simplesect') + if simplesect_list is None: + continue + for it in simplesect_list: + decorators = {'default': self.return_value_description_decorator, + 'para': self.parameter_name_decorator} + result = it.walk(decorators, 1) + if result is not None: + ret.append(result) + return ret + + + def _process_retval_description(self, node): + """ + retval descriptions reside inside detailed description section. + """ + para = node.children.get('para') + + result = None + ret = list() + if para is not None: + + for e in para: + param_list = e.children.get('parameterlist') + if param_list is None: + continue + for p in param_list: + kind = p.attributes['kind'] + if kind == 'retval': + + param_items = p.children.get('parameteritem') + if param_items is None: + continue + + + for it in param_items: + param_descr = it.children.get('parameterdescription') + if param_descr is not None: + val = param_descr[0].children.get('para') + + if val is not None: + val_descr = val[0].getContent() + + else: + val_descr ='' + + decorators = {'default': self.parameter_name_decorator} + + name = it.walk(decorators, 1).split(':') + + val = name[0] + result = " %s %s" % (val, val_descr) + ret.append (result) + return ret + + def return_warning_decorator(self, node, value): + if node.name == 'simplesect': + if node.attributes['kind'] == 'warning': + return value + else: + return None + + def _process_warning_description(self, node): + result = None + para = node.children.get('para') + if para is not None: + for p in para: + simplesect_list = p.children.get('simplesect') + if simplesect_list is None: + continue + for it in simplesect_list: + decorators = {'default': self.return_warning_decorator, + 'para': self.paragraph_content_decorator} + result = it.walk(decorators, 1) + # Assuming that only one Warning per function + if result is not None: + return result + return result + + def return_seealso_decorator(self, node, value): + if node.name == 'simplesect': + if node.attributes['kind'] == 'see': + return value + else: + return None + + def _process_seealso_description(self, node): + result = None + para = node.children.get('para') + if para is not None: + for p in para: + simplesect_list = p.children.get('simplesect') + if simplesect_list is None: + continue + for it in simplesect_list: + decorators = {'default': self.return_seealso_decorator, + 'para': self.paragraph_content_decorator} + result = it.walk(decorators, 1) + return result + + def return_notes_decorator(self, node, value): + if node.name == 'simplesect': + if node.attributes['kind'] == 'note': + return value + else: + return None + + def _process_notes_description(self, node): + result = None + para = node.children.get('para') + if para is not None: + for p in para: + simplesect_list = p.children.get('simplesect') + if simplesect_list is None: + continue + for it in simplesect_list: + decorators = {'default': self.return_notes_decorator, + 'para': self.paragraph_content_decorator} + result = it.walk(decorators, 1) + if result is not None: + return result + return result + + def return_deprecated_decorator(self, node, value): + if node.name == 'xrefsect': + if node.attributes['id'].find('deprecated_') > -1: + xreftitle = node.children.get('xreftitle') + if xreftitle[0] is not None: + xrefdescr = node.children.get('xrefdescription') + deprecated_descr = "DEPRECATED %s" % xrefdescr[0].getContent() + return deprecated_descr + else: + return None + + def _process_deprecated_description(self, node): + result = None + para = node.children.get('para') + if para is not None: + for p in para: + xrefsect_list = p.children.get('xrefsect') + if xrefsect_list is None: + continue + for it in xrefsect_list: + decorators = {'default': self.return_deprecated_decorator, + 'para': self.paragraph_content_decorator} + result = it.walk(decorators, 1) + if result is not None: + return result + return result + + def break_into_lines(self, value, linelen=82): + breaks = range(0,len(value),linelen) + [len(value)] + result = list() + for (start,end) in zip(breaks[:-1],breaks[1:]): + result.append(value[start:end]) + result = '\n'.join(result) + + return result + + def _save(self, table, path = None): + if path is None: + f = sys.stdout + else: + f = open(path, 'w') + for l in table: + f.write('%s\n' % ','.join(l)) + if path is not None: + f.close() + + + +class DoxyFuncsTest(DoxyFuncs): + def __init__(self, xmlpath, rstpath): + super(DoxyFuncsTest,self).__init__(xmlpath) + self.target_dir = rstpath + outfile = '%s/%s' % (self.target_dir, 'out.txt') + self.tmp = open(outfile, 'w') + + def run_tests(self): + self.test_save() + + def test_run(self): + self.run() + + def test_save(self): + self.run() + templates = {'function': 'func_document.tmpl'} + self.save(templates, self.target_dir) + +if __name__ == '__main__': + tester = DoxyFuncsTest(xmlpath, rstpath) + tester.run_tests() + diff --git a/doc/rst_tools/doxybuilder_types.py b/doc/rst_tools/doxybuilder_types.py new file mode 100644 index 000000000..ae40ff7b3 --- /dev/null +++ b/doc/rst_tools/doxybuilder_types.py @@ -0,0 +1,286 @@ +''' + Copyright 2011 by the Massachusetts + Institute of Technology. All Rights Reserved. + + Export of this software from the United States of America may + require a specific license from the United States Government. + It is the responsibility of any person or organization contemplating + export to obtain such a license before exporting. + + WITHIN THAT CONSTRAINT, permission to use, copy, modify, and + distribute this software and its documentation for any purpose and + without fee is hereby granted, provided that the above copyright + notice appear in all copies and that both that copyright notice and + this permission notice appear in supporting documentation, and that + the name of M.I.T. not be used in advertising or publicity pertaining + to distribution of the software without specific, written prior + permission. Furthermore if you modify this software you must label + your software as modified software and not distribute it in such a + fashion that it might be confused with the original M.I.T. software. + M.I.T. makes no representations about the suitability of + this software for any purpose. It is provided "as is" without express + or implied warranty. +''' +import sys +import os +import re + +from lxml import etree + +from docmodel import * + + +class DoxyTypes(object): + def __init__(self, xmlpath): + self.xmlpath = xmlpath + + def run_compound(self, filename, include=None): + path = '%s/%s' % (self.xmlpath,filename) + tree = etree.parse(path) + root = tree.getroot() + + brief_node = root.xpath('./compounddef/briefdescription')[0] + brief_description = self._get_brief_description(brief_node) + details_node = root.xpath('./compounddef/detaileddescription')[0] + detailed_description = self._get_detailed_description(details_node) + + fields = list() + for node in root.iterfind(".//memberdef[@kind]"): + data = {} + kind = node.attrib['kind'] + if include is None or kind in include: + if kind == 'variable': + data = self._process_variable_node(node) + else: + pass + fields.append(data) + + result = {'brief_description': brief_description, + 'detailed_description': detailed_description, + 'attributes': fields} + + return result + + + + def run(self, filename, include=None): + """ + Parses xml file generated by doxygen. + + @param filename: doxygen xml file name + @param include: members sections to include, in None -- include all + """ + path = '%s/%s' % (self.xmlpath,filename) + tree = etree.parse(path) + root = tree.getroot() + result = list() + for node in root.iterfind(".//memberdef[@kind]"): + data = {} + kind = node.attrib['kind'] + if include is None or kind in include: + if kind == 'typedef': + data = self._process_typedef_node(node) + elif kind == 'variable': + data = self._process_variable_node(node) + result.append(data) + print "\nnumber of types processed ==> " , len(result) + return result + + + def _process_typedef_node(self, node): + t_name = node.xpath('./name/text()')[0] + + print t_name + + t_Id = node.attrib['id'] + t_definition = node.xpath('./definition/text()')[0] + t_type = self._process_type_node(node.xpath("./type")[0]) + brief_node = node.xpath('./briefdescription')[0] + t_brief = self._get_brief_description(brief_node) + details_node = node.xpath('./detaileddescription')[0] + t_detailed = self._get_detailed_description(details_node) + + typedef_descr = {'category': 'composite', + 'name': t_name, + 'Id': t_Id, + 'type': t_type[1], + 'short_description': t_brief, + 'long_description': t_detailed, + 'attributes': list() + } + if t_type[0] is not None : + filename = '%s.xml' % t_type[0] + path = '%s/%s' % (self.xmlpath,filename) + if not os.path.exists(path): + # nothing can be done + return typedef_descr + + compound_info = self.run_compound(filename) + if compound_info is not None: + brief_description = compound_info.get('brief_description') + if brief_description is not None and len(brief_description): + # override brief description + typedef_descr['short_description'] = brief_description + detailed_description = compound_info.get('detailed_description') + if detailed_description is not None and len(detailed_description): + # check if this is not a duplicate + if detailed_description.find(t_detailed) < 0: + typedef_descr['long_description'] = '%s\n%s' % \ + (detailed_description, + typedef_descr['long_description']) + typedef_descr['attributes'] = compound_info['attributes'] + return typedef_descr + + def _process_variable_node(self, node): + v_name = node.xpath('./name/text()')[0] + v_Id = node.attrib['id'] + v_definition = node.xpath('./definition/text()')[0] + v_type = self._process_type_node(node.xpath("./type")[0]) + brief_node = node.xpath('./briefdescription')[0] + v_brief = self._get_brief_description(brief_node) + details_node = node.xpath('./detaileddescription')[0] + detailed_description = self._get_detailed_description(details_node) + + variable_descr = {'category': 'variable', + 'name': v_name, + 'Id': v_Id, + 'type': v_type[1], + 'short_description': v_brief, + 'long_description': detailed_description + } + + return variable_descr + + + def _get_brief_description(self, node): + result = list() + for p in node.xpath("./para"): + x = self._process_paragraph_content(p) + if x is not None and len(x): + result.append(x) + result = '\n'.join(result) + + return result + + + def _get_detailed_description(self, node): + """ + Description node is comprised of ... sections. + There are few types of these sections: + a) Content section + b) Return value section -- skip + c) Parameter list section -- skip + @param node: detailed description node + """ + result = list() + for p in node.xpath("./para"): + if len(p.xpath("./simplesect[@kind='return']")): + continue + elif len(p.xpath("./parameterlist[@kind='param']")): + continue + else: + x = self._process_paragraph_content(p) + result.append(x) + result = '\n'.join(result) + + return result + + def _process_paragraph_content(self, node): + + result = list() + content = node.xpath(".//text()") + for e in content: + if node is e.getparent(): + result.append(e.strip()) + elif e.getparent().tag == 'ref': + if e.is_tail: + result.append(e.strip()) + else: + result.append(':c:type:`%s`' % e.strip()) + elif e.getparent().tag == 'emphasis': + if e.is_tail: + result.append(e.strip()) + else: + result.append('*%s*' % e.strip()) + elif e.getparent().tag == 'computeroutput': + if e.is_tail: + result.append(e.strip()) + else: + result.append('*%s*' % e.strip()) + result = ' '.join(result) + + return result + + def _process_type_node(self, node): + """ + Type node has form + type_string + for build in types and + + + 'type_name' + + postfix (ex. *, **m, etc.) + + for user defined types. + """ + p_id = node.xpath("./ref/@refid") + if len(p_id) == 1: + p_id = p_id[0] + elif len(p_id) == 0: + p_id = None + p_type = ' '.join(node.xpath(".//text()")) + + # remove macros + p_type = re.sub('KRB5_CALLCONV_C', ' ', p_type) + p_type = re.sub('KRB5_CALLCONV', ' ', p_type) + + return (p_id,p_type) + + def save(self, obj, templates, target_dir): + template_path = templates[obj.category] + outpath = '%s/%s.rst' % (target_dir,obj.name) + obj.save(outpath, template_path) + + + +class DoxyTypesTest(DoxyTypes): + def __init__(self, xmlpath, rstpath): + self.templates = { 'composite': 'type_document.tmpl'} + self.target_dir = rstpath + + super(DoxyTypesTest,self).__init__(xmlpath) + + def run_tests(self): + self.test_process_typedef_node() + + # TESTS + + def test_run(self): + filename = 'krb5_8hin.xml' + self.run(filename) + + + def test_process_variable_node(self): + filename = 'struct__krb5__octet__data.xml' + result = self.run(filename, include=['variable']) + + def test_process_typedef_node(self): + # run parser for typedefs + filename = 'krb5_8hin.xml' + result = self.run(filename, include=['typedef']) + target_dir = '%s/types' % (self.target_dir) + if not os.path.exists(target_dir): + os.makedirs(target_dir, 0755) + for t in result: + obj = DocModel(**t) + self.save(obj, self.templates, target_dir) + + def test_run_compound(self): + filename = 'struct__krb5__context.xml' + result = self.run_compound(filename) + +if __name__ == '__main__': + + tester = DoxyTypesTest( xml_inpath, rst_outpath) + tester.run_tests() diff --git a/doc/rst_tools/func_document.tmpl b/doc/rst_tools/func_document.tmpl new file mode 100644 index 000000000..830e5fc24 --- /dev/null +++ b/doc/rst_tools/func_document.tmpl @@ -0,0 +1,105 @@ +#if $function.short_description is not None + #set $title = $function.name + ' - ' + $function.short_description +#else + #set $title = $function.name +#end if +$title +#echo ''.join(['=']*len($title)) # + +:polyvox:`Doxygen reference to $function.name <$function.name>` + +.. + +.. c:function:: $signature + +.. + + +:param: + +#for $param in $function.parameters: + #if $param.name is '' + #continue + #end if + #if $param.direction is not None + #set name_description = '**[%s]** **%s**' % ($param.direction, $param.name) + #else + #set name_description = '**%s**' % $param.name + #end if + #if $param.description is not None + #set $description= ' - ' + $param.description + #else + #set $description='' + #end if + $name_description$description + +#end for + +.. + +#if len($function.retval_description) > 0 + +:retval: +#for $retval in $function.retval_description: + - $retval +#end for +#end if + +#if len($function.return_description) > 0 + +:return: +#for $retval in $function.return_description: + - $retval +#end for +#end if + +.. + +#if $function.deprecated_description is not None + +$function.deprecated_description +#end if + + + + +#if $function.long_description is not None + + +$function.long_description + +#end if + + +.. + +#if $function.sa_description is not None +.. seealso:: + $function.sa_description +#end if + + +#if $function.warn_description is not None or $function.notes_description is not None + + +#if $function.warn_description is not None +.. warning:: + $function.warn_description +#end if + +#if $function.notes_description is not None +.. note:: + $function.notes_description +#end if + +#end if + + + +Feedback +------------------ + +#set $msg_subject = 'Documentation___' + $function.name + +Please, provide your feedback on this document at krb5-bugs@mit.edu?subject=$msg_subject + diff --git a/doc/rst_tools/type_document.tmpl b/doc/rst_tools/type_document.tmpl new file mode 100644 index 000000000..17a8b8119 --- /dev/null +++ b/doc/rst_tools/type_document.tmpl @@ -0,0 +1,62 @@ +.. highlightlang:: c + +.. $composite.struct_reference($composite.name): + + + +#if $composite.short_description is not None and len($composite.short_description) + #set $title = $composite.name + ' - ' + $composite.short_description +#else + #set $title = $composite.name +#end if +$title +#echo ''.join(['=']*len($title)) # + + + +:polyvox:`Doxygen reference to $composite.name <$composite.name>` + +.. +.. c:type:: $composite.name +.. + +$composite.long_description + + +#if $composite.Id is not None and $composite.name is not 'krb5_trace_callback' + + +#if len($composite.attributes) + +Members +--------- + +#else + +#if $composite.type is not None +typedef :c:type:`$composite.type` $composite.name +#end if + +#end if + +#for $attr in $composite.attributes: +#if $attr.name is not None +.. c:member:: $attr.type $composite.name.$attr.name + + $attr.short_description +#if $attr.long_description is not None + $attr.long_description +#end if + +#end if +#end for +#end if + + +Feedback +---------- + +#set $msg_subject = 'Documentation___' + $composite.name + +Please, provide your feedback on this document at krb5-bugs@mit.edu?subject=$msg_subject + -- 2.26.2