LTのスライドつくるのだるい

Keynoteの使い方よくわからないし、なんでこんなものに数千円はらったのか意味がわからない。だいたい、マスタという概念はwebでいうCSSなんだからテキストファイルとして定義できるようにすればいいのに。そうすれば、githubとかで共有できるから、かっこいいスライドのマスタを再利用することができる。そういうところでLTのスライドつくるのがだるくなる。明らかな非効率性を目の前にするとまったく手が進まなくなる。

だるいことは自動化するのがプログラマの美徳なので、土日で自動化を試みた。

naoty/slide_template · GitHub

cloneして、content.mdというファイルにスライドの内容をmarkdownで書いて、rakeすればHTMLのスライドができる。cssでスライドを自由にデザインできる。面倒なGUIの操作をいちいち覚える必要はなくなった。cssということはそれを共有することで、感じのいいスライドを再利用できる。あの人のスライドで使ってるフォントをいちいち調べる必要もない。

こういうフレームワーク的なものは既にいくつかあって、最初はreveal.jsを使おうと思った。ただ、実際に使ってみると確かにかっこいいアニメーションがついていい感じなんだけど、いくつか不満な点があった。まず、海外で作られたデザインなので、日本語を使うと文字サイズや余白のバランスに違和感を感じる。で、カスタマイズしてみようと思ったんだけど、どこをいじっていいのかわからなかった。いや、これは僕のスキルに問題があるだけかもしれない。実際、sassを使ったりmixinを使ったり工夫がこらしてあった。でも、正直、ここまで複雑で凝ったものはいらなかった。なんでもそうだと思うけど、長く使っていくツールに必要なのは"カスタマイズしやすさ"だと思う。違和感を感じたときにすぐに修正できないとストレスがたまっていって、ずっと使っていくことができなくなる。カスタマイズしやすいツールに必要なのは、疎結合な設計だと思う。設定の変更が及ぼす影響をなるべく小さくしないと、恐ろしくてカスタマイズできない。

というわけでreveal.jsもよかったけど、もっと小さいライブラリを自作することにした。

naoty/haas.js · GitHub

上のテンプレではこのライブラリを使ってHTMLをスライドっぽくしている。これまでJSのライブラリ(というには小さくておこがましいけど)を作ったことがなかったので、yak-shavingをすることになった。まず生JSはイヤだったのでcoffeescriptで書くことにして、coffeescriptをJSにコンパイルするためにgruntを使った。コンパイルされたJSは自動的にminifyするようにした。ここらへんの環境構築については以下のエントリが参考になった。

昨今のWebアプリケーションのひな形その2 - Grunt - naoyaのはてなダイアリー

で、このライブラリをテンプレで使うためにbowerのレポジトリに登録することにした。これが思ったより簡単でbower.jsonを決められたフォーマットで記述して

$ bower register haas.js git://github.com/naoty/haas.js.git

を実行するだけだった。

テンプレの話に戻ると、rakeタスク内でRedcarpetとTiltを使ってmarkdownやhamlをHTML化している。で、そのHTMLを上のhaas.jsでスライドっぽくすることで、なんとかスライドの体裁を整えることはできた。あとは、肝心のデザインをなんとかしなくちゃいけない。デザインについてはCSS難しいしできればどなたかにお願いしたいところではある。というか、それがこのテンプレをgithubで公開する目的なんだけど。

