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

среда, 31 августа 2016 г.

Бенчмаркинг cgrep, ack и hlgrep

Когда-то я сравнивал cgrep и ack с точки зрения функциональных возможностей, пришла пора сравнить их скорости выполнения. Кроме того, недавно мне удалось серьезно оптимизировать программу hl, да так, что при рекурсивном грепе она стала во многих случаях быстрее, чем ack. Сильной оптимизации было подвергнуто время старта программы, поэтому преимущество в скорости особенно заметно при небольшом объеме входных данных. Как и ack, hl написана на perl, но реализует другую функциональность, а именно богатую подсветку входных данных, в том числе на основе сниппетов. Таким образом, рекурсивный греп в hl — возможность чисто факультативная, без большого количества опций, доступных в cgrep и ack. Тем интересней добавить его для сравнения к двум последним. Я не стал добавлять для сравнения стандартный grep. Ему по плечу такие простые задачи, как рекурсивный поиск символов, однако он настолько быстр, что с легкостью опережает на порядок все три узкоспециализированные программы (в частности, на моей машине скорость поиска символов ARGS программой grep внутри директории /usr/include/boost в четыре раза превосходит скорость cgrep). Для бенчмаркинга я использовал замечательную программу bench, которая по сути является простым объединением библиотек criterion (я писал о criterion здесь) и turtle. Поскольку я хочу погонять шелл-функциию cgr, представленную в статье, ссылка на которую приводится в самом начале данной статьи, мне придется обернуть команду для bench в bash -ci — ведь turtle в лице bench не является полноценной bash-совместимой оболочкой и не способна загружать функции bash. Для hl я тоже буду использовать шелл-функцию hlgrep, которая определена так:
function hlgrep
{
    `env which hl` -t -r -n "$@" |
            sed 's/:\(\x1b\[22;38;5;224;49m\)\([[:digit:]]\+\)'`
               `'\(\x1b\[22;38;5;248;49m\)\(\x1b\[0m\): / ⎜ \1\2\1\3 ⎜ /' |
            column -t -s-o|
            sed 's/\(\s\+\)\(\(\x1b\[22;38;5;224;49m\)[[:digit:]]\+\3'`
               `'\x1b\[22;38;5;248;49m\)\(\s\+\)/\4\2\1/' |
            hl -u -216 '\x{239C}'
}
Но даже чистую cgrep нужно будет обернуть в bash. Проблема в том, что bench подменяет канал stdin в дочерних процессах, запущенных для тестирования, на новый канал, а cgrep, определив подмену, считает, что последний аргумент его командной строки, который обычно указывает на директорию для чтения входных файлов, должен быть просто еще одним элементом для поиска. Таким образом, нам придется сначала выполнить cd, а уже затем запускать cgrep, но уже без ссылки на директорию. Проще всего это сделать в отдельной оболочке. По той же самой причине подмены stdin, в ack нужно будет добавить специальную опцию --nofilter, которая, впрочем, не должна повлиять на его скорость. Есть еще один тонкий момент, связанный с bench: он аварийно завершается в случае, если тестируемая программа возвращает не нулевое значение. Именно это делает ack, если не находит ни одного совпадения. Поэтому перед запуском bench следует проверить, что совпадения имеются для всех тестов с ack (и, автоматически, для других тестируемых программ), при этом желательно, чтобы совпадений было больше — в конце концов мы тестируем полезную нагрузку. В качестве сценариев для тестирования я выбрал поиск символов ARGS в директории /usr/include/boost на моей машине и поиск символов parse внутри относительно небольшого проекта cexmc. Во втором случае я проверил также чистые cgrep, hl и ack. Каждый тест длился по 60 секунд. И вот что из этого вышло.
bench -v2 -L60 \
'bash -ci "(cd /usr/include/boost; cgr ARGS)"' \
'bash -ci "(cd /usr/include/boost; hlgrep ARGS)"' \
'bash -ci "(cd /usr/include/boost; ack --nofilter ARGS)"' \
'bash -ci "(cd ~/devel/cexmc; cgr parse)"' \
'bash -ci "(cd ~/devel/cexmc; hlgrep parse)"' \
'bash -ci "(cd ~/devel/cexmc; ack --nofilter parse)"' \
'bash -ci "(cd ~/devel/cexmc; cgrep -r parse)"' \
'bash -ci "(cd ~/devel/cexmc; hl -r parse)"'  \
'bash -ci "(cd ~/devel/cexmc; ack --nofilter --noenv parse)"' \
-o ~/bench-cgrep-hlgrep-ack-60s.html
benchmarking bench/bash -ci "(cd /usr/include/boost; cgr ARGS)"
analysing with 1000 resamples
measurement overhead 624.0 ns
bootstrapping with 11 of 12 samples (91%)
time                 694.1 ms   (644.7 ms .. 738.5 ms)
                     0.992 R²   (0.982 R² .. 0.999 R²)
