まーぽんって誰がつけたの?

iOS→Scala→インフラなおじさん技術メモ

MySQLで全く関係のないテーブルをJOINする

FROMでカンマで並べるだけ

SELECT * FROM A, B;

例えばゲームみたいなやつで、usersテーブルと、levelsテーブルがあって、usersとlevelsには何の関係もないけど、user一人一人に全レベルを結合したいみたいなとき。

SELECT users.name, levels.level FROM users, levels;

ってやれば、

name level
太郎 Level1
太郎 Level2
太郎 Level3
花子 Level1
花子 Level2
花子 Level3

CROSS JOIN

ググると、CROSS JOINってのも出てきたけどMySQLでは違うみたい。30歳にもなってこんなことも知らなかったです。。こういうのをデカルト積っていうらしい。

MySQL :: MySQL 5.6 リファレンスマニュアル :: 13.2.9.2 JOIN 構文

MySQL では、JOIN、CROSS JOIN、および INNER JOIN は構文上同等です (互いに置き換えることができます)。標準 SQL では、それらは同等ではありません。INNER JOIN は ON 句とともに使用され、CROSS JOIN はそれ以外のときに使用されます。

パイプしたときにlessコマンドでエスケープシーケンスの色表示を有効にする

例えば、$ docker logs hoge みたいな感じでログを見たいけど長くて画面におさまらないから、パイプしてlessに渡したいけどそうすると、色が出ないのでなんかないかなと思ったらあった。 -R をつければOK

$ docker logs hoge | less -R

golangでコマンドラインツールを作る #9 Compositeパターン

f:id:masato47744:20150816150451p:plain

その8の続き

PBXGroup

Xcodeは実際のファイル構造じゃなくて好きな構造で表示できる。例えば実際のファイルは全部一つのフォルダに入ってたとしても、階層構造の表示できる。それを表現してるsectionがPBXGroup。で、PBXGroupはchildrenを持ってて、そこにはPBXGroupかPBXFileReferenceを持ってるって訳。

f:id:masato47744:20150816220244p:plain

ん、確かこのパターンどこかで見たことあるぞ・・あっ!これ進研ゼミで習ったやつだ!!

ってことで、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、今回でいうと、GroupFileReferenceでこのメソッドを実装してあげれば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ないの?とふと思ってさらっと見てみたけど構文としてはなさそう

golangでコマンドラインツールを作る #8 ディレクトリを分けてリファクタする

f:id:masato47744:20150816150451p:plain

その7の続き

パッケージの分けかた

ファイル分けて処理を関数色々書いたけど、同じ名前にするとduplicateになる。 goの場合はpackageという概念があって、その中では同じ定義になるようだ。

ということで、別パッケージに分けようとしたら、そんなパッケージ見つからないよと言われた。 やったのは、普通にpbxprojっていうディレクトリ作って、その下にpbxproj.goを作って、package pbxprojって先頭にいれておく。 それで、他のファイルで、import "pbxproj"ってやった。

軽くググってみると外部に公開しないようなpackageはinternalってやつがGo1.4から導入されてmainのGoのpackageについては適用されるみたいな記事を見つけた。

golang - go1.4で追加されたinternal packageについて - Qiita

ただ、サブパッケージとか色々ググってたり、他のプロジェクト見てたら自分が間違ってることに気づいた。importするときに、"github.com/mpon/xgodeproj/pbxproj" みたいに github.comから始めないといけないようだ。 じゃあ、さっきのinternalってやつはなんなんだろうか。まぁ今は深追いはやめておこう。

structにメソッドを所属させる

packageは分けれたけど、インスタンス作成して、そいつに対してメソッドを呼び出していくみたいのやりたい。 structにメソッドを生やせるみたいのあったのを思い出した。これが参考になりそう。2年前だけど。

Go の interface 設計 - Block Rockin’ Codes

そして、structにコンストラクタもたせる方法。

