RSS новости
Играть онлайн в игры денди
 
Каталог / Разное
Ссылка на ленту:

советы.блогспот.ком

жизнь на свободе: каждый день с GNU/Linux

Загружается, подождите...

23:28 25.11.10

unplot.py: извлекаем табличные данные из графиков

Недавно разгребал резервные копии, и обнаружил скрипт, который, пожалуй, может кому-нибудь пригодиться. Программа делает действие обратное построение графику: по изображению графика восстанавливает таблицу с числовыми данными. Поэтому она и называется unplot.

Скачать: unplot.py.

Исходники лежат на bitbucket. Исправляйте и улучшайте.

Перед запуском программы нужно вначале определить координаты (в пикселях) той части графика, которую надо обработать. Это можно сделать в Gimp или в Geeqie.

Если на графике несколько линий, то нужную из них нужно покрасить отдельным цветом, отличным от всех других. Это тоже можно сделать в Gimp. Нужно записать HTML-код её цвета.

В общем, исходный график должен выглядеть примерно так:

colourized plot

После этого можно запустить и сам скрипт:

./unplot.py "#00ff00" 0 151 0 475 5.0 824 0.09 85 /путь/к/plot.png > /путь/к/data.txt


Первый параметр — это код цвета линии. Вторые два числа это значение и координата X левого нижнего угла. Потом ещё два числа, значение и координата Y того же угла. Потом ещё четыре числа — соответственно для правого верхнего угла. И в конце имя файла с графиком. Результаты (текстовую таблицу с данными) перенаправляем в файл. Можно ещё задавать «чувствительность» к цвету (параметр -s).

После применения скрипта полезно построить ещё раз график по полученным точкам и убедиться, что выбран правильный участок графика, указан правильный цвет и разумные числовые значения точек. Например, в этом случае я захватил слишком много справа, и зелёные буквы тоже были учтены как точки графика.



Обновление 20101203: теперь скрипт умеет обрабатывать графики с линией толще 1 пикселя; также в репозитории появился и небольшой скрипт для тестирования unplot.py на вменяемость.


Flattr this


По-английски: Unplot.py: from plots to tabular data.


Безболезненный upload с помощью Wondershaper

Наверное, многие замечали, особенно на домашнем интернете, что во время интенсивной закачивания данных в сеть (во время upload), соединение начинает «тормозить». Страницы сайтов подолгу не открываются, скачивание и медиапотоки подвисают на месте... Это легко измерить: у меня дома, например, пинг до гугла вырастает с 50 мс до порядка 1000 мс, при этом около 8% пакетов вообще теряются.

Причина этого явления (чаще всего) — загрузка в сеть занимает весь доступный исходящий канал, а соединения по протоколу TCP/IP требуют двухсторонней связи. Даже если мы пытаемся что-то просто скачать, машина должна посылать обратно пакеты подтверждения приёма (т.н. ACK). Если же исходящий канал весь занят, то и пакеты подтверждения, какими бы маленькими они не были, надолго задерживаются, и, соответственно, перестают приходить и входящие пакеты, даже если ширина входящего канала достаточна.

Решить эту проблему можно с помощью скрипта wondershaper (есть в составе большинства дистрибутивов). Он позволяет установить ограничения на скорость загрузки из сети и в сеть. Хитрость в том, чтобы вначале узнать свою реальную скорость соединения, а потом установить ограничения чуть-чуть ниже реальной доступной скорости. В этом случае будет всегда оставаться «запас» для своевременной передачи служебных пакетов, и задержки станут гораздо меньше.

Например, я хочу выложить кучу фотографий на яндекс-фотки. Иду на internet.yandex.ru и измеряю скорость соединения с Яндексом, сегодня вечером у меня получилось так:

Вниз: 4044 Кбит/с. Вверх: 490 Кбит/с.

Можно пойти на testspeed.net или другую измерительную страничку. Можно измерить вручную. Главное, получить реальные значения скорости в килобитах в секунду.

После этого надо включить wondershaper на используемом сетевом интерфейсе и указать ему значения немного меньше измеренных. Сетевой интерфейс — это обычно wlan0 в случае соединения по WiFi, или eth0 в случае соединения по локальной сети. Чтобы уточнить, можно выполнить команду route -n в терминале, имя используемого интерфейса искать в строке с флагами UG.

$ sudo wondershaper wlan0 3900 450


Готово. Запускаем загрузку в сеть (выкладываем фото, видео, раздаём торренты, и т.д.), измеряем время пинга. Чудесным образом, даже во время загрузки, пинг у меня упал до 61 мс, что не сильно больше его минимального значения при свободном канале, а веб-страницы стали скачиваться вполне шустро. Если не помогло с первого раза, можно попробовать ограничить скорость закачивания в сеть ещё сильнее.

Чтобы отключить wondershaper, выполнить:

$ sudo wondershaper clear интерфейс


Как аппликативные функторы с if-ами боролись

Maybe А и Maybe Б сидели на трубе,
А упало, Б пропало, что осталось на трубе?


На днях заметил, как полезны могут быть аппликативные функторы для избавления от леса вложенных условных конструкций (if-ов и case-ов). А именно, как удобно, что тип Maybe можно использовать как аппликативный функтор.

О функторах, пирогах и яблоках



Речь пойдёт о языке Хаскель, но постараюсь рассказать так, чтобы пользователям других языков было тоже понятно. Кто Хаскель знает, этот раздел может пропустить и сразу перейти к следующему.

Итак, для представления такого результата, которого может и не быть, в Хаскеле используется тип Maybe a, где a — любой тип. У Maybe a бывают значения двух видов: Just a («просто а», если значение есть) и Nothing («ничего», если значения нет). Maybe является функтором, то есть из любой функции из a в b (тип :: a -> b) можно сделать функцию из Maybe a в Maybe b (тип :: Maybe a -> Maybe b). В общем, функтором является любой тип f, для которого определена*:

fmap :: (a -> b) -> (f a -> f b)


* определение fmap должно удовлетворять при этом двум условиям: 1) fmap id = id, где id — функция, возвращаюая свой аргумент, 2) fmap должна быть дистрибутивна слева относительно композиции функций... [ссылка]


Например, пусть у нас есть типы Яблоко и Пирог и функция испечь :: Яблоко -> Пирог, превращающая одно в другое. Тогда мы легко можем взять яблоко, которого может и не быть, и испечь из него пирог, , которого может и не быть. То есть если будет просто яблоко, то выйдет просто пирог, а если ничего не было, то ничего и не выйдет. Делается это проще простого: fmap испечь.

data Яблоко = Яблоко deriving Show
data Пирог = Пирог deriving Show

испечь :: Яблоко -> Пирог
испечь Яблоко = Пирог

испечьЕслиЕсть :: (Maybe Яблоко) -> (Maybe Пирог)
испечьЕслиЕсть = fmap испечь


После этого мы можем печь пироги, которых может и не быть:

ghci> испечьЕслиЕсть (Just Яблоко)
Just Пирог
ghci> испечьЕслиЕсть Nothing
Nothing


А что делать, если самой функции из a в b может и не быть? Как применить Maybe (a -> b) к Maybe a? Такая операция определена для аппликативных функторов, Maybe — один из них. В Хаскеле для этого подключают модуль Control.Applicative, саму же операцию последовательного применения называют звучным словом (<*>).

Поясняю на яблоках. Что делать случае на входе у нас Maybe (Яблоко -> Пирог) — рецепт, которого может и не оказаться? Естественно, что пирог у нас получится тогда и только тогда, когда есть и яблоко, и сам рецепт. Если хоть что-то отсутствует, то и пирога не будет, будет Nothing. Итак, дано:

безРецепта = Nothing
сРецептом = Just испечь


Проверяем, как работает с такими «рецептами» аппликативный функтор Maybe:

ghci> :m + Control.Applicative
ghci> сРецептом <*> Just Яблоко
Just Пирог
ghci> сРецептом <*> Nothing
Nothing
ghci> безРецепта <*> Just Яблоко
Nothing
ghci> безРецепта <*> Nothing
Nothing


Это всё, что нам сейчас потребуется из «теории». И ещё пара замечаний: 1) с аппликативными функторами часто применяется ещё операция (<$>), которая является для них синонимом fmap и в инфиксной записи выглядит короче; 2) любая монада является также аппликативным функтором (обратное неверное), и для неё ap и (<*>) совпадают (об этом полезно знать, хотя нам сейчас и не потребуется).

Избавление от условных конструкций



Перехожу к практическим вопросам. На днях писал небольшой парсер для файлов формата Matrix Market. Это несложный формат, состоящий из заголовка в котором указывается, как хранится структура матрицы (покоординатно или сплошным массивом) и тип значений её элементов (целые, действительные или комплексные). Возможны разные комбинации.

Логику разбора такого формата на императивном псевдокоде можно описать так:

if (структура == координатная) {
if (тип == целые) {
читатьЦелыеПоКоординатам;
} else if (тип == действительные) {
читатьДействительныеПоКоординатам;
} else if (тип == комплексные) {
читатьКомплексныеПоКоординатам;
} else {
ошибка;
}
} else if (структура == массив) {
if (тип == целые) {
читатьМассивЦелых;
} else if (тип == действительные) {
читатьМассивДействительных;
} else if (тип == комплексные) {
читатьМассивКомплексных;
} else {
ошибка;
}
} else {
ошибка;
}


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

Опять псевдокод для пояснения этой идеи:

let читатьСтруктуру = lookup структура словарьФункцийРазбора
читатьЧисло = lookup тип словарьФункцийРазбораЧисла
in читатьСтруктуру читатьЧисло исходныеДанные


На практике дело обстоит немного сложнее. Операция поиска в словаре может ничего не найти. Как легко догадаться, lookup в Хаскеле возвращает Maybe a. Итак, у нас есть Maybe функция, Maybe аргумент, а мы хотим получить Maybe результат. Ничего не напоминает? Да-да, тут самое время применить (<*>).

Однако представим на минуту, как эта задача решается без операции последовательного применения Maybe a. И для функции, и для её аргумента нужно проверять являются ли они чем-то (Just a) или ничем (Nothing), и в результате получается тот же лес проверок, что и в императивном псевдокоде, только в этот раз с проверкой на результат поиска. Можно, правда, все проверки объединить в одной единственной условной конструкции на каждое применение функции, но все проверки надо всё равно записывать явно. То есть без (<*>) получается что-то такое:

