頓挫したくないでござる。
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日本ユーザー会とか無いんですかね