graywolf's lair

Inhuman being's diary…

“Складні речі – доступно, прості – робляться самі”

| 4 Comments

Якщо перефразувати девіз мови Perl
“Зробити прості речі простими, а складні можливими” для LISP,
то вийде “Зробити складні речі доступними, а прості робляться самі”

— Всеволод Дьомкін, Grammarly.
З презентації на HotCode 2013

Clojure Logo

У мене тут в чорновиках стільки записів — треба їх потроху публікувати. Але вони всі вже здоровезні, і я все не можу їх довести до ладу, тому я подумав, що варто робити з них менші і таки виводити в світ. Сьогодні я вирішив написати першу замітку про Clojure. Це, мабуть, наразі самий новий, але вже дуже популярний діалект LISP, що працює поверх JVM. Перші спроби наковбасити щось на Clojure у мене сталися ще кілька місяців тому, коли я написав простенький конвертор тест-кейсів з Testlink в TFS, але то було зроблено на колінці і окрім того, що я його написав дуже швидко, там пишатись особливо нема чим: це моя перша програмка на чомусь LISP-подібному не рахуючи простеньких завдань в універі, тому код страшненький, без всяких крутих ліспових фішечок типу макросів, з багами, але працює 🙂

Про Clojure я дізнався суто випадково на конференції Javascript Framework Days навесні цього року з презентації Олександра Соловйова про ClojureScript (це така підмножина мови, що транслюється в JavaScript, типу як CoffeeScript). До речі, рекомендую подивитись саму презенташку про FRP та ClojureScript – вона весела, динамічна, а мені не потрібно буде розповідати про те, що являють собою LISPоподібні мови і Clojure в тому числі.

А ось нижче приклад простенької задачки яку буквально на днях мну вирішив для себе. Суть полягає в тому, щоб розпарсити старий допотопний HTML на сайтах філармонії, театру Франка, органного залу та інших подібних закладів у iCalendar формат, аби їх можна було б собі підключити собі в Outlook чи Google Calendar. Парсити доводиться звичайний html, бо жоден з цих сайтів та агрегаторів не додумався зробити цього сам, або хоча б видавати афішу в rss-форматі (якщо хтось знає де є такі — скажіть). Вгадайте скільки рядків займає подібна програмка навіть на Python з будь-якими лібами 😉

Готовий закластися, що навіть на Python буде більше ніж це:

(ns eventic.core
  (:require [net.cgrand.enlive-html :as html]
            [clj-time.format        :as time]
            [clj-ical.format])
  (:import java.net.URL
           java.nio.charset.Charset))

; хелпер для завантаження сторінки (штатний рідер enlive не шарить кодувань)
(defn fetch-page [url]
  (-> url java.net.URL.
    .getContent (java.io.InputStreamReader. "WINDOWS-1251") html/html-resource))

; закачуємо сторінку
(def p (fetch-page "http://www.filarmonia.com.ua/ua.afisha"))

; хелпер для конвертації дат виду "30.07.2013" в datetime
(defn to-date [date]
  (time/parse (time/formatter "dd.MM.yyyy") date))

; переводимо усі теги <br> в \n, а всі інші теги прибираємо зберігаючи текст
(defn extractor [content]
  (map #(apply str
    (-> %  (html/at [:br] (html/content "\\n") [:*] html/unwrap))) content))

; вибираємо дати і конвертуємо в date-time формат
(def dates
  (map #(-> % :content first to-date) (html/select p [:table :tr :td :p :span])))
; вибираємо заголовки
(def titles
  (map #(-> % :content first str) (html/select p [:table :tr :td :h2])))
; вибираємо описи одразу переводячи їх в plain text
(def descriptions
  (rest (extractor (html/select p [:table :tr :td [:p (html/attr? :align)]]))))

; робимо структуру наповнення для iCalendar
(def ical
  (map #(vec (zipmap [:dtstart :summary :description] %))
    (map vector dates titles descriptions)))

; записуємо в файл
(spit "filarmony.ics"
  (->> (clj-ical.format/write-object
    (apply conj [:vcalendar] (map #(apply conj [:vevent] %) ical))) with-out-str))

І все. На виході отримуємо готовий файл у iCalendar форматі з усім необхідним. Можливо з першого погляду здаватиметься, що це якась тарабарщина, але насправді Clojure вчиться дуже швидко. Достатньо пари годин на день і вже за 3-4 дні можна писати маленькі корисні штуки для себе (я раніше постійно використовував Python для цього, але тепер в мене з’явилася нова робоча конячка 🙂 ). І суть навіть не в тому, що цей код компактний сам по собі. Суть в тому, що Clojure має такі засоби, що дозволяють створювати бібліотеки на кшталт Enlive, які надзвичайно потужні і прості в користуванні одночасно, а відповідно код що їх використовує стає простішим і його легше підтримувати.

А тим часом я думаю як це все оформити в простенький сервіс, який можна буде розгорнути на Google App Engine — все ж Clojure на JVM працює. Я вже надивився прикладів і презентацій на “Hackers with Attitude”, тож хочу тепер сам спробувати (і згодом описати це).

P.S. Ну і хто досі не читав “Перемогти посередність” Пола Грема, дуже рекомендую 😉

4 Comments

  1. Замість GAE рекомендую OpenShift, на ньому розвернути noir-сервіс мені було простіше. А замість Enlive краще взяти Hiccup, він в рази ідіоматичніший (якщо темлейти будеш писати ти сам, а не сторонні верстальщики далекі від sexp'ів).

    • Оу, дякую за поради! Обов'язково спробую. До речі, Noir же ж, здається, вже не підтримується – звідти виділили шматок в lib-noir, який не перекривався з Compojure та іншими бібліотеками над якими Noir був надбудовою.

      Я одразу планував використати Hiccup, але проблема в тому, що у мене вже є залежність від Enlive для парсингу HTML, тому вирішив не використовувати і те й інше і поки зупинитись на одному Enlive. Але Hiccup я обов'язково теж спробую на одному з наступних проектів в якості шаблонізатора.

      І мені ще цікаво а що за сервіс на OpenShift/Noir, якщо не секрет? Хочеться подивитись на живі реалізації.

      • То був іграшковий сервіс-привітання дівчини з 8 березня :-). Тому показувати особливо нічого, просто мені сподобалась простота розвертання Clojure на OS.

        Noir вже дійсно deprecated, але мені треба було зробити швидко і брудно, тому мені на той момент підійшло:).

        З вебу на кложурі в мене зараз лише статичний блог http://bytopia.org, зроблений за допомогою Static https://github.com/nakkaya/static

Залишити відповідь

Required fields are marked *.