Do you speak AppleScript?

Это — вторая статья цикла. Вы уже немного знакомы с тем, как пишутся скрипты, а может быть — и я на это надеюсь — попробовали что-то писать самостоятельно. Но те несколько конструкций языка, что были использованы в нашем первом занятии,- лишь малая его часть. Сегодня мы расширим наш инструментарий. Конечно, журнальная статья не сможет заменить четырехсотстраничный справочник по языку (AppleScript Language Guide). Так что воспринимайте ее как карманный словарик-разговорник.

Немного терминологии

Прежде всего договоримся об используемых терминах. Как Вы уже знаете, основное назначение AppleScript — управление объектами (object) в прикладных программах (окнами, элементами данных и т. п.). Объект может включать другие объекты — его элементы (element). Например, текст состоит из абзацев, слов и символов. Любой объект является представителем («экземпляром») какого-либо класса объектов (class). Все экземпляры класса имеют один и тот же набор свойств (property), состоят из однотипных элементов, для всех их определена реакция на одинаковые команды (в объектно-ориентированных языках их обычно называют методами; будем и мы использовать этот термин).

Таблица 1. Собственные методы AppleScript

ИмяОписаниеПример
Copy toКопирование значения выражения
в переменную (переменные)
copy "Article" to myWork
Count inПодсчет количества элементовcount integers in {"Yes", 2, 10, 3.5}
дает результат 2
Get Вычисление значения выражения.
Слово Get можно опускать
get 2*2 дает результат 4
Run Исполняет объект, не являющийся выражением 
Set toПрисваивает переменной (переменным)
значение выражения
set width to 158

В AppleScript существует механизм «общих данных» (Data Sharing). Т. е. несколько переменных (списков, записей или объектов скрипта) обращаются фактически к одной и той же области памяти. Например, записав

set SecondList to FirstList

мы создаем не новый список, а только второе имя для уже существующего. Соответственно, любое изменение в FirstList будет отражаться на SecondList и наоборот. Если мы хотим создать на самом деле новый список, только вначале совпадающий с другим, необходимо использовать метод Copy.

Для простых переменных методы Copy и Set взаимозаменяемы.

Однако, для написания скрипта только объектов и их методов недостаточно. Нужно связать эти слова в «предложения», определяющие ход выполнения программы,- операторы. Они бывают простыми — однострочными или составными — занимающими несколько строк и содержащими другие операторы (вспомните, например, «tell… end tell» или «if… then… end if»).

Классы и методы прикладной программы — это ее словарь. Все слова из него, а также из стандартного набора операторов AppleScript называют зарезервированными. Когда мы создаем собственные свойства, методы или переменные в скрипте, называть их именами, совпадающими с резервированными словами, нельзя. А вообще имена, как и во многих других языках, могут состоять из латинских букв, цифр и символа подчеркивания (_), причем первым символом обязательно должна быть буква. Различать здесь заглавные и строчные буквы AppleScript не умеет.

Комментарии

Чтобы человеку (например, Вам самим) было легче разобраться с программой, не помешает вставить в нее пояснения — комментарии. В AppleScript это можно сделать двумя способами:

  • Блочный комментарий. Занимает несколько строк. Заключается в символы (* и *).
  • Комментарий в конце строки. Начинается с символа — (два минуса).
(* Очень-очень
длинный
комментарий *)
set width to 100 -- а это комментарий к строке

Данные

AppleScript может работать с данными различных типов, как простыми, так и структурированными. Набор простых данных довольно обширен.

Таблица 2. Некоторые типы данных AppleScript

ИмяОписаниеПример
BooleanЛогическое значениеfalse, true
DateДата (строка, включающая день недели, число, месяц, год и время)"Среда, 9 января 2002 23:20:59"
IntegerЦелое3
RealВещественное число3.0
ReferenceСсылка на объектwindow "Неименованный"
StringСтрока (Последовательность символов)"Величина"
Styled TextСтилизованный текст (содержащий информацию о шрифте)
Textсиноним класса string

