『実行可能なモジュール』設計パターンについて…あるいはサブコマンドを持つコマンドを私はどう作るか
『実行可能なモジュール』と私が勝手に呼んでいる、ある種の設計パターン/コーディングイディオムについて、 私なりの意見を整理しておこうと思います。
(この設計パターンは Perl 以外の言語でもよく見かけるので、既に名前が付いているのでは?と予想しています。 教えて頂けるとありがたいです)
pm に shbang と unless caller を書く
unless caller
Perl スクリプトで、ファイルの最後にこんなコードを見たことが有る人は、いるでしょうか?
unless (caller) { ...何らかの処理... }
この unless (caller) {...}
のブロックは、このファイルをコマンドとして直接起動した時だけ
呼ばれる処理を記述したものです。私が初めてこの種の書き方に触れたのは 1996頃の
Perl/Tk の文脈
で、
MainLoop unless caller;
と書いて
- このスクリプト自体が起動された時は、
Tk::MainLoop()
を呼び出す - それ以外のケース、例えば上記スクリプトを別プログラムからクリップボード経由で直接 eval したり
do
などでロードした時は、何もしない。
という動作を実現するために使われていました。
myscript.pl の代わりに MyScript.pm
さて、この unless caller
というイディオムは、 Tk に限らず一般の Perl スクリプトでも役に立ちます。
例えばスクリプトを書く時、 myscript.pl
の代わりに MyScript.pm
という名前にして package
文も書いて、
正当なモジュールとしてロードできるようにします。その上で、最後に unless caller
で、コマンドとして
起動された時の処理を書くのです。
例えば以下のように書きます。
#!/usr/bin/env perl package MyScript; ... unless (caller) { my @opts; push @opts, split /=/, shift(), 2 while @ARGV and $ARGV[0] =~ /=/; # XXX:手抜き my $app = MyScript->new(@opts); $app->main(@ARGV); } 1;
すると、この MyScript.pm
は (chmodして) 直接コマンド行ツールとして起動するだけでなく、
モジュールとしてロードし、一部のメソッドだけを呼び出すことも出来るようになります。
# コマンドとして起動し、 MyScript->new(x=>100,y=>100)->main('foo','bar') を呼ぶ % ./MyScript.pm x=100 y=100 foo bar # モジュールとしてロードして new し、メソッド foo() を呼ぶ % perl -I. -MMyScript -le 'print MyScript->new->foo'
サブコマンドをメソッドに対応付ける
先の例では unless caller
時には MyScript->new->main
を呼ぶように決め打ちしてありました。
ここを
- posix style の long option
--name=value
の列をnew()
の引数にする。--name
のみなら--name=1
として扱う。--debug
みたいに。
- 次に来た引数をサブコマンドの名前に使う。
という動作にすればどうでしょう? こんなイメージです。
# 何らかのテキストファイルをパースして、DB にロードする % ./MyScript.pm --dbname=foo.db import journal.tsv # 上記 DB から特定の条件で検索をする % ./MyScript.pm --dbname=foo.db list_accounts
早速、これを実現する unless caller
ブロックを書いてみましょう。
parse_opts()
は後で定義することにします。
unless (caller) { my @opts = parse_opts(\@ARGV); my $self = __PACKAGE__->new(@opts); my $cmd = shift @ARGV || "help"; my $method = "cmd_$cmd"; # サブコマンドのメソッド名は cmd_... で始めることにする。 $self->can($method) or die "No such subcommand: $cmd"; $self->$method(@ARGV); }
- ここでサブコマンドのメソッド名に接頭辞
cmd_
を付けることにしたのは、 例えばimport
というメソッド名が Perl にとって特別な意味の有るメソッド名で、 これと被ると予期せぬ面倒を生みかねないからです。
発展:任意のメソッドをサブコマンドとして試せるようにする
先の unless caller
ブロックで呼び出せるのは cmd_...
で始まる名前のメソッドだけでした。
これを任意のメソッドまで呼べるように拡張すれば、内部的なメソッドも CLI から
簡単に呼び出して試せるようになります。特に Perl は REPL が弱いので、
これを使えば好きなメソッドを shell のヒストリ・エディタ上で反復的に試せるようになり、
REPL の弱さを補うことが出来ます。
ただし、普通のメソッドは結果を Perl のスタックに返すだけで画面には何も出しませんから、メソッドの結果を
出力する機能も作る必要があります。また戻り値は undef や []
, {}
... を含みますから、
出力時はシリアライザーを通したほうが良さそうです。
以上を考えた unless caller
ブロックは、例えばこんな感じでしょうか>
use Data::Dumper; unless (caller) { my @opts = parse_opts(\@ARGV); my $self = __PACKAGE__->new(@opts); my $cmd = shift @ARGV || "help"; # cmd_ で始まるメソッドがあるなら、そちらをそのまま呼び出す。 if (my $sub = $self->can("cmd_$cmd")) { $sub->($self, @ARGV); } # それ以外でも、メソッドがあるなら、デバッグ目的で実行できるようにしておく。 elsif ($sub = $self->can($cmd)) { my @res = $sub->($self, @ARGV); print Data::Dumper->new(\@res)->Dump; } else { die "No such subcommand: $cmd"; } }
もちろん、もっと強化できる所はあります。
@res
の内容に応じて終了コードを設定すると、シェルスクリプトから使う時に便利になります。- スカラーコンテキストとリストコンテキストをオプションで使い分けられると嬉しい人もいるでしょう。
- 出力時のシリアライザーを JSON にする手もあります
- 引数の文字列が
{...}
,[...]
の形式の時に JSON としてデコードする手も、ありえます。
この辺りに興味の有る方は MOP4Import::Declare の
MOP4Import::Base::CLI_JSON
もどうぞ…
この設計パターンの使いどころ
あまり有効でないケース
先に、このパターンがあまり有効でない状況を挙げます。
- 開発するプログラムの仕様が十分に確定しており、設計に時間を掛ける余裕が有る。
- 開発者が十分に足りていて、モジュールを分割するほど、手分けして並列で開発を進められる。
- 作ったけど使わない、という可能性を考えなくて済む。
この記事で挙げたパターンは複数のサブコマンドを一個のスクリプトファイルに書くので、 複数人で並行開発することは困難だろうからです。
有効に働くケース
逆に、
- 何を作れば『ビジネス上の要求』を満たせるか分からない、探索的な開発をする必要が有る。
- 顧客が要求仕様をまとめられず、どんなコマンドを何個作ることになるか、全然予測できない。
- それを作っても使うか分からない、業務に投入してみないと何も言えない時。
という状況下で、それでも前進しないと駄目な時には、以下のメリットがあります。
- 最初から OOP 用のクラス・モジュールとして書いているので、いつでも継承してメソッドの挙動を変える、など自由自在に
OOP の技法を投入できる。
- もし初手をコマンドとして書き後からクラスへ括り出す場合だと、そこでクラスの命名で悩む時間を取られる。
- サブコマンドを増やし放題。メソッドへ括り出し放題。あらゆる無茶振りを一旦受け止めるための、汚れ役クラスとも言える。
- CLI ベースで作って、後から Web 経由で (Fat な) Model として使う、という手も有る。
- モジュールになっているので、テストも書きやすい。
とは言え、これで作ったものは巨大な一枚 pm になりがちなので、機会を見つけて整理をするのが大事、ではあります。 (…吐血…)
なんでこれ書いたのか
songmu さんの blog 記事
私も UNIX 哲学は好きで、概ね同意できる、と思いつつ…
自分が Perl で仕事のツールを書く時によく使うスタイルの話も書いておいたほうが、誰かの役に立つかもな〜、と思ったからでした。
おまけ
parse_opts()
の例です(既存のコードを解説用に簡略化したものなので、動作は未検証です)>
sub parse_opts { my ($list, $result) = @_; $result //= []; while (@$list and my ($n, $v) = $list->[0] =~ m{^--$ | ^(?:--? ([\w:\-\.]+) (?: =(.*))?)$}xs) { shift @$list; last unless defined $n; push @$result, $n, $v // 1; } wantarray ? @$result : $result; }
更新履歴
業務向け情報共有サービスに私が求めるもの
Markdown Night 2017 Summer - connpass で色んな話を聞けて刺激を受けたものの、その後の懇親会で自分の問題意識・欲求がいまいち上手く伝わらない・伝えられないことに気づいたので、自分なりに整理(?)しておきます。
TL; DR. 社内マニュアル向けシステムには、情報の構造化と新陳代謝を支援して欲しい
10年以上前から Wikiの一つを チーム内マニュアル書きに使ってきたものの、長年不満を感じている。 Wiki も色々有るし、更にもっと良さげな情報共有サービスも色々出てきたので、 実は既にこの問題は解決されているのかもしれないけれど… とにかく自分の問題意識を書き下しておきたい。
オーソドックスな Wiki の特性
- ページはページの名前で識別されリンクされる (例外: tcler’s wiki)
- ページの追加は、既存のページに(新ページへの)リンクを書くことで始まる
- 既存ページと新ページの間には、単にリンクが貼られている以上の(システム的)関係は無い(例えば「このページは元ページの子ページである」という情報は、システム内には記録されない。 (例外: redmine )
- ページの並び、という概念も存在しない。ゆえに、親ページ上に並んだリンクの順番はあっても、それに基づいて子ページ間に「前へ←、→次へ」のようなページャを配置することは、手作業になる (…sphinx ベースの wiki ってあるのかしら?)
- ページ内については wiki 記法で章立て・構造化をすることが出来るが、これが検索の単位と連動するとは限らない。 (例外: mediawiki)
要するに、Wiki で書かれたページ群は、(Wiki 奉行でも配して特別な注意と労力を払わない限り) ランダムに更新され成長してゆくグラフ・ネットワーク に近い構造に至る。
ページ群がランダムグラフに至ると、誰がどう困るか
- 新人教育で、教える側が困る。
- 指導用の教材は一本道に整理したいが、ページャのリンクを手書きはやってられない。
- 教わる新人が困る
- 読むべき資料が一本道の木構造なら、要する努力も見積もれるが、グラフでは底なし。
- ベテランも日々困る
- 検索をセクションで部分木に絞り込めないので、全マニュアルから各人が案件ごとに書いたアンチョコが無限にヒットする。が、どれが正しいか、今のオススメはどれか、今見ているページが古くないか…全くわからない。
- 検索が弱いので、重複した内容の、けど少しずつ違うマニュアルを各人が好きに書くことになり、余計に検索を悪化させる。
- 事例を既存のどのページにぶら下げるかべきか、どんな名前のページにするべきか、新しい事例ほど迷って時間を食う。けど、書き残さないとあとで困るので、とりあえずの場所にぶち込むことに。そして整理されない。Drag & Drop で移動したい…
- 更新のタイムラインを見る暇があれば、誰かがマニュアルを書き換えたことは把握できる。が、後で実際にマニュアルを見ている時に、それが参照するに値するのか、判断する基準がない。(例えば業務システム revision X のリリースより前なのか後なのか…) いつ頃誰が書いた記述かわからない(要するに git blame が欲しい)
- 新機能リリース時に、マニュアルのどこを改訂すればよいのか。自分で書いたマニュアルは改訂すれば良いとして、それに基づいて皆が書いたアンチョコは古いまま。どうするか。(機能とドキュメントの対応関係がシステム化されていない)
理想を言えば php.net (docbook) や amon2 (sphinx) のような一本道の 「本」 として構成された公式マニュアルをオンラインで作ることが出来て、かつ、その特定の章に絞った検索が出来て欲しい。それとは別に、その本という枠組みに縛られずに自由にページを足す機能は欲しいが、未分類なページを似たもの同士集めて分類を育てていく支援も欲しい。タグは…破綻させない仕組みがあれば…。重複マニュアルをどう見つけるか…。そして何より、部分的に古くなったマニュアルを、どう見つけて改定していくか…
おまけ
曰く「…多分、この内部構造のため、脳は(情報が)木構造にマップされる階層構造を持つ場合、一番効率よく働く…」 pic.twitter.com/HWzmvfa79A
— hkoba (@hkoba) 2016年2月21日
メモ:mod_fastcgi で FastCgiExternalServer 使いたい時は FastCgiWrapper を Off にしないとダメぽい
Apache2 の mod_fastcgi で 初めてFastCgiExternalServer を使おうとしたらハマったのでメモ。ツッコミ歓迎です。
TL;DR FastCgiWrapper を Off にしないと、他を正しく設定しても 404 Not Found にされてしまう(ぽい)。
プロセスを起動するか、外部で起動したプロセスに繋げるかの判定フラグ
content_handler()
を見ると fr->dynamic
が肝らしい
fr->dynamic を設定する箇所
create_fcgi_request()
のここによれば、 fs == NULL
で判定している。
ファイルが有っても fs が NULL になってた…なんで?
create_fcgi_request()
のこの箇所が fs
を設定している
fcgi_util_fs_get_by_id(const char *ePath, uid_t uid, gid_t gid)
は何してる?
ソースのコメントに曰く>
Find a FastCGI server with a matching fs_path, and if fcgi_wrapper is enabled with matching uid and gid.
fcgi_wrapper (suexec の fcgi 版)が有効になっていると uid/gid 検査が走って、それが常に偽なので NULL が返る。
結局、将来 ~user
でユーザ権限 suexec な fcgi を使いたくなった時のため…と思って FcgiWrapper を On にしてたのが
引き金でしたと。
かんそう
- 外部プロセス型の FastCGI したいときは nginx の方が楽だと思った(こなみ
- gist-it.appspot.com - Embed files from a github repository like a gist便利ね…あとはてブロ普通に script タグ書けるの便利ね。
やぷしー(やっぱち, Y8)2017春に行ってきました感想〜
勉強になったし、なにより楽しかったです!
感想など
V8 for フロントエンドデベロッパー by brn(ブルーノさん)
さきほどの資料となります。https://t.co/0sl73eSSV1
— Buruno Gullickson (@brn227) 2017年5月27日
#y8spring
V8 のソースをこれから読み解こうとする人のための、ガイド的な内容でした。 ぼくもよむー!ってなったです。
(ブルーノさんは中の人ではないとのこと。だけど、めっちゃソースと周辺解説を読み込んでることは伝わってきました)
- V8 が関数内の AST 作成を最初の呼び出しまで遅延させている、って話を聞いて Tcl を思い出しました。 (V8 の方が PreParser で構文エラーまで検出しているとのことだったので、プログラマーにとっては V8 の方が有り難いですね)
- V8 のソースが結構グローバル変数に頼ってる、という話を聞いて、V8 もそうなのですか、言語処理系あるあるだなぁと思ったりしました。
- (それにしても V8 は現在 108万行ですか…煩悩の一万倍ですね…)
ScalaでウェブAPIを書いている人が設計や実装やその他について話そうか by たっくんさん
本日発表の「ScalaでウェブAPIを書いている人が設計や実装やその他について話そうか」の資料です https://t.co/qPhQJZ3xQe #y8spring
— たっくん (@takkkun) 2017年5月27日
恥ずかしながら私は DDD をちゃんと勉強しておらず、レイヤーだけは感覚で切り分けて書く、というスタイルです。
で、発表で解説されていた
- プレゼン層→アプリ層→ドメイン層→インフラ層(データストア)
の問題と、それを解決するための DIP
の話は非常に分かりやすかったので、この部分だけでも活かせそうだと感じました。
hyperapp – 1kbのビューライブラリ by Jorge Bucaran (ジョージさん)
みなさん、今日のLet's Hyper App!のリンクはこちらです 💥 https://t.co/2HwWp0l40x #y8spring 🐫 #javascript
— Jorge Bucaran (@jbucaran) 2017年5月27日
先日の吉祥寺pm でも発表を聞かせてもらった hyperapp 、 更に サンプルや解説も充実 してきてるんですね。これは流行るかも…
なにより、ソースが短い(約300行)のは、大きな美徳ですね!
.NET Core がLinuxでどのように動いているか、またわれわれはどのようにデバッグするのか by たなかさん
RedHat の中の人が .NET Core の話をするという点が、まず興味深かったです。
- dll のシンボル一覧の件を伺ったのは、先日 fsharp で nuget でライブラリを入れて dll が3つ入った後で、 ある関数を呼ぶのにどの dll への reference を追加すれば良いのか分からなくて、入れては試し、をする羽目になった時に ふと疑問に思ったのです(o)
- .oO(…本当は Mono の話も聞けたら嬉しいな〜、などと…すみません><…)
MySQLサーバーのパフォーマンスチューニング by まみーさん
初めてのMySQLパフォーマンスチューニング、先ほどのスライドですhttps://t.co/C49JsR7jaE
— まみー (@mamy1326) 2017年5月27日
#y8spring
もう何年も MySQL 触ってないので、こういう情報はありがたいです。
- 特に、改善前・後の秒間リクエスト数の実測値こみの発表だったことが、良かったです。
(効果確認の具体的な数値指標があれば、あとで自分で試した時に設定失敗に気づけるようになるので)
チームで取り組む Singe Page Application by オカムラさん
はてなブログに投稿しました #はてなブログ
— okamuuu (@okamuuu) 2017年5月27日
インディー系テックカンファレンス、Y8(通称ヤパチー) で登壇しました - あいつの日誌βhttps://t.co/zHWsX3FpgV
(うちの仕事ではまだまだ SPA は遠い先なのですが、)
プログラマーとデザイナー?さんとの分業を如何に互いにハッピーな形に切り分けるかは 私もずっと興味を持ってきた領域なので、とても興味深かったです。
- あと、かつての Flash 屋さん的な人こそが SPA 屋に向いているのでは?、という感覚は、私も薄々感じています〜
謹賀新年/2017目標
あけましておめでとうございます
昨年度仲良くして下さった皆様、ありがとうございました。
本年もよろしくお願いします。
昨年の良かったこと
- プログラミング方面
- 暮らし
- 引越し先候補を幾つか下見できた事。
- 荷物減らしが進んで、部屋のレイアウトを変更できた事。
- (人生3度めにしてやっと)メガネからコンタクトへの移行が出来た事。
昨年の反省点
- 俺々言語の実験成果を公開できるレベルにまとめられなかった事。 普通の lisp を作るだけならそう難しくないが、 そこから離れた独自の意味論を作る所をどう実験していくかが今の課題。
- 結局、引越し先を決めるに至らなかった事。(探す方向性を変える必要があると気付けたのは収穫)
要するに 昨年の目標 と見比べて ほぼ進展が無いこと(><
今年の目標
- 引っ越ししたい
- 俺々言語に進捗を
- fsharp 勉強しなきゃ… docker もそろそろ…
- 彼女(嫁)探しを諦めない…
Smalltalk の勉強会に参加してきました(ハッカソン編)
私にとって Smalltalk は学生時代から憧れの言語で、にもかかわらず何度挑戦しても (Emacs keybind が使えないがゆえに) 挫折を繰り返していた言語です。何とか突破口を開けられないかと参加させてもらいました。
以下、教えて頂いたこと・調べたことの備忘録です。間違いなど有るかもしれませんのでツッコミ歓迎です。
ハッカソン(午前)
私は Smalltalk 初心者なので、Pharo5 のキーボード・マッピング周りの仕組みを調べて学ぶことにしました。
途中、梅澤さん mumez (Masashi Umezawa) · GitHub に Pharo のショートカット周りのクラスやそのオーバーライド方法、 クラスブラウザの操作方法を色々教えて頂きました。
PharoShortcuts current
で取り出されるインスタンスを通して、ショートカットが設定されているらしいので、ここをオーバーライドすれば出来そう、とのこと。(ただ、そのために PharoShortcuts クラス自体もいじらなければならない。)- この使用箇所を探すとき、クラスブラウザ上でクラスを選んで右クリック→
Analyze
→Class refs...
が役に立つことを見せて頂きました。 - インスタンス変数は大文字はじまり、と。
- この使用箇所を探すとき、クラスブラウザ上でクラスを選んで右クリック→
- (Web ブラウザの開発ツールに有るような) マウスで画面要素を直接指して中身を Inspect したい時は、
Shift + Mouse Middle
で Halo を呼びだせば良いそう。
雑談
お昼ごはんの時にも雑談で色々教えて頂きました。
- イベントループはどこに有るの? →
WorldState
にあるよ - Pharo にも Object Table は有るの? → ある。 Newspeak, Pharo, Squeak は VM (の設計?) が同じ
allInstance
の逆はあるの? →allOwners
というのがある。(Morph のみ?)- バージョン管理機能があると聞いたけど、 CompiledMethod はイメージ内に複数持つの?一つだけ? → 一つだけ。
あと、皆さん pharo を一番使ってたのが印象的でした。 pharo のコア開発者はフランス inria 繋がりの人が多いとか…
<keymap>
は Pragma だった!
以前の挫折ポイントの一つが、この↓ <keymap>
TxTextEditorMorph>>#buildTextEditorKeymapsOn: aBuilder <keymap> { Character home. #moveToLineStart. Character home shift. #selectToLineStart. ...
これは Pharo の Pragma 構文で、所謂 コード注記 と同じ役割を果たしているそうです。 (メソッドに属性としてぶら下がる)。 Pharo は Pragma を色々活用しているとのこと。
ハッカソン (午後)
ところが PharoShortcuts
の中身をよく見たところ、肝心のカーソル移動が定義されていないことに気づきました。
これでは Emacs 風のカーソル移動は定義できません。
そこで方針を変えて、そもそもキーマップ機能と、イベントループ周りがどんな仕組みになっているのかを調べることにしました。
キーマップ関連
TxTextEditorMorph>>#initializeShortcuts: aKMDispatcher aKMDispatcher attachCategory: #TxTextEditorMorph
KMDispatcher というのが関係が深そう…
クラスのコメントを順々に抜き書きしてみます。
KMDispatch
I'm an object that saves a buffer of keyevents for the morph I'm attached. I am the one that dispatches the single and multiple shortcuts. If the morph has a keymap that matches the keyboard event, I tell the keymap event to execute with the morph I'm attached.
KMRepository
I have a singleton instance which can be accessed by executing the following: "self default" I am currently a god object to be refactored =D.
KMRepository>>#reset World setProperty: #kmDispatcher toValue: nil. self default: self new. KMCategory allSubclasses select: [ :c | c is GlobalCategory ] thenDo: [ :c | c new installAsGlobalCategory ]. KMPragmaKeymapBuilder uniqueInstance reset.
KMPragmaKeymapBuilder
I am a singleton object, subscribed to system events, to listen to the creation of methods marked with the <keymap> and keymap:> pragmas. When I listen one of those events, I reinitialize the KMRepository default instance and reload it with all declared keymaps.
う〜ん、らちがあかない…
イベントループ
下から追うのが難しそうなので、今度はイベントループを調べてみます。
教えて頂いた WorldState
と、グローバル変数 World
(こちらは WorldMorph
のインスタンス) が関係が深そう。
WorldMorph>>#doOneCycle worldState doOneCycleFor: self. WorldState>>#doOneCycleFor: aWorld self interCyclePause: MinCycleLapse. self doOneCycleNowFor: aWorld.
WorldState>>#doOneCycleNowFor: aWorld "Immediately do one cycle of the interaction loop. This should not be called directly, but only via doOneCycleFor:" DisplayScreen checkForNewScreenSize. "process user input events" LastCycleTime := Time millisecondClockValue. self handsDo: [:h | ActiveHand := h. h processEvents. "…ここが肝ぽい…" ActiveHand := nil ]. "the default is the primary hand" ActiveHand := self hands first. aWorld runStepMethods. "there are currently some variations here" self displayWorldSafely: aWorld.
displayWorldSafely: aWorld "Update this world's display and keep track of errors during draw methods." [aWorld displayWorld] ifError: [:err :rcvr | "Handle a drawing error" | errCtx errMorph | errCtx := thisContext. [ errCtx := errCtx sender. .... "If the morph causing the problem has already the #drawError flag set, then search for the next morph above in the caller chain." errMorph hasProperty: #errorOnDraw ] whileTrue. errMorph setProperty: #errorOnDraw toValue: true. "Install the old error handler, so we can re-raise the error" rcvr error: err. ].
↑今読み返すと WorldState>>#doOneCycleNowFor: aWorld
の中の ActiveHand processEvent
が鍵ぽいのですが、
この時点では読み取れませんでした。
キー入力周り
再び下から、でも目線を変えてキー入力に関係するコードを探してみようと考えました。
Smalltalk なので、メタキーを扱うコードが有るはず… #meta
の sender を見る…
→ SimulateKeystrokesSpecification>>#testSimulateCmdKeystroke
の中に self simulateKeyStrokes: ...
というコードを発見。
Morph>>#simulateKeyStroke: aCharacter |event| event := KeyboardEvent new setType: #keystroke buttons: 0 position: 0@0 keyValue: aCharacter charCode charCode: aCharacter charCode hand: ActiveHand stamp: 0. self keyStroke: event
KeyboardEvent
クラス! この Class ref から、 HandMorph>>#generateKeyboardEvent
を見つけました。
後はこれの sender を探すのみ。
HandMorph>>#processEvents "Process user input events from the local input devices." | evt evtBuf type hadAny | [(evtBuf := Sensor nextEvent) isNil] whileFalse: [evt := nil. "for unknown event types" type := evtBuf first. type = EventTypeMouse ifTrue: [recentModifiers := evtBuf sixth. evt := self generateMouseEvent: evtBuf]. type = EventTypeKeyboard ifTrue: [recentModifiers := evtBuf fifth. evt := self generateKeyboardEvent: evtBuf]. type = EventTypeDragDropFiles ifTrue: [evt := self generateDropFilesEvent: evtBuf]. type = EventTypeWindow ifTrue: [evt := self generateWindowEvent: evtBuf]. "All other events are ignored" (type ~= EventTypeDragDropFiles and: [evt isNil]) ifTrue: [^self]. evt isNil ifFalse: ["Finally, handle it" self handleEvent: evt. "多分ここで、 HandMorph>>#handleEvent:evt が呼ばれる" hadAny := true. ...]]. ...
そして HandMorph>>#handleEvent:anEvent
が呼ばれる、のではないか…
今日たどり着いたのはここまで!
(でもキーマッピングの話はまだ遠いにょ…)
おしまい
これからビールを飲みながら勉強会です。
zparseopts で posix style long option と一文字オプションを両立させる
Zsh の zparseopts を使って
-t TYPE
--type TYPE
--type=TYPE
全部の書き方をサポートしたい場合にどう書くか。 (代入先の配列名を同じにするだけ、だけど)
値の取り出しの書き方を忘れがちなので、メモ。
(我流なので、ツッコミ歓迎です)
zparseopts がどう働くか
% set -- -t foo % zparseopts -D -K h=o_help t:=o_type % typeset o_type o_type=( -t foo ) %
% set -- --type foobar % zparseopts -D -K h=o_help t:=o_type -type:=o_type % typeset o_type o_type=( --type foobar ) %
% set -- --type=foobar % zparseopts -D -K h=o_help t:=o_type -type:=o_type % typeset o_type o_type=( --type '=foobar' ) %
オプションの検査
配列のサイズを見るのが、良いのではないかと…
if (($#o_type)); then # …オプションが指定されている時の処理… fi
値の取り出し
値の先頭が = で始まる =なんとか
みたいな文字列を渡すことを諦めれば
↓この書き方で行ける。
% print -- $o_type[2] =foobar % print -- ${${o_type[2]}#=} foobar %
.oO(…もっと短く覚えやすい書き方があれば、是非教えて下さい…)