flowで依存ライブラリがRequired module not foundになる際の最小の対策

背景

先日から flow でシコシコ type 付けをしていたが、新たに lodash を使おうと思ったら以下のようなエラーが出てしばらく悩んだ。

3: import _ from 'lodash/lodash.min'
                   ^^^^^^^^^^^^^^^^^^^ lodash/lodash.min. Required module not found

まとめ

  • 上のエラーは最小限の declaration を用意することでしのげる
  • declaration はデフォルトでは flow-typed/ 以下に置く(これなら .flowconfig で設定不要)
  • declaration は module_vx.x.x.js の名前で作れる
    • lodash/lodash.min なら lodash.min_v4.x.x.js
  • 内容は以下の通り
declare module 'lodash/lodash.min' {
  declare module.exports: any;
}

実はlodashには定義済みのtype情報がある

んだけど、lodash/lodash.min には対応していないのであった。

最小限の方法で行くという選択

flowtype/flow-typed: A central repository for Flow library definitions

今回試しに flow-typed を入れて scaffold までやってみたのだが、

flow-typed は独自に flow-typed コマンドを使うので完全自動化まで考えると使い勝手が悪い

ということが分かった。例えば babel の plugin を babel コマンドで管理するようなものと思えばよいだろうか。npm や yarn のコマンド体系の外にいるので、それを適切に呼び出せる環境をちゃんと準備しないとこの恩恵をみんなで共有することはできない。(postinstall とかでやることが増える)

真面目に flow-typed で type 情報を共有できるようにするのは一つの正しい方法ではあると思うけど、今のところ手元での再現や CI での実行には工夫が必要になってしまう。大掛かりな仕組みで依存ライブラリがたくさんある場合は正しい手法を選択した方がよいとは思うが、今回は最小限の dirty hack でしのぐ方法を紹介することとした。

どうせ全部の定義が欲しいわけではないしね。

Sinatraからrails-assets.orgを使う

先日

Sinatra AssetPackを試してみた - あーありがち(2014-07-19)

において Sinatra でも Asset Pipieline のようなことができてある程度満足していたのだが、Bower の部分は実は解決しておらず、もやもやしていた。

bower componentsをどうやってproduction環境にinstallするのか

具体的には deploy まで考えた際に bower components をどう扱うのがよりよい選択なのかが見えていなかった。恐らく解決方法は以下の二つのいずれかになると思う。

  1. bower_components も git repository に入れる
  2. deploy process の中で bower components の install を実行する

1 については実は Bower 界隈でも答えは出ていないっぽい。

この辺の扱いももやもやするが、単純な話、Ruby 向け PaaS を使う場合、そもそも

bower install

を自由に実行できるのだろうか。bundle install はもはや常識だろう。だが bower install はどうだ? そもそも node.js は入っているのか? npm は入っているのか?

これが 2 の部分の疑問である。2 が解決するなら 1 で悩む必要はない。(もちろん、deploy のタイミングで外部の repository が落ちていたらどうする?と言った問題は残るが)

そうするとやはり全部 gem で解決できる方が何かと面倒がない。最近は「そもそも gem でないものを gem で install できるようにするためだけに依存性の複雑さが増すのはよくないのではないか」という考えも増えてきているが、しかし現実には gem でないと自由に install できるかどうかは分からない。生サーバや IaaS なら自由だろうけど PaaS だとちょっとね。

rails-assets.orgはrailsじゃなくても使えるのでは?

https://rails-assets.org の存在は以前から知っていたし、前回の記事にも取り上げていたんだけど、名前から単純に Rails 専用だと思っていた。しかし、待てよ。

rails-assets.org が解決してくれるのは「bundler だけで bower components を install できるようにする」という部分だけで、実際に assets を探して読み込んでくれるのは Asset Pipeline の仕事なのでは?

ということで試したらできた。前回と同じ jQuery 1.10 を bower components として利用する場合は以下のように書くとよい。

