#tqrk09 で「Rubyを使った開発環境構築の自動化」についてLTしてきた

TokyuRuby会議09でLTした

あんまり外部でRubyについて話したことなくて、こないだshibuya.rbでactiverecord-precountについてLTしたけど、それ以外にもまだ紹介したいgemがあったのでTokyuRuby会議にLT応募したら通ったので話してきた。

rebuild gem

github.com

OSXでgitのインストールとdotfilesのgit cloneとその中のスクリプトの実行までやってくれるgem。一度セットアップしたあとは、新しいのをgit pullしてきてgitconfigで指定しておいたスクリプトを走らせるのに使う。僕のgemの中だと結構ダウンロードされている奴で、今でも普段普通に使っている。

実行するスクリプトにRubyのライブラリ使うとメンテがダルいみたいな話をしたんだけど、適材適所だと思うので、どちらかだけ使っていれば良いみたいな話でもないとは思っている。あるいは自分が使ってて楽しいgemを使うのが良いと思う。

AppleScriptでGUI操作してるんだけど、boxenとか見ると別のやり方もすでにあったので、Macで自動化をするときは先にboxenとかを見ると良い。

karabiner-dsl gem

github.com

これはMacのキーの設定をするKarabinerというユーティリティのXMLの設定ファイルを吐き出すRuby DSL。日常的に頻繁にキーバインドをいじる人間なので、これもよく使っている。これを使って普段どんな最悪なキーバインドを設定しているかを紹介した。やめたい。

エディタ何使ってるんだろうみたいなツイートを見かけたけど、vimを使っていて、vimのキーバインドを潰してしまっている部分はvimrcで別のものに設定している。とにかく最悪という感じがする。

RubyのDSLでキーバインドの変更履歴を管理したいみたいな人には向いてると思う。

TokyuRuby会議に初参加した感想

スポンサーついててビールが無限に出てくるのやばい。あと持ち寄りの料理もすごいおいしかった。おいしいもの飲み食いしながらいろんな人と話せて最高の会だった。来年も参加したい。

ElectronでYoruFukurou風のTwitterクライアントを作った

f:id:k0kubun:20150824200844p:plain

最強のTwitterクライアント戦争

なんか戦争をやってる人たちがいたので乱入することにした。

YoruFukurou風のTwitterクライアント

最近デスクトップでLinuxを使い始めたんだけど、YoruFukurouみたいな感じで使えるTwitterクライアントがなくて困っていた。

なので、YoruFukurouを再実装した。Mac環境で本家を置き換えようとは思ってなくて、あくまでWindowsとかLinuxとかでYoruFukurouを使いたい人向けに作っている。

github.com

デモ

インストール方法

https://github.com/k0kubun/Nocturn/releases

からzipでダウンロードできるようにしてある。
これからカスタマイズするつもりはあるけど、現状はパクりアプリなのでApp Storeに出す予定はない。

実装されている機能について

下記のショートカットで使える機能と、画面に見えてるボタンの機能が実装されている。

Key Command
Enter ツイートの投稿、リプライ先の選択、検索実行
j, ↓ 次のツイートを選択
k, ↑ 前のツイートを選択
0, space 最初のツイートを選択
f お気に入りに追加
Cmd-backspace ツイートを削除

自分がよく使う機能しかまだ実装してない。面倒そうなのもちょっと省いているけど、8割くらいのユースケースは満たせる気がする。

なぜNocturnなのか

中二病です

Electron使ってみた感想

僕はJavaScriptの文法を知らない(CoffeeScriptを書く)くらいフロントエンド力が低いけど、Electron使ったことない状態から始めて1〜2日で完成したのでかなりわかりやすくて使いやすいと思う。 HTMLとCSSとJavaScriptをちょっと書くだけでデスクトップアプリがサクサク作れるのでめちゃくちゃ楽しい。みんな絶対やったほうがいい。
あと、Macで開発してたけどWindowsとかLinuxで起動したら普通にうごいた。すごい。

というわけで、Electron触ってみたい人はぜひNocturnをいじってみてください。

PCを自作してArch Linuxを入れた

f:id:k0kubun:20150816180540p:plain

