Quantcast
Channel: てっく煮ブログ
Viewing all 42 articles
Browse latest View live

LiveReloadX 0.2.0 公開! 除外ファイルを指定可能になったよ

$
0
0

LiveReloadX 0.2.0 を公開した。

新規インストールするなら今まで通り npm install -g livereloadx で、バージョンアップするなら npm update -g livereloadx でどうぞ。

以前のバージョンでは、監視対象のフォルダー配下のファイルが更新されるたびにブラウザーをリロードしていたので、たとえば、git で管理しているフォルダーを監視していると、git commit するたびにブラウザーがリロードされるという悲しい状態だった。

そこで、除外フォルダーやファイルを指定できるようにした。

たとえば、cache フォルダーを監視から除外したいときは

$ livereloadx --exclude cache

とすればよい。

また、デフォルトでは html, shtml, css, js, jpg, gif, png, cgi, php など、Web 開発に一通り必要そうな拡張子のみを監視するようにした。たとえば、追加で ini を編集したときにリロードさせたい場合には

$ livereloadx --include "*.ini"

とすればよい。いままで通り、すべてのファイルを監視したければ、

$ livereloadx --include "*"

とすればよい。

include/exclude の判定ロジックは rsync の真似をしているので自由度はかなり高い。ただ、rsync の include/exclude ルール自体が複雑怪奇なので、それについては rsync の複雑怪奇な exclude と include の適用手順を理解しよう で記事にした。


rsync の複雑怪奇な exclude と include の適用手順を理解しよう

$
0
0

rsync は便利なんだけど、オプションが多くて難しい。特にややこしいのがファイルを選別するための --exclude--include オプションだ。

man を読んでもイメージがつかみにくかったので、ググったり、-vvv の結果を見たり、ソースを読んだりしつつ調べてみたところ、3 つのルールを理解すれば何とかなりそうなことが分かった。

この記事では、その 3 つのルールをなるべく分かりやすく説明する。

ルール1: 指定順に意味がある

コマンドライン引数は、通常、どの順番に指定しても同じ挙動になることが多い。しかし、rsync の includeexclude に関しては、指定順が意味を持つ。

man にも出てくる例で説明しよう。MP3 だけをコピーするには次のようにする。

rsync -av --include='*/' --include='*.mp3' --exclude='*' src dst

-av はコピーするときのお決まりのオプション。ネットワーク越しにコピーするときは、-avz として圧縮して転送することが多い。-n (dry run) をつければ、どのファイルが同期されるのかを事前に知ることができる。

今回の本題は 3 つのフィルター。それぞれの意味は次の通り。

  1. --include='*/': フォルダーはコピーする
  2. --include='*.mp3': 拡張子が MP3 のファイルはコピーする
  3. --exclude='*': それ以外はコピーしない

もし、exclude を手前にもってきて

rsync --include='*/' --exclude='*' --include='*.mp3' src dst

とすると、フィルターの優先順位が

  1. --include='*/': ディレクトリーはコピーする
  2. --exclude='*': すべてのファイルをコピーしない
  3. --include='*.mp3': 拡張子が MP3 のファイルはコピーする

となる。*.mp3 の前に * にマッチしてしまうので、何もコピーしてくれない。

フィルターの指定順には意味があって、先頭から順番に判定していくと覚えておこう。

ルール2: 上の階層から順番に調べる

いきなりクイズ。

次のコマンドを実行したとき、src フォルダーにある public_html/index.html はコピーされるだろうか?

rsync -av --include='index.html' --exclude='public_html/' src dst

このコマンドのフィルターは次の 2 つ。

  1. --include=index.html
  2. --exlclude=public_html/

public_html/index.html は両方のフィルターにマッチしそうだ。さきほど「先頭のフィルターを優先する」と説明したので、1 つ目のフィルターを優先してコピーされそうに思える。

しかし、答えは「コピーされない」である。その理由を説明していこう。

まず最初に上の階層をチェック

rsync は public_html/index.html をコピーするか判定する前に、上の階層の public_html をコピーするかどうかを確認する。

public_html に対して、2 つのフィルターを順番に適用する。

  1. --include=index.html にはマッチしないので先へ進む。
  2. --exclude=public_html/ にはマッチする。

exclude になった場合、それより下位のフォルダーは確認しない。

つまり、public_html/index.html は問答無用でコピー対象から除外される。

フォルダー内の特定のファイルのみを転送するには?

では、public_html の下の index.html のみをコピー対象とするにはどう設定すればよいだろうか。

答えはこうなる。

rsync -av --include 'index.html' --exclude 'public_html/*' src dst

順番にみていこう。