golang - Goで見かける構造体パターン - Qiita

staticなfunctionとか作りたくなったりするけど、今はとりあえずutil.goっての作ってそこにfuncを定義しておいた。

これで、結構コマンドライン側のファイルからproject.pbxprojをパースするあたりを切り出せた。 pbxprojに、project.pbxprojのパスを渡すと、jsonとかパースしてそのインスタンスがデータを 持っておいてくれるみたいな感じ。golangではインスタンスっていっちゃいけないのかも。

// CmdShow for print sections
func CmdShow(c *cli.Context) {

    // find project.pbxproj path
    proj, found := findProjectPath()
    if !found {
        fmt.Println("Not found project.pbxproj file.")
        return
    }

    // get flags
    section := c.String("section")
    isSectionNotSet := section == ""

    // parse pbxproj
    pbxproj := pbxproj.NewPbxproj(proj)

略

ここまでの感想

  • packageの考え方が少しずつ分かってきた
  • structとstructにメソッドを所属させるところも分かってきた
  • internal packageってなんに使うんだろう?
  • static な func作りたくなった時はどうするのが正しいのか、そもそもそんなことしちゃいけない?
  • ClassとかOOPな考え方しかできなくてGoっぽい頭になるにはまだまだダメって感じ
  • Goやるならinterfaceをちゃんと使っていかないとダメっぽそう

その9

golangでコマンドラインツールを作る #7 ある拡張子のファイルを検索する

f:id:masato47744:20150816150451p:plain

その6の続き

project.pbxprojを探す

今はコマンドの引数でファイルを指定してる。でも、本当はproject.pbxprojって、xcodeprojの下にあるって決まってる。 なので、何も指定しなくても、コマンド実行ディレクトリ配下を再帰的に辿ってそれでパースして欲しい。 なので、golangでファイルを検索するやり方を調べる

再帰的にファイルを探す

早速よさそうなの見つけた。filepath.Walkでいけそう。

Go言語で指定したディレクトリ以下のディレクトリおよびファイルの一覧を取得する - taknb2nchのメモ

ルートパス指定するのに、カレントディレクトリどうやってとるんだろう。path.Base()でいいのかな? Getwd関数の方がそれっぽいかな。

とりあえず、こんな感じで隠しフォルダ以外を再帰的に探すようにできた。汚いけど。

   cur, err := os.Getwd()
    if err != nil {
        panic(err)
    }
    err = filepath.Walk(cur,
        func(path string, info os.FileInfo, err error) error {
            if info.IsDir() {
                if strings.HasPrefix(info.Name(), ".") {
                    return filepath.SkipDir
                }
                return nil
            }

            ext := filepath.Ext(path)
            if ext == ".pbxproj" {
                rel, err := filepath.Rel(cur, path)
                if err != nil {
                    panic(err)
                }
                proj = rel
            }
            return nil
        })

    if proj == "" {
        fmt.Println("Not found project.pbxproj file.")
        return
    }

あとは、Flagでpbxprojを指定できるようにしたい。

ここまでの感想

  • ファイル操作系は色々揃っててすごいやりやすい気がした

その8

golangでコマンドラインツールを作る #6 interface{}をstructにマッピングする

f:id:masato47744:20150816150451p:plain

その5の続き

PBXFileReference

isaが特定できるようになったので、どれか一つのisaのjsonをパースしてみる。 例えば、なんのファイルがあるかのisaはPBXFileReferenceでこれは必ずこういう形をしてる。(※進めていくうちに気づいたけどしてなかった。)

{
      "path" : "AppDelegate.swift",
      "isa" : "PBXFileReference",
      "lastKnownFileType" : "sourcecode.swift",
      "sourceTree" : "<group>"
}

これなら型にできそう。

golangの型

golangにはクラスという概念がないっぽい。その代わりに構造体を使って型を作れるって感じで合ってるのかな? よく分からないけどそんな雰囲気と仮置きしておいてとりあえず先に進む。

構造体を定義する。

type FileReference struct {
    path              string
    lastKnownFileType string
    sourceTree        string
}

構造体を初期化するやり方は色々あるようだ。これが参考になる。[Go] 構造体の初期化方法まとめ - Qiita で、初期化しようと思ってるんだけど、ループで回してるバリューがinterface{}なのでキャスト、どうすればいいかわからない。 ので、調べてみる。

interface{}のキャスト

interface{}をある型として扱うっていうやつなんかうまくいってたから、適当にv.(string)とかやってたけど これがまさにそうだったらしい。ここにやり方まとまってる。Golang: interface{}, type assertions and type switches 全部で3パターンやり方がある。 俺が今までやってたのは危険なキャストの仕方を使ってたようだ。間違えると、panicを起こす。 panic: interface conversion: interface is string, not map[string]interface {} 安全なキャストは、第2返却値をもらうようにする。キャストがうまくいかなかったかが分かる。 もう一つはswitchで考えうる限りの型チェックもできる。TPOによって使えばよさそう。

var a interface{} = "string"
s := a.(string) // キャストできたかチェックできない
s, ok := a.(string) // okにtrue、falseが帰ってきてキャストできなければチェックできる
switch v := a.(type) {
  case string:
    fmt.Println(v)
  case int32, int64:
    fmt.Println(v)
  default:
    fmt.Println("unknown")
}

swiftのas?に似てるかもしれない。

let a: AnyObject = "string"
if let s = a as? String {
    println(s)
} else if let i = a as? Int {
    println(i)
}

で、いざやってみたらやっぱりPBXFileReferenceは決まった型じゃないことが分かった。 mapのkeyがないとpanicになるようだ。 これも、さっきのキャストみたいに、第2返り値で安全にチェックできるようだ。 ということで新しい型を定義

type FileReference struct {
    name              string
    path              string
    lastKnownFileType string
    includeInIndex    string
    explicitFileType  string
    sourceTree        string
}

そして、mapでとるときに安全にとる。安全にとりつつなければデフォルト値とかってmapでできるのかな? なんかちゃっと調べたけどなさそう。なので、これも自分で書く。 ジェネリクスみたいのできないのかな。

func lookupStr(m map[string]interface{}, k string) string {
    if v, found := m[k]; found {
        if s, ok := v.(string); ok {
            return s
        }
    }
    return ""
}

そして、ようやくそれっぽいのができた。

コマンドラインのフラグで分ける

で、今のままだとセクションを指定してその情報を見るみたいなことができない。 こうやりたい。

$ xgodeproj show project.pbxproj --section PBXFileReference

どうやら、これはcodegangsta/cliの仕組みなのかgolangの仕組みか分からないけど、 Flagというやつでできるっぽい。 やり方は簡単。commands.goのFlagsに以下のように追加する。

var Commands = []cli.Command{
    {
        Name:   "show",
        Usage:  "Prints section names or each section information",
        Action: command.CmdShow,
        Flags: []cli.Flag{
            cli.StringFlag{
                Name:  "section",
                Value: "",
                Usage: "section name for pbxproj",
            },
        },
    },
}

使う側では、こんな感じで取得できる。

section := c.String("section")

この辺りを組み合わせて結果的にこうなった。それっぽくなってきたぞ。

$ xgodeproj show project.pbxproj --section PBXFileReference
SampleTests.xctest
Base.lproj/LaunchScreen.xib
Images.xcassets
Base.lproj/Main.storyboard
AppDelegate.swift
Sample.app
Info.plist
Info.plist
SampleTests.swift
ViewController.swift

ここまでの感想

  • interfaceに対して実装する、継承できないっていうのswiftのPOPに似てるかもと思った。
  • キャストのやり方分かった。
  • 安全にやるやり方は、たいてい、xxx, ok := process() みたいな感じってことが分かってきた
  • switchが便利だ。if-elseの代わりになる
  • structをnewするやり方がいっぱいあるのは逆にやめてほしい

その7

golangでコマンドラインツールを作る #5 配列をまとめる

f:id:masato47744:20150816150451p:plain

その4の続き

セクション名をまとめて表示する

READMEにこうなってほしいコマンドを先に書いた。 とりあえずどんなセクションがあるかは見たいかなと思ったので、

xgodeproj show project.pbxprojってやったらセクション名一覧が表示されるといいかもと思ったのでそれをやってみる。

goで配列を作って、重複を許さないようにして、適当にsortして表示するみたいなのやりたい。 このサイトよさそう。

配列の定義の仕方

配列の定義の仕方から調べないとだけど、固定長のやつのことを配列っていって、可変長のやつをスライスっていうらしい。 スライスを使っといた方がいいってことで、こう宣言する。

a := []string{}

で、あとは、これにisaのvalueを代入してく。append(スライス, 値)って書いてこいつが新しい配列を返してくる。 そして、それを自分に代入する。これはなんか直感的じゃない気がするな。

a = append(a, v)

最後に重複を取り除く。 これは、 golang-setってライブラリで実現できるようだ。

s := set.NewSetFromSlice(a)

しかし、[]stringが入らない。[]interface{}じゃないとダメなようだ。うーむ。

スライスから重複を取り除く

ということで重複を取り除く別の方法探す。パフォーマンスとか全く考えずにすでに配列に存在してたら、 追加しないでいいかと思って、containsみたいな関数ないかなと思って探してもないっぽい。 ありそうなのに標準で用意しないっていうのは何かポリシーでもあるんだろうか。

go - Contains method for a slice - Stack Overflow

しょうがないので、contains関数作る。

func contains(s []string, e string) bool {
    for _, a := range s {
        if a == e {
            return true
        }
    }
    return false
}

そして、愚直にisaのキーの値を重複なしで突っ込む。

for k, v := range mm.(map[string]interface{}) {
    if k == "isa" && !contains(ss, v.(string)) {
        ss = append(ss, v.(string))
    }
}

ソートもしておきたいのでどうやるのかなーと見ると、これは標準のパッケージがあるようだ。 これでOK。破壊的メソッドしかないっぽいのが気になる。

sort.Strings(s)

そして、重複抜きでそれっぽいのできた。

$ xgodeproj show project.pbxproj
PBXBuildFile
PBXContainerItemProxy
PBXFileReference
PBXFrameworksBuildPhase
PBXGroup
PBXNativeTarget
PBXProject
PBXResourcesBuildPhase
PBXSourcesBuildPhase
PBXTargetDependency
PBXVariantGroup
XCBuildConfiguration
XCConfigurationList

ここまでの感想

  • GoTourでスライスってなんか難しい気がしてたけど、可変長の配列ってだけ覚えてればよさそう?
  • golang-setがないって言われて気軽のgo getすればいいっての、だんだん慣れてきた。
  • interface{}っていうのが何でも入る可能性のある型みたいに使われてるってことが理解できてきた。
  • containsみたいなどう見ても使いそうなやつが用意されてないのなんでだろう?
  • 結構破壊的なメソッドが多いというかそれしか用意されてない感じな気がする
  • scalaのvalみたいな再代入禁止な宣言ができないの違和感感じた
  • スライスをfilter関数とかmap関数とかやりたいけどないのもそういうポリシーなのかな?

その6

golangでコマンドラインツールを作る #4 ファイルをパースする

f:id:masato47744:20150816150451p:plain

その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で外部コマンド実行するにはどうすればいいか調べる。ちゃんと用意されてるね。

exec パッケージ - golang.jp

あとは、コマンドを実行してみる。

   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 パッケージ - golang.jp

やり方としては、パースする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の違いがよく分かってない。

その5

参考URL

*1:よく調べてみると同じか分からないけどperlスクリプトのやつもあるっぽい。 plutil.pl for Windows/Linux

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

golangでコマンドラインツールを作る #2 ビルドしてコマンドを実行する

f:id:masato47744:20150816150451p:plain

その1の続きです。

開発環境

早速開発してみる。まずは、開発環境を何にするか問題。 IDEはちょっと大変かなってことでatomでやってる人もいそうなので、それでやってみる。

ATOMでGolangの環境設定 - Qiita

を参考にとにかく入れてみる。go-plusのsettingsが出てこなかったけど再起動したらいけた。 多分syntaxハイライトできてるしよさそう。

開発の進め方

Go言語のインストール - golang.jpにのってたHow to Write Go Code - The Go Programming Languageに、go installみたいなの書いてあるから開発したら、これで確認してくのかな?

ためしに、list.goに以下のようにimport文でfmtをimportしてみるように書いて保存すると、、強制的に元に戻される。なんじゃこりゃw

import(
    "fmt"
)

書き方が間違ってるから強制的に元に戻っちゃうのかな?よくわからん。書き換えてないのに、go-plusのところにはwarningでてるし。

色々悩んでいてふとひらめいた。fmtを使ってないのに、fmtをimportしようとしてるからなんか整理されちゃってるんじゃないかって。 きっと、色々プラグイン入れたからうまいこと保存時にフォーマットされるようになってるんじゃないかと予想。ためしに、関数の中に、fmt.Println("")ってのを書いておいたら大丈夫だった。 ふむ、余計なものをimportするなってことか。

あと、go-plusのエラーで exported function CmdList should have comment or be unexportedって出てる。 うーん。documentかけってことかなと思って色々javadoc的なもの書いてみても消えない。 waning messageに関数名から始めろみたいに書いてあるからまさかと思って、CmdListから始めてみたら・・

// CmdList print list
func CmdList(c *cli.Context) {

warning消えたw

なんかわかってきたかも知れない。ためしに、list.goを以下のように書いてgo installしてみる。

package command

import (
    "fmt"

    "github.com/codegangsta/cli"
)

// CmdList prints list
func CmdList(c *cli.Context) {
    // Write your code here
    fmt.Println("hello world")
}

そして、そのまま xgodeprojって打っても not foundになっちゃうから別シェル立ち上げて打ったら出てきた!

$ xgodeproj list
hello world

よし、なんかいけそうだ。

ここまでの感想

Atomでやってみはじめたけど、予測変換出てくるし、gofmtがあるから勝手にそれっぽくしてくれる。 あとは、ビルド方法が go installして別シェル立ち上げてってのがなんか面倒だな。きっともっといい方法あるんだろうな。 あと、テストをどう書けばいいか想像つかないけどそれは後回しにしよう。

その3

golangでコマンドラインツールを作る #1 gcliをインストールする

なぜ作ろうと思ったか

Go Tourは昔ちょっとやったことあったけどほとんど覚えてないってレベル。でも、これで発表することになったからやるぞってなった。

んで、題材が思いつかなかったけど最近iOSでxcodeproj/project.pbxprojがよくコンフリクトしてるから、あれをコマンドラインでさらっとパースできないかなと思ったので作ることにしてみた。

f:id:masato47744:20150816150451p:plain

何から調べるか

なんか、go getとかGOHOMEとか色々後戻りできない感じがあったようななかったような感じがしたがなんかその辺調べるのめんどくなってきた。とりあえずコマンドラインツールが作れればいいのでと思ってこれを見つけた。

そういえば社内の勉強会でcodegangsta/cli · GitHubよりよさそうみたいなこと言ってた気がするのでこれでやってみるかということに。

まずはGitHubレポジトリを作る

雰囲気出るし。xocdeprojとGolangを組み合わせて、xgodeprojにしてみた。ダサいw

そしたらこれで作ってみる

tcnksm/gcli · GitHub

どうやってインストールすればいいのか。ってかGo入ってんのか?俺の環境。

$ go
zsh: command not found: go

多分入ってない?

Goをインストールする

$ brew install go
==> Downloading https://homebrew.bintray.com/bottles/go-1.4.2.yosemite.bottle.2.
######################################################################## 100.0%
==> Pouring go-1.4.2.yosemite.bottle.2.tar.gz
==> Caveats
As of go 1.2, a valid GOPATH is required to use the `go get` command:
  https://golang.org/doc/code.html#GOPATH

You may wish to add the GOROOT-based install location to your PATH:
  export PATH=$PATH:/usr/local/opt/go/libexec/bin
==> Summary
🍺  /usr/local/Cellar/go/1.4.2: 4566 files, 155M
$ go version
go version go1.4.2 darwin/amd64

多分これでいいのかな。

gcliを取り込む

よくわからないけど多分、go getみたいのすればよさそう。READMEを読もう。Installationで説明してくれてる素晴らしい。 GOPATHとか設定されてないけどとりあえずやってみよう。

$  echo $GOPATH

$  echo $GOROOT

とりあえずなんも入ってない。go get してみると、

$ go get -d github.com/tcnksm/gcli
package github.com/tcnksm/gcli: cannot download, $GOPATH not set. For more details see: go help gopath

go help gopathって書いてある。読んでもすっと頭に入ってこない。 gopath調べるとよさそうな記事発見。

GOPATH は適当に決めて問題ない - Qiita

$HOME/go$HOME/.goが多いようだ。$HOME/.goにしよう。

zshrcの追加。

export GOPATH=$HOME/.go

そして、go getしてみる。が、エラー。

$ go get -d github.com/tcnksm/gcli
# github.com/tcnksm/gcli/skeleton
/Users/mpon/.go/src/github.com/tcnksm/gcli/skeleton/template.go:34: undefined: Asset

GOPATHの下を見ると、何かができてる感は感じられる。

$ ll $GOPATH/*
/Users/mpon/.go/pkg:
total 0
drwxr-xr-x  4 mpon  staff   136B  8 13 18:19 darwin_amd64

/Users/mpon/.go/src:
total 0
drwxr-xr-x  7 mpon  staff   238B  8 13 18:19 github.com
drwxr-xr-x  3 mpon  staff   102B  8 13 18:19 golang.org

Go言語 + cli-init でコマンドラインツールを作る | Developers.IOを真似して、

$ cd $GOPATH
$ go get -d github.com/tcnksm/cli-init
$ make install

をやってみた。けど、make installでこけた。

cd skeleton; go-bindata -pkg="skeleton" resource/...
/bin/sh: go-bindata: command not found
make: *** [install] Error 127

エラーメッセージでググって、go-bindataはなんか別のライブラリかな?

jteeuwen/go-bindata · GitHub

Installしてみる。-uってなんだろう。まぁいいか。

$ go get -u github.com/jteeuwen/go-bindata/

これをやったあともやっぱり、make installでこける。これは、issueないかなと思ったらあったw

/bin/sh: go-bindata: command not found · Issue #23 · tcnksm/gcli · GitHub

$GOPATH/binを$PATHに追加したらいけた!

$ make install
go get -v golang.org/x/tools/cmd/vet
go get -v github.com/golang/lint/golint
go get -v github.com/jteeuwen/go-bindata/...
go get -v -d -t ./...
cd skeleton; go-bindata -pkg="skeleton" resource/...
go install -ldflags "-X main.GitCommit \"$(git describe --always)\""

そして、この$GOPATHの下はgithub.comとかなってて、gcliのREADME読んでて、この$GOPATH/srcの下にに自分のソースコードを作るみたいなことが分かった。

gcliを実行してみる

cd $GOHOME/src/github.com/mpon/xcodeprojに移動して、READMEに沿ってコマンドをたたく。 なんか、codegangstaとかFrameworkが選べるってやつはよく分からなかったから指定しなかった。

gcli new -c list xgodeproj
  Created xgodeproj/version.go
  Created xgodeproj/command/list.go
  Created xgodeproj/CHANGELOG.md
  Created xgodeproj/commands.go
  Created xgodeproj/command/list_test.go
  Created xgodeproj/main.go
  Created xgodeproj/README.md
====> Successfully generated xgodeproj

そしたらxgodeprojがまるまる出来てしまった。そうか、こっちが先だったっぽい。なので、直下にファイルが来るようにmvでせっせと移動する。

出来上がったファイルをmain.goみてみたら、github.com/codegangsta/cliが書いてあるから、何も指定しないとこれになるんだね。あと、READMEとかcommands.goの自分のGitHub名が本名になっててgit configのuser.nameとかみてるのかな?その辺を直した。

テンプレで出来たコマンドが実行できるか確認してみる

$ go build github.com/mpon/xgodeproj
../.go/src/github.com/mpon/xgodeproj/commands.go:7:2: cannot find package "github.com/codegangsta/cli" in any of:
    /usr/local/Cellar/go/1.4.2/libexec/src/github.com/codegangsta/cli (from $GOROOT)
    /Users/mpon/.go/src/github.com/codegangsta/cli (from $GOPATH)
$ go get github.com/codegangsta/cli

go buildしてみるとcodegangsta/cliがないっぽいのでgo getする。 再びgo build するとディレクトリが出来上がった。 そしてコマンドを実行。

$ ./xgodeproj
NAME:
   xgodeproj -

USAGE:
   xgodeproj [global options] command [command options] [arguments...]

VERSION:
   0.1.0

AUTHOR(S):
   Masato Ohshima

COMMANDS:
   list
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h       show help
   --version, -v    print the version

なんか、よさそう。

その2

Swift1.2でenumにジェネリクス使ってその型を束縛できないって言ってるやつの意味が分かった

少し前のことだけど、今のプロジェクトが始まった頃にコード書いてて理解できたのでメモ。

ジェネリクスがない場合

成功か失敗か返すのにEnumを作るとしてジェネリクスがないとこうなっちゃう。

enum Result {
    case Success(AnyObject)
    case Failure(NSError)
}

使う方としては、

let result: Result = .Success("hoge")

switch result {
case .Success(let obj):
    if let str = obj as? String {
        // objがAnyObjectなのでキャストできるか調べないとダメ
        // しかも今回はStringが返ってくるってことを覚えておかないといけない
        println(str) 
    }
case .Failure(let error):
    // こっちはNSErrorって分かってる
    println(error.description)
}

ジェネリクスの場合

こうしたい。

enum Result<T> {
    case Success(T)
    case Failure(NSError)
}

使う方としてはこうあってほしい。

let result: Result = .Success("hoge")

switch result {
case .Success(let str):
        // strはString型って分かってるからキャストいらない
        println(str) 
    }
case .Failure(let error):
    // こっちはNSErrorって分かってる
    println(error.description)
}

でも、コンパイルエラーでダメ

Unimplemented IR generation feature non-fixed multi-payload enum layout

ジェネリクスに渡す値をクラスにする

堅牢で使いやすいAPIクライアントをSwiftで実装したいを参考にBoxを用意してあげる。こう書く。なるほど。

public class Box<T> {
    public let value: T
    
    public init(_ value: T) {
        self.value = value
    }
}

public enum Result<T> {
    case Success(Box<T>)
    case Failure(Box<NSError>)
    
    public init(_ value: T) {
        self = .Success(Box(value))
    }
    
    public init(_ error: NSError) {
        self = .Failure(Box(error))
    }
}

使うほう

let result: Result = .Success(Box("hoge"))

switch result {
case .Success(let box):
        // boxに包まれてる値であるvalueはStringて分かってる
        println(box.value) 
    }
case .Failure(let box):
    // こっちはNSErrorって分かってる
    println(box.value.description)
}

SwiftでClassを作るときにNSObjectを継承するべき?

どっちを採用するべき?

class Animal {}
// or 
class Animal: NSObject {}

こっちのにしておけばとりあえず間違いないとか、こっちを使うとこんな時に困るとかそういう指針が知りたい。

よさそうな回答

stackoverflow.com

  • NSObjectを採用する時

  • Nativeクラスでいいとき

Swift classes that are subclasses of NSObject:

  • are Objective-C classes themselves
  • use objc_msgSend() for calls to (most of) their methods
  • provide Objective-C runtime metadata for (most of) their method implementations

Swift classes that are not subclasses of NSObject:

  • are Objective-C classes, but implement only a handful of methods for NSObject compatibility
  • do not use objc_msgSend() for calls to their methods (by default)
  • do not provide Objective-C runtime metadata for their method implementations (by default)

Subclassing NSObject in Swift gets you Objective-C runtime flexibility but also Objective-C performance. Avoiding NSObject can improve performance if you don't need Objective-C's flexibility.

With Xcode 6 beta 6, the dynamic attribute appears. This allows us to instruct Swift that a method should use dynamic dispatch, and will therefore support interception.

public dynamic func foobar() -> AnyObject {}

Objective-CからSwiftを利用するときには必ずNSObjectをみたいな話

stackoverflow.com

No need for NSObject. Just say public init() {}. – matt Feb 15 at 5:24

これ見るとそんなことする必要ないよって書いてある。

まとめ

native classのままでその方がパフォーマンスがよさそう。なので基本的にNSObjectを継承しない方向で必要な場合は継承するっていうスタンスでOKぽい。

実プロジェクトにおけるSwiftっぽい書き方集が欲しい

勉強会とか開かれないかなー。こんなのを知りたい。自分用メモ。

  • UITableViewのDataSource、DelegateジェネリクスSwiftっぽく分離する。
  • UITableViewのSectionをEnumでうまいこと扱う
  • Structの使いどころ
  • Protocolの使いどころ
  • カテゴリ拡張をProtocol Extensionで代替する
  • viewWillAppearでviewDidLoad並みに1回だけ処理する
  • モックとかスタブとかをライブラリ使わずに書けるみたいなこと
  • Stringで指定してる部分をEnum
  • Enumに値をバインドして使うのはどんなとき
  • 異なるサブクラスreturnする関数
  • ArrayにProtocolを満たしたものだけを突っ込む

・・など

ヒントとなりそうな記事

Swiftでprivateメソッドのユニットテストをどうするか

こんな話は何年も前から語られてて答えも出てると思うけど、色々な意見を聞いて自分の考えが少しずつまとまってきたので残しておく。

聞いた意見

  • publicメソッドをテストすればprivateメソッドのテストも行われていることになる
  • リフレクションやモックなど使えばテストできるかもしれないけどテストをされている状態のほうがマシなのでpublicにしてしまうという手もある
  • そもそもテストしたいと思うということはそこに重要なロジックが入ってる
  • privateなメソッドがテストできないことに悩むのはTDDじゃない証拠
  • 変更後の状態をassertできない場合は確認するためのpublicメソッドを作る。例えアプリからは使われていなくてもテストからは利用してるのだから、テストも一つのアプリとして考えれば良い。

なんとなくまとめ

こういう話を聞いてなんとなく、もっとテストのために楽してもいいんだなという気になった。テストのためにメソッド作るとか元も子もないのかとか考えちゃってたけどテストされてないよりは全然いいよなと。 なのでテストのための確認メソッド作ったり、privateメソッドをpublicにすることにあまり抵抗を感じないでやっていこうという考えに至った。