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

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

ScalaでHTTPクライアントのライブラリは何を使えばいいのかと思ってAkkaを調べる

scalaのhttpクライアント

今までPlayしかやったことないからWSしか知らなくて、Playじゃないやつでhttpクライアント的なライブラリって何を使えばいいんだろうってのがスタート。Comparing Scala's HTTP client libraries - implicitdefとか見てdispatchってのがいいのかなーと思って同僚に質問してみたらakka-httpがよさそうって話になったので、何者なのかよく分からないので理解をしたいと思って調べた。

そもそもAkkaとは?

What is AkkaのGoogle翻訳

What is Akka? — Akka Documentationの冒頭を適当に日本語訳。

我々は正しい、分散、同時、フォールトトレラントでスケーラブルなアプリケーションを作成することはあまりにもハードであると信じています。我々は間違ったツールと抽象化の間違ったレベルを使用しているので、それはですほとんどの時間。アッカは、それを変更するためにここにあります。アクターモデルを用いて、我々は、抽象化のレベルを高め、より多くの詳細については、反応性マニフェストを、スケーラブルな弾力性と応答性の-参照アプリケーションを構築するためのより良いプラットフォームを提供します。フォールトトレランスのために我々は通信業界は、自己治癒すると停止することはありませんシステムアプリケーションを構築するために大きな成功を収めて使用されている "それがクラッシュしましょう」モデルを採用しています。アクターは、透明分布と真にスケーラブルでフォールトトレラントなアプリケーションのための基礎のための抽象化を提供します。

うーん、なんとなく分かったような分からないような・・・

Akka とはググる

www.slideshare.net

はぁー、、これはとっても分かりやすい。そもそもアクターモデルという原理があってそれを実装してるのがAkkaということかな。 今日はここまで

Xcode上でImplicit declaration of function 'notify_cancel' is invalid in C99 でハマった

Pureeをcocoapodsで導入しようとしてたら

Pureeが依存してるYapDatabaseというやつがCocoaLumberjackに依存してて、CocoaLumberjackのところでタイトルのエラーが出た。

Implicit declaration of function 'notify_cancel' is invalid in C99

f:id:masato47744:20170422004819p:plain

notify_cancelメソッドをクリックすると、osのコードっぽいところ/usr/include/notify.hにいきつく。

f:id:masato47744:20170422004848p:plain

Implicit declaration of function '' is invalid in C99というやつでググるとコンパイラのフラグでどうのこうのとか、osのコードっぽいところまできたので、xcode7.3.1とかxcode8とかswiftのバージョンとかを疑ってしまって、CocoaLumberjackをビルド対象から外せないかとか、other_c_flagsとかで無視できるってissueとかにつられて、盛大にはまってしまった。

敵はここにいた・・

そもそもCocoaLumberjack単体でビルドできるのかとか試してたけどなんの問題もなくてどうしようかなーと考えてて、ふと、エラーが出る冒頭で、#include <notify.h>としてるんだけど、そこをクリックすると、なんとFoxのSDKのNotify.hに飛んだ:joy:

f:id:masato47744:20170422004913p:plain

解決策

Podfileに追記

+  # HEADER_SEARCH_PATHSからfoxSdkを削除する
+  def remove_fox_sdk_from_header_search_path(pod)
+    workDir = Dir.pwd
+    xcconfigFilename = "#{workDir}/Pods/Target\ Support\ Files/#{pod}/#{pod}.xcconfig"
+    xcconfig = File.read(xcconfigFilename)
+    newXcconfig = xcconfig.gsub('"${PODS_ROOT}/Headers/Public/foxSdk"', '')
+    File.open(xcconfigFilename, "w") { |file| file << newXcconfig }
+  end
+
+  ['CocoaLumberjack', 'YapDatabase'].each do |proj|
+    remove_fox_sdk_from_header_search_path proj
+  end

cf: http://dev.classmethod.jp/smartphone/iphone/cocoapods-inherited/

役割を明確化すると働きやすいなと感じた

今まで

入社して開発メンバーとして仕事をしてきて、いつのまにかチームリーダー的な役割になった。 スケジュール間に合うかベロシティを計測したり、やることがなくならないようにタスクを検討したり、新卒メンバーの受け入れをやったり、ミーティングの調整・ファシリテーションしたり、外部との窓口やユーザーの問い合わせに対応したり、1日中プルリクレビューしたり、その他のこぼれ落ちていく雑用を拾ったりしてた。

その結果、いろいろ抱え込みすぎて辛くなった。

役割の明確化

このままだと辛いと言ったらすんなり部署を変えてもらった。 チーム運営みたいなところから離れて今は開発のための開発をする部署に異動になった。

今まで嫌々拾っていた雑用やこぼれ球も開発を支援するという役割になったことで、ポジティブに向き合えるようになった。 雑用も一つのタスクとして記録に残していこうとか、どうやったらこの雑用を自動化できるかとか、部全体として他のチームでも同じようなタスクを巻き取れないかとかそういうことを考えながら取り組めているので楽しい。あと、今までやりたいと思っていたインフラまわりの開発が多いので学ぶことが多くて楽しい。