まず最初に public_html に対して 2 つのフィルターを適用する。

  1. --include=index.html にはマッチしないので先へ進む。
  2. --exclude=public_html/* にもマッチしない (* は 1 文字以上の任意の文字にしかマッチしない)。

include にマッチしたときと、どのフィルターにもマッチしなかったときは、下の階層のチェックに進む。

ということで、public_html/index.html に対して 2 つのフィルターを適用してみよう。

  1. --include=index.html にはマッチする。

include にマッチしたので、次の階層のチェックに進む。が、これが最後の階層なので、public_html/index.html はコピー対象となる。

同じように考えていけば、public_html/other.html が除外されることも分かるだろう。

(参考) 擬似コード

ここまで文字で長々説明してきたが、プログラムが読める人なら、擬似コードで説明したほうが早いだろう (読めない人は飛ばしてね)。

// public_html/index.html をコピーするか確認 (上の階層から順番にチェックする)
foreach (name in ['public_html', 'public_html/index.html']) {
  // 1 つ目のフィルターから順番にチェックする
  foreach (filter in filters) {
    if (filter_match(name, filter)) {
      if (is_exclude(filter)) {
        // exclude フィルターにマッチしたらその場で中断
        return false;
      } else {
        // include フィルターにマッチしたら、次の階層のチェックに移る
        break;
      }
    }
  }

  // include にマッチしたとき、1 つもマッチしなかったときはここにくる
}

// すべての階層で除外されなければコピーする
return true;

ルール3: 個別のフィルターの記法いろいろ

あとはフィルターの記法を知ってれば完璧に理解できるだろう。

foo は何にマッチするか

単に foo というフィルターを書いたとき、末尾部分が foo になっているファイルにマッチする。

  • ○: foo
  • ×: abcfoo
  • ○: abc/foo
  • ×: foo/abc

正規表現で書くと (^|/)foo$ となる。

/ から始めると

一方、/ から始めて、/foo のようにすると、マッチ対象が先頭のみになる。

  • ○: foo
  • ×: abcfoo
  • ×: abc/foo
  • ×: foo/abc

正規表現で書くと ^foo$ となる。

/ で終わると

末尾に / をつけると、対象がフォルダーのときのみマッチする。

foo/foo がフォルダーのときはマッチするが、ファイルのときにはマッチしない。

* や ** を使ってみる

記号についてもまとめておこう。

記号 意味 対応する正規表現
* / 以外の 1 文字以上 [^/]+
** / を含む 1 文字以上 .+
? / 以外の 1 文字 [^/]

たとえば、*.png を正規表現で書くと (^|/)[^/]+\.png$ となる。abc.pngfoo/abc.png にはマッチするが、abc.png/foo.pngにはマッチしない。

さらに、***foo/*** のように指定することで、フォルダーとその配下のファイルをまとめて指定できる。foo/*** と指定するのは、foo/foo/** の両方を指定することと等しい。

[a-z] を使う

正規表現のように、マッチする文字の範囲を指定できる。[^a-z] のように特定の文字以外にマッチするようにも書ける。POSIX クラスで [[:digit:][:upper:][:space:]] のように書くこともできる。

正規表現のように書けてうれしいのだが、[a-z]* と書いたとしても、単に [a-z][^/]+ にしかマッチしない。間違えないように注意。

まとめ

rsync の複雑な include / exclude の処理について説明した。要点は次の 3 つ。

  • フィルターは指定順に優先されている
  • 判定処理は上の階層から実施して、各階層の中でフィルターを順番に適用していく
  • 個別の記法を理解すべし

これが理解できれば、あとは頭の体操でフィルター条件を作れるはず!

一見複雑怪奇ではあるが、このような仕組みになっているおかげで、一部除外や一部許可を設定しやすくなっている。一方、tar コマンドは man を見ると exclude を優先すると書いてあるので、一部除外しか指定できないような気がする。

Git で複数ブランチを同時に扱いたいなら git-new-workdir が便利

$
0
0

Git で管理してるレポジトリーで、いくつかのブランチを別々の場所にチェックアウトしたいことがある。

たとえば

  • GUI なツールでブランチ間の比較したい
  • 同時に実行して比較しつつテストしたい
  • ブランチ間でファイルをコピーしたい
  • ドキュメントの生成結果を別ブランチで管理したい

といったときに、必要になる。

ブランチの個数だけ clone しちゃえば用は足りそうなんだけど、でかいレポジトリーだったら時間もディスク容量ももったいない。

git-new-workdir を使うべきでしょう!

先日、「git-new-workdir を使えばワーキング ディレクトリーを複数を作れて便利」と書いてあるブログを読んだ。

git-new-workdir の usage を見てたら、別ブランチのワーキング ディレクトリー作成にも対応しているらしい!

git-new-workdir <repository> <new_workdir> [<branch>]

これは活用しない手はない。

git-new-workdir はこんなに便利

master ブランチで作業しているとして、develop ブランチの中身も展開したいと思ったとする。

git-new-workdir . ../foo-develop develop

これだけで ../foo-developdevelop ブランチの中身を展開してくれる。

展開の処理に工夫があって、ワーキングディレクトリーとステージ (インデックス) は独立なんだけど、コミット履歴などはシンボリックリンクで共有されている。

だから、ワーキング ディレクトリーの中身を比較できるのはもちろん、別々の場所で編集してコミットしてもよい。片方でコミットした内容は、もう片方で git log すれば表示できる。

git push すれば masterdevelop の両方の変更を一気に push できる。別々に clone していたらありえない。

もちろん、git fetch も、どちらか一方で実行すれば、もう片方も最新の状態になってる。

いろいろ便利でハッピー!

ドキュメントを別ブランチで管理するような場合にも使える

GitHub Pages を使うときにはありがちなんだけど、ドキュメントの生成結果を別ブランチにコミットする。このブログでは、Jekyll でサイト生成した結果を別ブランチにコミットしている (詳しくは 俺の最強ブログ システムが火を噴くぜ 参照)。

このようなケースには、サブモジュールを使うテクニックが知られている。

このテクニックは一見便利そうなんだけども、使っているうちに不便なところが目に付いてきた・・・。

  • 同じレポジトリーを 2 回 clone するので非効率的。
  • 2 つの作業ディレクトリーのそれぞれで push, pull しなきゃいけない。
  • ドキュメントのディレクトリーでコミットするたびに、submodule が更新された状態になる。それを放置してると、submodule init した人が古い状態のツリーを参照してしまうので、定期的にコミットして、サブモジュールの指す先を更新する必要がある。

サブモジュールはそもそも別の Git レポジトリーを管理するために設計されたものだし、同じレポジトリーをサブモジュールとして持つのはいろいろ弊害あるように思う。

で、サブモジュールに困っていたところで、git-new-workdir を使ってみたら便利だった。

開発用ブランチが master で、ドキュメントのブランチが gh-pages だとして、gh-pages ディレクトリーに gh-pages ブランチの中身をチェックアウトする。

git-new-workdir . gh-pages gh-pages

これで、Git レポジトリー直下の gh-pages ディレクトリーに gh-pages ブランチがチェックアウトされた。間違ってディレクトリーの中身をコミットしないように .gitignore に gh-pages を入れておくと安全だろう。

あとは個別のディレクトリーで編集してコミットしていく。先ほどの例と同じく、片方でコミットした内容は、もう片方の作業ディレクトリーで git log すれば反映されるし、git push すれば一気にリモートに反映される。

便利便利である。

git-new-workdir の導入方法

git-new-workdir は git-core の contrib に入っている。詳しくは git-new-workdir が便利 - #生存戦略 、それは - subtech を参照。

ただ、Windows で Git for Windows (msysGit) を使ってる場合は、そのまま持ってきても動かないので、次のようにした。

  1. git/contrib/workdir/git-new-workdir-win at master - dansmith65/git から git-new-workdir-win を拾ってくる。
  2. C:\Program Files (x86)\Git\libexec\git-coregit-new-workdir としてコピーする。(x86 環境なら Program Files (x86)Program Files に読み替えるべし)

これでおしまい。ただし、mklink.exe を使う関係で、UAC 有効にしてる環境では、bash を管理者として実行しておかないと、権限が足りなくてエラーになるので注意すべし。

まとめ

git-new-workdir で便利な Git 生活を!

SourceTree が Git のグローバルな無視リストを書き換えて困った話

$
0
0

Git を使ってるときに、git status に存在するはずのファイルが Untracked files に出てこない現象に出会って困ってしまった。

いろいろ調べてみたところ、SourceTree さんがインストール時にグローバルな無視リストを作成していたことが判明した。SourceTree を使ってないときにも影響がでるのでたちが悪い。

勝手に書き換えられてしまうファイルはこれだ!!

Windows 版の例だけど、まず、.gitconfig

[core]
    excludesfile = C:\\Users\\username\\Documents\\gitignore_global.txt

Mac の場合は /User/username/.gitignore_global に設定する模様。

gitignore_global.txt はこんな感じ。

#ignore thumbnails created by windows
Thumbs.db
#Ignore files build by Visual Studio
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*_i.c
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
*.dll
*.lib
*.sbr

Windows 系の開発で自動生成されたり、コミットする必要のないファイルが列挙されている。

.gitconfig に excludesfile がないときに発動

セットアップ時に、最初のダイアログには [Allow SourceTree to modify global Git and Mercurial config files] という設定項目がある。

デフォルトでチェック入ってるんだけど、これが入ってる以上は、.gitconfig がいじられても文句は言えない。

デフォルトのままで先に進むと、.gitconfigexcludesfile の設定がない場合には SourceTree さんは上に書いたような書き換えを行ってくれる。

シンセツダナー。

この親切すぎて涙がでてしまう挙動は、当然のように一部のユーザーの逆鱗に触れることとなり、非難轟々、雨嵐霰がふき乱れた。

「最低なやつめ! なんで gitignore_global.txt を勝手に作って .dll と .exe を除外しちゃうんだよ!?」というユーザーのお怒りの声。

これに対して、「怒らせてごめんよ。すでにグローバルな ignore ファイルがあればそんなことはしないよ。」と答える SourceTree さん。

「git に不慣れな人を助けるために、グローバルな ignore ファイルがないならそうしてるんだよ。だって、exe や dll をコミットすることなんてめったにないでしょ?」。まぁ、それはそうなんだけども・・・。

「次のリリースでは、もうちょっと注意を引きやすい警告を出すようにして、バイナリー ファイルをコミットしたい人を困らせないようにするよ」と、将来のバージョン アップを誓っている。

最近は警告ダイアログをだしてくれる

で、その結果、最近のリリースでは、こんなダイアログが表示されるようになった。

ざっと訳すと

グローバルな ignore ファイルがないようだけど、SourceTree がデフォルトのやつを設定したげようか?

.exe とか .dll とか .obj とか .suo とか Debug のようなフォルダーとか、普通はソース管理しないようなやつを追加しといてあげるよ。

もし、デフォルトで全部のファイルをみれるようにしたいなら、とりあえず No を選んどいてね。あとで、Tools > Options から設定することもできるよ。

と書いてある。

次のキャプチャーを見る限りは Mac 版でも「グローバル無視リスト」を書き換える処理はあるようだ。

(画像は kashew_nuts-tech: Mac用Git/MercurialのGUIクライアント-SourceTree-を試してみた より)

まとめ

SourceTree の Windows 版を試した人 (特に初期のバージョン) は、マイドキュメント直下に gitignore_global.txt がないか確認しておくとよいだろう。このファイルが残っていると、Git を使っていて無駄に混乱してしまうかもしれない。分かってて設定する分にはいいんだけども。

Jekyll のカテゴリーとタグの指定方法 3 パターン

$
0
0

Jekyll で記事にカテゴリーやタグを設定するには YAML の部分に書けばいいんだけど、指定方法が 3 通りもあって複雑だったのでまとめておく。

Jekyll 0.12.0 を前提に書いてるけど、将来的に大きな変更が入るとは思いにくい。

(1) 単数形を使う

単数形 (categorytag) で指定したときは 1 つだけしか指定できない。

---
category: Foo
tag: Bar

上の例では、Foo というカテゴリー、Bar というタグを設定したことになる。

1 つしか指定できないので、

---
category: Foo Bar
tag: Bar, Baz

のようにカンマやスペースで区切ったとしても、Foo Bar という名前のカテゴリー、Bar, Baz という名前のタグが指定されたものと解釈される。

(2) 複数形に文字列を指定する

複数形 (categoriestags) に文字列を与えると、スペース区切りで複数指定できる。

---
categories: Foo Bar
tags: Bar Baz

この例では、カテゴリーは FooBar、タグは BarBaz の 2 つずつ指定されたものとみなされる。

つまり、この書き方ではスペースを含むカテゴリー名やタグ名は指定できない。スペースを含めたいなら、次で紹介する「複数形+配列」で指定する方法を使うのがよい。前述の「単数形」の指定を使ってもよいけど、個数を増やしたときに無駄にハマりそうなので、複数形で統一したほうが分かりやすいと思う。

(3) 複数形に配列を指定する

複数形 (categories・`tags) に文字列の配列を渡すこともできる。

---
categories:
- Foo Bar
- AA,BBB
tags:
- Bar
- Baz, AAA

上の指定では、カテゴリーは Foo BarAA,BBB、タグは Bar BazAAA の 2 つずつが指定されたものと解釈される。名前にスペースやカンマを含めることが可能。

次のようにも書けるけど、この場合は YAML の制限でカンマを含む名前は指定できない。

---
categories: [Foo Bar, AABBB]
tags: [Bar Baz, AAA]

該当箇所のソースを読んでみる

ソースを読んだほうが早いかもしれない。

lib/jekyll/core_ext.rbHash#pluralized_array を見ればよい。

この関数が self.data.pluralized_array("tag", "tags") のように呼ばれる。

class Hash

  # Read array from the supplied hash favouring the singular key
  # and then the plural key, and handling any nil entries.
  #   +hash+ the hash to read from
  #   +singular_key+ the singular key
  #   +plural_key+ the singular key
  #
  # Returns an array
  def pluralized_array(singular_key, plural_key)
    hash = self
    if hash.has_key?(singular_key)
      array = [hash[singular_key]] if hash[singular_key]
    elsif hash.has_key?(plural_key)
      case hash[plural_key]
      when String
        array = hash[plural_key].split
      when Array
        array = hash[plural_key].compact
      end
    end
    array || []
  end

singular が単数形、plural が複数形。

ビルトインの Hash にメソッドを追加するあたりがなんともアグレッシブ。

まとめ

親切な計らいのおかげで、かえって悩むことが増えそうにも思える不思議な仕様・・・。

Google Chrome で超手軽にスマホ向けデザインを確認する方法

$
0
0

最近、Google Chrome のデベロッパー ツールにスマートフォンでの表示を確認する機能があることを知りました。

いままでは、レスポンシブデザイン Web デザインをするときに、ちまちまとブラウザーのサイズを変えたり、Web サービス (Responsive Design Testing とか Responsive Web Design Test Tool とか) を使っていたのですが、こちらの手順のほうがお手軽なので紹介します。

設定は超簡単!!

Google Chrome のデベロッパー ツールを開いて、右下の歯車のアイコンをクリックします。

左側から [Overrides] を選んで、[User Agent] と [Device metrics] にチェックを入れます。

これだけです!

ためしに Yahoo! を表示してみよう

[User Agent] で [iPhone -- iOS 4] を選んでから Yahoo! のトップページを開いてみると・・・。

m.yahoo.co.jp にリダイレクトされて、iPhone 4 のサイズで表示されました。

解像度指定の右にあるボタンを押すと「縦横切り替え」ができます。リロード不要です!

横方向になると、画面の構成が少し変わりましたね。

今度は [iPad -- iOS 5] にしてみます。リロードしてみると・・・。

はい、PC 版にリダイレクトされて、iPad のサイズで表示できました。

User Agent も切り替えてくれるので、レスポンシブ Web デザインに対応したサイトだけでなく、User Agent で表示を切り替えているサイトも確認できますね。

登録されているスマートフォン一覧

Google Chrome 26 に登録されているスマートフォンは次のもの。

  • iPhone -- iOS 5
  • iPhone -- iOS 4
  • iPad -- iOS 5
  • iPad -- iOS 4
  • Android 2.3 -- Nexus S
  • Android 4.0.2 -- Galaxy Nexus
  • BlackBerry -- PlayBook 2.1
  • BlackBerry -- 9900
  • BlackBerry -- BB10
  • MeeGo -- Nokia N9

試したい機種が登録されていなくても心配後無用。User Agent や解像度は手入力も可能です。

気になるところ

注意しなきゃいけないのは、スマートフォンの表示を完全にエミュレートできるわけではない、ということです。PC 版ブラウザーでUserAgent と表示サイズを変更しているだけだと割り切りましょう。

まず、viewport の設定が無視されます。そのため、viewport の設定によっては実際のスマートフォンでの表示と異なります。

たとえば、Yahoo! のモバイル版では viewport に device-width が設定してあるので、iPhone 4 では横幅 320px 相当で描画されるべきです。しかし、実際には 640px 相当で描画してしまってます。実際の iPhone の表示に近づけるためには、サイズを 320px に変更しなきゃいけません。

他にも、[Fit in window] をチェックすると、ブラウザーの領域内に収まるように表示してくれて便利なのですが、iPad の設定で Yahoo! のトップページを開くと横スクロールバーが表示されて変でした。[Fit in window] のチェックを外すと表示されないので、ズーム関係の処理と CSS の何かの指定がバッティングしてるのかもしれません。

ということで、最終確認は必ず実機でやるべきです。それでも、この機能を活用すれば、開発効率はかなり改善するはずです!

まとめ

Google Chrome に統合されているので、とてもお手軽に試せることが分かりました。レスポンシブ Web デザインしている場合も、サーバー側で User Agent みている場合でも、どちらでも活用できるのが便利です。大手サイトのスマホ デザインを確認するのも手軽にできるのが嬉しいですね。

ちなみに、User Agent を変更する機能は 2012 年 2 月の Chrome 17 から提供、サイズを変更する機能は 2012 年 10 月から提供されているようです。

git commit --amend を省力化する方法

$
0
0

Git で最後のコミットを修正するときには git commit --amend を使うんだけども、いままでは

  1. git add .
  2. git commit --amend
  3. エディターが立ち上がって、前回のコミット メッセージが表示される
  4. エディターを終了させる

としていた。

この作業は何度も繰り返すと面倒だったので、man を調べてみると --no-edit なるステキなオプションを発見した。

--no-edit を使う

--no-edit を指定すると、上の手順はこうなる。

  1. git add .
  2. git commit --amend --no-edit

コミット メッセージはそのままに、コミットの中身だけを書き換えられる。エディターが立ち上がらないので楽チン。

-a でさらに省力化

さらに git add . も省力化できて

git commit -a --amend --no-edit

とすればよい。

コマンド一発になった。超楽チン。

注意点は次の 2 つ。

  • 新しいファイルを追加したときは明示的に add する必要がある。git add .git c ommit -a ではステージするファイルが違うので注意。
  • コミットしたくない変更がワーキング ディレクトリーに残ってる状態では使えない。git stash するなどでよけておくべし。

Bugzilla に登録してあるバグをプログラムから更新する方法

$
0
0

会社で BTS として Bugzilla を使っているんだけど、修正したあとに手作業で Web インターフェースから書き込むのが面倒になってきたので、自動化してみた。

コミットしたとき (Git の場合は push したとき) に、コミットメッセージからバグ番号を読み取って、対応するバグにメッセージを書き込みつつ、FIXED にすればよい。

情報がなくて困ったのが、バグにコメントを書いたり、FIXED にする方法。この部分の処理を抜き出してみた。

#!/usr/bin/perl -I/path/to/bugzilla -I/path/to/bugzilla/lib

use strict;
use Bugzilla;
use Bugzilla::User;
use Bugzilla::Status;
use Bugzilla::Bug;
use utf8;

&update_bug(1, "ほげほげ");

# API document: http://www.bugzilla.org/docs/4.2/en/html/api/
sub update_bug {
    my ($bug_id, $text) = @_;

    # open bug
    my $bug = Bugzilla::Bug->new($bug_id);
    die $bug->error if defined $bug->error;

    # get user
    my $user = Bugzilla::User->new({name => 'admin@example.com'});
    die 'user not found!!!!' unless defined $user;

    # login
    Bugzilla->set_user($user);

    # comment to the bug
    $bug->add_comment($text);

    # FIXED
    $bug->set_bug_status(Bugzilla::Status->new({name => 'RESOLVED'}),
                         {resolution => 'FIXED'});

    # save to database
    $bug->update();
}

Bugzilla 4.2.5 で動作を確認している。

あとは、Git なら post-receive フックで、コミットログを解析して、この関数を呼ぶようにすればよい。

注意点:

  • Bugzilla がインストールされているサーバー上で動くことを前提としている。同一サーバーならパスワードなしで、特定のユーザーに su できるようだ。
  • Bugzilla::Bug はドキュメント化されていないので、バージョンアップしたら使えなくなる可能性がある。同じような機能の Bugzilla::WebService::Bug はドキュメントがあるんだけど、使おうとしたら Test/Taint.pm が必要だとか言われて面倒になったのでやめた。

ブログのタグ機能が復活したよ

$
0
0

昨年 9 月にはてなダイアリーから引っ越したときにタグ機能が消えてしまっていたのですが、このたび、重い腰をあげて復活させました。

たとえばこの記事の場合、上側の日付の右にタグが 2 つあることにお気づきいただけるでしょうか。タグ名をクリックすると、そのタグがついた記事の一覧が表示できますよ。

ついでに、既存のタグも整理しています。統廃合したり、無意味なものを消したり。整理した結果がこのタグ一覧

トップページの記事一覧にもタグを表示するようにしました。

Jekyll のタグ機能をカスタマイズ

以前に書いたとおり、このブログの出力には Jekyll を使っています。Jekyll 本体には、記事にタグを設定する機構は用意されているのですが、タグの記事一覧を出力する機能がなくて、自前で実装する必要があります。

そこで、blackbulletiv.github.com さんの tags_categories.rb を参考にしつつ、「日本語タグだけど URL は英語」「複数ブログ対応」といったあたりを実現するために、いろいろと手をいれてカスタマイズしています。

詳しくは、

あたりを見れば雰囲気が分かると思います。

この後の野望

このあとやってみたいことは・・・

  • サイト内検索:
    あると便利なのは間違いないし、検索向け AdSense というのがあるらしい。
  • サムネール:
    各記事にサムネールを設定して、それを利用したデザインになると嬉しい。
  • Web フォント:
    フラットなデザインが流行しつつあるので、Web フォントを使ったアイコンの利用率も増えてきそう。
  • 2 カラム化:
    スクロールすると固定するカラムには興味ある。せっかく 1 カラム化したのだけど・・・。
  • Jekyll 1.0 対応:
    いつのまにか正式版が出ていたらしい。何が便利になったのか、まだ追えていない。

そんなことより、もっと頻繁に記事を書けよ、という感じではありますが・・・。

Jekyll で --watch の代わりに Grunt を使ってみるテスト

$
0
0

このブログでは Jekyll を使ってることは何度か書いたのだけど、いままで記事を書くときには jekyll --auto を実行した状態で書いていた。このようにしておくと、ファイルを書き換えたら自動的にサイトをビルドしてくれるようになる。ただ、このコマンドを実行してると CPU がグオーンと音を上げ始め、クアッドコアで CPU 使用率 25% に達するという地球に優しくない状態であった。

原因を調べてみると directory_watcher モジュールが犯人だった。このモジュールは、監視対象のディレクトリー配下の全ファイルに対して、毎秒、File::Stat() を実行する、という富豪的実装になっている。もちろん、ファイルの数が少ないときには問題なく動くんだけども、ファイルの数が増えると CPU を浪費してしまう。

たとえば、このサイトの場合、600 個以上の記事があって、Jekyll が 900 個のファイルを生成する。しかも、.git フォルダーの下には 5,000 個程度のファイルがある。ひどいことに、Jekyll 0.12 までのバージョンは、これらのファイルすべてを監視対象にする。毎秒 6,500 個のファイルを stat するわけだから、当然、CPU は振り切る。

Jekyll 1.0 になって、コマンドも jekyll build --watch に変わって、_site (サイトの出力先) や .git が監視対象から外れたので、パフォーマンスはだいぶ改善した。それでも自分の環境で常時 10% ぐらい CPU を消費し続けている。

ということで、--watch (もしくは --auto) オプションの代わりに、流行の Grunt を使ってみることにした。

Grunt の設定ファイル

使っているバージョンやプラグインは次の通り。

  • Jekyll 1.0.3
  • Grunt 0.4.1
    • grunt-shell-spawn プラグイン
    • grunt-contrib-watch プラグイン

最初は、grunt-shell-spawn ではなく grunt-jekyll を使ってたんだけど、grunt-jekyll は Jekyll を実行中の途中経過を表示してくれないので使うのをやめた。

Grunt の使い方については Getting started - Grunt を見たほうが早いだろうから、ここでは package.jsonGruntfile.js を紹介する。

package.json

{
  "name": "tech-ni",
  "version": "0.1.0",
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-shell-spawn": "~0.2.4",
    "grunt-contrib-watch": "~0.4.4"
  }
}

Gruntfile.js

module.exports = function(grunt) {
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    shell: {
      jekyll: {
        command: 'jekyll build',
        options: {
          async: false
        }
      }
    },
    watch: {
      jekyll: {
        files: ['_posts/**/*.md', '_layout/*.html', '_includes/*.html'],
        tasks: ['shell:jekyll']
      }
    }
  });

  grunt.loadNpmTasks('grunt-shell-spawn');
  grunt.loadNpmTasks('grunt-contrib-watch');

  grunt.registerTask('default', ['shell:jekyll']);
};

(Jekyll 0.12 までを使用している場合は、jekyll buildjekyll で置き換えるべし)

使い方

grunt watch を実行すると、ファイルの監視を始める。記事や HTML が編集されたら、jekyll build が実行されるようになっている。

しかし、Jekyll で監視するよりも CPU 消費量は小さいが・・・0 ではない・・・!? と、ここで調べてみて気づいたんだけども、Grunt の watch は、ネイティブ API の fs.watch() を使ってファイルを監視していない!!! gaze モジュールを使って定期的にファイルを stat しているだけだった。

これでは Ruby の directory_watcher と同じである。

どうやら、Node.js の fs.watch() は Mac OS でファイル名を取れなかった過去があるなど、歴史的に不安定であり、結局、stat で独自に監視する、という手順が一般的になったようだ。なんという罠。

Node.js 0.12 では yield が使えるのでコールバック地獄にサヨナラできる話

$
0
0

Node.js の次のメジャーバージョン 0.12 で yield が使えるようになります。

そのおかげで、JavaScript のコールバック地獄に光が差し込むのです。ああ、さようなら、コールバック地獄。

7 年ごしで実現した yield

2006 年、Firefox 2 のリリースと同時に yield は JavaScript 界に登場しました。随分と前の話ですね。

登場した当時は JavaScript 界隈でけっこう話題になっていました。

登場したときにはインパクト大きかったものの、結局 Firefox でしか使えない yield さんは忘れ去られていたわけです。

それがここにきて、ECMAScript 6 に yield が入ることが決定して、V8 に実装されました。となれば、V8 を使ってる Node.js でも自動的に利用できるようになった、という流れであります。

7 年の月日を経て、日の目を見たわけで胸が熱くなります。何はともあれ、Node.js の次のバージョンに yield がやってくるのであります。ヤァヤァヤァ。

さようなら! コールバック地獄

ということで、Node.js の話です。

脱出する前に、皆さんに地獄を見てもらいましょう。

これが地獄絵図だ!

サンプルとして、簡単なスクリプトを書いてみました。

var fs = require('fs');

// カレント ディレクトリーのファイル一覧を取得する
fs.readdir('.', function(err, files) {
    // 先頭のファイルの中身を読み取る
    fs.readFile(files[0], 'utf-8', function(err, data) {
        // 読み取った結果を出力する
        console.log(data);
    });
});

ああ、美しきコールバック地獄。たった 3 つの処理をするだけなのに、コールバックが 2 つもあるわけです。

といっても、Node.js には fs.readdirSync()fs.readFileSync() といった同期処理をする関数も用意されておりますが、コールバック地獄を再現するために、あえて非同期版を使っております。

Deferred にすがりつく

こういうコールバック地獄から逃げるための今までの定石は Deferred だったわけです。jQuery にも実装されてるアレです。

Node.js で Deferred といえば Q が有名らしいので、Q を使って書き直してみました。

var fs = require('fs');
var Q = require('q');

Q.nfcall(fs.readdir, '.')
.then(function(files) {
    return Q.nfcall(fs.readFile, files[0], 'utf-8');
})
.then(function(data) {
    console.log(data);
})
.done();

確かにコールバックの階層は押さえられたのですが、かえって読みにくくなったようにも感じます。Deferred をマスターしてしまえば極楽なのかもしれませんが、Deferred は学習コストがそこそこ高いと思うわけです。

また、コードもいまいちすっきりしません。then() でコールバックを繋げるために deferred を返しているのが、何とも読みにくい構造になっています。

Deferred にすがりつくと、一見、幸せになりそうなんだけど、数ヶ月後にソースを読んだときに、ただのコールバック地獄よりも一層深い奈落の底に叩き落される恐れすらあるわけです。

yield の恩恵を体験する

で、yield です。

生で yield を扱うのは面倒なので、ライブラリーの力を借りましょう。

Node.js 界で数々の著名モジュールを作ってる TJ Holowaychuk (visionmedia) さんが、さっそく yield を活用するための co というモジュールを作ってるので使わせてもらいます。

こうなるんだぜ。

var co = require('co');
var fs = require('fs');

co(function *() {
  var files = yield co.wrap(fs.readdir)('.');
  var data = yield co.wrap(fs.readFile)(files[0], 'utf-8');
  console.log(data);
});

同期処理っぽく書いていますが、実はコールバック地獄版と同じ処理になっています。

それぞれの処理で失敗したときには例外が飛ぶので、エラー処理もばっちりです。

ああ、幸せですね。夢が広がりんぐです。いままで面倒だった非同期処理がとっても気楽に書けるのであります。C# の await みたいなことができます。

あ、いちおう細かな点について触れときます。

  • 上のソースを実行するには、Node.js の Nightlies builds から v0.11 のバイナリーを拾ってきて、node --harmony-generators foo.js として実行する必要がある。
  • co の最新のソース (co@5bd0169) は 48 行目の gen.send(res); でエラーになるので、gen.next(res); に書き換える必要がある。

yield について簡単に説明するよ

いちおう yield が何か、という話を簡単に触れておきます。詳しくは harmony:generators [ES Wiki] を見てください (ちょっと情報が古いようですが…)。

シンプルな例を書いてみました。

function* N() {
    console.log("start");
    yield 1;
    console.log("after 1");
    yield 2;
    console.log("after 2");
    return 3;
}

まず、yield を使う関数は function ではなく function* で宣言します。(Firefox の先行実装と少し違います)

で、この関数を呼んでみます。

var g = N();
console.log(g);
// {}

function* な関数を呼んでも、関数の処理は始まりません。変わりに関数の処理を開始させるためのジェネレーターが返ってきます。

では、ジェネレーターを使ってみましょう。ジェネレーターの next() を呼ぶと、関数の処理を開始できます。

console.log(g.next());
// start
// { value: 1, done: false }

yieldreturn みたいなもので戻り値を返します。ここでは、戻り値の 1value として返ってきています。

yieldreturn の違い、それは、yield は続きから処理を再開できるところにあります。そう、g.next() を呼べばね。

console.log(g.next());
// after 1
// { value: 2, done: false }

after 1 から処理が再開して、2 を返して、再び、関数は中断しました。もう一度 g.next() を呼んでみます。

console.log(g.next());
// after 2
// { value: 3, done: true }

return で関数が終わったので、donetrue になりました。(この戻り値も Firefox の先行実装と異なります)

こんな感じで、yield を使えば、関数の処理を途中で中断しておくことができます。で、上の co を使ったサンプルを見たら・・・なんとなく実現できそうな気がしてきましたか?

まとめ

  • ECMAScript 6 に yield が入った → V8 に実装 → Node.js 0.12 で使える
  • yield を使えばコールバック地獄から脱出できる。
  • この記事では co を紹介したけど、便利ライブラリーはまだまだ登場しそう。

git difftool --dir-diff が便利すぎて泣きそうです

$
0
0

Git の 1.7.11 から git difftool コマンドに --dir-diff というオプションが追加されたのですが、これがライフ チェンジングだと思ったので紹介します。

--dir-diff 登場以前の git difftool は「ファイルごとに順番に差分を表示していく」ことしかできず、使い勝手はいまいちでした。それが、--dir-diff オプションの登場で状況が一変したわけです。

こんな感じの使い心地だよ

ある Git レポジトリーで dir1/a.txtdir2/c.txt を編集したとしましょう。

この状態で git difftool --dir-diff または git difftool -d を実行してみると・・・。

はい、差分のあるファイルが一覧で表示されました。

(difftool に WinMerge を設定して、メニューから [ツリー表示] を有効にしたときの表示例です。設定方法は後述します)

個別のファイルの diff を見る

a.txt を選択すると、新しいタブが開いて、a.txt の差分が表示されます。

カラフルに色分けされているし、ショートカットキーで前後の差分に移動できるので便利です (WinMerge の場合は Alt + ↑ と Alt + ↓)。

当然、タブを移動して差分一覧に戻れば、c.txt の差分も表示できます。

コマンドラインの git diff に比べて、

  • 確認するファイルを選びやすい
  • 差分間の移動がキーボードでやりやすい
  • ファイル全体が表示されているので、差分から前後に好きなだけたどっていける

といったところが嬉しいですね。

おっと、ここまでなら、GUI な Git クライアントでも同じようなことはできますね。便利なのはこの次です。

ファイル編集も!

なんと、右側のファイルを編集して保存すると、ワーキング ディレクトリーに反映されるのです (ただし、右側のファイルがワーキング ディレクトリーのファイルと同じ内容のときのみ)。

これがとてつもなく便利です。

差分を見ながら、「この差分は不要」とか「typo 発見」とか気づいたときに、その場で修正ができちゃうわけです。

比較対象は無限大

git difftool で比較対象を指定する方法は git diff とまったく同じです。

たとえば、git difftool -d master...topic として、トピックブランチでの変更点をまとめて閲覧したりもできるわけです。git difftool -d --cached としてインデックスとの差分を確認できるわけです。任意のコミット間の差分も確認できるわけです。タグがうってあれば、特定のバージョン間の差分も確認できるわけです

うれしいですね。

裏側で起こっていること

git difftool -d を実行したときに、裏側で何が起こっているのでしょうか。

内部的に git diff を呼び出して、出てきたファイルを一時ディレクトリーにチェックアウトしています。

たとえば、先ほどの例でいうと

Temp/git-difftool.VoxJJ/
    left/
        dir1/
            a.txt
        dir2/
            c.txt
    right/
        dir1/
            a.txt
        dir2/
            c.txt

といった構造になります。中身が同一なファイルはチェックアウトされないので、大きなレポジトリーでも安心です。

さらに、Mac や Linux では、right のファイルがワーキング ディレクトリーと同じなら、「ワーキング ディレクトリーへのシンボリックリンク」になっています。その結果、right のファイルを書き換えると、即時にワーキング ディレクトリーに反映されるわけです。

Windows の場合は、difftool を終了したときに、ワーキング ディレクトリーに一時ファイルを書き戻す動作になっています。ちょっと不便なので、シンボリックリンクを使うように改造したいところです。(追記) 改造しました!! Windows でも git difftool --dir-diff でシンボリックリンクを使う方法

使えるようにするまでの準備

便利なのは分かってもらえたと思うので、使えるように準備してみましょう!

最新の git-difftool を取得しておく

先ほど書いた通り、git difftool --dir-diff は 1.7.11 から追加されています。ただ、最近になっていくつか大きなバグが修正されているので、なるべく新しいヤツ (Linux なら 1.8.3、Windows なら 1.8.3.2 ) を利用するのがオススメです。

Git 全体を更新するのが面倒な場合は、git-difftool だけを

から落としてきて、libexec/git-core/git-difftool に上書きすれば、よほど古いバージョンじゃなければ動く・・・と思います。

difftool の設定: WinMerge 篇

Windows で WinMerge を使う場合は、.gitconfig に次のように書きます (Git for Windows の場合)。

[diff]
    tool = winmerge
[difftool winmerge]
    path = C:/Program Files (x86)/WinMerge/winmergeu.exe
    cmd = \"C:/Program Files (x86)/WinMerge/winmergeu.exe\" -r -u \"$LOCAL\" \"$REMOTE\"

インストール先が異なる場合は適宜修正してください。

コマンドライン オプションの意味は次の通りです。

  • -r: 再帰的に比較する
  • -u: 最近開いた一覧に追加しない

人によっては -e を追加して ESC で WinMerge を閉じられるようにしているようですが、複数タブを開いてるときに ESC を押して WinMerge が閉じてしまうと困るので私は設定していません。

difftool の設定: meld 篇

Mac や Linux で meld を利用する場合は、.gitconfig に次のように書きます。

[diff]
    tool = meld

簡単ですね。

Git 本体に meld のコマンドラインオプションの情報が入ってるので、これで動くようです。

(手元に Mac がないので動作確認はできませんでしたが、git - View differences of branches with meld? - Stack Overflow に同じ手順が書いてあって、プラス評価がついてるので正しいはずです)

Pro Git には P4Merge を使う手順 が書いてあるんだけど、P4Merge はディレクトリーの比較に対応してないので、--dir-diff には活用できません。

エイリアスを設定する

このあたりは好みですが、.gitconfig でエイリアスを設定しておくと便利でしょう。

[alias]
    d = difftool -d
    dc = difftool -d --cached
    dp = difftool -d HEAD~

git d <branch> とか git d HEAD のように入力するだけで、difftool が --dir-diff で立ち上がるようになります。

まとめ

difftool --dir-diff で快適な Git 生活を!

Windows でも git difftool --dir-diff でシンボリックリンクを使う方法

$
0
0

git difftool --dir-diff が便利だよ、という話を git difftool --dir-diff が便利すぎて泣きそうです で書きましたが、1つ宿題が残っていました。Windows では一時ファイルがワーキング ディレクトリーへのシンボリックリンクにならないので、Unix や Mac に比べて、少しだけ不便だよ、という話です。

そこで、Windows でもシンボリックリンクを使えるようにしちゃおう、というのがこのエントリーの趣旨でございます。

Windows 向けの応急処置パッチ

Git for Windows 1.8.3 で動作確認しています。OS は Windows 7 (64 ビット)。

C:\Program Files (x86)\Git\libexec\git-core\git-difftool のパッチがこちら。

--- git-difftool    Sun Jun  2 11:28:06 2013
+++ git-difftool    Tue Jul  9 00:42:02 2013
@@ -283,7 +283,7 @@
            exit_cleanup($tmpdir, 1);
        }
        if ($symlinks) {
-           symlink("$workdir/$file", "$rdir/$file") or
+           !system("git", "mklink", "$workdir/$file", "$rdir/$file") or
            exit_cleanup($tmpdir, 1);
        } else {
            copy("$workdir/$file", "$rdir/$file") or
@@ -448,7 +448,7 @@
    my $indices_loaded = 0;

    for my $file (@worktree) {
-       next if $symlinks && -l "$b/$file";
+       next if $symlinks;
        next if ! -f "$b/$file";

        if (!$indices_loaded) {

適当な場所に保存して、GitBash を管理者権限で起動して適用してやります。

$ cd /c/Program\ Files\ \(x86\)/Git/libexec/git-core/
$ patch < ~/git-difftool.patch
patching file `git-difftool'

さらに、C:\Program Files (x86)\Git\libexec\git-core\git-mklink を作ります。

#!/bin/sh

cmd.exe /c "mklink \"$2\" \"$1\"" > /dev/null

(このスクリプトは /tmp/ といった msys 内のパスを Windows のパスに変換するために必要)

使い方

最初に、.gitconfig に difftool の設定をしておきます。WinMerge を利用するには次のようにしておきます。

[diff]
    tool = winmerge
[difftool winmerge]
    path = C:/Program Files (x86)/WinMerge/winmergeu.exe
    cmd = \"C:/Program Files (x86)/WinMerge/winmergeu.exe\" -r -u \"$LOCAL\" \"$REMOTE\"

GitBash を管理者権限で起動して、次のように実行します (Windows ではシンボリックリンクを作成するには管理者権限が必要)。

$ git difftool -d --symlinks [<commit> [<commit>]]

ついでに .gitconfig にエイリアスを定義しておくと便利でしょう。

[alias]
    d = difftool -d --symlinks

どうぞご利用ください。

Git にパッチを送って取り込まれた話

$
0
0

Git の挙動に変なところを見つけたので、パッチを作って Git のメーリングリストに投げてみたところ、何度かのレビューを経て、無事に取り込まれた

Git に貢献したい人とか、オープンソース開発の流れに興味がある人もいるだろうから、作業の流れを書いておくことにする。

1. バグを発見する

何はともあれ、修正したいところを見つけるところから。

先日、git difftool --dir-diff が便利すぎて泣きそうです という記事を書いたが、difftool --dir-diff の挙動を調べているうちに、一時ファイル書き戻し条件が変なことに気づいた。

手元のバージョンが古いのかとも思ったが、master ブランチでも再現したので、ちょっくら深入りしてみた。git difftool は Perl スクリプトだったので、ソースコードに print を追加しつつ挙動を探っていった。しばらく調べていると、コードの流れも分かってきて、--no-symlinks が指定されたとき (Windows ではこれがデフォルト) にのみ動作がおかしいことに気づいた。

マニアックなオプションで、しかも、Windows でしか発生しないバグ・・・ということで発見されてなかったのだろう。ちょっと修正したら期待通りの結果になったので、これはパッチを送ってみるチャンスである、と思い立った。

パッチを作るための手順は Documentation/SubmittingPatches に記載されている。この手順に従って、パッチを送ってみることにした。

2. テストコードを修正する

Documentation/SubmittingPatches を読んでいると、テストコードも一緒に修正しろ、とある。

Windows 上でテスト環境を作ろうとしたが、修羅の道っぽかったのですぐに諦めた。次に、レンタルサーバーを借りている sakura の FreeBSD 上で試そうとしたが、これも罠にハマって解決能力がないので諦めた。

結局、素直に仮想端末に CentOS を突っ込んだ。yum でいろいろインストールしたら、あっさりテストまで実行できる環境ができあがった。

テストはシェルスクリプトで記述されている。シェルスクリプトは不慣れなのだが、しばらく読んでるうちに作法が分かってきたので、真似をして修正した。

3. コミットログを書く

これが一番難しかった。

Documentation/SubmittingPatches を読むと、「修正前は何がおかしかったのか」「修正によって正しくなった理由」を説明することを求められている。もちろん英語で。

実際に、Git プロジェクトのコミットログを読んでみると、どれもコミットログが長くて、しっかりと修正の内容が説明されている。

しかし、自分には英語力がないので、言いたいことをうまく伝えられない。

第一稿では、同一ファイルへの過去のコミットログを切り貼りしつつ、なんとかそれっぽく書き上げたつもりになった (これが後々、「分かりにくいぞ」とツッコミを受けてしまうのだが・・・)。

あとは、コミットログの末尾に Signed-off-by: として自分の名前を書かなきゃいけない。「本名を書け」とあるので、名前を出したくない人にとっては厳しい関門かもしれない。

4. メーリングリストにパッチを投げる

Git の議論やパッチのレビューはすべてメーリングリスト上で行われている。ということで、次にすべきはパッチを Git ML に投げることである。間違っても、GitHub 上で pull リクエストを出してはいけない。

メールを送付する手順も Documentation/SubmittingPatches に書いてある。

まずは、git format-patch でパッチを作成する。作成したパッチはメール形式になっているので、これをそのままメールとして送る必要がある。

自分は GMail を使いたかったので、git-format-patch(1) の MUA-SPECIFIC HINTS に従って、git send-email コマンドを使って送信した。事前に .gitconfig[sendemail] に色々と設定を書くのだけど、最初、[sendmail] に設定を書いてしまってうまくいかずにしばらく悩んだのは、今となってはいい思い出。

5. レビュー結果に従う

しばらく待っているとメーリングリストに返事が届いた。

今回の修正では、コードについては文句が出なかったが、コミットログが分かりにくい、という注文がついた。

1 回目は John さんから、2 回目はメンテナの濱野さんから。

結局、2 回、コミットログのみを書き換えてパッチを再送信した。

三度目の正直、v3 で OK をもらえた。

6. ブランチを昇格していく

OK をもらったコミットは pu (Proposed Update) ブランチに取り込まれる。

しばらく経って異論が出なければ next ブランチにマージされて、最後に master ブランチにマージされる。

最終的に自分のコミット が v1.8.3.2 で取り込まれてリリースされた。リリースノートの末尾にも自分のパッチの情報が書いてあった。

 * "difftool --dir-diff" did not copy back changes made by the
   end-user in the diff tool backend to the working tree in some
   cases.

めでたい。

まとめ

以上でパッチを送ってから取り込まれるまでの流れを振り返った。

印象的だったのは、コミットログをしっかり書かせる文化である、ということ。今回の自分のコミットは「ソースコードの行数 < コミットログの行数」だった。ソースコード上のコメントは少なくても、コミットログにしっかりと歴史や意図が刻まれている。

あとは、英語をなんとかしたい・・・。

LiveReloadX を Grunt から使えるようにした

$
0
0

LiveReloadX に少し手を入れて Grunt のタスクとして動作させられるようにした。

今回のバージョンは 0.3.0。新規インストールするなら今まで通り npm install -g livereloadx で、バージョンアップするなら npm update -g livereloadx でどうぞ。

設定手順は LiveReloadX の「Run as a Grunt Task」あたりを参照してもらうとして、この記事では実装した背景を説明しておこう。

Grunt に対応させる価値

Grunt は、サイトの生成にあたっての必要な処理をひとまとめにできるのでとても便利である。そこに live reload する機能まで含んでいればさらに便利そうである。

といっても、実は、公式の grunt-contrib-watch プラグインに live reload 機能はついている。

じゃあ、公式でよさそうなのだけど、公式を使うためには「HTML に JavaScript のスニペットを埋め込む」か「ブラウザ拡張を導入する」必要がある。grunt-contrib-watch のページに connect-livereload を使う方法も紹介されてるが、そっちは少し煩雑そうだった。

LiveReloadX の一番のウリが static モードと proxy モードで、JavaScript スニペットの埋め込みを Web サーバー側でやってくれる。この機能を使いたかったので、LiveReloadX 自体を Grunt に組み込んでみた。

組み込むにあたっては、Grunt のドキュメントを見たり、各種プラグインのソースを見たりすれば、だいたいの方法は分かったので、比較的簡単に対応できた。

ブログ書くのがさらに手軽になった

個人的には、今回の機能によって、ブログを書くのがとても楽になった。

いままでは、このブログを書くにあたって、jekylllivereloadx を個別に実行していた。

以前、Jekyll で --watch の代わりに Grunt を使ってみるテスト で書いたとおり、JekyllGrunt から使うことには成功していた。今回、LiveReloadX も組み合わせられるようになった。

つまり、grunt と実行するだけで

  • Web サーバーが起動して、Jekyll のビルド結果を確認できる
  • Markdown 記法の記事をテキストエディターから編集したら、jekyll が走って HTML を生成する。
  • 表示しているページの HTML や画像が生成されたら、自動でブラウザーがリロードされる

という環境が整った。

このブログのソースコードは nitoyon/tech.nitoyon.com - GitHub を参照してほしい。

理想のブログ環境に近づいてきた。


D3.js の Data-Driven な DOM 操作がおもしろい

$
0
0

ここんところ D3.js を触ってみているんだけど、これがなかなか面白い。

D3.js は「ビジュアライズ用のライブラリー」だと紹介されがちなんだけども、意外にも D3.js にはグラフを描画する機能がない。

D3.js のトップページには次のように書いてある。

D3.js はデータからドキュメントを生成するためのライブラリーです。D3 は HTML, SVG, CSS を使ってデータに命を吹き込みます。Web 標準を重要視しているので、独占的なフレームワークに縛られません。強力なビジュアライズ用のコンポーネントと data-driven な DOM 操作手順を組み合わすことで、モダン ブラウザーの能力を最大限に活用できます。

D3.js is a JavaScript library for manipulating documents based on data. D3 helps you bring data to life using HTML, SVG and CSS. D3’s emphasis on web standards gives you the full capabilities of modern browsers without tying yourself to a proprietary framework, combining powerful visualization components and a data-driven approach to DOM manipulation.

曰く、

  • ビジュアライズ用のコンポーネント
  • data-driven な DOM 操作手順

がウリらしい。

順番に見ていこう。

ビジュアライズ用のコンポーネントの役割

「ビジュアライズ用のコンポーネント」は「データ」と「チャートの種類」から「どこに描画すればいいか」を計算する機能だけを実装している。

バブルチャートの例

ギャラリーBubble Chart をみてみよう。

このチャートでは d3.layout.pack() という Pack Layout コンポーネントを利用している。

この例では、描画するノードの情報を、こんな形の JavaScript 配列をコンポーネントに渡している。

[
  {
    className: "AgglomerativeCluster",
    packageName: "cluster",
    value: 3938
  },
  {
    className: "CommunityStructure",
    packageName: "cluster",
    value: 3812
  },
  {
    className: "HierarchicalCluster",
    packageName: "cluster",
    value: 6714
  },
  //...
]

これを pack.nodes() 関数に渡すと、value の値を元に計算して、ノードの位置情報をこんな感じで返す。

[
  { /* root node の情報 */ },
  {
    className: "AgglomerativeCluster",
    depth: 1,
    packageName: "cluster"
    parent: { /* root node */ },
    r: 22.159142384561406,
    value: 3938,
    x: 489.9621260527267,
    y: 532.5795625707091
  },
  {
    className: "CommunityStructure",
    depth: 1,
    packageName: "cluster",
    parent: { /* root node */ },
    r: 21.80175917979169,
    value: 3812,
    x: 535.3554715791261,
    y: 532.5795625707091
  },
  {
    className: ""HierarchicalCluster"",
    depth: 1,
    packageName: "cluster",
    parent: { /* root node */ },
    r: 28.933819019424202,
    value: 6714,
    x: 513.070926102582,
    y: 485.410700287188
  },
  // ...
]

