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

Windows でファイル作成日時をスクリプト言語から操作するために setctime.exe を作った

$
0
0

Windows でたくさんのファイルの作成日時を変更したくなったときに、Perl・Ruby・Python などのスクリプト言語を使おうとしたんだけど、作成日時を変更するための関数がなくて困った。

そもそも UNIX にファイルの作成日時がない

stat(2) で取得できる情報のうち時刻に関するものは次の 3 つがある。

  • atime: 最終アクセス日時 (access time)
  • utime: 最終更新日時 (modify time)
  • ctime: 最終変更日時 (change time)

名前からすると ctime が作成日時 (creation time) っぽいけど、ctime はファイルを更新したときとか chmod や chown したときなどに自動的に現在時刻に更新されるようだ。

atimeutimeutimes(2) で任意の時刻に変更できるんだけど、ctime を変更する手段は表立っては用意されていない。

ということで、UNIX 文化には「ファイルの作成日時」という概念がないので、UNIX 由来なスクリプト言語を使って「Windows のファイルの作成日時」を変更する公式の手法はない。

ということで、これを何とかするための補助アプリケーション setctime.exe を作ってみた。

setctime.exe の使い方

とてもシンプル。

  • コマンドライン引数でファイル名を受け取る
  • 受け取ったファイルに設定されている更新日時を、作成日時にそろえる
  • 成功したときのみ exit code が 0 になる

日時の指定方法って、言語ごとにロケールやらタイムゾーンやらフォーマットやらが色々あってややこしいので、更新日時を、作成日時にそろえるという機能だけを提供することにしてみた。

こんだけシンプルにしたおかげで、いろんなスクリプト言語から使いやすくなってる。

例をあげておく。

Perl

更新日時・作成日時を現在時刻にする Perl スクリプト。

utime undef, 0, 'test.txt';
system('setctime test.txt') == 0 or die "failed";

utime() してから、setctime.exe を呼び出すだけ。

mtime も変わっちゃっうけど、嫌なら stat() で取得して、utime() で戻してやればよい。

Ruby

同じく、更新日時・作成日時を現在時刻にする Ruby スクリプト。

t = Time.now
File::utime(t, t, 'test.txt')
system 'setctime.exe test.txt' or abort 'failed'

File::utime() してから、setctime.exe を呼び出すだけ。Ruby では atime を省略できないらしいので、一時変数 t を使って atime も更新している。

Python

同じく、更新日時・作成日時を現在時刻にする Python スクリプト。

import os
import sys
import time

t = time.time()
os.utime('test.txt', (t, t))
if os.system('setctime.exe test.txt') != 0:
    sys.exit('failed')

実装詳細

50 行ほどの簡単な Windows プログラミングになっている。ファイルを開いて、GetFileTime() して SetFileTime() しているだけ。

開発は Visual C++ 2008 で行っている。もっと新しいやつでやってもいいんだけど、新しいやつで開いたら自動でプロジェクトファイルが変換されるので、手元にある一番古い Visual C++ で開発している。

Visual C++ 再頒布可能ランタイム問題

Visual C++ で開発すると、再頒布可能ランタイムの扱いをどうするかが悩ましい。

静的リンクするとサイズが大きくなる。動的リンクすると開発者と同じバージョンの Visual C++ ランタイムが入ってないと、実行時にエラーになる。

今回は小さなアプリケーションだったので、libc.lib をリンクしないようにした。printf() が使えなくなったり、argc argv が参照できなかったり、それなりに制限はあるけど Win32 のネイティブな API を使って回避できる。

そんなアクロバットな努力の結果、実行ファイルのサイズが 4KB と小さくなって、ランタイムライブラリーのバージョン問題に悩まなくてよくなった。

まとめ

どうぞご利用ください。


タッチ操作に対応した画像ビューワーをJavaScriptで作るならD3.jsが便利

$
0
0

スマホやタブレットで写真を表示していると、ピンチでズームしたり、ドラッグで移動したりができて便利なので、あれを Web 上で実現してみたくなった。

最近のブラウザーでは touchstarttouchmove イベントでタッチ情報を取れたり、イベントの touches でマルチタッチを扱えたりするので、実現するための基盤はそろっている。

適当なライブラリーがあるかと思って探してみたが、意外と苦労してしまった。

Hammer.js が使えない

タッチを扱うためのライブラリーとしては Hammer.js がメジャーらしい。スワイプ・ピンチ・ドラッグなど、各種イベントにも対応していて、これを使えば一発解決してくれそうだ。

ところが、画像ビューワーを作るには不向きだった。困ったのは次の 2 点。

  • ピンチやドラッグは個別には動くが、組み合わせたときに「表示位置」と「倍率」の関係を自前で計算する必要がある
  • 人差し指でドラッグしながら親指を追加してピンチすると、ピンチイベントは来るもののスケールがいい加減 (ドラッグ開始位置を基準に算出している気配)

いちおう公式のサンプルに pinchzoom.html というものはあるんだけど、動かしてみると分かる通り、毎回、移動の初期位置は初期化される。

コードも読んでみたが、複雑なものを作るには少し不向きである予感がした。

D3.js の d3.behavior.zoom で万事解決!

自前で実装するにしても難しそうだし、しばらく途方に暮れていたのだけど、D3.jsZoomable Geography のサンプルを見たら、見事に希望の動作を実現していた。

ドラッグ中にピンチしてもきれいに動いている。マウスのドラッグやホイールにも対応してたり、ダブルクリック・ダブルタップでの拡大もしてくれる。Nexus 7 と Windows 8 + Google Chrome で動作することを確認した。すばらしい。

しかもサンプルのコードが超絶短い。どうやら、D3.js の d3.behavior.zoom() がタッチ操作を全部面倒みてくれるようだ。

画像ファイルでやってみた

そこで、試しに自分も作ってみた。本家のサンプルは SVG だけども、こっちのサンプルは単なる画像を CSS3 の 2D Transform で動かしている。

↑をピンチで拡大縮小、ドラッグで移動できるよ。

まとめ

D3.js がタッチ操作に対応してるのは、たぶんビジュアライズ結果を拡大・移動したいからだろう。他にも同じようなことを実現するライブラリーはありそうなのだが、意外と見つからなかったので記事にしておく。

D3.js については D3.js の Data-Driven な DOM 操作がおもしろい もご覧あれ。そうそう、このエントリーは d3.js Advent Calendar の 13 日目の記事だったりもする。

ソースは以下に (40行)。2D Transform では transform-origin の設定が必要なところで少しはまった。