Значения можно преобразовывать из одного типа в другой, используя операцию «as». Например, "123" as integer превратит исходную строку в число 123. Зачем? — Хоть бы для того, чтобы произвести с ним какие-либо вычисления. Если же мы захотим вставить результат расчетов в текст — придется наоборот преобразовать число в строку.

Структурированные переменные хранят сразу несколько значений. В AppleScript это данные типа список (list) и запись (record).

Список записывается как заключенная в фигурные скобки последовательность значений любых типов, перечисленных через запятую. Например: set MyList to {25, 324, "документ", 13}. Конкретный элемент может быть получен по своему порядковому номеру. В нашем случае, item 2 of MyList даст число 324.

Элементы записи — поля (field) — также записываются в фигурных скобках, но их порядок роли не играет, зато каждый имеет собственную уникальную метку. По этой-то метке мы и будем к нему обращаться. Например, задать всю запись целиком можно вот так: set MyData to {name: "Михаил", height: 180, weight: 80}. А получить значение одного поля — так: name of MyData.

Значение величины может быть либо задано в явном виде в самом скрипте{сноска: последовательность знаков, явно указывающую значение, называют литералом}, как в использованных выше примерах, либо получено как результат метода, либо — вычислено путем выполнения тех или иных операций над уже имеющимися значениями. Многие операции в AppleScript могут записываться несколькими разными способами, в таблице приведено по одному (обычно, наиболее короткому) варианту.

Таблица 3. Операции в AppleScript

ОбозначениеОписаниеОперандыРезультат
andИBooleanBoolean
orИлиBooleanBoolean
notНеBooleanBoolean
=равно?любой типBoolean
Is Notне равно?любой типBoolean
>больше?любой типBoolean
<меньше?любой типBoolean
>=больше либо равно?любой типBoolean
<=меньше либо равно?любой типBoolean
Starts Withначинается с?список или строкаBoolean
Ends Withзаканчивается на?список или строкаBoolean
Containsсодержит?список, запись или строкаBoolean
Does Not Containне содержит?список, запись или строкаBoolean
Is Inсодержится?список, запись или строкаBoolean
Is Not Inне содержится?список, запись или строкаBoolean
&конкатенация (соединение)строкистрока
&конкатенация (соединение)записизапись
&.любые другие типысписок
*умножениечислочисло
+сложениечисло, Dateчисло, Date
вычитаниечисло, Dateчисло, Date
/делениечислоReal
modделение нацелоIntegerInteger
divостатокIntegerInteger
^возведение в степеньчислоReal

Если требуется преобразовать какую-либо величину в значение другого типа, используют операцию приведения типа As. Например:

set MyResult to 132 / 11 as string -- результатом будет строка "12.0"

При работе с датами можно прибавлять к ним (и вычитать из них) не только значения того же типа, но и целые числа, которые при этом воспринимаются как промежуток времени в секундах. Для удобства предусмотрены стандартные константы: minutes, hours, days и weeks, равные соответствующему числу секунд (60, 3600 и т. д.).

Управляющие операторы

В простейшем случае программа на AppleScript может выглядеть как последовательность поочередно вызываемых методов, в каждом из которых указывается полная (т. е. однозначно определяющая объект среди всех, имеющихся на компьютере) ссылка. Однако, для практического использования это, чаще всего, не подходит. Нужно иметь возможность повторять одинаковые действия с различными объектами, выбирать те или иные варианты действий, адекватно реагировать на возникающие ошибки, да и ссылки хотелось бы записывать, по возможности, короче. Для решения таких проблем и служат управляющие операторы. Начнем с уже известных вам.

Tell

Оператор Tell указывает объект, к которому относятся все заключенные в нем операторы (кроме тех, где использована полная ссылка). Записывается он, как Вы, вероятно, помните, вот так{сноска: запись «[оператор]&» обозначает «любое количество операторов»}:

tell ссылка
[оператор]...
end tell
tell window 1 of application "Tex-Edit Plus"
  insert date
  set selection to "r" -- r обозначает перевод строки
  insert time
end tell

Если к данному объекту относится только один оператор, запись можно сократить:

tell ссылка to оператор
tell application "Finder" to set diskSpace to free space of disk "Macintosh HD"
tell window 1 of application "Tex-Edit Plus" to ¬
set selection to "На диске свободно " & diskSpace div (1024 * 1024) & " Мбайт"

Операторы цикла

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

В простейшем случае оператор цикла записывается вот так:

repeat
[оператор]...
end repeat

Здесь не указано, как компьютеру определить, что пора заканчивать повторения. Цикл будет бесконечным. Конечно, в реальной программе его надо будет рано или поздно прервать. Для этого есть оператор

exit -- «выход из цикла»

Чаще условие окончания цикла записывают непосредственно в его заголовке.

repeat while логическое выражение -- цикл «Пока»
[оператор]...
end repeat

или

repeat until логическое выражение -- цикл «До»
[оператор]...
end repeat

Единственное отличие между ними в том, что первый выполняется пока логическое выражение истинно, а второй — наоборот, заканчивается, когда оно станет истинным.

Зачастую, число повторов нам известно заранее, тогда можно сразу так и написать:

repeat целое times -- «повторить N раз»
[оператор]...
end repeat

А можно указать, что специальная переменная (параметр цикла) должна поочередно принять все значения из заданного диапазона. С каждым из них выполнится тело цикла. Сколько значений — столько и повторений.

repeat with переменная from целое to целое
[оператор]...
end repeat

Вообще-то, параметр может принимать и не все значения, то есть изменяться не на единицу, а на какую-либо другую (но обязательно целую!) величину — шаг:

repeat with переменная from целое to целое by целое
[оператор]...
end repeat

Наконец, мы можем заставить параметр перебирать элементы любого заданного списка.

repeat with переменная in список
[оператор]...
end repeat

Обратите внимание: при работе такого цикла параметр только ссылается на элемент списка. А для получения значения этого элемента нужно использовать операцию «contents» (содержимое), как в следующем примере.

tell application "Tex-Edit Plus"
  tell window 1
    set parNum to 1 -- счетчик абзацев
    set y to paragraphs -- список всех абзацев текста
    repeat with par in y
      set paragraph parNum to (parNum as string) & ¬
      " " & contents of par
      -- перед абзацем вставляем его номер
      set parNum to parNum + 1
    end repeat
  end tell
end tell

Условные операторы

Еще одна классическая программная структура — выбор. С простейшим ее вариантом, позволяющим компьютеру решить, надо ли выполнить какое-либо действие, Вы уже знакомы по первой статье цикла:

if логическое выражение then
[оператор]...
end if

На практике ничуть не реже применяют «полную» форму условного оператора:

if логическое выражение then
[оператор]... -- выполнить при истинности условия
else
[оператор]... -- а так сделать, когда условие — ложное
end if

За счет вложенных «if» можно выбирать и более чем из двух разных вариантов. Например:

if x > y then
  display dialog "Перелет!"
else if x < y then
  display dialog "Недолет!"
else
  display dialog "Попал!!!"
end if

Обработка ошибок

К сожалению, далеко не все и не всегда происходит так, как мы бы хотели. Наш скрипт вполне может столкнуться с «исключительной ситуацией». Например, не оказалось файла, который он должен был бы открыть, или скрипт пытается изменить свойства несуществующего объекта, или& Да всего не перескажешь. Кстати, некоторые «ошибки» появляются и при нормальном выполнении операций. К примеру, если пользователь в диалоге «Open File» щелкнет «Cancel». Так вот, если не принять специальных мер, во всех таких ситуациях скрипт будет просто прерывать свою работу. А это — не слишком хорошо. Поэтому в AppleScript предусмотрен особый управляющий оператор:

try
  [оператор]... -- «нормальные» операции