mean                 667.6 ms   (653.5 ms .. 687.1 ms)
std dev              29.20 ms   (19.88 ms .. 38.25 ms)
variance introduced by outliers: 8% (slightly inflated)

benchmarking bench/bash -ci "(cd /usr/include/boost; hlgrep ARGS)"
analysing with 1000 resamples
bootstrapping with 5 of 6 samples (83%)
time                 2.129 s    (2.004 s .. 2.287 s)
                     0.998 R²   (0.992 R² .. 1.000 R²)
mean                 2.197 s    (2.141 s .. 2.249 s)
std dev              68.12 ms   (47.76 ms .. 82.33 ms)
variance introduced by outliers: 14% (moderately inflated)

benchmarking bench/bash -ci "(cd /usr/include/boost; ack --nofilter ARGS)"
analysing with 1000 resamples
bootstrapping with 5 of 6 samples (83%)
time                 2.250 s    (2.211 s .. 2.360 s)
                     0.999 R²   (0.995 R² .. 1.000 R²)
mean                 2.239 s    (2.219 s .. 2.279 s)
std dev              32.61 ms   (5.023 ms .. 43.91 ms)
found 1 outliers among 5 samples (20.0%)
  1 (20.0%) high mild
variance introduced by outliers: 14% (moderately inflated)

benchmarking bench/bash -ci "(cd ~/devel/cexmc; cgr parse)"
analysing with 1000 resamples
bootstrapping with 36 of 37 samples (97%)
time                 77.04 ms   (76.21 ms .. 78.01 ms)
                     0.999 R²   (0.999 R² .. 1.000 R²)
mean                 76.23 ms   (75.70 ms .. 76.84 ms)
std dev              1.794 ms   (1.429 ms .. 2.352 ms)
found 2 outliers among 36 samples (5.6%)
  1 (2.8%) low mild
  1 (2.8%) high mild
variance introduced by outliers: 8% (slightly inflated)

benchmarking bench/bash -ci "(cd ~/devel/cexmc; hlgrep parse)"
analysing with 1000 resamples
bootstrapping with 33 of 34 samples (97%)
time                 94.47 ms   (93.40 ms .. 95.40 ms)
                     1.000 R²   (0.999 R² .. 1.000 R²)
mean                 93.74 ms   (93.27 ms .. 94.17 ms)
std dev              1.325 ms   (1.054 ms .. 1.740 ms)
variance introduced by outliers: 3% (slightly inflated)

benchmarking bench/bash -ci "(cd ~/devel/cexmc; ack --nofilter parse)"
analysing with 1000 resamples
bootstrapping with 29 of 30 samples (96%)
time                 121.5 ms   (121.0 ms .. 122.0 ms)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 121.6 ms   (121.1 ms .. 122.2 ms)
std dev              1.449 ms   (1.174 ms .. 1.830 ms)
variance introduced by outliers: 3% (slightly inflated)

benchmarking bench/bash -ci "(cd ~/devel/cexmc; cgrep -r parse)"
analysing with 1000 resamples
bootstrapping with 38 of 39 samples (97%)
time                 68.35 ms   (66.80 ms .. 70.55 ms)
                     0.996 R²   (0.994 R² .. 0.999 R²)
mean                 68.03 ms   (67.52 ms .. 68.83 ms)
std dev              2.001 ms   (1.543 ms .. 2.696 ms)
found 5 outliers among 38 samples (13.2%)
  4 (10.5%) high mild
  1 (2.6%) high severe
variance introduced by outliers: 12% (moderately inflated)

benchmarking bench/bash -ci "(cd ~/devel/cexmc; hl -r parse)"
analysing with 1000 resamples
bootstrapping with 34 of 35 samples (97%)
time                 87.98 ms   (87.55 ms .. 88.39 ms)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 88.04 ms   (87.77 ms .. 88.32 ms)
std dev              836.7 μs   (676.7 μs .. 1.051 ms)
variance introduced by outliers: 3% (slightly inflated)

