エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

AppleScrptでCotEditorのドキュメントをsaveするときにエラーがでる原因を探っていたがやっぱりわからなかった

一時撤退宣言をしてから3週間。どこかに攻略の糸口がないかと探っていたものの…結局わかりませんでした。AppleScriptObjective-Cおよびそれらのフレームワークの知識が絶対的にたりません。事実上敗北宣言デス。


3週間の戦況観察でわかったことをまとめておきます。

わからないなら自分で再現させてみる

と、いうわけで。AppleScriptを実行するプログラムを自分で書いみた。

CotEditorのコードを参考に書いたスクリプトを実行するだけの簡単なコード。こんな感じ。

#import <stdio.h>
#import <Cocoa/Cocoa.h>

void launchScript(const char* path)
{
    NSDictionary* error   = nil;
    NSURL*        url     = [NSURL fileURLWithPath:[NSString stringWithUTF8String:path]];
    NSAppleScript* script = [[[NSAppleScript alloc] initWithContentsOfURL:url error:&error] autorelease];

    if(script == nil)
    {
        return;
    }

    NSAppleEventDescriptor* descriptor = [script executeAndReturnError:&error];

    NSLog(@"result:descriptor %@\n", descriptor);
    NSLog(@"result:error      %@\n", error);

    if((descriptor == nil) && (error != nil))
    {
        NSLog(@"%@\nErrorNumber: %i",@"", [error valueForKey:NSAppleScriptErrorMessage],
                                          [[error valueForKey:NSAppleScriptErrorNumber] intValue]);
    }

}

int main(int argc, char* argv[])
{
    id pool = [[NSAutoreleasePool alloc] init];

    int i;
    for(i = 1; i < argc; ++i)
    {
        NSLog(@"execute %s\n", argv[i]);
        launchScript(argv[i]);
    }

    [pool release];

    return 0;
}


ビルドはこんな感じ

$ gcc -o launchScript launchScript.m -framework Foundation


CotEditorの方は、動きをトレースするためにCEDocumentにハンドラを追加。

- (id)handleSaveScriptCommand:(NSScriptCommand *)command
{
    id result = [super handleSaveScriptCommand:command];
    NSLog(@"%@", [command scriptErrorString]);
    return result;
}


これで件のAppleScriptスクリプトを実行させてみたところ…エラーが出ない。ナゼ?

内部から実行したときと外部から実行したときで動きが違う…?

スクリプトを書き換えながら格闘していたら、おやっ?と思うシーンに出くわしました。
要は、外部からAppleScriptを実行してCotEditorを操作しときと、CotEditorがAppleScriptを実行してCotEditor自身を操作したときで動作が違う。


こんなスクリプトを用意します。

tell application "CotEditor"
    save front document
end tell


ここではファイル名を指定していないので、操作する対象が新規のドキュメントだった場合はファイル名を入力するパネルが表示されます。

CotEditorの動作を追っていくと、上記のハンドラhandleSaveScriptCommand:の中でパネルが表示されますが、ファイル名の入力を待たずにハンドラは終了します(handleSaveScriptCommand:から抜けてしまいます)。
このとき外部から実行した場合、たとえば上の例ではハンドラが終了しても[script executeAndReturnError:&error]からは復帰しません。ファイル名を入力するかキャンセルするか一定時間経過するまでどこかでブロックされているようです(キャンセルしたばあいや一定時間が経過したばあいはエラーで復帰します)。


一方、CotEditorから実行したばあい、同じようにパネルが表示されハンドラから抜けますが、ハンドラか抜けるとすぐにAppleScriptを実行しているコードに復帰します。そしてそのときのスクリプトの実行結果がエラーになっています。


どうやら自分でAppleScriptを起動して自分自身を操作したときになにやら問題がでるようです。

あらためてNSAppleScriptのリファレンスを読んでみると。「Important: You should access NSAppleScript only from the main thread.」と強調されています。ただこれが関係するのか否か正直わかりかねているところ。


…と、ここでGive up。

以下、余談という名の言い訳

(なので折り畳む)
もともとの動機は新規作成したドキュメントを「保存しますか?」と問われずに閉じる方法を見つけるということでした。そこでどこかに保存してしまえば(とりあえず)解決するはず、という動機でAppleScriptでドキュメントを保存する方法を探っているうちに今回のことにぶちあったというのが経緯。


その意味では、今のままでこのことに臨むのはいささか動機として弱い、かな、と。
ここで根を上げるのはいささか不甲斐ないのですが、これを解決するためにはAppleScriptObjective-C、加えてそれらのフレームワークにもう一歩踏み込まないといけないとわかってきて、今のような「なにかのついで」的な文脈でどうにかしようとするのは甘い考えとわかってきました。


一方でAppleScriptObjective-Cに俄然興味がわいてきたというのも事実。
先日OS X Lionが公式に発表されましたが、その新機能の中でAppleScriptが紹介されていますAppleScriptは過去の遺産ではなく今後も中心となる機能ととらえられているようです。


近いうちに、しきりなおして、再挑戦。きっと。