Как бы FAQ

Мы завершаем наше знакомство с AppleScript. Последняя статья цикла построена как ответы на различные вопросы, которые могут возникнуть у Вас при практическом использовании этого языка.

Как искать ошибки в скриптах?

К сожалению, Script Editor имеет очень ограниченные возможности отладки. Конечно, не стоит забывать и о них: в окне «result» выводятся результаты, возвращаемые обработчиками и скриптом в целом, более полезным может оказаться «Event Log», фиксирующее все вызовы и возвращенные результаты. Оба этих окна можно открыть, используя меню Controls.


Однако, чаще всего этих средств оказывается недостаточно и приходится прибегать к средствам «программистов прошлого».

Во-первых, это трассировка — отслеживание выполнения команд программы и изменений значений переменных. За неимением более удобных средств, выполнять ее придется, временно вставляя в текст скрипта дополнительные — отладочные — команды. Если достаточно просто получить сигнал об исполнении некоторого фрагмента кода, можно воспользоваться звуковой индикацией — командой «beep». Например:

tell application "Finder"
if exists folder "images" of desktop then
beep
if not(exists folder "backup" of desktop) then
beep 2
--какая-то обработка
end if
--какая-то обработка
end if
end tell

Если же необходимо получить более подробную информацию, подойдет «display dialog». Это может выглядеть вот так:

set SApath to path to scripting additions folder
display dialog SApath

Если ваш скрипт достаточно большой, может помочь еще один прием: временное отключение фрагментов программы. Для этого используется заключение их в символы комментария.

(*
на этот текст компьютер не обращает
никакого внимания
set thisdate to current date
set time1 to time of thisdate
*)
tell application "Finder"
repeat withMyFile in folder "images" of desktop
-- и на этот — тоже if name of MyFile contains ".jpg" then
set creator type of MyFile to "GKON"
-- end if
end repeat
end tell

