Мы завершаем наше знакомство с 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 — это особый вид скриптов, связываемых с определенными папками и выполняющих какие-либо действия при добавлении элементов в папку или удалении их из нее. Соответственно, основной частью такого скрипта будет обработчик события «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).
Конечно, можно. Для этого Вы должны использовать обработчик события «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 получаем полное имя программы, и только затем — обращаемся к ней, используя полученное имя.
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, ссылка на него сохраняется, независимо от перемещения соответствующего файла или папки. Рассмотрим пример. Пусть требуется регулярно удалять из находящейся на Столе папки 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 просто никуда не перемещается.
Все подобные операции по обработке строк основываются на временной замене «разделителя слов». Рассмотрим несколько примеров.
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 допускает использование большого числа синонимов, в скомпилированном же скрипте для каждой структуры существует одно строго определенное обозначение. Естественно, и для последующего вывода на экран никаких вариантов не будет. (Но переключив в настройках Script Editor диалект мы увидим «перевод» скрипта на другой язык)
Небольшой скрипт-шутка. После его запуска картинки файлов, папок, дисков, начинают скакать с одного места стола на другое, еще и меняя при этом свой цвет.
property d : 5 tell application "Finder" to set frontmost to true on idle try tell application "Finder" to setListItems to every item ofdesktop 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) не появится.