以上のような話を今度のebisu.rbで話すので、参加者の方はあんまり見ないようにお願いします(もう遅い


追記

ebisu.rbで話したので、上のテンプレで作ったスライドを公開します。

http://naoty.info/slides/darui/index.html#1

todoリストをwebに公開した話

自分が開発したいこと、勉強したいことをwebに公開した。

naoty.info/todo

以前に書いた記事の通り、ここ数週間vimでtodoリストを書くようになった。これがなかなかよくて、.vimrcをいじってtodoリストを書きやすいようにvimをカスタマイズした。GUIアプリだと自由にカスタマイズできなくて、痒いところに手が届かずに使わなくなってしまうケースがあったけど、vimだと自由にいじれるからそういうこともなく長続きしているのだと思う。

そうこうしてるうちに、vimで書いたtodoリストはどんどん増えていった。その中には誰かがやってくれればいいものもあったので、公開していいかと思った。あと、todoリストを買い物リストのように使うことがあって、そういうときに外出先でiPhoneからチェックしたいと思ったのでwebで公開した。viewportを設定してモバイル端末からも見やすいようにした。

公開の仕組みは単純で、todo.mdというファイルをguardで監視して保存されたら自動的にあるスクリプト(下記リンク)とscpが実行されるようにした。このスクリプトはtodo.mdをパースし用意しておいたテンプレートとくっつけてHTML化する。そして、そのHTMLがscpでサーバーにアップロードされる。

naoty/md2html · GitHub

あとRedcarpetをちょっと拡張して- [ ], - [x]チェックボックスに変換するようにした。これはGithub Flavored Markdownで実装されているTask Listの形式を参考にした。


余談

最初はこの仕組みをSinatra、Heroku、Dropbox APIで作ろうとしたのだけど、いろいろ問題があって今の仕組みにいたった。結局「markdownの変換」「scpによるアップロード」の2つを自動化しただけのシンプルな形になった。最近これ以外にも、仕組みを選択する段階で失敗する経験があった。単純な仕組みであるほど問題は少ないし、起きたときに解決しやすいと思った。

「todoリストをテキストファイルとして扱う」というアイデアは、vimを十二分にカスタマイズ可能なtodoアプリとして扱えるようになっただけでなく、今回のようにtodoリストをwebに公開するというところまで行き着いた。なんかで読んだけど、データをテキストとして保存した方が扱いやすいというのが身にしみて理解できたのでよかった。

Androidに乗り換えるかも

自分のために作ったアプリを公開したい場合にiOSはとてもハードルが高い。 仕事でやるなら別にいいんだけど、自分で使うのが主のモチベーションで 「他の人もこれ使ったら便利だと思う」程度のモチベで公開しようとするとそのハードルの高さに愕然とする。 審査が入るから、かなり完成度を高めないと落とされる。 完成度を高めていく作業はけっこう根気がいる。

iOSは審査プロセスもビルドプロセスも複雑で、開発以外の部分で時間をとられるのがむかつく。 なぜかよくわからないけど、Androidはantとかmavenでビルドを自動化するノウハウがたくさんあるのに、 iOSはビルドの自動化に関して情報が少ない気がする。 とりあえずrakeでビルドとTestFlightへのアップロードを自動化したけど、けっこう時間がかかった。

Rakefile for building and uploading to testflight

iOSはAndroidとくらべてUIがキレイというのはある。 だけど、Android2.3なんかと比べるとそうだと思うけど、4以降になるとそんなに気にする程でもなくなったと思う。

今のところiPhone5を使っているけど、片手で使えるNexusシリーズが出たらAndroidに乗り換えると思う。 Android端末はデカすぎる。日本のメーカーはあれだけ小型化・薄型化が好きだったのに、 なんでスマホになると大型化するのか意味がわからない。

mrb_valueについて調べてみた

昨日の続き。

mrubyのソースコードを読むと、mrb_valueという構造体がよく出てくるのでソースコードを追いかけて使い方を調べてみた。参照しているコミット番号は昨日と同じく「9663a7」です。

mrb_valueの定義

// include/mruby/value.h:40

typdef struct mrb_value {
  union {
    mrb_float f;
    void *p;
    mrb_int i;
    mrb_sym sym;
  } value;
  enum mrb_vtype tt;
} mrb_value;

mrb_value構造体は値とその値のデータ型をもつ。enum mrb_vtypeにはMRB_TT_FIXNUMとかMRB_TT_STRINGなどが入る。valuettは適切な組み合わせにする必要があるはず。

mrb_valueMRB_NAN_BOXINGが定義されているかどうかでその定義が変わるんだけど、MRB_NAN_BOXINGはmrbconf.hでコメントアウトされていたので、mrb_valueのデフォルトの定義は上のようになる。

// include/mrbconf.h:23

/* represent mrb_value in boxed double; conflict with MRB_USE_FLOAT */
//#define MRB_NAN_BOXING

どういうときにこれを使うのかはまだよくわかってない。

mrb_valueとデータ型の変換

int型、char型などとmrb_valueを変換する方法も調べた。まず、変換する関数によく使われているmrb_value構造体に値をセットするマクロがある。

// include/mruby/value.h:53

#define MRB_SET_VALUE(o, ttt, attr, v) do {\
  (o).tt = ttt;\
  (o).attr = v;\
} while (0)

