DockerでWindowsコンテナを起動する為に必要な設定

Docker for Windows では、 Windows コンテナが起動できるようになっています。

開発者に朗報! Windows 10でWindows Serverコンテナが実行可能に (1/2):企業ユーザーに贈るWindows 10への乗り換え案内(42) - @IT

https://hub.docker.com/_/microsoft-windows

ただ、私の環境では公式のドキュメントに書いてある通りに docker run mcr.microsoft.com/windows:1903 を実行すると no matching manifest for unknown in the manifest list entries. と表示され起動できない。

Windows コンテナは Docker を Windows コンテナ用に設定する必要があるみたい。

常駐タスクアイコンの中から、 docker for windows を右クリックして Switch to windows containers を選択しておくこと。

あと、 エクスペリメンタル機能を有効にしておく必要がある。

同じように docker for windows のアイコンを右クリックして Settings から Daemon を選択して Experimental features のチェックを入れる。

いろんなサイトではこれで有効になるようだが、私の環境では無理だった。

私の場合は、エクスペリメンタル機能を有効にするために、%USERPROFILE% の下にある .docker/config.json の中に "experimental": "enabled" を追加した。

あと、Windows コンテナは ホストの Windows OS と同じ環境で動作するため ホストのOSをゲストのOSと同じにしておく必要があるようだ。

私の環境は WIndows 10 Pro 1803 だったため、ちゃんと起動できなかった。 Windows Update を行って Windows 10 Pro 1903 にしておく必要がある。

これらをすべて実行し、 docker for windows を再起動したのち、再度 docker run mcr.microsoft.com/windows:1903 を実行すると実行できる。

ただ、実行してもすぐに終了してしまうので、以下のようにインタラクティブモードにしたほうがいい。

docker run -it --name wincont01 --isolation=hyperv mcr.microsoft.com/windows:1903 cmd

空白区切りの文字列をそれぞれのワードで括る簡単なやり方

vim-jp の slack で以下のような質問をした

f:id:bamch0h:20190709220350p:plain

すると、すぐに数人の方からレスポンスをいただいた。

置換を使う方法や

f:id:bamch0h:20190709220716p:plain

ciw と ドットオペレーションと組み合わせてやる方法

f:id:bamch0h:20190709220758p:plain

マクロを使うことを提案していただいたりもした。

f:id:bamch0h:20190709220822p:plain

置換を使う方法で、 & が 使われていて、私はこの記号を使ったことがなかったので勉強になった。おそらく、 検索文字全体を表す記号 なんだとおもう。

また、ciw と ドットオペレーションを組わせてやる方法では、 <C-R><C-O>レジスタの中身を挿入することでドットオペレーションで繰り返しても問題なく動作するようにするテクニックが使われており、色々と勉強になった。

ciwとドットオペレーションの繰り返しでは、<C-R> でやる方法ではうまくいかない。

f:id:bamch0h:20190709222515p:plain

理由は以下の通り、<C-R> ではドットレジスタに挿入後の値が入ってしまうため、繰り返しても期待した動作にならない。 <C-R><C-O> は ドットレジスタ<C-R><C-O がそのまま積まれるので、繰り返しても都度レジスタの内容が展開され、問題なく動作するということのようだ。

f:id:bamch0h:20190709222751p:plain

結論

Vim は奥が深い!!

vaffle.vim から netrw にお試しで移行してみた

今まで、vimのファイラとして vaffle.vim を使ってきた。正確な使用期間は思い出せないが私がvimを使い始めてから結構な期間使っていたと思う。

github.com

ただ、vaffle.vim は buftype を nofile に設定している関係上、 grep をしたときに閉じずに残ってしまうという現象が発生する。これは、dirvish でも同じ現象になるので、vaffle.vim だけの問題ではなさそうだった。自前でファイラを作ってしまおうか?とも思ったが、調べていく途中で、netrwでは発生しない問題だということに気いたので、netrwをカスタマイズして使えるようにすれば解決するのではないか?という思いから、vaffle.vim をやめてnetrw に移行することにした。

最近、Vim界隈で有名なゴリラさんもnetrwをお勧めされているようで、以下に記事がある。それを参考にしつつ私のvimrcもいじってみた。

qiita.com

以下が私のnetrwの設定である。

" バナーを非表示
let g:netrw_banner=0

" ファイル/ディレクトリ名のみ表示(日付等は表示しない)
let g:netrw_liststyle=0

