summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCyrille Bagard <nocbos@gmail.com>2015-10-27 16:49:10 (GMT)
committerCyrille Bagard <nocbos@gmail.com>2015-10-27 16:49:10 (GMT)
commit51e0be89c8f94ddc1bb023a4fb7ce4161e42df98 (patch)
tree810e9249d82d527b5da88d5de103b10930a9a985
parent602ac694fc28885a496a7cf377a8cdd08221da4d (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--ChangeLog10
-rw-r--r--tools/gendocs/exporter.py95
-rw-r--r--tools/gendocs/exporters/html.py201
-rw-r--r--tools/gendocs/exporters/mediawiki.py156
-rwxr-xr-xtools/gendocs/gendoc.py36
-rw-r--r--tools/gendocs/source.py54
-rw-r--r--tools/gendocs/sources/python.py207
7 files changed, 759 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
index 962bae3..6b748c9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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(" ", "&nbsp;").replace("<", "&lt;").replace(">", "&gt;").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('&nbsp;' * 5)
+
+ self._fd.write('&nbsp;╰──&nbsp;')
+
+ 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)