let читатьСтруктуру = lookup структура словарьФункцийРазбора
читатьЧисло = lookup тип словарьФункцийРазбораЧисла
in case (читатьСтруктуру, читатьЧисло) of
(Just f, Just g) -> Just (f g исходныеДанные)
_ -> Nothing


Код склонен паталогически ветвиться, если таких Maybe-аргументов у функции больше одного, или если полученный Maybe-результат нужно использовать в качестве аргумента ещё одной функции (тут не обойтись без вложенных проверок).

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

let читатьСтруктуру = lookup структура словарьФункцийРазбора 
читатьЧисло = lookup тип словарьФункцийРазбораЧисла
in читатьСтруктуру <*> читатьЧисло <*> (Just исходныеДанные)


Здесь нет ни одной явной условной конструкции, но код, тем не менее, делает все необходимые проверки, и вернёт Just результат только если нашлись обе функции.

У меня получилось немного длиннее, но суть та же:

let p = lookup fmt parsers        :: Maybe FormatReader
nr = lookup fieldq readers :: Maybe (Int, ValueReader)
sy = lookup symq symmetries :: Maybe Symmetry
p' = uncurry <$> p <*> nr :: Maybe ([String] -> Maybe MatrixData)
d = join $ p' <*> (Just toks) :: Maybe MatrixData
m = MM <$> d <*> sy :: Maybe Matrix
in ...


Что тут можно заметить. Во-первых, (<$>) практически так же часто используется, как и (<*>). Эта операция позволяет применять обычные функции к Maybe-значениям. Вместо (<$>) можно поднимать значения в функтор с помощью pure (для любых аппликативных функторов) или Just (конкретно для Maybe) и использовать только (<*>).

Во-вторых, если у нас кругом функции, возвращающие Maybe, и они сами оказываются внутри Maybe (см. p' в моём примере), то рано или поздно появляются «вложенные» значения типа Maybe (Maybe a). Тут помогает монадный join, превращающий их в просто Maybe a. join определён в Control.Monad. И кстати, это тоже помогает избавиться от вложенных тривиальных проверок (join (Just Nothing) даёт Nothing).

В-третьих, при таком подходе мы, естественно, теряем информацию о том, какое именно вычисление не дало результата (дало Nothing). Для передачи «исключения» по цепочке можно использовать другие типы, например Either e, определив для них Applicative.

Заключение



Мне такой способ комбинировать вычисления очень понравился. По-моему, случай, когда все условные проверки сводятся к «если в порядке, то считать дальше, а если нет, то прервать» — довольно распространённый. Тип Maybe с операцией последовательного применения (<*>) позволяет такие проверки, любой степени вложенности, не писать вообще.


Flattr this


Типографская раскладка в Линуксе

В блоге «Словомания» полезная заметка: Типографская раскладка в Линуксе. Оказывается, в свежих версиях есть уже готовая типографская раскладка, почти как раскладка Бирмана.

Типографская раскладка в Линуксе

Как включить: в параметрах раскладки клавиатуры включить «Клавишу для выбора 3-го уровня» (например, правый Alt). В списке «Разные параметры совместимости» — «Включить дополнительные типографские символы». Подробнее здесь.

По теме:

Ввод символов с акцентами в Linux
Как задавать произвольные Compose-последовательности

По-английски: Typography keyboard layout in Linux.


Удаление пыли на матрице в Gimp

Недавно нашёл лёгкий способ удалять пятна грязи с фотографий. Речь о пятнах пыли на матрице. Если на камере объективы сменные и иногда меняются, грязь на фоточувствительный элемент рано или поздно проберётся. И встроенная очистка справляется с ней с переменным успехом. Выглядят эти пятна примерно так:

Пятна от грязи на матрице

Их легко распознать, они появляются во всех снимках серии на одном и том же месте. Наиболее заметны они становятся на маленькой диафрагме, при съёмке более-менее однотонных объектов (небо, море, далёкие холмы). Поэтому можно поставить маленькую диафрагму (скажем, f/22 или меньше) и снять чистое небо. Если пятна сильно заметны, то матрицу придётся чистить.

Я же расскажу, что делать с фотками, на которых эти пятна уже есть. Использую для этих целей Gimp и плагин Resynthesizer. Конечно, можно было бы обойтись обычной клонирующей кистью, но вычищать несколько фотографий подряд, особенно если там по несколько пятен на каждой, утомительно. А Resynthesizer оказался неплохой автоматической «клонирующей кистью». Пользователи Ubuntu могут установить плагин пакетом gimp-resynthesizer.

Вначале пятна надо найти и выделить. Я обычно использую свободное выделение (лассо). Чтобы выделить сразу несколько пятен, удерживаю нажатой клавишу Shift. Важно захватить в выделение достаточное количество «чистых» пикселей на границе: они влияют на результат работы Resynthesizer.

Затем можно запустить сам плагин: Фильтры -> Карта -> Resynthesize.... В общем, параметры по-умолчанию работают обычно достаточно хорошо. Возможности периодического мощения (make ... tileable) нам в этом случае не нужны.



Плагин не очень шустрый. На большой фотке (10-15 мегапикселей) нужно будет несколько секунд подождать. Впрочем, это всё равно быстрее и легче, чем использовать клонирующую кисть.

Наконец, нужно осмотреть результат, нет ли никаках странностей. Скорее всего, всё будет нормально (пятна обычно заметны как раз на таких местах, которые Resynthesizer легко восстанавливает: небо, облака, море, сплошная растительность). Если выделение было слишком тесным, его граница прошла слишком близким к пятну, то на месте пятна может всё равно остаться размазанное потемнение. В этом случае нужно отменить исправление и повторить, выделив пятна по-другому. Однако обычно всё удаляется с первой попытки:

Пятна грязи на матрице удалены плагином Resynthesizer

В общем, чистого всем неба! Если полезно — Flattr this.

Riviera

Also in English: Cleaning sensor dust with Gimp.


Впечатления о Цюрихаке

Вчера вернулся с Цюрихака. Интересная была поездка. Программировать в компании 80 других фанатов очень даже занятно. Здорово, что можно прямо тут же что-то обсудить, наметить цели, поделить работу и её сделать. Приятно браться за незнакомый код и в сжатый срок добавлять к нему что-то полезное (а с Хаскелем такое по силам даже новичкам; и новичкам с готовностью помогали). Большинство участников поселились все вместе в одном хостеле, так что вечерами собирались большими компаниями, чтоб выпить кружечку пива, познакомиться, послушать рассказы других людей, поделиться своим опытом и впечатлениями. Ну и просто увидеть людей, имена которых были уже знакомы, тоже интересно. Вот они, цюрихакеры (я тут тоже есть):





Были, конечно, люди, для которых Хаскель — основная работа. Это и PhD-студенты, творящие что-то с Хаскелем, и сотрудники нескольких хаскельных контор, и небезызвестные исследователи, например Atze Dijkstra. Но было много и таких, как я, для которых Хаскель — хобби. Кто-то работает программистом, кто в банке, кто ещё где, а в свободное время — ну понятно. Много хороших, увлечённых людей. Из наших там ещё был Роман Чепляка из Киева. Очень приятными собеседниками оказался Бартек Войчик, работающий в Мюнхене, и Леннарт Колмодин, швед с итальянскими корнями (с ним мы жили в одной комнате). Много ещё кого, всех трудно перечислить. Контактов в твиттере прибавилось.

Я, наконец-то, сподобился выложить свой Snusmumrik на Hackage. Так что он теперь должен устанавливаться по одному волшебному заклинанию cabal install Snusmumrik (но только со старым GHC 6.10). Среди выложивших пакеты на Hackage разыгрывалась футболка, но мне она всё равно не досталась. Зря выкладывал?

В основном же работал с программой автоматического учёта времени, arbtt. Добавил в её классификатор календарные функции, которых мне прежде не хватало. Ребята сделали ещё кое-что полезное, так что набралось на новый релиз. Эту работу, как видно из лога, мы сделали с Йоахимом Брайтнером, немецким боснийцем Мухаремом Хрнъядовичем (сомневаюсь в правильности произношения фамилии) и Мартином Кифелем.

К сожалению, разные доклады, назначенные на вечер воскресенья, мне пришлось пропустить. Поезд. Очень жаль, что их не поставили пораньше.

Весь расчудесный офис Гугла в Цюрихе нам не показали. Мы проходили только через фойе, кухню-столовую (это где спиральная горка со второго этажа) и потом сразу в какой-то большой зал. То ли спортивный, то ли для встреч. В пятницу вечером Гугл угостил всех закуской, предложив, в том числе, сэндвичи метр на метр и ледяного пива с избытком. Воду и напитки можно было брать из холодильника без ограничений, чай тоже был. В гугловом туалете над писсуаром и в кабинке висят памятки «Git on the Go», как пользоваться Git-ом. Прочитал :-) А вот фотографировать в офисе запретили (но есть фотки — много фоток —, сделанные гуглерами-организаторами; всё так и было).

Сам Цюрих я не особо разглядел. Ну так, аккуратный городок, ок. Есть красивые места, но уж слишком много магазинов со сверкающими витринами. Убедительной обшарпанности ему не хватает. Ну и новая архитектура — как и офис гугла: кубы домов с панелями приглушённых серо-коричневых оттенков. Тоска. А вот дорога на поезде из Милана в Цюрих очень красива. Узкие, глубокие долины. Местами дорога поднимается достаточно высоко, по моим оценкам не ниже 1000 метров, там ещё лежит снег. А ниже — уже зеленеют луга. Кстати, в швейцарских электричках велосипедные места есть во всех вагонах. В самом Цюрихе велосипедистов тоже много. Видимо, Швейцария вообще чрезвычайно дружественна к велосипедистам (вспоминаю Лозанну, там с этим тоже хорошо). Вот только швейцарские банкиры хуже таксистов: разница обменных курсов 6% + 4 франка (почти 3 €) комиссии за каждый обмен. И всё дорого.

Общее же впечатление, что 2½ дней очень мало. Мне не хватило.

Дополнение: Критика хакатона Романом Чеплякой. По-английски.


15:10 29.01.10