いろんなプロパティーが追加されているけど、表示位置 (x, y) と半径 (r) の情報が返ってきている点に注目!

このように、ビジュアライズ用のコンポーネントは「データ」と「チャートの種類」から「どこに描画すればいいか」を計算するだけである。

描画するには...

この情報をどう画面に落とし込むかは実装側に任されている。

と聞くと、面倒そうに思えるかもしれないけど、Bubble Chart のソースを見ると、描画処理はたったの 15 行。

それだけ簡単に描画できてしまうのは、D3.js の「data-driven な DOM 操作手順」を使って SVG を生成しているのが大きい。

Data-Driven な DOM 操作を実感してみよう

D3.js には jQuery ライクな DOM 操作やイベント・アニメーション関係の API が用意されてる。jQuery を使ったことがある人なら簡単にマスターできるだろう。

jQuery にはないユニークな機能が data()enter()exit() 関数である。この関数の使い方を簡単なサンプルでみていこう。

JavaScript 上にこんな配列が定義されているとする。

var shiritori = ['りんご', 'ゴリラ', 'ラッパ'];

HTML には入れ物の <ul> タグだけを用意しておく。

<ul id="shiritori_list">
</ul>

<ul> タグの中に shiritori 配列の中身を表示してみよう、というのがお題である。

