Automator と AppleScript

Automator で作業をしていると、同じような作業を繰り返していることに気がつきます。定型作業を自動化させるのが Automator の目的なのに、その Automator で同じ作業を繰り返しているなんて。どうも、腑に落ちません。

Automator は登場した当初から AppleScript に対応していました。登場した当初はどうにも重たくて、以来使っていませんでした。Leopard の Automator で「変数」が使えるようになり、「ほう」と思ったものですが、やはりほとんど使いませんでした。

Snow Leopard インストールを期に使ってみたのですが、Automator 自身を Automator で操作できるわけでもなく、Automator の自動化は AppleScript からという環境に変わりはないようです。

唯一、変わったことといえば AppleScript からは「変数」が操作できるようになっていることぐらいでしょうか。反面、新規ワークフローの作成はスムーズにできなくなっています(必ずテンプレートを選択するシートが表示されるので)。

最初にも書いたように Automator は定型作業の繰り返しです。どのワークフローもおおむね同じようなアクションと変数を使います。特に「サービス」関連のワークフローにその傾向があります。

また、デバッグ時に同じようなアクションを何度も追加したり、削除したり...という作業が少なからず発生します。これが、結構手間だったりします。そのためでしょうか、Automator には add と remove という命令が備わっています。

Script Editor で開く

tell application "Automator"
    if not (front workflow exists) then return

    add Automator action "選択された Finder 項目を取得" to front workflow at index 1
end tell

名前参照で書いていますが、互換性を考えるなら Automator action の bundle id 属性などを使う方がいいと思います。

Script Editor で開く

tell application "Automator"
    if not (front workflow exists) then return

    set bundle_identifier to "com.apple.Automator.Get_Selected_Finder_Items_2"
    add Automator action id bundle_identifier to front workflow at index 1
end tell

ファイルやフォルダを処理するサービスなどは「選択された Finder 項目を取得」アクションを最初においてワークフローのデバッグを行うのですが、デバッグが終われば必要なくなります。remove 命令を使えば不必要なアクションや変数を取り除くことができます。

Script Editor で開く

tell application "Automator"
    if not (front workflow exists) then return

    set bundle_identifier to "com.apple.Automator.Get_Selected_Finder_Items_2"

    tell front workflow
        set remove_actions to Automator actions whose bundle id is bundle_identifier
        repeat with this_action in remove_actions
            remove this_action
        end repeat
    end tell
end tell

デバッグ時には特定のアクションの使用、不使用の切り替えを行いたいときがあります。Automator action の enabled 属性で切り替えることができます。次のスクリプトでは使用されているアクションの中から選択されたものを使用停止にします。

Script Editor で開く

tell application "Automator"
    if not (front workflow exists) then return

    tell front workflow
        set action_list to a reference to (Automator actions whose enabled is true)
        if (count of action_list) is 0 then return

        set action_names to name of action_list
        set use_actions to choose from list action_names default items (item 1 of action_names) with multiple selections allowed
        if use_actions is false then return
        repeat with this_action in use_actions
            set enabled of Automator action this_action to false
        end repeat
    end tell
end tell

変数なども同じように add, remove 命令で追加や削除が行えます。次のスクリプトは変数の一覧の中から選択されたものをワークフローに追加します。

Script Editor で開く

tell application "Automator"
    if not (front workflow exists) then return
    set variable_list to name of variables
    my combSort(variable_list)
    set used_variables to choose from list variable_list default items (item 1 of variable_list) with multiple selections allowed
    if used_variables is false then return

    repeat with this_value in used_variables
        add variable this_value to front workflow
    end repeat
end tell

on combSort(theList)
    set gap to count theList

    repeat
        set gap to (gap * 10) div 13
        if gap is 0 then set gap to 1

        set flag to false
        repeat with i from 1 to (count theList) - gap
            if (item i of theList) is greater than (item (i + gap) of theList) then
                set flag to true
                set tmp to item i of theList
                set item i of theList to item (i + gap) of theList
                set item (i + gap) of theList to tmp
            end if
        end repeat
        if not ((gap is greater than 1) or flag) then exit repeat
    end repeat
end combSort

変数などは複数のワークフローで使い回したいときがあります。テンポラリなファイルの保存先などはそういったものの一つです。あるワークフローにある変数を他のワークフローにコピーする...こういうことも add 命令で可能です。次のスクリプトはワークフローの変数を他のワークフローにコピーするスクリプトです。

Script Editor で開く

tell application "Automator"
    activate
    if (count workflows) < 2 then return

    set name_list to name of workflows

    set source_document to choose from list name_list default items (item 1 of name_list) with prompt "Select source workflow:"
    if source_document is false then return
    set source_document to source_document as text

    set name_list to my remove_item(name_list, source_document)

    set destination_document to choose from list name_list default items (item 1 of name_list) with prompt "Select destination workflow:"
    if destination_document is false then return
    set destination_document to destination_document as text

    set original_variables to name of variables of workflow source_document
    set used_variables to choose from list original_variables default items (item 1 of original_variables) with multiple selections allowed
    if used_variables is false then return
    repeat with this_value in used_variables
        set contents of this_value to variable this_value of workflow source_document
    end repeat

    repeat with this_value in used_variables
        add this_value to workflow destination_document
    end repeat
