golangでコマンドラインツールを作る #3 ファイルの中身を読み込む

f:id:masato47744:20150816150451p:plain

その2の続きです。

ひとまずファイルを読み込んでみよう

中身をパースとかの前にとにかくファイルが読み込めないといけない。 それっぽい記事を探す。インターネットすごい。

Go でファイルを1行ずつ読み込む(csv ファイルも) - Qiita

ふむ、なんとなく、bufio.Scannerで読み込むというのが推奨と書いてあるしよさそう。 このプログラムはコマンドラインの引数でファイルを指定しているけど、 今回は、codegangsta/cliを使ってるから、os.Argsではとれなそうな雰囲気する。 なので、本家のコマンドライン引数の取り方を調べる。

Argumentsというところにあった。親切だ。c.Args()[0]ってやればとれそう。

...
app.Action = func(c *cli.Context) {
  println("Hello", c.Args()[0])
}
...

これをもとに読み込んだやつをそのまま表示するやつ書いてみた。

func CmdList(c *cli.Context) {

    var fp *os.File
    var err error

    fp, err = os.Open(c.Args()[0])

    if err != nil {
        panic(err)
    }

    scanner := bufio.NewScanner(fp)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
    if err := scanner.Err(); err != nil {
        panic(err)
    }

}

panicってのはよく分かってないけど、とりあえず実行してみると、ファイルの中身をprintしてくれた!

あるセクションだけを読み込むということをやってみる

ここからはiOSの話だけど、project.pbxprojファイルはXcode上のプロジェクト構成を表現するファイル。なので、BuildSettingsから、Build Phase、はては、プロジェクトのファイルの並びなどたくさんの情報が入ってる。 それをパースしようって話。なので、まずは中のファイル構造がどうなってるかを調べてみる。

// !$*UTF8*$!
{
    archiveVersion = 1;
    classes = {
    };
    objectVersion = 46;
    objects = {

/* Begin PBXBuildFile section */
        53DA2D221B7CEAF900A18036 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DA2D211B7CEAF900A18036 /* AppDelegate.swift */; };
        53DA2D241B7CEAF900A18036 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DA2D231B7CEAF900A18036 /* ViewController.swift */; };
        53DA2D271B7CEAF900A18036 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 53DA2D251B7CEAF900A18036 /* Main.storyboard */; };
        53DA2D291B7CEAF900A18036 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 53DA2D281B7CEAF900A18036 /* Images.xcassets */; };
        53DA2D2C1B7CEAF900A18036 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 53DA2D2A1B7CEAF900A18036 /* LaunchScreen.xib */; };
        53DA2D381B7CEAFA00A18036 /* SampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53DA2D371B7CEAFA00A18036 /* SampleTests.swift */; };
/* End PBXBuildFile section */

略

だいたいこんな感じ。セクションは、コメントアウトみたいな形で Begin .... section〜 End .... section で区切られている。そして、それぞれは、jsonのような感じで key = value の形式で表現されてる。 ふむふむ、なんとなく規則性はありそうだしパースできそうな雰囲気。

いきなりは難しいので、とりあえず、Beginの文字列を見つけて、Endの文字列が来たら終わりにするみたいなことでセクションをパースしてみる。

逆引き文字列的なやつとリファランスを見つけた。

逆引きGolang (文字列)

strings パッケージ - golang.jp

これで、まずは、文字列があるかないかをチェックするやつを見てみる。多分、strings.Containsをやればよさそう。

Contains関数
func Contains(s, substr string) bool

Containsは、s内にsubstrがあるときtrueを返します。

   for scanner.Scan() {
        t := scanner.Text()
        if strings.Contains(t, "/* Begin") {
            fmt.Println(t)
        }
        if strings.Contains(t, "/* End") {
            fmt.Println(t)
        }

    }

こんな感じに書き換えると出力結果がこう。なんかそれっぽい。

/* Begin PBXBuildFile section */
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
/* End PBXNativeTarget section */
/* Begin PBXProject section */
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
/* End XCConfigurationList section */

ふむふむ。では、これらをsection名だけ表示するようにしてみよう。 /* Beginsection */を削除して表示すればOKっぽいけどどうやろうか。whiteスペースで区切って、必ず、3番目がそうなってるからそれでいいかな。

Fields関数
func Fields(s string) []string
Fieldsは、文字列sをひとつ以上の連続したホワイトスペースで分割し、sの部分文字列の配列を返します。sにホワイトスペースしか含まれていないときは空リストを返します。

ということでプログラムをこう書き換えて

   for scanner.Scan() {
        t := scanner.Text()
        if strings.Contains(t, "/* Begin") {
            fs := strings.Fields(t)
            fmt.Println(fs[2])
        }
    }

結果は、

PBXBuildFile
PBXContainerItemProxy
PBXFileReference
PBXFrameworksBuildPhase
PBXGroup
PBXNativeTarget
PBXProject
PBXResourcesBuildPhase
PBXSourcesBuildPhase
PBXTargetDependency
PBXVariantGroup
XCBuildConfiguration
XCConfigurationList

うむ。よさそうだ。

ここまでの感想

標準のリファレンスがとても分かりやすいと感じた。そして使いたいやつが揃ってる印象。 あと、昔GoTourやったときに、スライスってなんか難しいなって印象あったんだけど、適当に普通の配列っぽくやってみたら動いたしまぁいいかという感じ。 あと、いつのまにか、go installしただけでシェルを立ち上げ直さなくてもOKになった。なぞ。

その4