" プレビューは縦分割する(使うことはあまりなさそう?)
let g:netrw_preview=1

" デフォルト表示をドットファイル非表示に設定
let g:netrw_hide=1

" g:netrw_hide で非表示にするファイルの設定.
" これがないとちゃんと非表示にならない
let g:netrw_list_hide='\(^\|\s\s\)\zs\.\S\+'

augroup NetrwKeyMap
    au!

   " 親ディレクトリに移動する (h)
    au FileType netrw nmap <buffer> h -

   " ファイルを開く、ディレクトリ移動 (l)
    au FileType netrw nmap <buffer> l <CR>

   " ウィンドウ移動 (s) // デフォルト動作はソートの種類変更
   " (あまりつかわないかなぁという理由でつぶす)
    au FileType netrw nmap <buffer> s <C-W>

   " ドットファイルの表示/非表示切り替え
   " vaffle.vim に挙動を合わせる
    au FileType netrw nmap <buffer> . gh
augroup END

とりあえずはこの設定でやってみようと思う。懸念点としては、今まで i でファイルを作成していたがそれができなくなったことくらいかなぁと思っている。netrwのいい点は、デフォルトで x で関連付けられたプログラムでファイルを表示することができることや、 c で表示中のディレクトリをカレントディレクトリにすることができること。vaffle.vim ではシンプルさを追求しているため、このあたりの動作は実装されていなかったので自前で拡張する必要があったが、netrwはその必要がないので、vimrcがすっきりしそうだなぁという感覚がある。

vim の :rubyfile は rb_load を使うが ruby の load は rb_f_load を使う

事の発端

if_ruby を使って何かしたいなぁということで :rubyfile test.rb を実行したが、LoadError: cannot load such file -- test.rb となって実行できなかった。ただ、:ruby load "test.rb" は実行できるので、何が違うのか?という疑問が事の発端

vim-jp の slack で質問

slack で質問してみると :rubyfilerubyの公開APIrb_load() を呼んでいるだけとのこと。また、ruby loadKernel.#load‘ を使用しているが、なぜ:rubyfile` のほうが動かないのかはわからないとのことで原因を追究してみたくなった。

f:id:bamch0h:20190620222035p:plain

Kernel.#load は rb_load を実行しているのか?

2.6のブランチの load.c を見ると、 loadrb_f_load() をコールしているようです。

https://github.com/ruby/ruby/blob/ruby_2_6/load.c#L1233

rb_loadload.cに実装があり、単に rb_load_internal() をコールしているのみとなっているようです。

https://github.com/ruby/ruby/blob/ruby_2_6/load.c#L659

rb_f_load() の中でも rb_load_internal() をコールしていますが、前処理がいくつか入っているため、その部分に今回の原因がありそう?

rb_f_load の前処理を追う

追うといっても前処理で生成されている文字列を一個一個printfで出力してみるというだけのことをしました。

static VALUE
rb_f_load(int argc, VALUE *argv)
{
    printf("rb_f_load start\n"); // 追加
    VALUE fname, wrap, path, orig_fname;

    rb_scan_args(argc, argv, "11", &fname, &wrap);

    printf("fname(1):  %s\n", StringValueCStr(fname)); // 追加

    file_to_load(fname);

    orig_fname = rb_get_path_check_to_string(fname, rb_safe_level());
    printf("orig_fname:%s\n", StringValueCStr(orig_fname)); // 追加
    fname = rb_str_encode_ospath(orig_fname);
    printf("fname(2):  %s\n", StringValueCStr(fname)); // 追加
    RUBY_DTRACE_HOOK(LOAD_ENTRY, RSTRING_PTR(orig_fname));

    path = rb_find_file(fname);
    if (!path) {
        printf("path is null?? \n"); // 追加
        if (!rb_file_load_ok(RSTRING_PTR(fname)))
            load_failed(orig_fname);
        path = fname;
    }
    printf("path:      %s\n", StringValueCStr(path)); // 追加
    printf("wrap:      %d\n", RTEST(wrap)); // 追加
    rb_load_internal(path, RTEST(wrap));

    RUBY_DTRACE_HOOK(LOAD_RETURN, RSTRING_PTR(orig_fname));

    printf("rb_f_load start\n"); // 追加

    return Qtrue;
}