on error ¬
  [переменная-строка] ¬
  [number переменная-целая] ¬
  [from переменная-ссылка] ¬
  [partial result переменная-список] ¬
  [to переменная-класс]
  [global переменная [, переменная]... ]
  [local переменная [, переменная]... ]
  [оператор]...
end try

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

  • строку-сообщение;
  • числовой код;
  • ссылку на объект, при работе с которым возникла ошибка;
  • список промежуточных результатов, полученных к моменту ее возникновения, а также
  • ожидаемый класс объекта.

Вот пример обработки «ошибки», когда пользователь отказывается от выбора файла:

try
  choose file
  set fileName to result
on error errText
  display dialog "Ошибка: " & errText & ¬
  "r Использовать стандартные настройки?" ¬
  buttons {"Завершить", "Стандарт"} default button 2
  if button returned of result = "Завершить"
    error number -128 -- прерывание скрипта
  else
    set fileName to defaultFileName
  end if
end try

Обратите внимание: здесь использован еще один новый для Вас оператор — «error». Он позволяет сигнализировать об ошибке, причем мы можем использовать как стандартные коды ошибок MacOS или AppleScript, так и свои собственные. Почему бы, например, не написать строку-сообщение по-русски?

А вот как можно использовать параметры «from» и «to»:

display dialog "Введите число:" default answer ""
try
  set number1 to text returned of result as number
  -- display dialog возвращает результат типа
  -- запись, мы берем из нее только введенный
  -- пользователем текст и пытаемся преобразовать
  -- его в число
on error from errObj to errClass
  display dialog "Введено «" & errObj & ¬
  "», когда ожидались данные типа " & errClass
  set number1 to 0
end try

Обработчики

Как Вы, надеюсь, помните, реакция объекта на то или иное событие описывается подпрограммой-обработчиком (handler). В простейшем случае они оформляются вот так:

on событие ([параметр[, параметр]...])
[оператор]...
end событие

Вот, например, как можно осуществить поиск наименьшего числа в заданном списке:

on minimum(numList)
  local min
  -- возьмем первое число
  set min to item 1 of numList
  -- и просмотрим все элементы списка
  repeat with numRef in numList
    set num to contents of numRef
    -- если очередное число меньше,
    -- берем его за минимум
    if num < min then set min to num
  end repeat
  return min
end minimum

Обратите внимание: если обработчик должен возвращать какой-либо результат, это делается с помощью оператора Return. Таких операторов может быть и несколько, выполнение любого из них будет завершать работу обработчика. (Замечу, что можно обойтись и без Return. В этом случае будет возвращен результат последнего исполненного оператора. Но я не рекомендую так делать — если не хотите лишних проблем в процессе отладки скриптов)

Иногда разработчику скрипта бывает удобнее вместо упорядоченной записи параметров воспользоваться метками (в результате вызов обработчика становится более похожим на естественную английскую фразу). Оформляется подпрограмма с помеченными параметрами следующим образом:

on событие ¬
[ переменная-параметр ] ¬
[ спецметка переменная-параметр ]... ¬
[ given метка:переменная-параметр [, метка:переменная-параметр ]...]
[ оператор ]...
end событие

Имена меток можно выбирать какие угодно, но есть некоторое количество наиболее распространенных, использование которых упрощено. Среди этих спецметок: above, from, around, at, below, beside, between, by, for, from, instead of, into, over, thru, under и некоторые другие.

При вызове такого обработчика порядок фактических параметров (за исключением «непосредственного» — непомеченного, который должен всегда идти первым) роли не играет. Для логических параметров можно при вызове вместо указания значений «false» или «true» ставить перед соответствующей меткой соответственно «without» или «with» (в этом случае, к тому же, можно обойтись без «given»).

Например, несколько модифицируем подпрограмму поиска минимума, чтоб она могла при необходимости округлить полученный результат:

on minimum of numList given rounding: roundFlag
  local min
  set min to item 1 of numList
  repeat with numRef in numList
    set num to contents of numRef
    if num < min then set min to num
  end repeat
  if roundFlag then set min to (min + 0.5) div 1
  return min
end minimum

Вызвать ее можно будет либо так:

minimum of {2.5, 5, 3.2, 43, 5.1, 1.5, 3, 7} given rounding:true

либо вот так:

minimum of {2.5, 5, 3.2, 43, 5.1, 1.5, 3, 7} with rounding

В скриптах, сохраненных как приложения (Application либо, в новом ScriptEditor, Classic Applet/MacOS X Applet) используются обработчики для нескольких системных событий.

Во-первых, любая программа при запуске получает сообщение «run». Обработчик для него можно специально не оформлять, так как по умолчанию им считается все, что не входит ни в какие другие обработчики.

С другим событием Вы уже сталкивались в первой части статьи. Это — «open». Его обработчик — основной элемент дроплетов — оформляется вот так:

on open список файлов
[оператор]...
end open

Еще два события могут обрабатываться только «остающимися открытыми» (stay open) апплетами. Наиболее важное из них — «idle» — периодически передается программе, если не происходит никаких других внешних событий. Это очень удобно, если Вы хотите заставить компьютер выполнять в фоновом режиме какую-либо операцию через определенные промежутки времени. Обработчик не имеет никаких параметров:

on idle
[оператор]...
end idle

Есть здесь одна тонкость. Первоначально пауза между событиями idle устанавливается в 30 секунд, а затем (Внимание!) — приравнивается значению, возвращенному обработчиком (если это — число). Поэтому, чтобы избежать непредсказуемого поведения скрипта, рекомендую последним оператором обработчика записывать return — естественно, с параметром-числом, равным необходимому интервалу в секундах.

Попробуйте подложить коллеге в StartupItems вот такой простенький скрипт (не забыв при сохранении сделать его «stay open» и отключить стартовый диалог). Если приятель не слишком хорошо знаком с AppleScript, несколько приятных минут Вам обеспечено…

tell application "Finder"
  set frontmost to true
end tell

on idle
  beep
  return 2
end idle

on quit
  display dialog "Hello! rI'm virus!"
end quit

В этом примере использован обработчик еще одного события — «quit». А написан он таким образом, что на все попытки пользователя корректно завершить работу скрипта (через команду меню или ее эквивалент — Command-Q) будет просто выводится диалог, после чего «биканье» продолжится. На всякий случай, подскажу: можно завершить скрипт, минуя обработчик quit, если нажать Command-Shift-Q.

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

on quit
  display dialog "Завершить?" ¬
  buttons {"Нет", "Да"} default button "Да"
  if the button returned of the result is "Да" then
    continue quit
  end if
end quit

Обратите внимание: чтоб апплет мог корректно завершиться, в обработчике обязательно должен выполниться оператор «continue quit».

Область действия переменных

Последний вопрос, которого нам совершенно необходимо коснуться — три вида переменных в AppleScript: свойства, глобальные и локальные переменные.

Свойства объявляются и сразу же получают значения:

property theCount: 0

Глобальные и локальные переменные при объявлении никаких значений не получают:

global fileName
local first, last

Значения и свойств, и переменных могут быть изменены с помощью set или copy. Если в этих методах встречается переменная, ранее не объявленная, она будет создана локальной.

На первых порах Вам достаточно знать, что свойства и глобальные переменные могут быть использованы из любого места скрипта (причем, что интересно, их значения сохраняются и между запусками), а локальные — только в том обработчике, где они были объявлены.

Ну, что ж, закончим на этом наш «разговорник». Как справедливо замечено, нельзя объять необъятное. Не скрою, на эти страницы не попало еще очень много интересных и полезных элементов AppleScript. Возможно, мы еще вернемся к описанию языка. А пока, думаю, теорией я Вас уже загрузил изрядно. В следующей раз мы, обещаю, вновь возьмемся за наш верный ScriptEditor и…

Часть 3 →