これを使って変換する関数が実装されているっぽい。とりあえず見つけたのは以下の通り。

int -> mrb_value

// include/mruby/value.h:205

static inline mrb_value
mrb_fixnum_value(mrb_int i)
{
  mrb_value v;

  MRB_SET_VALUE(v, MRB_TT_FIXNUM, value.i, i);
  return v;
}

mrb_value -> int

// include/mruby/value.h:145

#define mrb_fixnum(o) (o).value.i

float -> mrb_value

// include/mruby/value.h:58

static inline mrb_value
mrb_float_value(mrb_float f)
{
  mrb_value v;

  MRB_SET_VALUE(v, MRB_TT_FLOAT, value.f, f);
  return v;
}

mrb_value -> float

// include/mruby/value.h:51

#define mrb_float(o) (o).value.f

char[] -> mrb_value

// src/string.c:670

char *
mrb_string_value_ptr(mrb_state *mrb, mrb_value ptr)
{
  mrb_value str = mrb_str_to_str(mrb, ptr);
  return RSTRING_PTR(str);
}

char[] -> mrb_value

// src/string.c:232

mrb_value
mrb_str_new(mrb_state *mrb, const char *p, size_t len)
{
  struct RString *s;

  s = str_new(mrb, p, len);
  return mrb_obj_value(s);
}

mrubyで定義したクラスとメソッドをCから呼び出す

mrubyで書いた方がいいところはmrubyで書いてそうじゃないところはCで書く、という開発をするには、Cで定義した関数をRubyから実行させたり、逆にRubyで定義したクラスやメソッドをCから呼び出せるようにする必要があると思った。前者のような実装はmrbgemsを読めばたくさんある一方で、後者の実装は調べたけどあんまりなかった。そこで、先日「Head First C」でCの初歩を学んだことだし、mrubyのソースコードを読みながら後者の「mrubyで定義したクラスとメソッドをCから呼び出す」実装を試行錯誤してみた。

試行錯誤してみてとりあえず動いたというだけで、正しいやり方じゃないかもしれないので、コメントか@naoty_k宛にメッセージをいただけるとありがたいです。また、参照しているmrubyのコミット番号は「9663a7」です。

Rubyのクラスとメソッドを用意

適当にPersonクラスとメソッド2つを用意する。あとでこれらをCから呼び出す。

// person.rb

class Person
  attr_accessor :name, :age

  def initialize(name, age)
    @name = name
    @age = age
  end

  def greeting
    "Hello, my name is #{name}, #{age} years old."
  end
end

mrbcでコンパイル

RubyのファイルをCからロードするにはいくつか方法があるようだけど、今回はmrbcで*.mrb形式にコンパイルしてCからロードするようにする。

$ ls
person.rb
$ mrbc person.rb
$ ls
person.mrb person.rb

Cから定義したクラスとメソッドを呼び出す

CからRubyで定義したPersonインスタンスを生成してgreetingメソッドの結果を標準出力に表示してみる。