DOM の作成処理は data()+enter()

配列の中身を <ul> に追加するには、D3.js では次のように書く。

// ul#shiritori_list を選択
d3.select('ul#shiritori_list')
  // その下の <li> を列挙
  .selectAll('li')
  // それぞれに shiritori 配列の要素を割り当てる
  .data(shiritori)
  // data より <li> が少ない場合は、足りない分について
  .enter()
  // <li> を追加する
  .append('li')
  // <li> の中身の文字を設定する
  .text(function(d, i) { return (i + 1) + '番目は' + d; });

これを実行すると、HTML はこうなる (なりそうですよね? ね?)。

<ul id="shiritori_list">
  <li>1番目はりんご</li>
  <li>2番目はゴリラ</li>
  <li>3番目はラッパ</li>
</ul>

再度同じ JavaScript 処理を走らせるとどうなるだろう。<li> は 6 個になるだろうか。答えは「ならない」だ。

というのも、enter() 以降の処理は「shiritori 配列に対応する <li> がないとき」のみ実行される。「<li> の個数 ≦ shiritori 配列の要素数」である限りは何度実行しても enter() 以降は実行されない。

では、shiritori.push('パイナップル') として配列側を増やしてから、再度、上の JavaScript を走らせてみるとどうなるだろう。

そうすると、新規追加分の 'パイナップル' に対してだけ、enter() 以降の処理が実行される。つまり、HTML は

