北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

dockerで古いバージョンのperlを使う

Docker Hub に perlのイメージ が上がっていて便利なのだけど、残念ながら 5.24 までしかない。検索してみたけど他に良さげなイメージもなかった。

仕方がないので 自分でビルド をして上げておいた(自分で使う用)。幸い、以下のリポジトリにいい感じの Dockerfile が落ちてるので、これを落としてきて自前でビルドするだけで済んだ。

github.com

追記

5.14 と 5.16 はビルドできなかった。

LTSのubuntuを16.04から18.04へアップグレードした

以下に書いてあるとおり。

wiki.ubuntu.com

基本的に sudo do-release-upgrade を叩いただけ。sshd の config を置き換えていいかみたいなことを聞かれたので、触った覚えがないので不安を覚えつつ置き換えた。後は、質問に答えながら延べで数時間放置していたら終わっていた。

たしかに新しくはなったが、何が変わったかわからないレベル。 emacs がなぜか無くなっていたようなので、

$ sudo apt install emacs25-nox

した。今のとこそのくらい。

minillaは便利

songmu さんの WEB+DB の記事を読むのが良い。

gihyo.jp

リリーステストで、

Hoge.pm requires 5.010 due to explicit requirement 

で死んだときは Perl version 指定が正しいか確認する。 // とか使ってると 5.8 系はサポートできないというか、流石に 5.8 系は平成半ばにしてすでに終わっているのではないか。

xt/minilla/spelling.t ......... skipped: no ~/.spellunker.en   

が出てたときは touch ~/.spellunker.en するとPODのスペルチェックもやってもらえる。

CircleCIのsave_caheするPATHとdocker imageの相違

.circleci/config.yml に以下のように書いておいたら、ある時からキャッシュが効かなくなってハマった。

version: 2
jobs:
  build:
    docker:
      - image: perl:5.28
    steps:
      - checkout
      - restore_cache:
          key: cacheminil-v1
      - run:
          name: Install Minilla
          command: |
            cpanm Minilla
      - save_cache:
          key: cacheminil-v1
          paths:
            - "/usr/local/bin"
            - "/usr/local/lib/perl5/site_perl/5.28.0"
..以下略..

正しくは以下。いつまでも 5.28.0 のままではないのだ。

          paths:
            - "/usr/local/bin"
            - "/usr/local/lib/perl5/site_perl"

本当は - image: perl:5.28.0 を指定したいところだが、残念ながら https://hub.docker.com/_/perl を見てもない。

mariadbのdokcer imageの10.2と10.3でmysql.dbが違う

https://hub.docker.com/_/mariadb

10.2 の方では、 MYSQL_USERtest_ データベースを作れない。

mariadb:10.2mysql.db

MariaDB [mysql]> SELECT Host, Db, User, Create_priv FROM db;
+------+--------------+----------+-------------+
| Host | Db           | User     | Create_priv |
+------+--------------+----------+-------------+
| %    | hogehoge_dev | hogehoge | Y           |
+------+--------------+----------+-------------+
1 row in set (0.00 sec)

mariadb:10.3mysql.db

MariaDB [mysql]> SELECT Host, Db, User, Create_priv FROM db;
+------+--------------+----------+-------------+
| Host | Db           | User     | Create_priv |
+------+--------------+----------+-------------+
| %    | test         |          | Y           |
| %    | test\_%      |          | Y           |
| %    | hogehoge_dev | hogehoge | Y           |
+------+--------------+----------+-------------+
3 rows in set (0.000 sec)

なんで違うのか調べてもわからなかった。 mariadb:10.3 の挙動が意図的なのかデグレってるのかも。

【朗報】健康になるために酒を辞める必要はなかった

前回の健康診断で、高脂血症のため要治療というショッキングな結果が出た。どうせ医者に行けば禁酒しろと言われると思い、2018年は完全に酒を絶った。本当に一滴たりとも酒は飲んでいない。

そして一年後の健康診断の結果がこちら。

f:id:hiratara:20190212211225j:plain
健康診断の結果

変わってないよ! 禁酒しても健康にはならないという貴重な実験結果である。

辞めるべきだったのは酒や食物 1 ではなく人生だったようだ。病院へ行きます。


  1. 同様の理由で肉類を経って7年、毎朝納豆を摂るようにしている。平日の昼食を COMP に変えて1年。カフェインも秋頃から摂るのを止めた。

django.urlsのコードリーディング

django ではURLのルーティングを django.urls パッケージで処理している。このパッケージのソースはなかなか読みにくいので、読むための手がかりを記しておく。

バージョンは2.1を仮定していることに注意1

クラス階層

django.urls パッケージでは継承もインタフェースも使っていないダックタイピングの見本のようなコードになっており 2 、この点が読みにくくしている一番の要因である。しかも、歴史的な都合なのか、クラスの命名規則にも致命的なわかりにくさが存在している。まずは主要なクラスの構造を説明することで見通しを良くする。

ゾル

いちばん重要な概念はリゾルバである。リゾルバは自分が解決すべきURL ( PATH_INFO )と、その解決方法を知っているデータである。 resolve() メソッドを提供し、入力されたURLに合致するビュー 3 を返す。リゾルバは木構造をなしており、以下の2種類がある。

URLResolver は他のリゾルバを含むリゾルバである。最上位のリゾルバは URLResolver となる。また、 path 関数で include を指定したときにも生成される。

一方、 URLPattern は末端に位置するリゾルバで、ビューを保持している。 path 関数でビューを指定したときに生成される。

「自分が解決すべきURL」は、パターンクラスによって表されている。

パターン