// greeting.c

#include <stdio.h>
#include <mruby.h>
#include <mruby/string.h>

int main()
{
    mrb_state* mrb = mrb_open();

    // mrubyファイルをロードする
    FILE *fd = fopen("person.mrb", "r");
    mrb_load_irep_file(mrb, fd);

    // クラスオブジェクトを取得する
    struct RClass *person = mrb_class_obj_get(mrb, "Person");

    // 引数をmrb_valueに変換する
    mrb_value person_value = mrb_obj_value(person);
    mrb_value name_value = mrb_str_new(mrb, "naoty", 5);
    mrb_value age_value = mrb_fixnum_value(25);

    // Person#newを呼び出す
    mrb_value naoty = mrb_funcall(mrb, person_value, "new", 2, name_value, age_value);

    // Person#greetingを呼び出す
    mrb_value greeting_value = mrb_funcall(mrb, naoty, "greeting", 0);

    // 返り値をchar*に変換して出力する
    char *greeting = mrb_string_value_ptr(mrb, greeting_value);
    printf("%s\n", greeting);

    mrb_close(mrb);
    return 0;
}
  • *.mrb形式のファイルをロードするにはmrb_load_irep_file()を実行する。
  • 次にクラスを取得するにはmrb_class_obj_get()を実行し、メソッドを呼び出すにはmrb_funcall()を実行する。
  • mrb_funcall()には、第2引数にメソッドのレシーバ、第3引数にメソッド名、第4引数にメソッドの引数の数、第5引数以降にはメソッドの引数を渡す。第2引数と第5引数以降はintchar*などをそのまま渡すことはできなくて、mrb_valueという構造体に変換する必要がある。変換するための関数については長くなりそうなので、別の記事にしようと思う。
  • mrb_funcall()の返り値もmrb_value構造体なので、標準出力をするためにchar*に変換する。

Cをコンパイルして実行

Cのソースコードをmrubyのヘッダーファイルやスタティックライブラリと一緒にコンパイルする。僕の環境だと以下のコマンドでコンパイルできた。

$ gcc -I ~/mruby/include greeting.c ~/mruby/build/host/lib/libmruby.a -lm -o greeting
$ ./greeting
Hello, my name is naoty, 25 years old.

greeting.cはperson.mrbに依存し、person.mrbはperson.rbに依存しているので、一連のビルドはMakefileRakefileで自動化したほうがいいと思う。

// Rakefile

require "rake/clean"

CC = "gcc"
MRBC = "mrbc"

CLEAN.include("person.mrb")
CLOBBER.include("greeting")

task default: "greeting"

file "greeting" => ["greeting.c", "person.mrb"] do |t|
  sh "#{CC} -I ~/mruby/include #{t.prerequisites[0]} ~/mruby/build/host/lib/libmruby.a -lm -o #{t.name}"
end

file "person.mrb" => ["person.rb"] do |t|
  sh "#{MRBC} #{t.prerequisites[0]}"
end
$ rake
$ ./greeting

参考

Head First Cの感想

以前参加したmrubyの勉強会でC言語でコードを書く機会があってよくわからなかったので、オススメされた「Head First C」を読んでみた。

Head First C ―頭とからだで覚えるCの基本

Head First C ―頭とからだで覚えるCの基本

500ページ超あるんだけどすごくわかりやすくて10日間で読み終わった。Head FirstシリーズはRailsなどいろいろあるのは知ってたけど読んだことなかった。どうやらこのシリーズは科学的なアプローチで分かりやすく書いているみたいで、確かにわかりやすかった。C言語の入門書というとすごい古い本や固っくるしい本が多いような印象があるけど、この本は今月発売されたばかりでかつカジュアルだったので、とっかかりやすかった。