自分は、リーダーとしてみんなを引っ張っていくというより、メンバーが働きやすくなるようにサポートするという方が性にあってる気がするなーと思った。

今後

インフラもアプリもサーバーもフロントもチーム運営もできるスーパー便利屋になっていきたい。

elasticsearchのindicesとindex

indiceとindexってどう違うか分からなくてなぜかindiceはindexの親概念かなと勘違いしてしまっていて、かなり混乱してたけど、indice 意味 で、検索したらそもそも同義語だった…

kibanaで見る場合はアスタリスクとか使ってどうせ複数のindexをまとめて見ることが多いのでindicesって表現されてるのかも。

fluentdのDockerfileの書き方がちょっと変わってた

ubuntuが16.04になってた

これが一番大きな原因。 ubuntu 16.04が2016/4にリリースされてて、今Dockerhubのubuntuのlatestは16.04になってる。ちなみに、コードネームは「Xenial Xerus(ジーニアル ジリス)」で『おもてなしのアラゲジリス』

https://hub.docker.com/_/ubuntu/

f:id:masato47744:20171110022629p:plain

install.shが変更

  • ubuntu 16.04 xenial用のものが出てる
  • httpsになってる

http://docs.fluentd.org/articles/install-by-deb#step-1--install-from-apt-repository

- http://toolbelt.treasuredata.com/sh/install-ubuntu-trusty-td-agent2.sh
+ https://toolbelt.treasuredata.com/sh/install-ubuntu-xenial-td-agent2.sh 

libssl0.9.8じゃダメ libssl1.0.0

こういうエラーが出る。

E: Package 'libssl0.9.8' has no installation candidate
- apt-get libssl0.9.8
+ apt-get libssl1.0.0 

sudoのinstallが必要

You will be prompted for your password by sudo.
100   563  100   563    0     0   1497      0 --:--:-- --:--:-- --:--:--  1501
sh: 5: sudo: not found
sh: 8: sudo: not found
The command '/bin/sh -c curl -L http://toolbelt.treasuredata.com/sh/install-ubuntu-trusty-td-agent2.sh | sh && mkdir -p /var/log/td-agent' returned a non-zero code: 127
make: *** [build] Error 127

insatall.shでsudoやってるけど、ubuntu 16.04には最初からsudoが入ってないらしくそこで怒られる。なので、apt-getでsudoもインストール。

+ apt-get sudo

余談

W: --force-yes is deprecated, use one of the options starting with --allow instead.

apt-getの際のオプションの--force-yesがdepricatedになったらしく、--alow-xxxを使えとwarningが出る。細かく指定してねってことらしい。今回は無視した。

--force-yes
       Force yes; this is a dangerous option that will cause apt to continue
       without prompting if it is doing something potentially harmful. It
       should not be used except in very special situations. Using force-yes
       can potentially destroy your system! Configuration Item:
       APT::Get::force-yes. This is deprecated and replaced by
       --allow-downgrades, --allow-remove-essential,
       --allow-change-held-packages in 1.1.

airbrake-logbackを非同期化することができなかった

背景

Logbackとは

  • Logback は巷で大人気の log4j プロジェクトの後継プロジェクト
  • java製のデファクトっぽいロギングライブラリ

Airbrakeとは

  • 商用のエラートラッキングシステム

Errbitとは

  • AirbrakeのOSSクローン

airbrake-logbackとは

  • logback はロギングイベントを出力する仕事を、アペンダーと呼ばれるコンポーネントに任せています
  • つまり、LogbackのAirbrakeのためのアペンダー -> Logback Appender for Airbrake

非同期にしたいということはどういうことか

  • 例: AsyncAppenderの設定(logback-examples/src/main/java/chapters/appenders/conc/logback-async.xml)
<configuration>
  <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    <file>myapp.log</file>
    <encoder>
      <pattern>%logger{35} - %msg%n</pattern>
    </encoder>
  </appender>
  <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="FILE" />
  </appender>
  <root level="DEBUG">
    <appender-ref ref="ASYNC" />
  </root>
</configuration>

結果

airbrakeのappender設定

     <appender name="AIRBRAKE" class="net.anthavio.airbrake.AirbrakeLogbackAppender">
         <apiKey>298c68320df62a7c2a7736ff6cca9b4e</apiKey>
         <env>production - ${config.resource}</env>
         <notify>ALL</notify>
         <url>https://errbit/notifier_api/v2/notices</url>
 
         <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
             <evaluator>
                 <expression>
                     event.getMessage().contains("Logger configuration in conf files is deprecated and has no effect.")
                 </expression>
             </evaluator>
             <onMatch>DENY</onMatch>
         </filter>
 
         <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
              <level>WARN</level>
          </filter>
      </appender>

asyncなappenderの設定

もともと設定されてるAIRBRAKEのappenderをasyncで囲うだけ。

