_code.scptライブラリ

coding()ハンドラを発見して以来、試行錯誤を続けてきたAppleScriptのライブラリ_code.scptの最新の状態。

ハンドラ一覧

基本

AppleScriptで何か作るとき、まず最初に必要になりそうなハンドラを定義。

  • split(sourceText, delimiter)
    • sourceTextをdelimiterで区切ったリストを返す。
  • join(sourceList, delimiter)
    • sourceListをdelimiterで接続したテキストを返す。
  • replace(sourceText, text_list1, text_list2)
    • sourceTextのtext_list1をtext_list2に置き換えたテキストを返す。
  • every_replace(sourceText, pair_list)
    • sourceTextをpair_listで置き換えたテキストを返す。
  • json_from(list_recode)
    • list_recodeのJSONを返す。
  • for_key(a_record, a_key)
    • レコードのキーを文字列で指定して、対応する値を返す。
  • set_key_value(a_record, a_key, a_value)
    • レコードのキーを文字列で指定して、値を変更・追加したレコードを返す。
JavaScript連携

AppleScriptJavaScriptと相性がいい。JSONを生成できるようになった今、情報のやり取りの制限はなくなった。AppleScriptに不足する機能は、JavaScriptで補うことも簡単にできる。実益とサンプルを兼ねたJavaScriptを活用するハンドラ集。

  • do_js(code_list_or_text)
    • code_list_or_textのJavaScriptコードを実行する。
  • reject_key(a_record, a_key)
    • a_recordのa_keyを取り除いたレコードを返す。
  • record_keys(a_record)
    • a_recordのキーをリストで返す。
  • record_values(a_record)
    • a_recordの値をリストで返す。(a_record as listと同等)
  • sort_list(a_list)
    • a_listを昇順にソートしたリストを返す。
Ruby連携

正規表現は強力だ。苦労して何十行も書いたAppleScriptコードが、正規表現を利用すればたった1行で簡潔に表現できてしまうこともある。しかし、AppleScriptには正規表現はない。しかし、Rubyを経由して正規表現を活用することはできる。実益とサンプルを兼ねたRubyを活用するハンドラ集。(Rubyだけでなく、Perlやその他の言語でも同じようにできるはず)

  • do_ruby_script(code_list)
    • code_listのRubyコードを実行する。
  • do_ruby_jcode_u(code_list)
    • code_listのRubyコードを日本語環境で実行する。
  • reg(reg_text, str)
    • 正規表現reg_textが、strとマッチするかどうか、真偽値を返す。
  • match_pos(reg_text, str)
    • 正規表現reg_textが、strとマッチした部分の位置を返す。
  • match_str(reg_text, str)
    • 正規表現reg_textが、strとマッチした部分のテキストを返す。
  • gsub(str, reg_text, replace_text)
    • 正規表現reg_textが、strとマッチした部分を、すべてreplace_textで置き換えたテキストを返す。
  • sub(str, reg_text, replace_text)
    • 正規表現reg_textが、strと最初にマッチした部分を、replace_textで置き換えたテキストを返す。
  • number_with_delimiter(num)
    • 数値を3桁区切りしたテキストを返す。
  • printf(format, value)
    • valueをprintfのformat形式にしたテキストを返す。
  • number_to_currency(num, decimal_place, prefix, suffix)
    • 数値numを3桁区切り、小数点以下の桁数decimal_place、先頭記号prefix、末尾記号suffixを設定したテキストを返す。
メッセージ

growlは邪魔にならないメッセージ伝達の方法として、とっても優れている。その機能をAppleScriptからも使いたい。message()ハンドラはgrowlnotifyコマンドを利用してgrowlで通知する。

  • message(title, msg)
    • growlnotifyコマンドがインストールされていればgrowlで表示し、なければdisplay dialogで表示する。

_code.scpt

修正履歴2
  • リストの要素数が1つだけの場合、エラーが発生する不具合を修正。
  • ハンドラ名の変更。
    • record_keys → keys
    • record_values → values
    • sort_list → sort
修正履歴1
コード
  • (* コメントブロック *)コメントブロック内は、サンプルコードとその実行結果。