結果的には、load "foo.rb" のようなスクリプトで文字列が変化することはなく。rb_load_internal() には foo.rb という文字列がそのまま渡されているようでした。wrap の値が怪しいのかな?とも思って見てみましたが、それも if_ruby の値と同じでした。

rb_load の実装を見てみる

rb_load()rb_load_internal()をコールしているだけといいましたが、実際には引数で渡ってきたパスを一旦file_to_load()関数に渡していました。

void
rb_load(VALUE fname, int wrap)
{
    rb_load_internal(file_to_load(fname), wrap);
}

file_to_load() の実装は以下の通りです。FilePathValue() ではエラーになってなかったっぽいので、rb_find_file()でエラーになって返り値が0になるため、load_failed()が呼ばれてしまうようです。

static VALUE
file_to_load(VALUE fname)
{
    VALUE tmp = rb_find_file(FilePathValue(fname));
    if (!tmp) load_failed(fname);
    return tmp;
}

rb_find_filerb_f_load() の中でも使われており、返り値が0の場合の処理が rb_load()とは異なっているようです。

    path = rb_find_file(fname);
    if (!path) {
        if (!rb_file_load_ok(RSTRING_PTR(fname)))
            load_failed(orig_fname);
        path = fname;
    }

rb_file_load_ok() という関数がキモな気がしてきました。

rb_file_load_ok()file.c 内に定義されています。ただ、内容を見た感じ、fdを取得して内部でロード可能かを判断しているだけのようなので、特別何があるというわけでもなさそう。カレントディレクトリにロード対象のファイルがあれば、それをちゃんとロード可能だと判断してくれそうな気がします。というかしてくれます。

結論

Kernel.#loadrb_f_load()を呼んでいて、vim:rubyfilerb_load()を呼んでいる。rb_load() の中では file_to_load()が使われていて、この関数の中のrb_find_file()が返り値0を返すためロードエラーとなっている。rb_f_load() でも同関数が使われている。ただ、一度ロードには失敗するが、そのあとの処理でrb_file_load_ok() でロード可能と判断されるため正常にロードされる。

なぜ、このような違いがあるのかまでは今回は追えなかったが、一応 :rubyfileruby load と異なる動作をする理由がわかったので今回は終了とする。

if_ruby 付き vim を使って ruby を実行するには PATHに ruby_builtin_dlls も足す必要がある

Could not load library msvcrt-ruby240.dll · Issue #2660 · vim/vim · GitHub

毎日ビルドしている Vim に if_ruby を足したのだが、 :ruby print "Hello" をしても dllが見つからない旨のエラーが発生してしまってなんでだ?と思っていたら、上のサイトで示されている通り、ruby_builtin_dlls環境変数の PATHに設定する必要があるとのこと。昔はそれでもよかったっぽいけど、ruby 2.4 以降からは必要らしい。日本語の説明記事がなかったので、メモとして書いた。

WEB上で打刻できるタイムカードサービスを作った

タイトルで落ちてるんですが、WEB上で打刻できるタイムカードサービスを作りました。

Time Card

アクセスすると、ログインダイアログが表示されます。

f:id:bamch0h:20190504212756p:plain

Sign-Up ボタンを押すと、ユーザー登録画面になります。

f:id:bamch0h:20190504212933p:plain

ユーザー登録してもらうと、仮登録状態になります。

f:id:bamch0h:20190504213334p:plain

登録したメール宛にメールが送信されるのでメール上のリンクを 押してもらって、本登録を完了してください。

f:id:bamch0h:20190504213529p:plain

リンクをクリックすると、以下のページが表示されます。 このページはこれ以上なにも起こらないので閉じてください。

f:id:bamch0h:20190504213608p:plain

上記のページが表示されると、ユーザー検証中のページが以下のページに遷移します。

f:id:bamch0h:20190504213809p:plain

名前を入れてもらうと、タイムカードページになります。

f:id:bamch0h:20190504213943p:plain

オレンジのボタンを押してもらうと、出社状態になります。

f:id:bamch0h:20190504214036p:plain

退社ボタンで退社します。

はい、それだけです。

時間が自動で計算されたりもしないし、管理者モードみたいなものもないので使い勝手は微妙ですが、もしよかったら使ってみてください。

ソースは こちら にありますので、PRもお待ちしております。

自然順ソート(マルチバイト対応改良版 & Windows エクスプローラー準拠)