end tell

on remove_item(the_list, removed_item)
    set new_list to {}

    repeat with this_item in the_list
        set this_item to contents of this_item
        if this_item is not removed_item then
            set end of new_list to this_item
        end if
    end repeat

    return new_list
end remove_item

保存されたワークフローの実態はパッケージなんですが、パッケージの中身は至ってシンプルで document.wflow というファイルがあるだけです(サービスなんかだと Info.plist も追加されるけど)。このファイルはすべての情報が書かれているプロパティリストファイルです。ファイルの中をのぞくだけで、そのワークフローがどんなアクションを利用しているかが分かります。

ダウンロードしてきたワークフローの中身を確認するのに Automator を起動する必要はなく、このファイルを見るとすべてが分かります。このことを利用して、Finder で選択されているワークフローで使われているアクションの一覧を表示するスクリプト。

Script Editor で開く

set workflow_plist to ":Contents:document.wflow"

tell application "Finder"
    set current_selection to selection
    if selection is {} then return

    set info_list to {}
    repeat with this_item in current_selection
        set this_item to this_item as text
        set plist_file to this_item & workflow_plist
        if exists (alias plist_file) then
            set workflow_info to my get_action_info(POSIX path of plist_file)

            if workflow_info is not {} then
                set action_text to my join(workflow_info, return & tab)
                set end of info_list to displayed name of alias this_item & return & tab & action_text & return
            end if
        end if
    end repeat
end tell

if info_list is not {} then
    tell application "TextEdit"
        activate
        set the_document to make new document
        set text of the_document to my join(info_list, return)
    end tell
end if

on join(the_list, delimiter)
    tell (a reference to text item delimiters)
        set {tid, contents} to {contents, delimiter}
        set {the_text, contents} to {the_list as text, tid}
    end tell

    return the_text
end join

on get_action_info(workflow_file)
    tell application "System Events"
        set plist_file to property list file (workflow_file)
        tell plist_file
            set action_info_list to {}
            repeat with this_item in property list items of property list item "actions"
                set end of action_info_list to value of property list item "ActionName" of property list item "action" of this_item
            end repeat
        end tell
        return action_info_list
    end tell
end get_action_info

サービスをいくつか作っていると、メニューに表示される名前を変更したいときがあります。これはワークフローの名前を Finder で変更しても変わるわけではなく、変更するには Automator でワークフローを開き別名で再保存するか、ワークフローのパッケージ内にある Info.plist の値を変更する必要があります。いちいちそういうことをするのも面倒なので、作ったのが次のスクリプト。

Script Editor で開く

property file_extensions : {".workflow"}

tell application "Finder"
    set current_selection to selection
    if current_selection is {} then return

    set workflow_files to {}
    repeat with this_item in current_selection
        set file_name to displayed name of this_item
        set this_item to POSIX path of (this_item as text)
        set {file_name, ext} to my splitext(file_name)
        if ext is in file_extensions then
            set plist_file to this_item & "/Contents/Info.plist"
            my set_defaut_menu_item_name(plist_file, file_name)
        end if
    end repeat
end tell

on set_defaut_menu_item_name(plist_file, document_name)
    tell application "System Events"
        if not (disk item plist_file exists) then return missing value

        tell property list file plist_file
            if not (property list item "NSServices" exists) then return missing value
            tell property list item "NSServices"
                if not (property list item "NSMenuItem" of property list item 1 exists) then return missing value
                tell property list item "NSMenuItem" of property list item 1
                    set menu_text to value of property list item "default"
                    if menu_text is not document_name then
                        set value of property list item "default" to document_name
                    end if
                    return value of property list item "default"
                end tell
            end tell
        end tell
    end tell
end set_defaut_menu_item_name

on splitext(file_name)
    set reversed_name to (reverse of (characters of file_name)) as text
    set num to offset of "." in reversed_name
    if num is 0 then
        set ext to ""
    else
        set reversed_ext to text 1 thru num of reversed_name
        set ext to (reverse of (characters of reversed_ext)) as text
        set ext_num to count ext
        set name_length to count file_name
        set file_name to text 1 thru (name_length - ext_num) of file_name
    end if
    return {file_name, ext}
end splitext

Finder で選択されているワークフローのメニュー名をファイルの名称に変更します。

以上のスクリプト以外にもいくつかまとめて。

いつものことですが、ご利用はご自身の責任でお願いします。

ところで、Automator には AppleScript という変数があります。この変数は変わった変数で、AppleScript の実行結果を変数として利用することができます(「シェルスクリプト」という変数もそうだけど)。変数のレベルで AppleScript やシェルスクリプトが利用でき、かつ、アクションにも同じようなアクションがある...。ここまでくるとやりたい放題ですね。

0 件のコメント :

コメントを投稿