社会人になって経済的に余裕ができてきたので、はじめての自作PCに挑戦することにした。 入社直後に生活が苦しすぎてヤフオクでiMacを売ってしまったのでデスクトップPCが欲しかった。

材料

種類 型番 値段
CPU Intel Core i7 4790K (4 core / 8 threads, 4.0GHz) ¥39,500
マザボ ASRock Z97 Extreme4 ¥12,980
メモリ CT2KIT102464BA160 (DDR3-1600, 8GB x 2) ¥13,999
SSD CT120BX100SSD1 (120GB) ¥8,277
DVDドライブ iHAS324-17 ¥1,780
電源 KRPW-PT600W/92+ (600W) ¥9,980
PCケース CMS-693-KKN1-JP ¥11,800

合計98,316円。前日が給料日だったのでうっかり手が滑って高いパーツを集めてしまった。 家賃を振り込んだのと持株会にフルベットしているのと合わせて今月の給料が1日で蒸発した。 困ったなぁ〜

歩くのがめんどくさかったので全部秋葉原のツクモeXで買った。全部手で持ち帰ったんだけど死ぬほど重くて、帰りに山手線のホームを間違えたけどそのまま目黒まで逆周した。全部持ったまま体重計に乗ったら 91kg あった。なお本人の体重については不詳。

組み立て

完全に素人であることが店員に一発で見ぬかれ「初めての自作PC講座」を渡されたので、これを読んで作った。この冊子につくもたんが出現するのは表紙だけで、中はマザボの写真とおっさんのイラストしか出てこない。

CPUの取り付け

なんかハンダ付けとかしないといけないと思ってたんだけどマザーボードの上にのせてフタを閉じるだけだった。なんでこれで動くのかよくわからない(本当はグリスとかする気がする)

メモリと電源を挿して動作確認

メモリと電源を挿してディスプレイつないでスイッチを押すともう電源がつく。一回つくと以降大分安心して作業できる。

PCケースに取り付けて配線

PCケースにマザボを取り付け、入りそうなとこに適当にSSDとDVDドライブと電源をいれる。刺さりそうなピンに勘で適当にケーブルを挿していき、動作するまでの回数を競うゲーム。

完成

真面目に配線したら一発では動かなくて、よくわからんけど似たようなとこに適当に付け替えたら動いた。やはりエンジニアに必要なのは運命力。

Arch Linux

どうやら僕に常識が欠如しているのはLinuxに慣れてないかららしいので、Linuxをいれることにした。

某所のサーバに使われてるubuntuと迷ったんだけど、常識を知るために触るならユーザーが全部自分でやらないといけないディストリビューションのほうがいいだろうと思ったのでArchを選択した。実際デフォルトだと何も入ってなくて、これ動かすのにはこれが必要なんだみたいなのが知れて面白い。

セットアップ時にやった作業はQiitaに残した。
http://qiita.com/k0kubun/items/e60fae90c688ac960ab7

感想

休日にコード書くのをやめてたまにはPCを組み立てるなどの運動をすると楽しいし健康に良くて最高

markdownからkeynoteを生成する「md2key」を作った

markdownからkeynoteを生成する奴作った

なんか昨日社内でLT頼まれててLTしたんだけど、前日に資料を作るモチベが沸かなすぎて資料を作る奴を作ってしまった。

使い方

  1. gem install md2keyでmd2keyをインストールしておく。
  2. キーノートを開いて好きなテーマを選んで新規作成。
  3. 一つ目のスライドが表紙のスライドになるので、好きなレイアウトのスライドに変える。
  4. 二つ目のスライドがコンテンツのスライドのレイアウトに使われるので、使いたいレイアウトのスライドを追加する。
  5. md2key **.mdを叩く。

markdownのフォーマット

# The presentation
@k0kubun

## Hello world
- I'm takashi kokubun
- This is a pen

## How are you?
- I'm fine thank you

みたいな奴が動く。最悪なことに現時点ではここに出てこない記法は全く対応してない。

Deckset互換性

giginet.hateblo.jp

僕がMarkdownからプレゼンを生成するときはいままではDecksetを使っていたので、最終的にはDecksetの記法を全部実装したいという気持ちである。(理想)

なので、---でスライドを区切るみたいな書き方に対応したい。でも---は不要では?と言われたので、現状は内部的には見出しでスライドを変えて---はただ無視している。