benchmarking bench/bash -ci "(cd ~/devel/cexmc; ack --nofilter --noenv parse)"
analysing with 1000 resamples
bootstrapping with 29 of 30 samples (96%)
time                 120.8 ms   (119.5 ms .. 122.5 ms)
                     0.999 R²   (0.999 R² .. 1.000 R²)
mean                 119.7 ms   (119.3 ms .. 120.3 ms)
std dev              1.456 ms   (1.006 ms .. 2.055 ms)
found 3 outliers among 29 samples (10.3%)
  3 (10.3%) high mild
variance introduced by outliers: 3% (slightly inflated)
Графический отчет здесь. Выводы следующие: cgrep быстрее всех, особенно при большом объеме входных данных (в директории /usr/include/boost) — 0.668 против 2.20 и 2.24 сек у hlgrep и ack. При небольших объемах данных (в директории cexmc) cgrep по-прежнему лидирует, хоть и с небольшим отрывом (0.076 сек), hlgrep значительно опережает ack (0.094 против 0.122 сек) за счет стартовой скорости. Чистые варианты cgrep, hl и ack в директории cexmc незначительно быстрее шелл-функций (0.068 против 0.076 сек в случае cgrep, 0.088 против 0.094 сек в случае hl и 0.120 против 0.122 сек в случае ack) — это говорит о том, что конвейеры, состоящие из программ sed, column и hl, из которых собраны эти функции, достаточно быстры.

среда, 11 ноября 2015 г.

cgrep vs ack

Cgrep и ack — это две grep-подобные программы, приспособленные для поиска внутри исходных текстов на разных языках программирования. Ack написана на perl и существует довольно давно, в то время как cgrep — относительно новая программа, написанная на haskell и обладающая большим количеством экспериментальных возможностей. Я не буду подробно сравнивать всевозможные аспекты использования этих программ, но отмечу их наиболее яркие положительные качества и недостатки и в конце приведу мои собственные настройки для декорирования их вывода на терминал. Итак, начнем с положительных качеств ack.
  • Возможность настроить рекурсивный поиск только внутри исходников указанного языка программирования (или нескольких языков). Соответствие файла определенному языку программирования определяется несколькими способами: расширением имени файла, точным именем файла (например, Makefile для make) и соответствием имени файла или первой строки в файле заданному шаблону регулярного выражения. Настройки соответствий языков по умолчанию можно вывести с помощью опции ack --dump: список языков находится внутри опций --type-add. Собственно включение в поиск определенного языка задается синтетической опцией --язык, где язык соответствует одному из значений в выведенном списке, стоящих сразу после символа =. Например, чтобы включить поиск внутри исходников C++, нужно указать опцию --cpp.
  • Возможность гибкой настройки вывода контекста вокруг строк с найденными совпадениями: для этого имеются целых три опции -A, -B и -C.
  • Гибкая настройка формата вывода, включая цветовую подсветку найденных совпадений и возможность перенаправления во внешнюю программу или скрипт с опцией --pager.
  • Задание настроек по умолчанию в файле .ackrc в домашней директории и поддиректориях.
  • Поиск файлов по имени или регулярному выражению (опция -g). Это просто киллер-фича! Теперь от громоздкой и негибкой команды find . -name '*pattern*' можно отказаться. Или почти отказаться… Есть два тонких момента, даже три. Во-первых, имеются опции по умолчанию --ignore-file, которые можно вывести с помощью опции --dump: в них указано, какие файлы будут игнорироваться при поиске. В частности, туда должны входить файлы jpg, png и тому подобные. Чтобы отменить все настройки по умолчанию, в том числе настройки игнорирования файлов, нужно просто добавить опцию --ignore-ack-defaults. Но… Вряд ли после этого ack начнет находить jpg и png файлы! Потому что они бинарные! И это было во-вторых. Чтобы преодолеть это ограничение, нужно поступить так, как сказано здесь, то есть добавить еще две опции --type-set='all:match:.*' -k (см. также определение алиаса ackf в конце этой статьи). Ну а в-третьих, пустые директории, а также всякого рода потоки и другие не-файлы, искусственно привязанные к файловой системе (сокеты, FIFO и т.п.), ack будет игнорировать в любом случае.