Автоматический учёт времени: Arbtt macht frei!

В линуксе есть несколько разных программок для учёта времени, самая простая и незамысловатая, и при этом вполне функциональная — это, пожалуй, Hamster. С ней всё понятно: добавляем на панель, вбиваем новое дело всякий раз, когда за него берёмся. Главное, не забывать.

А вот есть программка похитрее: arbtt. Пользоваться ей, правда, легче. Она полностью автоматическая. Достаточно запустить arbtt-capture и заниматься своими делами*. arbtt-capture будет записывать когда и какие программы были запущены и какие у окон были заголовки.

* Автор arbtt рекомендует сразу добавить arbtt-capture в автоматически запускаемые приложения.


Чтобы увидеть необработанные сырые данные, можно выполнить arbtt-dump, но это не очень полезно. Для просмотра статистики удобнее использовать использовать утилитку arbtt-stats.

Чтобы arbtt-stats могла выдавать осмысленные результаты, нужно вначале задать свою классификацию запущенных программ. Эти правила вписываются в файл ~/.arbtt/categorize.cfg. Пример и описание формата правил есть в документации. Приведу свой (сокращённый) пример с комментариями по-русски:
-- правила имеют вид:
-- [условие ==>] tag [категория_тега:]тег,
-- в условиях и тегах можно использовать несколько специальных переменных,
-- почти все они встречаются в примерах ниже

-- Не учитывать время простоя
$idle > 60 ==> tag inactive,

-- Все записи за последние 24 часа пометить тегом last-day
$sampleage <= 24:00 ==> tag last-day,
-- Пометить тегом last-hour все записи за последний час
$sampleage <= 1:00 ==> tag last-hour,

-- Все типы окон Firefox учитывать в одном теге program:web (program — это категория тега)
current window $program == "Navigator" ==> tag program:web,
current window $program == "firefox-bin" ==> tag program:web,
current window $program == "gecko" ==> tag program:web,
-- Общий тег для всех видов терминалов (на будущее)
current window $program == "gnome-terminal" ==> tag program:terminal,
-- Пометить все остальные программы пометить тегами вида program:имя_программы
tag program:$current.program,

-- Классифицировать заголовки Firefox с помощью регулярных выражений. Тут у каждого будут свои шаблоны.
-- Присваивать теги категории web.
current window ($program == "Navigator" && $title =~ /^Gmail.*/) ==> tag web:Gmail,
current window ($program == "Navigator" && $title =~ /.*Google Search.*/) ==> tag web:Google,
current window ($program == "Navigator" && $title =~ /^Twitter.*/) ==> tag web:Twitter,
current window ($program == "Navigator" && $title =~ /.* on Twitter - Iceweasel$/) ==> tag web:Twitter,
current window ($program == "Navigator" && $title =~ /^Springer.*/) ==> tag web:Papers,
current window ($program == "Navigator" && $title =~ /^Wiki - Editing.*/) ==> tag web:Papers,
-- ...
--
current window $program == "Navigator" ==> tag web:$current.title,

-- Теги категории time-of-day для классификации по времени суток
$time >= 2:00 && $time < 8:00 ==> tag time-of-day:night,
$time >= 8:00 && $time < 12:00 ==> tag time-of-day:morning,
$time >= 12:00 && $time < 14:00 ==> tag time-of-day:lunchtime,
$time >= 14:00 && $time < 18:00 ==> tag time-of-day:afternoon,
$time >= 18:00 && $time < 22:00 ==> tag time-of-day:evening,
$time >= 22:00 || $time < 2:00 ==> tag time-of-day:late-evening,

-- Помечать над каким проектом работаю судя по заголовку окна.
-- Присваивать теги категории project.
current window $title =~ m!~/work/projectA! ==> tag project:projectA,
current window $title =~ m!~/work/projectB! ==> tag project:projectB,
-- ...
--

-- Помечать, какой тип текста я редактирую судя по заголовку окна.
-- Присваивать теги категории edit.
current window ($title =~ /^[^ ]+\.c .* - G?VIM.*$/) ==> tag edit:c,
current window ($title =~ /^[^ ]+\.py .* - G?VIM.*$/) ==> tag edit:python,
current window ($title =~ /^[^ ]+\.hs .* - G?VIM.*$/) ==> tag edit:haskell,
-- Когда использую suduedit
current window ($title =~ m!.*\(/var/tmp\) - G?VIM.*$!) ==> tag edit:config,
-- Когда редактирую что-то онлайн в Its All Text
current window ($title =~ m!.*/itsalltext\) - G?VIM.*!) ==> tag edit:itsalltext,

Для отчёта по определённой категории:
$ arbtt-stats -c имя_категории
Для просмотра отчётов по всем категориям:
$ arbtt-stats --each-category
Для ограничения выборки только записями с определённым тегом, например, last-hour, есть опция -o. Всё вместе:
$ arbtt-stats -o last-hour -c program -c edit
Statistics for category program
===============================
_____________Tag_|___Time_|_Percentage_
program:terminal | 29m00s | 48.33
program:gvim | 17m00s | 28.33
program:web | 13m00s | 21.67
program:Pidgin | 1m00s | 1.67

Statistics for category edit
============================
_____________Tag_|___Time_|_Percentage_
edit:itsalltext | 17m00s | 28.33
edit:haskell | 4m00s | 6.67
(unmatched time) | 39m00s | 65.00
В последнем примере я показал примерный вывод программы. Сразу видно, сколько времени за последний час я что-то редактировал и что именно и какие программы использовал. Писал эту заметку, в общем.

Кстати, arbtt есть не только в линуксовых репозиториях, но в скором времени (а может и уже) будет доступна и пользователям Windows.

Некоторые замеченные изъяны: arbtt-stats при печати портит заголовки окон с уникодом (патчем на 20 строк исправляется, должно быть ОК при сборке новым GHC), пока нельзя классифицировать по дням недели или по месяцам, сообщения о синтаксических ошибках в правилах очень невнятны.

Дополнение: замеченные недостатки, не без моего скромного участия, исправлены во время Хакатона в Цюрихе; используйте GHC 6.12 и устанавливайте новую версию 0.5; там всё ОК.

Приятных всем выходных!


Три способа отрезать поля у PDF-документа

Речь пойдёт о том, как отрезать поля (и вообще изменить размер страниц) в PDF-документе. После того, как у меня появилась читалка на электронных чернилах, делать это приходится довольно часто.

В чём проблема: большинство PDF* свёрстаны под печатную страницу формата A4 (29,7×21 см) или Letter, с полями, колонтитулами, всё как положено. А типичный экран читалки — 12×9 см с разрешением 800×600 точек. Даже если показывать по половине странице, на страницу приходится всего 1200×800 точек (и 18×12 см площади экрана). Значит, даже при просмотре страниц «половинками» буквы будут примерно в 1,65 раза мельче, вдобавок и разрешение при этом будет тоже как минимум раза в полтора ниже. Короче говоря, значительная доля PDF, свёрстанных под печать, на нынешних электронных читалка нечитаема.

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

На сегодняшний день я нашёл и попробовал три способа обрезать поля у PDF-файла.

1. Обрезка полей с помощью pdfcrop



Есть скрипт pdfcrop на перле (не путать с одноимённым скриптом на питоне), который умеет обрезать поля автоматически. В Debian он входит в состав пакета texlive-extra-utils.

Использовать так:
$ pdfcrop --clip --margin 5 исходный.pdf обрезанный.pdf


Советую всегда всё равно оставлять небольшое поле (--margin 5), иначе касающиеся края буквы могут не отображаться на экране читалки.

В общем, всё просто. Преимущества: простой автоматический способ, по полученному таким способом PDF сохраняется возможность поиска. Недостатки такого способа: pdfcrop очень медленно работает с большими документами (сотни страниц), нельзя автоматически отрезать колонтитулы и заметки на полях (в некоторых случаях проще обойтись без номеров страниц и названия главы сверху, зато получить более крупное изображение основного текста), конкретно моя читалка иногда аварийно перегружается на полученных таким способом PDF, на некоторых файлах pdfcrop неправильно определяет границы текста, на некоторых портит шрифты.

2. Растеризация и обрезка страниц в ImageMagick



Пару раз мне пришлось прибегнуть к написанию самодельного скрипта, заточенного под определённый исходный PDF. Общая схема такая:

Исходный PDF → растеризованные изображения страниц (использую pdftoppm) → разрезание страниц на части и обрезка полей (использую convert из ImageMagick) → сборка нового PDF или DjVu из обрезанных страниц.

Вот пример такого скрипта, которым пользуюсь (он не только позволяет разрезать страницы на несколько колонок, но также отрезать поля и пережать, отбросив пустые страницы) — pdf-trim-to-djvu:

pdf-trim-to-djvu

Как пользоваться — должно быть ясно из его справки:

Usage: pdf-trim-to-djvu [options] document.pdf

Options:
-f the first page to process [default: 1]
-t the last page to process
-d resolution in DPI [default: 150]
-c|--columns multi-column mode [default: 1]
--mono bitonal compression (black and white only)
--gray DjVuPhoto compression (shades of gray images) [default]
--color DjVuPhoto compression (color images)
-h|--help print this message


Автоматическая обрезка полей довольно хорошо реализована в команде -trim ImageMagick, но можно задать параметры обрезки и вручную (приходилось). Например, чтобы принудительно обрезать по 3% с каждой стороны, в опции convert можно вставить -shave 3%x3% +repage.

Если хочется не DjVu, а именно PDF, то собрать из изображений PDF можно так (о создании PDF с помощью IM см. здесь):
convert -define pdf:use-trimbox=true `ls -v *.ppm` -density разрешение_в_dpi книжка.pdf
Если страниц много, такой способ будет очень медленным (и прожорливым), лучше конвертировать каждую отдельно (можно тем же convert, если качество устраивает, можно специально для этих целей предназначенным sam2p), а потом объединять страницы вместе. Для объединения PDF-страниц в PDF-документ я использую pdftk:
$ pdftk *.pdf cat output книжка.pdf


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

3. Изменение границ страницы в PDFedit



Наконец, есть ещё способ. Совмещающий и возможность указать вручную что именно следует отрезать, и сохраняющий PDF в почти исходном виде. Есть редактор для PDF-файлов — PDFedit. Однако хотя эта программа и с графическим интерфейсом, методы всё те же.

