golangでコマンドラインツールを作る #4 ファイルをパースする
その3の続き
project.pbxprojのファイル形式
さて、どうやってパースしようかと考えて中身を見てみると key = valueの形でなんか何かの形式っぽいんだけど、
拡張子が変なやつだからXcode特有かと思ったらそうじゃなかった。
how to parse project.pbxproj
とかググってたら出てきた。
Xcode のファイルツリーを名前順に並べたいという話 (フェンリル | デベロッパーズブログ)
この中で以下のように書いてあった。
このファイルは古い plist 形式になっていて、読み書きが大変やりにくいわけです。
そこでさらに調べ見ると、plutil というコマンドがある事を知りました。このコマンドを使用すると古い plist を新しい XML 形式の plist に変換できます。これで project.pbxproj の読み書きが可能になります。
見た目は特殊っぽいけど、plist形式の一種だったんだ。それならMacにそういうコマンドがあっても不思議ではない。 で、plutil*1コマンドを見てみると、どうやらjsonにもformat変換ができるぽいぞ。
-convert fmt rewrite property list files in format fmt is one of: xml1 binary1 json
よし、ということで、plutil
コマンドでjsonに変換してそれをgoで扱えばいいかなという方針にする。
pluitilのコマンドはこんな感じ。
$ plutil -convert json -o tmp.json -r project.pbxproj
これで、tmp.jsonに人間でも読めるようにインデントされたjsonが出力される。
外部コマンドを実行する
Golangで外部コマンド実行するにはどうすればいいか調べる。ちゃんと用意されてるね。
あとは、コマンドを実行してみる。
json := "tmp.json" cmd := exec.Command("plutil", "-convert", "json", "-o", json, "-r", c.Args()[0]) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr cmd.Run()
go installして実行すると、tmp.jsonできてた
{ "classes" : { }, "objectVersion" : "46", "archiveVersion" : "1", "objects" : { "53DA2D1B1B7CEAF900A18036" : { "buildConfigurationList" : "53DA2D3B1B7CEAFA00A18036", 省略
おっけー、あとはこのjsonファイルをgoでパースするだけだ。
jsonをパースする
golangでは標準のjsonパースできるやつが用意されてるようだ。
やり方としては、パースするjsonの型が分かってればそれをせっせと定義して、Unmarshalすればいいらしい。 でも、pbxprojをjsonにしたやつよく見ると型がめちゃくちゃだった。
外観はこんな感じでkey名が固定されてそうなんだけど、
{ "classes" : { }, "objectVersion" : "46", "archiveVersion" : "1", "objects" : {} "rootObject" : "53DA2D141B7CEAF900A18036" }
objectsの中身がてんでバラバラ。しかもkey名が謎の識別子みたいなやつ。
"53DA2D271B7CEAF900A18036" : { "isa" : "PBXBuildFile", "fileRef" : "53DA2D251B7CEAF900A18036" }, "53DA2D351B7CEAFA00A18036" : { "isa" : "PBXGroup", "name" : "Supporting Files", "children" : [ "53DA2D361B7CEAFA00A18036" ], "sourceTree" : "<group>" }, 省略
isaってやつがとれればどれが何を指してるかは分かりそう。これはどうやってパースすればいいんだろうか・・ 型が分からないからそういうやり方でやらないといけないかも。
型が分からない場合のjsonのパース
標準パッケージのやり方はInterface{} ってやつで受けて値の型ごとに処理を分割するってやつらしい。 ライブラリで簡単にできる風な感じな bitly/go-simplejson · GitHub でやってみる。 Goで簡単にJSON形式を扱うパッケージ: go-simplejson - Qiitaを参考にあうる。
パッケージをgetする。
$ go get github.com/bitly/go-simplejson
使い方はこんな感じ。stringを取りたい場合は、こう。
v := js.Get("objectVersion").MustString() fmt.Println("objectVersion = " + v)
range
ってのでmapを回せる。
cs := js.Get("classes").MustMap() for k, v := range cs { fmt.Println(k + " = ") fmt.Println(v) }
rangeで回したあとの、中でまたmapがあった場合は、キャストがいる。 キャストの仕方は、xxxx.(キャストしたい型)てやるようだ。 map[string]interface{}ってなんじゃらほいって感じだ。
objects := js.Get("objects").MustMap() for _, m := range objects { for k, v := range m.(map[string]interface{}) { if k == "isa" { fmt.Println(k + " = " + v.(string)) } } }
project.pbxprojをjsonに変換して、それをパースして、各objectのisaを出力するところまできた。
======== classes ========= ======== objectVersion ========= objectVersion = 46 ======== archiveVersion ========= archiveVersion = 1 ======== rootObject ========= rootObject = 53DA2D141B7CEAF900A18036 ======== objects ========= isa = PBXNativeTarget isa = PBXBuildFile isa = PBXNativeTarget isa = PBXProject isa = PBXGroup isa = PBXGroup isa = PBXGroup isa = PBXBuildFile isa = PBXResourcesBuildPhase isa = PBXGroup isa = XCConfigurationList isa = PBXBuildFile isa = PBXFrameworksBuildPhase isa = PBXFrameworksBuildPhase isa = PBXFileReference isa = PBXBuildFile isa = PBXVariantGroup isa = PBXResourcesBuildPhase isa = XCConfigurationList isa = PBXFileReference isa = XCBuildConfiguration isa = PBXFileReference isa = PBXContainerItemProxy isa = PBXSourcesBuildPhase isa = XCBuildConfiguration isa = PBXGroup isa = PBXFileReference isa = PBXFileReference isa = XCBuildConfiguration isa = PBXBuildFile isa = PBXSourcesBuildPhase isa = PBXFileReference isa = XCBuildConfiguration isa = PBXGroup isa = PBXTargetDependency isa = PBXVariantGroup isa = PBXFileReference isa = PBXFileReference isa = PBXBuildFile isa = PBXFileReference isa = XCConfigurationList isa = XCBuildConfiguration isa = PBXFileReference isa = XCBuildConfiguration
あとは、これらのisaをうまい感じにグルーピングしていい感じに表現してあげればよさそう。
ここまでの感想
- OSコマンドの実行方法と、jsonのパースの仕方がなんとなく分かった。
- あと、変数名のつけかたの流儀がなかなかつかめない。なんか長い変数名にするのはgoっぽくないってのは聞いたことあるんだけど、 どのくらい短くすればいいのかが感覚としてつかめてない。
- golang開発してていいなって思ったのは、使ってない変数警告を出すってのはいいかも。
- atomの予測変換がかなり優秀。IDEかと思っちゃう。
- interfaceってのがよく出てくるけどよく分かってない。
.(string)
でキャストできるぽいけどこれ間違っててもランタイムエラーにならないのか?- assertionって言葉がよく出てくるけどこれはgolangにそういう仕組みがあるのか?Optional binding的な
- 使ってもらうときの依存関係はどこに書いておくんだろう?cocoapodsのPodfile的なのいらない?
- 小文字でprivate functionを表現するということを理解した。
- log.fatalとpanicの使いどころの違いと、io、ioutilの違いがよく分かってない。
参考URL
- json パッケージ - golang.jp
- JSONの処理 | build-web-application-with-golang
- simplejson - GoDoc
- Goで簡単にJSON形式を扱うパッケージ: go-simplejson - Qiita
- Go maps in action - The Go Blog
- Discussion of the Go Programming Language ()
- Go言語のパッケージのテストでプライベートな関数や変数を呼び出すには - memoメモ
- os パッケージ - golang.jp
*1:よく調べてみると同じか分からないけどperlのスクリプトのやつもあるっぽい。 plutil.pl for Windows/Linux