# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr # # This file is part of logilab-common. # # logilab-common is free software: you can redistribute it and/or modify it under # the terms of the GNU Lesser General Public License as published by the Free # Software Foundation, either version 2.1 of the License, or (at your option) any # later version. # # logilab-common is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License along # with logilab-common. If not, see . """Sphinx utils ModuleGenerator: Generate a file that lists all the modules of a list of packages in order to pull all the docstring. This should not be used in a makefile to systematically generate sphinx documentation! Typical usage: >>> from logilab.common.sphinxutils import ModuleGenerator >>> mgen = ModuleGenerator('logilab common', '/home/adim/src/logilab/common') >>> mgen.generate('api_logilab_common.rst', exclude_dirs=('test',)) """ import os, sys import os.path as osp import inspect from logilab.common import STD_BLACKLIST from logilab.common.shellutils import globfind from logilab.common.modutils import load_module_from_file, modpath_from_file def module_members(module): members = [] for name, value in inspect.getmembers(module): if getattr(value, '__module__', None) == module.__name__: members.append( (name, value) ) return sorted(members) def class_members(klass): return sorted([name for name in vars(klass) if name not in ('__doc__', '__module__', '__dict__', '__weakref__')]) class ModuleGenerator: file_header = """.. -*- coding: utf-8 -*-\n\n%s\n""" module_def = """ :mod:`%s` =======%s .. automodule:: %s :members: %s """ class_def = """ .. autoclass:: %s :members: %s """ def __init__(self, project_title, code_dir): self.title = project_title self.code_dir = osp.abspath(code_dir) def generate(self, dest_file, exclude_dirs=STD_BLACKLIST): """make the module file""" self.fn = open(dest_file, 'w') num = len(self.title) + 6 title = "=" * num + "\n %s API\n" % self.title + "=" * num self.fn.write(self.file_header % title) self.gen_modules(exclude_dirs=exclude_dirs) self.fn.close() def gen_modules(self, exclude_dirs): """generate all modules""" for module in self.find_modules(exclude_dirs): modname = module.__name__ classes = [] modmembers = [] for objname, obj in module_members(module): if inspect.isclass(obj): classmembers = class_members(obj) classes.append( (objname, classmembers) ) else: modmembers.append(objname) self.fn.write(self.module_def % (modname, '=' * len(modname), modname, ', '.join(modmembers))) for klass, members in classes: self.fn.write(self.class_def % (klass, ', '.join(members))) def find_modules(self, exclude_dirs): basepath = osp.dirname(self.code_dir) basedir = osp.basename(basepath) + osp.sep if basedir not in sys.path: sys.path.insert(1, basedir) for filepath in globfind(self.code_dir, '*.py', exclude_dirs): if osp.basename(filepath) in ('setup.py', '__pkginfo__.py'): continue try: module = load_module_from_file(filepath) except: # module might be broken or magic dotted_path = modpath_from_file(filepath) module = type('.'.join(dotted_path), (), {}) # mock it yield module if __name__ == '__main__': # example : title, code_dir, outfile = sys.argv[1:] generator = ModuleGenerator(title, code_dir) # XXX modnames = ['logilab'] generator.generate(outfile, ('test', 'tests', 'examples', 'data', 'doc', '.hg', 'migration'))