как обрезать поля страницы в pdfedit

Порядок действий:
  1. открываем копию PDF-файла в PDFedit и выбираем страницу, целиком заполненную текстом, чтобы было видно его границы;
  2. засекаем примерные численные координаты углов прямоугольника обрезки;
  3. в меню «Страница» выбираем «Изменить метрики страницы»; далее вводим новые параметры страницы цифрами, жмём «Изменить», чтобы проверить результат (такой вот GUI; что от чего отмеряется придётся познать на опыте), подобрав параметры страницы применяем обрезку ко всем с 1 по последнюю;
  4. сохраняем результат.

Преимущества: способ быстрый (даже если в документе несколько сотен страниц), возможность поиска по тексту сохраняется (да и вообще всё сохраняется), можно как угодно отрезать заметки на полях, номера страниц и колонтитулы. Недостатки: способ требует ручного подбора параметров, нельзя вырезать две страницы из одной (может можно, если дублировать страницы?), сам редактор PDFedit далеко не прост и полон сюрпризов.


Flattr this


* Вот, кстати, не пойму. Современные научные статьи распространяются почти исключительно в электронном виде (бумажные отпечатки — сувениры для авторов). За каждую операцию копирования файла издатели стараются взымать по 30 долларов (думаю, не бедствуют), а вот набирают статьи зачастую таким скупым кеглем, словно свою бумагу жалко. Сравните публикации XIX века и нынешнего. Отчего?


Кнопки твиттера, жуйки, я.ру и ЖЖ для Blogger

Приятно, когда на тебя ссылаются, поэтому любой заядлый блоггер любит всякие кнопки вроде «retweet» и «в делишез». «Retweet» особенно: в микроблогах ссылаются охотнее. Однако кроме твиттера, у нас есть ещё и ЖЖ, и Я.ру, и juick. А вот каким-нибудь stumbleupon никто не пользуется.

Никакие готовые кнопки мне не понравились: 1) большинство из них неправильно работает, если заметка находится на главной странице, 2) они почти все требуют установки чужих скриптов, часто тормозных 3) многие кнопки игнорируют популярные в России (и среди моих читателей) ЖЖ, Я.ру и juick. Вот и пришлось сделать самому.

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

Что получилось

Первая версия выглядит примерно так (это просто картинка, не кликать! — настоящие кнопки внизу заметки):

Кнопки подписал по-русски. Для надёжности расшифровал. Проверил, точно работают «пожужжать |жж», «двумЯ.РУками |я.ру» и «расчирикать |twi». Кнопка для жуйки по идее должна работать, только я её проверить не смог, потому что у меня обработчик XMPP URI не настроен. Пожалуйста, проверьте, кто пользуется, отпишитесь и скажите, как надо поправить, если что.

Как установить себе

В шаблоне блога «раскрыть виджеты», найти подходящее место (например, я выбрал post-footer-line-2) и добавить туда такой код:

<div class="sharemebuttons">
<a class="shareme" expr:rel="nofollow" onclick="return opnfrm(this)" href='"http://www.livejournal.com/update.bml?subject=Ссылка: "
+ data:post.title + "&amp;event=" + data:post.title + ": "
+ data:post.url'>пожужжать |жж</a>

<a class="shareme" expr:rel="nofollow" onclick="return opnfrm(this)" href='"http://my.ya.ru/posts_add_link.xml?title="
+ data:post.title + "&amp;URL=" + data:post.url'>двумЯ.РУками |я.ру</a>

<a class="shareme" expr:rel="nofollow" onclick="return opnfrm(this)" href='"xmpp:juick@juick.com?message;body="
+ data:post.title + " " + data:post.url'>перетереть |juick</a>

<a class="shareme" expr:rel="nofollow" onclick="return opnfrm(this)" href='"http://www.google.com/reader/link?url="
+ data:post.url + "&amp;title=" + data:post.title + "&amp;srcURL="
+ data:blog.homePageUrl + "&amp;srcTitle=" + data:title'>побузить |buzz</a>

<a class="shareme" expr:rel="nofollow" onclick="return opnfrm(this)" href='"http://twitter.com/home?status="
+ data:post.url + " " + data:post.title'>расчирикать |twi</a>
</div>

Для ЖЖ бы хорошо в event помещать уже HTML-код, но у меня пока не получилось.

Надо, наверное, добавить красивые графические иконки (не знаю, дойдут ли руки, но собственноручно нарисованный чижик для твиттера у меня уже есть; нет ничего для жуйки и жж). Пока довольствуюсь вот таким фрагментом CSS (тоже черновой вариант, вставлять в таблицу стилей в верху шаблона):
div.sharemebuttons {
margin-top: 1em;
margin-bottom: 1em;
}
a.shareme, a.shareme:visited {
text-decoration: none;
padding: 3px 8px;
margin: 0em 8px 0em 0px;
background-color: #94cc32;
color: white;
}
a.shareme:hover {
background-color: #aced3a;
color: white;
}

Всякие улучшения приветствуются. Ну и ссылки, конечно :-)

Доп. 1: добавил кнопку для «Я.ру».

Доп. 2: исправление для кнопки «Я.ру» — чтобы при нажатии на кнопку незалогинненному пользователю вначале предлагалось войти в «Я.ру», добавить в шаблон блога такой Javascript-код (например, перед </head>):
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("a.shareme[href^='http://my.ya.ru']").attr("href", function() {
return "http://passport.yandex.ru/passport?mode=auth&amp;retpath=" + escape(this.href);
});
});
</script>
Без этого скрипта кнопка тоже работает, но только для залогинненых пользователей «Я.ру».

Доп. 3: добавил кнопку для Google Buzz (через Reader).


Скрипт для создания статического значка Flickr

Делюсь маленьким и несовершенным скриптом, который, надеюсь, кому-то всё же окажется полезен. Скрипт генерирует картинку-бейджик с последними фотками на Flickr. Зачем он нужен? Официальные виджеты Flickr основаны на Javascript и на флэше, их не везде можно вставлять. А статическую картинку можно куда угодно вставлять, хоть в ЖЖ, хоть на форумах...

В общем, скрипт состоит, на самом деле, из двух. Первый, вспомогательный, на питоне, flickrlatest.py находит и печатает ссылки на миниатюры N последних фоток заданного пользователя. Для работы нужно иметь ключ API, который скрипт считывает из файла ~/.flickr.apikey.

Второй скрипт, основной, обычный скрипт на bash, скачивает нужные картинки и объединяет их Graphics Magick-ом (при желании легко заменить на Image Magick, но, как я недавно узнал, GM быстрее, используется и на самом фликере, так что решил переходить потихоньку на GM).

Использование второго скрипта:

mkflickrbadge.sh пользователь геометрия файл-куда-сохранить.png


Несколько примеров.

$ mkflickrbadge.sh arboreus 3x2 arboreus-3x2.png


Flickr badge 3×2


$ mkflickrbadge.sh dobrych 5x1 dobrych-5x1.png


Flickr badge 5×1


Можно использовать NSID вместо имени пользователя:

$ mkflickrbadge.sh 7333287@N07 2x2 marjina-2x2.png


Flickr badge 2×2


Надеюсь, идея понятна. Скачать скрипты можно c битбакета (архив.zip). Распаковать, сделать скрипты исполняемыми, положить где-нибудь в PATH. Как добавить в crontab — умолчу.

P.S. Отмечу в качестве альтернативы скрипту — вебсервис http://www.flickriver.com/badge/create.


14:23 28.10.09

Переименование переменных и слияние изменений в Darcs

Ныне к традиционным холиварам, вроде vi против emacs, прибавился ещё hg (Mercurial) против git. И то, и другое — распределённые системы управления версиями (DVCS). В чём их преимущество перед старыми централизованными системами и как пользоваться новыми давно уже написано. Впрочем, выбор этими двумя системами не ограничивается, отдельные маньяки успешно пользуются и другими системами. А среди альтернативных систем совершенно особняком стоит darcs.

Почитал я тут руководство по darcs, и обнаружил что есть у него одна удивительная возможность, которой, насколько мне известно, у его более популярных собратьев нет. А именно, поддержка замен в управляемых файлах. Например, можно переименовать в одной ветке функцию или переменную, в другой ветке делать другие изменения, затрагивающие эти же строки, а потом совершенно волшебным способом автоматически объединить изменения обеих веток. И ручное слияние изменений не потребуется. Возможность настолько необычная, что захотелось поделиться.

Основное отличие darcs от собратьев: он отслеживает не состояние каталога с файлами (и историю его изменений), а хранит сами изменения — патчи. А уж состояние рабочего каталога определяется просто как результат применения всех накопленных изменений-патчей. Всякое такое изменение обратимо, а некоторые можно безболезненно переставлять местами (и это очень облегчает слияния).

В случае обычных DVCS, каждое изменение определяется разницей двух состояний каталога. Чтобы объединить такие изменения, нужен их общий «предок», к которому изменения можно применить. В darcs изменение не обязательно должно определяться разницей между двумя состояниями каталога. Это позвляет создавать разные типы изменений, и автор может определить что именно изменение делает (семантически). В том числе есть и такой вид изменений: замена слов в файле (token replace patch).

Покажу, как это работает, а вы уж сами судите, насколько это круто :-)

Завязка



Итак, создадим вначале исходный репозиторий и поместим в него простую программку. Тут отличия между darcs и hg или git минимальны:
$ mkdir repo-0
$ cd repo-0
repo-0$ darcs init
repo-0$ cat > hello.py
def hello(what):
print "Hello %s" % what

hello("World")
^D

repo-0$ darcs add hello.py
repo-0$ darcs record -m 'initial commit' hello.py
Recording changes in "hello.py":

addfile ./hello.py
Shall I record this change? (1/2) [ynWsfvplxdaqjk], or ? for help: y
hunk ./hello.py 1
+def hello(what):
+ print "Hello %s" % what
+
+hello("World")
Shall I record this change? (2/2) [ynWsfvplxdaqjk], or ? for help: y
Finished recording patch 'initial commit'


А теперь создадим две ветки. В каждой ветке сделаем свои изменения. В одной (A) изменим название переменной what на name, а в другой (B) переименуем и перепишем функцию hello().

