頓挫したくないでござる。
3行でおk
- dartの依存解決は
importとpart+part ofでできてる -
part "path/to/something.dart";めんどい - DartのAST系のツールのanalyzer使って自動生成できるようにした
Dart歴的なの
ここ2週間ほど、Dartをさわさわして楽しんでます。つまりDart歴2週間です。
JSが駆逐されてDartがユーザーのブラウザ上で動く世界が来るかもしれないので(5年後くらいにもしかしたら・・・)、今からさわさわしててもいいんじゃね?的なノリでさわり始めたのですが、普段PHPとかJSとかメインでかいてるゆるふわ系言語エンジニアとしては、中々新鮮で、楽しめてます。
Dartについて
Dartについての紹介的なのは、きっとDart Advent Calendar 2014に何かしら書かれてたり、書かれたりすると思うのでそちらをご覧ください。
2013年のアドベントカレンダーと比較しても、随分盛り上がってますね!Dart!2015年にはDartが流行るかもですよ!
partなにこれ面倒くさい
ここから若干前振りが長くなりますが、Dartの依存関係の解決方法について説明します。
Dartの依存関係解決
Dartは非常にモジュール性が高い言語ですが、大きく2つの依存関係の解決方法が用意されています。
- import
- part
簡単にいうと、importは外部のライブラリ(package)の読み込みに使い、partは自分自身(package)の内部のファイルの解決に使われます。
詳しくはこのへんとか見てもらえればいいんですが、簡単におさらいしておきます。
something_libで理解するimport/part
/packages (依存してる外部パッケージ群)
/lib1 (外部パッケージその1: lib1)
/src
foo.dart
bar.dart
lib1.dart
/lib2 (外部パッケージその2: lib2[以下省略])
/lib (something_lib
/src
something_foo.dart
something_bar.dart
something.dart
何がしかライブラリ「something_lib」を作る場合、上記のようなディレクトリ構成になります。
ディレクトリ講師ついてはlaco0416さんのこちらの記事が大変勉強になります。
上記のようなパッケージの場合、編集するのはsomething_foo.dart something_bar.dart something.dart の3つになります。
something.dart がメインとなるファイルで、something_foo.dartとsomething_bar.dartは、その構成要素としてのファイル、パーツとしてのファイルというニュアンスになります。
import
外部のパッケージは全て/packages以下に格納されるので、lib1やlib2といった外部ライブラリを利用したい場合は、something.dartからlib1やlib2を import してあげます。
library something; //パッケージ名宣言
import "packages:lib1/lib1.dart"; //lib1をインポート
void main() { //メインの処理
//importしたライブラリが使えるようになる
Lib1 lib1 = new Lib1();
}
このように、packages:パッケージ名/公開ライブラリファイル の形式でimportすると、外部ライブラリを読み込む事ができます。
初めてこれを見たときに気になったのが 「lib1読み込むっていってんだから/lib1.dartまで書くのが面倒くさいなりぃ」 だったんですが、これは、ひとつのパッケージの中から、必要なライブラリだけを読み込む事ができるようになっていると考えると、利点になります。
ひとつのpackageの中に複数のlibraryがあるわけですね。
例えば、GithubクライアントのDartパッケージであるgithub.dartを見ると、以下の画像のように、ひとつのパッケージの中に、「browser.dart(ブラウザ用公開ライブラリ)」と「server.dart(サーバーサイド用公開ライブラリ)」があり、利用用途に合わせて必要なファイルだけをimportする事ができるようになってます。
ブラウザ用・サーバーサイド用はちょっと極端だとしても、巨大なパッケージの一部のライブラリだけを使う、みたいな事が比較的簡単にできるようになっている感じです。たぶん。
part
外部の場合はimportでしたが、自分自身のパッケージ内のファイルの依存関係の解決は part と part of と呼ばれる2つのキーワードで行います。それぞれ日本語に直すと
-
part- 私のパーツはこの子です
-
part of- 私はこの親のパーツです
こんな感じの事を宣言するシンタックスになってます。
コードを見たほうが分かりやすいと思います。
library something;
part "src/something_foo.dart";
void main () {
Foo foo = new Foo();
}
と
part of something;
class Foo() {
// something
}
このように、それぞれがsomethingライブラリに属しながらも、親の方が「こいつが俺の子供だぜ!」って言う感じです。逆にいうと、いくら子の方が「part of!! part of!!」と叫んでも、親のほうでpartしてないとガン無視です。可哀想な子。
また、子のほうが複数の親に属する事もできないので注意です。
[error] Only one part-of directive may be declared in a file といった感じで怒られます。
import/partまとめ
-
importは外部packageを読み込む -
partとpart ofで内部の依存関係を表す
つまりpartは面倒臭い
importとpartが理解できたところで、partの面倒臭さに気づいた人とはお友達になれそうです。
慣れると対して面倒臭くもなくなってきたんですが、最初はガッツリ面倒くさいオーラを出しながらタイピングしてました。
つまり、子ファイルが増えれば増えるほど、part祭りになってくるわけです。
実際のコードを見たほうが理解してもらえそうな気がするので、また先ほどと同じくgithub.dartさんに登場してもらいましょう。common.dartという子ファイルを読み込んでるDartファイルがあるのですが、これを見ると、いかに面倒くさいかが伝わる気がします。
最終的に73行目までpart 'hoge/hoge.dart';が続いてます。
ライブラリの規模として適切なのか?といった、設計の適切さみたいな話はひとまず置いておくにして、クラス毎にファイルを切り出すと、こんな感じになってしまいます。
というわけで 「こんなにpart書くのは面倒くさいなりぃ」 となってしまうわけです。
※ちなみにDartのパッケージをいくつか見てると、一つのファイルに複数のクラス置くのは別にお作法違反じゃない雰囲気もあります。
ならば自動生成だ
前振りが大分長くなってしまいましたが、書くのが面倒くさいなら自動生成すればいいじゃない!ということで、パッケージを書きました。
part_generator
https://pub.dartlang.org/packages/part_generator
Githubはこちら
使い方はREADMEにも書いてますが、Dartのパッケージ依存管理ツールであるところの pub を使ってインストール&コマンド実行します。
- install
pub global activate part_generator
- run
pub global run part_generator -r ライブラリのディレクトリルート -l ライブラリ名
何をやってくれるツールなのか
とりあえず作った程度なので大した機能は無いですが、-r で指定したディレクトリ以下の *.dart ファイルを探し、それらが -l で指定したライブラリ名の part of を持ってたら、part "そのファイルのパス.dart"; をコンソールに出力するだけの簡単なお仕事です。
ためしに part_generator 自身に実行してみると、こんな感じ。
本当にコンソールに出力するだけなんですね。ええ。
DartのAST(Abstract Syntax Tree:抽象構文木)
で!ようやくタイトルのASTが出てきました。
前振りが長くてすみませぬ。
*.dartファイルの一覧取得はDart開発チームご謹製の glob パッケージを利用してサクッと取得できるのですが、part of ほげほげなファイルを取得するにはどうしたらよいか、といったあたりの話でようやくASTが出てきます。
ASTはご存知のとおり、テキストファイルでいらしゃるところのソースコードを、何かいい感じのアレにしてくれた状態のやつです。詳しくないからggrks。
で、ASTをDartのコードからいい感じに扱えるようにする analyzer パッケージがこれまたDart開発チームご謹製でありますので、これを使ってglobで取得した*.dartファイルから、part of ほげほげなものをピックアップしたいと思います。
analyzerでdirectiveを取得する
実際のコードはGithubを参照してください
lib/src/part_of_file.dart
簡略化したものが以下になります。
import "package:analyzer/analyzer.dart" as analyzer;
//注: dartFIle は取得した*.dartのFileSystemEntity
//まずanalyzerでDartファイルをパースしてASTに変換
analyzer.CompilationUnit parsed = analyzer.parseDartFile(dartFile.path);
// directive を取得
analyzer.NodeList<analyzer.Directive> directives = parsed.directives;
//ほげほげライブラリのpart of のdirectiveを含むかどうかを確認する
bool isPartOf = directives.any((analyzer.Directive directive) {
//まずは part of かどうかを確認
if (!(directive is analyzer.PartOfDirective)) {
return false;
}
//part ofで指定されてるライブラリ名が指定のものかどうか
if ((directive as analyzer.PartOfDirective).libraryName.name != "ほげほげ") {
return false;
}
return true;
});
上から順に見ていきます。
analyzer.CompilationUnit parsed = analyzer.parseDartFile(dartFile.path);
まず、parseDartFile()メソッドに*.dartのファイルパスを渡すと、Dartファイルをパースして、全てオブジェクトにしてくれます。やったね!
このとき、CompilationUnitが返ってきます。直訳するとコンパイル単位。
このCompilationUnitはdirectivesとdeclarationsの2つのNodeListを持っていて、それぞれ、
-
directives
-
analyzerのDirectiveのインスタンスのリストになってる - 先頭に書く系のシンタックス
library/import/part/part of等が該当
-
-
declarations
-
analyzerのCompilationUnitのインスタンスのリストになってる -
directives以外のクラスの宣言等が該当
-
みたいな感じになってます。
また、PartDirective/PartOfDirective/LibraryDirective/ImportDirectiveなどそれぞれが別のクラスになっている感じです。
PartOfDirectiveを確認する
part ofが含まれるファイルかどうかを見たい場合は、parseDartFile()でパースしたCompilationUnitのdirectivesにPartOfDirectiveが含まれているかどうかを確認すれば良いわけです。
//anyはListにFuture(クロージャ)の返り値がtrueのものが含まれていればtrueを返すメソッド
bool isPartOf = directives.any((analyzer.Directive directive) {
//まずは part of かどうかを確認
if (!(directive is analyzer.PartOfDirective)) {
return false;
}
//part ofで指定されてるライブラリ名が指定のものかどうか
if ((directive as analyzer.PartOfDirective).libraryName.name != "ほげほげ") {
return false;
}
return true;
});
directive is analyzer.PartOfDirective が該当箇所になります。
(※isはinstance of的なやつです)
で、PartOfDirectiveはlibraryNameを持ってますので、あとは名前を比較してあげればいいわけです。
(directive as analyzer.PartOfDirective).libraryName.name != "ほげほげ" が該当箇所になります。
ちなみにこの.libraryNameもLibraryIdentifierというクラスのインスタンスです。
以上の事をするだけで、ASTを利用してpart ofが書かれたファイルを取得する事ができました。すばらしい。
あとは、これで対象のファイル群を適当なリストに突っ込んで、パスを出力してあげるだけのツールがpart_generatorになってます。
AST周りまとめ
- analyzer パッケージを使うと簡単にASTを扱える
- コンパイル単位から、directiveとdeclarationに別れる
- ひとつひとつがクラスになっているので、OO的に扱える
おわり
- 頓挫せずにすんでよかった
- Dart Teamご謹製パッケージがたくさんあって助かる
-
pub publish叩くだけで何も考えずに(Githubすら不要)パッケージ公開できたすごい。 - Dart日本ユーザー会とか無いんですかね


