dgeni-markdownというngdoc系のドックコメントからマークダウン形式のドキュメントを生成するテンプレートを作成しました。
dgeni-markdownを作っている過程でdgeniの動きが分かってきたので、メモがてら解説していきます。
そもそもDgeniって何?という方はこちらを参照してください。
ディレクトリ構成
Dgeniで一般的なディレクトリ構成は下記のようになります。
docs
├── inline-tag-defs - インラインタグ定義
├── processors - プロセッサー
├── rendering
│ ├── filters - テンプレート内で呼び出すフィルタ定義({$ variable | filter$})
│ └── tags - テンプレート内で呼び出すタグ定義({% code %}...{% endcode %})
├── services - コンフィグやプロセッサにDIするサービス
├── tag-defs - タグ定義(@ngdoc ...)
├── templates - テンプレート
└──config.js - ドキュメントの設定ファイル
config.js内でrequireで読み込めれば良いため、独自にディレクトリを追加したり、使わないディレクトリを削除してしまって問題ありません。
プロセッサー
Dgeniはの処理はProcesserという実行器をつなげてしてドキュメントを生成しています。
はじめにreadFilesProcessorでソースファイルとテンプレートを読み込み、次のProcesserでドックコメントのアノテーションをパースしたり、ngdocのIDを生成したりして、最後にwriteFilesProcessorでドキュメントの書き出しを行います。
そのため何か独自のタグを定義したり、処理を拡張したい場合は新しいProcesserを作るだけで簡単に拡張することができます。
そのかわりに、テンプレート内で使用できる変数はProcesserに依存してしまうため、使える変数を確認するには最後の処理のwriteFilesProcessor内で確認するしかありません。
dgeni-packagesのngdocを実行したときのProcesserは下記の順序で実行されます。
- readFilesProcessor
- parseTagsProcessor
- filterNgDocsProcessor
- extractTagsProcessor
- codeNameProcessor
- computeIdsProcessor
- memberDocsProcessor
- moduleDocsProcessor
- generateComponentGroupsProcessor
- providerDocsProcessor
- computePathsProcessor
- renderDocsProcessor
- unescapeCommentsProcessor
- inlineTagProcessor
- writeFilesProcessor
サービス
DgeniではAngularJSと同様に引数に指定した名前に対応するサービスがDIすることができます。
module.config(function(dgeni, log) {
...
});
module.exports = function linkInlineTagDef(getLinkInfo, createDocMessage) {
...
}
さすがに全てのサービスを紹介するのは難しいため、今回使ったサービスを紹介していきます。
dgeni
dgeniサービスではdgeni全体に関わる設定を行います。
設定できるプロパティは2つのみでstopOnValidationErrorでValidationエラーが発生したさいに中断するかどうか、stopOnProcessingErrorでProcesserでエラーが発生したさいに中断するかどうかを設定することができます。
厳密にするなら常にtrueにしておくべきですが、Dgeniではエラーが発生しやすいので開発中はfalseにしておいた方が精神衛生上良かったりします。
log
logサービスでは出力するログレベルの設定とログの出力を行います。
ログレベルは下記になり、それぞれに対応したログの出力メソッドが存在します。
- silly
- input
- verbose
- prompt
- debug
- info
- data
- help
- warn
- error
readFilesProcessor
readFilesProcessorではドキュメントを生成するソースファイルの設定を行います。
var path = require('canonical-path');
readFilesProcessor.basePath = path.resolve(__dirname, '../libs/');
readFilesProcessor.sourceFiles = [
{
include: 'path/to/*.js',
basePath: readFilesProcessor.basePath
}
];
writeFilesProcessor
writeFilesProcessorでは生成したドキュメントの出力先の設定を行います。
writeFilesProcessor.outputFolder = path.resolve(__dirname,'../build');
templateFinder、templateEngine
templateFinderで読み込むテンプレートの設定、templateEngineでテンプレートで使用するフィルタやタグの設定を行います。
templateFinder.templateFolders = [path.resolve(__dirname, 'templates')];
// テンプレート名のファイルパターンを設定
// ここで指定したパターンに対応するテンプレートが読み込まれます
templateFinder.templatePatterns = [
'${ doc.template }',
'${ doc.area }/${ doc.id }.${ doc.docType }.template.html',
'${ doc.area }/${ doc.id }.template.html',
'${ doc.area }/${ doc.docType }.template.html',
'${ doc.id }.${ doc.docType }.template.html',
'${ doc.id }.template.html',
'${ doc.docType }.template.html'
];
// フィルターの追加
templateEngine.filters = templateEngine.filters.concat(getInjectables([
require('./rendering/filters/filter')
]));
// タグの追加
templateEngine.tags = templateEngine.tags.concat(getInjectables([
require('./rendering/tags/tag'),
]));
parseTagsProcessor
parseTagsProcessorではドックコメントで指定したタグ(@ngdogとか)の定義を追加します。
parseTagsProcessor.tagDefinitions =
parseTagsProcessor.tagDefinitions.concat(getInjectables(require('./tag-defs')));
inlineTagProcessor
inlineTagProcessorではドックコメントの文章中に指定したタグ(@linkとか)の定義を追加します。
inlineTagProcessor.inlineTagDefinitions =
inlineTagProcessor.inlineTagDefinitions.concat(getInjectables([require('./inline-tag-defs') ]));
computePathsProcessor
computePathsProcessorでは@ngdocに指定したdocTypeに合わせて使用するテンプレート、出力先のディレクトリ構成指定を行います。
computePathsProcessor.pathTemplates.push({
docTypes: ['provider', 'service', 'directive', 'input', 'object', 'function', 'filter', 'type' ],
pathTemplate: '${area}/${module}/${docType}/${name}',
outputPathTemplate: 'partials/${area}/${module}/${docType}/${name}.html'
});
computePathsProcessor.pathTemplates.push({
docTypes: ['module' ],
pathTemplate: '${area}/${name}',
outputPathTemplate: 'partials/${area}/${name}/index.html'
});
computePathsProcessor.pathTemplates.push({
docTypes: ['componentGroup' ],
pathTemplate: '${area}/${moduleName}/${groupType}',
outputPathTemplate: 'partials/${area}/${moduleName}/${groupType}/index.html'
});
getInjectables
getInjectablesではmodule.factory
などを使わずに、require
で読み込んだソースをdgeniで扱える形にします。
getInjectables([require('./inline-tag-defs') ])
factoryを使うべきか、getInjectablesを使うべきなのかいまいち使い分けはわかっていません。
作成したdgeni-markdownではfactoryを使うと上手く動かなかったので、getInjectableを使うように統一しています。
テンプレート
Dgeniのテンプレート定義はSmarty3ライクでextendsでテンプレートの継承や、includeでの部分読み込みが行えます。
{% include "lib/macros.md" -%}
{% extends "api/api.template.md" %}
{% block content %}
コンテンツ
{% endblock %}
条件分岐
条件分岐をおこなう場合は if 〜 elif 〜 else 〜 endifで行います。
{%- if doc.title -%}
{$ doc.title $}
{%- elif doc.module -%}
{$ doc.groupType $} components in {$ doc.module | code $}
{%- else -%}
{$ doc.groupType $}
{%- endif -%}
ループ
ループを行う場合は for 〜 endforで行います。
ループ時の改行処理の挙動が怪しいのでマークダウンのように改行が重要なフォーマットでは少し指定が難しくなります。
| Name | Description |
| :--: | :--: |{% for page in doc.components %}
| {$ page.name $} | {$ page.description | firstLine | marked | nobr $} |{% endfor %}
タグ
テンプレート内で指定するタグを拡張することができます。(Smartyでいうブロック)
module.exports = function(trimIndentation, encodeCodeBlock) {
return {
tags: ['code'],
process: function(context, lang, content) {
...
}
};
};
{% code %}this{% endcode %}
フィルタ
AngularJSと同様にテンプレート内で変数に対してフィルタをかけることができます。
module.exports = function() {
return {
name: 'br',
process: function(str) {
if (!str) {
str = '';
}
return str.replace(/[\n\r]{1,2}/g, ' ')
}
};
};
{$ doc.description | br $}
Dgeniを使う上で必要になりそうな情報をまとめてみましたが、いかがでしたでしょうか。
足りない情報はdgeni、dgeni-packages、dgeni-example、angularのリポジトリを見ると参考になると思います。
個人的にDgeniを触ってみた感想になりますが、AngularJSアプリケーションでのドキュメント生成をする上で一番優秀なドキュメント生成器ですね。
grunt-ngdocやgrunt-ngdocs、docularと比べると、手軽に使えるテンプレートがないため自作する手間がかかります。
しかし、その代わりに拡張がかなり容易であるため、タグのサポートががなかったり、出力するドキュメントが崩れていたりしても簡単に修正をかけれるという点がメリットになります。。
なにより本家AngularJSと同じドキュメント生成器を使えるため、フレームワークと同じスタイルでドックコメントが書けるというのはかなり嬉しいです。