From 51e0be89c8f94ddc1bb023a4fb7ce4161e42df98 Mon Sep 17 00:00:00 2001
From: Cyrille Bagard
Date: Tue, 27 Oct 2015 16:49:10 +0000
Subject: Added a tool to update Python documentation online.
git-svn-id: svn://svn.gna.org/svn/chrysalide/trunk@601 abbe820e-26c8-41b2-8c08-b7b2b41f8b0a
---
ChangeLog | 10 ++
tools/gendocs/exporter.py | 95 ++++++++++++++++
tools/gendocs/exporters/html.py | 201 ++++++++++++++++++++++++++++++++++
tools/gendocs/exporters/mediawiki.py | 156 ++++++++++++++++++++++++++
tools/gendocs/gendoc.py | 36 ++++++
tools/gendocs/source.py | 54 +++++++++
tools/gendocs/sources/python.py | 207 +++++++++++++++++++++++++++++++++++
7 files changed, 759 insertions(+)
create mode 100644 tools/gendocs/exporter.py
create mode 100644 tools/gendocs/exporters/html.py
create mode 100644 tools/gendocs/exporters/mediawiki.py
create mode 100755 tools/gendocs/gendoc.py
create mode 100644 tools/gendocs/source.py
create mode 100644 tools/gendocs/sources/python.py
diff --git a/ChangeLog b/ChangeLog
index 962bae3..6b748c9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,13 @@
+15-10-27 Cyrille Bagard
+
+ * 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
* 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("""\
+
+
+
+ Documentation
+
+
+ """)
+
+
+ def close(self):
+ """Termine l'édition de la documentation."""
+
+ self._fd.write("""
+
+
+ """)
+
+ 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", "
")
+
+
+
+ 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('Sub modules
')
+
+ self._fd.write('')
+
+ if gparent != None:
+ self._fd.write('- ..
' % gparent)
+
+ for o in others:
+ fullname = builder(parent, o._name)
+ self._fd.write('- %s
' % (fullname, fullname))
+
+ self._fd.write('
')
+
+
+ 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('Classes
')
+
+ self._fd.write('')
+
+ for cls in classes:
+ self._fd.write('- %s
'
+ % (parent, cls, builder(parent, cls)))
+
+ self._fd.write('
')
+
+
+ def describe_module(self, desc):
+ """Affiche la description du module courant."""
+
+ self._fd.write('Description
')
+
+ self._fd.write('%s
' % self._normalize_desc(desc))
+
+
+ def start_main_section(self, title):
+ """Affiche un en-tête pour la zone des classes."""
+
+ self._fd.write('%s
' % title)
+
+
+ def show_class_info(self, name, desc):
+ """Affiche les informations générales d'une classe."""
+
+ self._fd.write('%s
' % (name, name))
+
+ self._fd.write('%s
' % self._normalize_desc(desc))
+
+
+ def show_info_section(self, title):
+ """Affiche une section d'informations particulières."""
+
+ self._fd.write('%s
' % title)
+
+
+ def print_hierarchy_level(self, fullname, name, page, level):
+ """Affiche un élément hiérarchique."""
+
+ if level > 0:
+
+ self._fd.write('')
+
+ for i in range(level - 1):
+ self._fd.write(' ' * 5)
+
+ self._fd.write(' ╰── ')
+
+ self._fd.write('')
+
+ if page != None:
+ self._fd.write('' % (page, name))
+
+ self._fd.write(fullname)
+
+ if page != None:
+ self._fd.write('')
+
+ self._fd.write('
')
+
+
+ def show_constant_info(self, name):
+ """Affiche un élément de type 'constant'."""
+
+ self._fd.write('%s
' % name)
+
+
+ def show_data_info(self, name, value):
+ """Affiche un élément de type 'donnée'."""
+
+ self._fd.write('%s = %s
' % (name, self._normalize_desc(value)))
+
+ def show_attribute_info(self, name, desc):
+ """Affiche un élément de type 'attribut'."""
+
+ self._fd.write('%s
%s
' % (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('%s(%s)
%s
' % (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 += '\n==Sub modules==\n
\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 += '\n==Classes==\n
\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 += '\n==Description==\n
\n'
+
+ self._content += '%s
\n' % desc
+
+
+ def start_main_section(self, title):
+ """Affiche un en-tête pour la zone des classes."""
+
+ self._content += '\n=%s=\n
\n' % title
+
+
+ def show_class_info(self, name, desc):
+ """Affiche les informations générales d'une classe."""
+
+ self._content += '\n==%s==\n
\n' % (name, name)
+
+ self._content += '%s
\n' % desc
+
+
+ def show_info_section(self, title):
+ """Affiche une section d'informations particulières."""
+
+ self._content += '\n===%s===\n
\n' % title
+
+
+ def begin_hierarchy_level(self):
+ """Démarre une arborescence hiérarchique."""
+
+ self._content += '\n'
+
+
+ def terminate_hierarchy_level(self):
+ """Arrête une arborescence hiérarchique."""
+
+ self._content += '
\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 += '%s\n' % name
+
+
+ def show_data_info(self, name, value):
+ """Affiche un élément de type 'donnée'."""
+
+ self._content += '\n'
+ self._content += '%s = ' % name
+ self._content += '%s\n' % value
+
+
+ def show_attribute_info(self, name, desc):
+ """Affiche un élément de type 'attribut'."""
+
+ self._content += '\n'
+ self._content += '%s\n' % name
+ self._content += '\n'
+ self._content += '%s\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 += '%s(%s)\n' % (name, args)
+ self._content += '\n'
+ self._content += '%s
\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 ' % 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)
--
cgit v0.11.2-87-g4458