<ul id="shiritori_list">
  <li>1番目はりんご</li>
  <li>2番目はゴリラ</li>
  <li>3番目はラッパ</li>
  <li>4番目はパイナップル</li>
</ul>

となる。

enter() は配列の増加分に対して、DOM を生成してくれるわけだ。

DOM の削除処理は data()+exit()

次は、shiritori 配列が HTML より小さくなったときに対応しよう。

exit() で削除方法を書く。

// ul#shiritori_list を選択
d3.select('ul#shiritori_list')
  .select('ul#shiritori_list')
  // その下の <li> を列挙
  .selectAll('li')
  // それぞれに shiritori 配列の要素を割り当てる
  .data(shiritori)
  // data の数よりも多い <li> については
  .exit()
  // 削除する
  .remove();

はい。これで終わり。

shiritori が減ってないときは何も起こらないし、shiritori.pop(); してから実行すると末尾の <li> は消える。shiritori = []; してから実行すると <li> は消えてなくなる。

DOM の更新処理は data()

最後に更新処理。

// ul#shiritori_list を選択
d3.select('ul#shiritori_list')
  // その下の <li> を列挙
  .selectAll('li')
  // それぞれに shiritori 配列の要素を割り当てる
  .data(shiritori)
  // <li> の中身の文字を設定する
  .text(function(d, i) { return (i + 1) + '番目は' + d; });