Внезапно!



Клонируем исходный репозиторий:
repo-0$ cd ..
$ darcs get repo-0 repo-A
Copying patches, to get lazy repository hit ctrl-C...
Finished getting.

И переименовываем в этой ветке переменную. Только хитрость, мы хотим не просто сделать замену слов в файле, а мы хотим явно указать darcs-у, что это именно замена слов. Поэтому вместо текстового редактора выполняем такую вот команду:
$ cd repo-A
repo-A$ darcs replace what name hello.py

Убеждаемся, что программка изменилась:
repo-A$ cat hello.py
def hello(name):
print "Hello %s" % name

hello("World")

И записываем изменения в репозиторий:
repo-A$ darcs record -m 'renamed: what to name' hello.py
Recording changes in "hello.py":

replace ./hello.py [A-Za-z_0-9] what name
Shall I record this change? (1/1) [ynWsfvplxdaqjk], or ? for help: y
Finished recording patch 'renamed: what to name'


Тем временем...



Параллельно создаём другую ветку и как-нибудь меняем функцию hello:
repo-A$ cd ..
$ darcs get repo-0 repo-B
Copying patches, to get lazy repository hit ctrl-C...
Finished getting.
$ cd repo-B
repo-B$ cat > hello.py
def hello(what):
if len(what) > 6:
print "Hello %s" % what
else:
print "Hi %s" % what

hello("World")
^D

Изменения настолько серьёзны, что старое имя функции уже не подходит. Переименовываем её с помощью darcs replace:
repo-B$ darcs replace hello greet hello.py

И записываем изменения:
repo-B$ darcs record -m 'changed hello and renamed to greet' hello.py
Recording changes in "hello.py":

hunk ./hello.py 2
- print "Hello %s" % what
+ if len(what) > 6:
+ print "Hello %s" % what
+ else:
+ print "Hi %s" % what
Shall I record this change? (1/2) [ynWsfvplxdaqjk], or ? for help: y
replace ./hello.py [A-Za-z_0-9] hello greet
Shall I record this change? (2/2) [ynWsfvplxdaqjk], or ? for help: y
Finished recording patch 'changed hello and renamed to greet'


Кровавый финал



А теперь возвращаемся в исходный репозиторий и объединяем изменения:
repo-B$ cd ../repo-0
repo-0$ darcs pull ../repo-A ../repo-B
Wed Oct 28 17:21:41 CET 2009 me@example.com
* changed hello and renamed to greet
Shall I pull this patch? (1/2) [ynWsfvplxdaqjk], or ? for help: y
Wed Oct 28 17:12:12 CET 2009 me@example.com
* renamed: what to name
Shall I pull this patch? (2/2) [ynWsfvplxdaqjk], or ? for help: y
Finished pulling and applying.

И что же мы видим?
repo-0$ cat hello.py
def greet(name):
if len(name) > 6:
print "Hello %s" % name
else:
print "Hi %s" % name

greet("World")

Изменения объединились правильно. Система управления версиями оказалась достаточно умной, чтобы применить изменения в нужном порядке (вначале переписать функцию, а уж потом переименовать все случаи использования переменной).

Я впечатлён.


Микросоветы

Всё чаще в твиттер
одной строкой пост целый
пишу на память.

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

Приёмы работы
LaTeX и вёрстка
Программирование
Находки (всякие программки)

1. Приёмы работы:



2. LaTeX и вёрстка:



3. Программирование:



4. Находки (всякие программки):



Ух-ты, а немало получилось.


Как нарисовать стрелку в Inkscape

Очень люблю Inkscape, и часто в нём рисую разные схемы. А для того, чтобы рисовать схемы, нужны стрелки. Готового инструмента «стрелка» в Инкскейпе нет*, поэтому творю из подручных материалов сообразно вкусу и потребностям. В общем-то, минутное дело, умеючи...

Я подумал, что кому-то, возможно, будет интересно увидеть, как можно самому нарисовать практически любую стрелку. Предлагаю небольшой видеоурок, где показываю как нарисовать стрелку попроще и стрелку позатейливей:

Ой! Ваш браузер не может показать это видео (не поддерживает тэг <video>). Установите Firefox 3.5 или Google Chrome 3. Щёлкните по картинке, чтобы скачать видеофайл.

Скачать видеофайл
Скачать видеофайл


Это мой первый скринкаст. Прошу строго не судить. Кино немое, просто с титрами.

* В инкскейпе есть маркеры конца и начала линии. Красить их нельзя, они всегда чёрные. Впрочем, подсказывают из зала, можно — создав стрелку, надо «оконтурить обводку», получить два контура (древко и острие), вручную их подправить-подравнять, и получится полноценная контурная стрелка. Я пока остаюсь приверженцем своего способа создания контурных стрелок. Впрочем, смотрите на другой способ сами (подал идею и сделал видео freedomfidaj).


(Новичковые) ужасы Хаскеля

Я — начинающий программист на Хаскеле, и пока я ещё помню всё, чем он страшен. И это хочу записать. Сразу скажу, когда я приступал к Хаскелю, я ещё не знал практически ничего о функциональном программировании, поэтому одновременно с языком, нужно было осваивать новые идеи и образ мысли. И вообще-то это было здорово. А у страха, как известно, глаза велики. В общем, я думаю, эта заметка может быть полезна и другим начинающим. В ней я укажу на пять непонятных мне вначале, но относительно несложных вещей, поняв которые хотя бы приблизительно, освоить язык мне было гораздо легче.

1. Ламбда-функции


— Но мы называем его лембас или путевой хлеб, он подкрепляет лучше, чем любая пища людей, и он гораздо вкуснее.


Вообще-то, именно поэтому я и выучил Хаскель. Мне было просто любопытно, что означают все эти лямбды. Очень помогла в самом начале статья Пола Худака Conception, evolution, and application of functional programming languages (PDF также здесь). Возможно, есть введения и получше, но я начинал с него.

Ламбда-выражение — самая суть «функций» — это выражение вида
\lambda x \;.\; \text{expression with $x$}
Значением этого выражения является пока ещё безымянная функция одного аргумента (x), что-то с ним вычисляющая (а именно, выражение справа от точки). С лямбда-функциями связана серьёзная математическая теория, но с точки зрения программирования можно считать \lambda ключевым словом для определения функций. Действительно, когда я и до этого уже пользовался лямбдами в Питоне (и почти во всех других современных языках они тоже есть). В Питоне они выглядят вот так:
lambda x: expression with x
Ими было очень удобно пользоваться в filter() и reduce(). И вообще, почти везде, где в качестве аргумента требуется имя функции. Однако у лямбда-функций нет имён, и именно поэтому их ещё называют анонимными (безымянными) функциями. В пайтоне иногда я давал им имена прямо на лету:
add_42 = lambda x: x + 42
Теперь имя add_42 указывает на функцию. Точно такой же результат можно было получить, записав определение функции как обычно:
def add_42(x):
return x+42
А что же насчёт Хаскеля? Да почти то же самое. Символ \ заменяет \lambda, -> служит вместо точки. Всё вместе записывается так:
\x -> выражение с x
И мы даже можем давать имена таким безымянным функциям, так же как и в Питоне:
add_42 = \x -> x + 42
Согласитесь, очень похоже.

Однако тут есть одна тонкость. Как только я начал читать о Хаскеле, я увидел лямбда-выражения, которые поначалу казались немного странными:
\x -> \y -> выражение с x и y
Что означают все эти «стрелочки»? Ответ оказался очень прост и очень полезен в дальнейшем освоении языка.

В Хаскеле все функции являются функциями одного аргумента. Поначалу это может показаться ограничением, но на деле это очень удобная и практичная идея. Любую функцию n аргументов можно представить как функцию одного аргумента, возвращающую другую функцию n–1 аргементов. И по науке это азывается каррированием. Эта идея, в частности, позволяет передавать функции только часть аргументов.

Узнав об этом, мы теперь можем читать любые выражение с множеством «стрелок»:
\x -> (\y -> выражение с x и y)
Значением такого выражения будет фукнция, берущая один аргумент и производящая другую функцию, которая берёт ещё один аргумент. Такое выражение в целом ведёт себя как функция двух аргументов. Например, мы можем вычислить такую функцию двух аргументов в интерпретаторе Хаскеля ghci:
ghci> (\x -> \y -> x + y ) 3 4
7
Конечно, есть более краткий способ записи функций двух аргументов (обратите внимание, что список аргументов брать в скобки совсем не нужно):
ghci> (\x y -> x + y) 3 4
7
Однако знать, что на самом деле все функции нескольких аргументов являются функциями одного аргумента очень полезно. Например, это помогает читать описания типов функций. Например, тип функции map выглядит так:
map :: (a -> b) -> [a] -> [b]
Я обычно читаю это следующим образом: «функция map принимает два аргумента, первый — функцию преобразующую a в b, второй — список элементов типа a, а возвращает список элементов типа b». Но иногда гораздо естественнее записать тот же самый тип так:
map :: (a -> b) -> ([a] -> [b])
«Функция, которая берёт функцию, преобразующую a в b, и возвращает функцию, преобразующую список a в список b».

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

2. Знак равенства


— Ну что, если тут нет смысла, — сказал Король, — тогда у нас гора с плеч: нам незачем пытаться его найти! Сэкономим кучу работы! И все же...


По-моему, знак равенства (=) — самый важный символ в Хаскеле. Понять его важно для понимания языка. И мне кажется, смысл равенства недостаточно подчёркивается во всевозможных учебниках. Например, это единственное ключевое слово, отсутствующее в списке ключевых слов Хаскеля в его вики.

В отличии от большинства императивных языков, где = означает присваивание (то есть действие), в Хаскеле он означает, что левая часть равна правой (то есть описывает свойство).

Дополнение: в комментариях уточняют, «равно» в Хаскеле — связывание имени слева с определением справа.


«Равна» — не значит «становится». Это означает, что нечто равно чему-то ещё. Всегда. Как в математике. a = b в Хаскеле означает, что a равно b по определению, a эквивалентно b.

Таким образом, = в Хаскеле служит для записи определений. «Равно» может определять самые разные вещи, но определяет их статично. Оно не зависит от порядка выполнения операций. На него можно положиться.