(*
* CODEライブラリ
* ロード方法の例1(このライブラリをユーザースクリプトフォルダに"_code.scpt"で保存した場合)
* 親スクリプトとして...(_code.scptの内容をそのままコピーしたのと同等な環境になる)
* property parent : load script file ((path to scripts folder as text) & "_code.scpt")
*
* あるいは静的なCDオブジェクトとして...(_code.scpt側を変更しても、loadしているスクリプト側で保存し直さないと反映されない)
* property CD : load script file ((path to scripts folder as text) & "_code.scpt")
*
* あるいは動的なCDオブジェクトとして...(_code.scpt側の変更は、loadしているスクリプト側で実行する度に反映される)
* set CD to load script file ((path to scripts folder as text) & "_code.scpt")
*
* ロード方法の例2(同一フォルダに含めて再配置可能にする場合)
* シェルコマンド利用版
* set CD to load script POSIX file ((do shell script "dirname " & quoted form of ((path to me)'s POSIX path)) & "/_code.scpt")
*
* Finder利用版
* tell application "Finder" to set CD to load script file (((path to me)'s folder as text) & "_login_pass.scpt")
*
* 開発&テスト環境
* MacBook OSX 10.6.8
* AppleScript エディタ バージョン 2.3(118)
* AppleScript 2.1.2
*)



------------------------------------------
-- 基本
------------------------------------------
--テキストをdelimiterで区切ったリストに変換する
--依存するハンドラ:なし
--AppleScript2.0以降は、«constant conszkhk»などの拡張属性はサポートしない
--considering(考慮する)、ignoring(無視する)ブロックについて
--http://www.tonbi.jp/AppleScript/dic/Basic/Command/considering.html
(*
--大文字小文字の区別なし(=ignoring case)、全角半角の区別あり
split("abcdefgABCDEFGabcdefg", "d") --結果:{"abc", "efgABC", "EFGabcdefg"}

--大文字小文字の区別あり、全角半角の区別あり
considering case
split("abcdefgABCDEFGabcdefg", "d") --結果:{"abc", "efgABCDEFGabcdefg"}
end considering

--区切り記号のリストによる複数指定可能
split("abcdefgABCDEFGabcdefg", {"c", "e"}) --結果:{"ab", "d", "fgAB", "D", "FGabcdefg"}
*)
on split(src_text, delimiter) set last_delimiter to AppleScript's text item delimiters
set AppleScript's text item delimiters to delimiter
set res to src_text's text items
set AppleScript's text item delimiters to last_delimiter
res
end split

--リストをdelimiterで接続したテキストに変換する
--依存するハンドラ:なし
--AppleScript2.0以降は、«constant conszkhk»などの拡張属性はサポートしない
(*
--基本
join({"a", "b", "c"}, "_") --結果:"a_b_c"

--delimiterは、リストで複数指定しても第1要素以外は無視される
join({"a", "b", "c"}, {"_", "*"}) --結果:"a_b_c"
*)
on join(src_list, delimiter) set last_delimiter to AppleScript's text item delimiters
set AppleScript's text item delimiters to delimiter
set res to src_list as text
set AppleScript's text item delimiters to last_delimiter
res
end join

--テキスト中のtext1をtext2に置き換える--元のテキストは変化しない
--依存するハンドラ:join, split
--AppleScript2.0以降は、«constant conszkhk»などの拡張属性はサポートしない
(*
--大文字小文字の区別なし(=ignoring case)、全角半角の区別あり
replace("abcdefgABCDEFGabcdefg", "d", "_") --結果:"abc_efgABC_EFGabcdefg"

--大文字小文字の区別あり、全角半角の区別あり
considering case
replace("abcdefgABCDEFGabcdefg", "d","_") --結果:"abc_efgABCDEFGabcdefg"
end considering

--text1は、リストによる複数指定可能
replace("abcdefgABCDEFGabcdefg", {"c", "e"}, {"_"}) --結果:"ab_d_fgAB_D_FGabcdefg"

--text2は、リストで複数指定しても第1要素以外は無視される
replace("abcdefgABCDEFGabcdefg", {"c", "e"}, {"_", "*"}) --結果:"ab_d_fgAB_D_FGabcdefg"
*)
on replace(src_text, text1, text2) join(split(src_text, text1), text2) end replace

--リストまたはレコードをテキストに変換する
--依存するハンドラ:split, join
(*
""
result & return & coding({"a"}) --結果:{\"a\"}
result & return & coding({a:1, b:2}) --結果:"{a:1, b:2}"
result & return & coding({{a:1}, {b:2}}) --結果:"{{a:1}, {b:2}}"
result & return & coding({1, 2, 3}) --結果:"{1, 2, 3}"
result & return & coding({"1", "2", "3"}) --結果:"{\"1\", \"2\", \"3\"}"
result & return & coding("ABC") --結果:"\"ABC\""
result & return & coding("123") --結果:"\"123\""
result & return & coding(123) --結果:"123"
result & return & coding(true) --結果:"true"
result & return & coding({https___direct_jp_bank_japanpost_jp_tp1web_U010101SCK_do:{{checked:false, value:"1234", type:"text", |name|:"okyakusamaBangou1", |index|:"3"}, {checked:false, value:"5678", type:"text", |name|:"okyakusamaBangou2", |index|:"4"}, {checked:false, value:"12345", type:"text", |name|:"okyakusamaBangou3", |index|:"5"}}})
result & return
*)
on coding(obj) --1秒
try
if obj's class = record or obj's class = list then
obj as number
else if obj's class = text then
"\"" & obj & "\""
else
obj as text
end if
on error msg number -1700
try
set a_list to split(msg, "{") set a_list's item 1 to ""
set a_list to split(join(a_list, "{"), "}") set a_list's item -1 to ""
join(a_list, "}") on error number -10006
"{\"" & obj & "\"}"
end try
end try
end coding

--リスト・レコード複合体をJSONコードに変換する
--(リストの{}を[]に変換する、リスト内の|を削除する)
--依存するハンドラ:coding, replace
(*
set res to {}

{{a:1}, {b:2}}
set res's end to json_from(result)
{{{aa:{a:1}, bb:{b:2}}, {cc:{c:3}, dd:{d:4}}}, {{aa:{a:1}, bb:{b:2}}, {cc:{c:3}, dd:{d:4}}}, {{aa:{a:1}, bb:{b:2}}, {cc:{c:3}, dd:{d:4}}}}
set res's end to json_from(result)
{aaa:{{aa:{a:1}, bb:{b:2}}, {cc:{c:3}, dd:{d:4}}}, bbb:{{aa:{a:1}, bb:{b:2}}, {cc:{c:3}, dd:{d:4}}}, ccc:{{aa:{a:1}, bb:{b:2}}, {cc:{c:3}, dd:{d:4}}}}
set res's end to json_from(result)
{https___direct_jp_bank_japanpost_jp_tp1web_U010101SCK_do:[{checked:false, value:"1234", type:"text", |name|:"okyakusamaBangou1", |index|:"3"}, {checked:false, value:"5678", type:"text", |name|:"okyakusamaBangou2", |index|:"4"}, {checked:false, value:"12345", type:"text", |name|:"okyakusamaBangou3", |index|:"5"}]}
set res's end to json_from(result)

join(res, return)
*)
on json_from(list_recode) coding(list_recode) replace(result, "{", "__DLMT__{__DLMT__") replace(result, "}", "__DLMT__}__DLMT__") replace(result, "__DLMT____DLMT__", "__DLMT__") split(result, "__DLMT__") _parse_list_record(result) replace(result, "|", "") end json_from
--json_fromから呼び出され、リストの{}を[]に変換する--4×6×3=72倍速い--0.5秒
on _parse_list_record(elm_list) set num to 0
set stack to {} repeat with e in elm_list
set e to e as text
if e = "}" then
set inner to {} repeat
set pull to stack's item 1
set stack to stack's rest
if pull = "{" then
if _is_list_code(inner) then
set stack's beginning to "[" & inner & "]"
else
set stack's beginning to "{" & inner & "}"
end if
exit repeat
else
set inner's beginning to pull
end if
end repeat
else
set stack's beginning to e
end if
end repeat
stack as text
end _parse_list_record
--_parse_list_recordから呼び出され、リストかどうか判定する
on _is_list_code(inner) if inner's item 1's item 1 is in "{[" then
true
else
set kv_list to split(inner as text, ":") if kv_list's number = 1 then
true
else
set qt_list to split(kv_list's item 1, "\"") if qt_list's number = 1 then
false
else
true
end if
end if
end if
end _is_list_code

--レコードのキーに変数でアクセスして、読み出す--20倍以上速い
--依存するハンドラ:なし
(*
set a_record to {apple:100, |みかん|:50, |オレンジ|:150}
set a_key to "みかん"
for_key(a_record, a_key) --結果:50
*)
on for_key(a_record, a_key) run script " on value_of(obj) obj's |" & a_key & "| end me"
result's value_of(a_record) end for_key

--元レコードに、キーを追加したレコードを返す
--既存のキーは上書きされる、元レコードには影響する
--新しいキーは追加される、元レコードには影響しない
--依存するハンドラ:なし
(*
set a_record to {apple:100, |みかん|:50, |オレンジ|:150}
set_key_value(a_record, "apple", 120) --結果:{apple:120, |みかん|:50, |オレンジ|:150}
set_key_value(a_record, "メロン", 1000) --結果:{apple:120, |みかん|:50, |オレンジ|:150, |メロン|:1000}
*)
(*
set a_record to {a:1, b:2, c:3}

--既存のキーを変更する場合は、元レコードが変化する
set_key_value(a_record, "c", "test")
{result, a_record} --結果:{{a:1, b:2, c:"test"}, {a:1, b:2, c:"test"}}

--新しいキーを追加する場合は、元レコードは変化しない
set_key_value(a_record, "d", "test")
{result, a_record} --結果:{{a:1, b:2, c:3, d:"test"}, {a:1, b:2, c:3}}

--よって、元レコードを変更したい場合は、常に set a_record to を付加して利用した方が良い
set a_record to set_key_value(a_record, "c", "test")
set a_record to set_key_value(a_record, "d", "test")
*)
on set_key_value(a_record, a_key, a_value) run script " on set_value(obj, v) try set obj's |" & a_key & "| to v obj on error number -10006 obj & {|" & a_key & "|:v} end try end me"
result's set_value(a_record, a_value) end set_key_value



------------------------------------------
-- JavaScript連携
------------------------------------------
--リストまたはテキストで渡したJavaScriptを実行する
--依存するハンドラ:join
on do_js(code_list_or_text) set code_list_or_text to code_list_or_text as list
tell application "Safari"
if documents's number = 0 then make new document
my join(code_list_or_text, ";") do JavaScript result in document 1
end tell
end do_js

--レコードからキーを取り除いたレコードを返す(元のレコードは変化しない)
--依存するハンドラ:json_from, do_js
(*
set a_record to {apple:100, |みかん|:50, |バナナ|:150}
set a_key to "みかん"
reject_key(a_record, a_key) --結果:{apple:100.0, |バナナ|:150.0}
*)
(*
set a_record to {apple:"赤", |みかん|:"橙", |バナナ|:"黄"}
set a_key to "みかん"
reject_key(a_record, a_key) --結果:{apple:"赤", |バナナ|:"黄"}
*)
on reject_key(a_record, a_key) if a_record's classrecord then error "レコードではありません。in reject_key()" number -128 --強制終了
"json=" & json_from(a_record), ¬ "delete(json[" & quoted form of a_key & "])", ¬ "json"} do_js(result) end reject_key

--レコードのキーをリストで返す
--依存するハンドラ:json_from, do_js
(*
{{a:1}, {b:2}}
keys(result)
{aaa:{{aa:{a:1}, bb:{b:2}}, {cc:{c:3}, dd:{d:4}}}, bbb:{{aa:{a:1}, bb:{b:2}}, {cc:{c:3}, dd:{d:4}}}, ccc:{{aa:{a:1}, bb:{b:2}}, {cc:{c:3}, dd:{d:4}}}}
keys(result)
{https___direct_jp_bank_japanpost_jp_tp1web_U010101SCK_do:[{checked:false, value:"1234", type:"text", |name|:"okyakusamaBangou1", |index|:"3"}, {checked:false, value:"5678", type:"text", |name|:"okyakusamaBangou2", |index|:"4"}, {checked:false, value:"12345", type:"text", |name|:"okyakusamaBangou3", |index|:"5"}]}
keys(result)
*)
--on record_keys(a_record)
on keys(a_record) if a_record's classrecord then error "レコードではありません。in record_keys()" number -128 --強制終了
"var json=" & json_from(a_record), ¬ "var k=[]", ¬ "for(j in json){k.push(j)}", ¬ "k"} do_js(result) end keys

--レコードの値をリストで返す
--依存するハンドラ:なし
(*
{{a:1}, {b:2}}
values(result)
{a:1, b:2, c:3}
values(result)
{https___direct_jp_bank_japanpost_jp_tp1web_U010101SCK_do:[{checked:false, value:"1234", type:"text", |name|:"okyakusamaBangou1", |index|:"3"}, {checked:false, value:"5678", type:"text", |name|:"okyakusamaBangou2", |index|:"4"}, {checked:false, value:"12345", type:"text", |name|:"okyakusamaBangou3", |index|:"5"}]}
values(result)
*)
--on record_values(a_record)
on values(a_record) if a_record's classrecord then error "レコードではありません。in record_keys()" number -128 --強制終了
a_record as list
end values

--リストを昇順に並べ替える
--依存するハンドラ:json_from, do_js
(*
sort({"b", "c", "a"}) --結果:{"a", "b", "c"}
*)
--on sort_list(a_list)
on sort(a_list) if a_list's classlist then error "リストではありません。in sort_list()" number -128 --強制終了
do_js(json_from(a_list) & ".sort()") end sort



------------------------------------------
-- Ruby連携
------------------------------------------
--rubyコードを実行して結果を返す
--バックスラッシュのみ,エスケープ\\が必要、それ以外はRubyコードの書き方と同じ
--依存するハンドラ:join
(*
do_ruby_script({"require 'uri'", "URI.escape(%q|" & "tell application \"System Events\" --ショートカット操作をする限り" & "|)"})
*)
on do_ruby_script(code_list) set code_list to code_list as list
set code_list's last item to "puts(" & code_list's last item & ")"
set shell_code to "ruby -e " & quoted form of join(code_list, ";") --log shell_code
do shell script shell_code
end do_ruby_script

--rubyコードを require 'jcode'; $KCODE='u'; な日本語環境で実行して結果を返す
--依存するハンドラ:do_ruby_script
on do_ruby_jcode_u(code_list) do_ruby_script({"require 'jcode'", "$KCODE='u'"} & code_list) end do_ruby_jcode_u

--正規表現の比較をして真偽値を返す
--バックスラッシュのみ,エスケープ\\が必要、それ以外はRubyコードの書き方と同じ
--依存するハンドラ:do_ruby_jcode_u
(*
reg("/\\d/", "abc1")
reg("/^[\\+\\-]?[\\d\\.\\,]+$/", "+123,456.789")
*)
on reg(reg_text, str) try
do_ruby_jcode_u(reg_text & " =~ " & quoted form of str) as integer
true
on error
false
end try
end reg

--正規表現の比較をしてマッチした位置を返す(マッチしなければ0)
--位置情報はAppleScript仕様(先頭文字の位置が1)
--バックスラッシュのみ,エスケープ\\が必要、それ以外はRubyコードの書き方と同じ
--依存するハンドラ:do_ruby_jcode_u
(*
match_pos("/\\d/", "abc1")--結果:4
match_pos("/^[\\+\\-]?[\\d\\.\\,]+$/", "+123,456.789")--結果:1
*)
on match_pos(reg_text, str) try
(do_ruby_jcode_u(reg_text & " =~ " & quoted form of str) as integer) + 1
on error
0
end try
end match_pos

--正規表現の比較をしてマッチした文字列を返す(マッチしなければ"")
--バックスラッシュのみ,エスケープ\\が必要、それ以外はRubyコードの書き方と同じ
--依存するハンドラ:do_ruby_jcode_u
(*
match_str("/\\{.*\\}/", "{{a:1}, {b:2}}")--結果:"{{a:1}, {b:2}}"
match_str("/\\d/", "abc1")--結果:"1"
match_str("/[a-z]*/", "nil") --結果:"nil"
match_str("/[0-1]*/", "nil") --結果:""
*)
on match_str(reg_text, str) do_ruby_jcode_u({reg_text & " =~ " & quoted form of str, "($&==nil) ? '' : $&"}) end match_str

--テキスト(str)中の、正規表現(reg_text)でマッチした部分を、置き換え文字(replace_text)で、すべて置き換える
--依存するハンドラ:do_ruby_script
(*
gsub("abcdefgABCDEFGabcdefg", "/[e-g]/i", "_") --結果:"abcd___ABCD___abcdefg"
*)
on gsub(str, reg_text, replace_text) do_ruby_jcode_u(quoted form of str & ".gsub(" & reg_text & "," & quoted form of replace_text & ")") end gsub

--テキスト(str)中の、正規表現(reg_text)でマッチした部分を、置き換え文字(replace_text)で、最初のマッチだけ置き換える
--依存するハンドラ:do_ruby_script
(*
sub("abcdefgABCDEFGabcdefg", "/[e-g]/i", "_") --結果:"abcd_fgABCDEFGabcdefg"
*)
on sub(str, reg_text, replace_text) do_ruby_jcode_u(quoted form of str & ".sub(" & reg_text & "," & quoted form of replace_text & ")") end sub

--数値を3桁区切りのテキストにする
--依存するハンドラ:do_ruby_script
(*
number_with_delimiter(1234.1234) --結果:"1,234.1234"
number_with_delimiter(123456789) --結果:"123,456,789"
*)
on number_with_delimiter(num) --注意:バックスラッシュは、バックスラッシュでエスケープすること(\\)。AppleScriptでは特殊な文字として扱われるため。
--(num.to_s =~ /[-+]?\d{4,}/) ? (num.to_s.reverse.gsub(/\G((?:\d+\.)?\d{3})(?=\d)/, '\1,').reverse) : num.to_s
--(num.to_s =~ /[-+]?\\d{4,}/) ? (num.to_s.reverse.gsub(/\\G((?:\\d+\\.)?\\d{3})(?=\\d)/, '\\1,').reverse) : num.to_s
do_ruby_script("('" & num & "' =~ /[-+]?\\d{4,}/) ? ('" & num & "'.reverse.gsub(/\\G((?:\\d+\\.)?\\d{3})(?=\\d)/, '\\1,').reverse) : '" & num & "'") end number_with_delimiter

--printfコマンド
--依存するハンドラ:なし
(*
printf("%3d:", 1) --結果:" 1:"
*)
on printf(format, value) do shell script "printf" & space & format & space & value
end printf

--金額の書式を整える
--依存するハンドラ:printf、number_with_delimiter
(*
number_to_currency(1000.5, 2, "¥", "円") --結果:"¥1,000.50円"
*)
--number_to_currency(金額, 小数点以下の桁数, 先頭, 末尾)
on number_to_currency(num, decimal_place, prefix, suffix) printf("%" & "." & decimal_place & "f", num) number_with_delimiter(result) prefix & result & suffix
end number_to_currency



------------------------------------------
-- メッセージ
------------------------------------------
--growlまたはdisplay dialogでメッセージを表示する
-- http://growl.info/extras.php#growlnotify
on message(title, msg) try
--msgの文字数を350文字に制限する(\"等のエスケープ記号はカウントしない)
set msg to (msg's items 1 thru 350 as text) & "......"
end try
try
--do shell script "(sleep 4; /usr/local/bin/growlnotify -a Finder -m msg title) >/dev/null 2>&1 &" --バックグラウンドで時間差実行する場合の例
do shell script "/usr/local/bin/growlnotify -a " & quoted form of (my name as text) & " -m " & quoted form of msg & space & quoted form of title & " 2>&1"
if result is not "" then error number -128
on error
activate
display dialog msg buttons {"OK"} default button "OK" with title title with icon note giving up after 4 --with icon note=1 caution=2 stop=0
end try
end message