tag:lonelyelk.ru,2008:/postsLonelyElk::Blog2016-09-07T15:12:47+00:00tag:lonelyelk.ru,2008:Post/572016-09-07T15:12:47+00:002016-09-07T15:12:47+00:00Телеграм-бот для Яндекс.ПДД<h2>Введение</h2><p>Увлечение чат-ботами докатилась и до меня. Как это может случиться наилучшим образом, — по необходимости. А необходимость возникла в совместном использовании Яндекс почты для домена. Оказалось, что веб-интерфейс для этого совершенно не приспособлен, но есть <a href="https://tech.yandex.ru/pdd/doc/about-docpage/">API</a>. Но писать целый сайт для этого кажется накладным, а чат-бот — в самый раз. И вообще, мне кажется, это один из самых продуктивных способов использования технологии: интерфейс к API.</p><p>Ссылку на полный текст бота я приложу в конце. Сам бот не содержит в себе полного функционала всего API, а имеет лишь необходимую на данный момент часть. Сейчас хотел бы поделиться парой находок, которые пригодятся всем, кто захочет писать чат-ботов для Телеграм с помощью node.js.</p><p><img alt="Chat bot" height="337" src="http://lonelyelk.ru/images/uploads/0000/0059/chatbot.jpg" width="600" /></p><h2>Хуки в продакшне</h2><p>У ботов Телеграм есть два способа работы: когда бот сам обращается за обновлениями по определённому адресу (polling) и с помощью веб-хуков, когда сервера сами дёргают заданный хук для передачи данных боту. В продакшне, конечно, удобнее работать с хуками, а при разработке — нет, поскольку сервер запускается на локальной машине. Кроме этого я рекомендую завести другого бота для разработки, чтобы те, кто пользуются вашим ботом в продакшне, не замечали, как вы разрабатываете. Возможность сделать это я нашёл пока только в одной библиотеке: <a href="https://github.com/yagop/node-telegram-bot-api">node-telegram-bot-api</a> с помощью недокументированной функции <i>processUpdate</i>. Делается это довольно просто. При инициализации бота в файле <b>lib/bot.js</b>:</p><pre><code class='javascript'>if (process.env.NODE_ENV === 'production') {
bot = new TelegramBot(config.botToken, {polling: false});
bot.setWebHook(config.host + config.url);
} else {
bot = new TelegramBot(config.devBotToken, {polling: true});
}</code></pre><p>А затем уже в серверной части, которая, хоть и запускается всегда, имеет значение только для продакшна, в файле <b>lib/web.js</b>:</p><pre><code class='javascript'>app.post(config.url, function (req, res) {
options.bot.processUpdate(req.body);
res.status(200).send({}).end();
});</code></pre><p>Весь остальной код для бота работает в обоих случаях одинаково и в изменениях не нуждается, что совершенно прекрасно!</p><h2>Оповещение об остановке</h2><p>Второе, что нужно делать, как мне кажется, это оповещать хоть кого-нибудь о том, что сервер остановлен или запущен. Также это нужно, если при перезапуске бота, например, меняется кастомизированная клавиатура.</p><p>Если вы запускаете приложения с помощью <a href="http://pm2.keymetrics.io/">pm2</a>, то этот менеджер использует для остановки процесса тот же сигнал <i>SIGINT</i>, что мы используем, когда останавливаем сервер в разработке с помощью <i>Ctrl-C</i>. Очень удобно! В файле <b>index.js</b></p><pre><code class='javascript'>process.on('SIGINT', function () {
Promise.all(config.permitUsers.map(function (userId) {
return bot.sendMessage(userId, 'Бот временно выключается. Только спокойствие!', {
reply_markup: {
hide_keyboard: true
}
});
})).then(gracefulClose).catch(function (err) {
console.log(err);
gracefulClose();
});
});</code></pre><p>Таким образом, останавливая наш сервер через <i>Ctrl-C</i> мы видим то же, что увидит пользователь, когда перезапускается приложение на сервере.</p><h2>Материалы для самостоятельного изучения</h2><ol><li>Полный на текущий момент <a href="https://github.com/lonelyelk/yandex-pdd-telegram-bot">код Телеграм-бота для Яндекс.ПДД</a>;</li><li><a href="https://tech.yandex.ru/pdd/doc/about-docpage/">API Яндекс.ПДД</a>;</li><li><a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-16-04">Как установить приложение node.js на ubuntu 16.04</a>.</li></ol><p><br /></p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/562016-06-23T23:16:13+00:002016-06-23T23:16:13+00:00Тестирование сервера Node.js<h2>Вводная</h2><p>Уже не раз в этом блоге я ратовал за тестирование собственного кода. Поскольку писать мне интересно на разных языках, то и тестировать приходится по-разному. Каждый язык имеет свои паттерны, используя которые получается наиболее удобный код. Каждая библиотека или среда имеет в каком-то виде реализованные подходы к тестированию.</p><p>Для тестирования кода на ноде я использую <a href="http://jasmine.github.io/">жасмин</a>. Для запуска задач — <a href="http://gulpjs.com/">галп</a>.</p><p>Сегодня хочу рассказать о двух простых приёмах для тестирования кода сервера, которые позволяют выделять и тестировать только то, что нужно.</p><p><img alt="Staged reality" height="337" src="http://lonelyelk.ru/images/uploads/0000/0057/stagedreality.jpg" width="600" /></p><h2>Кто запускает сервер</h2><p>Первое, что хотелось бы сделать, это запускать сервер отдельно для каждого теста. Желательно на другом порту. Может быть, даже параллельно с работающим сервером, на котором мы что-то пробуем руками. Для этого в ноде есть возможность определить, находимся ли мы в основном файле, или его загружает какой-то другой файл:</p><pre><code class='javascript'>if (require.main === module) {
app.use('/', require(path.join(__dirname, 'routes', 'main'))());
var server = app.listen(5000, function () {
...
});
} else {
exports = module.exports = function (options) {
app.use('/', require(path.join(__dirname, 'routes', 'main'))(options));
return app;
};
}</code></pre><p>Таким образом, уже в тестовом фреймворке мы будем запускать:</p><pre><code class='javascript'>app = require('index.js');
server = app(...).listen(6000, function (err) {...});</code></pre><p>перед каждым тестом, а после каждого теста:</p><pre><code class='javascript'>server.close(function (err) {...});</code></pre><p>Очень удобно!</p><h2>Имитация библиотек</h2><p>Второе, что бы хотелось сделать при тестировании сервера, — имитировать используемые им библиотеки, чтобы тестировать только код сервера, а не библиотеки. Связки тоже важны, но только тогда, когда мы этого сами хотим. И тут появляется интересный момент. Если мы хотим имитировать библиотеки при тестировании, нам нужно использовать определённый паттерн при программировании. Паттерн фабрик. То есть мы не просто тестируем свой код, но устраиваем архитектуру и стиль так, чтобы его удобнее было тестировать.</p><p>В случае с паттерном фабрики мы возвращаем в качестве модуля не объект, а функцию, которая создаёт объект в соответствии с нашими параметрами:</p><pre><code class='javascript'>exports = module.exports = function (options) {
options = options || {};
var customLib = options.customLib || require(...);
/* GET main page. */
router.get('/', function (req, res, next) {
res.end('Real server text. CustomLib: ' + customLib.name);
});
return router;
};</code></pre><p>Как видно в вызовах выше, мы передаём сквозным образом наши опции при тестировании, а при нормальном запуске, не передаём ничего.</p><p>Таким образом, получается, что при разработке через тестирование, или даже просто при использовании тестов важно не только писать тесты, но и писать код, который, во-первых, можно, а во-вторых, удобно тестировать.</p><h2>Для самостоятельного изучения</h2><p><a href="https://github.com/lonelyelk/lonelyelk_code/tree/master/20160623_node_testing">Полный код примера в сборе</a></p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/552016-06-09T22:18:44+00:002016-06-09T22:18:44+00:00Куда уходят программисты<p>Сегодня хочу предаться размышлениям и выпустить статью без единой строчки кода. Программирование как общедоступное знание уже существует настолько давно, что уже есть люди, которые родились в этот период и начали программировать. То есть для них программирование, как понятная сфера деятельности, существовало всегда. Также это означает, что есть люди, которые были в этой сфере, а потом ушли, оставив после себя следы: разные и по разным причинам.</p><p><img alt="Missing people" height="337" src="http://lonelyelk.ru/images/uploads/0000/0055/missingpeople.jpg" width="600" /></p><h2>Без следа</h2><p>Недавно я проникся новым молодым языком программирования <a href="http://www.elm-tutorial.org/en/">elm</a>. Читал статьи, потом решил потрогать руками. Установил подсветку синтаксиса для текстового редактора, поставил библиотеки, и вдруг — беда! Поскольку язык молодой (максимум 4 года ему), то синтаксис немного поменялся в последнем релизе. И оказалось, что подсветка не работает с новым синтаксисом.</p><p>Я полез разбираться, начал строчить отчёты об ошибках и обнаружил такую историю. Человек, который написал и поддерживал подсветку синтаксиса для элма, пообещал добавить необходимые изменения сразу после выхода новой версии синтаксиса, и исчез бесследно. Имя его <i>deadfoxygandpa</i>. Совпадение?</p><p>Понятно, что проект этот приютят, и <a href="https://github.com/wbond/package_control_channel/pull/5551">усыновят</a>, но что будет с человеком? Комьюнити этого языка очень маленькое, и, не имея знакомых с ним лично людей, так никогда и не узнает, что случилось.</p><h2>Уйти красиво</h2><p>До этого случая, произошла история, про которую даже писали в новостях. Один программист обиделся на центральный репозиторий модулей для ноды, и удалил оттуда все свои проекты. Можно почитать <a href="http://blog.npmjs.org/post/141577284765/kik-left-pad-and-npm">разбор полётов</a>. Товарищ оказался очень плодовит, и написал кучу библиотек, одна из которых, использовалась в нескольких довольно больших и популярных проектах. Кстати сказать, <a href="https://github.com/stevemao/left-pad">сама эта библиотека</a> сделана для того, чтобы добавлять пробелы в левую часть строки, и весь её код занимает <b>11 строчек</b>.</p><p>Вообще, это открывает большую тему программисто-срачей и того, как в условиях непредсказуемых конфликтных мнений можно что-то делать полезное. Но я об этом не буду.</p><h2>Почему</h2><p>Первой же историей, которая обратила мой внимание на то, что люди отделены от своего виртуального персонажа, была история <a href="https://en.wikipedia.org/wiki/Why_the_lucky_stiff"><i>Why the Lucky Stiff</i></a>. По его <a href="http://poignant.guide/">учебнику</a> я изучал руби. Но в какой-то момент он решил уйти из интернета. Перед тем, как уйти в оффлайн, он написал: «Программирование весьма неблагодарно. Через год ты видишь, как твою работу замещает лучше сделанная, а через несколько лет её уже и не запустить». Это он сказал в 2009-м.</p><p>Сейчас сроки устаревания сжались ещё больше. О чём я <a href="http://lonelyelk.ru/posts/45">писал</a> после перерыва. От того и столпов-авторитетов, типа <i>_why</i> становится больше, а сами они становятся мельче и заметно быстротечнее. Но если вам, как и мне, стремительное устаревание технологий по душе, то предлагаю помянуть виртуальной минутой молчания ушедших персонажей, — и за работу!<br /></p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/542016-05-26T15:36:23+00:002016-05-26T15:47:16+00:00Создание своих правил для udev<h2>Магия</h2><p>Когда только начинаешь знакомиться с компьютером на уровне пользователя, то многие вещи воспринимаются как само собой разумеющаяся магия. Например, когда вставляешь в компьютер диск или флэшку, она появляется у тебя среди папок, а то и всплывает окном поверх всего. Или, через что много было заражено компьютеров, само что-то с флэшки запускается.</p><p>Но, чем более профессионально во всё вникаешь, тем больше приходится играть роль того самого «волшебника», ответственного за «магию», а по сути — фокусника. Для меня такое наступает примерно на двадцатый раз однообразных действий, когда уже хочется, чтобы оно само как бы по волшебству сделалось.</p><h2>Вставил диск</h2><p>В нашей работе очень много происходит пересылки данных. На текущий момент, я считаю, интернет ещё не готов, чтобы передавать через него абсолютно всё. Пока устанавливаются рекорды скорости передачи данных, ничто не может побить фуру, гружёную жёсткими дисками. Такие диски нам нужно вставлять и вынимать в компьютер, а разбираться с ними будет <b>udev</b>.</p><p><img alt="Sorting robot" height="337" src="http://lonelyelk.ru/images/uploads/0000/0053/sortingrobot.jpg" width="600" /></p><p>В случае с копированием данных, можно запросто обойтись именем тома при форматировании, но бывают случаи интереснее. Например, когда разбирается сетевой рэйд-накопитель, и отправляется диск от него. Для выявления чётких признаков, по которым мы будем диск определять, нам нужно две команды:</p><pre><code>udevadm info -a --name=/dev/sdd1
udevadm info --query=env --name=/dev/sdd1</code></pre><p>как уже говорил, имея инструкцию, как подготовить диск для копирования, можно всегда иметь одинаковую метку тома. Чем и пользуемся: создаём файлик <b>/etc/udev/rules.d/90-my-storage-copy.rules</b>, куда пишем:</p><pre><code>ACTION=="add", ENV{ID_FS_USAGE}=="filesystem", ENV{ID_FS_TYPE}=="ext4", ENV{ID_FS_LABEL_ENC}=="storage-copy", RUN+="/usr/local/bin/storage-copy-mount.sh"
ACTION=="remove", ENV{ID_FS_USAGE}=="filesystem", ENV{ID_FS_TYPE}=="ext4", ENV{ID_FS_LABEL_ENC}=="storage-copy", RUN+="/usr/local/bin/storage-copy-umount.sh"</code></pre><p>Всё, что выдавалось нам с параметром <i>--query=env</i> будет в параметрах окружения нашего скрипта <b>/usr/local/bin/storage-copy-mount.sh</b>:</p><pre><code>#!/bin/sh
mount_point="/mnt/myrules/$(basename $DEVNAME)"
mkdir -p $mount_point
mount -t $ID_FS_TYPE -o ro $DEVNAME $mount_point</code></pre><p><b>/usr/local/bin/storage-copy-umount.sh</b>:</p><pre><code>#!/bin/sh
mount_point="/mnt/myrules/$(basename $DEVNAME)"
umount -l -f $mount_point
rmdir $mount_point</code></pre><p>Некоторые гайды не рекомендуют вызывать команду <b>mount</b> из правил <b>udev</b>, но когда это останавливало настоящих волшебников? :)</p><h2>Массив без массива</h2><p>Второй случай — половина рэйд-массива. Нужно собирать и разбирать массивы так, чтобы это не пересекалось с работой остальной системы. Некоторые хранилища задают метки тома своим разделам с информацией, а некоторые можно определить только по номеру партиции. <b>/etc/udev/rules.d/90-my-storage-rais.rules</b>:</p><pre><code>ACTION=="add", ENV{ID_FS_USAGE}=="raid", ENV{ID_FS_TYPE}=="linux_raid_member", ENV{ID_PART_ENTRY_NUMBER}=="3", ENV{ID_FS_LABEL_ENC}!="system*", RUN+="/usr/local/bin/storage-raid-mount.sh"
ACTION=="add", ENV{ID_FS_USAGE}=="raid", ENV{ID_FS_TYPE}=="linux_raid_member", ENV{ID_FS_LABEL_ENC}=="DiskStation*", RUN+="/usr/local/bin/storage-raid-mount.sh"
ACTION=="remove", ENV{ID_FS_USAGE}=="raid", ENV{ID_FS_TYPE}=="linux_raid_member", ENV{ID_PART_ENTRY_NUMBER}=="3", ENV{ID_FS_LABEL_ENC}!="system*", RUN+="/usr/local/bin/storage-raid-umount.sh"
ACTION=="remove", ENV{ID_FS_USAGE}=="raid", ENV{ID_FS_TYPE}=="linux_raid_member", ENV{ID_FS_LABEL_ENC}=="DiskStation*", RUN+="/usr/local/bin/storage-raid-umount.sh"</code></pre><p>То есть, говоря человеческим языком, это или рэйд-партиция с номером 3, название которой не начинается с <i>system</i> или рэйд-партиция с именем, начинающимся с <i>DiskStation</i>. Теперь нам нужно собрать массив так, чтобы у него было уникальное имя, но при этом однозначно связанное с устройством, чтобы не плодить лишних сущностей. Для этого я решил точку для монтирования называть так же как имя устройства в <i>/dev</i>, а номер рейда брать из кода последней буквы. <b>/usr/local/bin/storage-raid-mount.sh</b>:</p><pre><code>#!/bin/sh
mount_point="/mnt/myrules/$(basename $DEVNAME)"
num=$(printf %d "'$(echo $DEVNAME | head -c8 | tail -c1)")
raid_device="/dev/md$num"
mkdir -p $mount_point
mdadm -S $raid_device
mdadm -A -R $raid_device $DEVNAME
mount -o ro $raid_device $mount_point</code></pre><p>Тут происходит магия баша. Я писал уже, что всегда испытываю большое удовольствие, когда что-то удаётся сделать на этом скриптовом языке:</p><pre><code>echo $DEVNAME | head -c8 | tail -c1</code></pre><p>Выдаёт нам восьмую букву имени устройства, то есть «d» для «/dev/sdd3», например, и «f» для «/dev/sdf5».</p><pre><code>printf %d "'d"</code></pre><p>Выдаёт нам 100, а в случае с «f» — 102. И мы получаем имя «/dev/md100», под которым насильно поднимаем <i>raid1</i> на одном диске из двух. И обратно то же самое. <b>/usr/local/bin/storage-raid-umount.sh</b></p><pre><code>#!/bin/sh
mount_point="/mnt/myrules/$(basename $DEVNAME)"
num=$(printf %d "'$(echo $DEVNAME | head -c8 | tail -c1)")
raid_device="/dev/md$num"
umount -l -f $mount_point
mdadm -S $raid_device
rmdir $mount_point</code></pre><p>Понятно, что с вытаскиванием сложнее, даже если монтировать, как это делаю я, только для чтения. Это всё актуально, если после работы с диском прошло значительное время. И я предпочитаю хотя бы размонтировать вручную. Но при этом совершенно прекрасно то, что все наши устройства будут создавать папки и появляться в <i>/mnt/myrules</i>, как флэшки появляются в <i>/media</i> на десктопных версиях Убунту.</p><h2>Для самостоятельного изучения</h2><p>1. <a href="https://www.kernel.org/pub/linux/utils/kernel/hotplug/udev/udev.html">man udev</a></p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/532016-05-11T23:13:23+00:002016-05-11T23:13:23+00:00Как показать Яндекс Панорамы где угодно<h2>История</h2><p>Когда Яндекс Панорамы только появились, и мы захотели разместить их у себя в портфолио на сайте, нам приходилось довольствоваться скриншотами наиболее удачных ракурсов, потому что никакого другого способа показать Яндекс Панораму кроме как на Яндекс Панорамах не было.</p><p>Со временем, сменился плеер и появился код вставки вида:</p><pre><code><script src="//panoramas.api-maps....."></script></code></pre><p>Но никакого способа управлять, кроме как переходить по стрелкам, в нём не предусмотрено. Мне стало интересно, можно ли что-то с этим сделать. И поэтому сегодня, дорогой читатель, мы поиграем в хакеров. Вредить мы никому не будем: хакеры вредители — это только часть хакеров. Иначе говоря, приспособим под наши запросы то, что изначально под них не было предназначено.</p><p><img alt="Hack" height="337" src="http://lonelyelk.ru/images/uploads/0000/0051/hack.jpg" width="600" /></p><h2>Параметры</h2><p>Сначала посмотрим на адрес. Сразу видно две вещи. Никакого уникального идентификатора панорамы в нём не присутствует. Если переходить между панорамами не меняя направление взгляда, и копировать код для вставки на сайт, то различаться будет только один параметр:</p><pre><code>ll=37.61782676%2C55.75074572</code></pre><p>Это же долгота и широта (в таком порядке) через запятую! Второе, с чем сразу же хочется повозиться — это параметр:</p><pre><code>size=690%2C495</code></pre><p>Это ширина и высота окна через запятую. Если посмотреть на то, как работает скрипт, и что он оставляет после себя на странице, то совсем не обязательно лезть и деобфусцировать код. Всё понятно: скрипт создаёт вместо себя тег <i>iframe</i> и другой тег <i>script</i>, а себя удаляет. Параметр <b>size</b> и задаёт размеры айфрейма.</p><p>Уже этого нам достаточно, чтобы собрать небольшой плеер панорам с картой, который по клике на карте открывает панораму из этого или ближайшего места. Для примера я использую <a href="http://leafletjs.com/">Leaflet</a>. У него такой приятный синтаксис и процесс!</p><pre><code class='javascript'>var latlng = L.latLng(55.75074572, 37.61782676);
var $map = L.map('map', {center: latlng, zoom: 15});
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo($map);
function openPanoramaAt(latlng) {
var panoDiv = $('#panorama');
var panoScript = document.createElement('script');
panoScript.type = 'text/javascript';
panoScript.src = 'https://panoramas.api-maps.yandex.ru/embed/1.x/?lang=ru&ll=' + latlng.lng + '%2C' + latlng.lat + '&ost=dir%3A0.0%2C0.0~span%3A130%2C70.26418362927674&size=' + panoDiv.width() + '%2C' + panoDiv.height() + '&l=stv';
panoDiv.empty();
panoDiv[0].appendChild(panoScript);
}
openPanoramaAt(latlng);
$map.on('click', function (me) {
$map.panTo(me.latlng);
openPanoramaAt(me.latlng);
});</code></pre><p>Кроме лифлета я, конечно, люблю джейквери. Но, к сожалению, на нём невозможно вставить тег <i>script</i>, чтобы он заработал. Поэтому тут немного намешано.</p><p>Но нам нужно идти дальше. Ведь внутри панорам можно переходить по стрелкам, а у нас это никак не отображается на карте. Что же делать?</p><h2>Человек посередине</h2><p>Чтобы определить, что делает скрипт, у нас и так уже открыт инспектор страницы. Теперь, дорогой читатель, давай переключимся во вкладку «сеть».</p><p>Переходя по стрелкам мы увидим, что, кроме всего прочего, плеер запрашивает файлик по адресу <i>https://panoramas.api-maps.yandex.ru/panorama/1.x/?l=stv&lang=ru_RU&...&format=json</i>. Тут уже нет широты и долготы, а присутствует идентификатор, но если открыть этот джейсон в новой вкладке, то внутри него мы увидим нужные нам координаты:</p><pre><code class='javascript'>JSON.parse(response).data.Data.Point.coordinates</code></pre><p>То, что браузер видит это запрос, означает, что скорее всего в основе лежит <b>XMLHttpRequest</b>. Так как заголовка <b>Content-Security-Policy</b> не видно (по правде сказать, настраивать его довольно сложно, и обычно если кто и прописывает такой заголовок, то там среди прочего есть <b>unsafe-inline</b>), то мы попробуем подслушать, о чём говорит плеер с сервером.</p><p>Можно не изобретать велосипед и просто поискать, <a href="http://stackoverflow.com/questions/6884616/intercept-all-ajax-calls">как это делают уже до нас</a>. В нашем случае плеер находится внутри генерящегося на лету айфрейма и именно класс внутри этого айфрейма нам и надо подменить. Для этого используем модный нынче <b>MutationObserver</b>. Я собрал <a href="https://jsfiddle.net/lonelyelk/02j5wLz2/">работающий пример на jsfiddle</a>, и предлагаю его вниманию дорогих читателей.</p><ol><lh>Сразу видно несколько недостатков:</lh><li>Размеры панорамы не адаптируются при изменении размеров окна.</li><li>Невозможно отследить направление взгляда, чтобы показать его на карте. Я рылся в объектах, но ничего не нашёл.</li><li>Пришла беда, откуда не ждали: случился прогресс!</li></ol><p></ol><h2>Правильный API</h2><p>Тема этого топика была запланирована у меня некоторое время назад. И основные фишки были опробованы и сделаны тоже некоторое время назад. Но когда я сел писать конкретный код, то обнаружил, что в <a href="https://tech.yandex.ru/maps/doc/jsapi/2.1/dg/concepts/panorama-docpage/">стандартной поставке API карт уже есть панорамы</a>. Произошло это в прошлой версии 2.1.38 от 31 марта 2016. Сейчас я работал с 2.1.39. Всего 42 дня как можно ставить панорамы на карты!</p><p>Конечно же, я собрал <a href="https://jsfiddle.net/lonelyelk/mh8uhsy4/">такой же пример на API Яндекс Карт</a>. (Всё-таки синтаксис лифлета намного изящнее, извините). Это настолько новое явление, что даже не входит в стандартный полный набор модулей. Заметьте во внешних ресурсах слева я написал для загрузки (иначе не работает):</p><pre><code>load=package.full,panorama.isSupported,panorama.locate,panorama.createPlayer,panorama.Player</code></pre><p>Недостатки «хакерского» метода отсутствуют. Плеер следует за размерами элемента. Для изменения параметров просмотра есть специальные события, поэтому можно рисовать на карте направление взгляда. В моём примере это не сделано, конечно.</p><p>Так что с точки зрения дальнейшего использования, моё решение утратило свою актуальность. Но с точки зрения образовательной — очень даже отличный материал, я считаю!</p><h2>Отдельно ссылки:</h2><ol><li><a href="https://jsfiddle.net/lonelyelk/02j5wLz2/">Как приспособить код для вставки Яндекс Панорам на свои карты (jsfiddle)</a>;</li><li><a href="https://jsfiddle.net/lonelyelk/mh8uhsy4/">Как показать панорамы с помощью API Яндекс Карт (jsfiddle)</a>;</li><li><a href="http://stackoverflow.com/questions/6884616/intercept-all-ajax-calls">Intercept all ajax calls (stackoverflow)</a>;</li><li><a href="https://tech.yandex.ru/maps/doc/jsapi/index-docpage/">API Яндекс Карт</a>;</li><li><a href="http://leafletjs.com/">Лучший API для картографических сервисов Leaflet</a>.</li></ol>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/522016-04-27T23:24:30+00:002016-04-27T23:37:44+00:00Стрелки для krpano на несущетсвующем языке<p>Когда-то давно, когда мы начинали делать панорамы для Яндекса, я написал для них плеер на флэше. Он потом был им полностью с потрохами передан на поддержку и значительно доработан. А сейчас уже, кажется, сдан в утиль. На смену флэшу приходит html5. И в прочих проектах, кроме наших внутренних, старый плеер тоже уже не используется.</p><p>Новый плеер написать пока так и не доходят руки, хотя, возможно, и придётся это сделать. Поэтому мы пользуемся уже готовыми продуктами. Например, плеер <a href="http://krpano.com/">krpano</a>. О моей с ним работе и хочу рассказать. Вот, где мой интерес к разным языкам сыграл свою роль.</p><h2>Стрелки</h2><p>Клаус разрабатывал свой плеер на стыке эпох, и поэтому он у него поддерживает как флэш, так и html5. По сути же это два плеера, которые работают с одним набором данных. Для панорамы обычно это изображения и некоторое описание, как их друг к другу приладить и что разместить поверх. В данном случае — это файл xml, спецификация которого довольно хорошо документирована.</p><p><img alt="Mayalanguage" height="337" src="http://lonelyelk.ru/images/uploads/0000/0049/mayalanguage.jpg" width="600" /></p><p>Передо мной встала задача создать стрелки как в панорамах улиц. Математика у них не очень сложная, а для реализации я решил использовать встроенные в плеер хотспоты. Они позволяют рисовать многоугольники любой формы поверх панорамы.</p><p>Поскольку кроме фотографий плеер использует только xml, то автору пришлось придумать свой язык программирования, который бы позволял программировать внутри xml. Можно, конечно, вызывать функции джаваскрипта, но во-первых, это тормозит флэшовую версию, а во-вторых, в чём же тут интерес?</p><p><a href="http://krpano.com/docu/actions/#top">Язык</a> напомнил мне ассемблер, хотя мне не довелось на нём много программировать. По сути это <a href="https://ru.wikipedia.org/wiki/Польская_нотация">польская нотация</a>, для которой проще всего писать интерпретатор. При этом отсутствует вложенность операций. Функции не возвращают значения (кроме одной), а изменяют аргументы. И самое главное не запутаться, где нужна переменная, а где её значение.</p><p>В общем, дорогой читатель, это очень интересный опыт. Я рекомендую всем писать хоть изредка на незнакомом языке. Освежает восприятие. Единственная проблема в том, как потом сделать что-нибудь сложнее. Если мне захочется усложнить математику и позволить наклонять стрелки относительно горизонта, то как быть?</p><h2>Сжатие</h2><p>Для тестирования я решил использовать микро-сервер node.js, а вместе с ним инструмент для исполнения задач <b>gulp</b>. Одна такая задача — убирать лишние пробелы в придуманном скриптовом языке krpano. Не столько для обфускации или экономии трафика, сколько опять же для интереса.</p><p>Для сжатия xml я нашёл pretty-data, а недостающий кусок дописал:</p><pre><code class='javascript'>var gulp = require('gulp'),
prettyData = require('gulp-pretty-data');
// Minify krpano action
function minifyAction() {
function dry (file, cb) {
file.contents = new Buffer(String(file.contents).replace(/(<action.+?>)([\s\S]+?)(<\/action>)/ig, function (str, opentag, cnt, closetag) {
return opentag + cnt.replace(/\s*(^|[;,=!])\s*/g, "$1") + closetag;
}));
cb(null, file);
}
return require('event-stream').map(dry);
}
// Minify plugin xml
gulp.task('xml', function () {
return gulp.src('dev/arrows.xml')
.pipe(prettyData({type: 'minify'}))
.pipe(minifyAction())
.pipe(gulp.dest('arrows/example/'));
});</code></pre><p>В общем, если вдруг вы используете krpano, то милости прошу за моими стрелочками. Для них мне тоже пришлось изобрести, как внутри xml задавать форму и поведение опорной точки (см. пример на странице плагина).</p><h2>Ссылки</h2><ol><li><a href="http://arc360.ru/">Виртуальный тур по Арктике с моими стрелками</a>.</li><li>Репозиторий <a href="https://github.com/lonelyelk/krpano-arrows">krpano-arrows</a> на гитхабе.</li><li><a href="http://krpano.com/plugins/userplugins/arrows/#top">Страница плагина</a> на krpano.com.</li></ol><p></ol>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/512016-04-14T15:27:35+00:002016-04-14T15:27:35+00:00Автоматическое монтирование папки NFS<h2>Источник вдохновения</h2><p>Кроме рабочих процессов и случайного вдохновения, самым надёжным источником программерских задач является повторение. Одно из самых ярких чувств удовлетворения наступает от того, что не нужно делать то, что вынужден был делать до этого сто раз.</p><p>В работе мы используем сетевые хранилища данных. Они не так гибки в настройке, как обычные компьютеры, но работать можно. Для монтирования дисков на рабочие машины мы используем протокол NFS. И он, вместе с этими устройствами, доставляет множество мелких проблем.</p><p>Одну из таких проблем решает <a href="http://lonelyelk.ru/posts/50">быстрый пинг</a> из предыдущей статьи. Потому что хуже того, чтобы пытаться монтировать выключенное хранилище может быть только выключение замонтированного хранилища.</p><p>Но сегодня мы будем бороться с тем, что каждое хранилище требует по-разному к нему обращаться с рабочей машины.</p><p><img alt="DifferentButNotMyProblem" height="337" src="http://lonelyelk.ru/images/uploads/0000/0047/differentformats.jpg" width="600" /></p><h2>Имя, сестра!</h2><p>После выполнения одинаковых инструкций — настройка адреса, создание папки c именем, например, <i>storage</i>, предоставление к ней доступа по NFS — на хранилищах разных марок оказываются доступны разные папки. Тут нам на помощь приходит <b>showmount</b>:</p><pre><code>$ showmount -e 192.168.4.50
Exports list on 192.168.4.50:
/storage
/homes
/Web
/Usb
/Recordings
/Public
/Network Recycle Bin 1
/Multimedia
/Download
$ showmount -e 192.168.4.60
Exports list on 192.168.4.60:
/volume1/storage *</code></pre><p>Мы бы могли на первом хранилище создать руками папки <i>/volume1/storage</i>, но всегда может появиться устройство с другим названием корневого раздела. Вот и первый кусок кода, где мы определяем имя удалённой папки:</p><pre><code>NFSPOINT=`showmount -e $HOST | grep storage | awk '{print $1}'`</code></pre><h2>autofs</h2><p>В случае с постоянными включениями-выключениями и переносами хранилищ одним из хороших решений является <a href="https://help.ubuntu.com/community/Autofs">autofs</a>. В таком случае нам нужно только автоматически конфигурировать его по запросу. Если в нашем <b>/etc/auto.master</b> написано:</p><pre><code>/mnt/autofspts /etc/auto.myrules</code></pre><p>То по запросу можно писать нужную конфигурацию в <b>/etc/auto.myrules</b> таким образом:</p><pre><code>#!/bin/bash
address=192.168.4.50
folder=somefolder
if ping -A -s16 -i0.5 -c3 -q $address > /dev/null 2>&1; then
set -o pipefail
mount_point=`showmount -e $address | grep storage | awk '{print $1}'`
if [[ $? == 0 ]]; then
entry="$folder -fstype=nfs,rsize=8192,wsize=8192,noatime,nodiratime,intr,async $address:$mount_point"
case "$mount_point" in
/storage)
model="QNAP"
;;
/volume1/storage)
model="Synology"
;;
*)
model="unkonwn"
;;
esac
output="$address:$mount_point $model"
if [ "x$(cat /etc/auto.myrules)" = "x$entry" ]; then
echo $output
else
echo $entry > /etc/auto.myrules && echo $output configured
fi
else
exit 101
fi
else
exit 102
fi</code></pre><ol><lh>Тут мы делаем сразу несколько волшебных вещей:</lh><li>Пингуем перед тем, как смотреть на папки;</li><li>Определяем марку из уже известных хранилищ;</li><li>Сверяем, что уже написано в конфиге и не пишем, если там всё ок;</li><li>Возвращаем разные статусы для разных ошибок.</li></ol><p></ol><p>Тут, однако, нужны права суперпользователя, чтобы писать в конфиг. О том, как я поступаю с суперпользователями на некоторых рабочих машинах я расскажу дальше.</p><h2>sudo mount</h2><p>Всем хорош autofs, кроме того, что не проверяет, опять таки, включена ли машина, прежде чем лезть на неё. Что приводит, например, к зависанию процедур листинга папок со ссылками внутрь внешних хранилищ. Вполне возможно обойтись просто командой <b>mount</b>. Но в случае, когда тома не прописаны в <i>/etc/fstab</i>, для монтирования нужны права суперпользователя. А автоматически править <i>/etc/fstab</i> очень не хочется.</p><p>На машинах, где все знают пароль для <i>sudo</i> я пользуюсь совершенно беззастенчиво совершенно опасной возможностью <i>sudo</i> получать пароль из стандартного ввода:</p><pre><code>#!/bin/bash
NAME=$1
POINT=$2
HOST=$3
if mount | grep $POINT -c > /dev/null; then
./ping.sh $HOST && echo -e "[\e[0;32mOK\e[0m] Already mounted $NAME" || ( echo -e "[\e[0;31mFAIL\e[0m] $NAME is mounted but unreachable. Check if it's powered and connected" && exit 1 )
else
if ./ping.sh $HOST; then
NFSPOINT=`showmount -e $HOST | grep storage | awk '{print $1}'`
echo "password" | sudo -S -p "" mount -tnfs -o"rw,rsize=8192,wsize=8192,noatime,nodiratime,intr,async" $HOST:$NFSPOINT $POINT && echo -e "[\e[0;32mOK\e[0m] Successfully mounted $NAME" || ( echo -e "[\e[0;31mFAIL\e[0m] Unable to mount $NAME" && exit 1 )
else
echo -e "[\e[0;31mFAIL\e[0m] $NAME is unreachable."
exit 1
fi
fi</code></pre><p>Конечно, кусочек <i>echo "password" | sudo -S -p "" mount</i> вызывает резонный вопрос: «А почему бы тогда не сделать sudo без пароля для данного пользователя?» Ответ тут такой, что за этой машиной работают не только роботы, но и люди, а человека ввод пароля, пусть даже и такого, который все знают, вводит в более сосредоточенное и серьёзное состояние.</p><h2>Вопрос для самостоятельного изучения</h2><p>Если вдруг вы знаете, как справляться ситуацией, когда замонтированное (любым способом) хранилище NFS выключили, то напишите мне.</p><pre><code>sudo umount -f -l /mnt/point</code></pre><p>Особенно для случаев, когда такая команда не работает.</p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/502016-03-30T23:10:45+00:002016-03-30T23:10:45+00:00Быстрый пинг<h2>Предисловие</h2><p>Сегодня хочу рассказать про чтение мануалов. Например, с их помощью я выяснил, что чтобы разрабатывать баш-скрипты на маке (он у меня не так давно) под линукс, нужно ставить <a href="http://apple.stackexchange.com/questions/69223/how-to-replace-mac-os-x-utilities-with-gnu-core-utilities">coreutils</a>. Потому что команды терминала ведут себя по-разному, имеют разные доступные опции и разные дефолтные настройки.</p><p>Баш мне вообще очень нравится. Я его довольно плохо знаю, он очень древний и немного уродливый, но даёт совершенно ни с чем не сравнимое удовольствие и чувство гордости, когда получается сделать что-то полезное на нём.</p><h2>Задача</h2><p>Очень многие, даже независимо от системы, пользовались командой терминала <b>ping</b>. Она позволяет быстро и наглядно определить, есть ли связь с тем или иным узлом. Передо мной встала задача, чтобы определял наличие связи с узлом скрипт, а не человек с глазами. Нужно было быстро и дёшево получить однозначный ответ в виде кода завершения. Что же, открываем</p><pre><code>man ping</code></pre><p><img alt="Simply Yes No" height="337" src="http://lonelyelk.ru/images/uploads/0000/0045/simplyyesno.jpg" width="600" /></p><h2>Ход решения</h2><ol><li>Линуксовый пинг продолжается бесконечно, поэтому нужно ограничить число пакетов. Опция <i>-с</i>.</li><li>Можно сделать пакет меньше с помощью опции <i>-s</i>.</li><li>Можно уменьшить интервал между посылаемыми пакетами (но не меньше 0.2 секунд, если пингует не суперпользователь) с помощью опции <i>-i</i>.</li><li>Или же интервал можно сделать адаптивным, чтобы он сам ускорялся, если ответ пришёл быстро с помощью <i>-A</i>.</li><li>можно не выводить ненужную нашему скрипту информацию с помощью <i>-q</i> и перенаправить стандартный вывод ошибок в никуда.</li></ol><p></ol><p>Что мы получили:</p><pre><code>$ ping -A -s16 -c3 -q ya.ru 2>/dev/null
PING ya.ru (93.158.134.3) 16(44) bytes of data.
--- ya.ru ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 400ms
rtt min/avg/max/mdev = 1.923/2.075/2.339/0.187 ms, ipg/ewma 200.281/1.973 ms</code></pre><p>Пока всё ещё многовато читать. Хорошо бы вытащить только число полученных пакетов. А оно у нас как раз после первой запятой в строчке рядом со словом «received».</p><pre><code>$ ping -A -s16 -c3 -q ya.ru 2>/dev/null | grep received
3 packets transmitted, 3 received, 0% packet loss, time 401ms
$ ping -A -s16 -c3 -q ya.ru 2>/dev/null | grep received | awk -F', ' '{print $2}'
3 received
$ ping -A -s16 -c3 -q ya.ru 2>/dev/null | grep received | awk -F', ' '{print $2}' | awk '{print $1}'
3</code></pre><p>Почти всё работает быстро, кроме выключенных машин и несуществующих адресов (не имён). В отличие от ошибки DNS пинг требует убедиться, что всё действительно недоступно. Но мне нужно быстро и однозначно. Повезло, что плохое соединение для моей задачи равносильно отсутствию соединения. На помощь приходит <b>timeout</b>:</p><pre><code>$ timeout 1 ping -A -s16 -c3 -q ya.ru 2>/dev/null</code></pre><p>Теперь нужно передавать фейл дальше и сверять, собственно, число пингов. Получаем наш красивенький быстрый <b>fastping.sh</b>:</p><pre><code>#!/bin/bash
cnt=3
to=1
rcvd=$(set -o pipefail; timeout $to ping -A -s16 -c$cnt -q $1 2> /dev/null | grep received | awk -F', ' '{print $2}' | awk '{print $1}')
if [[ $? != 0 || $cnt != $rcvd ]]; then
exit 1
fi</code></pre><h2>Дополнительно</h2><ol><li>man ping</li><li>Полная версия <a href="https://gist.github.com/lonelyelk/64768e88c2fd02992aade0838f4e235f">fastping.sh</a>, в которой можно задать число пакетов и таймаут, но они имеют дефолтные значения.</li><li>Пинг на стероидах — <a href="http://fping.sourceforge.net/">fping</a>.</li></ol>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/492016-03-16T23:25:13+00:002016-03-16T23:25:13+00:00Как я встраивал reCAPTCHA v2 в свой антикварный бложек<h2>Предпосылки</h2><p>Мы живём в интересное время. Когда я увлекался довольно серьёзно руби он рэйлз, лет шесть-семь назад, была версия руби 1.8.7 и версия рельсов 2.3. Все неспешно переходили на руби 1.9.1 и рэйлз 3.0. Сегодня, пять лет спустя, стабильная версия руби — 2.2.3, а про рельсы уже агитируют переходить на 5.0, хоть и бета. Когда же я начал свой первый проект на ноде — три года назад, — версия node.js была что-то типа 0.22. А сегодня уже 5.8!</p><p>А недавно имел разговор с читателем на тему того, что стремительно возросшая популярность джаваскрипта создаёт ситуацию, при которой, уходя в отпуск, программисты рискуют вернуться с устаревшими навыками, потому что за две недели появились три новых прекрасный фреймворка, а два не менее прекрасных — умерли.</p><h2>Антиквариат</h2><p>До последнего перерыва, рассказ о начинке моего блога мог бы легко заполнить парочку в меру интересных статей, а сейчас это представляет интерес только для археологов. Что касается движков для блога вообще, то про когда-то популярный ЖЖ все уже забыли. Стремительно набирает моду вести канал в Телеграме. А товарищ мой — Илья — в когда-то давно в качестве платформы для блога сделал самый правильный, как мне сегодня кажется, выбор — генератор статического сайта.</p><p>Что ещё? Технология оупенайди, на которой у меня были прикручены комментарии, умерла. Рекапча, которую я прикручивал в комментарии Ире, была куплена Гуглом и ещё пока жива, но уже выпустили <a href="https://www.google.com/recaptcha/intro/index.html">вторую версию</a>, и я боюсь, как бы они таки не закрыли первую, как <a href="http://www.slate.com/articles/technology/map_of_the_week/2013/03/google_reader_joins_graveyard_of_dead_google_products.html">Гугл это умеет делать</a>. Но даже тогда джем, который я использовал, чтобы встроить капчу, волшебным образом исчез из библиотек, и мне пришлось таскать его с собой в папочке <i>vendor</i>. Про вёрстку я даже не говорю.</p><p>В общем, для того, чтобы взять и переписать проект с нуля, много ума не нужно. Но сегодня мне интересно покопаться в старье и посмотреть, что ещё из него можно выжать. Поэтому, если вы любите свои старые поделки так, как люблю их я, то очень важно овладеть навыком написания обезьяньих заплаток (monkey patch).</p><p><img alt="DIY" height="337" src="http://lonelyelk.ru/images/uploads/0000/0043/diy.jpg" width="600" /></p><h2>reCAPTCHA v2</h2><p>Когда я решил использовать новую капчу, я даже не стал искать библиотек. С вероятностью 80% они не заработают на моей старинной системе, и с вероятностью 30% перестанут поддерживаться очень скоро. Просто читаем <a href="https://developers.google.com/recaptcha/intro">документацию</a>, встраиваем капчу в форму отправки комментария, а на сервере прямо в методе создания комментария пишем, например:</p><pre><code class='ruby'>url = URI.parse('https://www.google.com/recaptcha/api/siteverify')
req = Net::HTTP::Post.new(url.path)
req.set_form_data 'secret' => 'SECRET_KEY',
'response' => params['g-recaptcha-response'],
'remoteip' => request.remote_ip
https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
res = JSON.parse(https.start { |p| p.request req }.body)
if res['success'] ...</code></pre><p>И далее с ответом можно делать всё, что угодно. В тонкости уже не вдаюсь, потому что, как может увидеть дорогой читатель, даже хэши у меня написаны с ракетами, а не по-современному с двоеточиями.</p><p>Да, это противоречит паттерну MVC, да, это противоречит ООП. Но посмотрите на саму капчу: она не учитывает положения формы на странице, когда открывает своё окно. А также <a href="https://groups.google.com/forum/#!topic/recaptcha/Ui0VgCzMziM">не работает задокументированная фича <i>data-tabindex</i></a>. Полно хороших библиотек и поделок увядают так и не исправив своих ошибок. Такова реальность программиста сегодня. Красивая библиотека для встраивания в проект на рельсах не сделает эту капчу лучше, но исправлять описанные выше ошибки можно тоже обезьяньими заплатками.</p><p>В общем, есть, конечно, определённый кайф в том, чтобы сделать всё максимально по науке и близко к идеалу, но не менее приятно пользоваться смекалкой и собрать что-нибудь из грязи и палок. Безусловно, есть ещё и очень приятное чувство освобождения в том, чтобы разрешить себе делать что-то не идеально.</p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/482016-03-10T23:41:16+00:002016-03-10T23:43:33+00:00Эзотерический джаваскрипт<p>Конечно, это уже не новая история, но меня настолько впечатлило, что я решил написать внеочередную статью. Тем более, что и <a href="http://blog.checkpoint.com/2016/02/02/ebay-platform-exposed-to-severe-vulnerability/">ребята из ибэя тоже пропустили известие</a> о том, что можно писать валидный джаваскрипт, не используя букв и цифр. Поскольку статья внеурочная, то и иллюстрации сегодня не будет. К тому же фиг проиллюстрируешь эти эзотерические языки программирования.</p><h2>Что случилось?</h2><p>Началось всё, я так понимаю, ещё в 2009 году с <a href="https://web.archive.org/web/20110813230556/http://sla.ckers.org/forum/read.php?2,15812,page=14">обсуждений</a> на форумах sla.ckers.org потенциальных возможностей межсайтового скриптинга. Но там ещё был другой синтаксис. Впечатливший же меня пример написания джаваскрипта <a href="http://patriciopalladino.com/blog/2012/08/09/non-alphanumeric-javascript.html">обнаруживается</a> только в 2012 году. Были и раньше другие заходы. Например, <a href="http://utf-8.jp/public/aaencode.html">джаваскрипт из японских смайликов</a>. Я же узнал об этом от товарища, который прислал мне <a href="http://thedailywtf.com/articles/bidding-on-security">ссылку на the daily wtf</a>.</p><p>Позволю себе немного пересказать, а дорогому читателю предлагаю прямо открыть консоль и попробовать. Вся эта штука основана на конвертации типов в джаваскрипте при сложении или приведении к булевым значениям. Программировать на джаваскрипте можно всего лишь с помощью <b>восьми</b> символов: <i>[, ], (, ), {, }, ! и +</i>. Вот, что выдаёт мне консоль в ответ на ввод:</p><pre><code class='javascript'>> +[]
0
> !+[]
true
> !!+[]
false
> +![]
0
> +!![]
1
> +!![]+!![]
2
> +!![]+!![]+!![]
3</code></pre><p>И вот у нас уже есть числа и булевы константы. Теперь нам нужны буквы:</p><pre><code class='javascript'>> !![]
true
> !![]+[]
"true"
> ![]
false
> ![]+[]
"false"
> +!![]+!![]
2
> +!![]+!![]+[]
"2"
> ({})
Object {}
> []+{}
"[object Object]"
> ({}[+[]])
undefined
> ({}[+[]])+[]
"undefined"
> +{}
NaN
> +{}+[]
"NaN"</code></pre><p>Далее все строковые значения можно получить, комбинируя вышеизложенное, но я буду писать строки в виде строк, чтобы облегчить (сделать возможным) прочтение кода. Используя уже имеющиеся буквы и цифры, можно ещё получить:</p><pre><code class='javascript'>> +"1e1000"
Infinity
> +"1e1000"+[]
"Infinity"
> +"1e100"
1e+100
> +"1e100"+[]
"1e+100"</code></pre><p>Все буквы <i>(a, b, c, d, e, f, i, j, l, n, o, r, s, t, u, I, O, [, ], <пробел>, +)</i> можно доставать из строк, с помощью квадратных скобок и чисел. Но мы уже давно можем сделать eval — исполнить произвольный код из строки:</p><pre><code class='javascript'>> (![]+[])[3]
"s"
> (![]+[])[3]+([]+{})[1]
"so"
...
> []['sort']['constructor']
function Function() { [native code] }
> []['sort']['constructor']('return alert')()
function alert() { [native code] }
> []['sort']['constructor']('return alert')()(1)
/* должен выскочить алерт */</code></pre><p>Дело остаётся за малым — получить все остальные символы, с помощью которых можно написать программу. Чтобы уже совсем не чувствовать стеснения. И тут нам на помощь должна прийти функция <b>unescape</b>, которая из знака «%» и аски-кода делает символ. Но у нас пока нет для этого символов «%» и «p». Где же их взять?</p><pre><code class='javascript'>> []['sort']['constructor']('return location')()
Location {hash: "", search: "", pathname: "/blog/posts/48", port: "", hostname: "lonelyelk.ru"…}
> []['sort']['constructor']('return location')()+[]
"http://lonelyelk.ru/blog/posts/48"
> ([]['sort']['constructor']('return location')()+[])[3]
"p"
> ({}+[])[0]
"["
> []['sort']['constructor']('return escape')()('[')
"%5D"
> []['sort']['constructor']('return escape')()('[')[0]
"%"
> []['sort']['constructor']('return unescape')()('%'+'7a')
"z"</code></pre><p>Не знаю, как ты, дорогой читатель, а я нахожу такое использование джаваскрипта совершенно волшебным. Настолько волшебным, что даже несмотря на уже не первую свежесть темы, мне очень захотелось об этом написать, да ещё и в неурочное время. Все материалы для самостоятельного изучения доступны по ссылкам выше.</p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/472016-03-02T23:16:19+00:002016-03-02T23:19:18+00:00Лось<p>Сейчас меня уже почти не спрашивают: «Почему лось?» Отчасти потому, что я не очень продвигаю этого персонажа, а отчасти потому, что люди думают, что мне просто нравятся лоси, или я их коллекционирую. Второе приводит, например, к тому, что мне дарят разных игрушечных лосей. Это забавный результат, поэтому я считаю, что репутацию местного сумасшедшего нужно поддерживать. Как следствие я начал использовать лосей в повседневной работе.</p><h2>Терминал</h2><p>Первым делом нужно, чтобы лось приветствовал меня при каждом открытии терминала или новой его вкладки. Примерно так:</p><p><img alt="лось в терминале" height="337" src="http://lonelyelk.ru/images/uploads/0000/0037/gifanimations.jpg" width="600" /></p><p>При этом хотелось бы, чтобы у него было разное случайное выражение лица. Для этого нужно в <b>~/.bash_profile</b> (или какой у вас файл для создания окружения) написать следующее:</p><pre><code>eye=(o O @ . - \* \~ °)
let left=${RANDOM}%${#eye[*]}
let right=${RANDOM}%${#eye[*]}
cat << EOF
_ _ _ _ _
| || | ____ | || || |
\ |/ \| /
\____ ${eye[$left]} ${eye[$right]} ______/
| |________
| }=
| __ ____ _ |
|| || || ||
|| || || ||
"" "" "" ""
EOF</code></pre><p>Базовая работа с массивами. Встроенная в баш функция случайного числа. Обязательно предварять косой чертой «*» и «~», чтобы они не стали списком файлов и домашней директорией. По-моему — красота!</p><h2>Spec runner</h2><p>И, конечно же, для любителей разработки через тестирование нужен правильный репортер:</p><p><div style="width:100%;background-color:#000"><img alt="elk-spec" height="90" src="http://lonelyelk.ru/images/uploads/0000/0039/elk-spec.gif" width="200" /><br /></div></p><p>Здесь нужно пользоваться эскейп кодами для управления положением каретки, то есть курсора. Мало кто помнит, но курсор можно двигать во все стороны на любое количество позиций. Данный пример я собрал для раннера тестов на жасмине. Да, да, я разрабатываю в том числе на <a href="https://nodejs.org/en/">node.js</a> и тестирую с помощью <a href="http://jasmine.github.io/">jasmine</a>. Ключевая функция для рисования лося вот:</p><pre><code class='javascript'>function printElk() {
if (specCount > 1) {
print('\x1b[5A');
}
if (specCount % 2 === 0) {
print(Array(specCount + 1).join(' ') + ' ^^' + eyes() + '^^\n');
print(Array(specCount + 1).join(' ') + ' _____ U\n');
print(specTrail + '~( _ /\n');
print(Array(specCount + 1).join(' ') + ' || ||\n');
print(Array(specCount + 1).join(' ') + ' ^^ ^^\n');
} else {
print(Array(specCount + 1).join(' ') + ' ^^' + eyes() + '^^\n');
print(Array(specCount + 1).join(' ') + ' _____ U\n');
print(specTrail + '`( _ /\n');
print(Array(specCount + 1).join(' ') + ' // \\\\\n');
print(Array(specCount + 1).join(' ') + ' ^^ ^^\n');
}
}</code></pre><p>Здесь <i>specTrail</i> и <i>eyes</i> следят за историей вопроса, а вся функция вместо того, чтобы как раньше, ставить точку или <b style="color:#e00">F</b>, поднимается на пять строчек вверх и переписывает их полностью.</p><h2>Бонус</h2><p>Это не про лося, но тем не менее. Вообще, я считаю, что всегда лучше потратить немного времени, чтобы сделать всё удобно и интересно. Мелочи решают!</p><p>Эту вещь придумал не я. Скорее всего, до меня её придумали неоднократно, и я не могу найти первоисточник. Тем не менее, считаю, что это очень полезная вещь, и нужно ей поделиться. Речь идёт о том, чтобы показывать статус завершения предыдущей команды в строке запроса баша.</p><pre><code>export PS1="\[\e]0;\u@\h: \W\a\`if [ \$? = 0 ];then echo \[\e[32m\]^_^\[\e[0m\];else echo \[\e[31m\]o_O\[\e[0m\];fi\`[\u@\h: \w][\$(rvm-prompt v)][\$(nvm_version)]\$(parse_git_branch)\$ "</code></pre><p>Это моя строка запроса. В её начале мы видим или зелёный довольный смайлик, или красный удивлённый. Благодаря вот этой части:</p><pre><code>\`if [ \$? = 0 ];then echo \[\e[32m\]^_^\[\e[0m\];else echo \[\e[31m\]o_O\[\e[0m\];fi\`</code></pre><p>Когда сделал себе и привык, то теперь просто не понимаю, как другие без этого обходятся.</p><h2>Материалы для самостоятельного изучения</h2><ol><li><a href="http://www.tldp.org/LDP/abs/html/randomvar.html">$RANDOM</a>.</li><li><a href="https://gist.github.com/lonelyelk/9bdb5b3361c8c003f80f">elk_reporter.js</a>.</li><li><a href="http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html">Управление курсором в баше</a>.</li></ol>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/462016-02-17T23:59:01+00:002016-02-17T23:59:01+00:00Смена настроек /etc/hosts в одно касание<h2>Зачем?</h2><p>В какой-то момент я стал носить на работу ноутбук и отказался от двух компьютеров: домашнего и рабочего. В редких случаях приходится делать небольшие действия по работе, находясь дома. Для того, чтобы добраться до нужных машин внутри рабочей сети, я использую ssh-тоннель с пробрасыванием портов. Например, есть два сервиса: <i>server1:8080</i> и <i>server2:5000</i>. Когда было два компьютера, то было всё просто. Рабочий компьютер находился внутри сети и видел оба сервера с их сервисами по правильным адресам, а домашний адресовал оба имени серверов на <i>localhost</i>, где сервисы оказывались на тех же портах после поднятия тоннеля. Но с ноутбуком нужно было как-то переключаться.</p><p>Для смены настроек я использовал самый простой, как мне кажется, способ: редактировал файл <b>/etc/hosts</b>. У меня было два набора строчек: для дома и для офиса. Один всегда закомментирован. Файл открывался с помощью <b>sudo vi</b>, и внутри можно использовать замену, используя номера строк, которые видны:</p><pre><code>:2,7s/^#/
:9,13s/^/#/</code></pre><p>Первая команда означает «со второй по седьмую строчку удалить „#“ в начале строки», а вторая — «с девятой по тринадцатую строчку поставить „#“ в начале строчки». Но когда делаешь одно и то же много раз, всегда хочется это автоматизировать.</p><p><img alt="Changesettings" height="337" src="http://lonelyelk.ru/images/uploads/0000/0035/changesettings.jpg?1455752573" width="600" /></p><h2>Как?</h2><p>Для начала мне хотелось избавиться от номеров строк (мало ли, какие добавятся или исчезнут строки). Поэтому я решил выделять зоны файла для офиса и дома комментариями «#officestart», «#officeend» и, соответственно, «#homestart» и «#homeend». Теперь интервал для замены можно было выделять через них:</p><pre><code>:%s/#officestart\zs\_.\+\ze#officeend/smth_smth_smth/g
:%s/#homestart\zs\_.\+\ze#homeend/smth_smth_smth/g</code></pre><p>В данных командах <b>\zs</b> и <b>\ze</b> означают начало и конец паттерна, который мы хотим заменить командой <b>s</b> в интервале <b>%</b>, то есть во всём файле. То есть мы меняем не всё, что нашли, а только часть. А сам паттерн — это <b>\_.\+</b>, что означает «один или более любых символов, включая конец строки». Буква <b>g</b> в конце означает, что может быть несколько таких блоков, что необязательно.</p><p>На что же мы будем заменять найденный паттерн между комментариями? Во-первых, нам совершенно точно понадобится замена внутри замены. А во-вторых, нам не поможет символ <b>^</b> для обозначения начала строчек, т.к. у найденного паттерна всего одно начало перед всеми строчками. Поэтому мы будем использовать знание структуры файла <b>/etc/hosts</b>: в случае IPv4 каждая незакомментированная рабочая строчка начинается с цифры, а закомментированная, как и положено, с «#». Для дома получаем команды:</p><pre><code>:%s/#officestart\zs\_.\+\ze#officeend/\=substitute(submatch(0), '\n\(\d\)', '\n#\1', 'g')/g
:%s/#homestart\zs\_.\+\ze#homeend/\=substitute(submatch(0), '\n#\(\d\)', '\n\1', 'g')/g</code></pre><p>Использование <b>\=</b> заставляет редактор выполнить выражение, то есть вызвать функцию <i>substitute</i> в таком виде. Тут, вроде бы, должно быть понятно, что мы передаём в функцию найденный паттерн, регулярное выражение с одной группой и на что его поменять в том паттерне.</p><h2>От команд к скрипту</h2><p>Осталось сделать из этого удобную штучку. Лично я оформил это следующим образом. В файле <b>~/.bash_profile</b>:</p><pre><code>alias imhome="sudo vim -u NONE -f -s $HOME/.vim/homehosts /etc/hosts"
alias imwork="sudo vim -u NONE -f -s $HOME/.vim/officehosts /etc/hosts"</code></pre><p>Соответственно, файлы <b>~/.vim/homehosts</b>:</p><pre><code>:%s/#officestart\zs\_.\+\ze#officeend/\=substitute(submatch(0), '\n\(\d\)', '\n#\1', 'g')/g
:%s/#homestart\zs\_.\+\ze#homeend/\=substitute(submatch(0), '\n#\(\d\)', '\n\1', 'g')/g
:wq</code></pre><p><b>~/.vim/officehosts</b></p><pre><code>:%s/#officestart\zs\_.\+\ze#officeend/\=substitute(submatch(0), '\n#\(\d\)', '\n\1', 'g')/g
:%s/#homestart\zs\_.\+\ze#homeend/\=substitute(submatch(0), '\n\(\d\)', '\n#\1', 'g')/g
:wq</code></pre><p>Таким образом, команды <b>imhome</b> и <b>imwork</b> спрашивают пароль и меняют настройки. Это иллюстрирует, почему был выбран редактор <i>vim</i> в качестве инструмента. Любые <b>sed</b> и <b>awk</b> будут потом требовать <b>sudo tee</b> для того, чтобы записать файл с нужными правами. А здесь мы запускаем всего одну команду.</p><h2>Материалы для самостоятельного изучения</h2><ol><li><a href="http://vim.wikia.com/wiki/Search_and_replace">Поиск и замена в vim</a>.</li><li><a href="http://vim.wikia.com/wiki/VimTip755">Использование выражений при поиске и замене в vim</a>.</li></ol>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/452016-02-03T22:42:02+00:002016-02-03T22:42:02+00:00Вот это я называю «перерыв»!<h2>Что было</h2><p>Приветствую тебя, дорогой читатель! Каким-то образом ты остался подписан на этот блог или зашёл на него по неясной причине — это хорошо! В этом году я решил возобновить ведение этого самого блога. Посмотрим, что из этого выйдет.</p><p>Было странно копаться во внутренностях этого сайта снова. Все технологии устарели, версии не поддерживаются и тому подобное. Последний <a href="http://lonelyelk.ru/posts/11#48263">комментарий</a> оставил мне читатель в 2014 году на статью 2009 года про тестирование оупенайди, авторизовавшись с помощью оупенайди, о том, что технология оупенайди мертва. Пришлось прикрутить на её место новую рекапчу от Гугла. Напишу как-нибудь об этом. Почувствовал себя, конечно, археологом.</p><p>Время, когда я начал вести этот блог, было временем увлечения руби он рэйлз. Было сделано много проектов. Потом влюблённость прошла, а с ней прошло о большое количество нового и интересного, о чём бы я мог здесь писать. Я стал немного залезать в другие области, но общей концепции так и не находилось. Даже прекрасные иллюстрации не спасли от наступившего затишья.</p><p><img alt="Wakeup" height="337" src="http://lonelyelk.ru/images/uploads/0000/0033/wakeup.jpg?1454539198" width="600" /></p><h2>Что будет</h2><p>Сейчас я не могу сказать, что являюсь увлечённым фанатом какого-либо языка, фреймворка или технологии, как не могу сказать, что являюсь профессионалом в оных. Что мне нравится делать — это изучать новые вещи и делать из них прикладные продукты. Прикладные они, конечно, не для всех, а в основном по линии моей работы. Но именно о простых, прикладных и — самое главное — интересных мне вещах из совершенно разных областей программирования я и буду писать. Думаю, получится делать по одной небольшой статье раз в две недели.</p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/442011-05-18T22:54:29+00:002011-05-18T22:57:17+00:00Использование руби программ в качестве фильтров для поиска<h2>Предыстория</h2><p>Не далее как прошлой осенью я писал о том, <a hreh="http://lonelyelk.ru/posts/41">как изнутри процесса определить, запущен ли он</a>, используя инструмент <i>grep</i>. В комментариях мне посоветовали использовать <i>pidof</i>, но мне не удалось заставить его работать для руби, т.к. поиск происходит по имени запускаемого файла, а в случае руби-скрипта это всегда <i>ruby</i>. Но мне существенно удалось сократить получение списка запущенных процессов с таким же именем. Вместо:</p><pre><code class='ruby'>`ps ax | grep #{File.basename(__FILE__)} | grep -v grep`.split("\n").map{ |l| l.strip.split(/\s+/)[0].to_i }.reject{ |pid| pid == Process.pid }</code></pre><p>получилось</p><pre><code class='ruby'>`pgrep -f #{File.basename(__FILE__)}`.chomp.split(/\s+/).reject{ |pid| pid.to_i == Process.pid }</code></pre><p>Довольно часто мне необходимо отфильтровать вывод или содержимое файла хитрее, чем просто поиск по регулярному выражению. Поскольку мне очень нравится руби, и, как неоднократно писалось в этом блоге, я пытаюсь использовать его везде, где можно, то почему бы снова так не поступить?</p><p><img alt="ascannerdarkly" height="337" src="http://lonelyelk.ru/images/uploads/0000/0029/ascannerdarkly.jpg" width="600" /></p><h2>Командная строка руби</h2><p>Руби имеет умеренное количество ключей командной строки. Кратко они описаны в выводе:</p><pre><code class='ruby'>ruby --help</code></pre><p>Нас в большей степени интересуют ключи <b>-n</b> и <b>-p</b>, которые создают цикл вокруг чтения из пайпа. Ссылка на подробности — в конце статьи.</p><p>Например, мы хотим посчитать, сколько всего виртуальной памяти занимают все процессы браузера гугл-хром. В качестве источника информации будем использовать вывод команды:</p><pre><code>ps axo "%p %z %c"</code></pre><p>В которой собраны только необходимые данные (занимаемая виртуальная память и имя процесса без аргументов) и пид (ну а вдруг?). А теперь этот вывод отправим не грепу, а нашему родному руби:</p><pre><code>ps axo "%p %z %c" | ruby -nae 'num ||= 0; num += $F[1].to_i if $F[2] =~ /chrome/; END{puts "total chrome virtual memory size #{num} Kbytes"}'</code></pre><p>Что это означает? Ключ <b>n</b> означает, что вокруг нашего скрипта есть цикл вида:</p><pre><code class='ruby'>while gets(); ... end</code></pre><p>Ключ <b>a</b> означает, что вместо переменной <b>$_</b>, куда автоматически попадает результат <i>gets</i>, мы можем использовать <b>$F</b>, который есть суть <b>$_.split</b>. А <i>END</i> содержит блок, который выполняется после цикла.</p><p>Ту же магию можно использовать и внутри запускаемых руби-скриптов. Например, если мы хотим найти какое-то слово внутри файла, выделить его цветом и вывести строку с номером, где это слово нашлось, то наш скрипт будет выглядеть вот так (файл <b>look_for</b>):</p><pre><code class='ruby'>#!/usr/bin/ruby -n
BEGIN {
unless ARGV.size == 2
puts "Usage: ./look_for <word> <path/to/file>"
exit
end
str = ARGV.shift
}
next unless $_ =~ /#{str}/
printf "%6s%s", $., $_.gsub($&, "\e[31m#{$&}\e[0m")</code></pre><p>Теперь, если сделать этот файл запускаемым и запустить его:</p><pre><code>./look_for word /in/some/file</code></pre><p>То можно увидеть неземную красоту. Кстати, обратите внимание на <i>shift</i>. Без него программа не работает, т.к. <i>gets</i>, который тут за кадром правит бал, пытается воспринимать все аргументы как пути к файлам, из которых непременно нужно что-нибудь прочитать.</p><p>Прочие прекрасные применения параметров командной строки руби я предлагаю пытливому читателю подсмотреть в ссылках ниже или найти самостоятельно.</p><h2>Материалы для самостоятельного изучения</h2><ol><li><a href="https://github.com/lonelyelk/lonelyelk_code/tree/master/oneliners">Полный код статьи на гитхабе</a>.</li><li><a href="http://cheat.errtheblog.com/s/ruby/">Справочник по параметрам командной строки</a>.</li><li><a href="http://ruby.activeventure.com/manual/man-1.4/options.html">То же, что и выше, но подробнее</a></li><li><a href="http://reference.jumpingmonkey.org/programming_languages/ruby/ruby-one-liners.html">Множество прекрасных примеров</a> (со ссылкой на источник).</li></ol>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/432011-03-23T18:58:26+00:002011-03-23T18:58:26+00:00Рекурсия в регулярных выражениях<h2>Пролог</h2><p>Что-то большие перерывы в написании статей входят в привычку. Способность некоторых коллег по цеху регулярно выдавать что-нибудь полезное и интересное вызывает уважение.</p><p><img alt="worm" height="90" src="http://lonelyelk.ru/images/uploads/0000/0023/worm.jpg" width="600" /></p><h2>Введение</h2><p>С тех самых пор, как я только узнал про регулярные выражения, я слышал об их несовершенстве и моральном устаревании. Регулярные выражения продолжали использоваться, а недовольные теоретики — сетовать. Основной претензией было то, что регулярные выражения не позволяют исследовать вложенности паттернов в виду своей линейности. Действительно, соглашался я, невозможно проверить правильность открытия и закрытия тегов или получить выражение в самых внутренних скобках.</p><p>Однако, как оказалось, человечество шагнуло далеко вперёд в вопросе совершенствования регулярных выражений. Об одном из новшеств хочу сегодня рассказать.</p><p><img alt="mole_worm" height="337" src="http://lonelyelk.ru/images/uploads/0000/0025/mole_worm.jpg" width="600" /></p><h2>Именованные группы</h2><p>В регулярных выражениях руби 1.9 появились именованные группы. Вот, как выглядит их элементарное использование:</p><pre><code class='ruby'>if /\A(?<first>[a-zA-Z]+)\s+(?<last>[a-zA-Z]+)\Z/ =~ "Vassily Poopkine"
puts [first, last].inspect
end
if md = /\A(?<first>[a-zA-Z]+)\s+(?<last>[a-zA-Z]+)\Z/.match("Vassily Poopkine")
puts [md[:first], md[:last]].inspect
end</code></pre><p>То есть мы не только выделяем группу скобками, как обычно, назначая ей тем самым порядковый номер (по номеру открывающей скобки), но и даём имя. И использовать его можно не только в локальных переменных и объекте <i>MatchData</i>, но и в самом регулярном выражении.</p><p>Более того, обращение к объявленным группам внутри может быть рекурсивным. Мне сразу же захотелось написать давнишнюю мою задумку о функции, раскрывающей вложенные скобки. Вот так:</p><pre><code class='ruby'>str = "1 + 2 * (3 - 4 / {5 + 6} + [7 - 8 * (9 + 10 * 11) + 12 * {13 - 14}] + 15) + 16 * (17 + 18)"
re = %r{
(?<fill>[0-9+\-*/\s]+){0}
(?<expression>\g<fill>*\g<brackets>\g<fill>*|\g<fill>){0}
(?<braces>\{\g<expression>+\}){0}
(?<squarebrackets>\[\g<expression>+\]){0}
(?<parentheses>\(\g<expression>+\)){0}
(?<brackets>\g<braces>|\g<squarebrackets>|\g<parentheses>)
}x
def calculator(str)
if str =~ /\A[0-9+\-*\/\s]+\Z/
eval str
else
raise "Invalid expression: #{str}"
end
end
f =-> s do
if $~[:expression] == $~[:fill]
calculator($~[:fill])
else
calculator($~[:brackets][1..-2].gsub(re, &f))
end
end
puts calculator(str.gsub(re, &f))
puts eval(str.gsub(/(?<left>\{|\[)|\}|\]/) { |s| $~[:left] ? "(" : ")" })</code></pre><p>Итак, в регулярном выражении присутствует 6 именованных групп: <b>fill</b> (заполнения пространства между скобками), <b>expression</b> (выражение, содержащее одни или ни одних нераскрытых скобок), <b>braces</b> (фигурные скобки), <b>squarebrackets</b> (квадратные скобки), <b>parentheses</b> (круглые скобки), <b>brackets</b> (любые скобки). Как видите, выражение описывается через скобки, а скобки — через выражение.</p><p>Для проверки правильности расчёта, используем обычный eval, заменив все скобки на круглые.</p><p><img alt="mole" height="267" src="http://lonelyelk.ru/images/uploads/0000/0027/mole.jpg" width="600" /></p><p>Сделав этот пример, я был доволен, как стадо слонов, но потом решил проверить, а что будет, если скобки расставлены неправильно?</p><pre><code class='ruby'>str = "1 + 2 * (3 - 4 / {5 + 6} + [7 - 8 * (9 + 10 * 11) + 12 * {13 - 14]} + 15) + 16 * (17 + 18)"
re = %r{
(?<fill>[0-9+\-*/\s]+){0}
(?<expression>\g<fill>*\g<brackets>\g<fill>*|\g<fill>){0}
(?<braces>\{\g<expression>+\}){0}
(?<squarebrackets>\[\g<expression>+\]){0}
(?<parentheses>\(\g<expression>+\)){0}
(?<brackets>\g<braces>|\g<squarebrackets>|\g<parentheses>)
}x
str =~ re</code></pre><p>И я не смог дождаться завершения работы оператора <b>=~</b> для такого длинного выражения. Это, конечно, неприятно. В причины я вникал не особо, но похоже, это связано с поведением недетерминированной машины Тьюринга. По крайней мере вот <a href="http://redmine.ruby-lang.org/issues/3679">ответ на похожую проблему</a>. Для нас это всего лишь означает, что проверять правильность расстановки скобок нужно отдельно и другим способом. Чем я предлагаю заняться пытливому читателю самостоятельно.</p><h2>Материалы для самостоятельного изучения</h2><ol><li><a href="https://github.com/lonelyelk/lonelyelk_code/tree/master/recursive_regexp">Исходный код статьи</a>.</li><li><a href="http://www.igvita.com/2011/02/03/new-ruby-19-features-tips-tricks/">Новый синтаксис и прочие вкусняшки в руби 1.9</a>. Для тех, кто заметил <b>=-></b>.</li><li><a href="http://jimneath.org/2010/01/04/cryptic-ruby-global-variables-and-their-meanings.html">Глобальные переменные с непонятными именами</a>. Для тех, кто заметил <b>$~</b>.</li><li><a href="http://www.regular-expressions.info/ruby.html">Ещё немного базовых приёмов в регулярных выражениях руби</a>.</li></ol>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/422010-11-05T10:18:11+00:002010-11-05T10:18:11+00:00Ротация логов рельсового приложения<p><img alt="hay roller" height="337" src="http://lonelyelk.ru/images/uploads/0000/0021/hay_roller.jpg" width="600" /></p><h2>Введение</h2><p>Это уже давно известная тема, и я не претендую на открытие Америки, но для себя зафиксирую это знание.</p><p>Даже если вы используете капистрано для выкладывания проекта в сеть, логи приложения хранятся в одном и том же месте (папка <i>shared/log</i> и разрастаются до огромных размеров. Можно, конечно, запускать после каждого обновления файлов проекта комманду: </p><pre><code>rake log:clear</code></pre><p>Но есть более цивилизованные методы. Тем более, после определённого времени код проекта начинает обновляться всё реже и реже.</p><h2>С помощью системы</h2><p>Существует прекрасный системный инструмент, который назвается <b>logrotate</b>. С его помощью архивируются логи апача, баз данных и даже менеджера пакетов.</p><p>Чтобы организовать это удовольствие для своего проекта нужно создать файл <b>/etc/logrotate.d/my_project</b>:</p><pre><code>/path/to/my_project/shared/log/*.log {
weekly
missingok
rotate 10
nomail
compress
delaycompress
sharedscripts
postrotate
touch /path/to/my_project/current/tmp/restart.txt
endscript
}</code></pre><ol><lh>Здесь написано:</lh><li><i>weekly</i> — разбивать лог еженедельно;</li><li><i>missingok</i> — не выходить с ошибкой, если файла нет;</li><li><i>rotate 10</i> — хранить 10 предыдущих томов;</li><li><i>nomail</i> — не высылать удаляемые тома на электронную почту;</li><li><i>compress</i> — архивировать;</li><li><i>delaycompress</i> — архивировать не сразу, т.к. после переименования файла и до перезапуска пэссенджера логи пишутся в тот же переименованный файл;</li><li><i>sharedscripts</i> — запускать скрипт один раз для всех логов по маске;</li><li><i>postrotate...endscript</i> — скрипт, который нужно запустить после ротации: в данном случае перезапустить пэссенджер.</li></ol><p></ol><p>Файлом должен владеть <i>root:root</i>. Теперь можно проверить и запустить принудительно, убедившись, что наш файл включается в общий список:</p><pre><code>sudo logrotate -dv /etc/logrotate.conf
sudo logrotate -fv /etc/logrotate.conf</code></pre><h2>С помощью руби</h2><p>В руби есть встроенный метод ротации логов. Достаточно в файе <b>config/environment.rb</b> написать внутри блока <i>Rails::Initializer.run</i> один из вариантов:</p><pre><code class='ruby'>config.logger = Logger.new(config.log_path, "weekly")</code></pre><p>или</p><pre><code class='ruby'>config.logger = Logger.new(config.log_path, 10, 1.megabyte)</code></pre><p>Первый вариант осуществляет ротацию раз в неделю, а второй — по достижении файлом размера в 1 мегабайт и хранит 10 старых томов. Только в данном случае архивацию, если она нужна, придётся организовывать отдельно.</p><h2>Было бы интересно</h2><p>Для логротейт можно написать такую маску, которая бы включала в себя все логи всех рельсовых проектов. Но мне неизвестен способ потом написать такой скрипт, который бы перезапускал именно те проекты, для которых была сделана ротация. Например, если логротэйт не нашёл нужного файла, то и скрипт не запустит. А если мы указываем путь типа <i>/path/to/**/shared/*.log</i>, то и скрипт должен перебирать все эти проекты и создавать или просто менять дату редактирования файлов <i>restart.txt</i>. Или можно просто перезапускать апач.</p><h2>Материалы для самостоятельного изучения</h2><ol><li><a href="http://linuxcommand.org/man_pages/logrotate8.html">Документация logrotate</a> (<a href="http://www.opennet.ru/man.shtml?topic=logrotate&category=8&russian=0">по-русски</a>)</li><li><a href="http://maintainablesoftware.com/articles/rails_logging_tips">Что ещё можно делать с логами приложения на рельсах</a></li></ol>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/412010-10-20T16:20:11+00:002010-10-20T16:20:11+00:00Определение, запущен ли процесс<h2>Пролог</h2><p>Ого! Уже три месяца я ничего не писал в этот блог! Лето выдалось жаркое не только на погоду. Поскольку летом погода лучше, а световой день длиннее, было много работы. Причём работы связанной с поддержкой того, что уже и так нормально функционировало в прошлом сезоне. Ничего серьёзно нового не писалось активно, а значит и захватывающих сюжетов для статей не находилось.</p><p>Но теперь у меня появилась возможность писать кое-что новое. Поэтому есть, что рассказать.</p><p><img alt="to feed or not to feed" height="337" src="http://lonelyelk.ru/images/uploads/0000/0019/tofeedornottofeed.jpg" width="600" /></p><h2>Введение</h2><p>Если вы любите процессы-демоны, как люблю их я, то, возможно, перед вами уже возникала задача определить, запущен ли уже такой демон, перед тем как создавать дочерний процесс. Об этом и будет сегодняшняя статья.</p><h2>Баш в помощь</h2><p>Предположим, что у нас есть простейший демон. Хорошо бы имя у него было уникальное, чтобы можно его потом было отыскать. Файл <b>uniq_name_simple_daemon</b>:</p><pre><code class='ruby'>#!/usr/bin/env ruby
pid = fork do
begin
running = true
Signal.trap("TERM") do
running = false
end
while running
sleep 0.01
end
rescue Exception => e
puts e.to_s
puts e.backtrace.join "\n"
ensure
exit!
end
end</code></pre><p>Мы всегда можем запускать с помощью другого скрипта, например на баше (<b>simple_daemon_runner.sh</b>):</p><pre><code>#!/bin/bash
if ps ax | grep uniq_name_simple_daemon | grep -vq grep
then
echo "uniq_name_simple_daemon is already running"
else
echo "starting uniq_name_simple_daemon"
./uniq_name_simple_daemon
fi</code></pre><p>На подобной команде будут базироваться все наши последующие методы. Тут, если кто не понял, мы фильтруем вывод <i>ps ax</i> сначала ища там имя нашего скрипта, а затем исключая из списка сам процесс поиска (команду <i>grep</i>). Ключ <i>q</i> позволяет нам получить код выхода, не выводя ничего на экран. То есть если строчка найдена, то запускаем первый блок, если нет, то второй.</p><p>Можно сделать такой же скрипт для остановки процесса (<b>simple_daemon_stopper.sh</b>):</p><pre><code>#!/bin/bash
pid=$(ps ax | grep uniq_name_simple_daemon | grep -v grep | awk '{ print $1; }')
if [[ -n $pid ]]
then
echo "stopping uniq_name_simple_daemon"
kill -TERM $pid
else
echo "nothing to stop"
fi</code></pre><p>Конечно же, при таком раскладе всегда есть возможность запустить нашего демона без помощи скриптов. И тогда проверка делаться не будет. В таком случае полезно проверять, запущен ли процесс уже внутри самого руби, перед тем, как отпочковать дочерний процесс.</p><h2>Сам себе хозяин</h2><p>В данном случае задача сводится к проверке наличия в памяти ещё одного процесса с таким же именем кроме текущего. Так же нужно уметь останавливать процесс с помощью того же файла. Вот, какое решение получилось у меня (<b>uniq_name_auto_daemon</b>):</p><pre><code class='ruby'>#!/usr/bin/env ruby
ps_ax = `ps ax | grep #{File.basename(__FILE__)} | grep -v grep`.split("\n").map{ |l| l.strip.split(/\s+/) }.reject{ |l| l[0].to_i == Process.pid }
if ps_ax.any?
case ARGV[0]
when /stop/i
ps_ax.each do |l|
system "kill -TERM #{l[0]}"
end
when /kill/i
ps_ax.each do |l|
system "kill -KILL #{l[0]}"
end
else
puts "#{File.basename(__FILE__)} is already running. If you want to stop it, run './#{File.basename(__FILE__)} stop|kill'"
end
else
pid = fork do
begin
running = true
Signal.trap("TERM") do
running = false
end
while running
sleep 0.01
end
rescue Exception => e
puts e.to_s
puts e.backtrace.join "\n"
ensure
exit!
end
end
end</code></pre><p>Во-первых, обходимся одним файлом, который никак иначе не запустить. Во-вторых, нигде не нужно хардкодить его имя. По-моему, очень удобно.</p><h2>Оффтопик</h2><p>С одной стороны, когда я пишу текст, то мне удобнее писать все термины по-русски и склонять их: «демоны», «руби», «баш», но с другой стороны это не поможет тому, кто будет искать решение похожей задачи.</p><p>Внутри примеров кода — наоборот, удобнее писать комментарии и тексты по-английски, чтобы не переключать раскладку, но как-то это не очень соответствует русскоязычном блогу.</p><p>Что же делать? :)</p><h2>Материалы для самостоятельного изучения</h2><p><a href="http://github.com/lonelyelk/lonelyelk_code/tree/master/run_detect/">Полный код статьи на гитхабе.</a></p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/402010-07-21T11:28:18+00:002010-07-21T11:28:18+00:00Управление LEGO NXT Mindstorms с помощью телефона HTC Desire на Android<p><img alt="summer in the city" height="337" src="http://lonelyelk.ru/images/uploads/0000/0017/summerinthecity.jpg" width="600" /></p><h2>Введение</h2><p>Лето в этом году выдалось жаркое, но пока ни о каких отпусках речи не идёт. Пока солнечно, тепло и сухо — самое время снимать панорамы улиц (или ГрадоГлядъ, как я это называю). Однако, не смотря на хорошую плотность работы, ничего принципиально нового, о чём интересно было бы рассказать, не произошло. Поэтому я покажу концепт, над которым работаю уже какое-то время, и который лежит в стороне от моих основных языков программирования.</p><h2>Дистанционное управление</h2><p>Итак, у меня уже давно есть конструктор LEGO Mindstorms, который довольно бодро взаимодействует через bluetooth с компьютером или с таким же конструктором.</p><p>А в апреле я обновил телефон на HTC Desire под управлением операционной системы Андроид, которая уже давно привлекла моё внимание, но всё руки не доходили.</p><p>В итоге появились повод, возможность и желание «пощупать» Андроид. И вот, что из этого получилось:</p><p><object width="560" height="340"><param name="movie" value="http://www.youtube.com/v/VIkqhJL9DoQ&hl=en_US&fs=1?rel=0"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/VIkqhJL9DoQ&hl=en_US&fs=1?rel=0" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="560" height="340"></embed></object></p><h2>Секреты?</h2><p>Для тех, кто соберётся заниматься чем-то подобным, могу поделиться небольшим секретом. Стандартный метод получения <i>BluetoothSocket</i>, рекомендованный командой Гугла, не работает, а нужен небольшой хак:</p><pre><code>/* Не работает:
* socket = device.createRfcommSocketToServiceRecord(MY_UUID)
*/
Method m = device.getClass().getMethod("createRfcommSocket",
new Class[] { int.class });
socket = (BluetoothSocket)m.invoke(device, Integer.valueOf(1));</code></pre><p>Вот и всё, что я могу рассказать :) Если я ещё буду писать про Андроид, то придётся сделать подсветку кода для него.</p><h2>Материалы для самостоятельного изучения</h2><p><a href="http://developer.android.com/guide/topics/wireless/bluetooth.html">Руководство для разработчиков под Андроид на тему bluetooth</a></p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/392010-06-17T11:36:28+00:002010-06-17T11:36:28+00:00Ещё два сценария работы с git: git stash и git bisect<p><img alt="library" height="337" src="http://lonelyelk.ru/images/uploads/0000/0015/library.jpg" width="600" /></p><h2>Введение</h2><p>Моя <a href="http://git-scm.com/">любимая система контроля версий</a> имеет огромное количество инструментов. Как-то раз я участвовал в опросе, после которого выяснилось, что даже из самых популярных инструментов я использую от силы 10%.</p><p>Но иногда возникают ситуации, единственно продуктивным выходом из которых бывает изучение и использование нового для себя инструмента. О двух таких случаях я сегодня и расскажу.</p><h2>Внезапные просьбы: git stash</h2><p>Бывает так, что пока я работаю над нововведениями в программу, текущая стабильная её версия активно используется. При активном использовании, конечно же, могут возникнуть ошибки или пожелания что-то изменить. Бывает так, что при этом я нахожусь в середине тестирования какого-то новшества, и всё настолько сыро, что я даже не могу сделать коммит.</p><p>Итак, я нахожусь в середине правок на ветке <i>extremely_experimental</i>, а мне необходимо внести правки в ветку <i>master</i>. Вот, как это делается:</p><pre><code>git stash save
git checkout master</code></pre><p>После первой команды всё наши изменения, которые нельзя было закоммитить, сохранены и текущая ветка приведена в состояние до правок. После этого мы можем сменить ветку и внести наши правки. После того, как ошибки исправлены, нововведения сделаны и тесты проходят, мы можем вернуться обратно к нашим правкам.</p><p>Но скорее всего все или некоторые из сделанных изменений понадобятся нам в нашей экспериментальной ветке. После перехода на неё:</p><pre><code>git checkout extremely_experimental</code></pre><p>Если нам нужны все изменения, то:</p><pre><code>git merge master</code></pre><p>Если только некоторые, то:</p><pre><code>git cherry-pick ...</code></pre><p>После этого вернём наши правки:</p><pre><code>git stash pop</code></pre><p>Если возникли конфликты, то правим их и делаем:</p><pre><code>git stash drop
git reset --mixed</code></pre><p>Последнее нужно для того, чтобы вынести наши правки из индекса, т.к. при конфликте они не выходят оттуда самостоятельно.</p><p>Конечно же таких незавершённых правок может быть несколько, но это я оставлю на самостоятельное изучение пытливому читателю.</p><h2>Неизвестно, когда сломалось: git bisect</h2><p>Бывает так, что вдруг обнаруживается ошибка, про которую точно известно, что давным давно её не было. Так бывает в больших проектах, в непокрытых тестами областях. Бывает так, что обнаружить, в чём же дело, быстро не удаётся.</p><p>Хорошая новость в том, что это и не обязательно. Нужно просто начать процесс:</p><pre><code>git bisect start
git bisect bad</code></pre><p>Так мы обозначили, что текущий коммит содержит ошибку. После этого, либо мы знаем, как называется коммит, в котором ошибки ещё не было, или находим его.</p><pre><code>git bisect good v2.3.1</code></pre><p>или</p><pre><code>git checkout ...
git bisect good</code></pre><p>После этого за нас всё будет делать <i>git</i>. Он будет перемещать нас по истории, а мы будем проверять, есть эта ошибка или нет, и сообщать об этом:</p><pre><code>git bisect good</code></pre><p>или</p><pre><code>git bisect bad</code></pre><p>В конце концов нам сообщат, какой именно коммит всё поломал. Название инструмента подсказывает нам, что на тестирование нам всегда предоставляется коммит, который находится посередине между плохим и хорошим. Таким образом, мы просматриваем не все <i>N</i> коммитов в истории ошибки, а всего лишь <i>log<sub>2</sub>N</i>.</p><p>После того, как мы выяснили, в чём причина, убрать следы, которые оставил после себя <i>git bisect</i> можно так:</p><pre><code>git bisect reset</code></pre><p>А какими инструментами <i>git</i> пользуетесь вы?</p><h2>Материалы для самостоятельного изучения</h2><ol><li><a href="http://www.kernel.org/pub/software/scm/git/docs/v1.7.0.6/git.html">Документация по git</a></li><li><a href="http://www.kernel.org/pub/software/scm/git/docs/git-stash.html">git stash</a></li><li><a href="http://www.kernel.org/pub/software/scm/git/docs/git-bisect.html">git bisect</a></li></ol>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/382010-06-01T23:40:47+00:002010-06-01T23:40:47+00:00Работа над ошибками<p><img alt="Mistakes" height="337" src="http://lonelyelk.ru/images/uploads/0000/0013/mistakes.jpg" width="600" /></p><h2>Введение</h2><p>Основной целью этого блога является сбор в одном удобном месте необходимых мне по работе знаний и фишек. Однако, именно потому что это активно используемые в работе решения, со временем появляется более продуктивный или более правильный способ сделать то, о чём написано почти в каждой статье.</p><p>Иногда я просто ошибаюсь. Трудно представить что-то более полезное для опыта, нежели набивание шишек. Будет хорошо, если проведение работ над ошибками станет доброй традицией. Итак, в этом году.</p><h2>git hooks</h2><p>Недостатков <a href="http://lonelyelk.ru/posts/37">скрипта для удаления пробелов в концах строк</a> нашёл два:</p><ol><li>Скрипт без нужды дёргает ни в чём не повинные файлы, потому что <b>\s</b> соответствует и символу конца строки, который там всегда есть.</li><li>Скрипт не содержит решения для выбора всех текстовых файлов проекта.</li></ol><p></ol><p>Вот хороший скрипт:</p><pre><code class='ruby'>#!/usr/bin/env ruby
`git grep -I --name-only -e ""`.split("\n").each do |p|
lines = File.readlines(p).map(&:chomp)
if lines.inject(false) { |memo, l| l.gsub!(/\s+$/, "") || memo }
File.open(p, "w") do |f|
f.puts lines.join("\n")
end
puts "Removed trailing spaced from '#{p}'"
system "git add #{p}"
end
end</code></pre><p>Так же по совету Дмитрия в комментариях добавил <a href="http://github.com/lonelyelk/workspace/blob/master/git-hooks/post-commit">скрипт для проверки счастливого коммита</a>.</p><h2>Работа с версией в (ai)rake</h2><p>Совершенно очевидная ошибка в <a href="http://lonelyelk.ru/posts/36">примере про работу с версиями air-приложения в rake</a>. Когда увеличивается более старшая часть версии, то все младшие должны обнуляться:</p><pre><code class='ruby'>namespace :version do
[:major, :minor, :patch].each_with_index do |subv, index|
desc "Bump #{subv} in version"
task :"bump_#{subv}" do
unless `git status` =~ /nothing to commit/
raise "There are uncommitted changes. Failed to proceed."
end
appxml = YAML.load_file('airake.yml')["appxml_path"]
str = File.read(appxml)
msg = nil
new_version = nil
if str.gsub! /<version>(.*)<\/version>/ do |matched|
old_version = $1
major, minor, patch = old_version.split(".").map(&:to_i)
eval("#{subv} += 1")
new_version = [major, minor, patch].fill(0, index+1).join(".")
msg = "Version bump #{old_version} => #{new_version}"
puts msg
"<version>#{new_version}</version>"
end.nil?
raise "Cannot detect current version.\nMake sure appxml file contains <version>X.X.X</version> tag."
else
File.open(appxml, "w") do |f|
f.write str
end
puts `git commit -am "#{msg}"`
puts `git tag v#{new_version}`
end
end
end
end</code></pre><p>Теперь <i>rake version:bump_minor</i> делает из 0.1.6 не 0.2.6, а 0.2.0, как и должно быть.</p><h2>Мимоходом</h2><p>Тем временем я сменил тарифный план у своего провайдера на <a href="http://mediatemple.net/webhosting/ve/">(ve)</a>. И незаметно перенёс сайт. Посмотрим, как работает на собственном опыте. Работа по ssh, как была, так и осталась основным способом администрирования, а необходимость лазить в plesk пропала, потому что его теперь нет :)</p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/372010-05-26T21:42:02+00:002010-05-26T21:42:02+00:00Использование git hooks на руби в корыстных целях<p><img alt="Organizers" height="337" src="http://lonelyelk.ru/images/uploads/0000/0011/organizers.jpg" width="600" /></p><h2>Введение</h2><p>Как-то раз мне попался очень злой git-репозиторий, который отказывался работать, если я оставлял пробелы в конце строк. Есть такая версия, что git заточен под отправку патчей по почте, и что пробелы в концах строк могут навредить в таком процессе.</p><p>Тогда я просто отключил эту проверку, а недавно подумал, почему бы мне не использовать эти мощности в мирных целях.</p><h2>git hooks</h2><p>Для множества различных целей у git есть хуки. (Как бы их перевести нормально?) Они находятся в каждом репозитории в папке:</p><pre><code>.git/hooks</code></pre><p>И имеют говорящие названия. Используются они для соблюдения форматов и соглашений, для оповещений, для проверки и т.п. Почему бы не возложить на них корректорские функции?</p><h2>Использование pre-commit для удаления пробелов на концах строк</h2><p>Поскольку я, опять же, фанат руби, то и скрипты — благо есть такая возможность — напишу на руби. Создаём файлик <b>.git/hooks/pre-commit</b>:</p><pre><code class='ruby'>#!/usr/bin/env ruby
Dir.glob("*.{txt,rb}").each do |p|
lines = File.readlines(p)
if lines.inject(false) { |memo, line| line.gsub!(/\s+$/, "") || memo }
File.open(p, "w") do |f|
f.puts lines.join("\n")
end
system "git add #{p}"
end
end</code></pre><p>Как видно, этот скрипт ищет файлы <i>*.txt</i> и <i>*.rb</i> в корневом каталоге репозитория, и если в них есть пробелы в конце строк, перезаписывает их и добавляет в индекс для коммита.</p><p>Не забыть сделать его запускаемым:</p><pre><code>chmod +x .git/hooks/pre-commit</code></pre><p>Теперь у нас в распоряжении автоматический помощник-редактор, который удаляет пробелы в конце строк.</p><h2>Материалы для самостоятельного изучения</h2><p><a href="http://www.kernel.org/pub/software/scm/git/docs/githooks.html">Документация по git hooks</a></p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/362010-04-28T15:21:03+00:002010-04-28T15:21:03+00:00Использование Airake под Kubuntu<p><img alt="simple things" height="337" src="http://lonelyelk.ru/images/uploads/0000/0009/simple_things.jpg" width="600" /></p><h2>Введение</h2><p>Уже некоторое время назад обнаружил гениальный инструмент. Правда только недавно опробовал его на своих рабочих проектах и зафанател ещё больше. Подготовка к докладу на секции Яндекса про панорамы на РИФе не позволила мне поделиться этим ранее. Исправляю ошибку.</p><p>Я люблю руби. И, естественно, <i>rake</i>, как инструмент, продолжающий славные традиции make в руби и с помощью руби. Так же я питаю нежные чувства к <i>ActionScript</i>. Мне нравится <i>AIR</i>, который позволяет писать действительно кросс-платформенные приложения довольно быстро. Так же я неплохо отношусь к <i>TDD</i>, как к одному из способов разработки.</p><p>Какова же была моя радость найти инструмент, который всё это объединяет! Хотя ему уже пара лет, он по-прежнему прекрасен.</p><h2>Установка составляющих</h2><p>Предполагаю, что <i>ruby</i>, <i>rubygems</i> и <i>rake</i> уже установлены у тех, кто читает этот блог.</p><p>Далее, качаем и разархивируем куда-нибудь <a href="http://www.adobe.com/cfusion/entitlement/index.cfm?e=airsdk">Adobe AIR SDK</a> и <a href="http://www.adobe.com/cfusion/entitlement/index.cfm?e=flex4sdk">Adobe Flex SDK</a> (или <a href="http://www.adobe.com/ru/products/flex/flexdownloads/index.html#sdk">предыдущая версия</a>, если вы консерватор), а так же устанавливаем <a href="http://get.adobe.com/ru/air/">Adobe AIR Runtime</a>. Чтобы установить последний, после загрузки bin-файла нужно:</p><pre><code>chmod +x AdobeAIRInstaller.bin
sudo ./AdobeAIRInstaller.bin</code></pre><p>Теперь добавим в <i>PATH</i> пути к исполняемым файлам загруженных SDK. В <i>.bashrc</i> добавляем:</p><pre><code>export PATH="/path/to/air_sdk/bin:$PATH"
export PATH="/path/to/flex_sdk_4/bin:$PATH"</code></pre><p>Так же потребуется установить <i>java</i> для того, чтобы на ней работал компилятор:</p><pre><code>sudo apt-get install sun-java6-jre</code></pre><p>После установки, независимо от того, используете вы <i>Flex3</i> или <i>Flex4</i>, нужно переписать содержимое <i>AIR SDK</i> поверх <i>Flex SDK</i>. Мне не совсем понятен сакральный смысл этих действий, но иначе ничего не работает.</p><h2>Привѣтъ, Мiръ!</h2><p>Создание пустого air-приложения теперь просто:</p><pre><code>airake airake_hello_world</code></pre><p>Чтобы запустить его, однако, следует исправить в <i>src/AirakeHelloWorld-app.xml</i> и <i>test/Test-app.xml</i>:</p><pre><code>...
xmlns="http://ns.adobe.com/air/application/1.5"
...</code></pre><p>Если вы решили использовать <i>Flex4</i>, то вам необходимо отредактировать сгенерированное приложение, чтобы запустить его. Это связано с изменениями в стилях. Поэтому проще просто удалить всё содержимое тэга WindowedApplication в файле <i>src/AirakeHelloWorld.mxml</i>.</p><p>Про <a href="http://lonelyelk.ru/posts/15">использование <i>TDD</i> в <i>ActionScript</i></a> я уже писал, поэтому подробно останавливаться не буду. Для примера в код на <i>github</i> включён тривиальный тест. Запуск тестирования происходит привычным образом:</p><pre><code>rake test</code></pre><p>Документация, если вы пишете правильные комментарии <i>ASDoc</i>, тоже запускается привычным образом:</p><pre><code>rake docs</code></pre><p>Так же делается всё остальное: запуск приложения в отладочном режиме, генерирование сертификата, упаковка релиза приложения. Для того, чтобы это всё узнать, используйте:</p><pre><code>rake -T</code></pre><h2>Использование rake</h2><p>Конечно, вся прелесть <i>rake</i> не только в привычных и коротких командах для разработки, но и в том, что можно создавать свои сценарии. Например, вот как могла бы выглядеть работа с версиями приложения. Добавим в файл <i>raketasks/version.rake</i> следующий код:</p><pre><code class='ruby'>require 'yaml'
desc "Print out current version"
task :version do
if md = File.read(YAML.load_file('airake.yml')["appxml_path"]).match(/<version>(.*)<\/version>/)
puts "Current version is #{md[1]}"
else
raise "Cannot detect current version.\nMake sure appxml file contains <version>X.X.X</version> tag."
end
end
namespace :version do
[:major, :minor, :patch].each do |subv|
desc "Bump #{subv} in version"
task :"bump_#{subv}" do
unless `git status` =~ /nothing to commit/
raise "There are uncommitted changes. Failed to proceed."
end
appxml = YAML.load_file('airake.yml')["appxml_path"]
str = File.read(appxml)
msg = nil
new_version = nil
if str.gsub! /<version>(.*)<\/version>/ do |matched|
old_version = $1
major, minor, patch = old_version.split(".").map(&:to_i)
eval("#{subv} += 1")
new_version = [major, minor, patch].join(".")
msg = "Version bump #{old_version} => #{new_version}"
puts msg
"<version>#{new_version}<\/version>"
end.nil?
raise "Cannot detect current version.\nMake sure appxml file contains <version>X.X.X</version> tag."
else
File.open(appxml, "w") do |f|
f.write str
end
puts `git commit -am "#{msg}"`
puts `git tag v#{new_version}`
end
end
end
end</code></pre><p>А в <i>Rakefile</i> соответственно:</p><pre><code class='ruby'># Custom rake tasks
Dir.glob("raketasks/*.rake").each { |rf| load rf }</code></pre><p>Теперь мы можем привычным образом работать с версиями приложения (а версии эти потом будут распознаваться установщиком обновлений):</p><pre><code>rake version
rake version:bump_major
rake version:bump_minor
rake version:bump_patch</code></pre><p>И это не предел!</p><h2>Материалы для самостоятельного изучения</h2><ol><li><a href="http://github.com/lonelyelk/lonelyelk_code/tree/master/airake_hello_world/">Полный код статьи на github</a></li><li><a href="http://airake.rubyforge.org/">Инструкция по работе с airake</a>, которая во многом повторена в этой статье с добавлением манипуляций, чтобы всё заработало.</li><li><a href="http://opensource.adobe.com/wiki/display/flexunit/FlexUnit">Документация по FlexUnit</a>. Не уверен, что в поставке airake идёт самая последняя версия, но ничего не мешает написать rake task для обновления версии FlexUnit :)</li><li><a href="http://rake.rubyforge.org/">Документация по rake</a></li></ol>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/352010-04-07T12:59:47+00:002010-04-07T12:59:47+00:00Немного о $SAFE<p><img alt="secure code" height="337" src="http://lonelyelk.ru/images/uploads/0000/0007/se_ure_code.jpg" width="600" /></p><h2>Введение</h2><p>Совершенно не по работе заинтересовался переменной <b>$SAFE</b> и её ролью в жизни современного разработчика. Оказалось, что всё нужно проверять самому.</p><h2>Нежная безопасность</h2><p>Для тестирования возможностей на разных уровнях безопасности собрал небольшую программку. Она просит ввести имя файла, делая строковую переменную небезопасной, и пытается что-то с этим всем сделать.</p><pre><code class='ruby'>print "child: "
child = gets.chomp
puts "child tainted: #{child.tainted?}"
(0..4).to_a.each do |i|
puts "SAFE: #{i}"
$a = "safe"
th = Thread.new do
$SAFE = i
child_copy = child.dup
Thread.current[:out] = ""
begin
load child_copy
Thread.current[:out] += "1. Child loaded\n"
rescue SecurityError => e
Thread.current[:out] += "1. Security error: #{e.to_s}\n"
begin
child_copy.untaint
load child_copy
Thread.current[:out] += "2. Child untainted and loaded\n"
rescue SecurityError => e
Thread.current[:out] += "2. Security error: #{e.to_s}\n"
begin
Thread.current[:out] += "3. Read from file '#{child_copy}': '#{File.read(child_copy)}'\n"
rescue SecurityError => e
Thread.current[:out] += "3. Security error: #{e.to_s}\n"
begin
Thread.current[:out] += "4. Read from untainted file: '#{File.read("child.rb")}'\n"
rescue SecurityError => e
Thread.current[:out] += "4. Security error: #{e.to_s}\n"
end
end
end
end
begin
$a = "modified"
Thread.current[:out] += "5. Global variable modified: $a = '#{$a}'\n"
rescue SecurityError => e
Thread.current[:out] += "5. Security error: #{e.to_s}\n"
end
begin
Dir.mkdir "test"
Thread.current[:out] += "6. Created directory 'test': #{File.exist?("test")}\n"
Dir.rmdir "test"
rescue SecurityError => e
Thread.current[:out] += "6. Security error: #{e.to_s}\n"
end
begin
Thread.current[:out] += "7. Dir glob: #{Dir.glob(File.join("..", "*")).inspect}\n"
rescue SecurityError => e
Thread.current[:out] += "7. Security error: #{e.to_s}\n"
end
begin
Thread.current[:out] += "8. System ls output: '#{`ls`.chomp}'"
rescue SecurityError => e
Thread.current[:out] += "8. Security error: #{e.to_s}\n"
end
end
th.join
puts "Global variable: $a = '#{$a}'"
puts th[:out] if th[:out]
end</code></pre><p>Конструкция со <i>Thread.current[:out]</i> используется потому, что для <i>$SAFE >= 4</i> нельзя ничего писать ни в какие устройства вывода.</p><p>Вроде бы всё логично. Первый уровень годится для умеренного карантина внешних данных. При желании их можно и расколдовать. Второй уровень запрещает изменения в файловой системе. Третий уровень похож на осаду с постоянным подозрением на шпионаж. Все созданные объекты считаются небезопасными. А четвёртый уровень — это самое близкое к песочнице (sandbox) в руби, что что есть.</p><p>Кстати, когда ещё github работал как репозиторий библиотек, спецификация gemspec выполнялась там под <i>$SAFE = 3</i>. Для разработчиков это выливалось в то, что нужно было перечислять все файлы своей библиотеки вручную вместо использования какого-нибудь листинга.</p><h2>Суровый гайдлайн</h2><p>Конечно же, только использование <b>$SAFE</b> не убережёт от действительно настойчивой атаки или блокирующего кода. Например:</p><pre><code class='ruby'>Thread.new do
$SAFE = 2
class String
def ==(other_string)
true
end
end
end.join
puts "string modified: #{'a' == 'b'}"</code></pre><p>И это на втором уровне! А на третьем открыть класс тоже можно, но вызов перегруженного оператора будет вызывать <i>SecurityError</i>.</p><p>На сегодняшний момент эту концепцию безопасности можно считать сырой. Актуальное поведение руби 1.8 слегка отклоняется от описаний, что я нашёл. Поведение в 1.9 изменилось, но подробно нигде не описано (я не нашёл).</p><p>Это не значит, что этой переменной нет применения в жизни прогрессивного человечества. Адекватное текущему состоянию применение — это гайдлайн при разработке. Руководство для программистов, которое само следит за своим исполнением. Жестковато, но зато действенно. :)</p><h2>Материалы для самостоятельного изучения</h2><ol><li><a href="http://github.com/lonelyelk/lonelyelk_code/tree/master/safe/">Код примеров в статье на github</a></li><li>Старая, но <a href="http://ruby-doc.org/docs/ProgrammingRuby/html/taint.html">самая подробная документация по $SAFE</a></li><li>Просто дополнительно: <a href="http://www.tutorialspoint.com/ruby/ruby_quick_guide.htm">шпаргалка по руби</a></li></ol>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/342010-03-24T19:07:37+00:002010-03-24T19:07:37+00:00Процесс приёма правок в проекте с открытыми исходниками<p><img alt="opensource" height="337" src="http://lonelyelk.ru/images/uploads/0000/0005/opensource.jpg" width="600" /></p><h2>Введение</h2><p>Для работы в проекте с открытыми исходниками весьма удобна распределённая система контроля версий. Я использую <a href="http://git-scm.com/">git</a>. Понятно, что есть процесс с использованием патча, высылаемого по почте, но этот процесс не является эксклюзивным для распределённой системы контроля версий. Поэтому я опишу процесс с так называемым pull request.</p><h2>Постановка задачи</h2><p>Пишет мне некто Tallak Tveide, сообщая, что он сделал копию моего проекта <a href="http://github.com/tallakt/gphoto4ruby">у себя на github</a> и внёс несколько правок, которые ему были необходимы, и от которых другие ребята, пользующиеся <a href="http://github.com/lonelyelk/gphoto4ruby">этой библиотекой</a> только выиграют. Ветка, в которой находятся нужные мне правки, называется eos_40D_bugs. Это довольно кстати, что нашёлся человек с Кэноном, потому что я испытываю всё на Никонах :)</p><p>Каковы же мои действия?</p><h2>Решение</h2><p>Заходим в наш локальный рабочий репозиторий и добавляем новый источник правок:</p><pre><code>git remote add tallakt git://github.com/tallakt/gphoto4ruby</code></pre><p>Теперь рассмотрим правки:</p><pre><code>git fetch tallakt eos_40D_bugs:develop</code></pre><p>Эта команда заберёт из репозитория tallakt с ветки eos_40D_bugs исправления и создаст локальную версию в локальной ветке develop. Чтобы увидеть исправления:</p><pre><code>git diff develop</code></pre><p>Что выдаст нам исправления относительно текущей ветки.</p><pre><code>git checkout develop</code></pre><p>Чтобы работать с правками и тестировать то, что получилось.</p><p>Если я пока не готов сливать исправления с основной веткой master, но хочу ещё поработать с этим из разных мест, то мне нужно создать ветку develop в моём центральном репозитории на github, который относительно локальной копии у меня обычно называется origin.</p><pre><code>git push origin develop</code></pre><p>Это создаст ветку develop на удалённом репозитории, с которой я потом смогу работать из другого локального репозитория, выполнив:</p><pre><code>git pull origin develop</code></pre><p>После того, как я доволен изменениями и хочу сделать официальный релиз:</p><pre><code>git merge master
git branch -d develop
git push origin master
git push origin :develop</code></pre><p>Первая команда, предполагая, что текущая ветка — develop, сливает её в master. Вторая команда удаляет локальную ветку develop. Третья команда отправляет изменения в ветку master на центральном репозитории. Четвёртая команда удаляет ветку develop на центральном репозитории.</p><h2>Материалы для самостоятельного изучения</h2><ol><li><a href="http://help.github.com/">Несколько шпаргалок от github</a></li><li><a href="http://www.kernel.org/pub/software/scm/git/docs/">Дельная документация по git</a></li></ol><p></ol><h2>Послесловие</h2><p>Как вы заметили, в этом году мои статьи сопровождаются прекраснейшими тематическими картинками авторства <a href="http://irtroit.com/">Ирины Троицкой</a> — моей прекрасной супруги. Её перу также принадлежит дизайн сайта и логотипа.</p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/332010-02-26T10:47:47+00:002010-02-26T10:47:47+00:00Вернулся<p><img alt="back from holidays" height="337" src="http://lonelyelk.ru/images/uploads/0000/0003/backfromholidays.jpg" width="600" /></p><p>Давненько я не обновлял этот блог :)</p><p>Всё дело в том, что где-то там был Новый Год, в который я торжественно посетил родителей. До и после Нового Года была сдача <a href="http://clubs.ya.ru/company/replies.xml?item_no=22915&ncrnd=1404">панорам Санкт-Петербурга</a> и ещё одного пока не скажу какого города.</p><p>Когда съёмки 2009 года были сданы, выяснилось, что полным ходом идёт зима. А у нас не так много городов, которые можно красиво снимать зимой. Тогда мы отправились всем составом в тёплые края, в месячный отпуск.</p><p>Теперь, когда я снова в Москве, этот блог вернётся к своему обычному режиму. Спасибо всем, кто был со мной в прошлом году. Этот год обещает быть очень интересным.</p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/322009-12-15T13:46:17+00:002009-12-15T15:34:16+00:00Одновременное использование двух версий руби на одной системе<h2>Введение</h2><p>Как молодой язык с неутверждённой спецификацией, руби переживает подростковую болезнь, через которую большинство известных языков уже прошли. Есть новая более быстрая версия, на которую уже стоит переходить, но уже много написано на предыдущей, и так боязно всё ломать...</p><p>Поэтому необходимо найти удобный для себя способ (а лучше несколько) чтобы начать использовать руби 1.9.</p><h2>Постановка задачи</h2><p>Сейчас практически панацеей для использования более одной версии руби является <a href="http://rvm.beginrescueend.com/">rvm</a>. Очень удобно в использовании, полностью прозрачно, и позволяет иметь разные версии руби в разных окнах терминала.</p><p>Но недавно мне понадобилось скомпилировать wxRuby под свою систему (kubuntu 9.10 amd64), и rvm не справилась с этой задачей. По какой-то причине в момент компилляции были недоступны заголовки руби. Поэтому я решил поставить две версии руби более явно: одна системная (1.8.7) и одна в папке /opt (1.9.1). Причем все команды, связанные с руби 1.9 будут вызываться с суффиксом: <i>ruby1.9</i>, <i>irb1.9</i>, <i>gem1.9</i>, <i>rake1.9</i>.</p><p>Возможно, подобных инструкций уже полно, но мне будет удобнее, если я точно буду знать, где находится одна из них :) При всём этом, конечно, rvm продолжает работать. Мы никак ему не помешаем.</p><h2>Решение</h2><p>Сначала нужно поставить новый readline. Без него, когда мы будем использовать <i>irb1.9</i>, мы не сможем наслаждаться доступом к истории с помощью стрелок вверх-вниз и перемещаться по введенному тексту с помощью стрелок в стороны.</p><pre><code>sudo apt-get install libreadline5-dev</code></pre><p>Теперь хорошо бы вписать пути в наше окружение. В конце <b>~/.bashrc</b> добавим:</p><pre><code>export PATH=$PATH:/opt/bin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/lib</code></pre><p>Теперь следует скачать и разархивировать последнюю версию руби. Зайдя в папку скомпилировать и установить:</p><pre><code>./configure --prefix=/opt --enable-shared --program-suffix=1.9
make
sudo make install</code></pre><p>Теперь у нас есть две отдельных установки руби и сопутствующих инструментов. Единственное, что обе установки используют общие конфигурационные файлы: ~/.gemrc, ~/.irbrc и т.п., что вполне удобно. Также для обеих систем общей директорией джемов будет ~/.gem, куда будут устанавливаться библиотеки, запусти мы их установку без <i>sudo</i> (в случае с <i>sudo</i>, конечно же, директории установки различаются).</p><p>Так же я не нашёл быстрого способа добавить <i>/opt/bin</i> в переменную <b>PATH</b> для <i>sudo</i>. Поэтому в таких случаях пока использую полный путь. Например, первая команда, которую следует выполнить:</p><pre><code>sudo /opt/bin/gem1.9 update --system</code></pre><p>Потому что в пакете с руби идёт версия 1.3.1, а настоящие пацаны уже во всю используют 1.3.5.</p><h2>Материалы для самостоятельного изучения</h2><ol><li><a href="http://www.google.ru/search?hl=ru&rlz=1C1GGLS_ruRU291RU316&newwindow=1&q=install+multiple+ruby+versions">Всё об установке нескольких версий руби на одной системе</a></li><li><a href="http://www.ruby19orbust.com/">Проект «Используй руби 1.9 или вали!»</a></li></ol>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/312009-12-09T16:43:17+00:002009-12-09T16:43:17+00:00Забота о неблокировании потоков в руби<h2>Введение</h2><p>Сегодня будет блиц-молния, которой место, скорее в твиттере. Однако, мне необходимо развеять тучи, которые я сам же и нагнал. :)</p><h2>Постановка задачи</h2><p>Не так давно я писал о том, что при создании руби-оболочки вокруг библиотеки на си, легко получить <a href="http://lonelyelk.ru/posts/27">код, который блокирует все потоки приложения</a>. Можно ли с этим что-то сделать?</p><h2>Решение</h2><p>Если мы имеем чуть больше контроля над участками кода на си, которые выполняются долго (обычно в цикле), то можно добавить спасительный код в тело цикла:</p><pre><code class='cpp'>if (!rb_thread_alone()) rb_thread_schedule();</code></pre><p>Это позволит каждый раз при очередном витке передавать управление соседним потокам, если они есть, и сделает приложение более отзывчивым.</p><p>То есть, если мы сделаем точную копию функции из той статьи и добавим наш спасительный код внутрь <i>for</i>, то в выводе такой программы:</p><pre><code class='ruby'># coding: utf-8
require "block_thread.so"
t1 = Thread.new do
10.times { |i| puts i; sleep 0.1 }
end
t2 = Thread.new do
puts "Блокируем"
BlockThread.cycle
puts "Разблокируем"
end
t3 = Thread.new do
puts "Стараемся не блокировать"
BlockThread.cycle_with_schedule
puts "Закончили стараться"
end
t1.join
t2.join
t3.join</code></pre><p>Будет картина гораздо приятнее:</p><pre><code>Блокируем
0
Стараемся не блокировать
Разблокируем
1
2
3
4
5
Закончили стараться
6
7
8
9</code></pre><p>Заметьте, кстати, что два лишних вывода между «Блокируем» и «Разблокируем» благодаря передаче управления соседнимпотокам между <i>cycle</i> и <i>puts</i>.</p><p>Понятно, что соседние потоки тормозятся. Но теперь не на всё время, а только на исполнение одного витка цикла.</p><h2>Материалы для самостоятельного изучения</h2><p><a href="http://github.com/lonelyelk/lonelyelk_code/tree/master/threads/">Полный код статьи на github</a></p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/302009-11-18T23:16:00+00:002009-11-18T23:16:00+00:00Сборка руби-билиотеки в заданной среде<h2>Постановка задачи</h2><p>Как разработчику <a href="http://github.com/lonelyelk/gphoto4ruby">gphoto4ruby</a> мне приходится сталкиваться с особыми задачами. Связано это с тем, что этот gem является оболочкой поверх ещё одной библиотеки. И как у всякой более-менее развитой сторонней библиотеки, у libgphoto2 есть версия, распространяемая через системные репозитории и порты и есть, так сказать, последний писк моды (bleeding edge).</p><ol><lh>Отсюда вытекает необходимость:</lh><li>Иметь разные версии библиотеки не конфликтующие между собой, установленные не одной системе,</li><li>Компилировать свою руби-библиотеку под любую из версий.</li></ol><p></ol><h2>Установка двух gphoto2 :)</h2><p>Проделаю весь путь с самого начала. Для пущей целостности. Для начала установка из системного репозитория:</p><pre><code>sudo apt-get install libgphoto2-2-dev gphoto2
gphoto2 --version</code></pre><p>Теперь можно скачать нужную версию и установить её отдельно. Поскольку я в основном использую две версии, то версию из исходников нужно установить в <i>/opt</i>. Предположим, что исходники libgphoto2 и gphoto2 скачаны:</p><pre><code>tar zxvf libgphoto2-x.x.x.tar.gz
cd libgphoto2-x.x.x.tar.gz
./configure --prefix=/opt
make
sudo make install
tar zxvf gphoto2-x.x.x.tar.gz
cd gphoto2-x.x.x.tar.gz
./configure --prefix=/opt --with-libgphoto2=/opt
make
sudo make install
/opt/bin/gphoto2 --version</code></pre><p>Теперь мы имеем две библиотеки и две утилиты командной строки, поставленные раздельно и правильно залинкованные. Каждая утилита командной строки знает, где искать свою библиотеку. Надо, чтобы это же умел и gem</p><h2>Компиляция джема</h2><p>Если скачать исходник библиотеки, то можно проделать руками то, что делает команда <b>gem install</b>. Для создания <i>Makefile</i> используется утилита <b>mkmf</b>, которая входит в <b>ruby-dev</b> и с которой работает файл <i>extconf.rb</i>. В моём случае последовательность действий установщика такая:</p><pre><code>cd ext
ruby extconf.rb
make</code></pre><p>Теперь в папке ext мы имеем скомпилированную библиотеку (*.so или *.bundle в зависимости от системы). Установщик потом копирует её в папку lib, но мы пока остановимся. Мы можем посмотреть, какие другие библиотеки использует эта:</p><pre><code>ldd gphoto4ruby.so</code></pre><p>По выводу этой команды видно, что используется библиотека установленная из центрального репозитория. Теперь попробуем скомпилировать под версию «по последней моде». Поскольку я написал в extconf.rb</p><pre><code class='ruby'>dir_config("gphoto2")</code></pre><p>То это означает, что пользователю будет доступен целый ряд опций, позволяющих сказать компилятору, где искать libgphoto2. Попробуем:</p><pre><code>ruby extconf.rb --with-gphoto2-dir=/opt
make
ldd gphoto4ruby.so</code></pre><p>Но что это? Вывод показывает нам, что библиотека привязалась опять к тому, что установлено из репозиториев, а не тому, что в /opt. То есть компилятор, конечно, находит нужные ему заголовки (*.h), но ничего в них не говорит о том, где искать соответствующие им библиотеки. Об этом ему должны сказать мы:</p><pre><code>ruby extconf.rb --with-gphoto2-dir=/opt --with-dldflags="-Wl,-rpath,/opt/lib"
make
ldd gphoto4ruby.so</code></pre><p>Вуаля!</p><p>Теперь, собственно, главное. Как это сделать при установке джема. Чтобы передать ключи для extconf нужно задать их после дополнительного «--»:</p><pre><code>sudo gem i gphoto4ruby -- --with-gphoto2-dir=/opt --with-dldflags="-Wl,-rpath,/opt/lib"</code></pre><p>Вот такой экскурс в жизнь разработчиков библиотек. Как это звучало в школьные времена: «Спэтсыално дла джэма».</p><h2>Материалы для самостоятельного изучения</h2><p><a href="http://www.rubycentral.com/pickaxe/ext_ruby.html">Руководство по расширению руби с помощью C (см. главу про extconf.rb)</a></p>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/292009-11-11T20:46:05+00:002009-11-11T20:46:05+00:00Сравнения и неравенства в руби<h2>Постановка задачи</h2><p>Собрать в одном месте важные, на мой взгляд, особенности сравнений и неравенств в руби.</p><h2>Основа неравенств в руби</h2><p>Основным методом сравнения является <b><=></b>. Определив его, мы определяем все остальные операции, включив модуль <i>Comparable</i>:</p><pre><code class='ruby'>class MyComp
attr :value
include Comparable
def initialize(val)
@value = val
end
def <=>(other)
@value <=> other.value
end
end
v1 = MyComp.new(1)
v2 = MyComp.new(2)
puts v1 < v2 # > true
puts v1 <= v2 # > true
puts v1 > v2 # > false
puts v1 >= v2 # > false
puts v1 == v2 # > false</code></pre><p>Сам метод можно было бы описать как «возвращает -1, 0 или 1 в зависимости от того, меньше равен или больше объект, чей метод вызывается в сравнении с объектом переданным в качестве параметра». Но на самом деле, скорее, наоборот понятия «больше», «меньше» и «равен» определяются исходя из работы <b><=></b>.</p><p>Далее всё понятно и более ли менее очевидно для чисел, массивов и строк. Но есть и интересная особенность.</p><h2>Сравнение модулей и классов</h2><p>Сравнение для модулей и классов определено таким образом, что в результате мы знаем направление наследования или включение одного модуля другим:</p><pre><code class='ruby'>module T1
end
module T2
include T1
end
T3 = T1
class C1
end
class C2 < C1
end
C3 = C1
puts "T1 <=> T2: #{(T1 <=> T2).inspect}" # > 1
puts "T1 <=> T3: #{(T1 <=> T3).inspect}" # > 0
puts "C1 <=> C2: #{(C1 <=> C2).inspect}" # > 1
puts "C1 <=> C3: #{(C1 <=> C3).inspect}" # > 0
puts "C1 <=> T1: #{(C1 <=> T1).inspect}" # > nil
puts "T1 <=> C1: #{(T1 <=> C1).inspect}" # > nil
C3.send(:include, T1)
puts "после включения"
puts "C1 <=> T1: #{(C1 <=> T1).inspect}" # > -1
puts "T1 <=> C1: #{(T1 <=> C1).inspect}" # > 1</code></pre><p>Наследник или модуль, который включает другой модуль, меньше, чем родитель или включаемый модуль. Это видно даже из синтаксиса наследования.</p><h2>Равенство</h2><p>Существует три метода равенства: <b>==</b>, <b>eql?</b>, <b>equal?</b>. Последний из которых никогда не следует переопределять, т.к. он отвечает за идентичность. Первые же два обычно работают одинаково. Канонический пример различия из документации:</p><pre><code class='ruby'>3 == 3.0 # > true
3.eql? 3.0 # > false</code></pre><p>Что лишь свидетельствует о том, что <b>==</b> проводит конвертацию чисел перед сравнением. Обычно <b>==</b> соответствует случаю, когда <b><=></b> возвращает 0.</p><h2>Сравнение case...when</h2><p>Все мы знаем, что в <b>case...when</b> оператор сравнения — это <b>===</b>. В большинстве случаев он эквивалентен равенству из предыдущего параграфа. Но если равенство симметрично</p><pre><code class='ruby'>(a.==(b)) == (b.==(a))</code></pre><p>И если это не так, то это можно считать ошибкой. То <b>===</b> вовсе не обязано таковым быть. Нужно помнить, что в конструкции <b>case...when</b> вызывается метод сравнения объекта, стоящего после <b>when</b>, а в качестве параметра ему передаётся объект, стоящий после <b>case</b>:</p><pre><code class='ruby'>puts String === "строка" # > true
puts "строка" === String # > false
puts /ок/ === "строка" # > true
puts "строка" === /ок/ # > false
puts (1..10) === 5 # > true
puts 5 === (1..10) # > false</code></pre><h2>Материалы для самостоятельного изучения</h2><ol><li><a href="http://github.com/lonelyelk/lonelyelk_code/tree/master/compare/">Полный код статьи на github</a></li><li><a href="http://blog.rubybestpractices.com/posts/rklemme/018-Complete_Class.html">Что нужно помнить, создавая свой объект руби</a></li></ol>Sergey Kruksergey.kruk@gmail.comtag:lonelyelk.ru,2008:Post/282009-10-28T13:03:53+00:002009-10-28T13:03:53+00:00Удалённые вызовы через систему распределённых объектов в руби (dRuby)<h2>Введение</h2><p>Некоторое время назад я писал о <a href="http://lonelyelk.ru/posts/16">создании подпроцессов на руби</a>. В числе прочего один из вопросов был об общении между собой демона и родительского процесса. Об одном из методов пойдёт речь сегодня</p><h2>Постановка задачи</h2><p>Не только программисты знают, что важна цель коммуникации. :) Если цель общения между основным процессом и демоном в том, чтобы вызывать методы на объектах друг друга, до давайте на этом и сосредоточимся.</p><h2>Решение: DRb</h2><p>Для удалённого обращения с объектами существует стандартная руби-библиотека <b>dRuby</b>, в которой находится модуль <b>DRb</b>, который мы и будем использовать. Ничего устанавливать не нужно. Согласно документации, совершенно прозрачным образом можно вызвать методы на удалённом объекте даже на другой машине. Объекты и ссылки на них передаются в формате <b>Marshal</b>.</p><p>Ну, довольно теории! Перейдём к практике. Для эмуляции параллельных процессов (возможно на разных машинах (!)) мы будем использовать два окна терминала. В одном запустим <b>server.rb</b>:</p><pre><code class='ruby'># coding: utf-8
$KCODE = "utf-8" if RUBY_VERSION < "1.9.0"
require "drb/drb"
class RemoteObject
def remote_method_with_param(param)
puts "вызван метод на сервере с параметром #{param.inspect}"
case param.class.to_s
when "String"
puts "параметр типа строка"
param.reverse!
when "Array"
puts "параметр типа массив"
param.shift
else
puts "параметр оставшегося типа"
param.do_smth
end
end
end
$SAFE = 1 # Запретить eval() и eval-оподобные вызовы
DRb.start_service("druby://localhost:45678", RemoteObject.new)
DRb.thread.join</code></pre><p>Здесь мы используем банальный <b>Thread#join</b>, чтобы при необходимости просто прервать выполнение. Но те, кто читал предыдущую статью, знают, что в это время можно делать что угодно и следить за потоком dRuby отдельно.</p><p>В другом терминале запустим клиентский код <b>client.rb</b>:</p><pre><code class='ruby'># coding: utf-8
$KCODE = "utf-8" if RUBY_VERSION < "1.9.0"
require "drb/drb"
class MyString
def initialize(str)
@string = str
end
def do_smth
@string.reverse!
end
def inspect
"<#{@string}>"
end
end
rem_o = DRbObject.new_with_uri("druby://localhost:45678")
["строка", ["котик", "пёсик", "слоник"], MyString.new("суперстрока")].each do |obj|
puts "Вызов метода вернул: #{rem_o.remote_method_with_param(obj).inspect}"
puts "Параметр после вызова: #{obj.inspect}"
end</code></pre><p>Вывод в терминалы будет следующий (я использую вывод для версии руби 1.9.1, потому что он нормально переворачивает кириллическую строку без колдовства) для сервера:</p><pre><code>вызван метод на сервере с параметром "строка"
параметр типа строка
вызван метод на сервере с параметром ["котик", "пёсик", "слоник"]
параметр типа массив
вызван метод на сервере с параметром #<DRb::DRbUnknown:0x00000001248910 @name="MyString", @buf="\x04\bo:\rMyString\x06:\f@stringI\"\e\xD1\x81\xD1\x83\xD0\xBF\xD0\xB5\xD1\x80\xD1\x81\xD1\x82\xD1\x80\xD0\xBE\xD0\xBA\xD0\xB0\x06:\rencoding\"\nUTF-8">
параметр оставшегося типа</code></pre><p>Клиент же упадёт с ошибкой:</p><pre><code>Вызов метода вернул: "акортс"
Параметр после вызова: "строка"
Вызов метода вернул: "котик"
Параметр после вызова: ["котик", "пёсик", "слоник"]
(druby://localhost:45678) server.rb:17:in `remote_method_with_param': undefined method `do_smth' for #<DRb::DRbUnknown:0x00000001248910> (NoMethodError)
.....</code></pre><p>Что, безусловно, прекрасно. Прекрасно, что упал не сервер. :) Понятно, что он не знает ничего про этот объект и не знает, как с ним обращаться.</p><p>Как видно из вывода, объекты передаются в виде копий. Нашим же третьим, самодельным объектом, мы можем исследовать две возможности: таки передавать копию объекта или передавать лишь ссылку на него, чтобы вызовы выполнялись на клиентской копии. Для первой возможности достаточно вынести определение класса в общедоступное для клиента и сервера место — <b>common.rb</b>:</p><pre><code class='ruby'># coding: utf-8
$KCODE = "utf-8" if RUBY_VERSION < "1.9.0"
require "drb/drb"
REM_URI = "druby://localhost:45678"
class MyStringCopied
def initialize(str)
@string = str
end
def do_smth
@string.reverse!
self
end
def inspect
"<<#{@string}>>"
end
end
class MyStringSingle
include DRb::DRbUndumped # это ключ :)
def initialize(str)
@string = str
end
def do_smth
@string.reverse!
self
end
def inspect
"<#{@string}>"
end
end</code></pre><p>Добавим <i>require "common.rb"</i> в серверный код, а клиентский преобразится до такого:</p><pre><code class='ruby'># coding: utf-8
require "common"
rem_o = DRbObject.new_with_uri(REM_URI)
DRb.start_service # Это нужно для объекта, который не копируется при передаче
["строка",
["котик", "пёсик", "слоник"],
MyStringCopied.new("суперстрока"),
MyStringSingle.new("суперстрока без копий")].each do |obj|
puts "Вызов метода вернул: #{rem_o.remote_method_with_param(obj).inspect}"
puts "Параметр после вызова: #{obj.inspect}"
end</code></pre><p>Как видно, мы сразу позаботились и о второй возможности, создав для неё ещё один класс. Секрет заключается во включении модуля <b>DRb::DRbUndumped</b> и старте ещё одного серверного процесса на клиенте (для вызовов методов объектов клиента удалённо) Клиентский вывод теперь выглядит так:</p><pre><code>Вызов метода вернул: "акортс"
Параметр после вызова: "строка"
Вызов метода вернул: "котик"
Параметр после вызова: ["котик", "пёсик", "слоник"]
Вызов метода вернул: <<акортсрепус>>
Параметр после вызова: <<суперстрока>>
Вызов метода вернул: #<DRb::DRbObject:0x000000012588c8 @uri="druby://127.0.1.1:43998", @ref=9631244>
Параметр после вызова: <йипок зеб акортсрепус></code></pre><p>Если немножко почитать, и разобраться, какие объекты можно и нужно «маршализировать», а какие нельзя или не нужно, то получается вполне себе прекрасный инструмент. Который, повторюсь, входит в стандартную библиотеку и не требует никаких внешних зависимостей.</p><h2>Материалы для самостоятельного изучения</h2><ol><li><a href="http://github.com/lonelyelk/lonelyelk_code/tree/master/drb/">Полный код статьи на github</a></li><li><a href="http://ruby-doc.org/stdlib/libdoc/drb/rdoc/index.html">Документация по DRb (rdoc)</a></li><li><a href="http://www.ruby-doc.org/core/classes/Marshal.html">Документация по Marshal</a></li></ol>Sergey Kruksergey.kruk@gmail.com