diff options
author | Cyrille Bagard <nocbos@gmail.com> | 2015-10-27 16:49:10 (GMT) |
---|---|---|
committer | Cyrille Bagard <nocbos@gmail.com> | 2015-10-27 16:49:10 (GMT) |
commit | 51e0be89c8f94ddc1bb023a4fb7ce4161e42df98 (patch) | |
tree | 810e9249d82d527b5da88d5de103b10930a9a985 | |
parent | 602ac694fc28885a496a7cf377a8cdd08221da4d (diff) |
Added a tool to update Python documentation online.
git-svn-id: svn://svn.gna.org/svn/chrysalide/trunk@601 abbe820e-26c8-41b2-8c08-b7b2b41f8b0a
-rw-r--r-- | ChangeLog | 10 | ||||
-rw-r--r-- | tools/gendocs/exporter.py | 95 | ||||
-rw-r--r-- | tools/gendocs/exporters/html.py | 201 | ||||
-rw-r--r-- | tools/gendocs/exporters/mediawiki.py | 156 | ||||
-rwxr-xr-x | tools/gendocs/gendoc.py | 36 | ||||
-rw-r--r-- | tools/gendocs/source.py | 54 | ||||
-rw-r--r-- | tools/gendocs/sources/python.py | 207 |
7 files changed, 759 insertions, 0 deletions
@@ -1,3 +1,13 @@ +15-10-27 Cyrille Bagard <nocbos@gmail.com> + + * tools/gendocs/exporter.py: + * tools/gendocs/exporters/html.py: + * tools/gendocs/exporters/mediawiki.py: + * tools/gendocs/gendoc.py: + * tools/gendocs/source.py: + * tools/gendocs/sources/python.py: + Add a tool to update Python documentation online. + 15-10-18 Cyrille Bagard <nocbos@gmail.com> * po/fr.po: diff --git a/tools/gendocs/exporter.py b/tools/gendocs/exporter.py new file mode 100644 index 0000000..c34c468 --- /dev/null +++ b/tools/gendocs/exporter.py @@ -0,0 +1,95 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + + +class DocExporter: + """Exporte une documentation d'une façon donnée.""" + + + def open(self, name, fullname): + """Initialise les propriétés utiles.""" + + pass + + + def close(self): + """Termine l'édition de la documentation.""" + + pass + + + def show_list_sub_modules(self, gparent, parent, others, builder): + """Affiche des renvois vers les sous-parties présentes.""" + + pass + + + def show_list_sub_classes(self, parent, classes, builder): + """Affiche des renvois vers les sous-parties présentes.""" + + pass + + + def describe_module(self, desc): + """Affiche la description du module courant.""" + + pass + + + def start_main_section(self, title): + """Affiche un en-tête pour la zone des classes.""" + + pass + + + def show_class_info(self, name, desc): + """Affiche les informations générales d'une classe.""" + + pass + + + def show_info_section(self, title): + """Affiche une section d'informations particulières.""" + + pass + + + def begin_hierarchy_level(self): + """Démarre une arborescence hiérarchique.""" + + pass + + + def terminate_hierarchy_level(self): + """Arrête une arborescence hiérarchique.""" + + pass + + + def print_hierarchy_level(self, fullname, name, page, level): + """Affiche un élément hiérarchique.""" + + pass + + + def show_constant_info(self, name): + """Affiche un élément de type 'constant'.""" + + pass + + + def show_data_info(self, name, value): + """Affiche un élément de type 'donnée'.""" + + pass + + def show_attribute_info(self, name, desc): + """Affiche un élément de type 'attribut'.""" + + pass + + + def show_callable_info(self, ret, name, args, desc): + """Affiche un élément de type 'routine'.""" + + pass diff --git a/tools/gendocs/exporters/html.py b/tools/gendocs/exporters/html.py new file mode 100644 index 0000000..72eda46 --- /dev/null +++ b/tools/gendocs/exporters/html.py @@ -0,0 +1,201 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from exporter import DocExporter + + +class HtmlExporter(DocExporter): + """Exporte une documentation sous forme HTML.""" + + + def open(self, name, fullname): + """Initialise les propriétés utiles.""" + + self._filename = fullname + '.html' + self._fd = open(self._filename, 'w') + + self._fd.write("""\ +<HTML> +<HEAD> + <META http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/> + <TITLE>Documentation</TITLE> + <STYLE> + <!-- + H1 { + background-color: rgb(218, 218, 218); + border: 1px solid rgb(184, 184, 184); + border-radius: 3px 3px 3px 3px; + padding: 8px; + } + H2 { + background-color: rgb(238, 238, 238); + border: 1px solid rgb(204, 204, 204); + border-radius: 3px 3px 3px 3px; + padding: 4px; + } + .info { + background-color: #efc; + border: 1px solid #ac9; + border-radius: 3px; + margin-left: 40px; + padding: 8px; + } + .steps { + font-family: monospace; + } + .constant_name { + font-weight: bold; + } + .data_name { + font-weight: bold; + } + .data_value { + } + .property_name { + font-weight: bold; + } + .property_desc { + padding-left: 40px; + } + .callable_name { + font-weight: bold; + } + .callable_desc { + padding-left: 40px; + } + --> + </STYLE> +<BODY> + """) + + + def close(self): + """Termine l'édition de la documentation.""" + + self._fd.write(""" +</BODY> +</HTML> + """) + + self._fd.close() + + + def _normalize_desc(self, desc): + """S'assure d'un bon rendu HTML d'un commentaire prévu pour.""" + + return desc.replace(" ", " ").replace("<", "<").replace(">", ">").replace("\n", "<BR>") + + + + def show_list_sub_modules(self, gparent, parent, others, builder): + """Affiche des renvois vers les sous-parties présentes.""" + + if len(others) > 0 or gparent != None: + + self._fd.write('<H2>Sub modules</H2>') + + self._fd.write('<UL>') + + if gparent != None: + self._fd.write('<LI><A href="%s.html">..</A></LI>' % gparent) + + for o in others: + fullname = builder(parent, o._name) + self._fd.write('<LI><A href="%s.html">%s</A></LI>' % (fullname, fullname)) + + self._fd.write('</UL>') + + + def show_list_sub_classes(self, parent, classes, builder): + """Affiche des renvois vers les sous-parties présentes.""" + + if len(classes) > 0: + + self._fd.write('<H2>Classes</H2>') + + self._fd.write('<UL>') + + for cls in classes: + self._fd.write('<LI><A href="%s.html#%s">%s</A></LI>' + % (parent, cls, builder(parent, cls))) + + self._fd.write('</UL>') + + + def describe_module(self, desc): + """Affiche la description du module courant.""" + + self._fd.write('<H2>Description</H2>') + + self._fd.write('<P class="info">%s</P>' % self._normalize_desc(desc)) + + + def start_main_section(self, title): + """Affiche un en-tête pour la zone des classes.""" + + self._fd.write('<H1>%s</H1>' % title) + + + def show_class_info(self, name, desc): + """Affiche les informations générales d'une classe.""" + + self._fd.write('<A name="%s"><H2>%s</H2></A>' % (name, name)) + + self._fd.write('<P class="info">%s</P>' % self._normalize_desc(desc)) + + + def show_info_section(self, title): + """Affiche une section d'informations particulières.""" + + self._fd.write('<H3>%s</H3>' % title) + + + def print_hierarchy_level(self, fullname, name, page, level): + """Affiche un élément hiérarchique.""" + + if level > 0: + + self._fd.write('<SPAN class="steps">') + + for i in range(level - 1): + self._fd.write(' ' * 5) + + self._fd.write(' ╰── ') + + self._fd.write('</SPAN>') + + if page != None: + self._fd.write('<A href="%s.html#%s">' % (page, name)) + + self._fd.write(fullname) + + if page != None: + self._fd.write('</A>') + + self._fd.write('<BR>') + + + def show_constant_info(self, name): + """Affiche un élément de type 'constant'.""" + + self._fd.write('<P><SPAN class="constant_name">%s</SPAN></P>' % name) + + + def show_data_info(self, name, value): + """Affiche un élément de type 'donnée'.""" + + self._fd.write('<P><SPAN class="data_name">%s</SPAN> = <SPAN class="data_value">%s</SPAN></P>' % (name, self._normalize_desc(value))) + + def show_attribute_info(self, name, desc): + """Affiche un élément de type 'attribut'.""" + + self._fd.write('<P><SPAN class="property_name">%s</SPAN><BR><DIV class="property_desc">%s</DIV></P>' % (name, self._normalize_desc(desc))) + + + def show_callable_info(self, ret, name, args, desc): + """Affiche un élément de type 'routine'.""" + + if args == None: + args = '()' + + self._fd.write('<P><SPAN class="callable_name">%s</SPAN>(%s)<BR><DIV class="callable_desc">%s</DIV></P>' % (name, args, self._normalize_desc(desc))) diff --git a/tools/gendocs/exporters/mediawiki.py b/tools/gendocs/exporters/mediawiki.py new file mode 100644 index 0000000..d994dae --- /dev/null +++ b/tools/gendocs/exporters/mediawiki.py @@ -0,0 +1,156 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import pywikibot +from exporter import DocExporter + + +class MWExporter(DocExporter): + """Exporte une documentation vers un site Mediawiki.""" + + + def open(self, name, fullname): + """Initialise les propriétés utiles.""" + + self._site = pywikibot.Site() + self._page = pywikibot.Page(self._site, self._build_page_name(fullname)) + + self._content = '__NOTOC__' + self._content += '\n' + + + def close(self): + """Termine l'édition de la documentation.""" + + if self._content != self._page.text: + self._page.text = self._content + self._page.save('API update') + + + def _build_page_name(self, orig): + """Définit le nom final d'une page faisant référence à un module.""" + + return orig + ' Python Module' + + + def show_list_sub_modules(self, gparent, parent, others, builder): + """Affiche des renvois vers les sous-parties présentes.""" + + if len(others) > 0 or gparent != None: + + self._content += '<div class="h2">\n==Sub modules==\n</div>\n' + + if gparent != None: + self._content += '* [[%s|..]]\n' % self._build_page_name(gparent) + + for o in others: + fullname = builder(parent, o._name) + self._content += '* [[%s|%s]]\n' % (self._build_page_name(fullname), fullname) + + + def show_list_sub_classes(self, parent, classes, builder): + """Affiche des renvois vers les sous-parties présentes.""" + + if len(classes) > 0: + + self._content += '<div class="h2">\n==Classes==\n</div>\n' + + for cls in classes: + self._content += '* [[%s#%s|%s]]\n' % (self._build_page_name(parent), cls, builder(parent, cls)) + + + def describe_module(self, desc): + """Affiche la description du module courant.""" + + self._content += '<div class="h2">\n==Description==\n</div>\n' + + self._content += '<div class="fakepre info">%s</div>\n' % desc + + + def start_main_section(self, title): + """Affiche un en-tête pour la zone des classes.""" + + self._content += '<div class="h1">\n=%s=\n</div>\n' % title + + + def show_class_info(self, name, desc): + """Affiche les informations générales d'une classe.""" + + self._content += '<div id="%s" class="h2">\n==%s==\n</div>\n' % (name, name) + + self._content += '<pre class="fakepre info">%s</pre>\n' % desc + + + def show_info_section(self, title): + """Affiche une section d'informations particulières.""" + + self._content += '<div class="h3">\n===%s===\n</div>\n' % title + + + def begin_hierarchy_level(self): + """Démarre une arborescence hiérarchique.""" + + self._content += '<div class="fakepre treeclasses">\n' + + + def terminate_hierarchy_level(self): + """Arrête une arborescence hiérarchique.""" + + self._content += '</div>\n\n' + + + def print_hierarchy_level(self, fullname, name, page, level): + """Affiche un élément hiérarchique.""" + + if level > 0: + + for i in range(level - 1): + self._content += ' ' * 5 + + self._content += ' ╰── ' + + if page != None: + self._content += '[[%s#%s|' % (self._build_page_name(page), name) + + self._content += fullname + + if page != None: + self._content += ']]' + + self._content += '\n' + + + def show_constant_info(self, name): + """Affiche un élément de type 'constant'.""" + + self._content += '\n' + self._content += '<span class="constant_name">%s</span>\n' % name + + + def show_data_info(self, name, value): + """Affiche un élément de type 'donnée'.""" + + self._content += '\n' + self._content += '<span class="data_name">%s</span> = ' % name + self._content += '<span class="data_value">%s</span>\n' % value + + + def show_attribute_info(self, name, desc): + """Affiche un élément de type 'attribut'.""" + + self._content += '\n' + self._content += '<span class="property_name">%s</span>\n' % name + self._content += '\n' + self._content += '<span class="property_desc">%s</span>\n' % desc + + + def show_callable_info(self, ret, name, args, desc): + """Affiche un élément de type 'routine'.""" + + if args == None: + args = '()' + + self._content += '\n' + self._content += '<span class="callable_name">%s</span>(%s)\n' % (name, args) + self._content += '\n' + self._content += '<pre class="fakepre callable_desc">%s</pre>\n' % desc diff --git a/tools/gendocs/gendoc.py b/tools/gendocs/gendoc.py new file mode 100755 index 0000000..7c29b2f --- /dev/null +++ b/tools/gendocs/gendoc.py @@ -0,0 +1,36 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +import os +import sys + + +def validate_coverage(target): + + result = False + + for mod in sys.argv[1:]: + result = target.startswith(mod) + if result: + break + + return result + + +if __name__ == '__main__': + + if os.environ.get('PYWIKIBOT2_DIR') is None: + print('Environment variable "KEY_THAT_MIGHT_EXIST" is not set!') + sys.exit(1) + + if len(sys.argv) == 1: + print('Usage: %s <module>' % sys.argv[0]) + sys.exit(1) + + from exporters.html import HtmlExporter + from exporters.mediawiki import MWExporter + from sources.python import PythonReader + + for mod in sys.argv[1:]: + reader = PythonReader(None, mod, MWExporter) + reader.build(validate_coverage) diff --git a/tools/gendocs/source.py b/tools/gendocs/source.py new file mode 100644 index 0000000..3de4874 --- /dev/null +++ b/tools/gendocs/source.py @@ -0,0 +1,54 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + + +class SourceReader(object): + """Lecteur attentif de code source.""" + + + def __init__(self, parent, name, output): + """Initialise l'identité d'une partie de documentation.""" + + self._parent = parent + self._name = name + self._output_cls = output + self._output = output() + + + def build(self, validation): + """Construit de façon générique une documentation complète.""" + + self._output.open(self._name, self._fullname) + + others, mod_desc = self.prepare_module() + classes = self.list_all_classes() + + # Contenu du module + + self._output.start_main_section('Content') + + self._output.show_list_sub_modules(self._parent, self._fullname, others, self.make_path) + + self._output.show_list_sub_classes(self._fullname, classes, self.make_path) + + self._output.describe_module(mod_desc) + + # Description des classes + + if len(classes) > 0: + + self._output.start_main_section('Classes') + + for cls in classes: + self.describe_class(cls, validation) + + # Eléments propres au module + + self.describe_module_items() + + # Fermeture et suite + + self._output.close() + + for o in others: + o.build(validation) diff --git a/tools/gendocs/sources/python.py b/tools/gendocs/sources/python.py new file mode 100644 index 0000000..aa7d32e --- /dev/null +++ b/tools/gendocs/sources/python.py @@ -0,0 +1,207 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from collections import deque +from source import SourceReader +import importlib +import inspect + + +is_callable = lambda t: inspect.isroutine(t[2]) \ + or isinstance(t[2], staticmethod) \ + or isinstance(t[2], classmethod) + +is_attribute = lambda t: not(is_callable(t)) and (inspect.isgetsetdescriptor(t[2]) or isinstance(t[2], property)) + +is_constant = lambda t: not(is_attribute(t) or is_callable(t)) and t[0].isupper() + +is_data = lambda t: not(is_constant(t) or is_attribute(t) or is_callable(t)) + + +class PythonReader(SourceReader): + """Lecteur attentif de code Python.""" + + + def __init__(self, parent, name, output): + """Initialise l'identité d'une partie de documentation.""" + + super(PythonReader, self).__init__(parent, name, output) + + self._fullname = self.make_path(parent, name) + + + def make_path(self, parent, target): + """Construit le chemin complet d'un élément d'un module donné.""" + + return parent + '.' + target if parent != None else target + + + def prepare_module(self): + """Charge le module courant et liste tous ses sous-modules.""" + + result = [ ] + + self._module = importlib.import_module(self._fullname) + + submodules = inspect.getmembers(self._module, inspect.ismodule) + + for name, inst in submodules: + other = PythonReader(self._fullname, name, self._output_cls) + result.append(other) + + return result, self._module.__doc__ + + + def list_all_classes(self): + """Liste toutes les classes présentes dans le module courant.""" + + result = [ ] + + classes = inspect.getmembers(self._module, inspect.isclass) + + for name, inst in classes: + result.append(name) + + return result + + + def is_visible(self, name): + """Sélectionne les éléments à faire apparaître dans une documenation.""" + + # On évite les noms spéciaux internes et redondants + if name in {'__author__', '__builtins__', '__cached__', '__credits__', + '__date__', '__doc__', '__file__', '__spec__', + '__loader__', '__module__', '__name__', '__package__', + '__path__', '__qualname__', '__slots__', '__version__'}: + return False + + # Les noms spéciaux restants ne sont pas invisibles + if name.startswith('__') and name.endswith('__'): + return True + + return not name.startswith('_') + + + def describe_module_items(self): + """Décrit tous les éléments du module qui n'ont pas déjà été présentés.""" + + processed = lambda v: inspect.ismodule(v) or inspect.isclass(v) + + attrs = [(key, self._module, self._module.__dict__[key]) + for key in dir(self._module) + if self.is_visible(key) and not(processed(self._module.__dict__[key])) + ] + + self.describe_attribs_list(attrs) + + + def describe_class(self, name, validation): + + cls = getattr(self._module, name) + + self._output.show_class_info(cls.__name__, cls.__doc__) + + self._output.show_info_section('Class Hierarchy') + + self._output.begin_hierarchy_level() + + mro = deque(inspect.getmro(cls)) + mro.reverse() + level = 0 + + for base in mro: + + if (level + 1) == len(mro) or not validation(base.__module__): + page = None + else: + page = base.__module__ + + fullname = self.make_path(base.__module__, base.__name__) + + self._output.print_hierarchy_level(fullname, base.__name__, page, level) + + level = level + 1 + + self._output.terminate_hierarchy_level() + + attrs = [(name, owner, value) + for (name, kind, owner, value) in inspect.classify_class_attrs(cls) + if owner == cls and self.is_visible(name) + ] + + self.describe_attribs_list(attrs) + + + def describe_attribs_list(self, attrs): + """Describe some module/class attributes in a given order.""" + + def filter_attrs(lst, predicate, title): + + remaining = [] + + first = True + + for a in lst: + if predicate(a): + if first: + self._output.show_info_section(title) + first = False + self.describe_item(a) + else: + remaining.append(a) + + return remaining + + attrs = filter_attrs(attrs, is_constant, 'Constants') + attrs = filter_attrs(attrs, is_data, 'Data') + attrs = filter_attrs(attrs, is_attribute, 'Attributes') + attrs = filter_attrs(attrs, is_callable, 'Methods') + + assert(len(attrs) == 0) + + + def describe_item(self, item): + """Describe one module/class item.""" + + name, homecls, value = item + + if is_callable(item): + + value = getattr(homecls, name) + + if hasattr(value, '__text_signature__') and value.__text_signature__ != None: + + args = '' + + for p in inspect.signature(value).parameters.values(): + if len(args) > 0: + args = args + ', ' + args = args + p.name + + else: + + args = '...' + + # method_descriptor -> value.__doc__ + + doc = getattr(homecls, name).__doc__ + + self._output.show_callable_info('', name, args, doc) + + + elif is_attribute(item): + + self._output.show_attribute_info(name, value.__doc__) + + elif is_constant(item): + + self._output.show_constant_info(name) + + elif is_data(item): + + self._output.show_data_info(name, value) + + + else: + + assert(False) |