Gemfile

(snip
source 'https://rails-assets.org'
(snip
gem 'rails-assets-jquery', '< 1.11'

Sinatraアプリ

assets do
  serve '/assets/javascripts', from: 'assets/javascripts'
  serve '/vendor/assets/jquery', from: 'vendor/ruby/2.0.0/gems/rails-assets-jquery-1.10.2/vendor/assets/javascripts/jquery'

  js :app, '/javascripts/appication.js', [
    'assets/javascripts/a.js',
    'assets/javascripts/b.js',
    'vendor/assets/jquery/jquery.js'
  ]

  js_compression :uglify
end

うおーだせぇ(笑)

要は Rails が自動で解決してくれる部分を手で書いただけ。

とは言えこれで

  • bundler が使えれば bower components を install することができ
  • 必要な assets を読み込むことができる

ということで、今度こそ見えている課題は全部クリアになったはず!

おまけ

jsmin は obsolete だし copyright も潰してしまうので、UglifyJS を使ってみた。いい具合だった。

タリーズ金沢入江店に行ってきた

※ メモから2ヶ月前の記事を起こしています。

広い。ゆったりしてる。窓際カウンター電源席は少ない。iPadカウンターがあってそこは電源もあるんだけど、ちょっと自分のPCを使うには躊躇するよなぁ。

Twitter / @wtnabe: 広い。ゆったりしてる。窓際カウンター電源席は少ない。 …

あと壁際にいくつか電源あるけど、数は少ない。freespotあり。余裕もって座れるのでバッテリに余裕あれば気分よく作業できそう。雰囲気はとてもよい。以上レポート終わり。

Twitter / @wtnabe: あと壁際にいくつか電源あるけど、数は少ない。free …

今ごろ scraping に苦労している話

今回、ある HTML の断片を定期的に取得するという要求ができたので喜び勇んで yapra を使えば一発じゃーんとやり始めて恐ろしく苦労したのでその顛末を書いておく。なお、苦労したのは pragger や yapra が問題なのではなく、自分の思い描いていた動作と pragger の思想がズレていたこと及び Hpricot の不備が原因である。pragger ダメじゃんとか言うつもりは毛頭ないです。

まぁバカは要らぬ苦労をするということの記録です。言い直すとやはり道具は使ってみないとクセが分からない。なお、使ったのは yapra の git tip です。1

yapra の conf はちゃんと書きましょう

実は最初のつまずきの話は超簡単で、

存在しない class based plugin を呼び出そうとしていた。

という実にくだらないことでした。以下その trace

./bin/../lib/yapra/pipeline.rb:76:in `run_class_based_plugin':
LoadError (LoadError)
       from ./bin/../lib/yapra/pipeline.rb:52:in `execute_plugin'
       from ./bin/../lib/yapra/pipeline.rb:45:in `run'
       from ./bin/../lib/yapra/inflector.rb:53:in `inject'
..

はい、そのまんまですね。でもぼくはこのメッセージを素直に受け取れなくなっていた。理由は「class based plugin と legacy plugin を一つずつ呼び出していて、class based plugin は絶対に間違いなく load できているはず。」と思い込んでしまったから。

このメッセージは以下の conf で発生していた。

- module: RSS::Save

実はこれ、こう書いていたつもりだった。

- module: RSS::save

お分かりだろうか。s の大文字小文字を間違っているのだ。これに丸1日気づかなかった。というか作者の yuanying さんの手を煩わすまで気づかなかった。ひどい。ひどすぎるぞ > オレ。

この違い、実は RSS::Save と書くと class based plugin の呼び出し、RSS::save と書くと legacy plugin の呼び出しとして動作する。

詳しくは以下に yuanying さんにメールで教えていただいた内容をもとに現時点での自分の理解を書いておきます。

yapra の plugin の load

何のことやら分からない方と自分のためにもう少し詳細を書いておくと、yapra の plugin には

  1. pragger オリジナルの plugin(これを legacy plugin と呼ぶ)
  2. yapra 用で Ruby の標準的な module/class 構造の plugin

の2種類の plugin がある。今回自分は

1 を呼び出しているつもりで間違って 2 を呼び出していたことに気づかず、立ち往生していた

のだ。バカすぎる。ちゃんと 2 の呼び出しに失敗したというメッセージを何回も目にしているのに。で、呼び出しの切り替えはまさに書き間違った大文字、小文字によって行われている。

言い訳すると、Perl や Ruby の常識的には module や package は大文字で始まるじゃないですか。だからぼかぁてっきり plugin の名前は常に大文字で始まるもんだと思い込んじゃってたんですね。

でも違ったのです。pragger の場合は基本的に plugin の名前はメソッドの名前なので、メソッドの名前は当然 Ruby 的には小文字で始まる、結果として pragger 由来の legacy plugin の名前は小文字で始まります。RSS::save の RSS が大文字なのは単にディレクトリ名が大文字だから。そうです、ご想像通りこの plugin の実体は RSS/save.rb に書かれています。

yapra の class based plugin の場合は読んで字のごとく class based であり、class ってことは Ruby 的には定数であり、これは大文字で始まる必要があるわけです。

というわけで legacy plugin の呼び出しと class based plugin の呼び出しを分ける決定的な部分を完全に思い込みでミスっていたので、何が起きているのか正しく読み取れず行き詰まってしまったというわけであります。ちゃんとエラーメッセージはそのまま読まなきゃダメです。はい。

ちなみに yapra の最新版は

rubyforge のページに書かれているように git の方なのだそうです。

yuanying's yapra at master — GitHub

自分は以前 svn co しておいたコードを持っていたので、今回 svn up しただけで作業を始めてしまいましたが、今後は git の方だけでいくことにします。git の操作がよく分かってませんが、どうせ clone と pull しかしないだろう2から、悩むことはあるまい。

ところで今回初めて git を使いましたが、clone の速いこと速いこと。changeset が番号にならないのでどうも直感的な感じがしませんが、このスピードは魅力かも。(pull のスピードには特別感動しなかったので、日常的にはそんなに svn up と差が出ないかもしれませんけど。)

話はまだ終わらない

実はこの件の目的はまだ達成できていません。すいません、バカで。そもそもやりたいことは

  • HTML の一部の情報を抜き出したい

しか決まってません。

これをどう出力したいのかは曖昧なままでした。この状態で Feed::Custom を使い始めました。これが feed を組み立てるためのものだと気づくのに少々時間が掛かりました。オレの目的は単なる scraping で feed の組み立てじゃない気がするなぁと思いながらも、いやいやイマドキみんな plagger/pragger だよ、何ゆってんだよ wtnabe はバカだなぁ。scrape したら feed にするものなのさ、と思い込み、まともに plugin が動いたことに気をよくして作業を進めます。この時点での conf はこんな感じです。

- module: Feed::Custom
  config:
  ..
- module: print

print plugin は受け取った data をそのまま p してくれます。これで Feed::Custom で目的の scraping が行えているかどうかを確認します。

しかしまず XPath による切り出しでつまずきます。目的の node は、

  • あるclass属性を持っている子nodeを含むもの

なのですが、これの書き方が分かりません。子nodeの指定はできますが、目的は子nodeじゃない。XPather と格闘すること数時間。

親node//descendant::子node[@class='FOO']

という書き方で目的を達成できることに気づき狂喜乱舞! しかし、Hpricot でこの書き方が使えないらしく、print で何も出てきません。3

イヤになってきました。

Hpricot は便利だけどある程度いい加減な実装であるということは知っていました。でも最初に引っかかってしまうとは。だいたい、何も出力されないんじゃ何が起きているか分からない。

ここでいったん yapra は諦め、生 Hpricot で再チャレンジすることにしました。descendant:: の書き方が使えないってことは

Hpricot::Doc::search().each { |ele|
  if ( ele.search() )
    ..
  end
}

だよね、と直感的に思ったからです。そして XPath 一発で書けないルールを Feed::Custom 上で再現する方法は知らない。だったらまずは Hpricot で目的の node を取り出すところまでをどうにか動かしてみなくては、と思いました。

これはなんとかなりました。やはり descendant:: の書き方は通じません。うむうむ。状況は把握できた。ここではたと気づきます。

yapra では grep 掛ければいいんじゃなかろうか。

今回の目的は ある node の繰り返しのうち、class="FOO" が設定されている node を取り出す、というものです。これを XPath 一発で書くには descendant:: が必要だけど、とりあえず class があろうがなかろうが切り出しちゃって、class のないものをあとでフィルタで捨てちゃえばいいじゃん、と。つまり、

- module: Feed::Custom
- module: grep
- module: print

で、うまいこと書くと目的の node だけを取り出せるんじゃないかということです。

しかし Feed::Custom のことを自分は分かっていなかった。

Yapra で Pixiv – BONNOH FRACTION 13

だけを頼りにしていたのですが、config: extract_xpath: の中に各 item の個々の要素(link とか)を直接書けることに気づかず、apply_template_after_extraced: の中で組み立て作業を行おうとするもこの時点で渡ってくるのは Hpricot オブジェクトではなく文字列なので、属性値を取り出したい場合は文字列処理が必要です。

「なんかおかしくね?」

と思い始めます。頑張って XPath でやってきて最後は文字列処理なのか?と。せっかくの DOM node, せっかくの Hpricot オブジェクトじゃないのかと。じゃあ extract_xpath の方で頑張れるんじゃないだろうか。

しかし自分の力でできたのはここまででした。

capture: (ry
split: (ry
title: '//node/text()'

そうです。「内容」の取り方は分かったけど、「属性値」の取り方が分からない。XPath は node の指定のためのものだもん。属性値が取りたければやっぱ Hpricot オブジェクトを直接扱えないとダメだよなぁ。と諦めたのが昨日の夜のことでございます。

とりあえず今は生 Hpricot で書いて切り出しだけはできているんですが、今度はこれを feed まで組み立てるのもだるい。(もはや目的が feed の出力だったかどうかすらどうでもよくなっている。)というか yapra に挫折したという事実を受け止めたくない。feed の組み立てなんて自分で書いちゃダメだ、とさえ思い始めています。とりあえず YAML にでも吐き出しておいて、形はあとで考えるかと思ったら

今度は Syck の to_yaml() は日本語を正しく扱えない。もう完全にイヤになりました。

scraping ムズイっす。

たぶん conf だけで解決しようとせずに plugin を書けばいいんですよね。Hpricot を直接いじって、あとは誰か他の plugin に回す。でもまぁ、とりあえず頭を冷やすために放置します。こんなたった数行の処理すらまともに書けないという事実に対する怒りがさらに頭の回転を鈍くしている感じなので。

あと思ったこと

  • WWW::Mechanize はローカルのファイルを読み取れないので scraping 自体のテストをしたいときに不便

自分は今回手元で動かしていた Apache の適当な場所に取得済みの HTML を置いてテストしていたけど、そういう環境を用意できない人はどうすればいいんだろう? 毎回本番のサーバにアクセスしに行くのもイヤだよね。まぁパッチ書いている人もいるけど、パッチである以上は本家の更新で動かなくなる危険性は絶対残っちゃうし。

  • Syck の to_yaml() は結構致命的

なんとなく 1.9 による m17n が進むと解決しそうな気もするけど、DBMS とか使わずにお手軽に他のシステム(言語から全部違うとか)と連携したい場合、標準の Ruby + Syck は使いものにならない。

まぁだからこそ XML である feed を使う plagger 系ツールの出番なのかもしれないけど、まだ使いこなせてないし。あうー。

RubyForge: Ya2YAML - An UTF8 safe YAML dumper: Project Info

使えばいいっていう話ではあるんだけど。なんかこう釈然としないものは残る。

とは言え今回は

  • git
  • yapra
  • WWW::Mechanize
  • Hpricot
  • XPath(完全に初めてではないけど)

と初めて使った道具ばかりで、いい経験になったと言えばいい経験になった。次に繋げよう。というか近日中には目的は達成しないといけないわけだけど。

いちばん早いのは生 Hpricot で進める方向かな…。

  1. tip っていう表現でいいんだよね? svn で言う trunk の head のつもりなんだけど。 

  2. 一応 push できるようになるのは目標の一つですけど。 

  3. なんで Hpricot かっていうと、Feed::Custom の中で WWW::Mechanize を使っていて、Mechanize が Hpricot を使っているからです。 

WSHでリモートのスクリプトを読み込んで実行、、、できない?

ほんっとーにほしい情報ってないもんだ。今回は WSH で

hoge.wsf

<job id="moge">
  <script language="JScript" src="lib.js" />
</job>

を、

<job id="moge">
  <script language="JScript" src="http://lib/lib.js" />
</job>

としてみた。

hoge.wsf(0, 1) Windows Script Host: パラメータが間違っています。

という、まったく意味不明かつリファレンスに載っていないメッセージが返ってきた。ちなみに、上の例でいう lib.js をちゃんと読み込んでいるのかというと、ローカルの存在しないファイル名を指定すると

参照された URLを 取得できません。

というエラーに変わるし、Web サーバにはログが残っているので取得できているっぽい。が、実行はできない。で、そのファイルをローカルに置いてそれを読み込ませると普通に実行できる。

えーとりあえずこれだけは書いとく。

  • エラーメッセージを出すなら意味の分かるものを出せ
  • 制限があるならちゃんとそう書いとけ

なんでこんなことをしたかったのかというと、

  • サーバ管理のスクリプトを常にサーバ上でゴリゴリ書くのはやりたくない
  • 別の機械で書いて、CVS で管理しよう
  • CVS に commit したものがそのまま HTTP で取得できる場所に反映されたら1、サーバ上のスクリプトを毎回更新しなくてもいんじゃね?(サーバにはランチャになるファイルだけを置いておけばよい。)
  • 管理コスト減ってウマー

のつもりだった。まぁセキュリティの問題でリモートのファイルを読み込んでいきなり実行できなくても別に不思議はないんだけどさ。だったらそういうメッセージを返してくれよ。

※ 上のような使い方、うちではできてるよという方がいらっしゃいましたら情報お待ちしております。

  1. これはリポジトリ内の特殊なファイルを編集することで可能。詳しくはマニュアルを読め。 

携帯キャリアの Errors-To の解釈

試してみたところ、

docomo.ne.jp効果あり(Errors-To で指定したアドレスにエラーメールがくる1
vodafone.ne.jp未テスト2
ezweb.ne.jp効果なし(From にエラーメールが返ってくる)

Errors-To は私の適当な解釈によると、こちらの指定したアドレスにエラーメールを返信させるためのヘッダ。のはず。なんだけど、この指定に対してどのように振る舞うかはサーバの設定次第なので、使えるとは限らない。まーそれは知ってたんだけど、実際どのように機能するのかを知っておきたかったので、確認したというわけ。

エラーは、携帯端末側で受信拒否の設定をしたアドレスからメールを送ることで発生させた。存在しないアドレスには投げていない。(存在しないアドレスかどうかなんて分かんないもん。)これはしかし Vodafone に関しては、存在しないアドレスに送った場合も同じようにエラーを返さない場合、アドレスリストの精度を上げる方法ってないんだなぁ。spammer にはいやなキャリアだけど、まともにメールを配信したい業者にもいやなキャリアですよ、これは。[8/3 追記]存在しないユーザーに投げた場合はエラー返ってくるようです。

ところで今回 Thunderbird にて Errors-To を設定するために以下を参考にした。

たーくんずろぐ / Thunderbirdにて任意のヘッダを追加する。

誰か拡張にしてくんないかなぁ(ぼそ)。と思ったら、そういえば xpi を勉強するとかなんとか以前考えていたことを思い出した。宿題ばっかだ。

  1. ただし From と同じドメインのアドレスで実験している。 

  2. 今回実験した「受信拒否しているユーザーにメールを投げる」場合はそもそもエラーメールは返ってこない。存在しないユーザーにメールを投げた場合はエラーは返ってくるが、おいそれと試せないので、存在しないユーザーにメールを投げた場合に Errors-To が有効かどうかは未テスト。 

mode ファイル追加完了

  • php-mode.el
  • css-mode.el
  • ruby-mode.el

を手動で /usr/share/emacs/site-lisp/ に cp. Unison も入ったし、「作業」はできるようになった。あとは Namazu と英辞郎の検索をどうするかだなぁ。

aaacafe に領域確保

ruby が 1.6 だがまぁよかろう。tDiary 2.0 でスタートしてみますか。

Unison on OS X

  • ocaml を apt-get install
  • Unison を手動 make UISTYLE=text
  • できたバイナリを /usr/local/bin につっこんでそこにパスを通す

て形で 2.9.20 を入れた。何もしなくても static にリンクしてくれるらしく、うまく動いているみたい。

Fink の XFree86

たわむれに XFce とか使ってみたくなって Fink でガリガリ build させていたのだが、どうも Apple 標準の X11 とぶつかって Fink からの X アプリのインストールに成功しない。system-xfree86 っていうダミーのパッケージを入れればいいようにも読めるんだけど、そもそもそれができない。ちょっとよく分からん。Debian 系のプロジェクトはドキュメントが充実しているのはありがたいんだけど、Unix 系のドキュメントとしてありがちな、冗長だけど意味が分からないものもまた多い。うーん。

/usr/bin/open-x11

from X11 for Mac OS X

あの BasiliskII などのエミュ情報が充実したサイト内に分かりやすく情報がまとめられていた。こんなコマンドがあったとは。これで起動すれば X11 を事前に起動しておかなくても、起きてなければ自動的に起動される。

くそー。

意味が分からん

もう一年以上前に面白がってスラドのバナーを作ってみた。まったく音沙汰がないので忘れていたが、最近見たらそのバナーが登録されていた。

なぜか縦横倍に引き伸ばされた状態で。

意図が分からない。88*31 の最も一般的なバナーサイズで作っておいたものが、なぜか 176*62 に無造作に引き伸ばされて掲載されている。そう、無造作に。どう考えてもそのまま縦横倍。補完の処理はされていない。最初は HTML のサイズ指定で大きく見えているのかとも思ったが、そうではなく画像そのものが大きくなっている。

なんで?

インターネットの情報サービス

  • Apacheは「インターネットの情報サービス」であるため、 EULAにおける「インターネット情報サービス」に関する規定が適用される
  • *ApacheはEULAの「サーバーソフトウェア」に該当するため、 『サーバーソフトウェアとしては使用できない』旨の記述がある OS(Windows 2000 Professional等)では使用できない

Apache はインターネットの情報サービスであるってのがもう意味分からないんだけど。情報サービスじゃないでしょ。情報サービスを実現するための一つのアプリケーションに過ぎないでしょ。サービスって言葉の意味を勘違いしてるの? え、おれ?

サーバソフトウェアとしては使用できない OS 上でサーバソフトウェアに該当するアプリが使えない? うーん。日本語変だなぁ。

というかこの制限が IIS 以外に適用されるのってかなりむちゃくちゃじゃないかな。何がサーバで何がそうでないのか、明確な線引きはあるのかな? MSKK だいじょぶですか?

About

例によって個人のなんちゃらです