enter()exit() なしにすれば、更新時の処理になる。

データが与えられたときに、それぞれの要素をどのように表示すべきかを記述している。配列を書き換えて、この処理を実行すると、中身の文字が適切に更新される。

ここでは text() しか使ってないが、jQuery 的な attr()style() を活用すれば、柔軟な指定が可能である。

全部まとめる

生成・削除・更新の処理をまとめてみる。

function update_shiritori() {
  var s = d3.select('ul#shiritori_list')
    .selectAll('li')
    .data(shiritori);

  // 作成
  s.enter().append('li');

  // 削除
  s.exit().remove();

  // 更新
  s.text(function(d, i) { return (i + 1) + '番目は' + d; });
}

(「作成」のところで text() を実行しなくなっているが、そのあとの「更新」のところでまとめて設定できるのでご安心を)

完成したソースを改めて見てみると、

  • 作成: 増えたデータに対して DOM を追加
  • 削除: 減ったデータに対して DOM を削除
  • 更新: データに対応する表示に更新

という処理がシンプルに書けているのが嬉しい。

同じような書き方を jQuery でやるのは難しい。たぶん「毎回 DOM ツリーを作り直す」という富豪的な手順をとると思う。それだと効率が悪いし、次の例にあるような増減に関係したアニメーションを実現するのは困難である。

Data-Driven なアニメーション

今度は、少し色気を加えて、アニメーションさせる。

使い方

  • 初期状態では 10 個の要素を持った配列を表示している。
  • 横軸が配列のインデックス、縦軸が要素の値 (0~1) をあらわす。
  • [random] ボタンを押すと、配列の中身がランダムな値で置き換わる。
  • [push] ボタンを押すと、配列の末尾に要素を追加する。
  • [pop] ボタンを押すと、配列の末尾から要素を取り除く。

ボタンを押すと、アニメーションつきで見た目が変更するのを確認していただけるだろうか (SVG をサポートしてる必要があるので、モダンではないブラウザーでは表示できない)。

コンソールからの変更にも対応

このページを開いている状態であれば、JavaScript コンソールで直接

values = [0.5, 0, 1];
update();

と入力しても、アニメーションつきで配列が反映されるはずだ。

更新部分のソースコード

では、その update() 関数をみてみよう。

function update() {
  // 配列の個数を n に代入
  var n = values.length;

  // <svg> の中の <circle> を列挙して、values を割り当てる
  var circles = d3.select("svg#sample2")
    .selectAll('circle').data(values);

  // 作成: 足りない <circle> を追加する
  circles.enter()
    .append('circle')
    .attr('fill', 'red')
    .attr('cx', function(d, i) { return i * 280 / n + 10; })
    .attr('cy', 0).attr('r', 0);

  // 削除: 余分な <circle> はアニメーションつきで削除
  circles.exit()
    .transition()
    .duration(300)
    .attr('cy', 0).attr('r', 0)
    .remove();

  // 更新: アニメーションで正しい位置とサイズに移動
  circles
    .transition()
    .duration(300)
    .attr('cx', function(d, i) { return i * 280 / n + 10; })
    .attr('cy', function(d, i) { return d * 280 + 10; })
    .attr('r', 6);

  // 線の位置も調整する
  d3.select('svg#sample2 polyline')
    .transition()
    .duration(300)
    .attr('points', values.map(function(d, i) {
      return (i * 280 / n + 10) + ' ' + (d * 280 + 10);
    }).join(','));
}