Прежде чем перейти к плюсам cgrep, я отмечу ее серьезный недостаток по сравнению с ack. Это невозможность вывода контекста вокруг строк с найденными совпадениями. А теперь плюсы.
  • Скорость и возможность параллельного исполнения. Это все-таки haskell!
  • Поддержка разных типов поиска. Кроме дословного поиска и поиска совпадения с регулярным выражением (включая POSIX и PCRE), сюда входят префиксный и суффиксный поиски, а также поиск с учетом расстояния Левенштейна. Последний тип поиска позволяет находить похожие слова или слова с возможными ошибками.
  • Поддержка семантического поиска позволяет искать совпадения в коде, комментариях или строковых литералах отдельно (опции -c, -m и -l), ограничивать поиск специфическими типами идентификаторов, такими как ключевые слова, литералы, директивы препроцессора и операторы (наилучшим образом поддерживаются языки C и C++). Опция -S позволяет настроить семантический поиск с помощью специального семантического языка (простейшие примеры можно увидеть, запустив cgrep --help).
  • Как и в ack, можно настроить поиск только внутри исходников определенного языка (или языков). Список поддерживаемых языков и соответствующие им расширения имен файлов можно вывести с помощью опции --lang-maps. Чтобы включить в поиск определенный язык, нужно указать его в опции --lang, например, --lang=Cpp.
  • Как и в ack, можно гибко настроить формат вывода с помощью опции --format. Вывод подсветки задается, но сами цвета не настраиваются (в функции cgr, которую я приведу ниже, изменение цветов подсветки достигается с помощью обработки в sed).
  • Некоторые настройки по умолчанию можно записать в файле .cgreprc в домашней директории.
Нужно отметить, что поиск на основе регулярных выражений в cgrep отличается от того, как это реализовано в ack или в обычном grep. Если последние ищут совпадения в каждой отдельной строке ввода, то cgrep применяет шаблон ко всему входному тексту. Это может приводить к тонким семантическим отличиям для некоторых шаблонов. Например, класс символов \s включает переносы строк, а значит найденное совпадение после применения шаблона с \s может быть многострочным. Ситуация осложняется тем, что cgrep выведет только первую строку совпадения (а это уже баг, на мой взгляд), которая может не содержать значимой информации, тем самым дезориентируя пользователя. Поэтому я советую вместо \s использовать класс горизонтальных пробелов \h (правда, он доступен только в PCRE) — это будет гарантировать отсутствие переносов строк в найденном совпадении. Ниже я привожу картинку с выводом ack и cgrep на моем терминале, а также настройки и скрипты, которые позволяют добиться этого результата. Файл .ackrc.
--color-match=rgb551
--color-filename=rgb342
--color-lineno=rgb233

--noheading
--pager=sed -e 's/\(.\+\)\([:-]\)\(\x1b\[38;5;109m[[:digit:]]\+\x1b\[0m\)\2/\1 ⎜ \3 ⎜ /'
            -e 's/\x1b/@eCHr@/g' | column -t -s-o⎜ |
        sed -e 's/@eCHr@/\x1b/g'
            -e 's/\(\s\+\)\(\x1b\[38;5;109m[[:digit:]]\+\x1b\[0m\)\(\s\+\)/\3\2\1/' |
        hl -u -255 -b '^--$' -rb -216 '\x{239C}'
Обратите внимание, я разбил последнюю строку с опцией --pager на пять отдельных строк для удобочитаемости. На самом деле это должна быть одна строка и разбивать ее на части нельзя! А это функция-обертка cgr (ее можно поместить в .bashrc).
function cgr
{
    `env which cgrep` --color --format='#f ⎜ #n ⎜ #l' -r "$@" |
            sed -e 's/\x1b\[1m\x1b\[94m/@eCHrF@/' \
                -e 's/\x1b\[1m/@eCHrB@/g' \
                -e 's/\x1b\[m/@eCHrE@/g' |
            column -t -s-o|
            sed -e 's/@eCHrF@/\x1b\[38;5;150m/' \
                -e 's/@eCHrB@/\x1b\[38;5;227m/g' \
                -e 's/@eCHrE@/\x1b\[m/g' \
                -e 's/\(\s\+\)\([[:digit:]]\+\)\(\s\+\)/\3\2\1/' |
            hl -u -73 '\h+\d+\h+(?=\x{239C})' -216 '\x{239C}'
}
(Здесь переносы допустимы). Программа hl доступна отсюда — она нужна для дополнительной подсветки колонки с номерами строк, я писал о ней здесь и здесь. Ну и алиас для поиска файлов с помощью ack.
alias ackf='ack --ignore-ack-defaults --type-set=all:match:. -k --color --nopager -g'
Update. Начиная с версии util-linux 2.28 программа column научилась игнорировать управляющие последовательности ANSI, поэтому ужасные вре́менные замены текста типа @eCHr@ в опции --pager из .ackrc и в функции cgr больше не нужны. Соответственно, теперь они будут выглядеть так (содержимое опции --pager должно по-прежнему находиться в одной строке).
--pager=sed 's/\(.\+\)\([:-]\)\(\x1b\[38;5;109m[[:digit:]]\+\x1b\[0m\)\2/\1 ⎜ \3 ⎜ /' |
        column -t -s-o⎜ |
        sed 's/\(\s\+\)\(\x1b\[38;5;109m[[:digit:]]\+\x1b\[0m\)\(\s\+\)/\3\2\1/' |
        hl -u -255 -b '^--$' -rb -216 '\x{239C}'

