モジュールのドキュメントを抜き出す(別バージョン)
前回作ったのは、xml.dom.minidomを使ってxmlを出力する処理を行っていましたが、
今回はstring.Templateを使用して同じようなことをやってみました。
今までにstring.Templateはあまり使っていなかったのですが、使ってみるとなかなか便利です。
ソースコードはこんな感じです。
メソッド get_doc_str()の処理にstring.Templateを使用している以外は、前回のものとほぼ同じです。
(実行結果もほぼ同じです。)
xml.dom.minidomなどは、とても便利なんですけど、出力されるxmlの書かれ方を細かく決められないようなので、
場合によってはこちらのやり方のほうかシックリくるかもしれません。
# -*- coding: utf-8 -*- import os import sys import inspect from string import Template from xml.dom.minidom import Document DEFAULT_METHOD_TEMPLATE =''' <para class="synopsis"> <function>$method_name</function> <synopsis>$method_synopsis</synopsis> <para> $method_desc </para> </para> ''' DEFAULT_CLASS_TEMPLATE = ''' <sect2><title>$class_name</title> <para class="class_desc"> $class_desc </para> $template_method </sect2> ''' DEFAULT_MODULE_TEMPLATE = ''' <sect1><title>$module_name</title> $template_class </sect1> ''' DEFAULT_CHAPTER_TEMPLATE = ''' <?xml version="1.0" encoding="UTF-8"?> <chapter id="$chapter_id"><title>$chapter_title</title> $template_mod </chapter> ''' # # class ModuleDocGenerator: ''' モジュールのドキュメント文字列を抽出するためのモジュール。 生成時、コンストラクタで指定されたモジュールをパースし、内部情報を構築。 get_doc_str()を呼び出すことでdocbook形式のxmlテキストを取得することができる。 ''' def __init__(self, modPath, excludes=[]): ''' 初期化。 引数: modPath: 処理対象のモジュールファイルパス。 ディレクトリが指定された場合には、再帰的に.pyファイルを処理する。 excludes: 処理対象外のファイルまたはディレクトリ。 ''' modPath = os.path.normpath(os.path.expanduser(modPath)) self.excludes = excludes self.modDictList = [] self._parse_module(modPath) def _parse_module(self, modPath): ''' モジュールファイルからドキュメント文字列を取り出し、辞書を構築する。 内部的に構築される辞書の形式: {'module': モジュール名, 'doc': モジュールのドキュメント文字列 'classes': ['cls': クラス名称, 'doc': クラスのドキュメント文字列, 'methods': [{'method': メソッド名称, 'doc': メソッドのドキュメント文字列}]]} 引数: modPath: 処理対象のモジュールファイルパス。 ディレクトリが指定された場合には、再帰的に.pyファイルを処理する。 ''' if modPath in self.excludes: return if os.path.isdir(modPath): if modPath not in sys.path: sys.path.append(modPath) for p in os.listdir(modPath): self._parse_module(os.path.join(modPath, p)) elif os.path.isfile(modPath) == False: print 'No such module: %s' % modPath return if os.path.splitext(modPath)[1] != '.py': return modName = inspect.getmodulename(modPath) try: mod = __import__(modName) except Exception, err: print err return # モジュール情報を構築 modDict = {'module': modName, 'doc': None, 'classes':[]} modDict['doc'] = inspect.getdoc(mod) self.modDictList.append(modDict) # クラス情報を構築 for name, cls in inspect.getmembers(mod, inspect.isclass): if mod != inspect.getmodule(cls): continue cDict = {'cls': name, 'doc': None, 'methods': []} modDict['classes'].append(cDict) doc = inspect.getdoc(cls) if doc: cDict['doc'] = doc.strip().decode('utf-8') # メソッド情報を構築 for mName, mObj in inspect.getmembers(cls, inspect.ismethod): if mod != inspect.getmodule(mObj): continue mDict = {'method': mName, 'args': inspect.getargspec(mObj)[0], 'doc': None} cDict['methods'].append(mDict) doc = inspect.getdoc(mObj) if doc == None: continue mDict['doc'] = doc.strip().decode('utf-8') def _replace_special_char(self, text): ''' 渡されたテキスト中で、xml表現としての特殊文字("<", ">"など) が含まれる場合、それらを適切に変換する。 引数: text: 文字列 戻り値: 変換後のテキスト ''' if not text: return specials = [('<', '<'), ('>', '>')] for s, r in specials: if s in text: text = text.replace(s, r) return text def get_doc_str(self, chapterID, chapterTitle='class reference', tChapterStr=DEFAULT_CHAPTER_TEMPLATE, tModuleStr=DEFAULT_MODULE_TEMPLATE, tClassStr=DEFAULT_CLASS_TEMPLATE, tMethodStr=DEFAULT_METHOD_TEMPLATE): ''' docbook形式のxmlテキストを取得する。 このメソッドによって取得できる文字列すべての行が行頭から始まる。 引数: chapterName: チャプター名文字列。 戻り値: 文字列。 ''' tChapter=Template(tChapterStr.strip().rstrip()) tModule=Template(tModuleStr.strip().rstrip()) tClass=Template(tClassStr.strip().rstrip()) tMethod=Template(tMethodStr.strip().rstrip()) modStr = '' for modItem in self.modDictList: clsList = modItem['classes'] if len(clsList) == 0: continue clsStr = '' for clsItem in clsList: methodStr = '' for methodItem in clsItem['methods']: argList = [s for s in methodItem['args'] if s != 'self'] argStr = '' if len(argList) > 0: argStr = reduce(lambda a, b: a + ', ' + b, argList) funcStr = methodItem['method'] + '(' + argStr + ')' descStr = self._replace_special_char(methodItem['doc']) methodKW = {'method_name': methodItem['method'], 'method_synopsis': funcStr, 'method_desc': descStr} methodStr += tMethod.safe_substitute(methodKW) clsKW = {'template_method': methodStr, 'class_name': clsItem['cls'], 'class_desc': clsItem['doc']} clsStr += tClass.safe_substitute(clsKW) modKW = {'module_name': modItem['module'], 'template_class': clsStr} modStr = tModule.safe_substitute(modKW) chapKW = {'chapter_id': chapterID, 'chapter_title': chapterTitle, 'template_mod': modStr} text = tChapter.safe_substitute(chapKW) return text.encode('utf-8') # # if __name__ == '__main__': ''' テスト ''' if len(sys.argv) < 2: print 'argument error' sys.exit(0) gen = ModuleDocGenerator(sys.argv[1]) xmlDoc = gen.get_doc_str('class_ref') try: f = file('class_ref.xml', 'w') f.write(xmlDoc) f.close() except IOError, err: print err