Пользователям функциональных языком это покажется слишком уж очевидным, но именно в смысле знака равенства самое важное изменение для тех, кто раньше пользовался императивными языками. Теперь, кстати, мы можем давать имена нашим безымянным функциям:
add = \x -> \y -> x + y
Признаю, что читается это плохо, поэтому в большинстве случаев функции в Хаскеле определяются так:
add x y = x + y
Но и это по-прежнему определение функции add.

3. Классы типов


Significant benefits arise from sharing a common type system, a common toolset, and so forth. These technical advantages translate into important practical benefits such as enabling groups with moderately differing needs to share a language rather than having to apply a number of specialized languages. — приписывается Б. Страуструпу


Система типов в Хаскеле просто прекрасна. В ней очень легко и естественно выражаются многие идеи. И возможно, именно классы типов — это наименее чуждая концепция для тех, кто приходит в Хаскель из процедурного и объектно-ориентированного мира. Во всяком случае, мне так показалось. Однако классы типов — это совсем не то же самое, что классы в Си++ или в Джаве. Гораздо больше они похожи на абстрактные шаблоны классов в Си++, потому что классы типов

Как только мы привыкнем, что типы классов — это не классы Си++, а абстрактные интерфейсы, и экземпляры классов это не «объекты», а конкретные реализации абстрактных интерфейсов, Хаскель сразу станет привычным и уютным.

Я очень советую почитать вики-статью OOP vs type classes, которая гораздо более детально сравнивает объектно-ориентированный подход и классово-типовой.


4. Монады


И так как всякое настоящее состояние простой субстанции, естественно, есть следствие ее предыдущего состояния, то настоящее ее чревато будущим, — Лейбниц, «Монадология»


Не важно, насколько мягкое введение в Хаскель, рано или поздно его читатель упрётся лбом в крепкую стеную из монад. Да, это вам не плюшки тырить, это вам серьёзная математика. Где-то за этой стеной.

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

Поскольку тьюториалов по монадом огромное множество, я не буду их здесь повторять и ожидаю, что вы их уже прочитал парочку. Что же не так с монадами? Для человека, привыкшему к императивным языка, испорченному годами объектно-ориентированного мышления монады кажутся странными. Они выглядят как абстрактный класс-контейнер с загадочным методом >>=:
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
fail :: String -> m a
Хорошо, если return — конструктор, то почему такое чудное имя? Если это класс-контейнер, то как из него что-либо извлечь? И какой смысл применять функцию внутри контейнера (а именно это делает метод >>=, называемый также операцией связывания), если мы не можем вытащить результат из этого контейнера?

Отвечу вначале на последний вопрос. Зачем нужно связывание (>>=)? Монады являются и одновременно не являются контейнерами. Они — обёртки, упаковки для вычислений, а не для значений (return). Однако они обёртывают вычисления не для того, чтобы их было удобнее хранить в монадных коробочках, а чтобы их можно было удобнее соединять друг с другом. Вместо «коробочек» представьте обыкновенные кирпичи, которые ровно кладутся друг к другу. Это, кстати, похоже на шаблон Adapter в ОО-проектировании. Каждая монада определяет какой-то способ передавать результат от одного вычисления к другому и реализует стандартный интерфейс, чтобы этот способ использовать (>>=). И что бы ни случилось, результат всегда останется в той же монаде (даже, если произойдёт сбой, fail).

Самая простая программистская аналогия монадам, которую я придумал, это конвееры (pipes) в командной оболочке Unix. Монады обеспечивают однонаправленный конвеер для вычислений. То же самое делают и конвееры в Unix. Например:
$ seq 1 5 | awk '{print $1 " " $1*$1*$1 ;}'
1 12 83 274 645 125
seq создаёт список целых чисел. awk вычислят куб каждого из них. Что здесь замечательного? У нас есть две слабо связанные друг с другом программы, которым мы можем легко указать работать вместе. Поток текста создаваемый программой слева попадает по конвееру в программу справа, которая может читать этот поток, что-то с ним делать и создавать уже новый текстовый поток. Текстовый поток — общий формат для результата вычислений, | — операция, связывающая их воедино.

Монады очень похожи. >>= берёт внутреннее вычисление из монады слева и подставляет его в вычисление справа, которое всегда должно создавать ещё одну монаду того же типа.

Как вы уже, наверное, знаете, списки и тип Maybe в Хаскеле — монады. Например, пусть у нас есть простое вычисление, которое возвращает пару из числа и его куба обратно в монаду (return):
\x -> return (x, x^3)
тогда мы можем взять список и направить его «по конвееру» в это вычисление:
ghci> [1,2,3,4,5] >>= \x -> return (x,x^3)
[(1,1),(2,8),(3,27),(4,64),(5,125)]
Заметьте, что мы получили список пар. Это та же самая исходная монада (то есть список). Однако если мы возмьём значение Maybe и направим его в то же вычисление, на выходе у нас будет та же сама монада Maybe:
ghci> Just 5 >>= \x -> return (x,x^3)
Just (5,125)
Таким образом, мы можем создать конвеер из двух вычислений, и поведение этого конвеера зависит от контекста (т.е. от того, какая монада используется), а не от самих вычислений. В отличии от юниксовых конвееров, монады строго типизованы, и сама система типов заботится о том, чтобы выход одной монады был совместим с входом другой. И в отличии от юниксовых конвееров, мы можем задавать наши собственные правила связывания (>>=). Например, такие: «не делать более 42 вычислений подряд» или «посмотреть на входное значение, сделать то или это». Классы монад содержат в себе подобные правила, как соединять вычисления.
Дополнение: в комментариях подсказывают, что гораздо более подробно и более строго аналогия между юниксовым конвеером и монадами разобрана в статье Monadic i/o and UNIX shell programming.
Теперь, я надеюсь, вы понимаете монады не хуже меня (не обязательно полностью). Хочу обсудить несколько мнемонических правил. Почему return называется return?

В большинстве языков return возвращает результат вычисления из функции. В Хаскеле же он конструктор для монад. Это очень странно. Однако посмотрим как работает >>=: эта операция извлекает значение из монады слева, а затем связывает его с аргументом функции справа (отсюда, кстати, и другое название метода — bind). А функция справа должна вернуть значение обратно в монаду, чтобы можно было передать эстафетную палочку дальше следующей операции >>=. Это первое мнемоническое правило: return — возвращает вычисленное значения обратно в монаду.

Вторая мнемоника. Функция верхнего уровня любой программы на Хаскеле выполняется в монаде IO (тип функции mainIO ()). Эта монада позволяет выполнять ввод-вывод и вообще любые последовательные действия. Таким образом, монадный код выполняется на самом верхнем уровне программы, и именно он вызывает любой «чистый» код по мере необходимости, а не наоборот. Таким образом, любое «чистое» значение, если не отбрасывается, то рано или поздно возвращается в монаду её вызвавшую.

Надеюсь, что после этих объяснений имя return для монадного конструктора больше не кажется таким уж странным. Я, однако, не утверждаю, что мои объяснения 100% технически верны.

Следующий вопрос бывшего ОО-программиста, как вытащить значение вычисления из монады?. Начнём с того, что монады преднамеренно спроектированы именно так, что это не всегда возможно. Например, нельзя извлечь чистое значение из монады IO. Если дана такая «односторонняя» монада, то всё, что можно с ней делать — передавать внутреннее значение по монадному конвееру дальше. В Хаскеле разработан специальный синтаксис с ключевым словом do, которые делает такую многократную передачу монады по конвееру очень похожей на последовательную императивную программу. Следующие две программы делают одно и то же. Первая записана с do-нотацией:
main :: IO ()
main = do
name <- getLine
putStrLn ("Hi, " ++ name)
а вторая явно использует >>=:
main :: IO ()
main = getLine >>= \name -> putStrLn ("Hi, " ++ name)
Эквивалентная программа на Питоне:
from sys import stdin, stdout

if __name__ == "__main__":
name = stdin.readline()
stdout.write("Hi, " + name)
Однако иногда вытащить чистое значение из монадного вычисления можно. Это не предусмотрено общим монадным интерфейсом, поэтому разработчик монады должен специально предусмотреть возможность извлекать значения наружу. Например, можно извлекать значения из монады Maybe, используя функцию fromMaybe:
ghci> fromMaybe 0 $ Just 3
3
ghci> fromMaybe 0 $ Nothing
0


Заключение по монадам

Итак, связывание (>>=) позволяет объединять различные монадные вычисления вместе. Почти везде, где есть цепь вычислений, монады очень подходят. Конкретные реализации монад могут содержать разные правила комбинирования вычислений. Имя метода return сбивает с толку начинающих, это метод возвращает результа вычисления обратно в монаду, а не из монады. В общем, когда я понял эти простые идеи, это сильно помогло.

5. Страшные слова


Я знаю только то, что ничего не знаю.


Даже спустя месяцы после того, как я начал учить Хаскель, умея написать какие-то полезные программы на нём, я вижу вокруг, в мире Хаскеля, ещё много понятий, о которых не знаю ничего или имею только очень смутное представление. Я называю такие понятия «страшными словами». И я вижу, что есть люди, которые создают и используют библиотеки, воплощающие эти понятия в жизнь.

Надо признать, Хаскель остаётся испытательной площадкой для исследователей. И это одновременно и хорошо, и плохо. Это хорошо, потому что даёт чувство, что передний край науки и технологии очень близок, и можно при желании пользоваться преимуществами новых подходов. При желании. И одновременно это плохо, потому что иногда, когда хочется использовать новую изящную библиотеку, оказывается, что она активно использует незнакомые и не совсем понятные идеи, и нужно быть готовым такие идеи осваивать.

Например, есть современная XML-библиотека HXT. Она очень много использует стрелки. Стрелки — более универсальные комбинаторы вычислений, чем монады, но мне потребовалось гораздо больше, чем один день, чтобы их более-менее понять. Строго говоря, стрелки не являются частью языка, но они — понятие, которое применяют пользователи этого языка. И таких примеров немало. Хотя тем, кому стрелки осваивать не хочется, можно пользоваться более традиционной и активно поддерживаемой XML-библиотекой HaXml.

Я думаю, важно не бояться «страшных слов». К счастью, основополагающие идеи хорошо описаны. Как правило, есть статьи их очень детально объясняющие. Я сам решил осваивать такие идеи по мере необходимости. Это обещает быть и увлекательным, и одновременно посильным.