function cgr
{
    `env which cgrep` --color --format='#f ⎜ #n ⎜ #l' --no-column -r "$@" |
            column -t -s-o|
            sed -e 's/\x1b\[1;94m/\x1b\[38;5;150m/' \
                -e 's/\x1b\[1m/\x1b\[38;5;227m/g' \
                -e 's/\(\s\+\)\([[:digit:]]\+\)\(\s\+\)/\3\2\1/' |
            hl -u -73 '\h+\d+\h+(?=\x{239C})' -216 '\x{239C}'
}
Также обратите внимание на некоторые изменения в функции cgr, связанные с обновленным cgrep, в частности на новую опцию --no-column.

четверг, 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 (картинка кликабельна):

вторник, 17 августа 2010 г.

Приемы визуализации состояния кода под управлением Subversion

Здесь я не буду рассматривать стандартные приемы визуализации типа svn log, svn log -v и т.п. Вместо этого я хотел бы показать несколько скриптов-однострочников, которые можно запустить из командной строки оболочки и которые эффективно отражают состояние рабочей версии кода, управляемого системой контроля версий. Поскольку я чаще всего работаю с Subversion, я выбрал именно эту систему контроля версий, хотя это вовсе не обязательное условие и в принципе эти команды при необходимости могут быть адаптированы под любую другую систему, например CVS. В качестве примера рабочей версии кода я взял свой проект sudokurapid. Итак, переходим к приемам.
  1. Самая простая задача. Найти все исходники (например, .cc и .h файлы) в рабочей директории и вывести статистику по количеству строк, слов и символов. В данном случае присутствие какой-либо системы контроля версий вообще не требуется.
    $ find . -name '*.cc' -o -name '*.h' | xargs wc | sort -n
       33    60   651 ./sudokuRapidCommon.h
       62   146  1201 ./sudokuRapidQt/main.cc
       66   129  1277 ./sudokuRapidQt/sudokuForm.h
       66   156  1456 ./sudokuRapidQt/sudokuScene.h
      117   272  2684 ./sudokuRapidQt/sudokuCell.h
      167   373  3654 ./sudokuRapid.h
      171   497  4244 ./sudokuRapidConsole/main.cc
      175   603  4738 ./sudokuRapidQt/sudokuScene.cc
      191   499  4377 ./sudokuRapidQt/sudokuForm.cc
      248   781  6609 ./sudokuRapidQt/sudokuCell.cc
      481  1702 14315 ./sudokuRapid.cc
     1777  5218 45206 итого 
  2. Вывести список всех ревизий, выделив разными цветами имена отдельных пользователей (в данном проекте только один пользователь lyokha, но допустим, что имеется еще один пользователь someone)
    $ svn log | grep '^r[0-9]' | hl -46 lyokha -47 someone
    r16 | lyokha | 2010-08-17 11:40:36 +0400 (Втр, 17 Авг 2010) | 2 lines
    r15 | lyokha | 2009-03-23 16:22:27 +0300 (Пнд, 23 Мар 2009) | 2 lines
    r14 | lyokha | 2009-03-23 15:16:04 +0300 (Пнд, 23 Мар 2009) | 11 lines
    r13 | lyokha | 2009-02-19 22:00:51 +0300 (Чтв, 19 Фев 2009) | 2 lines
    r12 | lyokha | 2009-02-16 14:39:36 +0300 (Пнд, 16 Фев 2009) | 2 lines
    r11 | lyokha | 2009-02-16 14:23:15 +0300 (Пнд, 16 Фев 2009) | 5 lines
    r10 | lyokha | 2009-02-14 17:28:27 +0300 (Сбт, 14 Фев 2009) | 2 lines
    r9 | lyokha | 2009-02-14 16:04:59 +0300 (Сбт, 14 Фев 2009) | 3 lines
    r8 | lyokha | 2009-02-12 19:45:31 +0300 (Чтв, 12 Фев 2009) | 18 lines
    r7 | lyokha | 2009-02-11 23:27:04 +0300 (Срд, 11 Фев 2009) | 2 lines
    r6 | lyokha | 2009-02-11 22:08:13 +0300 (Срд, 11 Фев 2009) | 13 lines
    r5 | lyokha | 2009-02-07 14:39:56 +0300 (Сбт, 07 Фев 2009) | 2 lines
    r4 | lyokha | 2009-02-06 14:06:17 +0300 (Птн, 06 Фев 2009) | 3 lines
    r3 | lyokha | 2009-02-06 11:31:39 +0300 (Птн, 06 Фев 2009) | 2 lines
    r2 | lyokha | 2009-02-06 11:27:49 +0300 (Птн, 06 Фев 2009) | 2 lines
    r1 | lyokha | 2009-02-05 16:24:32 +0300 (Чтв, 05 Фев 2009) | 2 lines

    В данном случае я использовал утилиту hl, которая позволяет подсвечивать разными цветами (46 и 47 - это номера цветов для 256-цветного терминала) шаблоны (lyokha и someone) прямо в терминале. Если бы в списке был пользователь someone, его имя было бы подсвечено другим цветом. 
  3. Вывести статистику работы для определенного пользователя (lyokha) в псевдографическом виде. Здесь я использую замечательную утилиту diffstat:
    $ for i in `svn log | grep lyokha | awk '{print $1}' | sed 's/r//'`
    > do svn diff -c$i
    > done | diffstat -m -f 4 -w 80
     ChangeLog                       |   62    62     0     0 ++++
     Makefile.am                     |   41    36     0     5 ++!
     README                          |   37    37     0     0 ++
     configure.ac                    |   77    72     0     5 +++++
     sudokuRapid.cc                  |  539   507    26     6 +++++++++++++++++++++++++++++++++++++--
     sudokuRapid.h                   |  172   169     2     1 ++++++++++++
     sudokuRapidCommon.h             |   33    33     0     0 ++
     sudokuRapidConsole/Makefile.am  |    4     3     0     1 
     sudokuRapidConsole/configure.ac |   23    23     0     0 +
     sudokuRapidConsole/getStat.sh   |   54    54     0     0 ++++
     sudokuRapidConsole/main.cc      |  172   171     0     1 ++++++++++++
     sudokuRapidQt/main.cc           |   63    62     0     1 ++++
     sudokuRapidQt/sudokuCell.cc     |  346   256     8    82 ++++++++++++++++++-!!!!
     sudokuRapidQt/sudokuCell.h      |  138   117     0    21 ++++++++!!
     sudokuRapidQt/sudokuForm.cc     |  274   215    24    35 +++++++++++++++--!
     sudokuRapidQt/sudokuForm.h      |   72    68     2     2 +++++
     sudokuRapidQt/sudokuForm.ui     |   97    97     0     0 +++++++
     sudokuRapidQt/sudokuRapidQt.pro |   16    15     0     1 +
     sudokuRapidQt/sudokuScene.cc    |  205   182     7    16 +++++++++++++-
     sudokuRapidQt/sudokuScene.h     |   71    67     1     3 ++++-
     sudokurapid.spec                |  103    91     3     9 ++++++
     21 files changed, 2337 insertions(+), 73 deletions(-), 189 modifications(!)
    В первом столбце указано имя файла, в следующих - общее количество измененных строк, количество вставленных, удаленных и измененных строк, а также псевдографическая гистограмма активности изменений. Стоит заметить, что количество измененных строк (отмеченных символом !) определяется эвристически на основе анализа отдельных чанков, и поэтому в редких случаях баланс вставленных/удаленных/измененных строк может немного не соответствовать действительности.
  4. Можно графически визуализировать процесс изменения репозитория во времени с использованием утилиты gource. gource не поддерживает Subversion напрямую, поэтому нам понадобится дополнительный скрипт svn-gource.py. Приготовления включают создание промежуточных файлов:
    svn log --verbose --xml > sudokurapid.log
    svn-gource.py --filter-dirs sudokurapid.log > sudokurapid-gource.log
    После этого запускаем
    gource --log-format custom sudokurapid-gource.log
    откидываемся на спинку кресла и смотрим замечательный фильм. Вот, например, один из кадров: