Показаны сообщения с ярлыком Term::Highlight. Показать все сообщения
Показаны сообщения с ярлыком Term::Highlight. Показать все сообщения

четверг, 10 января 2013 г.

A perl script for gathering blogger stats and showing it on a terminal

I decided to experiment and write this post in English. Recently I uploaded a small perl script bloggerstats on the github (see here). The script can help to gather statistics of a blog hosted on the blogger.com. The stats can be dumped on a terminal or redirected to a file. As well pretty charts in PNG format can be created. See images below.


The usage of the script is shown with -h or --help option. The script requires several mandatory and optional perl modules: JSON, URI::Escape, Encode, Data::Dumper (for dumping received JSON data in debug mode), Text::TabularView (for printing result in pretty tables), Chart::Bars and Time::Local (for making charts). All modules are either normally shipped with perl distributions or available in the system repository (I found all of them in my standard Fedora 17 rpm repository). The script also requires several external programs: sed, curl (for network communication with blogger.com) and ngrep (for sniffing sensitive data required by blogger.com, see below).

To start using it one should provide basic configuration settings in file $HOME/.bloggerstatrc. I put a template file bloggerstatrc.tmpl in the github repository to start with. Here is its content:
$blogID      = 'put-here-your-blog-id';
$bloghost    = 'blogger.com';

$statsurl    = "http://www.$bloghost/blogger_rpc?blogID=$blogID";

$start_year  = 2010;
$start_month = 5;       # months start from 1, so January is 1

# AUTOGENERATED (do not delete this line!)
Actually .bloggerstatrc is an ordinary perl source file and is to be sourced from bloggerstats, so you can put there any statements that perl can compile, but those 5 variables defined in this template are mandatory ones. Of course you have to substitute your real blogID instead put-here-your-blog-id (this is the value of the parameter blogID in any related HTTP GET request to blogger.com: you can find it in the address line in your browser). You also have to substitute proper values of $start_year and $start_month: they must correspond to the first bin of the all-time visits chart in your blog. The line with comment starting with AUTOGENERATED must reside below your regular settings: it is used by the sniffer for updating other sensitive variables (cookies, headers and xsrf token) below it. Every time you run bloggerstats -w all content below this line gets removed!

Why do I say here about sniffer? Unfortunately blogger.com API seems to be closed at the moment. The only thing known is that it uses GWT (Google Web Toolkit) and JSON for sending data to the client. GWT produces very large obfuscated Javascript code which finally creates xsrf token and sends it to the server. The algorithm that creates it is unknown. The generated token is valid through approximately 24 hours. Besides the token blogger.com checks for sessionID cookies (which can also become invalid but not so often as the token) and GWT related HTTP headers. All these data are collected by the sniffer and put below the comment line starting with AUTOGENERATED in .bloggerstatsrc. As soon as sniffing network interfaces requires root privileges the script must possess them. To achieve this login as root, open some new sudoers file (say /etc/sudoers.d/users) with visudo and put there following lines
Cmnd_Alias       NETTASKS = /usr/sbin/ngrep
<your-login-id>  ALL = NOPASSWD: NETTASKS
where <your-login-id> is your system login name. Word NOPASSWD in the second line is important: it prompts sudo do not ask password when starting ngrep.

So now that you configured basic parameters in .bloggerstatsrc you may want to launch the sniffer by running bloggerstatrc -w in a terminal (it will wait until stats request is sent to blogger.com via a browser), open your blogger.com stats page in a browser or just refresh it. The sniffer must exit (though it may fail to exit at very heavy network load: refresh stats page several times in this case) and now bloggerstats is ready to gather blogger statistics. As soon as saved xsrf token or cookies become invalid (in one day or so) you will see errors, running the sniffer once again will bring bloggerstats back to life.

Finally I want to show settings for tabular highlights as seen on the first image above (look here about hl and Term::Highlight). File .hlrc:
snippet bstats  -b -82 '^(?:\+-+)+\+$' '^\|\s+' '\s+\|$' \
                '\s+\|\s+(?=.*\s+\|$)'-67 \
                '(?<=^\|)\s+(?:Overview|Page|Keyword|Site|URL|Country)\s+' \
                '(?<=^\|)\s+(?:Browser|OS)\s+' -215 '(?<=\|)\s+\d+\s+(?=\|$)' \
                -rb -48 '(?<=^\|)\s+Today\s+(?=\|)'
File .hl_functions:
function bloggerstats
{
    `env which bloggerstats` $@ | hl -sbstats
}
Vielleicht schreibe ich nächstes Mal auf Deutsch :)

суббота, 17 ноября 2012 г.

Статистика, diff, подсветка

Здесь я уже рассказывал, как с помощью утилиты diffstat можно выводить статистические данные по коммитам в Subversion, систематизированные по отдельным пользователям. На этот раз я хочу расширить возможности данного подхода путем абстрагирования параметров систематизации и улучшения пользовательского интерфейса. Вторая задача, в частности, означает, что команда-однострочник будет заменена программой с возможностью задания различных опций. Программа представляет собой bash-скрипт и выглядит так:
#!/bin/bash - 

PrintUsage()
{
    echo "Usage: `basename $0` [-m] [-l level] [-p pattern] [-w width] [path]"
    echo "  -m          calulate modifications, insertions and deletions"
    echo "  -l level    history level of svn log command"
    echo "  -p pattern  filter commits according pattern"
    echo "  -w width    width of diffstat histogram"
    echo "  path        in specified file or directory only"
}

while [ $1 ]
do
    case $1 in
        -h|-help|--help)    PrintUsage ; exit 0 ;;
        -m)                 modifications=-m ;;
        -l)                 shift ; level="-l $1" ;;
        -p)                 shift ; pattern=$1 ;;
        -w)                 shift ; width="-w $1" ;;
        *)                  path=$path' '$1 ;;
    esac
    shift
done

for revision in \
    `svn log $level $path | grep '^r[0-9]\+.*'$pattern | awk '{print $1}' |\
     sed 's/r//g'`
do
    svn diff -c$revision $path
done | diffstat -v $modifications -f 4 $width
Львиную долю кода составляет функция PrintUsage() и разбор опций командной строки. Основной код размещен внутри цикла for и представляет собой ту же самую, слегка видоизмененную, однострочную команду. Кстати, PrintUsage() дает полное представления об опциях программы и я не буду на них останавливаться. Я поместил этот код внутрь исполняемого файла svndiffstat в директории $HOME/bin/.

Теперь мы сделаем так, чтобы вывод скрипта был подсвечен. Я показывал, как этого можно добиться с помощью программы hl здесь и здесь. Следуя данной схеме, определим сниппет dstat в $HOME/.hlrc:
snippet dstat   -196 '\++[!-]*$' '(?<=\|)\s*\d+\s*\d+' '\d+ (?=insertion)' \
                '(?<=\()\+(?=\))' \
                -33 '\-+!*$' '(?<=\|)\s*\d+\s*\d+\s*\d+' '\d+ (?=deletion)' \
                '(?<=\()-(?=\))' \
                -30 '!+$' '(?<=\|)\s*\d+\s*\d+\s*\d+\s*\d+' \
                '\d+ (?=modification)' '(?<=\()!(?=\))' \
                -101 '(?<=\|)\s*\d+' -82 '\s+\|\s+' -155 '/' -215 '[^/]+\s+\|' \
                -203 '^\s*[^/]+/' -155 '\d+ (?=file)'
и новую функцию svndiffstat в $HOME/.hl_functions:
function svndiffstat
{
    `env which svndiffstat` $@ | hl -sdstat
}
Результат работы svndiffstat представлен на картинке:


Здесь была запрошена статистика по коммитам за февраль 2011 года. Фактически значение опции -p задает регулярное выражение для поиска внутри строки, выводимой командой svn log и начинающейся с номера ревизии, такая строка включает имя пользователя, дату коммита и число измененных строк: это и есть параметры, по которым можно осуществлять выборку. Представленная статистика была собрана в соответствии с метаданными svn, находящимися внутри текущей директории, кроме того, svndiffstat позволяет изменить директорию для сбора статистики, если таковая будет задана в конце списка аргументов командной строки.

Кстати говоря, поскольку программа hl не включает подсветку при выводе в пайп, то можно изменять правила подсветки вывода svndiffstat, например добавить подсветку слова Energy в примере на картинке:
$ svndiffstat -p 2011-02 -w 60 -m | hl -sdstat -98 Energy
Раз уж речь зашла о diff и подсветке вывода программ, то хотелось бы привести соответствующие настройки .hlrc и .hl_functions. (Кстати, раньше для подсветки diff я использовал программу colordiff. Однако colordiff имела существенный недостаток: невозможность использования всех цветов на 256-цветных терминалах.)

Итак, в .hlrc следует добавить
snippet diff    -155 -b '^Index:\s+.*' '^=+$' '^diff.*' '^Binary.*' \
                -180 '^Только.*' '^\\.*' -76 '^@.*' '^\d.*' -rb -196 '^[+>].*' \
                -26 '^[-<].*'
a в .hl_functions
function diff
{
    `env which diff` $@ | hl -sdiff
}

function svndiff
{
    svn diff $@ | hl -sdiff
}
Теперь вывод diff будет всегда подсвечиваться на экране терминала. Кроме того, здесь определена еще одна функция svndiff для подсветки, как не трудно догадаться, вывода команды svn diff. Кстати, наверняка нам понадобятся аналогичные определения cvsdiff, gitdiff, hgdiff и т.п. Чтобы не плодить множество подобных функций, можно воспользоваться командой оболочки eval внутри цикла for по всем типам VCS, которая определит все функции за нас:
for vcs in cvs svn hg git ; do
    eval "function ${vcs}diff
    {
        $vcs diff \$@ | hl -sdiff
    }"
done
В дополнение к сказанному, хочу затронуть тему вывода на экран терминала подсвеченного программного кода. Универсальные подсветчики типа hl или grc здесь не очень подходят, так как писать сложные правила подсветки для множества языков программирования с использованием одних только регулярных выражений - задача нереальная. Поэтому для этой цели я использую программу GNU source-highlight, обернув ее, как обычно, в новую функцию оболочки. Функцию я назвал cathl и поместил ее в $HOME/.bashrc.
function cathl
{
    source-highlight -f esc -q -i $@
}
Функция cathl выводит на экран терминала содержимое своего аргумента - исходного файла. Если нужно вывести подсвеченный код в другой файл, то следует добавить в список аргументов после (здесь это важно) имени исходного файла опцию -o output-file. Кроме этого в cathl в принципе доступны другие опции source-highlight, например указание языка исходного текста там где это нужно. Однако изменить формат вывода (опция -f) в cathl не получится, так как он уже закреплен за цветовыми escape последовательностями терминала (-f esc).

Главным недостатком source-highlight лично для меня является недоступность всей 256-цветовой палитры для подсветки. Однако в последней версии 3.1.7 этот недостаток отсутствует. У меня в Fedora 17 стоит более старая версия 3.1.4, ну а если вам повезло, или вы сами решите собрать source-highlight из исходников, то можете смело заменять -f esc в cathl на -f esc256.

понедельник, 19 сентября 2011 г.

Цветной ngrep (в продолжение предпоследнего поста)

ngrep - это что-то среднее между tcpdump и wireshark. Он может показаться не таким удобным как wireshark, зато превосходит по удобству использования tcpdump, поскольку основной особенностью ngrep является вывод на экран полезного содержимого пакетов (то, что обычно называют payload), а не всевозможных сетевых деталей. Кроме того, ngrep работает в терминале, а это, на мой взгляд, огромный плюс.

Итак, сначала картинка:


А теперь настройки .hlrc:
snippet ngrep   -191 '^\s*(?:POST|GET)\s+' '^[ITU]\s+' -211 '\[[A-Z]+\]' \
                -120 '^[A-Z]\S+: ' -130 '^#+' \
                -210 '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+' \
                -140 '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' -190 '^HTTP.*'
и .hl_functions:
function ngrep()
{
    sudo `env which ngrep` $@ | hl -sngrep
}
Чтобы этот пост не получился совсем уж скучным и банальным, ниже я покажу как предоставить права на запуск ngrep обычному пользователю (заметили sudo в функции ngrep?).

