Статьи

Розбираємося з JavaScript Automation для OS X

  1. Нове - добре забуте старе
  2. JavaScript for Automation
  3. Тісна інтеграція з системою
  4. Простий діалог з додатками
  5. Дружба з Automator
  6. документація
  7. готуємо інструменти
  8. рулимо браузером
  9. Лістинг 1. грабували посилання з вкладок
  10. Пишемо листа під копірку
  11. Лістинг 2. Працюємо з mail
  12. командуємо інтерактивно
  13. Міняємо bash на JS
  14. JavaScript vs. Objective-C
  15. # Лістинг 3. По мосту до Objective-C
  16. У пошуках alert'a
  17. Не все так просто

Останні роки JavaScript впевнено тримається на Олімпі популярних мов програмування. Численні фреймворки, розробка під популярні платформи закріплюють успіх і перуть в пам'яті бридкі кліше минулого. Мова росте, розвивається і стає логічніше, що не може не радувати багатотисячну армію його фанатів.

У 2014-му JavaScript удостоївся уваги серйозних хлопців з Apple. На конференції WWDC 2014 була анонсована нова технологія "JavaScript Automation", що дозволяє створювати додатки для OS X на цьому хіт-якій мові програмування. Спробуємо познайомитися з новинкою ближче і на реальних прикладах зрозуміти, а чи варта гра свічок?

Нове - добре забуте старе

У будь-якій нормальній операційній системі є засоби для автоматизації. Найчастіше вони являють собою хардкорні інструменти. Взяти, приміром, nix'и. Тут панує потужний BASH, здатний вирішувати найвитонченіші завдання. Проблема лише в його складності. Не всякий юзер вирішиться на творчий союз з цим швейцарським ножем. Виходить, що інструмент є, а користуються ним тільки просунуті одиниці.

Аналогічна ситуація з Windows, у якої є свій інтерпретатор у вигляді CMD. Простіший, але і менш функціональний - змагатися з BASH йому не під силу. Результат той же - інструмент є, а працювати з ним бажання немає.

Спеціально для таких користувачів були придумані всілякі прошарку і альтернативи. Так, наприклад, в Windows всі бажаючі можуть писати на тому ж JavaScript (причому дуже давно) або VBS. Пізніше ще додався потужний інструмент у вигляді PowerShell, робить можливість більш доступною автоматизації.

Світ OS X стикався з подібною ситуацією. Як не крути, а в основі цієї ОС завжди лежала BSD. Отже, засоби автоматизації в ньому початково слідують традиційному unix-way: BASH або будь-яку іншу мову програмування.

Є тільки одне "але". На відміну від своїх предків, OS X намагається по максимуму спрощувати користувачам життя. Їх середній користувач - зовсім не хардкорних гик, йому не потрібні 100500 малозрозумілих фич і чудові мовні пасажі. Для нього апріорі важлива юзабельность і простота - чим простіше, тим краще.

Бородаті користувачі повинні пам'ятати, що можливість писати автоматизують скрипти (сценарії) на JS з'явилася ще в далекому 2001 році. На жаль, тоді популярність він не завоював і декількома роками пізніше його змінив мову AppleScript, який надовго став стандартом. Це був не просто «ще одна мова програмування», а новий погляд на програмування для звичайних людей. Замість звичних досвідченим розробникам синтаксичних конструкцій, AppleScript спілкувався мовою, схожим на людський.

Маркетингові зусилля з боку Apple і райдужні відгуки простих смертних, зробили з AppleScript невеликий культ, який з виходом візуального кошти для автоматизації під назвою Automator тільки зміцнів.

Виходить, що JavaScript (JS OSA) в яблучному строю був давно, але за волею долі і з огляду на свою юність, несправедливо був покинутий на задвірки. І цю ситуацію легко можна зрозуміти, якщо згадати, що на початку нульових JS асоціювався більше з хуліганських інструментом для знущань над браузером, ніж з універсальною мовою програмування ...

JavaScript for Automation

Якщо програмувати на JavaScript для OS X теоретично можливо більше десяти років, то в чому ж тоді фішка настільки обговорюваного анонса? Невже компанія дозріла на запізнілу маркетингову компанію?

В останній версії OS X (Yosimite) була проведена велика робота, що дала можливість більш тісної взаємодії з системою. Тут мова навіть не про JavaScript, а про появу цілого комплексу API, бібліотек, що дозволяють в перспективі застосовувати для автоматизації не тільки JavaScript, але і інші мови програмування. Якщо не впиратися в технічні деталі, то це блюдо можна порівняти з .NET (вже вибач за грубе порівняння). Тобто, в нашому розпорядженні виявляється одне програмне ядро, однаково добре працює з іншими мовами програмування.

Виникає резонне питання: "А чому першопрохідцем вибрали JavaScript? ". Навряд чи хто відповість на це питання точно - офіційна інформація відсутня. Мені здається, що не останнім "за" в цьому питанні стала його популярність. Сьогодні ця мова переживає бум і армія його розробників впевнено зростає. Гріх не скористатися таким вдалим збігом обставин.

Ну а тепер - трохи технічних деталей.

Тісна інтеграція з системою

Мова вже не йде про банальну автоматизації в стилі "відкрив програму -> клікнув кнопку". Просунуті користувачі отримують можливість взаємодіяти з нативними фреймворками і бібліотеками. Раніше фіча була доступна знавцям AppleScript, а сьогодні її розширили і віддали в лапи ушлих JavaScript'еров. Завдяки доступу до Cocoa API, розробники можуть створювати додатки з нативним інтерфейсом прямо на JS. Причому, в більшості випадків не буде ніяких істотних провисань в швидкості в порівнянні з застосуванням Objective-C.

Простий діалог з додатками

Взаємодія з додатками зводиться до заповнення властивостей і виконання методів відповідних об'єктів. Ніяких хитромудрих і монструозних назв! Все зроблено в розрахунку на, скажімо так, програмістів середньої просунутості.

Дружба з Automator

Automator (візуальний засіб для автоматизації) не залишилося осторонь і його відразу потоваришували з JavaScript. Тепер, крім візуальних "кубиків" з логікою AppleScript, реально використовувати торішній код на JS.

документація

Презентація говорить про хорошу документації, але на мій погляд, тут все не так ідеально. Так, бібліотека з описом властивостей / методів стічних додатків зроблена добре. Наведено опис всіх вбудованих додатків і спробувати себе в ролі гуру OS X автоматизації стає можливим хвилин через 15 (само собою, при наявності досвіду програмування). А ось в питаннях більш тісної взаємодії з системою виникають деякі прогалини. Втім, упевнений, що це справа часу.

готуємо інструменти

Почнемо з редактора коду. В принципі, код можна писати в чому завгодно. Для мене останнім часом став стандартом вільний редактор Brackets. Правда, для першого знайомства з JavaScript Automation все ж краще скористатися стандартним редактором скриптів. Він знаходиться в "Програми" -> "Програми".

На мій погляд, стандартний редактор виглядає неповноцінне - відсутні звичні программерскую оці необхідні інструменти. Немає навіть елементарної нумерації рядків. Головний його плюс - простота запуску написаного коду. Закоділі кілька рядків коду і однією кнопкою виконали тестовий запуск.

Аналогічної поведінки можна домогтися за будь-яким іншим редактором, але тоді доведеться витратити час на настройку. Я цим питанням поки не морочився, але думаю, що особливих складнощів виникнути не повинно. У всякому разі, утиліта osascript (про неї трохи пізніше) покриває всі потреби по запуску сценаріїв з консолі.

Під час написання коду буде вкрай корисний вбудований в редактор скриптів журнал подій (Вікно -> Журнал подій). З нього JXA -девелопер черпає інформацію, необхідну для налагодження. На перших порах туди заглядати доведеться часто, тому що навіть наявність досвіду в розробці на JavaScript не врятує від деяких несподіванок, властивих JXA.

Відразу поглянемо на ще один інструмент, без якого навряд чи вдасться написати серйозний сценарій - "Бібліотека". Бібліотека зберігає інформацію про методи і властивості стандартних додатків. Як задумав щось автоматизувати, відразу зазирни в бібліотеку ( "Вікно" -> "Бібліотека").

Тепер спробуємо перевірити все вищесказане на практиці і створити найпростіший скрипт. Нехай це буде традиційний HelloWorld, але тільки своє вітання світу ми скажемо голосом. Спочатку у вікні редактора скриптів змінимо мову програмування на JavaScript. Потім наберемо три рядки і запустимо сценарій на виконання:

App = Application.currentApplication (); App.includeStandardAdditions = true; App.say ( "Hello, World!");

Якщо все введено без помилок, то приємний жіночий голос (все залежить від системних налаштувань) виголосить замилений в программерскую колах фразу.

рулимо браузером

У моєї улюбленої інтернет-бродилки постійно висять десятки відкритих вкладок. Побачиш що-небудь цікаве, а читати часу немає. Залишаєш вкладку відкритою і даєш собі обіцянку: "Трохи пізніше прочитаю". Ось тільки це "пізніше" не настає ніколи і вкладки починають хаотично збиратися. Ентропія наростає, і в підсумку зжирає всю доступну пам'ять. Потім розбиратися в цьому непотребі вже не хочеться і все відкриті вкладки разом закриваються.

З описаної проблемою я почав боротися давно, шляхом проб і помилок прийшовши нарешті до використання розширення OneTab. Зараз я покажу як приблизно те ж саме повторити засобами JXA. Заодно на реальному прикладі ми побачимо нюанси взаємодії з популярними програмами - Goolge Chrome і TextEdit. Зробимо новий скрипт і напишемо в нього код з лістингу 1 (ех, пам'ятаю, як Микиту Кисліцина дико дратували ці Ігореві «листинги», він навіть окремо забороняв використання слова «лістинг» в журналі :) - прим. ред.).

Лістинг 1. грабували посилання з вкладок

var googleChrome = Application ( "Google Chrome"); var textEdit = Application ( "TextEdit"); var newDoc = textEdit.Document (). make (); var content = ""; newDoc.name = "pagesFromBrowser.txt"; for (j = 0; j <= googleChrome.windows.length-1; j ++) {var window = googleChrome.windows [j]; for (var i = 0; i <= window.tabs.length-1; i ++) {content = content + window.tabs [i] .url () + "" + window.tabs [i] .name () + ""; } TextEdit.documents [ "pagesFromBrowser.txt"]. Text = content; }

Цей сценарій збереже посилання на відкриті вкладки в усіх відкритих вікнах браузера. Для наочності адреса сайту відділяється від заголовка сторінки символом табуляції. Тепер трохи загострити увагу на коді.

Насамперед необхідно встановити зв'язок з бажаним додатком. У моєму випадку це "Google Chrome" і "TextEdit". Нам потрібно створити екземпляр об'єкта для подальшої взаємодії. Для цього ми беремо і виконуємо глобальний метод Application. Як параметр йому необхідно передати ім'я додатка (id процесу, шлях до програми). Якщо все пройшло ok, то можна приступати до роботи.

Після отримання примірника об'єкта слід відразу відкрити "Бібліотеку" і подивитися доступні властивості / методи у вибрану програму. Я спеціально вибрав Google Chrome, так як його опис в бібліотеці відсутня. Як же бути? Офіційні коментарі мені не попалися, тому я ризикнув і списав назву методів з розділу про Safari. Код прекрасно заробив.

З TextEdit ситуація аналогічна - встановлюємо зв'язок і створюємо новий документ. Опис усіх методів і властивостей беремо з документації.

Оскільки у браузера може бути відкрито кілька вікон і в кожному з них відкриті закладки, необхідно пройтися по кожному з них. Для цього перебираємо колекцію windows, а потім у чергового вікна пробігають по вкладках (tabs). Далі йдуть стандартні можливості JS, які в додаткових коментарів вони потребують.

Наведену ідею легко розвинути і дописати код відкриття посилань з файлу. А що, вийде дуже навіть непогано! Подібна функція колись навіть була реалізована в полеглого смертю хоробрих (я про його оригінальний двигун) браузері Opera. Ну і само собою зробити підтримку різних браузерів. Відразу розглянемо приклад відкриття нової вкладки в Google Chrome:

window = googleChrome.windows [0]; newTab = googleChrome.Tab ({url: "http://iantonov.me"}) window.tabs.push (newTab);

Пишемо листа під копірку

Тепер поглянемо на вбудований поштовий клієнт (mail) з точки зору JXA. Спробуємо підключитися до цього додатка і сформувати новий лист. Цей приклад люблять наводити всі блогери, але вони обмежуються створенням нового листа з заповненою темою і текстом. Начебто нічого не заважає розширити приклад, але тут обов'язково (StackOverflow тому підтвердження) виникають труднощі. У другому лістингу я привів повноцінний код скрипта, що дозволяє створити новий лист, додати кілька одержувачів і пріаттачіть довільний файл.

Лістинг 2. Працюємо з mail

myMailApp = Application ( "Mail"); bodyTxt = "Привіт, мен! Це приклад відправлення листа за допомогою JS з OS X." + "Все гранично просто і зрозуміло."; newMessage = myMailApp.OutgoingMessage (). make (); newMessage.visible = true; newMessage.content = bodyTxt; newMessage.subject = "Лист щастя"; newMessage.visible = true; newMessage.toRecipients.push (myMailApp.Recipient ({address: "[email protected]", name: "Igor Antonov"})); newMessage.toRecipients.push (myMailApp.Recipient ({address: "[email protected]", name: "bot"})); newMessage.attachments.push (myMailApp.Attachment ({fileName: "/Users/spider_net/Downloads/[rutracker.org].t4878348.torrent"})); myMailApp.outgoingMessages.push (newMessage); myMailApp.activate ();

Тут ми йдемо за вже знайомою стежкою - встановлюємо зв'язок з додатком і починаємо заповнювати його властивості. Назви використовуваних властивостей і методів описані в документації. Варто лише звернути увагу на стиль заповнення об'єкта з листом.

За ідеєю, нічого незвичайного: инициализируем відповідний об'єкт і заповнюємо властивості. Однак, є один нюанс, з яким я зіткнувся при першому знайомстві з JXA. Дивись, по ідеї ми могли б записати весь код в традиційному для JS стилі:

myMessage = Mail.OutgoingMessage ({subject: "subject", content: "", visible: true, toRecipients: [myMailApp.Recipient ({address: "[email protected]", name: "Igor Antonov"}),] ...});

Код виглядає елегантніше, синтаксис абсолютно коректний ... але приклад правильно не відпрацьована. Новий лист буде створено, але рядок з одержувачами і список аттачей будуть порожні. На це варто відразу звернути увагу, тому що така ситуація буде тебе чекати ще в декількох ситуаціях.

У наведеному прикладі одержувач захардкожен, а в реалі його дані напевно є в адресній книзі. Навчити код працювати з додатком "Контакти" - справа кількох хвилин:

var contactsApps = Application ( "Contacts"); var recipientFromContacts = contactsApps.people [ "Igor Antonov"]; var name = recipientFromContacts.name (); var email = recipientFromContacts.emails [0] .value ();

Код знову ж досить логічний, тільки слід звернути увагу на отримання імені та email. Пам'ятай, name () - це метод, а не властивість. Отже, не забуваємо про дужки, інакше доведеться довго ламати голову над вивалюється помилками.

командуємо інтерактивно

Можливості автоматизації не обмежуються написанням скриптів в традиційному стилі. JXA дозволяє також виконувати код інтерактивно, дозволяючи відразу ж бачити результат дії кожного рядка. Продемонструвати все вищесказане допоможе утиліта OSASCRIPT. Відкриємо термінал і запустимо osascript:

$ Osascript -l JavaScript -i

Першим ключем ми вибрали використовуваний мову програмування (не забуваємо, все те ж саме можна зробити за допомогою AppleScript). Другий ключ вказує на бажання працювати в інтерактивному режимі.

Виконавши цю команду, ми отримаємо запрошення для введення (>>) JavaScript коду. Спробуємо для прикладу запустити браузер Google Chrome і відкрити в ньому кілька вкладок. Вводимо рядок і відправляємо її на виконання натисканням клавіші Enter.

$ Osascript -l JavaScript -i >> Chrome = Application ( "Google Chrome") => Application ( "Google Chrome") >> window = Chrome.windows [0] => Application ( "Google Chrome"). Windows.at (0) >> newTab = Chrome.Tab ({url: "http://xakep.ru"}) => app.Tab ({ "url": "http://xakep.ru", "make": [function anonymous]}) >> window.tabs.push (newTab) => 28 >> ​​newTab = Chrome.Tab ({url: "http://iantonov.me"}) => app.Tab ({ "url ":" http://iantonov.me "," make ": [function anonymous]}) >> window.tabs.push (newTab)

Міняємо bash на JS

За допомогою все тієї ж утиліти osascript можна писати традиційні консольні скрипти в стилі BASH. А що робить типовий консольний скрипт? Як правило, виконує якийсь довгий процес (типу резервного копіювання). Для будь-якого серйозного Скіпт потрібно робота з параметрами від користувача. Подібне цілком реально реалізувати на JXA. Приклад найпростішої болванки:

function run (argv) {console.log (JSON.stringify (argv))}

Для тесту запускаємо цей сценарій з консолі і передаємо йому кілька параметрів:

> $ Osascript cli.js -firstArgument -twoArgument >> [ "-firstArgument", "- twoArgument"]

Профіт очевидний - додаємо обробку необхідної кількості параметрів і робимо сценарій максимально гнучким.

До речі, якщо потрібно виконати невеликий код на JavaScript в консолі негайно, то необхідності в створенні окремого сценарію немає ніякої:

> Osascript -l JavaScript -e 'Application ( "Safari"). Windows [0] .name ()' >> JavaScript для OS X - Google Документи

JavaScript vs. Objective-C

Після прочитання пестрящей прикладами першої частини статті, може скластися враження про нескінченної крутості JavaScript. Почасти це дійсно так - робота зі стічними додатками проста, але ж на цьому JXA не закінчується.

Пам'ятаєш, я говорив про можливість використання нативних фреймворків? Так ось, це воістину потужна фіча! "Тепер-то можна не забивати голову неподатливим Objective-C і писати повноцінні програми на улюбленому мовою" - ось думка істинного фана JS ... стоп, я теж фанат, але ти не тіште себе ілюзією;). Можливість створювати додатки на JS з використанням нативних бібліотек - фіча, а не повноцінна заміна ObjC. Чим глибше ти будеш занурюватися в цю тему, тим більше помітиш обмежень.

Не варто також забувати, що Apple зовсім недавно представила нову мову програмування Swift. У найближчі роки він буде йти по п'ятах Objective-C і якщо експеримент вдасться, потіснить або зовсім витіснить його. На тлі цього факту перспективи JavaScript виглядають туманно.

Все вищесказане - не офіційна інформація, а суто особисті міркування. За роки практики роботи розробником мені вдалося засвоїти одне просте правило: навіть найкрутіші і вражаючі надбудови не зможуть повноцінно конкурувати з нативними засобами розробки.

Ми знаємо, що є крутий HTML5 і орда фреймворків / технологій, що дозволяють упакувати web-технологію в мобільний додаток, але за можливостями вони завжди будуть поступатися нативним інструментам. Саме тому (будь-яка статистика підтвердить) що 99% хітових додатків були створені на Objective-C, а не за допомогою чарівних надбудов.

До фиче розробки під OS X на JavaScript, на мій погляд, Варто ставити так само. Це прекрасний привід спростити свою роботу, не заморочуючись з вивченням екзотичного AppleScript, але ні в якому разі не срібна куля, що рятує від використання інших технологій.

Незважаючи на всю мою бурчання, Objective-C Bridge існує, а значить гріх ним не скористатися. Код повноцінного додатка привести не зможу - я не експерт в розробці під OS X, тому обмежуся створенням типового вікна з нативними елементами управління (див. Лістинг 3).

# Лістинг 3. По мосту до Objective-C

ObjC.import ( "Cocoa"); var styleMask = $ .NSTitledWindowMask | $ .NSClosableWindowMask | $ .NSMiniaturizableWindowMask; var windowHeight = 85; var windowWidth = 400; var window = $ .NSWindow.alloc.initWithContentRectStyleMaskBackingDefer ($ .NSMakeRect (0, 0, windowWidth, windowHeight), styleMask, $ .NSBackingStoreBuffered, false); var newLabel = $ .NSTextField.alloc.initWithFrame ($. NSMakeRect (25, (windowHeight - 40), 200, 24)); newLabel.stringValue = "Label"; newLabel.drawsBackground = false; newLabel.editable = false; newLabel.bezeled = false; newLabel.selectable = true; var newEdit = $ .NSTextField.alloc.initWithFrame ($. NSMakeRect (25, (windowHeight - 60), 205, 24)); newEdit.editable = false; var button = $ .NSButton.alloc.initWithFrame ($. NSMakeRect (230, (windowHeight - 62), 150, 25)); button.title = "Пімпа"; button.bezelStyle = $ .NSRoundedBezelStyle; button.buttonType = $ .NSMomentaryLightButton; window.contentView.addSubview (newLabel); window.contentView.addSubview (newEdit); window.contentView.addSubview (button); window.title = "Тема вікно"; window.center; window.makeKeyAndOrderFront (window);

Що почитати по темі?

Зазвичай я привожу купу корисних посилань до статті, але сьогодні набір буде біднішим. JXA занадто "молода" технологія і більшого обсягу літератури з нею поки немає. Є окремі невеликі огляди зарубіжних колег, трохи питань з відповідями (причому, питань без відповідей більше) на StackOverflow і мізерний офіційна документація. Тому, якщо ти всерйоз вирішив зайнятися JXA, то приготуйся до самостійного ресеченгу.

  • http://goo.gl/M3Bqx3 - офіційна документація в Mac Developer Library. До нормальної документації вона не дотягує (по правді кажучи, це технічний прес-реліз), тому особливо радіти не варто. Однак, прочитати її на разок потрібно, тому що по ній розкидано купа корисних фрагментів.
  • http://goo.gl/m5AO3h - Bath File Rename Script. Приклад повноцінного скрипта з використанням JavaScript for Automation. Як видно з назви, сценарій дозволяє перейменовувати файли, виділені в Finder. Друга цікава особливість прикладу - готовність до використання з Automator.
  • http://goo.gl/veMVWn - Репозиторий з прикладами, що демонструють використання моста до Objective-C. Приклади найпростіші і покликані допомогти розібратися із застосуванням стандартних елементів управління в JavaScript.
  • http://goo.gl/87SnZb - стаття Building OS X Apps with JavaScript. Добротний матеріал з прикладом розробки невеликого додатки для OS X, з використанням фреймворку Foundation (не плутати з однойменною CSS продуктом).
  • http://goo.gl/2egSwH - офіційна документація по Foundation Framework.
  • http://goo.gl/NrftJs - JavaScript for Automation CookBook. Репозиторій з корисною інформацією про застосування JXA. Інформації не так багато, як хотілося б, але обійти увагою її не можна.

У пошуках alert'a

Перше, що кидається в очі початківцям JXA розробникам - відсутність стандартних функцій на зразок Alert, Promt і т.д. У цьому немає ніякої помилки, тому що всі перераховані функції є тільки в браузерах. Для відображення діалогових вікон в JXA правильніше користуватися плагіном (includeStandartAdditions) або нативними API з бібліотеки Cocoa. Ось два корисних сниппета (з використанням плагіна і Cocoa відповідно):

// Alert засобами плагіна function alertPlugin (text) {App = Application.currentApplication (); App.includeStandardAdditions = true; App.displayAlert (text); } // Cocoa alert ObjC.import ( 'Cocoa') function alertCocoa (text) {var alert = $ .NSAlert.alloc.init var window = alert.window window.level = $ .NSStatusWindowLevel alert.messageText = text var result = alert.runModal}

Не все так просто

JXA, безумовно, цікаве рішення, але користуватися ним варто обережно. З точки зору можливості банальної автоматизації (взаємодія зі стічними додатками) все просто шикарно. JavaScript розробникам розв'язали руки і тепер вони можуть творити дрібні полезняшкі в своєму улюбленому форматі. Що стосується горезвісного моста до нативним бібліотекам, то, як то кажуть, проблем немає, користуйся, але не забувай про описані мною трохи вище обмежень. Чи плануєш серйозний проект - роби його за допомогою нативного інструментарію.

Вихідні тексти прикладів

Стаття опублікована в Журналі "Хакер" (http://xakep.ru). Лютий 2015 р

Посилання на агентство журнал: https://goo.gl/D22kkM

Спробуємо познайомитися з новинкою ближче і на реальних прикладах зрозуміти, а чи варта гра свічок?
Невже компанія дозріла на запізнілу маркетингову компанію?
Як же бути?
А що робить типовий консольний скрипт?
Пам'ятаєш, я говорив про можливість використання нативних фреймворків?

Новости