コマンドラインを拡張しやすくするヤツ書いた
gitなど既存のコマンドラインを拡張して新しいサブコマンドを追加する方法はいくつか考えられる。
git alias
gitの場合はgit alias
を使うことで簡単にサブコマンドを追加できる。gitのとき限定。
ラッパー
github/hubのような既存のコマンドラインをラップしたスクリプトを書き、alias hub=git
のようにalias
することで既存の機能を保ちつつ機能を追加できる。
問題点としては、複数のラッパーによる拡張が難しくなる。例えば、ここでbub
というgit
のラッパーを書いたとする。git
にhub
の機能とbub
の機能を拡張したい。hub
は入力されたサブコマンドがhub
になければgit
にフォワードしている。なので、hub
とbub
を同時に拡張するにはbub
をhub
のラッパーとして実装することになってしまう。依存関係をハードコーディングすることになるため、まったくスケーラブルじゃない。
命名規則とext
command subcommand
と入力されたらcommand-subcommand
を実行するように名前解決する仕組みがよさそうだと思う。例えば、git pr
というコマンドはまずgit-pr
を探し、あれば実行し、なければgit pr
を実行する(そしてエラーになる)。gem uninstall all
というコマンドはgem-uninstall-all
、gem-uninstall all
、gem uninstall all
の順に探索されて見つかり次第実行される。
$ go get github.com/naoty/ext $ go get github.com/naoty/gem-uninstall-all $ alias gem="ext gem" $ gem uninstall all # Run gem-uninstall-all
正直、いろんな問題がありえそうだが、昨日思いついたままに書いたものなので、まだ想定できてない。gem-uninstall-rails
というコマンドがあったらrailsをアンインストールできないとかありそう。
上の例で、hub
とbub
を同時に拡張したい場合にext
を使うと以下のようにできる。
$ go get github.com/naoty/hub-bub $ alias git="ext hub" $ git bub # Run `hub-bub`
残念ながら、hub
を使いたい場合はこうするしかないような気がする。
追記(2015-07-23)
gitには、git subcommand
をgit-<subcommand>
として名前解決して実行する機能があったことをさっき知った。なので、gitに限って言えばextのようなツールは不要だと思う。
$ cd $HOME/bin $ vi git-hello #!/bin/sh echo "Hello, world!" $ chmod +x git-hello $ git hello Hello, world!
Pocketのもう読んでない記事を掃除するヤツ書いた
普段、Pocketを使って「あとで読む」記事を管理している。ちょっとした時間に見つけた記事をPocketに追加しておいて、通勤時間などにiOSアプリで消化している。ただ、長い記事が増えてくるとだんだん消化しきれなくなってきて、消化しようというモチベーションが失せてくる。そこで、一定期間が経っても消化できていない記事を自動的に削除するツールを書いた。
使い方
まず、Pocketのdeveloperサイトに行ってアプリケーションを作成する。すると、Consumer keyが得られる。次に、Access tokenが必要なのだけど、これはmotemen/go-pocketを使ってOAuth認証を行い取得した。
Herokuボタンからデプロイする。ここで、上で取得したconsumer keyとaccess tokenを環境変数としてセットする。さらに、削除対象とする期限を環境変数で指定できる。デフォルトは24時間となっている。僕は72時間にしている。
Heroku schedulerのダッシュボードで
sweep
を実行するタイミングを設定する。
所感
先日、Herokuが公式にGoをサポートしたので、さっそくテストを兼ねてこういうものをGoで書いてみた。tools/godepの使い方を覚えなくてはいけないことを除けば、いつもどおりにHerokuにアプリケーションをデプロイできた。
個人的にちょっとしたCLIツールをGoで書くことが増えたが、ちょっとしたジョブを定期実行させるときにGoでちょっとしたツールを書いてHeroku schedulerにやらせるという手法は非常にお手軽なので今後も機会がありそうだなと思った。
コミット数が多いファイルを表示するコマンドを書いた
インストール
$ go get github.com/naoty/hot
使い方
$ cd src/github.com/naoty/Timepiece $ hot 24: README.md 17: Sources/NSDate+Timepiece.swift 15: Tests/NSDate+TimepieceTests.swift 10: Timepiece.xcodeproj/project.pbxproj 9: Timepiece.podspec 7: Sources/Duration.swift 7: Tests/DurationTests.swift 7: Tests/Int+TimepieceTests.swift 6: Sources/Int+Timepiece.swift 4: Sources/NSDateComponents+Timepiece.swift 2: .travis.yml 2: Tests/NSTimeInterval+TimepieceTests.swift 2: Timepiece.xcodeproj/xcshareddata/xcschemes/Timepiece OSX.xcscheme 2: Sources/NSTimeInterval+Timepiece.swift 1: Timepiece.xcodeproj/xcshareddata/xcschemes/Timepiece iOS.xcscheme 1: Timepiece.xcworkspace/contents.xcworkspacedata 1: .gitignore 1: LICENSE 1: Sources/NSCalendar+Timepiece.swift 1: Sources/NSCalendarUnit+Timepiece.swift 1: Sources/String+Timepiece.swift 1: Tests/NSCalendarUnit+TimepieceTests.swift 1: Tests/String+TimepieceTests.swift 1: Timepiece.playground/Contents.swift 1: Timepiece.playground/Sources/SupportCode.swift 1: Timepiece.playground/contents.xcplayground 1: Timepiece.playground/playground.xcworkspace/contents.xcworkspacedata 1: Timepiece.xcodeproj/Timepiece-Info.plist 1: Timepiece.xcodeproj/TimepieceTests-Info.plist 1: Timepiece.xcodeproj/project.xcworkspace/contents.xcworkspacedata
表示件数を-n <表示したい件数>
で指定したり、パターンを指定してマッチしたファイルだけ表示することができる。
$ hot -n 5 "**/*.swift" 17: Sources/NSDate+Timepiece.swift 15: Tests/NSDate+TimepieceTests.swift 7: Sources/Duration.swift 7: Tests/DurationTests.swift 7: Tests/Int+TimepieceTests.swift
動機
仕事で1年以上開発が行われているコードベースを引き継ぐことになった。僕の仕事は既存のコードを理解しつつ、新たに機能を追加していくことだ。そこで、効率的に既存のコードベースの全体像を把握するため、このようなツールを作ることにした。どれが主要なファイルなのかコミットログから把握できる。
他の使い方としては、例えば各ファイルのコミット数、循環的複雑度、テストカバレッジ等から、プロジェクト全体のバグの出やすさみたいなものを可視化できるかもしれない。
「すごいHaskell たのしく学ぼう!」を読んだ
- 作者: Miran Lipovaca
- 出版社/メーカー: オーム社
- 発売日: 2012/09/21
- メディア: Kindle版
- 購入: 4人 クリック: 9回
- この商品を含むブログを見る
本書は一度は8章あたりで挫折したが、今回13章あたりまで読みファンクタ―、アプリカティブファンクタ―、モノイド、モナドといった概念がなんなのか理解とまでは言えないけど知ることができた。
一度は挫折したが今回またリベンジしようと思った理由は、今後モバイルアプリを開発していくにあたって関数型プログラミングの概念を理解して採り入れていくことが必要になってくると思ったからだ。Swiftはlet
による不変型の宣言やOptional
型などの文脈付きの型など関数型プログラミング言語としての側面をもっていると思う。また、データバインディング(SwiftBond/Bondなど)やJSONのパース(thoughtbot/Argoなど)といった場面で関数型プログラミングの概念が登場してきている。Swiftのポテンシャルを最大限に発揮して、堅牢で生産性の高いコードを書くには関数型プログラミングの知識が必要になってきていると最近感じている。
本書を読んだ結果として、データの構造について新しい視点を得ることができた。Maybe
やEither
といった概念を"文脈"と呼んでいるのが自分の中にはなかった発想だった。例えば、Maybe
とMaybe Int
を区別して考えるのはとても抽象的だけど強力な考え方と思った。Maybe
は「あるかもしれないし、ないかもしれない」という文脈を表し、Maybe Int
は「Int
型かもしれないし、何もないかもしれない」型を表している。これらを分けることで、文脈を保ったまま計算するという発想が出てくるのだと思う。文脈を保ったまま計算する段階として、本書ではFunctor
やApplicative
、そしてMonad
が登場してきた。
Swiftでは、Haskellにおける型コンストラクタにあたる概念がない。Genericsを使うことでMaybe
のような型を表現することはできるが、ある型が型引数をとるのかとらないのか、とるとしたらいくつとるのかを知る術はない(はず)。Haskellではそれらは種類という概念で説明されている。Maybe
の種類はMaybe :: * -> *
だし、Either
の種類はEither: * -> * -> *
となっているので、それぞれ型引数を1つと2つとることがわかる。HaskellのFunctor
は種類が* -> *
の型コンストラクタしかインスタンスにできないのだけど、こういう概念をSwiftで表現できない。
というわけで、Swiftで関数型プログラミングをするにはHaskellほどうまくはできないことがなんとなくわかった。Genericsなどで擬似的に表現するしかない。Functor
のfmap
を以下のように実装してみた。
extension Optional { func fmap<U>(f: T -> U) -> U? { switch self { case .Some(let value): return f(value) case .None: return .None } } } let maybeOne: Int? = 1 let maybeTen = maybeOne.fmap({ x in x * 10 })
SwiftのOptional<T>
型はつまりT?
型のことなのだけど、Optional
型を拡張してfmap
を追加している。return f(value)
のところは暗黙的にU?
型にラップしている。このように実装することで、Optional
型のもつ「あるかもしれないし、ないかもしれない」という文脈を保ちつつ、中身の1
というInt
を計算している。
ここではFunctor
だけを簡単に実装してみたが、これに加えてApplicative
とMonad
を実装するとより抽象的な計算が可能になってくる。JSONのパースなどを実装する際にはApplicative
の操作が必要になってきそうな感じがする。自分はまだ関数型プログラミングの実装を実際にしたわけではないので、理解したとは到底いえない。パーサーの実装をしてみたり、上で紹介したライブラリのコードを読んでみることで関数型プログラミングを実践的に理解していきたい。
naoty/todoとnaoty/nowisで定期的なtodoを管理できるようにした
前回のエントリで紹介したnaoty/todoと今回作ったnaoty/nowisを組み合わせることで、定期的なtodoをコマンドラインで管理できるようにした。
使い方
$ nowis saturday && echo 'Today is Saturday!' Today is Saturday!
nowis
コマンドは、現在時刻が引数で与えた曜日かどうかを判定して真なら終了コード0
を返し偽なら1
を返す。上のように&&
で任意のコマンドと組み合わせることで、特定の曜日だけ実行できるようになる。
定期的なtodoの管理
nowis
を組み合わせて定期的なtodoを管理するにはいくつか方法が考えられるが、zshの設定ファイルを使う。
# .zlogin nowis sunday && todo add --once 部屋を掃除する (nowis tuesday || nowis thursday) && todo add --once 燃えるゴミを出す
上のように設定することでzshにログインするたびに上のスクリプトが実行される。todo add --once
で既に存在する場合は追加しないようにできるので、これで特定の曜日になると自動的にtodo add
されるようになる。
15分くらいで作ったので現状は曜日の判定しかできないけど、応用範囲が広そうなのでもうちょっと細かく判定できるようにするかも。
自分専用のtodo管理ツールを書いた
最近、プライベートでの開発したいことや勉強したいことが増えてきたので、それらを管理するツールを書いた。
使い方
$ todo add Go言語を勉強する $ todo add todo管理ツールを書く $ todo add ブログ記事を書く $ todo list [ ] 001: Go言語を勉強する [ ] 002: todo管理ツールを書く [ ] 003: ブログ記事を書く $ todo done 1 $ todo done 2 [x] 001: Go言語を勉強する [x] 002: todo管理ツールを書く [ ] 003: ブログ記事を書く $ todo clear $ todo list [ ] 001: ブログ記事を書く
その他、todoの削除や移動などができる(詳細はGitHubのページを参照)。個人的に便利だと思っている機能がtodoをmarkdownのtask list形式で出力する機能だ。
$ todo list -m - [x] Go言語を勉強する - [x] todo管理ツールを書く - [ ] ブログ記事を書く
これを使ってQiita:Teamの日報に今日やったこと、やれなかったことを簡単にコピペできる。一日の作業フローはこうだ。
todo list
で残タスクを確認する。- 適宜
todo add
でタスクを追加したり、todo move
で順番を入れ替えて優先度を調整する。 - 完了したら
todo done
でタスクを完了させる。 - 一日の終わりに
todo list -m
で作業内容を出力してQiita:Teamにコピペして、感想などを付け加えて日報として公開する。 todo clear
で完了したタスクを消去する。
tips
- todoはLTSV形式のファイルとして保存され、ファイルのパスは
TODO_PATH
という環境変数で指定できる(デフォルトはHOME
)。なので、環境変数でDropbox内のパスを指定すれば簡単にtodoを同期できる。 - zimbatm/direnvを使うと、プロジェクトルートに
cd
したときにTODO_PATH
を書き換えられるのでプロジェクトのスコープのtodoを別に管理できる。
実装
最近はGoが気に入っているので、コマンドラインツールを作るときはすべてGoで書いている。CLIを作る際のフレームワークはいくつかあるようだが、一番Starが多そうだったcodegangsta/cliを使っている。標準の出力とmarkdown形式の出力の切り替えを実装する際にinterface
を使ってみた。ファイルの入出力にはioutil
パッケージが手っ取り早かった。ファイルの扱いを通じてio.Writer
インターフェイスについても理解が深まった。
done
$ todo done 1
#potatotips でTimepieceについて発表した
potatotips
資料
最近のTimepiece
- GW前あたりから急激にバズってきた。一時GitHubのトレンドで1位になった。それまでは☆70くらいだったけど、もうそろそろ☆500になりそうな勢いだ。
- それに伴っていくつかの要望をPull requestでいただいた。それらはほぼすべてmergeした。機能追加やバグ修正まで自分では見落としていた部分を指摘していただいて、多くの方に使われていそうだという実感がある。
イベントの感想
- 最近はiOSではなくAndroidアプリ開発をしているので、iOS/Android両方楽しめて非常に良かった。
- Timepieceを検討したけど採用を見送った方の意見を聞けたのが非常に良かった。そういう方の意見を聞ける機会は多くないからだ。いただいた要望について今実装方針を考えていて、ちゃんと形にしていきたい。
- 最近気になっているResultについての議論はとても勉強になった。naoty/SwiftCSVでエラー情報を扱う際にResultが使えそうだと思っていた。ただ、議論を聞いてオレオレResultが乱立しそうな流れがありそうだというのを知った。そうなると、ライブラリ提供者が実装するよりも利用者側でResultを定義する方が利便性を損ねないのでは、という意見に変わった。
- ドキュメントだけではよく理解できなかったDagger 2については、あまりよくわかってなかった
@Provide
について理解が深まった。Androidのテストについて意見交換をさせていただいて、自分の意見は間違ってなさそうだという確信を得られたのもよかった。 - その他、Androidの
@Nullable
,@NonNull
はすぐに使おうと思ったし、Lastlaneやdeliverといったワークフローを自動化するツールも実践的な内容で勉強になった。