Итак, в обычной ситуации обычный пользователь не имеет права на запуск ngrep, поскольку может получить доступ к информации, для него не предназначенной. Но что если нам часто требуется получать доступ к содержимому сетевых пакетов (например, при тестировании какого-нибудь веб-приложения), или мы работаем на домашнем компьютере и доступ к информации о том, что происходит в нашей сетке не только не ослабляет безопасность системы, а наоборот повышает ее? В этом случае нужно добавить себя в список sudoers. Я не стану добавлять записи в файл /etc/sudoers, а создам новый файл (назовем его users) в директории /etc/sudoers.d/. Итак, от имени суперпользователя открываем файл /etc/sudoers.d/users и записываем в него следующие строки:
Cmnd_Alias  NETTASKS = /usr/sbin/ngrep, /usr/sbin/wireshark, /usr/sbin/tcpdump

user1      ALL = NOPASSWD: NETTASKS
Вместо user1 нужно поставить ваше настоящее регистрационное имя. После завершения редактирования файла необходимо изменить его атрибуты доступа:
chmod 0440 /etc/sudoers.d/users
Если этого не сделать, sudo откажется выполнять инструкции данного файла с соответствующей диагностикой. Ввиду данного изменения, при дальнейшем редактировании /etc/sudoers.d/users придется подтверждать внесение изменений при записи на диск (в vim это делается командой :w!), но лучше всего воспользоваться командой visudo -f /etc/sudoers.d/users, которая знает, как работать с файлами sudoers

Кроме права на запуск ngrep я наделил пользователя user1 правами на запуск wireshark и tcpdump. Теперь для запуска этих двух команд данный пользователь может ввести sudo wireshark и sudo tcpdump, а для запуска ngrep достаточно просто ввести ngrep, поскольку в интерактивной среде ngrep - это функция, записанная нами в файл .hl_functions, которая через sudo вызывает настоящий ngrep.

Заметим также, что ввод пароля пользователем при запуске этих команд не требуется, так как в файле /etc/sudoers.d/users перед задачами NETTASKS  для пользователя user1 указана опция NOPASSWD. Кроме того, слово ALL указывает на то, что пользователь user1 имеет право запускать команды с любого хоста. В целях безопасности, наверное имеет смысл заменить ALL на localhost, но в этом случае user1 не сможет запускать команды из NETTASKS удаленно.

Update. Не сразу заметил, что функция ngrep() в приведенном выше виде,  работает неверно для многих достаточно типичных случаев, в частности, когда паттерн для поиска пуст (например, в случае ngrep '' 'tcp port 80'). Проблема в том, что переменная $@ внутри ngrep() удаляет все кавычки, которые в случае пустого паттерна (или паттернов, содержащих пробелы) имеют значение. К счастью, если переменную $@ взять в двойные кавычки, то информация о пустых аргументах и аргументах, содержащих пробелы, не теряется, и ее можно восстановить. Я не знаю, существует ли в sh или bash штатный способ вернуть кавычки в этом случае на место, скорее всего нет, поэтому я написал отдельную функцию quote_args_if_needed() и поместил ее в начале файла .hl_functions.
function quote_args_if_needed()
{
    local result=''
    for i in "$@" ; do
        if [[ -z "$i" || "$i" =~ ' ' ]] ; then
            result=$result" '"$i"'"
            continue
        fi
        result=$result' '$i
    done
    echo $result
}
Функция проходит по переданным ей аргументам и добавляет их (через пробел) к изначально пустой переменной result. Если очередной аргумент пуст, либо содержит пробелы, то он обертывается одинарными кавычками. Мы будем передавать в quote_args_if_needed() значение "$@". Переменная $@, взятая в двойные кавычки представляет собой правильный массив переданных аргументов, в котором те аргументы, которые были заключены в кавычки, представляют единое целое (в отличие от бескавычечной версии, в которой все содержимое переданных аргументов разбивается по пробельным символам). Теперь нужно использовать новую функцию в ngrep():
function ngrep()
{
    eval sudo `env which ngrep` `quote_args_if_needed "$@"` | hl -sngrep
}
Как видите, мне пришлось обернуть все в eval. Это потому, что кавычки для аргументов, которые нам удалось вернуть с помощью quote_args_if_needed(), будут переданы без eval в `env which ngrep` дословно, и он будет ругаться на syntax error. Команда eval очистит нашу строку от кавычек, и при этом сохранит пустые аргументы и аргументы, содержащие пробелы.