<!DOCTYPE html>
<meta charset="utf-8">
<style>
html,body {
    margin: 0;
    width: 100%;
    height: 100%;
    overflow: hidden;
    background: white;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
var zoom = d3.behavior.zoom()
    .scale(1)
    .scaleExtent([.1, 10])
    .on("zoom", zoomed);

var svg = d3.select("body")
    .call(zoom);

function zoomed() {
    var t = "translate(" + d3.event.translate[0] + 'px,' + d3.event.translate[1] +"px) " +
        "scale(" + d3.event.scale + ',' + d3.event.scale + ")";
    d3.select("img")
        .style("transform-origin", "0 0")
        .style("-moz-transform-origin", "0 0")
        .style("-webkit-transform-origin", "0 0")
        .style("-o-transform-origin", "0 0")
        .style("-ms-transform-origin", "0 0")
        .style("transform", t)
        .style("-moz-transform", t)
        .style("-webkit-transform", t)
        .style("-o-transform", t)
        .style("-ms-transform", t);
}
</script>
<img src="/images/logo-blog.png">
</body>

うちの年賀状2014

$
0
0

毎年恒例で、一年の最初のエントリは我が家の年賀状でのご挨拶です。

写真2つとイラストを無理矢理合成して作りました。

twitter アイコン

twitter のアイコンも合わせて更新しています。

今年もよろしくおねがいします。

Windows で Chef するときに PATH で混乱しないように専用のコンソールを作った

$
0
0

話題の Chef をいまさら試したくなって、手元の Windows 環境に環境を作ってみた。

http://www.getchef.com/chef/install/ からインストーラーをダウンロードしてインストールした。Cygwin を使うと無駄にはまるし、ぐぐるといっぱい出てくる「gem から入れる手順」は公式サイトでは見つからなくなっている。

すべてデフォルトでインストールすると、C:\opscode\chef にファイルがいっぱい展開されていた。

PATH に追加される 2 つのフォルダー

インストーラーから Chef を入れると、PATH

  • C:\opscode\chef\bin
  • C:\opscode\chef\embedded\bin

の 2 つが追加される。

C:\opscode\chef\bin には Chef 関係のプログラム (chef-solo, knife など...) が入っている。

一方、C:\opscode\chef\embedded\bin には UNIX 関係のプログラム (ruby.exe, perl.exe, ls.exe, cat.exe など...) が入っている。

ピンチ! 既存の Ruby 環境と競合!!

全部入りでありがたいんだけど、ruby.exeperl.exe にパスが通ってしまうのが気持ち悪い。

自分の環境では、Chef のやつ以外にも Ruby を C:\Ruby200-x64\bin にインストールしていて、そっちにも PATH が通った状態になっている。

この状態で、gem install knife-solo すると、先にインストールしたほうの Ruby の gem が動く。自分の場合は Chef が後だったので、C:\Ruby200-x64\bin のほうの gem が動いた。その結果、C:\Ruby64 の下に knife-solo が入って、おかしな状態になった。

この環境では、Chef 側の何らかのコマンドが C:\opscode\chef\embedded\bin\ruby.exe を実行するつもりで ruby を実行したとしても、C:\Ruby200-x64\bin\ruby.exe が実行されてしまうわけで、変なバグに困らされる確率が高そうだ。

何かと嫌な予感がするので、対策することにした。

解決法: Chef 用のコンソールを作る

Visual Studio を入れたら「開発者コンソール」がスタート メニューに入るし、Git for Windows でも MS-DOS から隔離された Git Bash で作業をする。

同じように、Chef にも専用のコマンド プロンプトを用意することにした。

ということで作ってみた。手順は 2 段階。

1. バッチファイルを作る

こちらがそのバッチファイル。これを C:\opscode\chefenv.bat といった名前で保存しておく。

@ECHO OFF

SET PATH=c:\opscode\chef\bin;c:\opscode\chef\embedded\bin
SET PATH=%PATH%;c:\windows\system32;c:\windows

title Chef Env
chef-solo -v

2. ショートカットを作る

このファイルへのショートカットを作る。

[リンク先] は C:\Windows\System32\cmd.exe /K C:\opscode\chefenv.bat に設定する (/K は「バッチを実行して、ウインドウを閉じない」という意味)。

[作業フォルダー] は Chef レポジトリーのパスか、マイドキュメントのパスにでもしておくとよいだろう。

ショートカットのファイル名は何でもよいけど、「Chefコマンド プロンプト」とでもしておいてスタートメニューに登録するなり、デスクトップに置くなり、ランチャーに登録するなりしておくとよいだろう。

使い方

ショートカットを叩くと、PATH が限定された状態でコマンドプロンプトが立ち上がってくれる。

chef-soloknife も実行できる。めでたし。

ついでに、システム全体の PATH から c:\opscode 以下の 2 つを削っておくと、コマンドプロンプトを使ったときの混乱も起きなくなって幸せ。

ぜひお試しください。

リモート デスクトップでポート番号を指定して接続する方法

$
0
0

Windows のリモート デスクトップは 3389 番ポートを利用している。

通常はポート番号は変わらないんだけど、ポートフォワードで別のポート番号で待機する場合がある。

たとえば、NAT で外に出ていく仮想マシンにリモートデスクトップで接続したい場合、ポートフォワードを使って、ゲストの 3389 番をホストの 13389 番などで公開することがある。

「ホスト名:ポート番号」でいける

13389 番で待機中のリモートデスクトップサービス接続するには、[コンピューター] の欄に localhost:13389 と入力すればよい。

コマンドラインから接続する場合は mstsc /v:localhost:13389 と入力する。

調べた手順

まずは、コマンドライン オプションでの指定方法を調べるために、mstsc /? を実行したところ、

/v:<サーバー[:ポート]> -- 接続先のリモート コンピューターを指定します。

と出てきた。

同じ書き方を GUI でも試したらうまくいった。

ローカルの 3389 にポートフォワードできない問題

ちなみに、先ほどの例で、ゲストの 3389 番をホストの 3389 番にポートフォワードして、localhost に接続しようとすると

既に進行中のコンソール セッションがあるため、リモート コンピューター上の他のコンソール セッションに接続できませんでした。

というエラーがでて接続できない。

要は「自分自身に RDP できませんよ」ということなんだろうけど、リモート デスクトップを有効にしてなくてもこのエラーがでてしまう…。

ということは、接続を試みる前にクライアント側でエラーを表示しているっぽい。少し納得できないところではあるが、仕方がない。

Vagrant で作ったり壊したりできる Windows 環境を手に入れるまでの手順

$
0
0

最近話題の Vagrant さんは「Linux の環境を作ったり壊したりして開発とか試験が楽になるよ」と紹介されることが多いけど、Windows の環境だって作ったり壊したりしたい!

いろいろ調べつつ環境を作ってみたので、その手順を共有しておく。

完成イメージはこんな感じ。コマンドプロンプトから vagrant up をしたら VirtualBox 上に Windows Server 2012 R2 の環境が準備されて、そこにリモート デスクトップで接続している。

いろいろいじったあとに vagrant destroy したら環境は消え去って、vagrant up したら、また、まっさらな状態から使える。

ちょっと注目してほしいのは、ゲスト OS の C:\vagrant にホスト側の Vagrantfile がマウントされているところ。このあたりの処理は Vagrant-Windows というプラグインがうまくやってくれている。このプラグインのおかげで、Linux と同じように Vagrantfile を設定してやると、ホスト名を変更したり、IP アドレスを指定してネットワーク アダプターを追加したり、ホスト側の Chef のレシピを vagrat provision を使って、ゲスト上で走らせたりできるようになる。

目次

最初に目次をドーン。

  1. VirtualBox をインストールする
  2. Vagrant をインストールする
  3. ゲスト OS をインストールする
  4. Vagrant-Windows 用の設定を行う
  5. Base Box を作成する
  6. Base Box を試す

正直いって長い。Linux ならネットに転がっている Box を使って「ハイ終わり」なんだけど、Windows の場合は自分で Box を作らなきゃいけないので、こんな長い手順になっている。

ゲスト OS は Windows 8.1 Pro 64bit と Windows Server 2012 R2 Standard を試してみた。

ホスト OS は Windows 8.1 Pro x64 を使っている。試してないけど Windows 以外の OS でも VirtualBox と Vagrant が動くならいけると思う。

なるべく一次情報を併記しつつ手順を示すので、バージョンが違うときにも応用はきくはずだ。また、裏側で何が起こっているのかを調べてみたので、うまくいかないときには参考にしてほしい。

では、さっそく行ってみよう。

1. VirtualBox をインストールする

VirtualBox を次の場所からダウンロードする。

執筆時点で最新の 4.3.6 をインストールした。

すべてデフォルトでインストールしたら、C:\Program Files\Oracle\VirtualBox にインストールされた。

補足

Vagrant が対応している VirtualBox をインストールしなきゃいけない。

VirtualBox Provider - Vagrant Documentation に Vagrant が要求する VirtualBox のバージョンが書いてある。執筆時点 (2014年2月) は次のようになっている。

The VirtualBox provider is compatible with VirtualBox versions 4.0.x, 4.1.x, 4.2.x, and 4.3.x.

パスを通す

Vagrant さんは C:\Program Files\Oracle\VirtualBox\VBoxManage.exe を使って VirtualBox に色んな指令をだす。

パスが通ってないとエラーになるので、C:\Program Files\Oracle\VirtualBox にパスを通しておく。

2. Vagrant をインストールする

Vagrant のインストーラーを次の場所から取得して、すべてデフォルトでインストールする。

執筆時点で最新の 1.4.3 を利用した。

Vagrant は C:\HashiCorp\Vagrant にインストールされる。自動で、C:\HashiCorp\Vagrant\bin にパスが通る。C:\HashiCorp\Vagrant\embedded に ruby や mingw などの UNIX 環境が入る。

Vagrant-Windows プラグイン

Vagrant で Windows をゲスト OS として扱うために、Vagrant-Windows プラグインを導入しておく。

コマンドプロンプトから次のコマンドを実行する。

>vagrant plugin install vagrant-windows
Installing the 'vagrant-windows' plugin. This can take a few minutes...
Installed the plugin 'vagrant-windows (1.5.1)'!

Vagrant-Windows の 1.5.1 がインストールされた。

補足

プラグインの gem は C:\Users\username\.vagrant.d\gems\gems に展開される。

3. ゲスト OS をインストールする

UNIX 系の OS だと、Vagrantbox.es から Base Box (Vagrant 用のイメージ) を拾ってくればいいんだけど、Windows だとそうもいかない。自分で Base Box を作るところから始まる。

Base Box を作るときの注意点は Creating a Base Box - Vagrant Documentation にある。ざっと要約すると

  • ディスク サイズは大きめにしてね。VirtualBox なら [可変サイズ] のディスクを大きめのサイズで作ってね。
  • メモリーは大きすぎない値にしてね。ユーザーは Vagrantfile で変更できるんだから、512MB ぐらいにしておいてね。
  • オーディオとか USB は無効にしてね。
  • vagrant というユーザー (パスワードも vagrant) を作ってね。authorized_keys に追加してね。sudoers に追加してね。root のパスワードも vagrant にしてね。

といったことが書いてある。これに従って作業してみる。

仮想マシンの作成

次の手順で実施した。インストールメディアの ISO ファイルがあるものとする。

  1. VirtualBox を起動する。
  2. メニューから [仮想マシン] > [新規] で新しい仮想マシンを作る。
  3. 名前とバージョンを選択して、[次へ] を押す。
    • Windows Server 2012 R2 のとき: 名前は [Windows 2012 R2]、バージョンは [Windows 2012 (64bit)]。
    • Windows 8.1 のとき: 名前は [Windows 8.1]、バージョンは [Windows 8.1 (64bit)]。
  4. メモリーは 2048MB を提案されるので、1024MB に減らして [次へ] を押す。
    • 512MB まで減らすと、Windows 8.1 で 0xc0000017、Windows Server 2012 R2 で 0xE0000100 エラーが出て先に進めなかった。
  5. ハードドライブはデフォルトで 25GB の可変サイズで [作成]、[次へ] を押していく。
  6. 新しくできた仮想マシン [Windows 2012 R2] を選択して、メニューから [仮想マシン] > [設定] を選ぶ。
  7. オーディオと USB を無効にする。
    • [オーディオ] を選択して、[オーディオを有効化] のチェックを外す。
    • [USB] を選択して、[USB コントローラーを有効化] のチェックを外す。
  8. ISO イメージを割り当てる。
    • [ストレージ] を選択する。
    • [コントローラー: IDE] の下の [空] を選択する。
    • [CD/DVD ドライブ] の右側のディスクのアイコンをクリックして、[仮想CD/DVDディスクファイルの選択...] を選ぶ。
    • インストールメディアの ISO ファイルを選ぶ。
  9. [OK] を押して閉じる。

OS のインストール

引き続き、OS のインストールを行う。

  1. メニューから [仮想マシン] > [起動] で仮想マシンを起動する。
  2. ISO イメージから起動するので、デフォルトの設定でインストールを始める。
  3. (2012 R2 のみ) OS の種類は [Windows Server 2012 R2 Standard (GUI 使用サーバー)] を選択して、[次へ]。
  4. インストールの種類は [カスタム: Windows のみをインストールする (詳細設定)] を選ぶ。
    • インストールが始まるので、しばらく待つ。
  5. アカウントのパスワードを設定する。
    • Windows Server 2012 R2: Administrator アカウントのパスワードを決める画面になるので、適当に入力する (この時点では "vagrant" は「簡単すぎる」と怒られるので設定できない)。
    • Windows 8.1: Microsoft アカウントは登録しない。ローカルアカウントをユーザー名 vagrant、パスワード vagrant で登録する。
  6. ログオン画面になるので、作成したアカウントでログオンする。
    • Ctrl + Alt + Del は「右 Ctrl + Del」で入力する。メニューから [仮想マシン] > [Ctrl-Alt-Delを送信] でもよい。
  7. Guest Additions をインストールする。

    • メニューから [デバイス] > [Guest Additions のCDイメージを挿入...] を選ぶ。
    • D: ドライブを開いて、Guest Additions のセットアップを行う。
    • 作業が終わったら、[デバイス] > [CD/DVD デバイス] > [仮想ドライブからディスクを除去] でディスクを抜いておく。

4. Vagrant-Windows 用の設定を行う

ゲスト OS 側にいろんな設定を行う。

Linux 側の手順でいうところの

  • vagrant というユーザー (パスワードも vagrant) を作ってね。authorized_keys に追加してね。sudoers に追加してね。root のパスワードも vagrant にしてね。

の作業をやるのだが、Windows の場合は SSH ではなく WinRM を使う。

詳しい手順は Vagrant-Windows のページに書いてあるので、この手順に従って、ちまちまと作業を行う。

  • Create a vagrant user, for things to work out of the box username and password should both be "vagrant".
  • Turn off UAC (Msconfig)
  • Disable complex passwords
  • Disable Shutdown Tracker on Windows 2008/2012 Servers (except Core).
  • Disable "Server Manager" Starting at login on Windows 2008/2012 Servers (except Core).
  • Enable and configure WinRM (see below)

パスワードの複雑性を無効にする

Windows Server 2012 R2 のみ。

  1. サーバー マネージャーのメニューから [ツール] > [ローカル セキュリティー ポリシー] を選択する。
  2. [アカウント ポリシー] > [パスワードのポリシー] > [複雑さの要件を満たす必要があるパスワード] をダブルクリックして [無効] に設定する。
  3. Ctrl-Alt-Del から Administrator のパスワードを vagrant に変更しておくとよい。

ユーザー vagrant を作成する

Windows Server 2012 R2 のみ。

  1. サーバー マネージャーのメニューから [ツール] > [コンピューターの管理] を選択する。
  2. [システム ツール] > [ローカル ユーザーとグループ] > [ユーザー] を選択する。
  3. 右クリックから [新しいユーザー] を選択する。
  4. ユーザーを作成する。
    • ユーザー名、パスワードを vagrant にする。
    • [ユーザーは次回ログオン時にパスワードの変更が必要] のチェックを外す。
    • [パスワードを無期限にする] をチェックする。
    • [作成] ボタンを押す。
  5. 管理者に変更する。
    • 新しく作成した vagrant 右クリックして [プロパティ] を選択する。
    • [所属するグループ] タブを開く。
    • Administrators を追加して、[Users] を削除する。
    • [OK] ボタンを押す。

UAC を無効にする

Windows Server 2012 R2、Windows 8.1 共通。

  1. msconfig を起動する。たとえば、Windows + R で [ファイル名を指定して実行] を開いて、msconfig と入力して [OK] を押す。
  2. [ツール] タブを開いて、[UAC 設定の変更] を選択して、[起動] ボタンを押す。
  3. スライダーを一番下の [通知しない] にして、[OK] ボタンを押す。

Shutdown Tracker を無効にする

Windows Server 2012 R2 のみ。

  1. [ローカル グループ ポリシー エディター] を起動する。たとえば、Windows + R で [ファイル名を指定して実行] を開いて、gpedit.msc と入力して [OK] を押す。
  2. ツリーから [コンピューターの構成] > [管理者用テンプレート] > [システム] を選択する。
  3. 右側のペインから [シャットダウン イベントの追跡ツールを表示する] をダブルクリックする。
  4. [未構成] を [無効] に変更して、[OK] ボタンを押す。

ログオン後にサーバー マネージャーが表示されないようにする

Windows Server 2012 R2 のみ。

  1. [ローカル グループ ポリシー エディター] を起動する。たとえば、Windows + R で [ファイル名を指定して実行] を開いて、gpedit.msc と入力して [OK] を押す。
  2. ツリーから [コンピューターの構成] > [管理者用テンプレート] > [システム] > [サーバー マネージャー] を選択する。
  3. 右側のペインから [ログオン時にサーバー マネージャーを自動的に表示しない] をダブルクリックする。
  4. [未構成] を [有効] に変更して、[OK] ボタンを押す。 5.コマンドプロンプトから gpupdate を実行して、その場でポリシーを反映する。

WinRM を有効にする

Windows Server 2012 R2、Windows 8.1 共通。

コマンドプロンプト上で次のコマンドを実行する。

winrm quickconfig -q
winrm set winrm/config/winrs @{MaxMemoryPerShellMB="512"}
winrm set winrm/config @{MaxTimeoutms="1800000"}
winrm set winrm/config/service @{AllowUnencrypted="true"}
winrm set winrm/config/service/auth @{Basic="true"}
sc config WinRM start= auto

Windows Server 2008 では追加の設定が必要になるようなので、Vagrant-Windows を参照のこと。

設定しておいたほうが便利そうなこと

Vagrant-Windows の手順には書いてなかったが、やっておいたほうがよいかもしれないのは次の手順。

  • リモートデスクトップを有効にする: ヘッドレスで利用する場合は、リモートデスクトップでつなぐことになる。いずれにしても有効にしておいたほうが便利だろう。
  • Windows Update をとめる: 自動でダウンロードしたりインストールしたりする設定は OFF にしておくと、いきなり端末が重くなって悩まされない。

4. Base Box を作成する

ここまでで準備は完了。ゲスト OS は電源を切っておく。

ホスト OS 側でイメージを Base Box としてパッケージ化していく。

デフォルトの Vagrantfile を用意

Box に Vagrantfile を同梱したいので、次の中身を Vagrantfile.txt として保存しておく。

Vagrant.configure("2") do |config|
  config.vm.guest = :windows
end

「ゲスト OS は Windows だよ」「Vagrant-Windows を使ってゲスト OS に指令をだしてね」という意味だ。

この手順は省略してもよいんだけど、省略した場合は、プロジェクトを作るたびに上の設定を Vagrantfile に書く必要がある (忘れてしまうと、電源起動以外がうまく動かない)。

参考情報

  • Vagrantfile - Vagrant Documentation: Vagrantfile の優先順位について書いてある。プロジェクトごとの Vagrantfile に設定がない場合は、Box の Vagrantfile を見に行くそうだ。

vagrant package で Box を作成

コマンドプロンプトで次のように入力するだけ。

C:\Users\username>vagrant package --base "Windows 2012 R2" --vagrantfile Vagrantfile.txt
[Windows 2012 R2] Clearing any previously set forwarded ports...
[Windows 2012 R2] Exporting VM...
[Windows 2012 R2] Compressing package to: C:/Users/username/package.box```

カレントディレクトリに package.box というファイル名で出力される。Win2012R2.boxWin81.box などにリネームしておくと分かりやすいだろう。

補足

  • Windows 2012 R2 の部分は VirtualBox の仮想マシン名なので、別名で作った場合は適宜変更すべし。
  • Vagrantfile.txt が別のディレクトリーにあるときは、そこへのパスを入力すべし。
  • Vagrantfile を同梱しない場合は、--vagrantfile の指定は不要。

裏側の挙動

それなりの時間がかかる。内部的には次のような処理をしている様子。

  1. ディスクイメージ (この時点で約 8GB) を C:\Users\username\.vagrant.d\tmp の下に圧縮した vmdk として出力する (4GB に圧縮)。
  2. box.ovfVagrantfile も同じ場所に設置。
  3. tar でカレントディレクトリに package.box として固めて出力。

参考情報

5. Base Box を試す

ここからは他の OS の Base Box を試す手順とかなり似てくる。

vagrant box add

次のコマンドで Box を作成する。

> vagrant box add Win2012R2 path\to\package.box
Downloading box from URL: file:C:/Users/username/package.box
Extracting box...ate: 124M/s, Estimated time remaining: --:--:--)
Successfully added box 'Win2012R2' with provider 'virtualbox'!

path\to\package.box の部分は 4. で作成した box ファイルへのパスに置き換えて実行してね。

補足

package.box を Web サーバー上に置いた場合は vagrant box add BoxName http://example.com/package.box のように指定できる。Web 上に公開された Box ファイルの情報を寄せ集めたのが Vagrantbox.es である。

裏側の挙動

C:\Users\username\.vagrant.d\boxes\Win2012R2\virtualbox に box ファイルの中身が展開される。

vagrant init

プロジェクトのディレクトリーを適当に作って、vagrant init する。

>cd C:\Users\username\Documents
>mkdir Win2012R2
>cd Win2012R2
>vagrant init Win2012R2
A `Vagrantfile` has been placed in this directory. You are now
ready to `vagrant up` your first virtual environment! Please read
the comments in the Vagrantfile as well as documentation on
`vagrantup.com` for more information on using Vagrant.

Vagrantfile がカレントディレクトリーに出力される。

ゲスト OS の画面を表示する方法を

  • VirtualBox のウインドウを表示する
  • リモートデスクトップする

のいずれを選ぶかに応じて、Vagrantfile の設定手順が変わる。

VirtualBox のウインドウを表示する場合は、次のように # Don't boot with headless mode の部分のコメントを取り除いておく。

  config.vm.provider :virtualbox do |vb|
    # Don't boot with headless mode
    vb.gui = true

    # Use VBoxManage to customize the VM. For example to change memory:
    #vb.customize ["modifyvm", :id, "--memory", "1024"]
  end

リモートデスクトップを利用する場合は、次のような設定を書いておく。

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|

  # Port forward RDP
  config.vm.network :forwarded_port, guest: 3389, host: 13389

  # ここより下は省略...
end

ゲストの電源を入れたあと、ホスト上のリモートデスクトップ クライアントを立ち上げて localhost:13389 に繋ぎに行けばよい。詳しくは リモート デスクトップでポート番号を指定して接続する方法 を参照してほしい。なお、Vagrant-Windows にはホスト側を 3389 とする設定例が載っていたが、それではうまく動かなかった。

vagrant up

いよいよ起動。

> vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
[default] Clearing any previously set forwarded ports...
[default] Clearing any previously set network interfaces...
[default] Preparing network interfaces based on configuration...
[default] Forwarding ports...
[default] -- 3389 => 13389 (adapter 1)
[default] Booting VM...
[default] Waiting for machine to boot. This may take a few minutes...
[default] Machine booted and ready!
[default] Mounting shared folders...
[default] -- /vagrant
[default] VM already provisioned. Run `vagrant provision` or use `--provision` t
o force it

vagrant up すると端末が上がってくる。

補足

  • コピーが発生するので初回は少し待つ。
  • VirtualBox を起動しておくと、イメージが登録されて起動してくる様子を確認できる。
  • ログオンしてみると、C:\vagrant にホスト側のプロジェクト ディレクトリーが見えているのが確認できる。

裏側の挙動

  • C:\Users\username\.vagrant.d\boxes\Win2012R2\virtualbox の中身を C:\Users\username\VirtualBox VMs にコピーする。
  • 上記イメージを VirtualBox に登録して、電源が入る。
  • プロジェクト ディレクトリーが C:\vagrant で見えているのは「VirtualBox の共有フォルダーの機能」と「Vagrant-Windows が共有へのシンボリックリンクを作る機能」の合わせ技で実現している。シンボリックリンクは WinRM で mount_volume.ps1 を実行して作成している。

まとめ

以上で手順の解説は終わる。このあとは Vagrantfile を編集していろいろ試すとよい。

Vagrant-Windows が頑張ってくれているおかげで、Linux と同じように Vagrant の機能を使えることがわかった。手順が面倒だったのは、ライセンス上の問題で Windows の Box がネットに落ちていないから。

実際に使ってみると、VirtualBox がたまに落ちたり、ゲスト OS 内で VirtualBoxService.exerundll32.exe aepdu.dll,AePduRunUpdate が CPU を消費しまくっている現象が起きたりして悲しい。VirtualBox や Windows の問題なので、Vagrant は悪くない。

Windows の Jenkins で JENKINS_HOME を別のフォルダーに変更する方法

$
0
0

Jenkins を Windows 環境に MSI ファイルで導入すると、デフォルトでは C:\Program Files (x86) にインストールされる (64 ビットの場合)。

Java の実行環境を同梱してくれていたり、自動でサービスに登録してくれたりして嬉しいのだけど、気になるのが JENKINS_HOMEC:\Program Files (x86)\Jenkins になってしまう点。ジョブやワークスペース、プラグインなどのデータなどが Program Files の下に置かれてしまう。Windows 的な作法では「アプリケーションのデータは ProgramDataAppData に置きましょう」となっているので少し気持ち悪い。

JENKINS_HOME を変更するには環境変数を設定したらいけそうなんだけど、MSI から導入した場合は環境変数ではなく jenkins.xml の値が優先されるようだ (jar から直接実行する場合は未確認)。

ということで、「MSI から導入した Jenkins で JENKINS_HOME を変更する手順」をまとめておく。Jenkins のバージョンは 1.550。

ここでは、JENKINS_HOMEC:\ProgramData\Jenkins に変更するものとする。

  1. Jenkins サービスをとめる。
  2. インストール フォルダーの jenkins.xml を開いて

      <env name="JENKINS_HOME" value="%BASE%"/>
    

    を次のように書き換える (改行コードが LF なので、メモ帳ではできなくはないが探すのが少し大変)。

      <env name="JENKINS_HOME" value="%ProgramData%\Jenkins"/>
    
  3. インストール フォルダーの次のファイル・フォルダー以外%ProgramData%\Jenkins移動する (コピーだと、jobs 内にシンボリックリンクがあった場合に壊れてしまうので、必ず「移動」すること)。

    • jre フォルダー
    • jenkins.err.log
    • jenkins.exe
    • jenkins.exe.config
    • jenkins.out.log
    • jenkins.war
    • jenkins.war.bak
    • jenkins.war.tmp
    • jenkins.wrapper.log
    • jenkins.xml
  4. Jenkins サービスを開始する。

  5. http://localhost:8080/systemInfo から JEKNINS_HOME が設定した値になっていることを確認する。ジョブやプラグインなどの情報が引き継がれているかどうかも合わせて確認する。

とっても簡単ですね。インストーラーで設定できるようになっていると、より嬉しいので、気が向いたら pull request してみよう・・・。

ConEmu 突っ込んだら Git for Windows の Git Bash がカッコよくなった

$
0
0

Git for Windows の Git Bash の配色がイマイチだなーと思ってググってたら、Console2 だとか ConEmu を使うと楽にできるっぽいことが、Stack Overflow とか英語のブログで見つかった。

そこで、ConEmu を試してみたら色々と幸せになった (Console2 はそのままでは日本語が使えなかった。解決方法はあるらしいけど…)。

左が Git Bash、右が ConEmu さん。

アンチエイリアス効いてるし、色もオサレ。

起動から色を設定するまで

ConEmu を起動すると、初回は設定の保存場所などを確認される。お好みで答えて [OK] を押すと、タブ化した MS-DOS プロンプトみたいなのが立ち上がってくる。

[Win] + [N] を押すと新しいタブを開始できる。

上のキャプチャーのように、どんな環境のタブを開始するかをメニューで聞いてきている ({cmd} やら {cmd (Admin)} やら {PowerShell} やら {Git bash} やら {Putty} やら…。システムにインストールされてるものを自動で検知して、立ち上げられるようになっているようだ)。

{Git Bash} を選ぶと、Git Bash が新しいタブに出てくる。

この時点ではデフォルトの配色なんだけど、タブの部分を右クリックしたら、いろんなカラースキーマを試せるようになっているのが手軽だ。

<Terminal.app> (MacOS 風) とか、<xterm> (X Window) とかあるけど、世間的には Solarized が人気っぽいので、<Solarized (Luke Maciak)> を選んだ。

色が気に入ったら、全体設定の [Features] > [Colors] からデフォルトのカラースキーマを選んでおくと、次回からはその色で表示してくれるようになる。

多すぎる設定項目

設定パネルをみたら、気力を失うぐらいの設定項目がある。

ショートカットキーにからしか使えない機能も多数あるようで、上下左右分割とかもできるっぽい。プラグインとかマクロとかもあるご様子…。

とりあえずは、タブの使い勝手が気になったので、[Features] > [Tabs] から [Lazy tab switch] と [Recent mode] をオフにしておいた。

あと、ちょっとキーコンフィグをいじって、標準的な Windows っぽいやつに置き換えてみたりした。

エディター起動中の Duplicate root が便利

ちょっと使っていて便利だなー、と思ったのは、タブの右クリック メニューにある [Duplicate root...]。カレントディレクトリをそのまま引き継いで、新しいタブで Git Bash を起動してくれる。

何がうれしいかというと、コミットとか rebase -i でエディターが立ち上がっているときに、log とか diff とか見たくなることはそれなりにあると思う。こんなときにも、[Duplicate root...] して、新しい GitBash 上で確認できる。いままでは、別の Git Bash を立ち上げるとか、一時的なコミットログを書くとかしてたので、便利になった。

まとめ

  • ConEmu 便利だよ。
  • タブ化して、色とかフォントがキレイになるだけでもメリットある。
  • 他にも便利な機能はいっぱいありそう。

全角半角混在の文章で 1 行に半角何文字分あるか調べる方法

$
0
0

「ソースコードは 1 行あたり 80 文字以内」とか「コミットログは横幅 72 文字以内」とか、文字数に関するルールはいろいろある。

ルールを徹底するには機械的に判定したい。と思って、簡単なスクリプトを書こうとした瞬間、意外と「1 行あたりの文字数」をカウントするのが難しいことに気付いた。

たとえば、「あA」は「全角 1 文字+半角 1 文字」なので半角 3 文字分としてカウントしたい。

しかし、UTF-8 の世界では「あA」の文字長は 2 だし、バイト数は 4 (あ=0xE38182、a=0x41) である。

EUC-JP や Shift-JIS の時代なら、単純に「あA」は 3 バイトなので「半角 3 つ分」とすぐ分かったのだけども… (逆に文字長を調べるのが面倒だった)。

はて、どうするか? というのがこの記事でいいたいこと。

East Asian Width を見よ

いろいろとググった結果、Unicode の East Asian Width を参照するとよいらしい。

East Asian WidthEastAsianWidth.txt という資料が紹介されている。この資料では、全ての文字の横幅が 6 つのカテゴリーに分類されている。

そのカテゴリーが次の 6 つ。

分類 意味
F East Asian Fullwidth A (全角の A)
H East Asian Halfwidth ア (半角のア)
Na East Asian Narrow A (半角の A)
W East Asian Wide ア (全角のア)
A East Asian Ambiguous ○☆※ (一部の記号)、Д (ロシア語)
N Neutral (Not East Asian) À

たとえば、「あ」なら 3042;W # HIRAGANA LETTER A と書いてある。カテゴリーは W なので、全角 (=半角 2 文字分) だと分かる。

ということで、FW は全角 (= 2 文字分)、HNaN は半角 (= 1 文字) として扱えばよいわけだ。

Ambiguous をどうするか問題

で、A (East Asian Ambiguous) の扱いが難しい。

例にあるような ○☆※ などの記号を見ると全角として扱いたくなる。しかし、A にはロシア語も含まれている。

A を一律に全角として扱うと、「1 行に 80 文字以上書いたら警告出すシステム」を作ると、「80 文字超えてないのに警告出るんだけどなにこれ?」とロシアの人に怒られてしまうかもしれない。

具体例をみてみよう。ロシア語でヘンタイをあらわす「Хентай」をいくつかの環境で表示してみた。

Cent OS のコンソールでは半角になっているが、MS ゴシックなら全角になる。プロポーショナルなメイリオでは、だいたい半角で表示している。

悩ましい・・・!

とりあえず、Ambiguous は 1 文字として扱うのが安全そうだ。ある環境で 80 文字以上に見えていても、別の環境では 80 文字以内に見えている…ということは十分に起こりうるのだ…!

いろんな言語で判別したいよ

さて、一次情報と判定方法が分かったところで、いろんなスクリプト言語で実装するにはどうしたらいいだろう。"East Asian Width" 言語名 で検索したら、それなりに情報が出てくる。

Python

一番簡単だったのが Python さん。標準モジュールの unicodedataunicodedata.east_asian_width(unichr) なるメソッドがあった。

>>> import unicodedata
>>> unicodedata.east_asian_width(u'あ')
'W'
>>> unicodedata.east_asian_width(u'※')
'A'

PHP

伝家の宝刀 mb うんたらの関数群の中に、期待通り mb_strwidth() がいた。

echo mb_strwidth("あ"); // 2
echo mb_strwidth("※"); // 1

Ambiguous は 1 を返すようだ。

Ruby

標準ではムリだけど、unicode-display_width なるモジュールが gem にあった。

require 'unicode/display_width'
"○".display_width #=> 1
'一'.display_width #=> 2

現時点では Ambiguous は全部 1 を返しているよ、ということが TODO のところに書いてある。

JavaScript

こちらも標準ではムリだけど、eastasianwidth なるモジュールが npm にあがっている。

var eaw = require('eastasianwidth');
console.log(eaw.eastAsianWidth('₩')) // 'F'
console.log(eaw.eastAsianWidth('。')) // 'H'
console.log(eaw.eastAsianWidth('뀀')) // 'W'

console.log(eaw.length('あいうえお')) // 10

ソースコードを読んだところ、Ambiguous は 2 を返している。

Perl

CPAN にある Unicode::EastAsianWidth を使えば、次のようにできるらしい。

use Unicode::EastAsianWidth;

$_ = chr(0x2588); # FULL BLOCK, an ambiguous-width character

/\p{InEastAsianAmbiguous}/; # true
/\p{InFullwidth}/;          # false

長さやカテゴリーが返ってこない分、少し面倒そう。

まとめ

3 行でまとめちゃう。

  • UTF-8 では半角何文字分で表示されるのかを調べるのは少し面倒になった。
  • しかも、環境によって何文字分で表示するかは違うので、完全に調べきるのはあきらめたほうがよい。
  • 一次情報は East Asian Width にあるが、各種スクリプト言語には便利なライブラリーを使えばよい。

Git for Windows でレポジトリー上の CR LF を LF に変換する手順

$
0
0

Git for Windows では改行コードが「レポジトリー上は LF」「ワーキング ディレクトリーは CR LF」となるように、git config の core.autocrlftrue となる状態でインストールされる (インストーラーでデフォルトの [Checkout Windows-style, commit Unix-style line endings] を選択した場合)。

Windows 以外の文化圏の人は CR LF を見ると CR がゴミに見えるので、妥当な設定だろう。

標準設定の autocrlftrue のときに、レポジトリー上に CR LF なファイルが紛れ込んでいないか調べたり、紛れ込んだ CR LF を LF に変換したかったのだけど、この手順が少しややこしかったので記事にまとめておく。

(autocrlffalse にして clone したらすぐに分かる…という話なんだけど、大きなレポジトリーを clone しなおすのはストレスフルなので、その場で確認する手順を調べてみた)

※ Git for Windows は 1.9 と 1.8.4 で検証した

レポジトリー上に CR LF のファイルがあるか調べたい

レポジトリー上のファイルの改行コードは LF で統一しているつもりなのに、CR LF のファイルが混ざりこんでたら悲しい。

autocrlftrue な状態で、レポジトリー上に CR LF のファイルが入ってないか調べるには次のようにする。

$ git grep --cached -I $'\r'

オプションの補足:

  • --cached を指定しないと、ワーキング ディレクトリー上のファイルから CR を探してしまう。つまり、core.autocrlftrue な状態では、すべての改行がヒットしてしまう。
  • -I でバイナリー ファイルを無視している。

実行例

https://gist.github.com/nitoyon/9808563 に CRLF と LF のファイルの 2 つがあるレポジトリーを用意してみた。

core.autocrlftrue な状態で clone すると、両方のファイルが CRLF でチェックアウトされる。

この状態で上記のコマンドを実行してみると、CR LF のほうだけが引っかかる。

$ git grep --cached -I $'\r'
crlf-test.txt:End of Line Character Test^M
crlf-test.txt:^M
crlf-test.txt:Which do you like CR LF or LF?^M
crlf-test.txt:^M
crlf-test.txt:This file uses "CR LF"!!!!^M
crlf-test.txt:^M
crlf-test.txt:Clone me!^M

めでたし。

レポジトリー上の CR LF を LF に変換したい

万が一、レポジトリー上に CR LF を発見したら、LF に統一しよう。

こちらも core.autocrlftrue になっている状態からの手順を示しておく。

  1. 一時的に core.autocrlffalse に設定する。

    $ git config core.autocrlf false
    
  2. LF でチェックアウトするために、ワーク ディレクトリーのファイルを全部削除してから、チェックアウトする (一度削除しないと反映されないことがあった!!)。

    $ rm -rf .
    $ git checkout .
    
  3. CR LF になっているファイルを何らかのエディターで LF に変換する。

  4. コミットする。

    $ git add . && git commit
    
  5. autocrlf の設定を戻す。

    $ git config core.autocrlf true
    

実行例

再び、https://gist.github.com/nitoyon/9808563 で試してみる。

作業完了後の diff はこんな感じになる。

$ git diff HEAD~
diff --git a/crlf-test.txt b/crlf-test.txt
index ad9608a..85a3c62 100644
--- a/crlf-test.txt
+++ b/crlf-test.txt
@@ -1,7 +1,7 @@
-End of Line Character Test
-
-Which do you like CR LF or LF?
-
-This file uses "CR LF"!!!!
-
-Clone me!
+End of Line Character Test
+
+Which do you like CR LF or LF?
+
+This file uses "CR LF"!!!!
+
+Clone me!

違いが分かりにくい diff が表示されていて悲しい。git の diff では CR があると ^M として警告してくれるはずなんだけど、^M が表示されるのは + から始まる行だけのようだ。今回のような「CR LF が LF になった」ケースでは CR があるのは - の行なので ^M は表示してくれない…。

--ignore-space-at-eol を指定して行末スペースの変更を無視したら、diff が消え去るので、うまくいっていると信じる。

$ git diff --ignore-space-at-eol HEAD~

git grep でも CR を見つけられなくなるので、うまくいっていると信じる。

$ git grep --cached -I $'\r'

まとめ

Windows はつらいよ。

D3.js で自作クラスにイベント発行機能を追加する

$
0
0

D3.js を使っていると、自作クラスのイベント発行も D3.js を使いたくなる。そんなときには d3.dispatch を使うとよい。

使う側の実装

イメージしやすいように、最初に使う側のコードを示しておく。

var myButton = new MyButton(d3.select("button"));
myButton.on("myclick", function(e) {
  alert(e.name); // MyEvent
  console.log(this, e); // MyButton, { name: "MyEvent", MouseEvent }
});

こんな感じで、自作の MyButton クラスで myclick イベントを発行したい。

MyButton の実装

では、さっそく MyButton の実装。最初はコンストラクターから。

function MyButton(selector) {
  // ボタンがクリックされたときに onClick を呼ぶ
  selector.on('click', this.onClick.bind(this));

  // myclick イベントを発行する dispatcher を作成
  this.dispatcher = d3.dispatch("myclick");
}

d3.dispatch の引数に「発行したいイベント名」を渡して、dispatcher オブジェクトを取得している。複数のイベントを出す場合は d3.dispatch("event1", "event2"); のようにする。

dispatcher オブジェクトには次の 2 種類のメソッドが定義される。

  • イベントを発行するためのメソッド (イベント名と同じ名前)
  • イベントを監視するための on(type, listener)

イベント発行処理

イベントの発行処理からみていこう。

MyButton.prototype.onClick = function() {
  this.dispatcher.myclick.call(this, {
    name: "MyEvent",
    event: d3.event
  });
};

ボタンがクリックされたときに myclick イベントを発行している。今回は myclick イベントと名付けたので、dispatcher.myclick() を実行すれば、myclick イベントが発火する。引数はそのままリスナーに渡される。

Function.call() を使っているのは、リスナー側で thisdispatcher ではなく MyButton にしたいから。

on の実装

次に MyButton.prototype.on() の実装。こちらは、単純に dispatcher.on() に中継している。

MyButton.prototype.on = function() {
  return this.dispatcher.on.apply(this.dispatcher, arguments);
};

単純に中継しているだけなので、もっと簡略化して書けそうである。そう、d3.rebind を使えばね。

function MyButton(selector) {
  selector.on('click', this.onClick.bind(this));
  this.dispatcher = d3.dispatch("myclick");

  // !!! ここが追加 !!!
  d3.rebind(this, this.dispatcher, "on");
}

コンストラクターに 1 行追加したおかげで、MyButton.prototype.on() が不要になった。コードは短くなったが、d3.rebind() の学習コストが増えたので微妙なところではある。

ちなみに、d3.rebind() は 11 行の短い関数。上で手で書いたのとだいたい同じ動作になるのが分かるはず。

d3.rebind = function(target, source) {
  var i = 1, n = arguments.length, method;
  while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
  return target;
};
function d3_rebind(target, source, method) {
  return function() {
    var value = method.apply(source, arguments);
    return value === source ? target : value;
  };
}

これで目的は完遂。めでたし。

完成後の全体のソースコードを貼っておく。

<!DOCTYPE html>
<meta charset="utf-8">
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<body>
<button>click me</button>

<script>
function MyButton(selector) {
  selector.on('click', this.onClick.bind(this));
  this.dispatcher = d3.dispatch("myclick");
  d3.rebind(this, this.dispatcher, "on");
}

MyButton.prototype.onClick = function() {
  this.dispatcher.myclick.call(this, {
    name: "MyEvent",
    event: d3.event
  });
};

var myButton = new MyButton(d3.select("button"));
myButton.on("myclick", function(e) {
  alert(e.name); // MyEvent
  console.log(this, e); // MyButton, { name: "MyEvent", MouseEvent }
});
</script>
</body>

複数イベント登録の罠

D3.js のイベントで厄介な点は、1 つのイベント名に複数のイベントを登録できないところ。新しいイベントを登録したら、古いほうは消される…。DOM イベントと同じ感覚でいると混乱してしまう。

これを回避するには myclick.foomyclick.bar のように optional namespace をつけてイベント登録する必要がある。くわしくは selection.on を参照あれ。

Ruby で HTTPS 接続するときの証明書で悩んだ話

$
0
0

flickr の API が HTTPS のみになるというので、以前作ったスクリプトに手を入れようとしたら無駄に手こずったので、ググって得た知見をメモしておく。

Ruby 1.8 系で証明書を検証しない問題

上記のスクリプトは、さくらのレンタルサーバーにて Ruby で動かしている。

2014 年にどうかという話だけど、Ruby のバージョンは 1.8.7 である・・・。

Net::HTTP で https に接続したら

warning: peer certificate won't be verified in this SSL session

という警告が出てきて困ったので調べてみた。

つかったコードはこんなやつ。

require 'net/https'
https = Net::HTTP.new('www.google.com', 443)
https.use_ssl = true
https.start { |h|
}

警告が出る理由

Ruby 1.8 の時点では上記のようなコードで HTTPS 接続したときには、デフォルトでは証明書を検証しないポリシーになっていた。

    unless @ssl_context.verify_mode
      warn "warning: peer certificate won't be verified in this SSL session"
      @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
    end

(https://github.com/ruby/ruby/blob/v1_8_7/lib/net/http.rb#L563-566 より)

デフォルトで証明書検証しないというのは罠なので、そのことを教えようとしている warning のようだ。

警告を消す方法

上のコードをみたら分かる通り、明示的にポリシーを指定すれば警告は消える。

  • 証明書を検証したい場合: https.verify_mode = OpenSSL::SSL::VERIFY_PEER を追加する
  • 証明書を検証したくない場合: https.verify_mode = OpenSSL::SSL::VERIFY_NONE を追加する

1.9 系で変わってる

ちなみに、「デフォルトでは証明書を検証しない」という挙動は、1.9 系では修正されている

ルート証明書どうするの問題

証明書を検証する設定にしたら、「証明書の検証に失敗したぜ」というような例外がでるようになった。

OpenSSL::SSL::SSLError: SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed

これはどうも、自分の環境にルート証明書が正しく入ってないっぽい。

いかんせんレンタルサーバーなので、何ともできない。仕方がないので、どこかからルート証明書を取ってくることにした。

ググると http://curl.haxx.se/docs/caextract.html が有名っぽい。cURL の人が mozilla.org のデータを PEM 形式に変換してくれているヤツ。

ここにある cacert.pem を拾ってきて、ca_file に cacert.pem のパスを指定すればおしまい。

require 'net/https'
https = Net::HTTP.new('www.google.com', 443)
https.use_ssl = true
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
https.ca_file = '/path/to/cacert.pem'
https.start { |h|
}

まとめ

新しい OS を新規セットアップした場合はたぶんまったく問題にならないであろうことに苦しめられつつ、以上の無駄な知見を得た。

flickr の更新用に使っていた flickraw というライブラリーは verify_modeca_file を指定するオプションがなかったので、「追加してね」という pull request を投げたらマージしてもらえた。めでたし。

あと、Ruby 「warning: peer certificate won’t be verified in this SSL session」 | HAPPY*TRAP が非常に役に立った。ありがたや。

Vue.js が data に渡した値を激しく書き換える件について

$
0
0

最近、JavaScript の MV* フレームワークの中で Vue.js が少しずつ注目を浴びてきているようであります。

そんなわけで、自分も Vue.js (v0.10.5) を触ってみたのですが、data で渡した値を激しく書き換えるところに面食らったので記事にしておきます。

自作クラスのオブジェクトを Vue.js に渡すと壊される

何らかのビジネスロジックを持ったモデルを作って、それを Vue.js のデータバインディングで HTML に反映しようすると破綻します。

簡単な例として、よくある Animal クラスを作ったとしましょう。

function Animal(name) {
  this.name = name;
};
Animal.prototype.say = function() {
  console.log(this.name);
};

var dog = new Animal('dog');
dog.bark(); // I'm a dog

まぁ、当然動きます。

では、dog をデータバインディングで HTML に表示してみます。

<body>
<p>{{animal.name}}</p>
<script src="vue.js"></script>
<script>
function Animal(name) {
  this.name = name;
};
Animal.prototype.bark = function() {
  console.log("I'm a " + this.name);
};

var dog = new Animal('dog');
dog.bark(); // I'm a dog

new Vue({
  el: "body",
  data: { animal: dog }
});
</script>
</body>

期待通り、{{animal.name}} の部分が dog になります。

JavaScript コンソールにて、dog.name = "dog!!!" とすると、HTML は dog!!! に書き換わります。ちゃんと動いてるようにみえますね。

しかし・・・JavaScript コンソールで dog.bark() と入力すると・・・。

さっきまで動いていたコードが動かなくなりました・・・。恐怖!

激しく書き換えられた犬と書き換えられる前の猫

Vue.js では、data に渡した値を書き換えます。激しく書き換えます。

ためしに、dog を表示してみます。

ははは。プロトタイプ (__proto__) が別のオブジェクトになってしまっていますよ。bark() がなくてエラーになるのも無理はありません。

ちなみに、普通にインスタンス化した猫はこうなってますよ。当たり前だけども、bark() あります。

Vue.js さんは、new Vue()data に渡した値を書き換えてしまうのです。恐ろしい子!

なぜにあなたは書き換える?

Vue.js は data に変更が加わったことを検知するために、data の値を書き換えているようです。

たとえば、dog.name というキーは ECMAScript 5 のプロパティーに置き換えられます。get nameset name が定義されてますね。

このようにすることで、dog.name が書き換えられた瞬間に set name が呼ばれるので、Vue.js はデータの書き換えを検知するわけでございます。

じゃ、なんで prototype まで置き換えるのか。

Displaying a List - vue.js によると、ECMAScript 5 ではキーの追加・削除は検知できないので、$add$delete を使ってくれ、ということのようです (ざっくりと日本語訳してみた)。

In ECMAScript 5 there is no way to detect when a new property is added to an Object, or when a property is deleted from an Object. To counter for that, observed objects will be augmented with two methods: $add(key, value) and $delete(key). These methods can be used to add / delete properties from observed objects while triggering the desired View updates.

ECMAScript 5 では、Object に対するプロパティーの追加や削除を検出する方法がないんだぜ。だから、監視対象の Object には $add(key, value) と $delete(key) の 2 つのメソッドを追加するんだよ。このメソッドを使ってプロパティーを追加・削除すると、View への反映をトリガーできるんだぜ。

$add$delete を追加するのには、そういう理由があったわけですね。

さて、自作のクラスを渡せないのは明らかにバグっぽいので修正したいところではあります。

(追記) よくよく公式ドキュメントの Instantiation Options - vue.js を読んでみると

The object must be JSON-compliant (no circular references)

data に渡すオブジェクトは JSON の仕様に従っていて、循環参照してないものにしてね

とあるので、自作クラスのオブジェクトを渡せないのは仕方ないようです。

Array も激しく書き換える

Vue.js が激しく書き換えるのは Object だけではありません。配列も猛烈に書き換えます。

そのことは、ドキュメントの Displaying a List - vue.js からも伝わってきます (先ほどと同じくざっくりと日本語訳してる)。

Under the hood, Vue.js intercepts an observed Array's mutating methods (push(), pop(), shift(), unshift(), splice(), sort() and reverse()) so they will also trigger View updates.

You should avoid directly setting elements of a data-bound Array with indices, because those changes will not be picked up by Vue.js. Instead, use the agumented $set() method.

内部的に、Vue.js は Array の状態を変更するメソッド (push(), pop(), shift(), unshift(), splice(), sort() and reverse()) の呼び出しを監視して、View が更新されるように処理をしてるんだぜ。

インデックスを指定しての値を変更すると Vue.js が検知できないのでやめてほしいよ。その代り、`$set() メソッドってのを用意したから、こっちを使ってほしいんだぜ。

まぁ、こんな感じで、扱うには少し工夫が必要であります。

data に渡した Object が console.log() で見にくい問題の対処方法

data に渡した値を console.log() すると getter, setter の山になるわけで、値がどうなっているかを確認しにくいですね。

これをなんとかするには JSON.stringify を使う回避方法が Instantiation Options - vue.js にて示されております。

var vm = new Vue({
  el: "body",
  data: {
    animal: { name: "foo" }
  }
});

console.log(JSON.stringify(vm.$data));
// {"animal":{"name":"foo"}} 

やや面倒だし、巨大なデータを渡したときは見つけるのが大変そうであります。そんなときは、さらに JSON.parse() で Object に変換すればよい。

面倒だから、Object に復元する関数でも作っておくとよいでしょう。

function deepCopy(o) {
  return JSON.parse(JSON.stringify(o));
}

console.log(deepCopy(vm.$data));

Object.observe() に期待

こんな面倒なことになっているのも、Vue.js が ECMAScript 5 の世界で頑張っているからです。

ECMAScript 6 の Object.observe() が使える世界になれば、Object や Array の置き換えも不要になるし、Vue.js の設計もシンプルになることでしょう。

Vue.jsロードマップ には 0.11 で Object.observe() が使えるなら使うようにする、と書いてあります。

まとめ

  • Vue.js はオブジェクトの監視を行うために、Object のキーをプロパティーに置き換えたり、Array.push() を置き換えたりする
  • この努力は ECMAScript 5 の限界ゆえ
  • ECMAScript 6 で Object.observe() がやってきたら、Vue.js の実装もシンプルになるし、もっと楽な API になる

JavaScript フレームワークがデータバインディングを実現する4通りの手法

$
0
0

最近流行りの JavaScript MV* フレームワークは、どれもデータバインディングをサポートしているが、実現方法はフレームワークによって異なる。

この記事では、各種フレームワークがどのようにモデルの変更を検知しているかを次の 4 つのパターンに分類して紹介する。

  1. モデル クラス方式 (Ember.js、Backbone.js、Ractive.js、Knockout.js など)
  2. 力ずく方式 (AngualrJS)
  3. モデル書き換え方式 (Vue.js)
  4. Object.observe 方式 (Polymer)

パターン名は私が勝手に名づけたものだけど、このへんの雰囲気が理解できれば、フレームワークごとの個性が分かるだろうし、利用イメージもわきやすいんじゃないかと思っている。

1. モデル クラス方式

「モデルとして扱えるのはフレームワークが用意したモデル クラスのインスタンスだけ」という制約を課すのがこの方式。

たとえば、Ember.js では {title: "てっく煮"} という情報をデータバインディングで利用しようと思ったら、次のようにしてモデル クラスのインスタンスを作る必要がある。

var a = Ember.Object.create({title: "てっく煮"})
console.log(a.get('title')); // てっく煮

モデルを変更するには set() メソッドを使う。

a.set('title', '!!!!');
console.log(a.get('title')); // !!!!

当然、フレームワーク側はモデルの値が変更されたことを検知できる。検知したら、あとはフレームワークは新しい値に応じて DOM を書き換えればよい。とてもシンプルな構造だ。

使う側の視点でみると、get()set() を呼ぶのが面倒だし、ラッパーの API は JavaScript の ObjectArray と扱い方が違うし、さらに、既存のモデルがある場合は流用できない (作成・取得・変更処理を全部置き換える必要がある) し・・・、と不便な印象がある。

これ以降の方式は、ネイティブな ObjectArray などをモデルとして扱えるように工夫している。

この方式を採用するフレームワークが多い

現時点においては、この方式を採用するフレームワークが多数派である。

Backbone.jsなどのライブラリのgetter, setterがダサい理由と、その解消方法 - Qiita によると、Backbone.js、Knockout.js、Ractive.js も同じ方式を採用しているらしい。

Backbone.js と Ractive.js は get("hoge")set("hoge", value) という形式。 Knockout.js は少しマシで hoge() で取得し hoge(value) で設定する形式。

2. 力ずく方式

次の方式は AngularJS が採用しているヤツを説明しよう。

AngularJS はネイティブな ObjectArray をモデルとして渡せるし、独自クラスのオブジェクトだって渡せる。

たとえば、モデルが {title: 'てっく煮'} だったとしよう。モデルはネイティブなオブジェクトなので、変更をプッシュ通知で受け取るのは諦めている。

そんな前提なので、AngularJS は 何かあるごとに title の値が変化したかどうかを自力でチェックする。変化してれば DOM を書き換える。オブジェクトの参照が変わってしまうこともあるので、「前回の値」はディープコピーして覚えている。

とても力技なので処理にはオーバーヘッドがある。ソースコードも複雑になっている。

「何かあるごとに」はいつか?

さて、さきほど「何かあるごとに」と書いたが、前回の値と今の値を比較するタイミングはいつだろうか。

正解は「AngularJS 経由で JavaScript のコードを実行したとき」である。

たとえば、ボタンのクリックイベントに関しては <button ng-click="foo()"> のように書くと、AngularJS 経由で foo() が呼ばれる。setTimeout() の処理であれば、$timeout を使えば AngularJS 経由でコールバックが呼ばれる。

コールバックを読んだとき、モデルの値は変更されるかもしれないし、実際には変更されないかもしれない。変更されたか調べるために、呼び終わったあとに、力ずくで調べるのだ。

直接 DOM イベントを監視するようなときには AngularJS を経由させるのは難しい。そういうときには $scope.$apply() に関数を渡すと、関数を呼んだあとに力ずくの比較処理をやってくれる。

AngularJS を使う側の視点からすると、モデルへの変更を HTML に確実に反映させるには、ng-click$timeout$scope.$apply() など使って AngularJS 経由でモデルを変更する必要がある。

力ずく方式のまとめ

  • ObjectArray や独自クラスのインスタンスをモデルにできる
  • 比較処理がトリガーされるかどうかを意識する必要がある
  • 比較処理のオーバーヘッドがある

3. モデル書き換え方式

こちらは Vue.js が採用している方式である。

Vue.js もネイティブな ObjectArray をモデルとして渡せる。

たとえば、オブジェクトのキーの変更を検知するために、キーを (ECMAScript 5 的な) プロパティーに書き換える。配列の変更を検知するために、Array.prototype を書き換えて push() メソッドなどを置き換える。

AngularJS のように力ずくの比較は行わないので動作は速いのが利点。Vue.js のサイトでも 他のフレームワークより速いこと を自慢している。

一方の弱点は、次の通り。

  • モデルとして受け取ったオブジェクトを書き換えてしまう
  • 完全に変更を検知できない (完全に検知できないので、一部の変更処理については、$set()$add() といったメソッドを使う必要がある。ラッパー オブジェクト的な要素が残っているといえる)。

詳しくは Vue.js が data に渡した値を激しく書き換える件について に書いたので見てほしい。

4. Object.observe() 方式

現在、ECMAScript には「オブジェクトの変更を検知する」という機能を持った Object.observe() というメソッドが提案されていて、Google Chrome 36 ではデフォルトで有効 になるらしい。

まさにデータバインディングで使ってくれといっているようなメソッドである。

PolymerObject.observe() を前提としている。さらに、先ほどから紹介していた各種ライブラリーについても、AngularJS は 2.0 での対応を検討している し、Vue.js は v0.11.x で対応予定 となっている。

ECMAScript に Object.observe() が取り込まれれば、ここまで紹介したようなややこしいデータバインディングの仕組みは不要となる。まさに、データバインディングのための API であるが、ECMAScript 6 にも入っておらず、すべてのブラウザーで使えるようになるには時間がかかりそうだ。 Object.observe() については以下のページが詳しかった。

まとめ

MV* フレームワークが「どのようにモデルの変更を検知しているか」を 4 通り紹介した。

  • OOP 的なモデル クラスを使う方式が主流。
  • AngularJSVue.js はネイティブな値をモデルとして扱うために頑張っている。
  • Object.observe() が使えるようになれば、フレームワークの苦しみが減る。Polymer はそこを見据えている。

Object.observe の死 (ECMAScript の提案取り下げ、V8 からも削除予定)

$
0
0

1年前の記事 JavaScript フレームワークがデータバインディングを実現する4通りの手法 では、Object.observe() について次のように説明した。

  • JavaScript オブジェクトが変更されたときにコールバックを呼んでくれる API
  • データバインディングの実装が簡単になる
  • Google Chrome には実装済み
  • ECMAScript 7 に提案中
  • 提案が通れば MV* フレームワークの実装がシンプルになってハッピー

将来を期待されていた Object.observe() であったが、2015 年 11 月頭、ES Discuss メーリングリストへの An update on Object.observe という投稿で、ECMAScript からの提案が取り下げられて、V8 エンジンからも年内に削除される予定であることが表明された。

Object.observe() に何があったのか

メーリングリストへの投稿をざっくりと訳してみた。

3年前、Rafael Weinstein、Erik Arvidsson、私 (Adam Klein) で、データバインディングを実現するための API を設計し始めた。V8 上のブランチでプロトタイプを作成したあと、V8 チームに正式版を開発することを認めてもらった。さらに、Object.observe (以下、"O.o" と表記) を ES7 標準に提案しつつ、Polymer チームと協力して O.o を使ってデータバインディングを実装した。

3年後、世界は大きく変わった。Ember や Angular といったフレームワークは O.o に興味を示したものの、既存のモデルを O.o のモデルに発展させるのは難しかった。Polymer は 1.0 をリリースするにあたりゼロから書き直されたが、そこでは O.o は使われなかった。さらに、React のような、データバインディングでミュータブルなものを避けようとする処理モデルが人気を博している。

関係者との議論の末、Object.observe の提案を TC39 (現在、ES 仕様策定のステージ 2) から取り下げることにした。また、今年の終わりまでには V8 でのサポートを終了したいと考えている (chromestatus.com によると、Chrome がアクセスしたページのうち 0.0169% でしか O.o は利用されていない)。

O.o を使っていた開発者は、object-observe のような polyfill や observe-js のようなラッパーライブラリーを使うことを検討してほしい。

Object.observe() は他のフレームワークで使われることもなかったし、Polymer でも使われなくなってしまったので、ECMAScript への提案を取り下げる、ということのようだ。

Polymer 1.0 のデータバインディング実現方法

Polymer が Object.observe() を使わなくなった理由については、Polymer の開発にも関わっている Brian Chin さんから スレッド 内に次のようなコメントがでている

O.o はプロパティーを変更したあと、非同期でコールバックが呼ばれるのがイケてなかった。Polymer 1.0 ではプロパティーを変更したら、すぐに変更が UI に反映されるので、利用者にも分かりやすくなっている。

モデルを書きかえた瞬間に、UI に反映されるようにしたかった、というのがメインの理由のようだ。

「じゃ、Polymer は Object.observe() の代わりに何を使ってるの?」という質問に対しては、次のような回答が出ている。

getter/setter を定義して、DOM イベントで伝搬させてるよ。詳しくは polymer-project.org を見てね。たとえば ここ とか ここ だよ

ざっと上記のページを見た感じでは、次のような感じでデータバインディングに使うプロパティーを宣言しておくらしい。

  Polymer({
    is: 'custom-element',
    properties: {
      someProp: {
        type: String,
        notify: true
      }
    }
  });

スレッドには、他にも「ブラウザーでは使われないかもしれないけど、Node.js のエコシステムには大きな影響があるかもしれないから、そんなに急に削除しないでくれ」と Node.js の中の人が書き込んでいたり、「Firefox の Object.watch() はデバッガーで使われてるからすぐに削除されることはないはず」といった書き込みがあったりして興味深い。

まとめ

Object.observe() は ECMAScript 7 に入ることはなくなったので、他のブラウザーに 実装される可能性はなくなったし、Google Chrome でも近い将来、使えなくなるだろう。


Visual Studio Code は JavaScript 開発が超絶便利になる可能性を秘めている!

$
0
0

クロスプラットフォームでオープンソースな IDE 環境、Visual Studio Code が公開されたので試してみた。

拡張を入れなくても、デフォルトで JavaScript の「自動 Lint」「Grunt、Gulp 連携」「デバッグ」が動いた。なんだかすごく便利そうな予感。

Windows 環境で起動してみたらこんな画面だった。

なんか黒いが、色は好みにカスタマイズできるし、プリセットからも選べる。

フォルダーを開くことから始まる

Visual Studio Code にはプロジェクトの概念はない。

[File] > [Open Folder] からフォルダーを開けばよい。

ためしに、過去に作った Node.js 製の livereloadx のフォルダーを開いてみた。

左側にツリーが表示されている。プロジェクト内のファイルを開くにはツリー上でクリックしてもいいし、Ctrl+P を押せば、ファイル名でインクリメンタル検索できる (上の図では fi と入力しているので、filter.jsconfig.js が引っかかってる)。

ファイル内の文字列で検索するには、左の虫眼鏡を押すか、Ctrl+Shift+F から。

自動 Lint

左下の警告マークのところに 4 と出てるのでクリックしてみると、Buffer とか __dirname が見つからないといわれた。

警告をクリックして、電球マークをクリックすると、「node.d.ts をダウンロードする」というオプションがでてきた。

d.ts は TypeScript の型定義ファイル。これの Node.js 用を自動的に落とせるようだ。

落とすと警告は減った。が・・・本当に対処したほうがいいやつが出てきた・・・。

ESLint と JSHint に対応しているらしい。

Git 連携!

コードを修正すると、左側の Git マークのところに数字が!

Git マークを押すと、最後のコミットから変更されたファイルが出てくる。git status の GUI 版みたいな感じ。

ファイルを選択すると差分が分かるし、+マークを押すと git add するし、矢印を押すと変更を破棄できる。上の部分にメッセージを書くとコミットできる。

Git フレンドリー!

Grunt, Gulp 連携

Ctrl+Shift+T を押すと、何も設定してないのに単体テストが走った!!

右側に単体テストの結果が表示されている。

理由は Gruntfile.jstest というタスクが登録してあるから。Visual Studio Code さんは、Grunt や Gulp、Jake のタスクを自動的に解釈してくれているのだ。

他にも build というタスクがあれば、Ctrl+Shift+B でビルドが走るらしい。カッコイイ。

もちろん、それ以外のタスクも開始できる。

F1 から Run Task を入力すると、実行できるタスクの一覧が出てくる。ためしに、watch タスクを選択すると、IDE 内において「編集したら、Lint して単体テスト、結果を表示」というフローが実現できた。カッコイイ。

デバッグ!!!

コードの適当なところに F9 でブレークポイントを設定して、F5 でデバッグを開始してみる。

「Node.js と Mono のどっちをデバッグする?」と聞かれた。拡張を入れたら他の言語もデバッグできるらしい。

ここでは Node.js を選ぶ。

.vscode/launch.json が開くので、ここで何をデバッグするか設定する。

program のところで開始する JS ファイルを指定してみた。

再度、F5 でデバッグを始める。

ブレークポイントで止まった。

左上の Variables にはローカル変数やクロージャー内の変数の値が出てくる。左下にはスタックトレースがある。

F10 でステップオーバー、F11 でステップイン、Shift+F11 でステップアウトできるのは Visual Studio と同じ。

他にも機能はたくさん

まだまだ確認しきれていないが、機能はたくさんあるようだ。

  • TypeScript なら変数名の変更やシンボル名での検索にも対応してるらしい。
  • コードスニペットは、特定の文字を入力して、Tab を押したら定型文を挿入してくれるので便利そう。
  • CSS (Sass, Less) にも対応してる。Lint するし、Ctrl+Shift+O でシンボルに飛べる。
  • この記事の Markdown を Visual Studio Code で書いているが、Ctrl+V でプレビューできる。

言語ごとに何ができるかは、ドキュメント の LANGUAGES の中をみると一通り書いてある。

エディターとしての機能はまだ確認しきれていない。Windows なら日本語は問題なく通る。折り返しできない (?) のが、今この記事を書いていて不便に感じる。動作はサクサクしている。

(追記) Workspace Settings から "editor.wrappingColumn" の値を変えたら、折り返し幅も変わった。

雑感

ここまでの機能がデフォルトで有効になっている、というのはなかなかの衝撃であった。

ほめちぎりすぎたので、最後に悪いところも書いておく。

現状の Visual Studio Code は、メインメニューやコンテキストメニューが状況に応じて変化しない。便利な機能を使うためには、ショートカットキーを覚えるのが前提になっている。ドキュメントを読まないと何ができるのか見当つかないし、ショートカットを覚えるコストも高い。

公式のドキュメントはよくできているので、一通り読めば分かるんだけど、言語ごとに使える機能に違いがあるので、そのあたりもややこしい。

自分は Visual Studio を普段使いしてるので、慣れてるショートカットキーがそのまま使えて、手触りはとてもよかった。

Go 言語でソースコードから画像生成する

$
0
0

Go 言語には画像生成する image パッケージが標準で入っている。imagemagick や GD を導入する必要がないので、気軽に画像を生成できて便利そうだったので試してみた。

ただ、標準ではピクセル単位で色を設定することしかできないので、線を引いたり色を塗ったりするには、何らかのライブラリーに頼る必要がある。

今回は、ライブラリーには頼らず、標準で提供されている機能だけでできることを試してみた。

一番簡単な例

簡単な画像を生成する例は次の通り。1つ点を打つだけの例。

package main

import "image"
import "image/color"
import "image/png"
import "os"

func main() {
    // 100×50 の画像を作成する
    img := image.NewRGBA(image.Rect(0, 0, 100, 50))

    // (2, 3) に赤い点をうつ
    img.Set(2, 3, color.RGBA{255, 0, 0, 255})

    // out.png に保存する
    f, _ := os.OpenFile("out.png", os.O_WRONLY|os.O_CREATE, 0600)
    defer f.Close()
    png.Encode(f, img)
}

もっと複雑な例

こんな画像を生成してみる。

コードはこうなった。

package main

import (
    "fmt"
    "image"
    "image/color"
    "image/png"
    "math"
    "os"
)

type Circle struct {
    X, Y, R float64
}

func (c *Circle) Brightness(x, y float64) uint8 {
    var dx, dy float64 = c.X - x, c.Y - y
    d := math.Sqrt(dx*dx+dy*dy) / c.R
    if d > 1 {
        return 0
    } else {
        return 255
    }
}

func main() {
    var w, h int = 280, 240
    var hw, hh float64 = float64(w / 2), float64(h / 2)
    r := 40.0
    θ := 2 * math.Pi / 3
    cr := &Circle{hw - r*math.Sin(0), hh - r*math.Cos(0), 60}
    cg := &Circle{hw - r*math.Sin(θ), hh - r*math.Cos(θ), 60}
    cb := &Circle{hw - r*math.Sin(-θ), hh - r*math.Cos(-θ), 60}

    m := image.NewRGBA(image.Rect(0, 0, w, h))
    for x := 0; x < w; x++ {
        for y := 0; y < h; y++ {
            c := color.RGBA{
                cr.Brightness(float64(x), float64(y)),
                cg.Brightness(float64(x), float64(y)),
                cb.Brightness(float64(x), float64(y)),
                255,
            }
            m.Set(x, y, c)
        }
    }

    f, err := os.OpenFile("rgb.png", os.O_WRONLY|os.O_CREATE, 0600)
    if err != nil {
        fmt.Println(err)
        return
    }
    defer f.Close()
    png.Encode(f, m)
}

Circle 構造体を定義して、円の中に入っているかどうかを判定する処理をメソッドとして定義している。

ちょっとしたテクニックとして、色を決定する部分は次のようにしている。

c := color.RGBA{
    cr.Brightness(float64(x), float64(y)),
    cg.Brightness(float64(x), float64(y)),
    cb.Brightness(float64(x), float64(y)),
    255,
}

赤い色の中のときは赤色成分は 255、外のときは 0 としている。他の成分も同じ。

周辺をぼかしてみる

色がくっきりしすぎているので、画像の周りをぼかしてみた。

円の中のときに return 255 としていた部分を書きかえるだけでできた。

func (c *Circle) Brightness(x, y float64) uint8 {
    var dx, dy float64 = c.X - x, c.Y - y
    d := math.Sqrt(dx*dx+dy*dy) / c.R
    if d > 1 {
        // 円の外のとき
        return 0
    } else {
        // 円の中のとき
        return uint8((1 - math.Pow(d, 5)) * 255)
    }
}

math.Pow で 5 乗しているのは、ぼけすぎないようにするための工夫。

Go 言語でアニメーション GIF を作成する

$
0
0

Golang でアニメーション GIF を作る手順を 3 通り紹介します。

  • フレームごとの画像から生成
  • ビデオから生成
  • Go 言語で最初から生成

フレームごとの画像から生成

こんな GIF 画像があったとします (ここ より拝借)。

変換結果はこんな感じ。

生成するためのコードはこんな感じ。

package main

import "image"
import "image/gif"
import "os"

func main() {
    files := []string{"g1.gif", "g2.gif","g3.gif", "g2.gif"}

    // 各フレームの画像を GIF で読み込んで outGif を構築する
    outGif := &gif.GIF{}
    for _, name := range files {
        f, _ := os.Open(name)
        inGif, _ := gif.Decode(f)
        f.Close()

        outGif.Image = append(outGif.Image, inGif.(*image.Paletted))
        outGif.Delay = append(outGif.Delay, 0)
    }

    // out.gif に保存する
    f, _ := os.OpenFile("out.gif", os.O_WRONLY|os.O_CREATE, 0600)
    defer f.Close()
    gif.EncodeAll(f, outGif)
}

注意したいポイントは次の通り。

  • フレームの GIF を順番に gif.Decode で読み込んでいる。JPEG から生成するには、GIF への変換処理を実装する必要がある (goanigiffy では gif.Encodegif.Decode を呼んで変換している)。
  • GIF アニメーションを生成するには gif.EncodeAll を呼ぶ。

ビデオから生成

MPlayer を使って各フレームの画像を抽出してから、goanigiffy で GIF アニメーションを生成する (詳しくは GoAniGiffy を参照)。

Go 言語で最初から生成

こんな感じのものを作ってみた。

各フレームの画像を Go 言語で描画して []*image.Paletted を作って、gif.EncodeAll に渡している。

    var images []*image.Paletted
    var delays []int

    // 20 個の画像を生成して円を描く
    for step := 0; step < 20; step++ {
        img := image.NewPaletted(image.Rect(0, 0, w, h), palette)
        images = append(images, img)
        delays = append(delays, 0)

        // 描画処理 (長いので省略)
    }

    // rgb.gif に保存する
    f, _ := os.OpenFile("rgb.gif", os.O_WRONLY|os.O_CREATE, 0600)
    defer f.Close()
    gif.EncodeAll(f, &gif.GIF{
        Image: images,
        Delay: delays,
    })

全体のコードはこんな感じ。

package main

import (
    "image"
    "image/color"
    "image/gif"
    "math"
    "os"
)

type Circle struct {
    X, Y, R float64
}

func (c *Circle) Brightness(x, y float64) uint8 {
    var dx, dy float64 = c.X - x, c.Y - y
    d := math.Sqrt(dx*dx+dy*dy) / c.R
    if d > 1 {
        return 0
    } else {
        return 255
    }
}

func main() {
    var w, h int = 240, 240
    var hw, hh float64 = float64(w / 2), float64(h / 2)
    circles := []*Circle{&Circle{}, &Circle{}, &Circle{}}

    var palette = []color.Color{
        color.RGBA{0x00, 0x00, 0x00, 0xff},
        color.RGBA{0x00, 0x00, 0xff, 0xff},
        color.RGBA{0x00, 0xff, 0x00, 0xff},
        color.RGBA{0x00, 0xff, 0xff, 0xff},
        color.RGBA{0xff, 0x00, 0x00, 0xff},
        color.RGBA{0xff, 0x00, 0xff, 0xff},
        color.RGBA{0xff, 0xff, 0x00, 0xff},
        color.RGBA{0xff, 0xff, 0xff, 0xff},
    }

    var images []*image.Paletted
    var delays []int
    steps := 20
    for step := 0; step < steps; step++ {
        img := image.NewPaletted(image.Rect(0, 0, w, h), palette)
        images = append(images, img)
        delays = append(delays, 0)

        θ := 2.0 * math.Pi / float64(steps) * float64(step)
        for i, circle := range circles {
            θ0 := 2 * math.Pi / 3 * float64(i)
            circle.X = hw - 40*math.Sin(θ0) - 20*math.Sin(θ0+θ)
            circle.Y = hh - 40*math.Cos(θ0) - 20*math.Cos(θ0+θ)
            circle.R = 50
        }

        for x := 0; x < w; x++ {
            for y := 0; y < h; y++ {
                img.Set(x, y, color.RGBA{
                    circles[0].Brightness(float64(x), float64(y)),
                    circles[1].Brightness(float64(x), float64(y)),
                    circles[2].Brightness(float64(x), float64(y)),
                    255,
                })
            }
        }
    }

    f, _ := os.OpenFile("rgb.gif", os.O_WRONLY|os.O_CREATE, 0600)
    defer f.Close()
    gif.EncodeAll(f, &gif.GIF{
        Image: images,
        Delay: delays,
    })
}

以上です。

Go 言語で宇宙旅行風のアニメーション GIF を作った

$
0
0

宇宙旅行風のアニメーション GIF を Golang で生成してみました。完成品はこちら。

ソースコードはこの記事の末尾に掲載しています。以下では使ったライブラリーやテクニックを簡単に説明します。

draw2d を使って描画する

Golang の標準ライブラリーだけでは複雑な図形を描画するのは難しいので、draw2d を使ってみることにした。こいつを使えば、線とか弧とかベジェ曲線を描けるし、線の色や塗る色も設定できる。

次のコードでは、draw2dimgdraw2dkit を使って、#808080 の四角を描画する例。

package main

import (
    "github.com/llgcode/draw2d/draw2dimg"
    "github.com/llgcode/draw2d/draw2dkit"
    "image"
    "image/color"
)

func main() {
    img := image.NewRGBA(image.Rect(0, 0, 200, 200))
    gc := draw2dimg.NewGraphicContext(img)

    // Draw rectangle (#808080)
    gc.SetFillColor(color.Gray{0x80})
    draw2dkit.Rectangle(gc, 50, 50, 100, 100)
    gc.Fill()
    gc.Close()
}

draw2dimg.NewGraphicContext は引数に image.RGBA (透明度つきの RGB 画像) を渡す必要があるんだけど、アニメーション GIF を gif.EncodeAll で作るときには image.Palettted (パレットの色だけを使った画像) を渡さなきゃいけない。

つまり、draw2d でアニメーション GIF を作るには、次のような処理が必要になる。

  1. iamge.RBGA を作る
  2. draw2d を使って描画する
  3. image.RBGAimage.Paletted に変換する
  4. gif.EncodeAll[]*image.Paletted を渡して、アニメーション GIF を作る

image.RBGAimage.Paletted に変換する方法

1 枚の GIF を生成する gif.Encode は自動的に image.RBGAimage.Paletted に変換するんだけど、アニメーション GIF を生成する gif.EncodeAll は変換してくれない。

なので、自分で変換処理を実装する必要がある。といっても、gif.Encode と同じように、標準ライブラリで用意された draw.FloydSteinberg を使って フロイド-スタインバーグ・ディザリング を使うと簡単。こんな風に。

package main

import (
    "image"
    "image/color"
    "image/draw"
)

func main() {
    img := image.NewRGBA(image.Rect(0, 0, 200, 200))

    // パレットを準備 (#ffffff, #000000, #ff0000)
    var palette color.Palette = color.Palette{}
    palette = append(palette, color.White)
    palette = append(palette, color.Black)
    palette = append(palette, color.RGBA{0xff, 0x00, 0x00, 0xff})

    // ディザリングする
    pm := image.NewPaletted(img.Bounds(), palette)
    draw.FloydSteinberg.Draw(pm, img.Bounds(), img, image.ZP)
}

ソースコード全体

全部で 100 行になってます。

package main

import (
    "github.com/llgcode/draw2d/draw2dimg"
    "github.com/llgcode/draw2d/draw2dkit"
    "image"
    "image/color"
    "image/draw"
    "image/gif"
    "math"
    "math/rand"
    "os"
)

var w, h float64 = 500, 250
var palette color.Palette = color.Palette{}
var zCycle float64 = 8
var zMin, zMax float64 = 1, 15

type Point struct {
    X, Y float64
}

type Circle struct {
    X, Y, Z, R float64
}

// ループするように星を描画する
func (c *Circle) Draw(gc *draw2dimg.GraphicContext, ratio float64) {
    z := c.Z - ratio*zCycle

    for z < zMax {
        if z >= zMin {
            x, y, r := c.X/z, c.Y/z, c.R/z
            gc.SetFillColor(color.White)
            gc.Fill()
            draw2dkit.Circle(gc, w/2+x, h/2+y, r)
            gc.Close()
        }
        z += zCycle
    }
}

func drawFrame(circles []Circle, ratio float64) *image.Paletted {
    img := image.NewRGBA(image.Rect(0, 0, int(w), int(h)))
    gc := draw2dimg.NewGraphicContext(img)

    // 背景を描画
    gc.SetFillColor(color.Gray{0x11})
    draw2dkit.Rectangle(gc, 0, 0, w, h)
    gc.Fill()
    gc.Close()

    // 星を描画
    for _, circle := range circles {
        circle.Draw(gc, ratio)
    }

    // ディザリングする
    pm := image.NewPaletted(img.Bounds(), palette)
    draw.FloydSteinberg.Draw(pm, img.Bounds(), img, image.ZP)
    return pm
}

func main() {
    // 4000 個の星を準備
    circles := []Circle{}
    for len(circles) < 4000 {
        x, y := rand.Float64()*8-4, rand.Float64()*8-4
        if math.Abs(x) < 0.5 && math.Abs(y) < 0.5 {
            continue
        }
        z := rand.Float64() * zCycle
        circles = append(circles, Circle{x * w, y * h, z, 5})
    }

    // パレットを準備 (#000000, #111111, ..., #ffffff)
    palette = color.Palette{}
    for i := 0; i < 16; i++ {
        palette = append(palette, color.Gray{uint8(i) * 0x11})
    }

    // 30 個の画像を作成
    var images []*image.Paletted
    var delays []int
    count := 30
    for i := 0; i < count; i++ {
        pm := drawFrame(circles, float64(i)/float64(count))
        images = append(images, pm)
        delays = append(delays, 4)
    }

    // gif を出力
    f, _ := os.OpenFile("space.gif", os.O_WRONLY|os.O_CREATE, 0600)
    defer f.Close()
    gif.EncodeAll(f, &gif.GIF{
        Image: images,
        Delay: delays,
    })
}

小学一年生向けの「けいさんカード」アプリを作った話

$
0
0

子どもが小学校に入学して、毎日、ちょっとした宿題が出るわけですが、その中でちょっと手間がかかったのが 計算カードの宿題 でした。

けいさんカード (実物)

こんな感じの計算カードの中から親が問題を選んで、子どもに答えていってもらう・・・という宿題です。

どこが面倒なのか

カードをあらかじめシャッフルしておくと、毎回同じ問題の並び順になってしまいます。

毎回シャッフルするのは面倒なので、適当にめくりながら出題してると、簡単な問題ばかり出たり、すでに出した問題が出てきたり・・・。

カードを使わずに自分で出題していくと、同じ問題を何度も出してしまったり、答えてもらったあとに自分が出した問題を覚えてなかったり・・・。

アプリを探してみたけど見つからず

面倒を解消すべく、よさそうなアプリを探してみました。

小学生の勉強をトータルでサポートするような本格的なものだったり、1年生の範囲を超えた問題も出てくるアプリだったり・・・はあったのですが、手ごろなものは見つかりませんでした。

ないならば作ってみよう

そんなわけで、ないならば自分で作ってしまうことにしました。

けいさんカードアプリ からどうぞ。

タイトル画面
https://nitoyon.github.io/1-10calc/

Web で作ったので iPhone, iPad, Android, PC のどれでも動きます。

アプリのように使いたければ、ホーム画面に追加 (Android ならショートカットを作成) しておくのがオススメです。

使いかた

たとえば、「たしざん 1」 (10 までの足し算) を選ぶと、こんな感じの画面が出てきます。

問題

正解を選ぶとと、ほめられます。

正解!!

すぐに反応があるのが面白くて、子どもは勝手にどんどん解いていってくれます。

同じ問題は出てこないし、開くたびに問題が出る順番は変わります。

終わったら結果を確認してみよう

[←] で戻って、下の [勉強結果を見る] を選ぶと、その日の子どもの勉強の成果を確認できます。

勉強結果

毎日やってるうちに、解くまでの時間が短くなったり、間違えが減ったりして、子どもの成長を実感できます。

日ごとのグラフを表示できたらカッコイイでしょうけど、さすがにそこまでは作っていません。

子どもの反応は・・・

子どもに渡すと、し操作に戸惑う様子もなく 1 人でどんどん解いていきました。スマホ ネイティブ世代・・・おそるべし。 スマホ育児は避けてきたので、合法的に親のスマホで遊べるのもうれしかったようです。

何日か繰り返すうちに飽きてはきましたが、それでもサクッと宿題が終わるのが最高です。親の手間も大幅に減っています。やるほうもやらせるほうも気軽になりました。

さらに、アプリに興味を持った下の子が、両手を使って指折り数えながら「たしざん」の問題に回答していく様子はとてもかわいらしかったです。6 以上の足し算がでてくると、周りに助けを求めていたのですが・・・。

開発の裏側

素材については、Font Awesome と絵文字を活用しました。この辺の素材を使えば、気軽にリッチっぽい見た目になるのが便利な時代であります。絵文字を使うと、機種や端末によって印象が変わってしまうのは悲しいところですが。

ライブラリーは jQuery を使っていたのですが、HTML の生成処理が面倒になってきたので、Vue.js 3 で作りなおしてみました。初めての Vue.js で戸惑ったものの、公式のチュートリアルがよくできていたので助かりました。

まとめ

子育ての面倒なところを、自分が持っている技術を使って解決した話でした。

もう一度 URL を載せておきます。https://nitoyon.github.io/1-10calc/ です。どうぞご利用ください。

Viewing all 42 articles
Browse latest View live