けど僕としては普通のMarkdownがいい感じに変換されることよりDecksetとの互換性のほうが重要度が高いので、いつか見出しが複数あるスライドを許容して必ず---で区切るようにするかもしれない。

どういう仕組みなの

2ヶ月くらい前も同じことを考えていたんだけど、AppleScriptでGUI操作してなんとかするというあまり現実的ではない && Keynoteを起動しなければならないダサい方法しか思いつかなかった。

絶対にAppleScriptを書きたくなくて、その日は https://github.com/obriensp/iWorkFileFormat を読みながらKeynoteファイルの中身を見ていたけど、xmlだったころのKeynote '09はともかく最新のKeynoteのフォーマットは生成するの大変だなあと思って諦めていた。

結局AppleScriptからKeynoteをGUI操作することにした

今週社内ですごいかっこいいKeynoteテンプレートが公開されて使いたくなったけど、GUI操作だけで1から資料作るの死ぬほどめんどくさいし本当にやりたくなかったので、もうこれはAppleScriptでなんとかするしかないと思った。

あまりにもKeynoteを操作したくなくて key2txt というmd2keyとは逆のことをやってる奴を見てたら、なんかTaskPaperというフォーマットからKeynoteを生成するスクリプトがあって、その中にスライドのタイトルとコンテンツにアクセスするコードと新しいスライドを作るコードがあったので、そのコードだけを組み合わせてmd2keyを作った。

作るの(AppleScriptの書き方調べるのが)めんどくさそうと思ってたけど結局このコードを見つけてから2時間くらいで書けてしまった…

markdownパーサ

Rubyでmarkdownをパースして(HTMLではなく)いい感じのASTを生成してくれるGemを探してたんだけど、たいていのmarkdown処理する奴はHTMLまで変換してしまっていて、そうじゃないのはRDoc::Markdownしか見つからなかった。RDoc::Markdownのパース結果がなんか使いづらそう(あんまちゃんと調べてないです)だったので、最初小学生並みのマークダウンパーサを書いた。

エゴサしてたら「*でリストかけないじゃん」という意見を見つけたので、マークダウンパーサ書くのしんどいし最終的にはRedcarpetからXHTMLに変換してOgaというXMLパーサを使って無理やりASTにした。

今後追加したい機能

  • 太字
  • リストのネスト
  • シンタックスハイライトされたコードの挿入

実装が簡単そうな順。プライオリティは逆順。これの開発にそこまで興味があるわけではないのでいますぐ拡張しようというモチベはなくて、次に自分が資料作りで困ったときに拡張すると思う。

#shibuyarb でRailsのN+1 countクエリについてLTしました

追記: 2017/09/06

少しAPIが冗長なもののActiveRecordへのモンキーパッチが少ないバージョン activerecord-precounter というのを作りました。こちらの方がバグりにくいはずなので、現在はactiverecord-precounterの方を使うことが推奨されます。

github.com

#shibuyarb でLTをした

ぼくが一番長い期間書いている言語はRubyなんだけど、なぜかGoの話ばかりしていてRubyについての発表をしたことがなかった。
新卒研修も終わって平日やっている勉強会にも気軽に出れる感じになったので、以前作ったactiverecord-precountというgemについて発表をしてきた。

ActiveRecordのN+1 countクエリどうすんの問題

Tweet.all.each do |tweet|
  tweet.favorites.count
end
# SELECT `tweets`.* FROM `tweets`
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 1
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 2
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 3
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 4
# SELECT COUNT(*) FROM `favorites` WHERE `favorites`.`tweet_id` = 5

みたいなやつがあるとき、

  • 基本的にはRails標準のcounter_cacheを使う
  • あんまりALTER TABLEしたくないとか、カラムを増やすほどでもないときはeager loading
    • Railsにその手段が用意されていないので、その機能を入れてくれるのがactiverecord-precount gem

という話をした。

使い方

使い方については RailsでN+1 countクエリを潰すactiverecord-precountを作った にも書いたけど、

Tweet.all.precount(:favorites).each do |tweet|
  p tweet.favorites.count