何度も同じようなブログを連投してしまいますが、先のブログには大きな数字を使うとちゃんと動かないという欠点がありました。今回はそれを改良したバージョンになります。

先のバージョンでは、strconv.Atoi を使用している関係で どうしても数値が int の範囲を超えることができませんでした。

今回はstrconv.Atoi を使用せずに、文字列比較のみで数字を順番に並べます。

Windows エクスプローラーを色々触って確かめていると、どうもゼロサプレスした値を右寄せ比較しているだけなのではないか?ということに気付きました。

なので、数字比較の処理の中で頭の 0 は読み飛ばした後、右寄せ比較を行うようにしました。

そうすると、WIndows エクスプローラーのソートに近づけることができました。

package main

import (
    "fmt"
    "sort"
    "unicode"
)

func getRune(ru []rune, i int) (rune, int) {
    for x := i; x < len(ru); x++ {
        if unicode.IsSpace(ru[x]) {
            continue
        }
        return ru[x], x
    }
    return rune(0), len(ru)
}

func compareNumber(a, b []rune, ai, bi int) int {
    aj := ai
    for len(a) > aj {
        if a[aj] != '0' {
            break
        }
        aj++
    }

    bj := bi
    for len(b) > bj {
        if b[bj] != '0' {
            break
        }
        bj++
    }

    bias := 0

    for {
        if (len(a) <= aj || !unicode.IsNumber(a[aj])) && (len(b) <= bj || !unicode.IsNumber(b[bj])) {
            return bias
        }

        if len(a) <= aj || !unicode.IsNumber(a[aj]) {
            return -1
        }

        if len(b) <= bj || !unicode.IsNumber(b[bj]) {
            return 1
        }

        ca := a[aj]
        cb := b[bj]

        if ca < cb {
            if bias == 0 {
                bias = -1
            }
        }

        if ca > cb {
            if bias == 0 {
                bias = 1
            }
        }

        aj++
        bj++
    }
}

func natualSort0(a, b []rune) int {
    var ca, cb rune
    var ai, bi int
    for {
        if len(a) <= ai && len(b) <= bi {
            return 0
        }

        if len(a) <= ai {
            return -1
        }

        if len(b) <= bi {
            return 1
        }

        ca, ai = getRune(a, ai)
        cb, bi = getRune(b, bi)

        if unicode.IsNumber(ca) && unicode.IsNumber(cb) {
            if ret := compareNumber(a, b, ai, bi); ret != 0 {
                return ret
            }
        }

        if ca < cb {
            return -1
        }

        if ca > cb {
            return 1
        }

        ai++
        bi++
    }
}

func naturalSort(strings []string) func(i, j int) bool {
    return func(i, j int) bool {
        return natualSort0([]rune(strings[i]), []rune(strings[j])) <= 0
    }
}

func dumpByNaturalSort(strings []string) {
    sort.Slice(strings, naturalSort(strings))
    for i, s := range strings {
        if i != 0 {
            fmt.Print(" < ")
        }
        fmt.Printf("%+v", s)
    }
    fmt.Println()
}

func main() {
    dumpByNaturalSort([]string{"a20", "a10", "a2", "a1b", "a1a", "a1", "a0", "a"})
    // a < a0 < a1 < a1a < a1b < a2 < a10 < a20

    dumpByNaturalSort([]string{"x8-y8", "x2-y08", "x2-y7", "x2-g8", "x01-y09"})
    // x01-y09 < x2-g8 < x2-y7 < x2-y08 < x8-y8

    dumpByNaturalSort([]string{"1.3", "1.1", "1.02", "1.010", "1.002", "1.001"})
    // 1.001 < 1.1 < 1.002 < 1.02 < 1.3 < 1.010

    dumpByNaturalSort([]string{"001_a", "001", "01", "1", "10", "010", "2", "002"})
    // 001 < 001_a < 01 < 1 < 002 < 2 < 010 < 10

    dumpByNaturalSort([]string{"a001", "a01", "a1", "a10", "a010", "a2", "a002"})
    // a001 < a01 < a1 < a002 < a2 < a010 < a10

    dumpByNaturalSort([]string{"3", "1", "10", "2", "20"})
    // 1 < 2 < 3 < 10 < 20

    dumpByNaturalSort([]string{"4294967294", "1", "4294967296", "4294967299", "11111111111111111222222222222222"})
    // 1 < 4294967294 < 4294967296 < 4294967299 < 11111111111111111222222222222222
}