画面の描画は SVG で行っている。

削除と更新のときに transition() でアニメーションを指定してる点に注目。これがアニメーションの肝である。

円の大きさのアニメーションを例に説明する。

「追加」のときには <circle> の半径を 0 で初期化している。そのあとの「更新」で 6 にしてる。その結果、追加時には徐々に大きくなりながら画面に現れる。

ソースが長くなって、多少複雑になったが、「追加」「削除」「更新」の基本は変わっていない。

まとめ

D3.js の Data-Driven な DOM 操作を説明した。入力されたデータに対して、「追加」「削除」「更新」の処理を分けて書くことで、驚くほどシンプルに記述できることが分かった。

基礎が分かったら、ギャラリーAPI リファレンス を見比べれば、いろんな使い方が分かってくることだろう。あと、SVG の知識も必要にはなってくる。

D3.js は「表示機能がない」という異色のビジュアライズ用のライブラリーだけども、表示処理を自由に操れるということは、見た目のカスタマイズをやりやすくなる。この手のライブラリーで一番苦労するのが、ちょっとしたカスタマイズをやりにくいところなので、こういう設計は実はありがたいのかもしれない。

D3.js の d3.svg.line() を試してみた

$
0
0

1つ前の記事 D3.js の Data-Driven な DOM 操作がおもしろい のサンプルコードではシンプルにするために、座標計算の処理を泥臭く書いていた。

たとえば

  circles.enter()
    .attr('cx', function(d, i) { return i * 280 / n + 10; })

のような座標を計算する関数が何箇所かに散らばっていた。

これ、d3.svg.line() を使ったらまとめられるし、便利な interpolate の機能も使えるよ、というのが今回のお話。

d3.svg.line() の使い方

たとえば

var line = d3.svg.line()
  .x(function(d, i) { return i; })
  .y(function(d, i) { return d * d; });

としておくことにする。line.x() とすると function(d, i) { return i; } を返してくれるので、関数を再利用してコードが読みやすくなる。line.y() も同様。

さらに、line([5,2,4]) のようにして配列を渡すと "M0,25L1,4L2,16" を返す。これは (1, 5*5)(2, 2*2), (3, 4*4) を結んだ線を表す SVG である。

ま、これだけならちょっと便利かなぁ、というぐらいだけども、line.interpolate("cardinal") を実行しておくと、line([5,2,4])"M0,25Q0.7999999999999999,4.9,1,4Q1.2,3.1,2,16" を返す。

これは、座標を曲線で結んだ SVG をあらわす。うまいこと計算してくれている。

今回のサンプル

というわけで、前回のサンプルを少し改善しつつ、各種の interpolate を試せるようにしたものを置いておく。

使い方

  • 初期状態では 10 個の要素を持った配列を表示している。
  • 横軸が配列のインデックス、縦軸が要素の値 (0~1) をあらわす。
  • [random] ボタンを押すと、配列の中身がランダムな値で置き換わる。
  • [push] ボタンを押すと、配列の末尾に要素を追加する。
  • [pop] ボタンを押すと、配列の末尾から要素を取り除く。
  • 選択欄で interpolate の値を変更できる。

ボタンを押すと、アニメーションつきで見た目が変更するのを確認していただけるだろうか (SVG をサポートしてる必要があるので、モダンではないブラウザーでは表示できない)。

HTML のソース

<div>
<svg id="sample" width="300" height="300"
  style="background: white; border: .3em solid #ccc;"></svg>
</div>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<button onclick="random()">random</button>
<button onclick="push()">push</button>
<button onclick="pop()">pop</button>
<select id="line-interpolate">
  <option>linear</option>
  <option>linear-closed</option>
  <option>step</option>
  <option>step-before</option>
  <option>step-after</option>
  <option>basis</option>
  <option>basis-open</option>
  <option>basis-closed</option>
  <option>bundle</option>
  <option>cardinal</option>
  <option>cardinal-open</option>
  <option>cardinal-closed</option>
  <option>monotone</option>
</select>
<script src="d3js-svg-line.js" charset="utf-8"></script>

d3js-svg-line.js のソース

var svg = d3.select("svg#sample")
  .attr('width', 300).attr('height', 300)
  .style('display', 'block');
var polyline = svg.append('path')
  .attr('stroke', 'red')
  .attr('stroke-width', '1')
  .attr('fill', 'transparent');

var values = [];
for (var i = 0; i < 10; i++) {
  values.push(Math.random());
}
d3.select('#line-interpolate').on('change', update);

function update() {
  var n = values.length;

  var s = d3.select('#line-interpolate')[0][0];
  var interpolate = s.options[s.selectedIndex].value;

  var line = d3.svg.line()
    .x(function(d, i) { return (i + 1) * 300 / (n + 1); })
    .y(function(d, i) { return d * 280 + 10; })
    .interpolate(interpolate);

  var circles = svg.selectAll('circle').data(values);
  circles.enter()
    .append('circle')
    .attr('cx', line.x()).attr('cy', 0).attr('r', 0);
  circles.exit()
    .transition()
    .duration(300)
    .attr('cy', 0).attr('r', 0)
    .remove();
  circles
    .attr('fill', 'red')
    .transition()
    .duration(300)
    .attr('cx', line.x())
    .attr('cy', line.y())
    .attr('r', 6);
  polyline
    .transition()
    .duration(300)
    .attr('d', line(values));
}

function random() {
  var n = values.length;
  for (var i = 0; i < n; i++) {
    values[i] = Math.random();
  }
  update();
}

function push() {
  values.push(Math.random());
  update();
}

function pop() {
  values.pop();
  update();
}

update();

K-means 法を D3.js でビジュアライズしてみた

$
0
0

クラスタリングの定番アルゴリズム K-means 法(K平均法)の動作原理を理解するために、D3.js を使って可視化してみました。

  • 図をクリックするか [ステップ] ボタンを押すと、1ステップずつ処理を行います
  • [最初から] ボタンを押すと、最初の状態に戻ります
  • [新規作成] ボタンを押すと、N (ノード数) と K (クラスタ数) の値で新しく初期化します
  • 古いブラウザーではうまく表示できない可能性があります (IE 10、Firefox 25、Chrome 30 で動作確認しています)

K-Means 法とは

英語版 Wikipedia の k-means clustering - Wikipedia, the free encyclopedia の手順に沿って実装しています。

英語版の手順をザックリと書くとこんなイメージになります。

  1. 初期化: N 個のノード (丸印) と K 個のクラスター (×印) を作成する
  2. Assignment ステップ: 各ノードを一番近いクラスターに所属させる
  3. Update ステップ: クラスターをノードの重心に移動させる
  4. ステップ 2 に戻る

最初は 1 の状態で表示していて、クリックするごとに、ステップ 2 とステップ 3 を実行していきます。

ステップを繰り返すにしたがって、クラスターの重心 (×印) が移動しながら、塊ができていく様子を確認できると思います。

D3.js と ActionScript 3

ここからはプログラミングの話です。

お気づきの方もいるかもしれませんが、このビジュアライズは 4 年ほど前に ActionScript 3 で作った クラスタリングの定番アルゴリズム「K-means法」をビジュアライズしてみた の焼き直しです。

同じものを D3.js と ActionScript 3 で実装してみて気づいたことがいくつかあります。

実装のしやすさ

ActionScript 版にくらべて、D3.js 版では

  • クラスタが変わるときの色の変化のアニメーション
  • 重心が移動するときの線のアニメーション
  • 新規作成のときのアニメーション

を追加しています。

D3.js の data()enter() といった API を活用すれば、このようなアニメーションが簡単に実現できました。ActionScript で同じことを実現するのは、かなり面倒です。data()enter() の嬉しいところについては、D3.js の Data-Driven な DOM 操作がおもしろい で書いているので、興味があれば読んでください。

ただ、D3.js で作る場合は、data() などの API にあわせてデータ構造を作りあげる必要があるので、少し慣れが必要でした。

表示速度

やっぱり Flash は速い。

D3.js 版では、ノードが増えたときのコマ落ちが目立ちます。このあたりは、ブラウザーの将来の進化に期待したいところです。

まとめ

  • K means 法をビジュアライズしてみたよ
  • D3.js 便利
  • ブラウザーの進化に期待

JavaScript のソースは k-means.js においてます。

Windows で Jekyll 1.3 を動かすまでの手順

$
0
0

Jekyll を Windows で動かそうとすると、いくつか難関がある。今回、自分の環境を新しく作り直したキッカケがあったので、導入までの手順をメモしておく。

自分の環境は Windows 8 Pro x64。各種ツールのバージョンは次の通り。

  • Jekyll 1.3.0
  • Ruby 2.0.0 p247
  • DevKit 4.7.2-20130224-1432
  • Python 2.7.6
  • Pygentize 1.6

基本的には Running Jekyll on Windows – Madhur Ahuja の手順に近いけど、Pygments の導入手順などは少し違っているのと、日本語独自の問題についても書いている。

1. Ruby 環境を整備する

RubyInstaller for Windows から One-Click Ruby のインストーラーと Development Kit を導入する。

Development Kit はネイティブな gem の導入に必要。

今回導入したのは次のバージョン。

  • Ruby 2.0.0-p247 (rubyinstaller-2.0.0-p247-x64.exe)
  • DevKit-mingw64-64-4.7.2-20130224-1432-sfx.exe

Ruby インストーラーの実行

まず、Ruby のインストーラーを導入する。デフォルトで C:\Ruby200-x64 にインストールされる。

環境変数の PATHC:\Ruby200-x64\bin を追加しておこう。

DevKit の導入

DevKit-mingw64-64-4.7.2-20130224-1432-sfx.exe を実行すると、展開先を聞かれる。ここでは C:\RubyDevKit を選択した。

次に設定する。コマンドプロンプトで dk.rbinitinitialize を実行する。

c:\RubyDevKit>ruby dk.rb init
[INFO] found RubyInstaller v2.0.0 at C:/Ruby200-x64

Initialization complete! Please review and modify the auto-generated
'config.yml' file to ensure it contains the root directories to all
of the installed Rubies you want enhanced by the DevKit.