Заключение



Я перечислил пять простых идей, освоив которые, мне стало легче привыкнуть к Хаскелю. Лямбды — это просто способ записи функций, и функции нескольких аргументов можно всегда записать как функцию одного, возвращающую другую функцию. Типы классов очень похожи на абстрактные полиморфные интерфейсы в объектно-ориентированном подходе. Монады — стандартизованный способ соединять вычисления вместе. А страшные слова — просто страшные слова. Без них можно жить, но скучно.

Надеюсь, мои заметки будут полезны и ещё кому-нибудь.

Эта статья есть также по-английски. This post is also available in English.


Ещё одна библиотека комбинаторного парсинга

Не так давно я писал о библиотеке pyparsing для комбинаторного парсинга в Python. В комментариях появилась ссылка на ещё одну библиотеку, о которой я вначале не знал, а именно на funcparserlib, написанную Андреем Власовских.

В общем-то, я посмотрел на новую библитечку, и она мне тоже понравилась. Подкупает сравнительная простота самой библиотеки, ясные исходники и подробно написанные руководства — понять как работает библиотека нетрудно. Правда, при чтении документации нужно быть знакомым с нотацией типов, принятой в Haskell (ArgType -> ResultType). Общее впечатление: имеющиеся в funcparserlib комбинаторы практически полностью ортогональны друг другу, записываются кратко, места на экране занимают мало, называются понятно.

Комбинаторов в библиотеке всего-то: some, a, many, finished, maybe, skip, oneplus, forward_decl, +, | и >>. Действие большинства комбинаторов угадывается из названия. Рабочие лошадки: some(функция-предикат) берёт токен, удовлетворяющий условию, a(токен) берёт токен, указанный в аргументе, many(парсер), maybe(парсер), oneplus(парсер) — множественное, возможное или хотя бы однократное срабатывание парсера, skip(парсер) отбрасывает всё найденное парсером. Плюс (+) последовательно применяет два парсера, «или» (|) пробует альтернативные варианты, >> подставляет результат парсера в функцию и создаёт новый парсер (очень полезный комбинатор!). Ещё есть tuple для группировки.

Для пробы я решил написать пример разбора того же файлового формата, что и в заметке о pyparsing. Мне кажется, что наиболее эффективно применять funcparserlib с предварительным лексическим анализом (разбиением текста на токены-лексемы). В принципе, готовый токенизатор — часть стандартной библиотеки Python, поэтому это не проблема. Удобная обёртка приведена в руководстве к funcparserlib. Однако мне хотелось написать пример в том же стиле, что и для pyparsing, поэтому в примере ниже я разбираю текст на уровне отдельных символов.

Полный текст примера с тестами кладу в pastebin. Далее пояснения.

Половина кода пришлась на разбор чисел. Вообще, по-моему, в такого рода библиотеках было бы хорошо класть готовые парсеры для чисел, дат, e-mail и тому подобных обыденных вещей где-нибудь в разделе contrib... Впрочем, написать разбор чисел было даже занятно.

Чтение знака числа. Если ни плюса, ни минуса нет, подразумеваю плюс. Волшебный комбинатор >> позволяет обработать результат и сразу подготовить ответ, или -1, или +1:

sign = maybe(a("-")|a("+")) >> (lambda c: c == "-" and -1 or +1)

Целые числа состоят из знака и последовательности цифр. Так и запишем:
digits = many(some(lambda c: c.isdigit()))int_num = sign + (digits >> to_int) >> mk_int

Здесь я пользуюсь опять комбинатором >> и двумя вспомогательными функциями. Одна превращает последовательность символов-цифр в число (to_int), другая умножает результат на -1, если необходимо (mk_int). Эти функции вспомогательные, можно пропустить:
powers = lambda digs: zip(digs,xrange(len(digs)-1,-1,-1))add_digit = lambda acc, dp: acc+int(dp[0])*10**dp[1]to_int = lambda digs: reduce(add_digit, powers(digs), 0)mk_int = lambda (s,i): s*i

Аналогично строю и парсер для рациональных чисел, только прибавляю ещё и дробь, и, соответственно, ещё две вспомогательных функции:
to_frac = lambda digs: to_int(digs)*1.0/10**len(digs)mk_frac = lambda (s,i,f): s*(i+f)frac_num = sign + (digits >> to_int) + (skip(maybe(a("."))) + (digits >> to_frac)) >> mk_frac

Вот и всё. Использовать примерно так:
>>> frac_num.parse("-1.25")-1.25

Второй половиной кода оказалось написание парсеров для аналогов Literal и Word из pyparsing. Это необходимо, потому что без токенизации приходится распознавать цепочки токенов. Дополнительно создал парсер ws для пропуска пробелов:
pack = lambda cs: ''.join(cs)literal = lambda s: reduce(lambda a,b: a+b, map(a,s)) >> packword = lambda p: oneplus(some(p)) >> packws = skip(many(some(lambda c: c.isspace())))

После этих определений собственно парсер выбранного формата укладывается в три выражения:
varname = word(lambda c: c.isalpha())var = (ws + varname + ws + frac_num + ws)custom_format = skip(literal("Inspection")) + \ws + skip(literal("#")) + \ws + int_num + \ws + skip(literal("SHOULD")) + \ws + skip(literal("Ref. Sys")) + \ws + int_num + \many(var)

Смотрим на результат:
$ python test.py < input.txt (2, 1, [('X', 28.749300000000002), ('Y', 78.995999999999995), ('Z', -1.0014000000000001)])


В общем, впечатления хорошие. Документация у библиотеки приличная. Использовать приятно. Только одно досадное неудобство было связано с тем, что setup.py install --prefix=... из дистрибутивного тарбола не сработал как надо. Впрочем, библиотека такая маленькая, что можно положить все три её файла прямо в свой проект, без общесистемной установки.

Тонкости: нужно быть осторожным, не помещая универсально успешный парсер внутрь many, чтобы избежать вечного цикла. Короче, нельзя внутрь many помещать many, maybe и pure. Подробнее — см. FAQ.

См. также заметку про pyparsing.

Дополнение: слайды презентации funcparserlib на DevConf 2010:


Как сделать видеофайл из GIF-а и добавить поля к видео

Для того, чтобы из анимированного GIFа сделать видеофайл, я недавно использовал gifsicle (чтобы разоптимизировать GIF и разбить на кадры) и ffmpeg (чтобы сделать из кадров видео):

gifsicle -U --explode "input.gif"
for f in *.gif.* ; do mv "$f" "$f.gif" ; done
ffmpeg -r 25 -i "input.gif.%03d.gif" -sameq -s 320x240 output.flv

Если нужно добавить чёрных полей (до нужного размера), действую примерно так (в данном случае, хочу получить 320×240):
ffmpeg -i input.file -s 320x180 -padtop 30 -padbottom 30 output.file

Дополнение: с новыми версиями ffmpeg (например, 0.6.90), поля к видео добавляются с помощью видеофильтра pad:
ffmpeg -i input.file -vf "scale=320:180,pad=320:240:0:30" output.file

Я не использую для разделения на кадры ImageMagick (convert), потому что мне кажется, что gifsicle работает быстрее и требует меньше памяти.

(in English)


Как пометить пакеты в Aptitude, чтобы потом удалить

Очень полезная возможность в aptitude — пользовательские метки для выбранных пакетов.

Например, нужно поставить какой-то набор пакетов, чтобы собрать программу X из исходников, а потом нужно эти пакеты удалить. При установке помечаем выбранные пакеты какой-то своей меткой (builddeps в моём примере):

$ sudo aptitude install --add-user-tag builddeps libчто-то-dev libчто-то-ещё-dev ...

А потом, когда эти пакеты больше не требуются, их удаляем, выбрав по той же метке:
$ sudo aptitude purge '?user-tag(builddeps)'

Поисковый шаблон ?user-tag(метка) можно использовать совместно со всеми другими поисковыми шаблонами. Присваивать метки можно не только при установке (install), но и во многих других операциях.

Как видно из примера, особенно эта возможность полезна для самостоятельной сборки пакетов и программ из исходников.

P.S. Не помню, есть ли --add-user-tag в Ubuntu, но в Debian Lenny (aptitude-0.4.11) точно есть.

This post in English


00:59 28.08.09

Скорое обновление RuNIX.org

Наконец собрался и подготовил обновление планеты русских блогов о *NIX RuNIX.org. Дело в том, что скрипт Planet Planet, на котором аггрегатор работает сейчас уже давно не обновляется, а на смену ему пришёл Planet Venus. Вот на него и переходим.

Грядущие изменения, большие и маленькие:

На сайт runix.org изменения попадут после того, как GQ установит на сервере новый скрипт. Посмотреть одним глазком на новую планету уже можно здесь — для сравнения сохранил как было.

Кто не заметил ссылку — новая планет будет выглядеть так.

Приветствуются замечания по делу (если по вёрстске — лучше сразу патч к CSS, если считаете, что что-то нужно фильтровать-менять — поделюсь XSLT, обсудим). Если что-то из ряда вон и лучше ничего не трогать — кричите!

Есть некоторые задумки (не факт, что скоро сделаю):Помощь и пожелания принимаются.

P.S. Да, забыл предупредить. Допускаю, что при обновлении скрипта некоторые записи пройдут повторно. Прошу извинить за неудобство.


15:31 27.08.09

Декоративная табличка в LaTeX

В TeXblog появилась заметка, как делать декоративные таблички в LaTeX. Вот такие:

Fancy tables with LateX and Tikz

Табличка набрана как обычно, но внутри «узла» окружения tikzpicture, а фон и раскрашенные шапки — средствами Tikz на фоне. Исходник примера — в TeX blog.

Кстати, давно хочу написать про PGF/Tikz. Что-нибудь интересно?


12:53 26.08.09

Не видно букв в японском PDF?

Бывают такие PDF, родом из Японии, в которых, если попытаться открыть их в Evince или XPDF — букв вообще не видно, а в Adobe Reader-е вместо букв видны только точки. В свойствах документа список встроенных шрифтов вообще выглядит пустым. Google Docs же такие PDF открывает, что интересно. И открыв такие PDF в Google Docs, можно увидеть, что в них всё таки есть кое-что и латиницей. Только латиница эта — из японских шрифтов (квадратная и широкая).