<appender name="ASYNC_AIRBRAKE" class="ch.qos.logback.classic.AsyncAppender">
    <appender-ref ref="AIRBRAKE"/>
</appender>

試した結果

root=info root=warn threshold filter どうなったか
AIRBRAKE ASYNC_AIRBRAKE WARN warnが2回飛ぶ 🙅
ASYNC_AIRBRAKE - WARN 何も飛ばない 🙅
- ASYNC_AIRBRAKE WARN 何も飛ばない 🙅
AIRBRAKE, ASYNC_AIRBRAKE - WARN warnが2回飛ぶ 🙅
- ASYNC_AIRBRAKE - 何も飛ばない 🙅

まとめ

  • できなかった
  • そんなに困ってる訳ではないのであきらめた

deis上にhubotをデプロイする

hubot作成

FROM node:latest

RUN mkdir /bot && cd /bot
ADD . /bot

WORKDIR /bot

EXPOSE 11000

CMD ["bin/hubot", "--adapter", "slack"]

deisでアプリケーション作成

  • deisのclientをインストール。homebrewだとver1だったので、shで入れ直す。
  • $ deis register <controller>
  • $ deis create

Slack側の準備

HUBOT_SLACK_TOKEN=xxxxxxxx
HUBOT_SLACK_TEAM=hoge
HUBOT_SLACK_BOTNAME=fuga
HUBOT_SLACK_CHANNELMODE=whitelist
HUBOT_SLACK_CHANNELS=piyo

deisにdeploy

  • $ deis keys:add <- 公開鍵登録
  • $ git push deis master
  • $ deis config:push

ping

  • HUBOT_SLACK_CHANNELSHUBOT_SLACK_BOTNAMEをinviteしてping pong

Future.sequenceは1つずつ順番に実行される訳じゃない

sequenceっていう名前からどうしても順序を意識してしまって順番に(直列に)実行されるものだと思っていた。 でもそうじゃなかった。

Future.applyの場合

ためしに、こんな処理を書いてみる。1から10までを1s待って出力するだけのやつ。

def process: Future[Seq[Int]] = Future.sequence {
  for {
    i <- 1 to 10
  } yield {
    Future {
      println(s"start -> ${i.toString} ${System.currentTimeMillis}")
      Thread.sleep(1000)
      println(s"end -> ${i.toString} ${System.currentTimeMillis}")
      i
    }
  }
}

これを実行すると、出力はこうなる。同時に4つの処理が走るみたいだ。並列で同時に実行されているので全ての処理が終わるまでに3秒ぐらいで終わっている。ただ、最終的な結果は、Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)となって順番通りになっている。

scala> Await.result(process, Duration.Inf)
start -> 1 1459695509918
start -> 3 1459695509919
start -> 2 1459695509919
start -> 4 1459695509919
end -> 3 1459695510923
end -> 4 1459695510923
end -> 1 1459695510923
start -> 6 1459695510923
start -> 7 1459695510923
end -> 2 1459695510923
start -> 5 1459695510923
start -> 8 1459695510923
end -> 8 1459695511929
end -> 7 1459695511929
start -> 9 1459695511929
start -> 10 1459695511929
end -> 5 1459695511929
end -> 6 1459695511929
end -> 9 1459695512933
end -> 10 1459695512933
res24: Seq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Future.successfulの場合

こんな風にFuture.successfulで書き直して

def process: Future[Seq[Int]] = Future.sequence {
  for {
    i <- 1 to 10
  } yield {
    Future.successful {
      println(s"start -> ${i.toString} ${System.currentTimeMillis}")
      Thread.sleep(1000)
      println(s"end -> ${i.toString} ${System.currentTimeMillis}")
      i
    }
  }
}

実行すると、順番に実行される。実行時間も10秒ぐらいかかってる。結果もVector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)で同じになってる。

scala> Await.result(process, Duration.Inf)
start -> 1 1459696443179
end -> 1 1459696444180
start -> 2 1459696444180
end -> 2 1459696445180
start -> 3 1459696445180
end -> 3 1459696446180
start -> 4 1459696446181
end -> 4 1459696447181
start -> 5 1459696447182
end -> 5 1459696448186
start -> 6 1459696448186
end -> 6 1459696449187
start -> 7 1459696449187
end -> 7 1459696450188
start -> 8 1459696450188
end -> 8 1459696451188
start -> 9 1459696451188
end -> 9 1459696452188
start -> 10 1459696452188
end -> 10 1459696453191
res25: Seq[Int] = Vector(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Future.successfulで同じことをした場合は、順番に行われる。これは、Future.applyFuture.successfulの処理をとるときに、名前渡しかそうじゃないかが関係ある。(となんとなく思う)

scala/Future.scala at 2.11.x · scala/scala · GitHub

def apply[T](body: =>T)(implicit @deprecatedName('execctx) executor: ExecutionContext): Future[T] = impl.Future(body)
def successful[T](result: T): Future[T] = Promise.successful(result).future

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