doxygenのソースコードに手を加えて自前のフォーマットで簡易データ出力
今更説明するまでも無いような気もするけど,doxygenは多言語に対応しているソースコードのドキュメンテーションツール.出力フォーマットはhtml,LATEX,RTF等に対応しており,コード中のコメントを元にいい感じのドキュメントを生成してくれる.
しかし高機能であれこれ出力できる反面,必要な情報だけを簡潔にまとめたドキュメントが欲しいなーという時にはやや使いづらい面もあったり.今回,クラスメンバの一覧だけをコード中から取得したいなーという用途で良いツールが無かったので,doxygenのコードに手を加えてみることにした.
まず自前ビルドの環境を用意
ソースからビルドする方法は以下に記載されているので,基本的にはこれ通り進めばOK.
ただしMSVCでビルドする場合,BOMなしのutf-8を正しく認識できていないらしく,多言語対応用のヘッダ群(translator_xx.h)でビルドに失敗する.とりあえず今回は日本語以外いらないので,lang_cfg.hで定義されているLANG_JP以外のdefineをコメントアウトしてビルドを通した.
コードを読んでみる
doxygenのmain関数はいたってシンプル.
int main(int argc,char **argv) { initDoxygen(); readConfiguration(argc,argv); checkConfiguration(); adjustConfiguration(); parseInput(); generateOutput(); return 0; }
parseInput()が終わった段階で,グローバル変数の中にパースされた情報が入っている.具体的にはdoxygen.cppの100〜152行目.
// globally accessible variables ClassSDict *Doxygen::classSDict = 0; ClassSDict *Doxygen::hiddenClasses = 0; NamespaceSDict *Doxygen::namespaceSDict = 0; MemberNameSDict *Doxygen::memberNameSDict = 0; MemberNameSDict *Doxygen::functionNameSDict = 0; FileNameList *Doxygen::inputNameList = 0; // all input files FileNameDict *Doxygen::inputNameDict = 0; GroupSDict *Doxygen::groupSDict = 0; FormulaList Doxygen::formulaList; // all formulas FormulaDict Doxygen::formulaDict(1009); // all formulas FormulaDict Doxygen::formulaNameDict(1009); // the label name of all formulas PageSDict *Doxygen::pageSDict = 0; PageSDict *Doxygen::exampleSDict = 0; SectionDict Doxygen::sectionDict(257); // all page sections CiteDict *Doxygen::citeDict=0; // database of bibliographic references StringDict Doxygen::aliasDict(257); // aliases FileNameDict *Doxygen::includeNameDict = 0; // include names FileNameDict *Doxygen::exampleNameDict = 0; // examples FileNameDict *Doxygen::imageNameDict = 0; // images FileNameDict *Doxygen::dotFileNameDict = 0; // dot files FileNameDict *Doxygen::mscFileNameDict = 0; // dot files StringDict Doxygen::namespaceAliasDict(257); // all namespace aliases StringDict Doxygen::tagDestinationDict(257); // all tag locations QDict<void> Doxygen::expandAsDefinedDict(257); // all macros that should be expanded QIntDict<MemberGroupInfo> Doxygen::memGrpInfoDict(1009); // dictionary of the member groups heading PageDef *Doxygen::mainPage = 0; bool Doxygen::insideMainPage = FALSE; // are we generating docs for the main page? FTextStream Doxygen::tagFile; NamespaceDef *Doxygen::globalScope = 0; QDict<RefList> *Doxygen::xrefLists = new QDict<RefList>; // dictionary of cross-referenced item lists bool Doxygen::parseSourcesNeeded = FALSE; QTime Doxygen::runningTime; SearchIndex * Doxygen::searchIndex=0; QDict<DefinitionIntf> *Doxygen::symbolMap; bool Doxygen::outputToWizard=FALSE; QDict<int> * Doxygen::htmlDirMap = 0; QCache<LookupInfo> *Doxygen::lookupCache; DirSDict *Doxygen::directories; SDict<DirRelation> Doxygen::dirRelations(257); ParserManager *Doxygen::parserManager = 0; QCString Doxygen::htmlFileExtension; bool Doxygen::suppressDocWarnings = FALSE; ObjCache *Doxygen::symbolCache = 0; Store *Doxygen::symbolStorage; QCString Doxygen::objDBFileName; QCString Doxygen::entryDBFileName; bool Doxygen::gatherDefines = TRUE; IndexList Doxygen::indexList; int Doxygen::subpageNestingLevel = 0; bool Doxygen::userComments = FALSE; QCString Doxygen::spaces; bool Doxygen::generatingXmlOutput = FALSE;
クラス情報はDoxygen::classSDict, 名前空間はDoxygen::namespaceSDictあたりを手繰っていけば,だいたいどこに自分の欲しい情報が入っているのかが分かる.ちなみにdoxygen内部のコードにはあまりdoxygen形式のコメントが付けられていない.公式のWish Listによると,doxygenのソースコードにdoxygenコメントを付けて欲しいという要望はあるが,それに対するDifficulty Levelは10レベル中7で「結構しんどい」らしい.
手を加える
内部では様々な出力フォーマットに対応するためにOutputGeneratorを継承したHtmlGeneratorやLatexGeneratorクラスが定義されているが,残念ながら多態的な実装はされておらず,各所にif-else節が散らばっているために自前のSubClassを定義しただけでは動作しない.めんどくさい….
とはいえ今回は一部の情報を取り出したいだけなので,適当なモジュールの中に標準出力へのプリントを挟む込み形にした.というわけでクラスに関するパース情報を持っているClassDefに対してoperator << を以下のように定義してやる.
#undef strlen #include <iostream> #include <string> #define strlen qstrlen namespace { static const char* sep = " "; static const int indent_width = 1; std::ostream& indent(std::ostream& os, int indent_level) { os << std::string(indent_width * indent_level, ' '); return os; } std::ostream& brief(std::ostream& os, const MemberDef& memberDef) { if (!memberDef.briefDescription().isEmpty()) os << sep << memberDef.briefDescription(); return os; } std::ostream& print(std::ostream& os, const MemberDef& memberDef, int indent_level = 0) { indent(os, indent_level) << memberDef.typeString() << sep << memberDef.name(); brief(os, memberDef); return os; } std::ostream& print(std::ostream& os, const ClassDef& def, int indent_level = 0) { MemberNameInfoSDict *dict = def.memberNameInfoSDict(); MemberNameInfoSDict::Iterator mnii(*dict); MemberNameInfo *mni; for (mnii.toFirst(); mni = mnii.current(); ++mnii) { MemberInfo *mi = mni->first(); while (mi) { MemberDef *md=mi->memberDef; ClassDef *d1 = md->getClassDef(); ClassDef *d2 = md->category(); print(os, *md, indent_level + 1); os << std::endl; ClassDef *def = Doxygen::classSDict->find(md->typeString()); if (def) print(os, *def, indent_level + 1); mi = mni->next(); } } return os; } inline std::ostream& operator << (std::ostream& os, const ClassDef& def) { os << def.name(); if (!def.briefDescription().isEmpty()) os << sep << def.briefDescription(); os << std::endl; return print(os, def); } }
#undef strlenは,内部で使っているqtoolsとMSVCとの衝突を避けるために入れている.上記のコードを,doxygen.cppのgenerateClassList関数の上に追加.そしてgenerateClassListの中に一行コードを追加する.
static void generateClassList(ClassSDict &classSDict) { ClassSDict::Iterator cli(classSDict); for ( ; cli.current() ; ++cli ) { ClassDef *cd=cli.current(); std::cout << *cd; // この一行を追加 // 以下略
これでビルドすれば,本来の機能を果たしつつ標準出力に何かしらの情報を吐き出せるdoxygenが出来る.今回のコードだと,こんなソースを解析すると
struct Bar { int k; ///< fugafuga. int l; ///< hogehoge. }; struct Foo { int i; short j; Bar bar; ///< bar. };
こんなのを吐き出すようになった.自分が欲しいのはCの構造体に対するテキストファイルだったので,メンバ関数の存在は考慮されていない.あと必要無かったので修飾子も吐いていない.手抜き.そこらへんも必要ならClassDefやMemberDefのgetterを使えば良いはず.
Bar bar int k fugafuga. int l hogehoge. Foo int i short j Bar bar bar. int k fugafuga. int l hogehoge.
今回大したコードは書いてないので,ちょっと手を加えればYAMLでもJSONでも好きな形式に変更するのは簡単.デフォルトだと解析途中にメッセージも標準出力に吐き出すので,DoxyfileでQUIET=YESにしておくと良い.
まとめ
コード解析してちょっと何か出力したいなーという時,良いツールが無ければdoxygenに乗っかると楽できるかも.