Последнее в категории Приложения

RPC::XML v. XXX

| 1 комментарий

Из записок на языке Эразма Роттердамского.

Сделали sandbox на новеньком сервере, стали запускать тесты — половина не работает. Вместо ok видим wanted a data type, got `i8'. Кто говорит? Слон? Ошибку можно разглядеть в XML, который как-то где-то формируется через третьи руки. Разумеется, автор кода на этой неделе в отпуску, поэтому тратим еще час на то, чтобы выяснить, что сообщение об ошибке формирует модуль RPC::XML.

Мы-то уже догадались, что i8 — скорее всего тип целочисленных данных, потому что в стандарте есть тип i4, но про i8 там ни слова.

Еще какое-то время, и выясняется, что на другом сервере такой ошибки нет. Остается догадаться посмотреть $RPC::XML::VERSION. Ну точно, в одном месте — 1.36, в другом — 1.41.

Читаем документацию и удивляемся: тип i8 добавлен как нестандартный. Потому что им уже кто-то пользуется. Смотрим на спан и удивляемся еще раз: последняя версия там — 0.70. Ij? (в русской раcкладке — шо?) Может github? Там тоже 0.70. Начинаем подозревать всех в том, что они втихаря переписывают модуль прямо на серверах. Но в итоге смотрим в исходник версии 0.70 и видим, что версия модуля — 1.50. Релаксируем.

На днях прочитал 42 статьи о том, как пользоваться юникодом в современном C++ и заодно придумал еще один практический пример, где может быть полезна state-переменная, появившаяся в Perl 5.10, — при разборе последовательности байтов UTF-8.

Вот простая функция parse_utf8, которая принимает очередной байт и формирует в переданном по ссылке массиве @$buf последовательность кодов символов. Важно, что функция при каждом вызове принимает байт, но выходной буфер изменяется только после того, как принята вся последовательность, соответствующая юникодному символу, то есть на каждый второй, третий или четвертый раз, если очередной символ требует для записи в UTF-8 несколько байтов.

В этом примере суть довольно точно описывается словом state: переменные хранят состояние разбора между вызовами функции.

use v5.12;

(5.12 удобно использовать потому, что инструкция use v5.12 автоматически подключает и use strict. Но все должно работать и с 5.10.)

sub parse_utf8 {
    my ($byte, $buf) = @_;
   
    state $bytes = 0;
    state $value = 0;
    my $mask = 0;
   
    given($byte) {
        when(!($_ & 0x80)) {
            ($bytes, $value, $mask) = (0, $byte, 0);
        }
        when(0b1111_0000 == ($_ & 0b1111_1000)) {
            ($bytes, $value, $mask) = (3, 0, 0b0000_0111);
        }
        when(0b1110_0000 == ($_ & 0b1111_0000)) {
            ($bytes, $value, $mask) = (2, 0, 0b0000_1111);
        }
        when(0b1100_0000 == ($_ & 0b1110_0000)) {
            ($bytes, $value, $mask) = (1, 0, 0b0001_1111);
        }
        when(0b1000_0000 == ($_ & 0b1100_0000)) {            
            $bytes--;
            $mask = 0b0011_1111;           
        }
        default {
            $mask = 0;
            $value = ord('?');         
        }
    }
   
    $value += ($byte & $mask) << ($bytes * 6) if $mask;
   
    push @$buf, $value unless $bytes;
}

Когда обнаруживается начало многобайтовой последовательности, в state-переменной сохраняется число оставшихся байтов, а в $value начинает накапливаться результат. Каждый последующий байт (старшие биты которого — единица и нуль) на единицу уменьшает значение $bytes.

Обновление буфера происходит в последней строке только в том случае, если прочитана вся последовательность байтов очередного символа.

Здесь есть даже примитивная обработка ошибок (хотя она находит ошибку лишь в первом байте многобайтовой последовательности).

Теперь проверяем:

my @buf;
parse_utf8($_, \@buf) for (
    0x34, # 4
    0x32, # 2
    0xd1, 0x9e, # ў
    0xc2, 0xa2, # ¢
    0xe3, 0x89, 0xbf, # ㉿
    0xe2, 0x82, 0xac, # €
    0xf0, 0xa4, 0xad, 0xa2, # 𤭢
);
say "&#$_;" for @buf;

Вызов say печатает HTML-сущности опознанных символов (да, строка "&#$_;" может вызвать улыбку):

&#52;
&#50;
&#1118;
&#162;
&#12927;
&#8364;
&#150370;
Именно такой результат и ожидался: 4 2 ў ¢ ㉿ € 𤭢.

В следующий раз обратим внимание на бинарные операции внутри when.

19 апреля 2009

Сегодня утром мне захотелось посмотреть, как над Европой распространяется облако вулканического пепла (тем более, что моя коллега, на прошлой неделе поехавшая в Берлин, до сих пор не может оттуда выбраться).

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

Интересных источников не обнаружилось, зато (в очередной раз) нашелся сайт flightradar24.com, с которого можно было снимать регулярные скриншоты.

10:57. Коммит. Ревизия 5022.

Карта хорошая, но обновлять ее вручную совсем не хочется. Ищу дальше, где бы разжиться списком закрытых аэропортов. Находится (в очередной раз) нечто лучшее — сайт flightstats.com, на котором текущий статус аэропорта не только показан, но еще и проградуирован по пятибальной шкале. Ура, на коленке пишу скрипт.

#!/usr/bin/perl
 
use v5.10;
use strict;
use LWP::Simple;
 
my $xml = "<?xml version=\"1.0\"?>\n<delays>\n";
open my $list, '<', 'europe.list';
while (<$list>) {
    chomp;
    my ($cc, $iata, $name) = split /\t/;
    
    my $page = get_airport_status($iata);

    $xml .= <<XML;
    <item cc="$cc" iata="$iata" name="$name" index="$delay_index"/>
XML
}
close $list;
 
$xml .= "</delays>\n";
if (length $xml > 100) {
    open my $out, '>', 'airport-status.xml';
    print $out $xml;
    close $out;
}

12:24. Копи-пейст XSLT, формирующего список, коммит. Ревизия 5024.

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

Прошу коллегу поставить скрипт в крон, подвязать к аэропортам координаты и отправляюсь в офис.

Тем временем проект анонсируется и мы получаем первые пользовательские баг-репорты.

В офисе коллегиально решили, что по разным причинам будет удобнее перенести информацию в базу данных, и мимоходом (совсем неожиданно) приходит мысль дополнить наш движок методом, который одним махом превращает SQL-запросы в XML-данные, избавляя от необходомости многократно писать мелкие функции, извлекающие данные. (В частности, в примере, идущем в составе с WWW::Page, часть методов примитивно дампят в XML соответствующие таблицы базы, и этот подход — тупо вывалить данные из базы, переложив их обработку на XSLT, — оказался очень продуктивным и работает не первый год.)

17:37. Ревизия 5042.

sub sql { 
    my ($this, $page, $node, $args) = @_; 
    my $sql = $args->{sql} or return $node; 
    $node->setAttribute('name', $args->{name}); 
    my $sth = dbh->prepare_cached($sql); 
    $sth->execute(); 
    while (my $row = $sth->fetchrow_hashref) { 
        add_node($node, 'item', $row); 
    } 
    return $node;
}

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

В это время подключается третий коллега, который улучшает клиентскую JavaScript-сортировку таблицы со списком. А четвертый потом делает еще одно изменение в коде, которое улучшило сортировку колонки со статусом задержек рейсов.

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

19:12. Ревизия 5051.

Perl-программист берется за JavaScript-API Гугл-карт, и находит, что сделать замыкание в яваскрипте — это совсем не так просто, как в перле. Тем не менее, коммит, все работает, а карта обновляется автоматически (а вдобавок еще и зумится, а каждый маркер — еще и ссылка).

Добавляю легенду, прошу перекрасить конфети.

Можно с честной совестью идти домой, но хочется — раз уж мы все знаем про аэропорты — поместить статус и на страницу аэропорта.

20:14. Раз, два, ревизия 5058.

Имеем работающее, полезное и злободневное приложение — «Задержки рейсов в аэропортах Европы», списком и на карте.

Итого, за день — пяток тикетов, сорок коммитов, фан и радость.

  • Ticket #860 (airport-status в cron) created by ash
  • Ticket #861 (Автоматизировать карту аэропортов) created by ash
  • Ticket #862 (Карта аэропортов Европы.) created by hsw
  • Ticket #863 (Перекрасить шкалу задержек) created by ash
  • Ticket #864 (Вынести в локаль строки из сервисов про задержки рейсов) created by ash

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

P. S. 22:44. Коммит, ревизия 5062. Коллега сообщает о том, что закончил еще один геосервис, который мы давно хотели сделать.

Great Circle

| Нет комментариев

Гугл-карты с третьей версией API научились рисовать путь между двумя пунктами на Земле по «большому кругу» (great circle), поэтому без жалости можно отправить в архив фрагмент кода, вычисляющий координаты дуги.

my $span = 10;
$span = int($distance) / 100 if int($distance) > $span * 100;

my $arc = (new Geo::GoogleEarth::Pluggable)
          ->GreatCircleArcSegment(
    startPoint => {
        lat => $coords_pair[0],
        lon => $coords_pair[1]
    },
    endPoint   => {
        lat => $coords_pair[2],
        lon => $coords_pair[3]
    },
    span        => $span * 1000, # meters
);

my $arcNode = add_node($pairNode, 'arc');   
for (@{$arc->coordinates}) {
    add_node(
        $arcNode,
        'point', {
            lat  => $_->lat,
            long => $_->lon,
        },
    );
}

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

Great circle

Hello, Perl 5--

| Комментариев: 2

Сегодня открыл свой CGI-скрипт 2002 года, который до сих пор, кстати, работает, сто раз дописанный и истерзанный, но выполненный за это время 105 миллионов раз. Хочу показать тот фан, на который сегодня смотреть смешно, но девять лет назад приходилось с трудом выцарапывать из книг и интернетов.

Ввод-вывод

binmode STDIN;
binmode STDOUT;

Глобальные переменные

our $dbh;
our $sth;

Дата и время

Разбор дат после чтения из базы данных.

my ($year, $month, $day, $hour, $min, $sec) = $date =~ /^(\d{4})-?(\d\d)-?(\d\d) ?(\d\d):?(\d\d):?(\d\d)/;

Дорисовка ведущего нуля.

$mday = "0$mday" if $mday =~ /^\d$/;
$hour = "0$hour" if $hour =~ /^\d$/;
$min = "0$min" if $min =~ /^\d$/;
$sec = "0$sec" if $sec =~ /^\d$/;
return "$days[$wday], $mday-$mons[$mon]-$year $hour:$min:$sec GMT";

Сохранение даты в хеше.

%datetime = ();
($datetime{"sec"}, $datetime{"min"}, $datetime{"hour"},
 $datetime{"day"}, $datetime{"month"}, $datetime{"year"},
 $datetime{"wday"}, $datetime{"yday"}, $datetime{"isdst"}) = localtime (time);
$datetime{'month'}++;
$datetime{'year'} += 1900;

Работа со строками

Декодирование строки запроса.

sub _urldecode{
 local($val)=@_;
 $val=~s/\+/ /g;
 $val=~s/%([0-9A-H]{2})/pack('C',hex($1))/ge;
 return $val;
}

Отдельный фетиш — ручное декодирование UTF. В частности, есть такая таблица. И еще пара нетривиальных функций, которые я здесь не привожу.

sub init_utftable{
%utftable = (
  0xD020   => 0x20,
  0xD082   => 0x80,
  0xD083   => 0x81,
  0xE2809A => 0x82,
  0xD193   => 0x83,
  0xE2809E => 0x84,
  . . .
  0xD18D   => 0xFD,
  0xD18E   => 0xFE,
  0xD18F   => 0xFF);
}

Смена регистра.

sub lower_case{
  my $word = shift;
  $word =~ tr /АБВГДЕЁЖЗИКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ/ абвгдеёжзиклмнопрстуфхцчшщъыьэюя/;
  return lc $word;
}

sub upper_case{
  my $word = shift;
  $word =~ tr /абвгдеёжзиклмнопрстуфхцчшщъыьэюя/ АБВГДЕЁЖЗИКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ/;
  return lc $word;
}

REST::Client

| Нет комментариев

В феврале этого года O’Reilly издало книгу «RESTful Web Services Cookbook», и в этой связи есть смысл упомянуть модуль, который появился на спане в начале прошлого года, а сегодня обновился, — REST::Client, который, возможно будет в чем-то удобнее традиционных LWP et. al.

Использование весьма прямолинейно (хотя и не без странностей): названия методов REST::Client совпадают с именами методов HTTP-протокола: GET, PUT, POST, DELETE, OPTIONS, HEAD.

use v5.10;
use strict;

use REST::Client;

my $client = new REST::Client;
$client->OPTIONS('http://onperl.ru/');
say $client->responseCode();
say $client->responseContent();

$client->HEAD('http://onperl.ru/');
say $client->responseCode();

$client->GET('http://onperl.ru/');
say $client->responseCode();

Дополнительно имеется возможность использовать SSL, и, что интересно, создать для полученного XML-ответа XPath-контекст.

Вот пример того, как переписать скрипт, получающий курс для любой пары валют, с использованием модуля REST::Client, и с честным разбором XML.

#!/usr/bin/perl

use v5.10;
use strict;

use REST::Client;

my ($from, $to) = map {uc} @ARGV;
unless ($from) {
    say "Usage: rate FROM [TO]\n";
    exit;
}
$to //= 'RUB';

my $client = new REST::Client;
$client->GET("http://whoyougle.com/money/api/$from/$to");
say $client
    ->responseXpath
    ->findvalue('/currency/rate/value/text()')
;

Проверка:

$ ./rate usd
29.2416

$ ./rate eur usd
1.3393

  • Брайн ди фой пишет новую редакцию книги Effective Perl Programming.
  • С новым маком и Mac OS X 10.6 в комплекте идет Perl 5.10.0.
  • Алекс Капранов анонсировал CPAN Hubble — одновременный поиск по CPAN и GitHub.

Одна из задач, где Gearman позволяет получить удобство и скорость работы, — обработка текстовых поисковых запросов. Детально я хочу рассказать об этом на YAPC::Europe, а в этом посте покажу несколько фрагментов кода, который сейчас работает в продакшне.

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

misc::e — поиск по кодам пищевых добавок,
misc::typo — проверка орфографии;

place::airport — поиск аэропортов,
place::country — поиск стран,
place::locality — поиск населенных пунктов;

whl::money — конвертер валют;

whl::area, whl::volume, whl::temperature, whl::speed, whl::pressure, whl::power, whl::mass, whl::length, whl::information, whl::force, whl::energy, whl::calendar — конвертеры единиц измерения;

whl::calculator — калькулятор.

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

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

Сканеры подгружаются автоматически при старте. О том, как это делается, недавно упоминалось в рассылке Moscow.pm. Расстановка тасков — перебор по списку:

my $tasks = $client->new_task_set;
for(@{$self->{scanners}}) {
    $tasks->add_task(
        $_ => nfreeze([$query, $self->{locale}]),
        {
            on_complete => sub {
                push @{$self->{results}}, thaw(${$_[0]});
            },
        },
    );
}
$tasks->wait;

Типовой сканер содержит метод, принимающий строку запроса. В частности, сканер единиц измерения и валют раздает задачи своим парсерам:

sub scan {
    my $query = shift @args;

    for my $parser (@{$self->{parsers}}) {
        my $result =
           $parser->parse($query, $self->{locale}) // next;
        push @{$self->{results}}, $result;
    }
}

(Обатите внимание, кстати, на использование оператора defined-or для завершения итерации цикла.)

После того, как сканеры собрали результаты от парсеров, итоговый ответ попадает (в случайном порядке) в общий XML, и всю дальнейшую работу по визуализации данных выполняют с помощью XSLT.

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

Вот пример запроса: sin(pi/2) + cos(pi/2). Нефильтрованные данные, полученные от всех сканеров, выглядят следующим образом:

Те же данные с точки зрения XML-наблюдателя:

<pack for="sin(pi/2) + cos(pi/2)" scanner="whl::calculator">
    <item>
        <from expr="sin(π/2) + cos(π/2)"/>
        <to value="1"/>
    </item>
</pack>
<pack for="sin" scanner="place::locality">
    <item country-code="FR" geonameid="2974494">Sin-le-Noble</item>
    <item country-code="AF" geonameid="1124363">Sīn</item>
    . . .
</pack>
<pack for="pi" scanner="place::locality">
    <item country-code="FR" geonameid="2984891">Py</item>
    <item country-code="ES" geonameid="6424319">Pi</item>
    <item country-code="IN" geonameid="1259715">Pi</item>
</pack>
<pack for="cos" scanner="place::locality">
    <item country-code="FR" geonameid="3023491">Cos</item>
    <item country-code="ES" geonameid="3124419">Cos</item>
    . . .
</pack>

Парсер калькулятора вернул один результат (собственно, наиболее релевантный), а географический сканнер нашел города с названиями и Синус, и Косинус, и Пи — эти результаты при наличии первого результата, который поглотил всю строку, показывать, разумеется, не обязательно. Большинство возможностей, которые сегодня умеет делать упоминаемый здесь поиск, описаны на соответствующей странице.

Вчера я упоминал о баге в Padre, связанном с не-ASCII-символами. Строка

say "Превед!";

превращалась при сохранении файла в

say "\x{041f}\x{0440}\x{0435}\x{0432}\x{0435}\x{0434}!";

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

return 'ascii' unless $content =~ /[^[:print:]\015\012]/;
#return 'ascii' unless $content =~ /[^[:print:]\015\012]/;

Да здравствует перл! Этот блог работает на перле, но писали его неизвестные мне люди Я тут зашел куда-то в админской части, и увидел массу комментариев, которые, оказывается, висят неодобренными. :-O 

Часть я пооткрывал, а часть (ай!) удалил. Перл + юзабилити часто так же несовместимо, как и многие другие продукты, созданный программистами.

И еще я забыл с собой зарядное устройство от телефона.

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

Страницы

  • img

Об архиве

Эта страница содержит последние записи категории Приложения.

Предыдущая категория — Презентации.

Следующая категория — Развлечения.

Смотрите новые записи на главной странице или загляните в архив, где есть ссылки на все сообщения.