【Alfred Workflow】Script Filterで自作コマンドを開発する
せっかくAlfred Powerpackに入っているのでもっと使いこなしたいと思い、Workflowを使ってツールを作ってみた。
作ったのはこんな感じのモールス信号解読機。趣味でパズルを解いたりしていると、ふとしたときに意外と出てくるので、爆速で立ち上がるAlfredで解読したいと思っていた。
Alfred Workflowとは
Alfred Workflowを使うと、Alfredを拡張し、独自のキーワードで様々な機能を開発することができる。ただし有料ライセンスであるPowerpackを購入していることが条件。
全体の流れはGUIで作成するが、ビジネスロジックの部分はスクリプトで書くことができる。対応言語もBash, PHP, Ruby, Pythonなどいろいろある。
Script Filterを設定する
今回のツールでは、
- モールス信号を入力している間に結果がリアルタイムで下に表示され、更新されていく
- Enterキーを押すと結果がクリップボードにコピーされる
という挙動にしたかったので、Script Filterを使用した。
Script Filterはもともとは検索機能のために用意されているもので、スクリプトの実行中にAlfredに結果を表示できる。また、どのような書式で結果を表示するかもカスタマイズできる。
Script Filterを設定する
Alfred Preferenceの画面からWorkflows
を選ぶと、新しいWorkflowを開発できる。ちなみにAlfredの入力画面で alfred
と入力すれば、Alfred Preferenceに飛べる。
右クリックで Inputs
以下にある Script Filter
を選択すると、新しいScript Filterを作成できる。また、最終結果をクリップボードに渡したいので、Outputとして Copy to Clipboard
を選び、Script Filterから線をドラッグしてつないでおく。
Script Filterを作成すると、以下のような設定画面になる。 Keyword
の欄には、このworkflowを開始するためにAlfred上で入力するキーワードを指定する。引数はqueryとargvのどちらでも良いが、エスケープの心配がいらない点やパフォーマンス面から、Alfredではargvを推奨している。
Run Behaviour
からは、入力中のスクリプトの挙動を設定できる。文字をどんどん入力していったら前の段階での結果はいらなくなるので、Queue Mode
は Terminate previous script
にする。また、結果のリアルタイム表示といっても、入力が一段落した時点でスクリプトを実行すれば十分なので、 Queue Delay
は Automatic delay after last character typed
にする。
スクリプトを作成する
今回はPythonで作成した。日本語と英語の解読ができれば良かったので、モールス信号の辞書をそのまま貼ったのだが、その部分は長いので省略している。
# coding: utf-8 import json import sys MORSE_CODE_DICT_EN = { 'a': '.-', 'b': '-...', 'c':'-.-.', 'd':'-..', 'e':'.', 'f':'..-.', ... } MORSE_CODE_DICT_JA = { 'あ': '--.--', 'い': '.-', 'う': '..-', 'え': '-.---', 'お': '.-...', ... } def decrypt(lang, input): if lang == 'ja': return _morse_to_letters(input, MORSE_CODE_DICT_JA) elif lang == 'en': return _morse_to_letters(input, MORSE_CODE_DICT_EN) else: return 'Invalid argument. Usage: <lang (`ja` or `en`)> <morse code (`.` or `-`, split by space)>)' def _morse_to_letters(input, morse_dict): if not input: return 'No morse code input.' reversed_dict = {value:key for key,value in morse_dict.items()} result = '' for m in input: result += reversed_dict.get(m, '#') return result if __name__ == '__main__': args = sys.argv[1].split() lang, input = args[0], args[1:] result = { 'items': [{ 'uid': 'morse', 'title': decrypt(lang, input), 'arg': decrypt(lang, input) }] } sys.stdout.write(json.dumps(result, ensure_ascii=False))
注意すべきポイントは3つある。まず、通常のPythonスクリプトのsys.argv
とは異なり、Script Filterではキーワードの後に入力した引数全てがsys.argv[1]
に入る。例えば morse ja .-- --.--
と入力する場合、ja .-- --.--
がsys.argv[1]
の値になる。最終的なargsの数を予測できないので当然といえば当然だが、自分はここでかなりハマった。
2つ目は、Alfred WorkflowはPython 2のみに対応していること。これはWorkflowがMacのビルトイン環境のみで動くことを重視しているためらしい。今回の場合、コード内でひらがなを使っているため、冒頭に # coding: utf-8
をつけないとエラーになった。
最後に、Script FilterからAlfredに結果を渡すためには、専用のJSONまたはXMLフォーマットに従う必要がある。JSONフォーマットは以下のページに記載されている。
複雑なWorkflowになると、専用のライブラリ(例: alfred-workflow )を使うことも多いが、今回は単純なスクリプトなので手動でJSONを作った。
{ 'items': [{ 'uid': 'morse', 'title': decrypt(lang, input), 'arg': decrypt(lang, input) }] }
uid
はAlfredが結果のitemを識別するためのID(必須ではない)。titleの部分に解読結果を載せている。また、 args
に指定された値は、Enterキーを押した時点でOutputに渡される。今回の場合、argsにも解読結果を指定することで、最終的な結果をクリップボードに渡すことができる。
この他にも任意のフィールドがたくさんあり、アイコンの指定などもできる。
これでいつものAlfredウィンドウから、morse
コマンドが使えるようになった。
なお、Alfred Workflowにはデバッグツールも用意されているので、Script Filterが思うように動かない場合はログを確認してみよう。