├── doxyphp2sphinx ├── __init__.py ├── __main__.py ├── logger.py ├── cli.py └── rstgenerator.py ├── setup.cfg ├── LICENSE ├── .gitignore ├── README.md └── setup.py /doxyphp2sphinx/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_file = LICENSE 3 | 4 | [bdist_wheel] 5 | universal=1 6 | -------------------------------------------------------------------------------- /doxyphp2sphinx/__main__.py: -------------------------------------------------------------------------------- 1 | from .cli import Cli 2 | 3 | if __name__ == "__main__": 4 | Cli.run() 5 | -------------------------------------------------------------------------------- /doxyphp2sphinx/logger.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | 4 | class LogLevel(IntEnum): 5 | """ 6 | Named log levels 7 | """ 8 | OFF = 0 9 | ERROR = 1 10 | WARN = 2 11 | INFO = 3 12 | DEBUG = 4 13 | TRACE = 5 14 | ALL = 6 15 | 16 | 17 | class FilteredLogger: 18 | def __init__(self, loglevel): 19 | self._loglevel = loglevel 20 | 21 | def log(self, msg, loglevel = LogLevel.INFO): 22 | if loglevel > self._loglevel: 23 | return 24 | print(msg) 25 | -------------------------------------------------------------------------------- /doxyphp2sphinx/cli.py: -------------------------------------------------------------------------------- 1 | import xml.etree.ElementTree as ET 2 | import argparse 3 | 4 | from .logger import FilteredLogger 5 | from .rstgenerator import RstGenerator 6 | 7 | 8 | class Cli: 9 | @staticmethod 10 | def run(): 11 | parser = argparse.ArgumentParser(prog="doxyphp2sphinx", description='Generate Sphinx-ready reStructuredText documentation or your PHP project, using Doxygen XML as an input.') 12 | parser.add_argument('--xml-dir', dest='xml_dir', default='xml', help='directory to read from') 13 | parser.add_argument('--out-dir', dest='out_dir', default='.', help='directory to write to') 14 | parser.add_argument('--verbose', '-v', action='count', default=0, help='more output') 15 | parser.add_argument('--quiet', '-q', action='count', default=0, help='less output') 16 | 17 | parser.add_argument('root_namespace') 18 | args = parser.parse_args() 19 | 20 | logger = FilteredLogger(args.verbose - args.quiet + 3) 21 | 22 | inp_dir = args.xml_dir 23 | out_dir = args.out_dir 24 | root_namespace = args.root_namespace 25 | 26 | tree = ET.parse(inp_dir + '/index.xml') 27 | generator = RstGenerator(inp_dir, out_dir, root_namespace, logger) 28 | generator.render_namespace_by_name(tree, root_namespace) 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2018, Michael Billington 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # doxyphp2sphinx 2 | 3 | API Documentation generator for PHP projects which use Sphinx. 4 | 5 | It acts as a compatibility layer between Doxygen (which is good at reading PHP), 6 | and Sphinx (which is used by some online services to host HTML docs). 7 | 8 | - http://www.sphinx-doc.org/en/master/ 9 | - http://www.doxygen.org/ 10 | 11 | This tool is compatible with Python 2 and 3. 12 | 13 | ## Installation 14 | 15 | ### From source 16 | 17 | ```bash 18 | git clone https://github.com/mike42/ 19 | python setup.py bdist_wheel --universal 20 | pip install dist/doxyphp2sphinx-*.whl 21 | ``` 22 | 23 | ### From pip 24 | 25 | ```bash 26 | pip install doxyphp2sphinx 27 | ``` 28 | 29 | ### Verification 30 | 31 | Test that you have the command. 32 | 33 | ``` 34 | doxyphp2sphinx --help 35 | ``` 36 | 37 | ## Command-line use 38 | 39 | This package provides the `doxyphp2sphinx` command, which generates `.rst` files as output, given a directory 40 | of `doxygen` XML files. 41 | 42 | ```bash 43 | usage: doxyphp2sphinx [-h] [--xml-dir XML_DIR] [--out-dir OUT_DIR] [--verbose] 44 | [--quiet] 45 | root_namespace 46 | 47 | Generate Sphinx-ready reStructuredText documentation or your PHP project, 48 | using Doxygen XML as an input. 49 | 50 | positional arguments: 51 | root_namespace 52 | 53 | optional arguments: 54 | -h, --help show this help message and exit 55 | --xml-dir XML_DIR directory to read from 56 | --out-dir OUT_DIR directory to write to 57 | --verbose, -v more output 58 | --quiet, -q less output 59 | ``` 60 | 61 | ## Example 62 | 63 | The `gfx-php` project uses this tool to publish documentation to [readthedocs.org](https://readthedocs.org/), so 64 | we'll use that as an example: 65 | 66 | - Code: https://github.com/mike42/gfx-php 67 | - Docs: https://gfx-php.readthedocs.io 68 | 69 | ``` 70 | git clone https://github.com/mike42/gfx-php 71 | cd docs 72 | doxygen 73 | doxyphp2sphinx Mike42::GfxPhp 74 | make html 75 | ``` 76 | 77 | ## License 78 | 79 | doxyphp2sphinx is released under a BSD 2-Clause License. See LICENSE for the full text. 80 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """API Documentation generator for PHP projects which use Sphinx. 2 | See: 3 | https://github.com/mike42/doxyphp2sphinx 4 | """ 5 | 6 | # Always prefer setuptools over distutils 7 | from setuptools import setup, find_packages 8 | # To use a consistent encoding 9 | from codecs import open 10 | from os import path 11 | 12 | here = path.abspath(path.dirname(__file__)) 13 | 14 | # Get the long description from the README file 15 | with open(path.join(here, 'README.md'), encoding='utf-8') as f: 16 | long_description = f.read() 17 | 18 | setup( 19 | name='doxyphp2sphinx', 20 | version='1.0.1', 21 | description='API Documentation generator for PHP projects which use Sphinx.', 22 | long_description=long_description, 23 | long_description_content_type='text/markdown', 24 | url='https://github.com/mike42/doxyphp2sphinx', 25 | author='Michael Billington', 26 | author_email='michael.billington@gmail.com', 27 | classifiers=[ # Optional 28 | 'Development Status :: 3 - Alpha', 29 | 'Environment :: Console', 30 | 'Intended Audience :: Developers', 31 | 'Framework :: Sphinx', 32 | 'License :: OSI Approved :: BSD License', 33 | 'Operating System :: OS Independent', 34 | 'Programming Language :: Python :: 2', 35 | 'Programming Language :: Python :: 2.7', 36 | 'Programming Language :: Python :: 3', 37 | 'Programming Language :: Python :: 3.4', 38 | 'Programming Language :: Python :: 3.5', 39 | 'Programming Language :: Python :: 3.6', 40 | 'Topic :: Software Development', 41 | 'Topic :: Software Development :: Documentation', 42 | 'Topic :: Documentation', 43 | 'Topic :: Documentation :: Sphinx', 44 | 'Topic :: Utilities' 45 | ], 46 | 47 | keywords='doxygen sphinx restructuredtext readthedocs php documentation', # Optional 48 | 49 | packages=find_packages(exclude=['contrib', 'docs', 'tests']), # Required 50 | 51 | install_requires=['enum34'], # Optional 52 | 53 | extras_require={ # Optional 54 | 'dev': ['check-manifest'], 55 | 'test': ['coverage'], 56 | }, 57 | 58 | entry_points={ 59 | 'console_scripts': [ 60 | 'doxyphp2sphinx=doxyphp2sphinx.cli:Cli.run', 61 | ], 62 | }, 63 | 64 | project_urls={ # Optional 65 | 'Bug Reports': 'https://github.com/mike42/doxyphp2sphinx/issues', 66 | 'Source': 'https://github.com/mike42/doxyphp2sphinx', 67 | }, 68 | ) 69 | -------------------------------------------------------------------------------- /doxyphp2sphinx/rstgenerator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | This script converts the doxygen XML output, which contains the API description, 4 | and generates reStructuredText suitable for rendering with the sphinx PHP 5 | domain. 6 | """ 7 | 8 | from collections import OrderedDict 9 | import xml.etree.ElementTree as ET 10 | import os 11 | 12 | 13 | class RstGenerator: 14 | def __init__(self, inp_dir, out_dir, root_namespace, logger): 15 | self._root_namespace = root_namespace 16 | self._inp_dir = inp_dir 17 | self._out_dir = out_dir 18 | self._logger = logger 19 | 20 | def render_namespace_by_name(self, tree, namespace_name): 21 | root = tree.getroot() 22 | for child in root: 23 | if child.attrib['kind'] != 'namespace': 24 | # Skip non-namespace 25 | continue 26 | this_namespace_name = child.find('name').text 27 | if this_namespace_name != namespace_name: 28 | continue 29 | self.render_namespace_by_ref_id(child.attrib['refid'], this_namespace_name) 30 | 31 | def render_namespace_by_ref_id(self, namespace_ref_id, name): 32 | self._logger.log("Processing namespace " + name) 33 | self._logger.log(" refid is " + namespace_ref_id) 34 | prefix = self._root_namespace + "::" 35 | is_root = False 36 | if name == self._root_namespace: 37 | is_root = True 38 | elif not name.startswith(prefix): 39 | self._logger.log(" Skipping, not under " + self._root_namespace) 40 | return 41 | xml_filename = self._inp_dir + '/' + namespace_ref_id + '.xml' 42 | self._logger.log(" Opening " + xml_filename) 43 | ns = ET.parse(xml_filename) 44 | compound = ns.getroot().find('compounddef') 45 | # Generate some markup 46 | title = "API documentation" if is_root else name[len(prefix):] + " namespace" 47 | 48 | parts = name[len(prefix):].split("::") 49 | shortname_idx = "api" if is_root else ("api/" + "/".join(parts[:-1] + ['_' + parts[-1]]).lower()) 50 | shortname_dir = "api" if is_root else ("api/" + "/".join(parts[:-1] + [parts[-1]]).lower()) 51 | glob = "api/*" if is_root else parts[-1].lower() + "/*" 52 | outfile = self._out_dir + "/" + shortname_idx + ".rst" 53 | if not os.path.exists(self._out_dir + '/' + shortname_dir): 54 | os.mkdir(self._out_dir + "/" + shortname_dir) 55 | 56 | self._logger.log(" Page title will be '" + title + "'") 57 | self._logger.log(" Page path will be '" + outfile + "'") 58 | 59 | # TODO extract description of namespace from comments 60 | desc = compound.find('detaileddescription').text 61 | self._logger.log(" Desc is ... '" + desc + "'") 62 | 63 | with open(outfile, 'w') as nsOut: 64 | nsOut.write(title + "\n"); 65 | nsOut.write("=" * len(title) + "\n") 66 | nsOut.write("""\n.. toctree:: 67 | :glob: 68 | 69 | """ + glob + "\n\n" + desc + "\n") 70 | 71 | for node in compound.iter('innerclass'): 72 | cl_id = node.attrib['refid'] 73 | cl_name = node.text 74 | self.render_class_by_ref_id(cl_id, cl_name) 75 | 76 | for node in compound.iter('innernamespace'): 77 | ns_id = node.attrib['refid'] 78 | ns_name = node.text 79 | self.render_namespace_by_ref_id(ns_id, ns_name) 80 | 81 | # Walk the XML and extract all members of the given 'kind' 82 | def class_member_list(self, compounddef, member_kind): 83 | res = self.class_member_dict(compounddef, member_kind) 84 | return OrderedDict(sorted(res.items())).values() 85 | 86 | def class_member_dict(self, compounddef, member_kind): 87 | # Find items declared on this class 88 | ret = OrderedDict() 89 | for section in compounddef.iter('sectiondef'): 90 | kind = section.attrib['kind'] 91 | if kind != member_kind: 92 | continue 93 | for member in section.iter('memberdef'): 94 | method_name = member.find('definition').text.split("::")[-1] 95 | ret[method_name] = member 96 | # Follow-up with items from base classes 97 | if ("private" in member_kind) or ("static" in member_kind): 98 | # Private methods are not accessible, and static methods should be 99 | # called on the class which defines them. 100 | return ret 101 | for base_class in compounddef.iter('basecompoundref'): 102 | refid = base_class.attrib['refid'] 103 | base_compound_def = self.compounddef_by_ref_id(refid) 104 | inherited = self.class_member_dict(base_compound_def, member_kind) 105 | for key, value in inherited.items(): 106 | if key not in ret: 107 | ret[key] = value 108 | return ret 109 | 110 | def class_xml_to_rst(self, compounddef, title): 111 | rst = title + "\n" 112 | rst += "=" * len(title) + "\n\n" 113 | 114 | # Class description 115 | detailed_description_xml = compounddef.find('detaileddescription') 116 | detailed_description_text = self.paras2rst(detailed_description_xml).strip() 117 | if detailed_description_text != "": 118 | rst += detailed_description_text + "\n\n" 119 | 120 | # Look up base classes 121 | extends = [] 122 | implements = [] 123 | for base_class in compounddef.iter('basecompoundref'): 124 | baserefid = base_class.attrib['refid'] 125 | base_compound_def = self.compounddef_by_ref_id(baserefid) 126 | if base_compound_def.attrib['kind'] == "class": 127 | extends.append(base_compound_def) 128 | else: 129 | implements.append(base_compound_def) 130 | 131 | # TODO All known sub-classes 132 | qualified_name = compounddef.find('compoundname').text.replace("::", "\\") 133 | rst += ":Qualified name: ``" + qualified_name + "``\n" 134 | if len(extends) > 0: 135 | extends_links = [] 136 | for base_class in extends: 137 | base_class_name = base_class.find('compoundname').text.split("::")[-1] 138 | extends_links.append(":class:`" + base_class_name + "`") 139 | rst += ":Extends: " + ", ".join(extends_links) + "\n" 140 | if len(implements) > 0: 141 | implements_links = [] 142 | for base_interface in implements: 143 | base_interface_name = base_interface.find('compoundname').text.split("::")[-1] 144 | implements_links.append(":interface:`" + base_interface_name + "`") 145 | rst += ":Implements: " + ", ".join(implements_links) + "\n" 146 | rst += "\n" 147 | 148 | # Class name 149 | if compounddef.attrib['kind'] == "interface": 150 | rst += ".. php:interface:: " + title + "\n\n" 151 | else: 152 | rst += ".. php:class:: " + title + "\n\n" 153 | 154 | # Methods 155 | methods = self.class_member_list(compounddef, 'public-func') 156 | self._logger.log(" methods:") 157 | for method in methods: 158 | rst += self.method_xml_to_rst(method, 'method') 159 | 160 | # Static methods 161 | methods = self.class_member_list(compounddef, 'public-static-func') 162 | self._logger.log(" static methods:") 163 | for method in methods: 164 | rst += self.method_xml_to_rst(method, 'staticmethod') 165 | 166 | return rst 167 | 168 | def method_xml_to_rst(self, member, method_type): 169 | rst = "" 170 | documented_params = {} 171 | dd = member.find('detaileddescription') 172 | return_info = self.ret_info(dd) 173 | params = dd.find('*/parameterlist') 174 | if params != None: 175 | # Use documented param list if present 176 | for arg in params.iter('parameteritem'): 177 | argname = arg.find('parameternamelist') 178 | argname_type = argname.find('parametertype').text 179 | argname_name = argname.find('parametername').text 180 | argdesc = arg.find('parameterdescription') 181 | argdesc_para = argdesc.iter('para') 182 | doco = (" :param " + argname_type).rstrip() + " " + argname_name + ":\n" 183 | if argdesc_para != None: 184 | doco += self.paras2rst(argdesc_para, " ") 185 | documented_params[argname_name] = doco 186 | method_name = member.find('definition').text.split("::")[-1] 187 | args_string = self.method_args_string(member) 188 | 189 | if return_info != None and return_info['returnType'] != None: 190 | args_string += " -> " + return_info['returnType'] 191 | rst += " .. php:" + method_type + ":: " + method_name + " " + args_string + "\n\n" 192 | # Member description 193 | m_detailed_description_text = self.paras2rst(dd).strip() 194 | if m_detailed_description_text != "": 195 | rst += " " + m_detailed_description_text + "\n\n" 196 | 197 | # Param list from the definition in the code and use 198 | # documentation where available, auto-fill where not. 199 | params = member.iter('param') 200 | if params != None: 201 | for arg in params: 202 | param_key = arg.find('declname').text 203 | param_defval = arg.find('defval') 204 | if param_key in documented_params: 205 | param_doc = documented_params[param_key].rstrip() 206 | # Append a "." if the documentation does not end with one, AND we 207 | # need to write about the default value later. 208 | if param_doc[-1] != "." and param_doc[-1] != ":" and param_defval != None: 209 | param_doc += "." 210 | rst += param_doc + "\n" 211 | else: 212 | # Undocumented param 213 | param_name = param_key 214 | type_el = arg.find('type') 215 | type_str = "" if type_el == None else self.para2rst(type_el) 216 | rst += " :param " + (self.unencapsulate(type_str) + " " + param_name).strip() + ":\n" 217 | # Default value description 218 | if param_defval != None: 219 | rst += " Default: ``" + param_defval.text + "``\n" 220 | # Return value 221 | if return_info != None: 222 | if return_info['returnType'] != None: 223 | rst += " :returns: " + self.itsatype(return_info['returnType'], False) + " -- " + return_info[ 224 | 'returnDesc'] + "\n" 225 | else: 226 | rst += " :returns: " + return_info['returnDesc'] + "\n" 227 | if (params != None) or (return_info != None): 228 | rst += "\n" 229 | self._logger.log(" " + method_name + " " + args_string) 230 | return rst 231 | 232 | def method_args_string(self, member): 233 | params = member.iter('param') 234 | if params == None: 235 | # Main option is to use arg list from doxygen 236 | arg_list = member.find('argsstring').text 237 | return "()" if arg_list == None else arg_list 238 | required_param_part = [] 239 | optional_param_part = [] 240 | optional_switch = False 241 | for param in params: 242 | param_name = param.find('declname').text 243 | type_el = param.find('type') 244 | type_str = "" if type_el == None else self.para2rst(type_el) 245 | type_str = self.unencapsulate(type_str) 246 | param_str = (type_str + " " + param_name).strip() 247 | if param.find('defval') != None: 248 | optional_switch = True 249 | if optional_switch: 250 | optional_param_part.append(param_str) 251 | else: 252 | required_param_part.append(param_str) 253 | # Output arg list as string according to sphinxcontrib-phpdomain format 254 | if len(required_param_part) > 0: 255 | if len(optional_param_part) > 0: 256 | # Both required and optional args 257 | return "(" + ", ".join(required_param_part) + "[, " + ", ".join(optional_param_part) + "])" 258 | else: 259 | # Only required args 260 | return "(" + ", ".join(required_param_part) + ")" 261 | else: 262 | if len(optional_param_part) > 0: 263 | # Only optional args 264 | return "([" + ", ".join(required_param_part) + "])" 265 | else: 266 | # Empty arg list! 267 | return "()" 268 | 269 | def unencapsulate(self, typeStr): 270 | # TODO extract type w/o RST wrapping 271 | if typeStr[0:8] == ":class:`": 272 | return (typeStr[8:])[:-1] 273 | return typeStr 274 | 275 | def all_primitives(self): 276 | # Scalar type keywords and things you find in documentation (eg. 'mixed') 277 | # http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration 278 | return ["self", "bool", "callable", "iterable", "mixed", "int", "string", "array", "float", "double", "number"] 279 | 280 | def ret_info(self, dd): 281 | ret = dd.find('*/simplesect') 282 | if ret == None: 283 | return None 284 | paras = ret.iter('para') 285 | desc = self.paras2rst(paras).strip() 286 | desc_part = (desc + " ").split(" ") 287 | if desc_part[0] in self.all_primitives() or desc_part[0][0:8] == ":class:`": 288 | return {'returnType': self.unencapsulate(desc_part[0]), 'returnDesc': " ".join(desc_part[1:]).strip()} 289 | # No discernable return type 290 | return {'returnType': None, 'returnDesc': desc} 291 | 292 | def paras2rst(self, paras, prefix=""): 293 | return "\n".join([prefix + self.para2rst(x) for x in paras]) 294 | 295 | def xmldebug(self, inp): 296 | self._logger.log(ET.tostring(inp, encoding='utf8', method='xml').decode()) 297 | 298 | def para2rst(self, inp): 299 | ret = "" if inp.text == None else inp.text 300 | for subtag in inp: 301 | txt = subtag.text 302 | if subtag.tag == "parameterlist": 303 | continue 304 | if subtag.tag == "simplesect": 305 | continue 306 | if txt == None: 307 | continue 308 | if subtag.tag == "ref": 309 | txt = ":class:`" + txt + "`" 310 | ret += txt + ("" if subtag.tail == None else subtag.tail) 311 | return ret 312 | 313 | def itsatype(self, inp, primitives_as_literals=False): 314 | if inp == None: 315 | return "" 316 | if inp == "": 317 | return "" 318 | if inp in self.all_primitives(): 319 | if primitives_as_literals: 320 | return "``" + inp + "``" 321 | else: 322 | return inp 323 | else: 324 | return ":class:`" + inp + "`" 325 | 326 | def compounddef_by_ref_id(self, class_ref_id): 327 | xml_filename = self._inp_dir + '/' + class_ref_id + '.xml' 328 | cl = ET.parse(xml_filename) 329 | return cl.getroot().find('compounddef') 330 | 331 | def render_class_by_ref_id(self, class_ref_id, name): 332 | self._logger.log("Processing class " + name) 333 | self._logger.log(" refid is " + class_ref_id) 334 | compounddef = self.compounddef_by_ref_id(class_ref_id) 335 | prefix = self._root_namespace + "::" 336 | parts = name[len(prefix):].split("::") 337 | shortname = "api/" + "/".join(parts).lower() 338 | outfile = self._out_dir + "/" + shortname + ".rst" 339 | title = parts[-1] 340 | 341 | self._logger.log(" Class title will be '" + title + "'") 342 | self._logger.log(" Class path will be '" + outfile + "'") 343 | class_rst = self.class_xml_to_rst(compounddef, title) 344 | 345 | with open(outfile, 'w') as classOut: 346 | classOut.write(class_rst) 347 | --------------------------------------------------------------------------------