c:\RubyDevKit>ruby dk.rb install
[INFO] Updating convenience notice gem override for 'C:/Ruby200-x64'
[INFO] Installing 'C:/Ruby200-x64/lib/ruby/site_ruby/devkit.rb'

ここまでで Ruby のセットアップは完了。

2. Jekyll を導入する

コマンドプロンプトから gem install jekyll を実行する。

次の gem が導入される。

  • classifier-1.3.3
  • colorator-0.1
  • commander-4.1.5
  • fast-stemmer-1.0.2
  • ffi-1.9.3-x64-mingw32
  • highline-1.6.20
  • jekyll-1.3.0
  • liquid-2.5.4
  • listen-1.3.1
  • maruku-0.6.1
  • posix-spawn-0.3.6
  • pygments.rb-0.5.4
  • rake-0.9.6
  • rb-fsevent-0.9.3
  • rb-inotify-0.9.2
  • rb-kqueue-0.2.0
  • rdoc-4.0.0
  • redcarpet-2.3.0
  • safe_yaml-0.9.7
  • syntax-1.0.0
  • test-unit-2.0.0.0
  • yajl-ruby-1.1.0

pygments.rb のダウングレード

悲しいことに、pygments.rb の 0.5.1 以降は Windows では動作しない。(pygments.rb #90 で Pull Request が出ているが、執筆時点でマージされていない)

そのため、導入された pygments.rb をアンインストールして、0.5.0 を入れなおしておく。「Jekyll が依存してる」と文句いわれるけど、気にせずに作業しちゃう。

> gem uninstall pygments.rb --version ">0.5.0"
> gem install pygments.rb --version "=0.5.0"

UTF-8 対策

HTML や記事に日本語が含まれると、Jekyll の実行時に次のようなエラーが出る。

Liquid Exception: invalid byte sequence in Windows-31J in post.md/#excerpt
error: invalid byte sequence in Windows-31J. Use --trace to view backtrace

Windows Ruby にありがちな FAQ で、ググると色々出てくる。

set LANG=ja_JP.UTF-8 するとよい、と書いてある記事もあったが Ruby 2.0 では効かないらしい。

Ruby 2.0 では環境変数の RUBYOPT-EUTF-8 にしておけば解決した (参考: WindowsでEncoding.default_externalをUTF-8にするには - すがブロ)。

環境変数をいじるのが面倒なら、C:\Ruby200-x64\bin\jekyll.bat の 2 行目あたりに set RUBYOPT=-EUTF-8 と書いてもいい。

もしくは、C:\Ruby200-x64\lib\ruby\gems\2.0.0\gems\jekyll-1.3.0\bin\jekyll の冒頭 2 行目に

Encoding.default_external = "utf-8"

を追加して回避してもよいだろう。(改行コードが LF のみなので、メモ帳では追加できない点に注意)

3. Pygments を導入する

コードのハイライトを行うためには Pygments を導入する。

Python 2.7

Download Python から 2.7 をダウンロードする。今回導入したのは Python 2.7.6 Windows X86-64 Installer (python-2.7.6.amd64.msi)。

インストーラーを実行してデフォルトの設定でインストールする。インストール先は C:\Python27 となる。

環境変数の PATHC:\Python27C:\Python27\Scripts を追加しておく (Scripts フォルダーはこのあと自動で作成される)。

easy_install

次に、setuptools から ez_setup.py をダウンロードする。

ez_setup.py を実行する。

> python easy_install.py
   :
Installing easy_install-script.py script to C:\Python27\Scripts
Installing easy_install.exe script to C:\Python27\Scripts
Installing easy_install-2.7-script.py script to C:\Python27\Scripts
Installing easy_install-2.7.exe script to C:\Python27\Scripts

Installed c:\python27\lib\site-packages\setuptools-1.3.2-py2.7.egg
Processing dependencies for setuptools==1.3.2
Finished processing dependencies for setuptools==1.3.2

Pygments

いよいよ Pygments をインストール!

easy_install pygments を実行すればよい。

> easy_install pygments
   :
Adding Pygments 1.6 to easy-install.pth file
Installing pygmentize-script.py script to C:\Python27\Scripts
Installing pygmentize.exe script to C:\Python27\Scripts

Installed c:\python27\lib\site-packages\pygments-1.6-py2.7.egg
Processing dependencies for pygments
Finished processing dependencies for pygments

4. Jekyll が動くか確認する!

ここまでくれば導入は完了したはず。動作するかテストしてみよう。

>jekyll new jekyll-test
New jekyll site installed in path/to/jekyll-test.

>cd jekyll-test

>jekyll serve
Configuration file: path/to/test-site/_config.yml
            Source: path/to/test-site
       Destination: path/to/test-site/_site
      Generating... done.
    Server address: http://0.0.0.0:4000
  Server running... press ctrl-c to stop.```

http://localhost:4000/ を開いて結果が出力されていれば成功。

お疲れ様。

CVS レポジトリを Git に変換した手順とか注意点とか

$
0
0

この前、10 年以上前に趣味で作っていたフリーソフトについてメールで質問が来た。もはや完全に記憶から消えているだけでなく、いま使っている PC にソースコードもない。何も分からない、答えられない。

そのままでは古いソースコードも成仏しきれない。供養するために、古い HDD を引っ張り出して探したところ、自宅サーバーをやってた HDD の中に CVS レポジトリーが見つかった。せっかくなので、Git に変換して GitHub で公開してみた (その1, その2)。これで成仏できるだろう。

そこで、この記事では CVS レポジトリーを Git に移行した手順をまとめておく。レガシーな CVS から Git に移行したい人の参考になるとうれしい。

git cvsimport の使い方

Git には git-cvsimport というコマンドがある。CVS の履歴を Git に変換してくれる。

CVS はファイルごとに履歴を保存する構造になってるんだけど、cvsps というツールを組み合わせることで、同時に変更したファイルを 1 コミットとして扱ってくれるようになる。

大きいレポジトリーだと結構時間かかる。cygwin だと超絶に時間がかかったので、UNIX 上で実行した。Git for Windows には git-cvsimport がない。

自分は次のように実行した。

git cvsimport -v -i -R -A author-conv-file.txt \
-d :local:/path/to/CVS <module>

それぞれのパラメータの意味は次の通り。詳しくは git-cvsimport を参照のこと。

  • -v: 出力を verbose にする。
  • -i: インポートだけを行ってチェックアウトしない。
  • -R: 「CVS のリビジョン番号」と「それに対応する Git のコミット」の対応付けを .git/cvs-revisions に出力する。一応生成しておく。
  • -A: ユーザー名の変換テーブル。詳細は後述。
  • -d: CVSROOT のパス。cvs コマンドで -d に指定するやつ。もしくは、環境変数の CVSROOT に指定してるやつ。
  • <module>: モジュール名。サブディレクトリを指定することもできる。

-d で指定したパスに CVSROOT ディレクトリがないと Expected Valid-requests from server, but got: E Cannot access /path/to/CVS というエラーになる。ダミーでよいので mkdir /path/to/CVS/CVSROOT しておけば回避できた。

author-conv-file の書き方

author-conv-file には「CVS のユーザー名」から「Git の Author 名・メールアドレス」への対応を記述していく。

user1=User 1 <user1@example.com>
user2=User 2 <user1@example.com> Asia/Tokyo

1.8.1 からはタイムゾーンも指定できるようになっている。それ以前のバージョンだと、すべて UTC でのコミットとなってしまう。

実行が終わったら git log | grep Author: | sort | uniq を実行して、author-conv-file に漏れがないか確認しておくといいだろう。

cvsps のキャッシュ

個人的に悩まされたのが、cvsps が結果を ~/.cvsps にキャッシュすること。CVS レポジトリーを変更したあとに git cvsimport を実行しても反映されない。そういうときは、rm -rf ~/.cvsps して再実行するとよい。

文字コードの変換

いまなら「全部 UTF-8 でやっちゃえ」となるんだけど、CVS 全盛の時代は Windows は ShiftJIS で、UNIX 上では EUC-JP がまだまだ主流だった。

コミットログやファイル名が UTF-8 でない場合は、ちょいとソースコードを修正する必要がある。

以下は 1.7.3.1 の git-cvsimport に対する修正。

--- /usr/local/libexec/git-core/git-cvsimport.bak
+++ /usr/local/libexec/git-core/git-cvsimport
@@ -722,6 +722,11 @@
 sub update_index (\@\@) {
        my $old = shift;
        my $new = shift;
+
+       # file name
+       use Encode qw/decode encode/;
+       $_ = encode('utf8', decode('shift-jis', $_)) for @$old;
+       $_->[2] = encode('utf8', decode('shift-jis', $_->[2])) for @$new;
+
        open(my $fh, '|-', qw(git update-index -z --index-info))
                or die "unable to open git update-index: $!";
        print $fh
@@ -810,6 +816,11 @@
        substr($logmsg,32767) = "" if length($logmsg) > 32767;
        $logmsg =~ s/[\s\n]+\z//;

+       # commit log
+       use Encode qw/encode decode/;
+       $logmsg = encode('utf8', decode('shift-jis', $logmsg));
+
        if (@skipped) {
            $logmsg .= "\n\n\nSKIPPED:\n\t";
            $logmsg .= join("\n\t", @skipped) . "\n";

ここではコミットログやファイル名が Shift_JIS だという前提で書いてある。

自動判定したい場合は

+       use Encode::Guess qw/euc-jp shiftjis/;
+       $logmsg = encode('utf8', decode('guess', $logmsg));

のように書けばいける。

歴史の書き換え

push する前に、インポート結果を十分に確認しておきたい。次のような点に注意して作業した。

コミットログを書き換えたいなら git rebase -i でやっちゃう。

不要なファイルがあったら git filter-branch --tree-filter 'rm -f passwords.txt' HEAD のようにして消しておく (参照: Git - 歴史の書き換え)。

一度でも歴史を書き換えたら、commit date が実行時刻になってしまうので、git filter-branch --env-filter 'GIT_COMMITTER_DATE=$GIT_AUTHOR_DATE; export GIT_COMMITTER_DATE' で author date にそろえておく (参照: git rebase without changing commit timestamps - Stack Overflow)。

このあたりは git-cvsimport の Tips というよりも、Git での歴史書き換えの Tips。

まとめ

負の遺産 CVS を捨てて、健全な Git ライフを送ろう!

Viewing all 42 articles
Browse latest View live