суббота, 10 сентября 2011 г.

Использование пользовательской подсветки команд в терминале (на примере make)

Сформулируем задачу следующим образом: мы хотим обеспечить собственные правила подсветки вывода произвольной команды в терминале. Для простоты будем считать, что наш терминал поддерживает 256 цветов (что уже давно справедливо и для vte, и для konsole, и для xterm) и мы используем bash. Задача абсолютно нетривиальная хотя бы потому, что нормального полноценного решения ее, насколько я знаю, пока не существует. Если бы вышеперечисленные терминалы поддерживали систему плагинов, то решить ее было бы, наверное, просто, но на данный момент это не так.

Стоит отметить, что существует целый ряд решений, которые могут помочь с расцвечиванием консоли для разных программ: это colordiff, GNU souce-highlight (который я часто использую для подсветки кода в этом блоге) и другие, а также использование различных цветовых опций общеизвестных команд, таких как ls, tree, grep и т.д., например в моем файле .bashrc определены алиасы
alias ls='ls -F --color=tty'
alias tree='tree -C'
Недостатки этих программ очевидны: собственно, они не решают поставленную нами задачу, так как предназначены для подсветки вывода заранее определенных программ по заранее определенным правилам.

Совсем недавно я прочитал про программу grc (generic colouriser). Точный ресурс сейчас точно не вспомню, но из свеженайденных - например здесь. Это уже что-то: в конфигах grc можно задать правила подсветки для вывода произвольной программы, основанные на регулярных выражениях. Я не стал устанавливать данную программу, поскольку имею собственное решение, очень похожее по смыслу и, надеюсь, не уступающее по возможностям. На нем и остановимся поподробнее.

Программа была написана на perl еще в 2007 или 2006 году. Она состоит из двух частей: модуля Term::Highlight, реализующего основную работу по поиску вхождений, соответствующих заданным регулярным выражениям, и вставке цветовых тегов на их границы, и скрипта hl, в котором определяются режимы работы программы и куда передаются аргументы, определяющие подсветку. В пакет программы входят справочные страницы man, поэтому разобраться в ней будет несложно. Скачать программу можно с sourceforge.net (здесь), или с cpan.org (здесь).

Изначально я планировал использовать Term::Highlight как фильтр для подсветки слов или регулярных выражений в длинном выводе программ, например так:
$ cat some_long_file | hl -100 some_word -101 another_word
Здесь осуществляется вывод на экран длинного файла some_long_file, при этом слова some_word и another_word будут подсвечены цветами, соответствующими значениям 100 и 101 в 256-цветной палитре терминала. Term::Highlight прекрасно справляется в ситуации, когда участки текста, соответствующие заданным словам или регулярным выражениям в hl оказываются вложены или пересекаются (причем многократно) друг с другом, например в случае
$ cat some_long_file | hl -100 some_word -101 'w\w+'
внутри подсвеченного цветом 100 слова some_word будет подсвечен цветом 101 участок, соответствующий выражению 'w\w+', т.е. слово word. Кроме этого, Term::Highlight поддерживает 8-цветные консоли, поиск в двоичных файлах, установку жирного текста, поиск без учета регистра и др. В дальнейшем я реализовал режим цветного grep и поддержку сниппетов.

Теперь подробнее о сниппетах. Очевидно, что для сложной подсветки, например синтаксиса какого-нибудь языка, нам понадобится передать в hl огромное число аргументов - это не удобно, а главное, система скорее всего не справится с такой длинной строкой и придется использовать xargs, а это очень неудобно. Поэтому в  версии 1.7 была добавлена возможность записывать именованные сниппеты в файле $HOME/.hlrc. Сниппеты содержат аргументы подсветки hl и их можно передавать по имени с помощью опции -s. Ниже мы напишем сниппет по имени make для подсветки вывода одноименной команды.

