コマンドライン型ランチャーとexplorerの連携

20070225: ちょっとコマンドを追加してみました。verbsとlsです。また、ウインドウタイトルではなくウインドウハンドルを使ってCOMのオブジェクトを探索するようにしました。

こちらにも書いたとおり、Windowsではコマンドライン型ランチャーのcraftlaunchとexplorer.exeを連携させて使ってます。

最前面のディレクトリ(アクティブなディレクトリ)に対して、キーボードで、Linuxと同じ感覚でmkdirとかrmとかしたいなー、ってのを実現してみました。

今まではC言語で作った自前のライブラリで処理していたんですが、COMを使ったものに書き直しました。同様の目的にはAHKが使えるんですが、やっぱり使い慣れた言語でいろいろコマンドを作りたかったのでCOM+Javascriptにしてみました。もちろん、COMなのでRubyでもPerlでもPythonでも同様の実装が作れます。

craftlaunchを前提にしてますが、コマンドライン型のランチャーならなんでも適応できる方法だと思います。explorerを使っていてもキーボードだけでmkdir,rm,mvなんかが実行できちゃって、非常に便利です。

ダウンロード

簡単なコマンドラインランチャーもどきも同梱してますので、現在コマンドライン型ランチャーをいれていない人でも試せます。

以下readmeからの転載です。

利用方法

explorehelper.wsf,explorehelper.js,getadir.exeは同じフォルダになければ なりません。

 code
  1. explorehelper.wsf [command] [args]
  2.  

のように実行します。craftlaunchの場合はexplorhelper.wsfをコマンド登録し、 ctrl-eなどにショートカット登録しておくと良いでしょう。すると、 mkdirと入力→ctrl+eで最前面のフォルダに、フォルダを作成できます。

それぞれのファイルについて

simple_launcher.exe

コマンドライン型ランチャーを導入していない方でも試せるように、AHKで作った 簡単なランチャーを同梱してあります。ただしこのランチャーは

  • ホットキー(有効化)はalt+space
  • コマンド実行はctrl+e

に固定されています。つまり

「起動」→(「alt+space」でアクティブに)→「command入力」→「ctrl+e」で実行

という操作になります。気にいった人はちゃんとしたコマンドライン型ランチャー の導入をオススメします。

getahwnd.exe

最前面のExplorer.exeのウインドウハンドルを出力するだけのプログラムです。

explorehelper.js

JscriptによるCOMに対するヘルパーです。

explorehelper.wsf

Jscriptによるカスタムコマンドが定義されたファイルです。このファイルを編集 することによってcommandが自由に追加できます。

ビルトインコマンド

  • mv
  • mkdir
  • cmd
  • touch
  • rm
  • chvm : 表示モードを変更します。
  • ls : -x, -s, -tオプションのみ受け付けます。つまりフォルダ内をソートします。
  • verbs : 右クリックメニューを出します。shift+F10と同じ効果です。

説明の無いものはUnixのそれから類推してください。

カスタムコマンドの追加について

