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