Буквально вчера я внес в Term::Highlight изменение, связанное с безопасным использованием hl в пайпах. Новая версия имеет номер 1.7.1. Отныне hl знает, куда направлен ее вывод, и если это не stdout, то цветовые теги вставляться не будут. Это поможет использовать hl для подсветки вывода произвольных программ и не беспокоиться в том случае, если вывод подсвечиваемой программы направлен через пайп на вход другой программы. Напомню, что точно так же работает ls --color=tty: если ее  вывод перенаправить в файл или на вход другой программы, или раздвоить с помощью tee , то цветовые теги исчезнут.

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

Я предлагаю сделать так: в файле $HOME/.bash_profile установить переменную HL_ALIASES, которая будет указывать на путь к файлу, содержащему нашу функцию. Пусть этот файл называется .hl_functions. Тогда в .bash_profile записываем строку
export HL_ALIASES=$HOME/.hl_functions
Затем в $HOME/.bashrc считываем содержимое этого файла:
[ -n "$HL_ALIASES" ] && [ -f "$HL_ALIASES" ] && . $HL_ALIASES
В файл .hl_functions поместим функцию make (в дальнейшем туда же можно поместить функции, определяющие подсветку вывода других программ):
function make()
{
    `env which make` $@ 2>&1 | hl -smake
}
Все очень просто: передаем stdout и stderr настоящего make (`env which make`) со всеми аргументами на вход команды hl, которая преобразует его на основании сниппета make (hl -smake). Такой подход безопасен в случае использования вывода подсвечиваемой программы для передачи на вход другой: hl не будет подставлять цветовые теги в этом случае. Более того, поскольку функция make() определена в .bashrc, то скриптам она будет неизвестна, поскольку объекты .bashrc определены для интерактивного использования. Соответственно можно совершенно не беспокоиться, если подсвечиваемая команда будет использоваться в каком-нибудь скрипте: в этом случае всегда будет вызываться настоящая программа. (Если вы действительно хотите использовать функции подсветки в скриптах, чего я делать крайне не рекомендую, то сорсить .hl_functions следует не в .bashrc, а в .bash_profile, а затем экспортировать определенные функции с помощью export -f).

Теперь приведем определение сниппета make, которое следует поместить в $HOME/.hlrc:
snippet make    -b -215 '^\s*gcc\b' '^\s*g\+\+(?=\s)' '^\s*libtool:\s*\w+:' \
                '^s*\/bin\/sh\s+[\w/.]+' -rb -108 '[\w/.-]+\.c\b(?!:)' \
                -119 '[\w/.-]+\.(cc|cpp|cxx|c\+\+)\b(?!:)' \
                -30 '\s*[\w/.*-]+\.o\b' -203 '(?<=\s-o)\s*[^-][\w/.-]*' \
                -42 '^\s*make\[\d+\]' -64 '(?:^|\s+)\-l[\w/+-]+' \
                -50 '^[\w/.-]+\.\w+:(?:\d+:\d+:)?' \
                -204 '^\s*(?:rm|mv|ln|cp)\s+(?:-\w+)?' \
                -196 '(?:О|о)шибка( \d+)?' -202 '(?:П|п)редупреждение( \d+)?' \
                -120 'Выход из.*$' 'Вход в.*$'
С помощью числовых тегов здесь определены цвета для следующих за ними регулярных выражений, на которых я останавливаться не буду - безусловно, для того чтобы эффективно использовать этот подход, вам самим необходимо знать, как составляются регулярные выражения perl. Тег (или опция) -b устанавливает, что следующие за ним слова и выражения нужно печатать жирным шрифтом, тег -rb отменяет жирное начертание. Подробнее о различных опциях hl можно прочитать на странице справки man hl. В приведенных выше правилах сниппета make задаются цвета подсветки команд gcc, g++, libtool и др., исходников C и C++, объектных файлов с суффиксом .o, предупреждений и ошибок компилятора (на русском языке), сообщений о входе и выходе make в директории, подключаемых с помощью опции -l библиотек, названий файлов, получаемых на выходе, которые задаются с помощью опции компилятора -o и других артефактов. Самое замечательное, что данные опции можно изменять и добавлять и они сразу становятся эффективным без ре-сорсинга файла .hl_functions.

А теперь главное - картинка. Это пример работы функции make при компиляции исходников sane-backends (картинка кликабельна):