内容はmain()とかポインタから始まって、最後はマルチプロセスのwebサーバーを作ったりマルチスレッドプログラミングを扱うところまでカバーしてる。やっぱりポインタが鬼門で、何度も読み返したけど80%くらいの理解。文字列ポインタとか配列変数とかで???ってなった。それ以外は本当にわかりやすくてよかった。特にメモリ管理のところはわかりやすかった。普段Rubyとか書いてるとあまりメモリのことは考えないけど、スタックとヒープの区別がついたり、valgrindを使ってメモリリークを調べたりすることでだいぶメモリに対する意識が高まった。また、gccでコンパイルしたりリンクをひとつひとつ丁寧にやることでmakeの有難みを感じた。iOSアプリを開発するとたまに出てくるスタティックリンクライブラリ(lib*.aみたいなの)の正体もわかってよかったし、今後iOSアプリのビルドに失敗しても怯えなくて済みそう。あと、途中のコラムでOpenCVについて簡単な説明があってちょっと興味がわいてきた。Raspberry Piとウェブカメラを買ってOpenCVで遊んでみたいと思った。

vimでTodoリスト

今までいろんなTodo管理アプリを試してきたけど、「GUIアプリほど高機能はいらない」「ターミナル上でtodoを確認したい」という理由でvimでTodoリストを書くようになった。これによるとGithubがGithub Flavored MarkdownにTodoリスト記法を実装したようなので、これに倣ってmarkdownでTodoリストを書くことにした。

todoコマンド

まず、Todoリストを開くコマンドをaliasで定義してみた。これでtodoでTodoリストを確認できる。さらに、Dropbox上にファイルを置けば複数PCで共有できるので、オフィスのPCとプライベートPCでTodoリストを共用できる。

# .zshrc

if [ -e "$HOME/Dropbox" ]; then
  alias todo="$EDITOR $HOME/Dropbox/.todo.md"
else
  alias todo="$EDITOR $HOME/.todo.md"
end

vimでmarkdownを書く準備

次に、vimでmarkdownを書く準備をする。普通に*.mdを開くとmodula2というfiletypeで認識されてしまい、markdownファイルとして見なされないので、便利プラグインをインストールする。

" .vimrc

NeoBundle 'tpope/vim-markdown'

折り返しを有効にする

これだけでも十分なんだけど、より使いやすくするための設定を自分なりに考えてみた。まず、一行が長くなるとリストとしては見づらいので、普段は折り返さないけどmarkdownのときだけ折り返すようにしてみた。

" .vim/ftplugin/markdown.vim

" 折り返しを有効にする
set wrap

" 80文字で折り返す
set textwidth=80

" マルチバイト文字の場合も折り返しを有効にする
set formatoptions+=m

Todoリストを簡単に書く

上でふれたGithubが実装したTodoリスト記法- [ ], - [x]を簡単に入力するための設定も書いた。abbreviateを使うと略記を登録することができる。下の設定ではtl<space>と入力すると- [ ]と自動的に変換される。さらに、Todoリストのある行の上で<Leader>を2回おすと(僕は<Leader><space>にしてる)、チェックをon/off切り替えられる。

" .vim/ftplugin/markdown.vim

" todoリストを簡単に入力する
abbreviate tl - [ ]

" todoリストのon/offを切り替える
nnoremap <buffer> <Leader><Leader> :call ToggleCheckbox()<CR>

function! ToggleCheckbox()
  let l:line = getline('.')
  if l:line =~ '^\-\s\[\s\]'
    let l:result = substitute(l:line, '^-\s\[\s\]', '- [x]', '')
    call setline('.', l:result)
  elseif l:line =~ '^\-\s\[x\]'
    let l:result = substitute(l:line, '^-\s\[x\]', '- [ ]', '')
    call setline('.', l:result)
  end
endfunction

スクリーンショット

普段は下のようにtmuxで画面を分割して小さいウィンドウ(右上)にTodoリストを表示しながら開発している。

f:id:naoty_k:20130428002301p:plain