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パターン
その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ないの?とふと思ってさらっと見てみたけど構文としてはなさそう
golangでコマンドラインツールを作る #8 ディレクトリを分けてリファクタする
その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) 略
ここまでの感想
golangでコマンドラインツールを作る #7 ある拡張子のファイルを検索する
その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を指定できるようにしたい。
ここまでの感想
- ファイル操作系は色々揃っててすごいやりやすい気がした
golangでコマンドラインツールを作る #6 interface{}をstructにマッピングする
その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するやり方がいっぱいあるのは逆にやめてほしい
golangでコマンドラインツールを作る #5 配列をまとめる
その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関数とかやりたいけどないのもそういうポリシーなのかな?
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
golangでコマンドラインツールを作る #3 ファイルの中身を読み込む
その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の文字列が来たら終わりにするみたいなことでセクションをパースしてみる。
逆引き文字列的なやつとリファランスを見つけた。
これで、まずは、文字列があるかないかをチェックするやつを見てみる。多分、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名だけ表示するようにしてみよう。
/* Begin
とsection */
を削除して表示すれば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になった。なぞ。
golangでコマンドラインツールを作る #2 ビルドしてコマンドを実行する
その1の続きです。
開発環境
早速開発してみる。まずは、開発環境を何にするか問題。 IDEはちょっと大変かなってことでatomでやってる人もいそうなので、それでやってみる。
を参考にとにかく入れてみる。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
して別シェル立ち上げてってのがなんか面倒だな。きっともっといい方法あるんだろうな。
あと、テストをどう書けばいいか想像つかないけどそれは後回しにしよう。
golangでコマンドラインツールを作る #1 gcliをインストールする
なぜ作ろうと思ったか
Go Tourは昔ちょっとやったことあったけどほとんど覚えてないってレベル。でも、これで発表することになったからやるぞってなった。
んで、題材が思いつかなかったけど最近iOSでxcodeproj/project.pbxprojがよくコンフリクトしてるから、あれをコマンドラインでさらっとパースできないかなと思ったので作ることにしてみた。
何から調べるか
なんか、go getとかGOHOMEとか色々後戻りできない感じがあったようななかったような感じがしたがなんかその辺調べるのめんどくなってきた。とりあえずコマンドラインツールが作れればいいのでと思ってこれを見つけた。
そういえば社内の勉強会でcodegangsta/cli · GitHubよりよさそうみたいなこと言ってた気がするのでこれでやってみるかということに。
まずはGitHubのレポジトリを作る
雰囲気出るし。xocdeprojとGolangを組み合わせて、xgodeprojにしてみた。ダサいw
そしたらこれで作ってみる
どうやってインストールすればいいのか。ってか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調べるとよさそうな記事発見。
$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
はなんか別のライブラリかな?
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
なんか、よさそう。
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 {}
こっちのにしておけばとりあえず間違いないとか、こっちを使うとこんな時に困るとかそういう指針が知りたい。
よさそうな回答
NSObjectを採用する時
- Objective-Cのクラス
- メソッドを呼ぶ時にobjc_msgSend()を使ってる
- Objective-Cのランタイム時に取得する情報を使ってる時
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をみたいな話
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にすることにあまり抵抗を感じないでやっていこうという考えに至った。