golangでコマンドラインツールを作る #9 Compositeパターン
その8の続き
PBXGroup
Xcodeは実際のファイル構造じゃなくて好きな構造で表示できる。例えば実際のファイルは全部一つのフォルダに入ってたとしても、階層構造の表示できる。それを表現してるsectionがPBXGroup。で、PBXGroupはchildrenを持ってて、そこにはPBXGroupかPBXFileReferenceを持ってるって訳。
ん、確かこのパターンどこかで見たことあるぞ・・あっ!これ進研ゼミで習ったやつだ!!
ってことで、Compositeパターンを思い出したのでやってみた。
Compositeパターン
ディレクトリ構造とか再帰的に辿る的なやつのときに使うやつ。 これ→11. Composite パターン | TECHSCORE(テックスコア)
今回は、GroupっていうtypeとFileReferenceっていうtypeを同一視するために、interfaceを宣言する。
interface名悩むなぁ。Xcodeだと、グループとかファイルの情報ってFile Inspectorっていうペインで見れる。 FileInspectorっていう名前か、Fileか、それか参考サイトみたいにDirectoryEntryにするか、うーむ。 よし、GruopEntryにしよう。
GroupEntryはグループかどうかを教えてくれるIsGroup()と、その子供達を返すChildren()、そのEntryの情報を返してくれるDescription()あたりがあればよさそう。
とりあえずそんな流れでいこう。
// GroupEntry describes xcode project's entry // it is file or group type GroupEntry interface { IsGroup() bool Children(p Pbxproj) []GroupEntry Description() string }
Goの場合、interfaceを定義したらあとは同一視したいtype、今回でいうと、Group
とFileReference
でこのメソッドを実装してあげれば2つとも全然違うtypeなのに、GroupEntityとして処理できる。すばらしい。
実装の仕方は例えばIsGroupのやつはこんな感じ。レシーパーとしてそのstructを指定する。
func (g Group) IsGroup() bool { return true } func (f FileReference) IsGroup() bool { return false }
ここで、ポインタをつけた方がいいのかつかなくてもいいのかがよく分からなかった・・ 紹介されてる記事だとよくポインタついてたりして、その辺ちゃんと理解しないといけなそう。(そのときがきたら考える)
再帰処理をどうする?
で、再帰処理をどう書くか悩んでて、GoはOSSだったこと思い出した! filepath.Walkと同じことやればいいやと思って実装パクることにした。 いやー、OSSってすばらしい。普段、iOS書いてるからすごく素晴らしく感じる。
filepath.Walkの処理はここに書いてある。
go/path.go at master · golang/go · GitHub
今回のやつの場合、丁寧にerror返すみたいなところまでやってないからその辺削ってこんな感じで実装した。 表示するときに階層構造表したいから、levelも渡すようにした。
// WalkFunc is the type of the function called for each fileReference or // group visited by Walk. type WalkFunc func(entry GroupEntry, level int) // walk recursively descends group entry func (p Pbxproj) walk(entry GroupEntry, level int, walkFn WalkFunc) { walkFn(entry, level) for _, c := range entry.Children(p) { p.walk(c, level+1, walkFn) } } // Walk walks the xcode project tree rooted at root // calling walkFn for each group or file in the tree, including root func (p Pbxproj) Walk(walkFn WalkFunc) { for _, g := range p.groups { if g.isRoot() { rootLevel := 0 p.walk(g, rootLevel, walkFn) } } }
で、コマンドで利用する側は、こんな感じで好き勝手できる。
proj.Walk(func(entry pbxproj.GroupEntry, level int) { if level == 0 { return } for i := 1; i < level; i++ { fmt.Print(" ") } if entry.IsGroup() { fmt.Println("+ " + entry.Description()) } else { fmt.Println(" " + entry.Description()) } })
結果は、こんな感じ。それっぽい。今回は、グループの場合は、+表示してるけど、ほんとは色とか変えたり絵文字とか表示したりしたらXcodeでみてるやつっぽくなるかも?swiftだったら鳥の絵文字表示するとかw
+ Sample AppDelegate.swift ViewController.swift Images.xcassets + Supporting Files Info.plist + SampleTests SampleTests.swift + Supporting Files Info.plist + Products Sample.app SampleTests.xctest
ということである程度完成した。
ここまでの感想
- ポインタがよくわかんなくなってきた。つけてもつけなくても動いてるっぽいのなぜ
- 標準パッケージのソース見てたら意外と長い名前つけてる。forの変数は1文字にしないとと思ってたけどそんなことなさそう
- 調べ物してるときにforの中でselect使ってる構文があったけどなんだろうか、あれは。
- パッケージ名と変数名がかぶってたときってどうするんだ?
- classがないっていうの、ポリシーというか設計者の思想が分かればすっと理解できるかな?
- Enumないの?とふと思ってさらっと見てみたけど構文としてはなさそう