end
# SELECT `tweets`.* FROM `tweets`
# SELECT COUNT(`favorites`.`tweet_id`), `favorites`.`tweet_id` FROM `favorites` WHERE `favorites`.`tweet_id` IN (1, 2, 3, 4, 5) GROUP BY `favorites`.`tweet_id`
Tweet.all.eager_count(:favorites).each do |tweet|
  p tweet.favorites.count
end
# SELECT `tweets`.`id` AS t0_r0, `tweets`.`tweet_id` AS t0_r1, `tweets`.`user_id` AS t0_r2, `tweets`.`created_at` AS t0_r3, `tweets`.`updated_at` AS t0_r4, COUNT(`favorites`.`id`) AS t1_r0 FROM `tweets` LEFT OUTER JOIN `favorites` ON `favorites`.`tweet_id` = `tweets`.`id` GROUP BY tweets.id

みたいにするだけなので、別にカラムとか追加しないから、いざ何か問題が起きてもロールバックしやすいと思う。内部的にはActiveRecord::Baseのインスタンスを余計に作ったりしないから速い。
(単にCOUNTしたIntegerをいれておくだけ。)

放置されているN+1 countクエリを見かけたら是非お試し下さい。

Go Conference 2015 summer でLTをしました

#gocon でLTしてきた

GoCon、LTしたかったけど特に発表することないなあと思ってたら同僚に「ppとかあるじゃん」って言われて、確かにppについて話したことないなと思ったので申し込んでみたら通った。

話した内容について

k0kubun/pp

ppはGo Advent Calendar 2014でそこそこ広まったし、「プリントデバッグにpp.Println()を使え」以上の説明が特にないのでppにまつわる小ネタを2つ話した。
特に伝えたかったのはpp.Println()をラップしただけの関数を含むファイルを用意しておく方で、こうするとimportの書き直しがいらないだけでなく記述量も減って便利なのではと思っている。

reflectとメタプログラミング

ppがあまりにも話すことなくて尺余りそうだったのでreflectについてRubyistのためのGolangメタプログラミングに書いた内容を少しGoよりに変更して話した。(もはやppの説明ですらない)

メタプロについては発表のためにひと通り情報を集め直し、Goだとコードを生成するやり方が一番現実的だなあと思ったけど、monochromeganeさんと被りそうだったのでgo generateについては触れないようにした。おかげでLTにぴったりな非実用的なコンテンツになってちょうどよかった。

クイズで「○」になる部分に関しては一応reflectじゃないとできなさそうなコード片を貼ったつもりだけど、実際に使いそうなケースはほとんど思いつかない。
他の発表でも言われていた通りreflect使うと遅くなるので、ppみたいな開発時とかテストに使うツールにしか使えなさそう。

感想

発表者19人いるの盛りだくさんすぎて疲れそうと思ってたけど、面白い発表だらけだったので飽きずに楽しめた。 5分話しただけなのにGopher人形いただけたのが予想外で嬉しかった。

運営の皆様お疲れ様でした!

Ripperによるhamlのattributeパースの話

Hamlのパーサぶっ壊れてる問題

本家のHamlは以下の入力を与えるとSyntaxErrorになる。

%div{ foo: "}" }

これはHamlが単に{}の数だけ合わせてパースしているからである。 *1

通常この問題を解決するには字句解析器を使い、現在パースしているのが何のトークンなのか判別する必要がある。
Hamlitではこの問題を解決するため、標準ライブラリとして提供されているRipperを使っている。
そのため、Hamlitでは上記の入力を以下のように正しく解釈することができる。

<div foo='}'></div>

HamlのattributeがRubyの式としてvalidでない問題

上記のhamlの%divを取り除いた部分であればRubyのHashとして解釈できそうに見える。
しかし実際には以下のような入力もHamlのattributeとして有効である。*2

- hash = { hoge: "piyo" }
%div{ hash, foo: "bar", hello: "world" } hello

もはやHashでもなんでもない。
なぜこれが動くかというと、以下のようにコンパイルされるようになっているからだ。

 hash = { hoge: "piyo" }
_hamlout.push_text("<div#{_hamlout.attributes({}, nil,  hash, foo: "bar", hello: "world" )}>hello</div>\n", 0, false);

つまり{}の内側は_hamlout.attributesに渡す可変長の引数の一部なのである。*3
末尾にHashっぽいのが書いてあるとそれは引数の一つとして解釈され、ここではHashが2つ渡されていることになる。