Оказывается, для отображения этих PDF нужно поставить кое-какие дополнительные пакеты. Для Evince — нужно поставить рекомендуемый пакет poppler-data*. Для XPDF — нужно поставить пакет xpdf-japanese*. И только после этого мы действительно сможем нормально смотреть такие PDF-файлы.

* названия пакетов даны для Debian/Ubuntu.


11:37 26.08.09

Старая флэшка монтируется только для чтения?

Уже не раз столкнулся: пользуясь ГНОМом, вставляешь какую-нибудь старую флэшку или карточку памяти в кард-ридер, она вроде как обычно автоматом подключается, но права доступа какие-то чудные (скажем, только для чтения). А другие карточки и флэшки вставляешь — всё нормально.

Догадался, что дело в том, что когда-то ещё на старой машине я настраивал параметры монтирования для каждого носителя отдельно. С тех пор домашний каталог благополучно переезжал из системы в систему, и настройки ГНОМа переезжали вместе с ним. А вот новые группы и пользователи не всегда точно соответствовали тому, что было раньше.

В результате, при попытке вставить какой-нибудь старый внешний носитель, ГНОМ использует настройки рассчитанные на совсем другую машину (другие группы и другую принадлежность пользователя к ним). Естественно, такие настройки gnome-mount надо просто удалить (ну или поправить), вот только найти их в гномовских закромах не так-то просто.

Мой способ: вставляю проблемный носитель и выполняю blkid. Например,

$ blkid 
/dev/sdb1: SEC_TYPE="msdos" LABEL="PALM-CARD" UUID="15F4-492C" TYPE="vfat"

Запоминаю UUID и запускаю редактор реестра gconf-editor. Нахожу /system/storage/volumes/_org_freedesktop_Hal_devices_volume_uuid_15F4_492C и там сбрасываю установленный ключ mount_options. Отсоединяю носитель и подключаю опять. Пока что все проблемы с монтирование старых внешних носителей разрешались именно таким образом.


10:13 25.08.09

Как отслеживать изменения файлов в скриптах

Задача: отслеживать в скриптах изменения файла или файлов и выполнять какие-нибудь команды, когда файлы изменяются.

Пример: пересобирать документ LaTeX при изменении (сохранении) одного из исходных файлов.

Решение: остлеживать изменения можно с помощью утилит inotify-tools. Одна утилита, inotifywait ждёт указанных изменений и после этого завершается с тем или иным кодом возврата. Если произошло ожидаемое событие, код возврата 0 (успех). Именно inotifywait и используется в моём примере ниже. Другая утилита, inotifywatch, наблюдает за файлами и собирает информацию об изменениях, на выходе выводит табличку того, что заметила. Примеры применения этой утилиты смотрите на сайте inotify-tools, там же есть и дополнительные примеры использования inotifywait.

Пример использования: в данном случае я предполагаю, что все исходные файлы документа LaTeX лежат в текущем каталоге, а для сборки достаточно использовать pdflatex и bibtex. Вечный цикл: ждём любых изменений файлов текста или библиографии (первая команда цикла), в случае успеха (обнаруженных изменений) исполняем все нужные команды сборки документа (вторая команда цикла).

while true ; do \
inotifywait *.tex *.bib \
&& ( pdflatex -interaction=nonstopmode mypaper && \
bibtex mypaper && \
pdflatex -interaction=nonstopmode mypaper ) \
done

Естественно, применять можно для чего угодно, не только для LaTeX.

P.S. Вариант запуска LaTeX с опцией -interaction=nonstopmode позволяет с одной стороны избежать запроса интерактивного ввода в случае ошибки компиляции, а с другой стороны, позволяет эти ошибки компиляции всё же увидеть.

P.P.S. Рецепт работает только в линуксе. Для *BSD есть библиотечка pnotify и kqueue.


Автоматические отступы в XML

Для просмотра какого-нибудь XML часто нужно автоматически его отформатировать (чтобы отступы слева соответствовали вложенности элементов). Особенно это полезно, когда весь исходный XML записан в одну большую строку. Такие файлы — это нечитаемая каша, которую, однако, легко привести в порядок.

Первый способ — используем XSLT
Есть у меня файл с вот таким XSL-преобразованием:
<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml"/>
<xsl:param name="indent-increment" select="' '" />

<xsl:template match="*">
<xsl:param name="indent" select="'&#xA;'"/>

<xsl:value-of select="$indent"/>
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:apply-templates>
<xsl:with-param name="indent"
select="concat($indent, $indent-increment)"/>
</xsl:apply-templates>
<xsl:value-of select="$indent"/>
</xsl:copy>
</xsl:template>

<xsl:template match="comment()|processing-instruction()">
<xsl:copy />
</xsl:template>

<!-- WARNING: this is dangerous. Handle with care -->
<xsl:template match="text()[normalize-space(.)='']"/>

</xsl:stylesheet>

Код взял здесь (предложил Николай Григорьев). Там ещё несколько вариантов есть.

В дополнение к XSL-файлу есть у меня ещё и скрипт-однострочник, который это преобразование применяет. Я выполняю XSL с помощью любимого мной xmlstarlet. Это программка с интерфейсом командной строки для работы с XML.
#!/bin/sh
# указать правильный путь к файлу с преобразованием!
xmlstarlet tr ~/bin/indent-xml.xsl

Пользуюсь этим скриптом так:
$ xmlindent < исходный.xml | view -

И всё, можно читать любой XML с правильными отступами. И подсветкой синтаксиса (view — это Vim!). Кроме xmlstarlet есть и другие XSLT-процессоры. На память приходит xsltproc и библиотечки для разных языков программирования. Вот, например, однострочник на Python.

Второй способ — используем xmllint
В пакете libxml2-utils есть программка для проверки и форматирования XML — xmllint. Для форматирования использовать так:
$ xmllint --format исходный.xml

Так даже проще.

Третий способ — xmlindent
xmlindent — отдельная утилита, написанная на чистом Си. Говорят, работает и с задачей справляется.

По теме:
Редактирование HTML и XML в Vim (добавил про HTML Entities)
Выделение HTML-тегов, строк и блоков кода в Vim
Vim в терминале: сохранение отступов вставленного текста


Как ускорить или замедлить видеоролик

Иногда нужно замедлить (растянуть) видеоролик, чтобы он игрался как будто в режиме замедленного воспроизведения, а иногда нужно наоборот, показать «на перемотке» слишком длинный ролик, выбросить часть кадров и ускорить воспроизведение. О том как это сделать — сия заметка.

Изменить частоту кадров в видеопотоке позволяет программа yuvfps из пакета mjpegtools. Как и большинство утилит пакета она принимает и отдаёт видеопоток в формате YUV4MPEG. И ffmpeg, и mencoder тоже умеют работать с YUV4MPEG (и умеют читать и писать всякие другие форматы). Я приведу пример использования ffmpeg.

Итак, чтобы ускорить видео, нужно взять исходный файл и посмотреть, какая в нём частота кадров

$ ffmpeg -i normal.ogg
FFmpeg version SVN-r13582, Copyright (c) 2000-2008 Fabrice Bellard, et al.
...
Input #0, ogg, from 'normal.ogg':
Duration: 00:00:10.49, start: 0.000000, bitrate: 150 kb/s
Stream #0.0: Video: theora, yuv420p, 320x240 [PAR 1:1 DAR 4:3], 30.00 tb(r)
Must supply at least one output file

В данном случае исходный файл — 30 кадров в секунду. Затем нужно решить, во сколько раз уменьшить число кадров (исходя из желаемой длительности ролика). Потом берём исходный файл (normal.ogg в примере) и преобразовываем его в YUV4MPEG-поток (первый вызов ffmpeg), после нужно дважды вызвать yuvfps, первый раз, чтобы изменить число кадров в потоке (yuvfps -s 5:2 -r 1:1 сокращает исходные 2.5 кадра до одного), второй раз, чтобы перезаписать заголовок потока, указав скорость воспроизведения (yuvfps -r 30:1 -c устанавливает скорость 30 кадров в секунду). В конце опять вызываем ffmpeg и кодируем в нужный формат (я сохранил в формате Ogg/Theora, чтобы можно было вставлять в веб-странички тэгом <video>). Всё вместе:
$ ffmpeg -i normal.ogg -sameq -f yuv4mpegpipe - | \
yuvfps -s 5:2 -r 1:1 | yuvfps -r 30:1 -c | \
ffmpeg -f yuv4mpegpipe -i - -sameq -y fast.ogg


Аналогично можно увеличить число кадров. Дополнительные кадры интерполируются:
$ ffmpeg -i normal.ogg -sameq -f yuv4mpegpipe - | \
yuvfps -s 1:3 -r 1:1 | yuvfps -r 30:1 -c | \
ffmpeg -f yuv4mpegpipe -i - -sameq -y slow.ogg

В этот раз видео замедляется в 3 раза: на каждую «треть» исходного кадра (-s 1:3) создаётся целый кадр (-r 1:1). Вообще, как легко заметить, в качестве частоты кадров для yuvfps можно указывать любые дроби в виде числитель:знаменатель.

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

Теперь можно сравнить все три видео. Для просмотра нужен современный браузер с поддержкой тэга <video/>.

Исходное видео:



Ускоренное в 2.5 раза:



И замедленное в 3 раза:



P.S. В качестве иллюстрации использовал ролик Breitenlee-VESTAS-V-52.


Вы не поверите — Ubuntu Cola!

Эта заметка не про линукс. Эта заметка про газированный напиток Ubuntu Cola. Вот он:



Купил вчера в автомате в итальянском университете за 1,80 €. На этикетке спереди значок Fairtrade. Сзади надпись:

Ubuntu. “Я есть, потому что есть мы”. Благодаря программе Fairtrade производители тростникового сахара в Малави и Замбии могут заключать более выгодные контракты и вкладывать средства в социальные, экономические и природоохранные проекты. К тому же, мы возвращаем 15% нашей прибыли в эти страны через программу Ubuntu Africa. Посетите: www.ubuntu-trading.com
Вот такая кола. Вот такая убунту. Кстати, на вкус оказалась довольно хороша.