Рубрики
Без рубрики

Автоматизация простых задач с помощью Scheme (конкурирует с Perl, Python и Ruby)

Написание скриптов Scheme/Lisp для автоматизации простых задач, которые обычно автоматизируются с помощью Perl, Python или Ruby

Автор оригинала: Rudolf Olah.

Логотип схемы ракетки/PLT

Пользователь reddit по имени Алан шутко заявил, что необходимо сделать Scheme , Common Lisp , Haskell и другие не мейнстрим-языки более привлекательными для среднего программиста.

Сравните это с типами простых программ, которые мы видим в Perl и Python. “У меня есть куча файлов, и я хочу переименовать их все по какому-то шаблону.” Общая проблема, простое решение. “У меня есть файл журнала, полный адресов электронной почты, мне нужно удалить их из записей журнала, удалить дубликаты и добавить их в базу данных.” Опять же, довольно простой, довольно маленький, действительно полезный. Когда Haskell сможет конкурировать в таких задачах, будет легче заставить людей изучать его. (То же самое с CL, моим любимым языком….)

Итак, вот схема программы, которая делает это. Он написан для использования MzScheme , потому что это единственная схема, которую я установил в Windows на данный момент. Таким образом, он использует преимущества PLaneT и других библиотек, которые поставляются вместе с MzScheme.

Надеюсь, это убедит других в том, что Scheme-хороший язык для общих задач.

Переименование файлов в схеме

У меня есть куча изображений, которые я загрузил с цифровой камеры, и все они ужасны. DSCxxxx.JPG узор. Давайте переименуем их, чтобы включить название коллекции изображений и дату их создания. Наиболее распространенным способом включения метаданных в изображение является использование EXIF. Я не буду вдаваться во все детали формата, так как это не имеет значения для примера. Все, что нам нужно знать, это то, что метаданные находятся в верхней части файла, файл является двоичным, и мы ищем отметку даты и времени.

Регулярные Выражения

Отметка даты и времени появляется после слова “Выход” в какой-то момент файла и имеет вид:

ГГГГ:ММ:ДД чч:мм:сс

Таким образом, наше регулярное выражение будет выглядеть так:

Exif.+(\d{4}):(\d{2}):(\d{2}).(\d{2}):(\d{2}):(\d{2})

Вот хорошая ссылка, которая поможет при расшифровке приведенного выше регулярного выражения .

Мы переименуем файлы, чтобы они выглядели так:

имя коллекции [year_month] идентификатор изображения

Например, файл DSC02484.JPG будет переименован в Canada Day [2007_08] DSC02484.JPG .

При циклическом просмотре файлов нам нужно будет убедиться, что мы переименовываем правильные файлы. Поэтому нам нужно еще одно регулярное выражение для проверки DSC в начале и расширения файла JPG в конце. Это выглядит так:

^DSC.+(jpg|JPG)$

Префикс для изображений, загруженных с вашей камеры, может быть другим, но я использую камеру Sony, и “DSC” – это типичный префикс.

Результат в схеме

Результирующий код таков:

(require
 (lib "file.ss")
 (lib "pregexp.ss")
 (lib "list.ss"))

;; Utility functions
;; Reads lines from the current input port until the end-of-file is reached
(define (read-lines)
 (letrec ((loop (lambda (lines)
            (let ((line (read-line)))
   	     (if (eof-object? line)
   		 (reverse lines)
   		 (loop (cons line lines)))))))
   (loop '())))

;; Reads and returns a list of all lines from a file
(define (read-lines-from-file filename)
 (with-input-from-file filename read-lines))

;; image? returns true if the file extension
;; of the path f is ".jpg" or ".JPG" and the filename begins with "DSC"
(define (image? f) (pregexp-match "^DSC.+(jpg|JPG)$" (path->string f)))

(define exif-datetime-regexp
 (pregexp "Exif.+(\\d{4}):(\\d{2}):(\\d{2}).(\\d{2}):(\\d{2}):(\\d{2})"))

;; The body
(define image-collection-name "Canada Day")
(define jpg-files (filter image? (directory-list)))

(for-each
(lambda (file)
  (for-each (lambda (line)
              (let ((winner (pregexp-match exif-datetime-regexp line)))
                (if winner
                    (rename-file-or-directory
                     file
                     (build-path (current-directory)
                                 (format "~a [~a_~a] ~a"
                                         image-collection-name
                                         (list-ref winner 1)
                                         (list-ref winner 2)
                                         (path->string file)))))))
            (read-lines-from-file file)))
jpg-files)

Первая строка импортирует необходимые библиотеки: pregexp.ss (библиотека регулярных выражений, совместимых с Perl), list.ss, которая содержит функции манипулирования списками, и file.ss, которая имеет полезные функции для манипулирования каталогами и файлами. Одной из полезных функций file.ss, используемых в нашем скрипте, является directory-list , которая возвращает список всех файлов и каталогов в пути (текущий каталог используется, если путь опущен).

Первые две функции-это просто служебные функции, которые я не хотел помещать в другой файл. Они считывают все строки из файла и превращают их в список для использования с map , for-each и другими функциями списка.

Следующая функция была объяснена выше. Он используется с функцией фильтра, чтобы отфильтровать файлы, которые нам не нужно переименовывать. После этой функции мы устанавливаем имя коллекции на то, что хотим. Затем возьмите список файлов, которые передают изображение ? тест.

После этого мы перебираем файлы, а затем перебираем строки в поисках формата даты/времени EXIF. Если мы найдем формат, мы переименуем файл. Мы должны использовать функцию build-path , чтобы убедиться, что разделители имен файлов работают на платформе, на которой мы находимся. Функция list-ref используется для выбора частей формата даты/времени, которые мы хотели бы использовать: (list-ref winner 1) выбирает год и (list-ref winner 2) выбирает месяц.

По запросу будут предоставлены разъяснения. Этот код кажется самоочевидным, но я уверен, что разработчики Perl/C++/Java чувствуют то же самое по отношению к своему самому запутанному коду.

Это Было Похоже На То, Но Не Было

Такие языки, как Perl, Python и Ruby, позволяют писать ненужные скрипты. Perl был назван языком только для записи, потому что вы пишете сценарий, чтобы исправить что-то, а затем выбрасываете его, потому что вы можете легко переписать его в следующий раз, когда это потребуется. Философия этих языков состоит в том, чтобы дать вам возможность писать простые, распространенные программы как можно проще. Я заметил, что, когда я писал сценарий, я искал полезные функции, которые я мог бы повторно использовать в будущем, когда это будет необходимо.

На днях я написал небольшой скрипт Python для замены определенных строк в определенных файлах. Когда я писал ее, все, о чем я мог думать, – это о том, чтобы починить эти проклятые файлы. Это было так просто, что я не думал о том, как я буду делать это в будущем для других типов файлов. Ответ прост: создайте функцию, которая принимает имя файла и список списков, где каждый подсписок содержит строку для сопоставления и строку для замены. Этот ответ пришел легко после того, как давление было снято. Но с помощью Scheme я всегда ищу макросы и функции для создания , чтобы облегчить потенциальные проблемы, которые у меня могут возникнуть.

В заключение следует отметить, что философия и сообщество, окружающие семейства Lisp и Perl/Python/Ruby, различны. Это хорошо видно по слабым попыткам эмулировать CPAN. Это видно по отсутствию общих задач, выполняемых с помощью Lisp. Но, как я уже показал, это можно сделать…