Если же Вы решили всерьез заняться написанием сложных сценариев на языке AppleScript, возможно стоит изучить возможности программ, предлагаемых другими фирмами. Например, Scripter от Main Event Software (http://www.mainevent.com) или ScriptDebugger от Late Night Software (http://www.latenightsw.com). Для тех же, кто уже полностью перешел на Mac OS X и будет писать скрипты исключительно под Cocoa, Apple предлагает свою весьма развитую среду — AppleScript Studio.

Любителям экспериментов могу предложить еще один «вставной код»:

set thisdate1 to current date
-- тут будет «полезная часть» скрипта
set thisdate2 to current date
display dialog thisdate2 — thisdate1 as number

С его помощью Вы сможете определить, сколько времени (в секундах) выполняется ваша программа.

Как увеличить быстродействие скрипта?

AppleScript — язык «неторопливый», поэтому вынесенный в подзаголовок вопрос отнюдь не праздный. Оптимальная структура алгоритма совсем не помешает. Тут работают все приемы, известные по «классическим» языкам программирования. Особенно внимательно стоит относится к циклам.

Сравните, например, вот эти два фрагмента:

repeat withMyFile in FileList
tell application "Finder"
set creator type of MyFile to "GKON"
end tell
end repeat

и

tell application "Finder"
repeat withMyFile in FileList
set creator type of MyFile to "GKON"
end repeat
end tell

Разница, как видите, минимальная. За пределы цикла вынесено всего-навсего «обращение» к программе Finder. Но даже это при обработке каких-то 25 файлов дало примерно 5-процентный выигрыш. А ведь и операции могут быть гораздо сложнее, и повторов намного больше.

Однако, кроме стандартных способов оптимизации, есть и свои, характерные только для AppleScript, приемы. Допустим, Вам нужно, чтобы все фотографии с расширением «.jpg» из некоторой папки открывались программой GraphicConvertor. Первое, что приходит в голову: в цикле перебирать файлы и, если встретится имя содержащее заданную последовательность символов, менять код «создателя». Примерно вот так:

tell application "Finder"
repeat withMyFile in folder "images" of desktop
if name ofMyFile contains ".jpg" then
set creator type of MyFile to "GKON"
end if
end repeat
end tell

Казалось бы, что тут можно улучшить? Ан, нет. Воспользовавшись имеющимся в AppleScript механизмом фильтров, мы получаем вот такой вариант:

tell application "Finder"
set creator type of ¬
(every file infolder "images" ofdesktop ¬
whose name contains ".jpg") to"GKON"
end tell

Здесь вместо явного цикла мы приказали компьютеру обработать каждый (every) файл. Причем, отфильтровав среди этих файлов только те, чье (whose) имя содержит (contains) заданную подстроку. При желании можно было бы указать и более строгое условие: Ends With (заканчивается на&).

Работать такой скрипт будет не в пример быстрее первоначального варианта. (Проведя эксперимент с папкой, содержащей 25 файлов, я получил примерно тридцатикратную разницу в скорости выполнения). Стоит, однако, заметить, что фильтр — средство гораздо менее универсальное, чем цикл в сочетании с условным оператором.

Еще один (правда, небольшой — процентов на 10-15) резерв повышения быстродействия — использование, по возможности, однострочного «tell» вместо блока «tell-end tell». Например, наш скрипт можно переписать в виде:

tell application "Finder" to ¬
set creator type of ¬
(every file infolder "images" ofdesktop ¬
whose name contains ".jpg") to"GKON"

Напоследок странный, казалось бы, вопрос: всегда ли нужно оптимизировать программу? Да простят меня преподаватели программирования, отвечу я на него: «Нет.» Ведь цель использования AppleScript — облегчить себе жизнь. Так стоит ли тратить час ради минутного выигрыша во времени исполнения скрипта, если этот скрипт будет запускаться раз в месяц? А вот если им предстоит пользоваться по двадцать раз на дню — тогда совсем другое дело.

Что такое Folder Actions?

Folder Actions — это особый вид скриптов, связываемых с определенными папками и выполняющих какие-либо действия при добавлении элементов в папку или удалении их из нее. Соответственно, основной частью такого скрипта будет обработчик события «adding folder items to» или «removing folder items from».

Например, Вы можете сделать папку, при помещении файлов в которую у них автоматически будет меняться код-creator:

on adding folder items to thisFolder after receiving addedItems
tell application "Finder"
repeat withAnItem in addedItems
set creator type of AnItem to "BOBO"
end repeat
end tell
end adding folder items to

Подобным же образом делается и обработчик события «изъятие элементов»:

on removing folder items from thisFolder
display dialog "Положь на место!!!"
end removing folder items from

Чтобы назначить папке тот или иной скрипт, используется контекстное меню.

Заметьте: папка «видит» произошедшие в ней изменения только в том случае, если в этот момент открыто ее окно. Для устранения этого неудобства можно воспользоваться свободно распространяемым расширением Folder Actions Plus (http://www.eagrant.com) Эрика Алена Гранта (Eric Allen Grant).

Можно ли с помощью AppleScript выполнять некую задачу в определенное время?

Конечно, можно. Для этого Вы должны использовать обработчик события «idle». Естественно, скрипт нужно будет сохранить как приложение, отметив флажок «stay open». А для отсчета текущего времени у нас есть метод «current date».

Вот так, например, можно сделать, чтоб компьютер сам напоминал ребенку, что пора идти спать:

property hoursX : 21 -- «время икс»
property minuteX : 30
-- сразу же преобразуем время в секунды,
-- используя стандартные константы minutes и hours
property timeX : minuteX * minutes + hoursX * hours
property d : 300 -- через такие промежутки времени
-- компьютер будет повторять напоминание
tell application "Finder" to set frontmost to true
on idle
set theDate to current date
set theTime to time of theDate
if theTime e timeX then
say "Hey Patya"
activate
display dialog "Пора спать!" buttons {"Понятно"}
end if
return d
end idle

Готовый скрипт нужно положить в папку Startup Items. Для большего эффекта можно использовать еще и обработчик «quit»:

on quit
display dialog "Спать, я сказал!!!" buttons {"Ничего не поделаешь"}
end quit

Напомню, что в этом случае завершить скрипт можно будет только комбинацией «command-shift-Q».

Можно ли обращаться к программе не по имени, а по creator?

Для этого придется выполнить дополнительную операцию. Сперва по creator получаем полное имя программы, и только затем — обращаемся к ней, используя полученное имя.

tell application "Finder" to set appName ¬
to application file id "BOBO" as string
tell application appName to activate

Можно ли определить работающие в данный момент программы?

Это позволяет сделать класс «process» программы Finder. Рассмотрим практический пример. В ходе работы нередко приходится держать открытыми большое число программ. Перед сменой деятельности их всех нужно закрыть. Вот такую операцию — закрытие всех работающих прикладных программ мы сейчас и автоматизируем.

Используя тот факт, что тип файла приложения — «APPL», с помощью фильтра получаем список процессов, которые предстоит закрыть.

tell application "Finder"
set theProcs to every process whose (file type is"APPL")
end tell

А теперь поочередно определяем имя программы, соответствующей каждому процессу, и передаем ей сообщение «quit».

repeat with x intheProcs
set appName to name of x
tell application appName toquit
end repeat

На практике этот скрипт, скорее всего, придется усовершенствовать. Например, включить в фильтр дополнительные условия, если некоторые программы Вам нужны постоянно:

set theProcs toevery process whose ¬
(file type is "APPL" and name is not "Drop Drawers")

Чем отличается использование объекта alias от file (folder)?

Если в скрипте указывается объект alias, ссылка на него сохраняется, независимо от перемещения соответствующего файла или папки. Рассмотрим пример. Пусть требуется регулярно удалять из находящейся на Столе папки Work все, за исключением папки Archival. Скрипт, выполняющий такую задачу, может выглядеть примерно так:

property archiv : alias "HD2GB:Desktop Folder:Work:archival:"
property work : alias "HD2GB:Desktop Folder:Work:"
tell application "Finder"
move archiv to desktop
move every item ofwork to trash
put away archiv
end tell

Несмотря на то, что первой же командой мы выносим папку archival на Стол, связь с ней не разрывается. В результате впоследствии, после удаления всех остальных элементов, она без лишних сложностей может быть возвращена на свое исходное место.

Замечу, однако, что для подобных задач есть и другое решение (с использованием фильтра):

property work : alias "HD2GB:Desktop Folder:Work:"
tell application "Finder"
move (every item ofwork whose name is not"archival") to trash
end tell

В этом случае папка archival просто никуда не перемещается.

Как можно разделить на элементы, например, URL или путь к файлу?

Все подобные операции по обработке строк основываются на временной замене «разделителя слов». Рассмотрим несколько примеров.

1. Имеется html-файл. Из содержащихся в нем ссылок вида «mailto:nemo@nautilus.org» нужно извлечь адрес электронной почты и отдельно — доменное имя компьютера (без «собачки» и имени пользователя). Вот фрагмент кода, выполняющий такую обработку (предполагается, что исходный URL помещен в переменную aText):

set oldDelim to AppleScript's text item delimiters
try
set AppleScript's text item delimiters to {":"}
set email to text item 2 of aText
set AppleScript's text item delimiters to {"@"}
set domain to text item 2 of aText
set AppleScript's text item delimiters to oldDelim
on error
set AppleScript's text item delimiters to oldDelim
end try

Обратите внимание: поскольку смена разделителя не отменяется вплоть до перезагрузки, для страховки используется блок «try — on error — end try», восстанавливающий старый список разделителей даже при ошибке исполнения скрипта.

2. Следующий скрипт, будучи сохранен как приложение, выводит свое имя. Аналогично может быть «разобран» и любой другой путь.

set myPath to path to me as string
set oldDelim to AppleScript's text item delimiters
try
set AppleScript's text item delimiters to {":"}
-- при записи полного пути в Mac OS в качестве
-- разделителя используется двоеточие
set myName to last text item of myPath
-- нам нужен только последний элемент пути — имя файла
set AppleScript's text item delimiters to oldDelim
on error
set AppleScript's text item delimiters to oldDelim
end< try
display dialog myName

3. Ненамного сложнее будет выглядеть скрипт, преобразующий запись полного пути из формата, принятого в Mac OS, в использующийся в Unix (и Mac OS X). Тут придется сперва «разобрать» путь на отдельные элементы, ориентируясь по двоеточиям, а затем — вновь «собрать», но уже через наклонные черточки.

set oldDelim to AppleScript's text item delimiters
try
set AppleScript's text item delimiters to ":"
set pathSegments to every text item of thisFilepath
set AppleScript's text item delimiters to "/"
set thisFilepath to"/" & (pathSegments as string)
set AppleScript's text item delimiters to oldDelim
on error
set AppleScript's text item delimiters to oldDelim
end try

Обратите внимание: программа очень сильно упростилась благодаря использованию особенности преобразования типов в AppleScript.

Можно ли при оформлении диалогов использовать свои картинки?

Да. Причем для этого не требуется никаких особых хитростей. Сохраните скрипт как приложение. Откройте его с помощью ResEdit. Создайте (или скопируйте откуда-нибудь) ресурс «cicn», при необходимости поменяйте его ID. Все. Теперь Вы можете вписать в скрипт что-то вроде:

tell me
display dialog "Hello!" with icon 10000
-- иконка в ресурсе cicn ID=10000
end tell

или, например,

property myIcon : 10000
-- здесь что-то делается
tell me
display dialog "Hello!" with icon myIcon
end tell

Заметьте, чтоб иконка была взята именно из вашего скрипта, команда display dialog должна быть вне любых блоков «tell», либо в блоке «tell me».

***

Как Вы уже вероятно заметили, ни скомпилированный скрипт, ни приложение AppleScript не могут работать сами по себе, без установленных системных компонентов. То есть компиляция здесь — не такой процесс, как при работе, например, на Си. В чем же она состоит?

Первым делом AppleScript выделяет в тексте слова-символы (токены): set, repeat, if и т. д. Затем поочередно собирается информация из словарей всех программ, к которым должен обращаться скрипт. В большинстве случаев для этого просто прочитывается ресурс «aete». Однако, некоторые программы, прежде чем выдать свой словарь, выполняют вспомогательные действия (например, дополняют его командами дополнительных модулей — Plug-In). Процесс этот достаточно долгий, поэтому однажды полученный словарь Script Editor сохраняет в своем кэше. Именно поэтому повторная компиляция скрипта после внесенных изменений происходит заметно быстрее.

Следующий этап — упаковка, исходный текст преобразуется в ресурс скрипта, содержащий промежуточный код.

Если все прошло успешно, в окне Script Editor мы видим отформатированный текст исходного скрипта. При этом некоторые слова могут поменяться. Причину понять несложно: AppleScript допускает использование большого числа синонимов, в скомпилированном же скрипте для каждой структуры существует одно строго определенное обозначение. Естественно, и для последующего вывода на экран никаких вариантов не будет.

Напоследок…

Небольшой скрипт-шутка. После его запуска картинки файлов, папок, дисков, начинают скакать с одного места стола на другое, еще и меняя при этом свой цвет.

property d : 5
tell application "Finder" to set frontmost to true
on idle
try
tell application "Finder" to setListItems to every item of desktop
repeat withAnItem in ListItems
set newX torandom number from 60 to 600
set newY torandom number from 60 to 450
set newLabel to random number from 0 to 7
tell application "Finder" to ¬
set position of AnItem to {newX, newY}
tell application "Finder" to ¬
set label index of AnItem to newLabel
end repeat
on error
return d
end try
return d
end idle

Если приятель, которому Вы решили подбросить этот «подарочек», уже достаточно опытен в общении с Маками, можно усложнить ему работу по обнаружению «закладки». С помощью ResEdit откройте сохраненный как приложение скрипт. В ресурсе SIZE найдите пункт «only background» и переключите его. Затем смените тип файла с «APPL» на «appe». Теперь компьютер будет воспринимать наш скрипт как расширение (класть его, естественно, теперь надо в Extensions, а не Startup Items), он будет автоматически запускаться при старте, но в меню программ (и Application Switcher) не появится.