パターンはリゾルバに付属する。URLが自分が扱うべきものであるかを判定し、さらにビューに必要なパラメータを抜き取ることができるクラスである。 match メソッドを提供し、マッチ後に残ったURL、抽出したパラメータ ( args または kwargs )を返す。以下の3種類があるが、 URLPatternはパターンではない ことに注意する。

RegexPattern はおそらく django 1.x 系の頃使われていたもの。今でも re_path によって生成することはできるが、利用する必要はないだろう。

RoutePattern が通常使われるもので、 path 関数を使ってパターンを記述すると生成される。結局入力されたパターン文字列は正規表現に変換されるが、 re_path のように正規表現で直接記述するよりはわかりやすい記述が可能と言えよう。

LocalePrefixPattern は特殊なもので、 i18n 対応でURLの先頭に言語 /ja/... /en/... などをつけるときに使われる。マッチする文字列は現在の言語設定によって異なる。例えば、現在の設定が日本語の場合、 ja/ という静的な文字列にマッチするパターンとして振る舞う 4 。このパターンを含め、URLの i18n 対応は無理やり突っ込んだ感がかなりある。例えば、このパターンは最上位のリゾルバにしか指定できないが、その判定も include を使った構築時に 配下となる予定のリゾルバがこのパターンを使っていないかをチェックする と言っただいぶ強引な方法で判定している。後、URLの先頭に含まれる静的な文字列 ja/URLの先頭に含まれる文字列から決まったりする のだが、卵が先か鶏が先かよくわからない。国際化対応の実装は他にやりようがなかったんだろうか・・・。

resolve : 順方向の解決

与えられたURLを処理するビューと呼び出しに使うパラメータを得るための resolve 関数のコードを見ると、 最上位のリゾルバに依頼するだけ 。とてもシンプル。他の箇所もこれくらい読みやすければ文句がない。

念のためリゾルバ内の resolve も読んでみると思ったより長くてウッとなるが、実際は 再帰的にresolveを呼び出している だけ。リゾルバのクラスが成す階層構造さえ知っていれば怖いことはない。ただし、ここにでてくる変数 patternパターンではなくリゾル なので注意が必要(自分でも何を言っているかわからない)。

reverse : 逆方向の解決

reverseresolve の逆で、ビューの名前と呼び出し時に使うパラメータからURLを生成する。こいつもリゾルバに依頼すれば終了なのではないかと 淡い期待で読み始める と、全くそうではない難解なコードであることがわかる。なんでリゾルバに reverse させないの・・・マジなの・・・?

reverse 関数内では、リゾルバの階層を潜ってビューを持つ URLResolver を得る 5 。このとき、途中で現れたパターン(を表す正規表現)を拾う 6 。得られた URLResolver新しいURLResolverでラップされる 。この URLResolver は集めておいたパターン(正規表現)に紐付けられるので、新しい URLResolver は完全なURLを再構築できる。

reverse 関数がリゾルバの階層を潜るには URLResolver が持つ reverse_dict という辞書が必要になるが、この辞書は _populate というメソッドが生成する。実質、 _populate が逆引きロジックの本丸になっていると言え、非常に重要な役割を果たすメソッドである。

URLの再構築は、新たな URLResolver_reverse_with_prefix メソッドが行う。なんとプライベートメソッドである・・・。このメソッドは一見長いが、 reverse_dict に入っている再構築に必要な情報を元にURLを再構築し、 パターンに適合するURLができたかをチェック した上で返すだけである。

ということで、URLの再構築に必要な情報は _populate の中で構築される。 _populate の実装を見てこのエントリは終わることにしよう。 _populate は配下のリゾルバをすべて読み込み(再帰的に _populate が呼ばれる)、ビュー名 ( pathname 引数)をキーとする辞書を作る。これが reverse_dict で、 reverse 関数が URLResolver を見つけるために用いる。

そして、この辞書の値がURLの再構築に必要な情報である。値にはパターンの正規表現(検証用)、引数のデフォルト(穴埋め用)、コンバータの一覧が含まれるが、一番重要なのは正規表現normalize関数 で変換したフォーマット文字列と引数リストである。例えば <int:year>/<int:month> のような RoutePattern からは (?P<year>[0-9]+)/(?P<month>[0-9]+) のような 正規表現が作られる が、この正規表現から '/%(year)s/%(month)s/' のようなフォーマット文字列と ['year', 'month'] という引数リストが抽出される。 normalize の実装は真面目に正規表現をパースしており、利便性のためとは言えここまで頑張るか・・・と思わせる実装になっている。この情報を使うことで、 _reverse_with_prefix は URL を再構築できるというわけである。長かった。

ところで、 normalize の戻り値は1つではない。これは、 ?* など、 0 個を許すパターンにおいて、フォーマット文字列の該当する引数があるパターンとないパターンを場合分けする必要があるからだ。URL再構築時にはキーワード引数などからどの引数があるか、ないかをチェックし、利用するフォーマット文字列を決めることになる。また、 .[0-9] などがパターンに使われている場合はいずれの文字を選んでもマッチするので、前者は . 、後者は 0 を含むURLが代表として構築される。


  1. 今のプロジェクトで使っているため。世間では2.2が出ているようだ。

  2. しかも、ダックタイピングを使う必要はまったくない。

  3. djangoのビューは一般的なWAFで言うコントローラであり、 callable な関数またはクラスである。

  4. 静的な文字列なので、 reverse 関数適用時に利用者が言語名を指定する必要はない。

  5. 末端である URLPattern ではないことに注意。

  6. 正確には値の変換に使うコンバータも。