explorehelper.wsfを編集することによってcommandが自由に追加できます。

 code
  1. var procs = {
  2.   mv : function() {
  3.     var path = get_dir_and_focused_item_path();
  4.     var name = InputBox("新しい名前を入力してください。", path.item._base_name());
  5.     if(name) get_dir_and_focused_item().item.Name = name;
  6.   }._item_proc()._auto_win_activate(),
  7.  
  8.   mkdir : function() {
  9.     var path = get_dir_and_focused_item_path();
  10.     var name = InputBox("フォルダ名を入力してください。");
  11.     if(name) fs().GetFolder(path.win).SubFolders.Add(name);
  12.   }._auto_win_activate(),
  13.   .
  14.   .
  15.   .
  16.   .
  17.  

commandは上記のように、procオブジェクトのプロパティとして定義されます。その際、 command名がキー、値は引数なしの関数になります。キーに大文字は使用できません。

commandを定義する際にはexplorehelper.jsで定義されたヘルパを使用することができます。 カスタムcommandを定義しようとする人は当然、JScript(Javascript)が理解できる人 だと思いますので、ヘルパの詳細はexplorehelper.jsを見てください。ここではリスト のみ紹介します。

  • InputBox : 入力フォームを表示します。
  • MessageBox : メッセージを表示します。
  • Confirm : 確認フォームを表示します。
  • shell : Shell.Applicationオブジェクト返します。
  • wscript_shell : WScript.Shellオブジェクトを返します。
  • fs : Scripting.FileSystemObjectを返します。
  • win_activate : ディレクトリをアクティブにします。SendKeysする前に実行します。
  • get_dir_and_focused_item : アクティブなディレクトリと選択されているアイテムを返します。
  • get_dir_and_focused_item_lst : アクティブなディレクトリと選択されている アイテムのリストを返します。
  • get_dir_and_focused_item_path : アクティブなディレクトリと選択されているアイテムのパス を返します。
  • get_dir_and_focused_item_path_lst : アクティブなディレクトリと選択されている アイテムのパスのリストを返します。
  • $wsh_args : argsを格納した配列です。
  • Function.prototype._item_proc : アイテムに対する手続きであることを宣言します。 アイテムを選択していない場合、処理が実行されなくなります。
  • Function.prototype._auto_win_activate : 手続きを終了後、ウインドウをアクティブにします。

こんな感じです。COMなら大概なんでもできるので、便利です。しかもJScriptとexeなのでUSBメモリなどにいれて持ち運べますので、俺ポータビリティもそこそこあります。

また、いかにAHKスクリプトが簡単でも、普段使い慣れた言語でかけるほうが落ち着きます。AHKなんかをみてると、完全なDSLというのは俺的に扱うのがめんどくさくて(覚える熱意があればいいんだろうけど、趣味で使うものにそれほどの情熱がでるかというと・・・)なじみにくいようだ。Rakeみたいな言語内DSLがいいよね、やっぱ。ああ、2007年はRubyの年になるんでしょうかねえ。

07.27.08/12am

Ajax.InPlaceEditorでバリデーションに引っかかったら

nil.to_sが"nil"になるような言語は(ry というのは置いといて、まだまだPythonをリハビリ中。
Rubyより関数志向での開発がしやすくて楽しいですよねえ。

リハビリを兼ねてweb.pyで個人的なツールを作ってます。web.pyはシンプルで軽いので、個人用途(非公開でツールとして使う)だったらレンタルサーバーでCGIとして動作させても問題なさそうです。(XREAでは簡単なものなら負荷率0ptでした。Railsとかならいれるだけでダメなんで、素晴らしい)

そこで、scriptaculousのAjax.InPlaceEditorを使っているわけですが、困ったことがひとつ。InPlaceEditorなわけで、値を当然入力します。でその値がサーバー側でバリデーションエラーな時どうしたもんかと。

理想的な動作としては

  • エラーをユーザーに通知する

  • サブミットする前の値のままtextareaを表示しておく

なんですが、コンストラクタの中のoptionsあたりをのぞいてもそれらしきオプションはなさげ。

ということでとりあえず場当たり的に書いてみたものが以下。

javascript code
  1. (function(){
  2. Ajax.RollbackableInPlaceEditor = Class.create();
  3. var dummy = function(){};
  4. dummy.prototype = Ajax.InPlaceEditor.prototype;
  5. Ajax.RollbackableInPlaceEditor.prototype = new dummy;
  6. Ajax.RollbackableInPlaceEditor.prototype.constructor = Ajax.RollbackableInPlaceEditor;
  7. })();
  8. Object.extend(Ajax.RollbackableInPlaceEditor.prototype, {
  9.   initialize : function(element, url, options) {
  10.     var self = this;
  11.     options = Object.extend({
  12.       callback: function(form) {
  13.         self.oldValue = self.editField.value;
  14.         return Form.serialize(form);
  15.       },
  16.       onFailure : Prototype.emptyFunction
  17.     }, options || {});
  18.     Ajax.InPlaceEditor.prototype.initialize.apply(this, [element, url, options]);
  19.   },
  20.   onclickCancel: function() {
  21.     this.onComplete();
  22.     this.element.innerHTML = this._oldInnerHTML;
  23.     this.element.show();
  24.     return false;
  25.   },
  26.   onComplete: function(transport) {
  27.     this.leaveEditMode();
  28.     if(transport && !Ajax.Base.prototype.responseIsSuccess.call({transport:transport})) {
  29.       this.options.onFailure.bind(this)(transport, this.element);
  30.       return this.enterEditMode();
  31.     }else {
  32.       this.oldValue = null;
  33.     }
  34.     this.options.onComplete.bind(this)(transport, this.element);
  35.   },
  36.   onFailure: function(transport) {
  37.     return false;
  38.   },
  39.   getText : function() {
  40.      if(this.oldValue != null && this.oldValue != undefined){var v = this.oldValue; this.oldValue = null; return v;}
  41.     this._oldInnerHTML = this.element.innerHTML;
  42.     return this.element.innerHTML;
  43.   }
  44. });
  45.  

サーバー側でエラーがあったときは適当に500とか406とかでレスポンスを返せばオッケーです。

なのでサーバー側ではたとえばweb.pyなら

python code
  1.     # errors = ['データが長すぎます。'] など
  2.     if len(errors) != 0:
  3.       web.header("Content-Type", "text/javascript"),
  4.       web.ctx["status"] = "406 Not Acceptable"
  5.       print "".join(["alert('", "\n".join(errors), "');"])
  6.       return
  7.  

とすればエラーになって(エラーメッセージがアラートされる)、サブミットした内容のままで再度編集できます。

豆知識ですが、以上のようにサーバー側でtext/javascriptでレスポンスを返すとprototype.jsでは自動的にjavascriptとして評価してくれます。

うーむ、まさに場当たり的。いろいろまずそうだけど、まぁ動いてるっぽいし気にしないことにしとこう・・・・

07.27.08/12am

$$で属性セレクタ

これはかなり嬉しい機能だなあ。 最近Prototype.jsの$$で属性セレクタが使用できるようになりました。

どんな風にかけるのか、コードをテストから引っ張ってきてみました。

javascript code
  1. $$('a[href="http://inforno.net/#"]')
  2. $$('a[class~=internal]')
  3. $$('*[xml:lang|="es"]')
  4. $$('*[xml:lang|="ES"]')
  5. $$('a[href!=#]')
  6. $$('div[style] p[id] strong')
  7. $$('a[class~=external][href="http://inforno.net/#"]')
  8.  

こんな感じです。 値はクオートしてもしなくても大丈夫っぽいですね。

すばらすい。inputに対する操作なんかで特に便利。今までちょっとボタン操作するためだけに、そのボタンにIDふったりクラスふったりしてたんですけど$$('#hoge input[type=submit]')でいけるんだなあ。そんなにパフォーマンスに気をつかわなくて良いページでは使ってみよう。

07.27.08/12am

About

Author:yuin(http://inforno.net/)

文学部文化学科卒という生粋の文系趣味プログラマ。

主にRuby、Javascript、PHP、JAVA,Python,C,Scala,Schemeなどを使っています。今はPythonな感じかもしれない。今後作曲活動なども復活するかもしれない。

Pages