Ripperをどう使うか

パース時にどこまでがattributeか判別するため、%divを除いた以下の入力をRipperに渡すと、

[3] pry (main)> Ripper.lex('{ hash, foo: "bar", hello: "world" } hello')
# [[[1, 0], :on_lbrace, "{"],
#  [[1, 1], :on_sp, " "],
#  [[1, 2], :on_ident, "hash"],
#  [[1, 6], :on_comma, ","],
#  [[1, 7], :on_sp, " "],
#  [[1, 8], :on_label, "foo:"],
#  [[1, 12], :on_sp, " "],
#  [[1, 13], :on_tstring_beg, "\""],
#  [[1, 14], :on_tstring_content, "bar"],
#  [[1, 17], :on_tstring_end, "\""],
#  [[1, 18], :on_comma, ","]]

途中で終わる…

では可変長引数ならばと先頭をメソッド呼び出しに改変して渡すと、

Ripper.lex('a( hash, foo: "bar", hello: "world" } hello')
# [[[1, 0], :on_ident, "a"],
#  [[1, 1], :on_lparen, "("],
#  [[1, 2], :on_sp, " "],
#  [[1, 3], :on_ident, "hash"],
#  [[1, 7], :on_comma, ","],
#  [[1, 8], :on_sp, " "],
#  [[1, 9], :on_label, "foo:"],
#  [[1, 13], :on_sp, " "],
#  [[1, 14], :on_tstring_beg, "\""],
#  [[1, 15], :on_tstring_content, "bar"],
#  [[1, 18], :on_tstring_end, "\""],
#  [[1, 19], :on_comma, ","],
#  [[1, 20], :on_sp, " "],
#  [[1, 21], :on_label, "hello:"],
#  [[1, 27], :on_sp, " "],
#  [[1, 28], :on_tstring_beg, "\""],
#  [[1, 29], :on_tstring_content, "world"],
#  [[1, 34], :on_tstring_end, "\""],
#  [[1, 35], :on_sp, " "],
#  [[1, 36], :on_embexpr_end, "}"],
#  [[1, 37], :on_sp, " "],
#  [[1, 38], :on_ident, "hello"]]

とりあえず最後までスキャンしてくれる。

ここで判別に使いたい}:on_embexpr_endになっていることに着目する。
これは本来:on_embexpr_beg(string interpolationの開始とか)と対になるトークンである。

したがって、非常にRipperの実装依存っぽい感じだが、:on_embexpr_endの数が:on_embexpr_begの数より1つ少なくなる場所でストップすればいい感じに動く。
現在のHamlitのmasterはこのように実装されている。
:on_embexpr_endの手前まではvalidな式を渡せているのでこれ以上マシな使い方は思いつかなかったが、Rubyのバージョンが上がっても動きそうな使い方をしたい…

文字列がRubyのHashとしてvalidか判別する方法

Hamlitでは、attributeの{}内の部分がRubyの式としてvalidだった場合に限り静的なコンパイルを試みて最適化をしている。

https://bugs.ruby-lang.org/issues/10405 を見ると、Ripper.sexpは引数がRubyの式としてinvalidなときはnilを返すようなことが書かれていて、Hamlitでも一度それに依存した実装を行った。

バージョンによって異なる返り値

しかしRipper.sexp(str)だけで判別するコードをpushしてみるとRuby2.1と2.0のCIが落ちた。

2.0.0-p645 (main)> Ripper.sexp('{a, b: "c"}')
#=> [:program, [:string_literal, [:string_content, [:@tstring_content, "c", [1, 8]]]]]

2.1.6-p336 (main)> Ripper.sexp('{a, b: "c"}')
#=> [:program, [:string_literal, [:string_content, [:@tstring_content, "c", [1, 8]]]]]

2.2.2-p95 (main)> Ripper.sexp('{a, b: "c"}')
#=> nil

結局、以下のように対応した。

def valid_hash?(str)
  sexp = Ripper.sexp(str)
  return false unless sexp

  sexp.flatten[1] == :hash
end

まとめ

Ripperは後方互換性が保証されないので必ずサポートしたいRubyのバージョン全てで動作確認を行いましょう