вторник, 24 декабря 2013 г.

Что делать, если у вас маленький /, а обновить Федору очень хочется

В общем-то, не обязательно /, в общем случае это точка монтирования файловой системы, в которой находятся директории /var/lib/ и /var/tmp/. На моем рабочем компьютере эти директории находятся в корневой файловой системе, размер которой составляет всего 20 Гб. Это приводит к проблемам при штатном обновлении ядра через yum, что уж тогда говорить об обновлении всей системы.

Итак, обновляться я решил вчера, 23 декабря 2013 года, с Fedora 19 до Fedora 20, то есть уже после официального выхода Fedora 20. Инструмент для обновления - fedup-0.8.0-3, совсем недавно заменивший версию 0.7. Он запускается из командной строки и в процессе загрузки пакетов использует директории /var/lib/system-upgrade/ и /var/tmp/system-upgrade/. Опций для настройки других директорий нет (хотя в файле TODO.asciidoc, входящем в состав пакета fedup, такая возможность анонсируется в будущем). То есть, если запускать fedup без всяких предварительных настроек следующим образом:
fedup --network 20 --nogpgcheck
, то мой корневой раздел будет заполнен до отказа еще до начала реального обновления системы. Кстати, обратите внимание на опцию --nogpgcheck. Как я понял, она появилась в версии fedup 0.8, и без нее мой fedup не захотел работать.

Итак, что же делать? Самое очевидное решение - снести директории /var/lib/system-upgrade/ и /var/tmp/system-upgrade/ вместе с их содержимым, найти диск, на котором достаточно места для пары-тройки гигабайт обновлений, и создать символические ссылки на эти директории из /var/lib/system-upgrade и /var/tmp/system-upgrade. У меня нашлось достаточно места на диске, который монтируется в корневую файловую систему как /home. Поэтому, от имени суперпользователя я создал на нем две директории /home/fedup/cache/ и /home/fedup/packages/, а затем и упомянутые символические ссылки:
ln -s ../../home/fedup/cache /var/tmp/system-upgrade
ln -s ../../home/fedup/packages /var/lib/system-upgrade
Обратите внимание на то, что ссылки задаются не через абсолютные пути, а через относительные, это очень важно! Дело в том, что после перезагрузки системы для ее обновления все файловые системы, в том числе /home, будут смонтированы в /sysroot, а не в /, и загрузчик просто не сможет найти пакеты для обновления (см. об этом здесь).

В общем-то, на этом историю про обновление Fedora с небольшим дисковым пространством можно было бы и закончить. Но не тут-то было! Обновление Федоры - это почти всегда приключение, сопряженное с риском и опасностью, в которых не место домохозяйкам! После перезагрузки системы для дальнейшего обновления и загрузки нескольких сервисов systemd, запустился релэйблинг SELinux, как я понял на загруженные новые пакеты (хотя SELinux в системе у меня отключен). Помурыжив минут 15, компьютер перезагрузился, оставив опцию с апгрейдом системы в верхней строке загрузчика GRUB2. Я попробовал загрузить апгрейд снова: в итоге обычная загрузка GDM как ни в чем не бывало. То есть система не обновилась! Я перезагрузился в старое ядро от Fedora 19, снес Upgrade опцию из загрузчика:
fedup --resetbootloader
и запустил fedup--network 20 --nogpgcheck сначала. После серии таких попыток обновления я понял, что проблема не в новых пакетах, установленных неожиданным для fedup образом, а в чем-то другом. На правильный путь меня натолкнуло описание этого бага. В одном из комментариев было сказано про аргумент ядра systemd.unit=system-upgrade.target, который устанавливал старый fedup 0.7 для старта процесса обновления после перезагрузки системы. Я открыл /boot/grub2/grub.cfg, нашел секцию, соответствующую апгрейду системы, и вставил туда этот параметр, на авось, поскольку так и не понял, нужен ли он новому fedup 0.8 и установочному образу ядра. Кроме того, добавил туда же опцию selinux=0, чтобы не ждать релэйблинга новых пакетов (или чего-то там еще, не суть важно). Более того, новый fedup был так добр, что не установил в аргументы загрузки ядра опцию plymouth.splash=fedup: это в дальнейшем позволило наблюдать за реальным процессом установки пакетов в консоли, а не бестолковый графический прогрессбар. Если вы тоже захотите удалить эту опцию, то не пугайтесь, когда в процессе установки консоль заснет и дисплей погаснет: просто нажмите на клавиатуре Shift - это разбудит консоль.

Итак, как вы уже поняли, после добавления в аргументы загрузки ядра опции с systemd.unit, процесс пошел. Система обновилась, и после перезагрузки опция с апгрейдом системы ушла из меню GRUB2, что и должно было произойти. Однако, в момент загрузки новой Fedora 20 вернулась проблема с SELinux, с одной стороны неудивительно - я ведь устанавливал ее только для ядра, загружаемого для обновления системы. С другой стороны непонятно, почему fedup не вычистил эти пакеты сразу после обновления. После загрузки Fedora 20 и запуска fedup --clean все стало понятно: fedup сообщил, что он не в состоянии выполнить rmtree на символических ссылках. Пришлось вручную удалять все содержимое из директорий /home/fedup/cache/ и /home/fedup/packages/.

Осталось сказать, что проблему с нехваткой дискового пространства при обновлении ядра через yum решить просто - намного проще, чем с fedup. Создаем директорию /home/yum/cache/, открываем файл /etc/yum.conf и заменяем строку
cachedir=/var/cache/yum/$basearch/$releasever
на
cachedir=/home/yum/cache/$basearch/$releasever

воскресенье, 22 декабря 2013 г.

Интеграция гистограмм CERN ROOT в Qt GUI Geant4

Собственно, возможность открывать гистограммы из GUI Geant4 я прикрутил уже давно. Реализацию кооперации ROOT и Geant4 можно найти в исходном коде ChargeExchangeMC, который поставляется вместе с Geant4 как один из advanced examples. Эта реализация основана на использовании бэкенда Qt CERN ROOT, поэтому она доступна только при использовании плагина визуализации Qt Geant4.

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

На следующей картинке изображена сессия Qt Geant4 c одной из гистограмм, изображающей пучок, прошедший сквозь входной мониторный счетчик. Гистограмма была открыта из меню Histo, находящемся в верхней левой части окна справа от меню Viewer. Все картинки ниже кликабельны.


А это меню Histo в развернутом виде:


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

Самой главной технической сложностью здесь было построение подменю. Интерфейсы GUI Geant4 их просто не поддерживают. Один из вариантов реализации поддержки подменю - субклассинг G4UIQt - мне не понравился, так как из пушки по воробьям. В итоге я решил добавить новую функцию AddSubmenu() прямо в класс CexmcHistoManager. Вот реализация функции BuildMenuTree(), которая рекурсивно строит меню гистограмм, и функции AddSubmenu():
void  CexmcHistoManager::BuildMenuTree( G4UIQt *  session,
                                        const G4String &  menu, TList *  ls )
{
    TIter      objs( ls );
    TObject *  obj( NULL );

    while ( ( obj = ( TObject * )objs() ) )
    {
        G4String  name( obj->GetName() );
        G4String  title( obj->GetTitle() );

        if ( obj->IsFolder() )
        {
            AddSubmenu( session, menu, name, title );
            BuildMenuTree( session, name, ( ( TDirectory * )obj )->GetList() );
        }
        else
        {
            G4String  options( name );

            do
            {
                if ( obj->InheritsFrom( TH3::Class() ) &&
                     ! drawOptions3D.empty() )
                {
                    title = G4String( "3: " ) + title;
                    options += G4String( " " ) + drawOptions3D;
                    break;
                }
                if ( obj->InheritsFrom( TH2::Class() ) &&
                     ! drawOptions2D.empty() )
                {
                    title = G4String( "2: " ) + title;
                    options += G4String( " " ) + drawOptions2D;
                    break;
                }
                if ( obj->InheritsFrom( TH1::Class() ) &&
                     ! drawOptions1D.empty() )
                {
                    options += G4String( " " ) + drawOptions1D;
                    break;
                }
            } while ( false );

            G4String  cmd( CexmcMessenger::histoDirName + "draw " + options );
            session->AddButton( menu, title.c_str(), cmd );
        }
    }
}

void  CexmcHistoManager::AddSubmenu( G4UIQt *  session,
                                     const G4String &  parent,
                                     const G4String &  name,
                                     const G4String &  label )
{
  QMenu *  menu( new QMenu( label.c_str() ) );
  QMenu *  parentMenu( ( QMenu * )session->GetInteractor( parent ) );

  parentMenu->addMenu( menu );
  session->AddInteractor( name, ( G4Interactor )menu );
}
Предполагается, что BuildMenuTree() будет вызвана в момент инициализации менеджера гистограмм примерно таким образом:
    G4UIQt *  qtSession( dynamic_cast< G4UIQt * >( session ) );

    if ( qtSession )
    {
        qtSession->AddMenu( histoMenuHandle, histoMenuLabel );
        BuildMenuTree( qtSession, histoMenuHandle, gDirectory->GetList() );
    }
Здесь session - указатель на объект типа G4UIsession, histoMenuHandle и histoMenuLabel - строки, первая задается произвольно - это дескриптор нового главного меню в строке меню (menu bar), вторая - имя этого меню, в нашем случае она равна Histo.

Этого кода еще нет в составе ChargeExchangeMC, но я думаю добавить его к следующему релизу Geant4.

среда, 11 декабря 2013 г.

Как изящно разбить на несколько строк длинный строковый литерал в bash

Под изяществом я здесь понимаю простую возможность контролировать отступы от левого края перенесенных частей литерала. Возьмем, к примеру, следующий алиас g4conf, который я хочу поместить в сценарий $HOME/.bashrc.
alias g4conf='cmake -DCMAKE_INSTALL_PREFIX:PATH=$G4WORKDIR -DGeant4_DIR=$G4CONFIGDIR'
Этот алиас позволяет не писать в терминале длинные команды каждый раз, когда нужно сконфигурировать билд приложения с использованием библиотеки Geant4 (см. мой предыдущий пост). Кстати, обратите внимание на то, что значение алиаса взято в одинарные кавычки: это запрещает интерпретацию переменных среды в момент чтения интерпретатором сценария .bashrc и переносит ее на момент выполнения команды.

К сожалению, эта строка не умещается в 80 символов, чего я всегда стараюсь придерживаться в любом коде. Возникает вопрос о ее переносе. Удобнее всего перенести на новую строку последний аргумент cmake -DGeant4_DIR=$G4CONFIGDIR. Очевидное решение - использовать обратный слэш.
alias g4conf='cmake -DCMAKE_INSTALL_PREFIX:PATH=$G4WORKDIR \
                    -DGeant4_DIR=$G4CONFIGDIR'
Это сработает, хотя и выглядит некрасиво. Так, если ввести в терминале команду alias g4conf, то она выведет вот такую кривулю:
alias g4conf='cmake -DCMAKE_INSTALL_PREFIX:PATH=$G4WORKDIR \
                    -DGeant4_DIR=$G4CONFIGDIR'
Но самое главное - это то, что добавление дополнительных пробелов во многих случаях недопустимо или нежелательно, например при объявлении переменных среды PS1 и PS2. Другой вариант - сформировать две отдельных строки и соединить их обратным слэшем - тоже сработает неправильно.
alias g4conf='cmake -DCMAKE_INSTALL_PREFIX:PATH=$G4WORKDIR '\
                   '-DGeant4_DIR=$G4CONFIGDIR'
На этот раз алиас будет просто неверным.
$ alias g4conf
alias g4conf='cmake -DCMAKE_INSTALL_PREFIX:PATH=$G4WORKDIR '
И это неудивительно. Такой перенос строки вместо конкатенации двух ее частей формирует два аргумента для alias, при этом перенесенный второй аргумент просто игнорируется, что мы и видим в выводе команды alias g4conf.

Очень классное решение я подсмотрел здесь. Оно заключается в нецелевом использовании обратных кавычек.
alias g4conf='cmake -DCMAKE_INSTALL_PREFIX:PATH=$G4WORKDIR '`
                  `'-DGeant4_DIR=$G4CONFIGDIR'
На этот раз вывод команды alias g4conf будет таким, каким мы ожидали его увидеть:
alias g4conf='cmake -DCMAKE_INSTALL_PREFIX:PATH=$G4WORKDIR -DGeant4_DIR=$G4CONFIGDIR'
Как это работает. Интерпретатор во время выполнения сценария .bashrc находит первую обратную кавычку, затем пропускает все символы (включая возможные переносы строк) до второй обратной кавычки, и то, что находится между ними пытается эвалюировать как внешнюю команду. В нашем случае между первой и второй обратными кавычками находятся символ переноса строки и несколько пробелов: это эвалюируется в пустую строку. Строки, склеенные вместе без всяких пробелов bash интерпретирует как одну непрерывную строку, этот эффект и позволяет склеивать перенесенные части строк в одну. Обратите внимание на то, что между обратными кавычками и кавычками, ограничивающими части строк, не должно быть пробелов. Иначе произойдет не склейка нескольких строк в одну, а разбиение большой строки, которую мы пытаемся склеить, на несколько отдельных аргументов, как это было в предыдущем варианте.

В строках с двойными кавычками или без кавычек вовсе этот подход работает еще лучше. Поскольку обратные кавычки интерпретируются внутри двойных кавычек, то нет нужды расщеплять длинную строку вставкой промежуточных двойных кавычек, cклеенных перемычкой из обратных кавычек: достаточно просто вставить обратные кавычки в нужном месте. Например:
$ echo "My shell is `
>      `$SHELL"
My shell is /bin/bash
То же самое при отсутствии двойных кавычек:
$ echo My shell is `
>      `$SHELL
My shell is /bin/bash

вторник, 10 декабря 2013 г.

Проблемы, с которыми я столкнулся при компиляции нового Geant4 10.0

Новая версия замечательной библиотеки Geant4 вышла 6 декабря. Я решил написать данную статью, так как встретился с одним интересным багом, который не давал скомпилировать новый Geant4. Баг связан не с самой библиотекой Geant4, и даже не с cmake, а с компилятором moc из Qt4.

Итак, последние версии Geant4 компилируются исключительно с помощью cmake. Для этого создается новая директория, например geant4.10.00-build/, и в ней запускается cmake. Я обычно использую псевдографическую оболочку ccmake (см. здесь) или обычный консольный cmake в интерактивном режиме (с опцией -i) - это позволяет включить различные опции, выключенные по умолчанию. Так, для своих нужд я обычно включаю опции GEANT4_USE_GDML, GEANT4_USE_OPENGL_X11 и GEANT4_USE_QT. Кроме того, в версии Geant4 10.0 появилась возможность включить многопоточную обработку событий при компиляции библиотеки с опцией GEANT4_BUILD_MULTITHREADED: ее включение позволит протестировать эту новую киллер-фичу.

По завершении конфигурирования билда cmake запускаем обычный make. Ждем окончания компиляции, если повезет... Мне не повезло. Когда статус достиг 84%, она прервалась с таким сообщением об ошибке:
[ 84%] Generating basic/include/moc_G4UIQt.cxx
moc: Cannot open options file specified with @
Usage: moc [options] <header-file>
  -o<file>           write output to file rather than stdout
  -I<dir>            add dir to the include path for header files
  -E                 preprocess only; do not generate meta object code
  -D<macro>[=<def>]  define macro, with optional definition
  -U<macro>          undefine macro
  -i                 do not generate an #include statement
  -p<path>           path prefix for included file
  -f[<file>]         force #include, optional file name
  -nn                do not display notes
  -nw                do not display warnings
  @<file>            read additional options from file
  -v                 display version of moc
make[2]: *** [source/interfaces/basic/include/moc_G4UIQt.cxx] Ошибка 1
make[1]: *** [source/interfaces/CMakeFiles/G4interfaces.dir/all] Ошибка 2
make: *** [all] Ошибка 2
Это и есть тот самый интересный баг, который я анонсировал в начале статьи. Для его воспроизведения требуется стечение нескольких обстоятельств: Geant4 должен компилироваться с поддержкой Qt, мажорная версия Qt не должна быть выше 4 (на моей Fedora 19 установлена Qt4 8.5) и в полном пути к директории, из которой выполняется cmake, должны присутствовать нелатинские символы (у меня эта директория находилась в $HOME/Загрузки/). Выяснилось, что во всем виноват компилятор moc, который ошибочно обрабатывает опцию @<file>, если в ней присутствуют нелатинские символы. В этом багрепорте описана проблема. Оказалось, что она уже решена в Qt5 и, поскольку ее проявления должны быть весьма редкими, то чинить ее в Qt4 нет смысла. Что же делать, если вас постигла такая неудача? Да просто строить билд Geant4 в директории, в пути к которой нет нелатинских символов!

После успешного завершения компиляции выполняем make install, переходим в директорию, куда были установлены данные (она задается переменной cmake GEANT4_INSTALL_DATADIR), и проверяем все ли они на месте: у меня почему-то не оказалось данных в поддиректориях G4PII1.3, G4SAIDDATA1.1 и RealSurface1.0, и из-за этого уже скомпилированное и запущенное приложение упало при запуске первого же события. В случае отсутствия данных в поддиректориях скачиваем их отсюда (из раздела Data Files) и устанавливаем вручную.

Теперь о настройках среды. Я уже рассказывал об этом здесь. Но, поскольку в новой версии Geant4 использование простого Makefile больше не поддерживается, то и сорсинг geant4make.sh больше не нужен. Соответственно, в $HOME/.bash_profile теперь помещаем такие строки:
export G4ROOT=/usr/local/geant4
export G4CONFIGDIR=$G4ROOT/lib64/Geant4-10.0.0
export G4WORKDIR=$HOME/geant4

[ -f $G4ROOT/bin/geant4.sh ] && . $G4ROOT/bin/geant4.sh

PATH=$PATH:$G4WORKDIR/bin
Для компиляции приложений, которые используют Geant4, нужно использовать cmake. Создаем отдельную директорию, переходим в нее и конфигурируем билд:
cmake -DCMAKE_INSTALL_PREFIX:PATH=$G4WORKDIR -DGeant4_DIR=$G4CONFIGDIR <path-to-srcs>
Здесь <path-to-srcs> - это директория с исходниками приложения. В принципе, билд можно сконфигурировать прямо внутри этой директории и аргумент cmake <path-to-src> в этом случае не нужен, однако это замусорит директорию с исходниками, поэтому такой подход обычно не рекомендуют. После успешной конфигурации запускаем make и, если нужно, make install.

Еще одна проблема, с которой я столкнулся при компиляции своего приложения - это огромное количество предупреждений о перекрытии (shadowing) имен. Дело в том, что в новой версии Geant4 в скрипты cmake была добавлена опция компилятора -Wshadow, которая выдает предупреждения, если, например, имена формальных параметров конструкторов класса и их членов совпадают. Я писал об этом здесь. Проблема в том, что такой уж у меня стиль: я всегда именую формальные параметры конструкторов и члены класса, которые они инициализируют, одинаково. Такой подход полностью противоречит модели, в которой перекрытия имен не дозволяются. Как быть? Ведь я не собираюсь переименовывать огромное количество членов всевозможных классов в своем проекте только для того, чтобы убрать эти предупреждения. Оказывается, их легко подавить, если вставить в файл CMakeLists.txt строку
add_definitions(-Wno-shadow)
где-нибудь внизу после включения ${Geant4_USE_FILE}. В этом случае противоположная опции -Wshadow опция -Wno-shadow победит исходно установленную -Wshadow и предупреждения уйдут.

четверг, 14 ноября 2013 г.

Снятие свойства бинарности при добавлении нового файла в subversion

Каждый раз сталкиваюсь с этой проблемой при добавлении нового файла XML в subversion. Пример:
$ svn add example.xml
A  (bin)  example.xml
Я не хочу, чтобы subversion рассматривал новый файл как бинарный, ведь он, как и другие исходники, наполнен смыслом, который лучше выражается в текстовой, а не бинарной, форме.

Решается эта проблема просто: изменением свойства svn:mime-type. Смотрим текущее значение.
$ svn propget svn:mime-type example.xml
application/xml
Тип application/xml рассматривается subversion как бинарный. Заменим его на text/xml.
$ svn propset svn:mime-type text/xml example.xml
property 'svn:mime-type' set on 'example.xml'
Убедимся, что файл example.xml стал текстовым.
$ svn status
A       example.xml
Отлично, сработало. Теперь можно смело коммитить новый файл.

среда, 30 октября 2013 г.

Прямое цитирование подсветки синтаксиса из vim в tex

В предыдущей статье я показал, как с помощью pandoc экспортировать статьи в формате HTML в формат tex. Поскольку статьи в моем блоге содержат множество примеров исходного кода, то необходимо, чтобы pandoc умел их правильно подсвечивать. С этой задачей pandoc справляется неплохо, делегируя ее исполнение библиотеке highlighting-kate; если же какой-то язык не поддерживается (например VimL), то можно воспользоваться пакетом latex minted.

Всё это прекрасно. Однако выяснилось, что некоторые статьи данного блога (например эта) требуют прямого цитирования подсветки vim, поскольку в них, собственно, обсуждается как vim подсвечивает код и приводятся примеры. Я рассказывал, как я вставляю подсвеченный исходный код из vim в статьи блога здесь. Главная идея - волшебная команда vim MakeBlogArticle, которая преобразует выделенный текст (или весь буфер) в формат HTML с сохраненной подсветкой синтаксиса. MakeBlogArticle использует в качестве бэкенда скрипт TOhtml, поэтому реализовать ее не сложно. Теперь же нам нужна подобная команда MakeTexCodeHighlight, которая будет транслировать выделенный текст (или весь буфер) в некоторое представление tex, совместимое с документами, сгенерированными pandoc. Требования к совместимости понятны. Поскольку pandoc для подсветки исходного кода генерирует среду Shaded, реализованную через fancyvrb, то результирующий документ должен находиться внутри тэгов
\begin{Shaded}
\begin{Highlighting}[]

...

\end{Highlighting}
\end{Shaded}
, а внутри своего тела использовать цветовые тэги \textcolor.

Давайте реализуем команду MakeTexCodeHighlight.

Прежде всего нам понадобятся функции, определяющие цвета текста и фона синтаксической группы элемента под курсором. Вот их реализация:
fun<SID>get_color_under_cursor(bg)
    let synId = synID(line(".")col(".")1)
    let name = synIDattr(synId, "name")
    if name == ''
        return 'none'
    endif
    let layer = a:bg ? "bg" : "fg"
    return <SID>Xterm2rgb256(synIDattr(synIDtrans(synId), layer))
endfun

fun! GetFgColorUnderCursor()
    return <SID>get_color_under_cursor(0)
endfun

fun! GetBgColorUnderCursor()
    return <SID>get_color_under_cursor(1)
endfun
Функция get_color_under_cursor() - рабочая лошадка, выполняющая задания двух основных функций: GetFgColorUnderCursor() и GetBgColorUnderCursor(). Ее задача очень простая - выдать шестнадцатиричное значение цвета под курсором. Выражение
synIDattr(synIDtrans(synId), layer)
возвращает номер этого цвета для 256-цветного терминала (да, я ограничился текстовой версией vim, в gvim это работать не будет!), а функция Xterm2rgb256() должна вернуть окончательное шестнадцатиричное число в виде строки. Функции Xterm2rgb256() в vim нет, мы должны ее реализовать. К счастью, подобных функций в разных плагинах vim полно. Многие из них используют обычную таблицу соответствия, однако я нашел аналитическую реализацию данной функции в плагине Colorizer. Вот адаптированная версия Xterm2rgb256(), возвращающая шестнадцатиричное число в виде строки:
" next Xterm2rgb... conversion functions are adopted from plugin Colorizer.vim
fun<SID>Xterm2rgb16(color)
    " 16 basic colors
    let r=0
    let g=0
    let b=0
    let basic16 = [
                \ [ 0x000x000x00 ], 
                \ [ 0xCD0x000x00 ], 
                \ [ 0x000xCD0x00 ], 
                \ [ 0xCD0xCD0x00 ], 
                \ [ 0x000x000xEE ], 
                \ [ 0xCD0x000xCD ], 
                \ [ 0x000xCD0xCD ], 
                \ [ 0xE50xE50xE5 ], 
                \ [ 0x7F0x7F0x7F ], 
                \ [ 0xFF0x000x00 ], 
                \ [ 0x000xFF0x00 ], 
                \ [ 0xFF0xFF0x00 ], 
                \ [ 0x5C0x5C0xFF ], 
                \ [ 0xFF0x000xFF ], 
                \ [ 0x000xFF0xFF ], 
                \ [ 0xFF0xFF0xFF ]
                \ ]
    let r = basic16[a:color][0]
    let g = basic16[a:color][1]
    let b = basic16[a:color][2]
    return printf("%02x%02x%02x", r, g, b)
endfun

fun<SID>Xterm2rgb256(color)
    let r=0
    let g=0
    let b=0
    " 16 basic colors
    if a:color < 16
        return <SID>Xterm2rgb16(a:color)
    " color cube color
    elseif a:color >= 16 && a:color < 232
        " the 6 value iterations in the xterm color cube
        let valuerange6 = [ 0x000x5F0x870xAF0xD70xFF ]
        let color=a:color-16
        let r = valuerange6[(color/36)%6]
        let g = valuerange6[(color/6)%6]
        let b = valuerange6[color%6]
    " gray tone
    elseif a:color >= 232 && a:color <= 255
        let r = 8 + (a:color-232) * 0x0a
        let g = r
        let b = r
    endif
    return printf("%02x%02x%02x", r, g, b)
endfun
Теперь мы можем определить команды vim для того, чтобы комфортно получать значения цветов синтаксического элемента под курсором из терминала (для нашей основной цели эти команды не нужны, разве что только для отладки):
command GetFgColorUnderCursor echo GetFgColorUnderCursor()
command GetBgColorUnderCursor echo GetBgColorUnderCursor()
Следующим шагом будет реализация функции split_synids(), которая будет разбивать текст внутри области текста, ограниченной значениями номеров строк, переданных ей в качестве аргументов, на отдельные элементы, включающие в себя имя синтаксического элемента, его содержание (т.е. соответствующую подстроку), номер строки, в которой он находится, а также значения цветов текста и фона в шестнадцатиричном формате.
fun<SID>split_synids(fst_line, last_line)
    let result = []
    let save_cursor = getpos('.')
    let save_winline = winline()
    call setpos('.', [0a:fst_line10])
    let cursor = getpos('.')
    while cursor[1<= a:last_line
        let old_synId = '^'
        let old_start = cursor[2]
        let cols = col('$')
        if cols == 1
            let cursor[1+= 1
            let cursor[2= 1
            call setpos('.', cursor)
            continue
        endif
        while cursor[2<= cols
            let synId = synIDattr(synID(line('.')col('.')1)'name')
            let fg = toupper(GetFgColorUnderCursor())
            let bg = toupper(GetBgColorUnderCursor())
            let cursor[2+= 1
            call setpos('.', cursor)
            if synId != old_synId
                if old_synId != '^'
                    call add(result,
                            \ {'name': old_synId,
                            \ 'content'strpart(getline('.'), old_start - 1,
                            \            cursor[2- old_start - 1),
                            \  'line'line('.')'fg': old_fg, 'bg': old_bg})
                endif
                let old_synId = synId
                let old_start = cursor[2- 1
            endif
            let old_fg = fg
            let old_bg = bg
        endwhile
        call add(result,
                    \ {'name': synId,
                    \ 'content'strpart(getline('.'), old_start - 1,
                    \            cursor[2- old_start - 1),
                    \  'line'line('.')'fg': fg, 'bg': bg})
        let cursor[1+= 1
        let cursor[2= 1
        call setpos('.', cursor)
    endwhile
    call setpos('.', save_cursor)
    let move = winline() - save_winline
    if move != 0
        let dir = move < 0 ? '^Y' : '^E'
        exe "normal ".abs(move).dir
    endif
    return result
endfun
В переменной result будем сохранять список синтаксических элементов. Манипуляции с save_cursor и save_winline нужны для восстановления положения курсора и окна после завершения работы функции. Внутренний цикл while обходит элементы одной строки слева направо, добавляя информацию о встреченных синтаксических областях в result. Для этой цели вызываются функции GetFgColorUnderCursor() и GetBgColorUnderCursor(), которые идентифицируют синтаксический элемент под курсором. Возвращенное ими значение (строка, представляющая шестнадцатиричное число) переводится в верхний регистр: это будет нужно в итоговом документе, так как latex почему-то не любит нижний регистр в шестнадцатиричных числах (во всяком случае это верно для тэгов \textcolor). Внешний цикл while перебирает строки сверху вниз.

Теперь напишем функцию write_latex_code_highlights(). Она будет выполнять основную работу по созданию документа: открывать окно для нового буфера, вводить открывающие и закрывающие тэги tex и, собственно, тело документа на основании списка, возвращенного функцией split_synids().
fun<SID>write_latex_code_highlights(fst_line, last_line, ...)
    let colors = g:colors_name
    colorscheme lucius
    let parts = <SID>split_synids(a:fst_linea:last_line)
    exe "colorscheme ".colors
    let save_paste = &paste
    new +setnowrappaste
    normal a\begin{Shaded}^M\begin{Highlighting}[]^M
    let old_line = a:fst_line
    let line = old_line
    for hl in parts
        let line = hl['line']
        while line > old_line
            normal o
            let old_line += 1
        endwhile
        let part = escape(hl['content'], '\{}_$%')
        let part = substitute(part, '\\\\''\\textbackslash{}''g')
        let fg = hl['fg']
        " small hack to paint syntax group links that could have been lost
        " after switching colorscheme in black instead white
        if a:0 && a:1 && fg == 'FFFFFF'
            let fg = '000000'
        endif
        if fg != 'NONE' && part !~ '^\s*$'
            let part = '\textcolor[HTML]{'.fg.'}{'.part.'}'
        endif
        exe "normal a".part
    endfor
    while line < a:last_line
        normal o
        let line += 1
    endwhile
    normal a^M0^D\end{Highlighting}^M\end{Shaded}^[gg
    set ft=tex
    if !save_paste
        set nopaste
    endif
endfun
Код, в общем-то, не должен вызывать затруднений. Прокомментирую лишь некоторые моменты. Я уже писал, зачем при создании документов для публикации я заменяю рабочую цветовую схему на схему lucius: просто она светлая и более подходит для создания статей, чем моя стандартная темная схема. Элементы ^M, ^D и ^[ в коде состоят не из двух символов; это одиночные символы, соответствующие вводу <Enter>, Ctrl-D и <Esc>. Чтобы их ввести в редакторе vim, наберите Ctrl-V и, удерживая клавишу Ctrl, соответствующий символ (M, D или <Esc>). Команда new устанавливает в новом буфере значения nowrap (чтобы строки не заворачивались - нет смысла) и paste (чтобы vim не выполнял вредную автоматическую индентацию текста). Поскольку значение paste устанавливается глобально, то оно восстанавливается при выходе из функции. Внутри цикла for собранные функцией split_synids() синтаксические элементы выводятся с помощью normal a, новые строки добавляются с помощью normal o. При выводе в буфер все символы {, }, _, $ и % слэшируются, а символ \ заменяется на \textbackslash{} - если этого не сделать, то возможны ложные интерпретации строк буфера как команд latex! Если цвет fg данного элемента не NONE, то он обрамляется тэгом \textcolor[HTML]{}{}.

Опциональный третий аргумент write_latex_code_highlights() преобразует белый текст в черный. Белый текст может появиться в результате исчезновения синтаксических групп при переходе в новую цветовую схему. Дело в том, что я использую расширенную цветовую схему для отображения тэгов ctags (см. эту статью), и при замене ее на lucius все группы, добавленные ctags, исчезают, при этом функция GetFgColorUnderCursor() возвращает почему-то значение ffffff, соответствующее белому цвету, хотя реальный цвет "потерянных" элементов черный. В общем этот третий аргумент можно рассматривать как хак и в реальности он вряд ли нужен.

Вот определение команды MakeTexCodeHighlight:
command -range=% MakeTexCodeHighlight
        \ silent call <SID>write_latex_code_highlights(<line1><line2>1)
Теперь можно выделять нужные строки в буфере (без выделения команда обработает весь буфер) и, после ввода команды MakeTexCodeHighlight, копировать текст в новом окне и вставлять его в документ tex, созданный pandoc.

Пример.

Код на C++:
int  main( void )
{
    return 0;
}
Код tex, сгенерированный MakeTexCodeHighlight:
\begin{Shaded}
\begin{Highlighting}[]
\textcolor[HTML]{005F87}{int}  \textcolor[HTML]{008700}{main}\textcolor[HTML]{870087}{(} \textcolor[HTML]{005F87}{void} \textcolor[HTML]{870087}{)}
\textcolor[HTML]{870087}{\{}
    \textcolor[HTML]{005FAF}{return} \textcolor[HTML]{AF5F00}{0}\textcolor[HTML]{870087}{;}
\textcolor[HTML]{870087}{\}}
\end{Highlighting}
\end{Shaded}
(кстати, чтобы вставить сюда этот результат я сначала выполнил MakeTexCodeHighlight, а затем, уже в новом буфере, MakeBlogArticle).
 
Update. Заметил, что MakeTexCodeHighlight неправильно обрабатывает эти дословные символы ^M, ^D и т.п. Команда MakeBlogArticle делает это правильно, видимо в TOhtml они предварительно транслируются.

Update 2. В функции split_synids() тоже есть контрольные символы ^Y и ^E. Во всех случаях их использования можно избежать. Вообще, их появление в скрипте связано с эмуляцией действий пользователя в терминале с помощью комады normal. Если мы найдем соответствующие функции в API VimL, то команда normal будет не нужна. В самом деле, для сохранения и восстановления окна в API VimL предусмотрены функции winsaveview() и winrestview() - использование их вместо эмуляции действий пользователя в split_synids() упростит эту функцию и поможет избежать побочных эффектов, связанных с возможным ремаппингом  ^Y и ^E пользователем. В функции write_latex_code_highlights() команда normal с контрольными символами используется для эмуляции ввода пользователем строк. В VimL есть функция append(), которая может легко заменить эту эмуляцию и, соответственно, убрать контрольные символы из кода write_latex_code_highlights() и упростить ее понимание.

суббота, 26 октября 2013 г.

pandoc: экспорт блога в tex

Однажды, примерно полгода назад, я решил перевести статьи из этого блога в книжный формат. Для этого, в первую очередь, мне пришлось выбрать, в какой формат переводить исходные HTML страницы блога, а затем изучить доступные в сети программы, способные хорошо справиться с этой задачей. Собственно, с выбором формата не было никаких проблем: трудно предложить что-либо такое, что превосходило бы по возможностям формат PDF. Наиболее перспективной программой оказалась wkhtmltopdf (при этом, чтобы добиться достойного результата, мне пришлось скачать уже собранную версию программы, включающую важные патчи для Qt). Затем я создал в своей домашней директории новую директорию blog/, а внутри нее две поддиректории articles/ и images/. В первую поддиректорию я поместил все исходные HTML страницы блога, а во вторую - все изображения с этих страниц. Далее я разными автоматическими и полуавтоматическими способами добавил HTML хедеры и футеры в HTML страницы и исправил в них атрибуты src тэгов img, дабы они правильно указывали на изображения в директории articles/. После этого я написал простейший скрипт makepdf.sh, который генерировал PDF документ из этих страниц.
#!/bin/bash

BLOGNAME=Тех-детали
RESULT=blog.pdf
SRC=articles

WKHTMLTOPDF=wkhtmltopdf-amd64
FH_OPTS="-B 27 -T 24 --footer-line --header-line --footer-spacing 10 
 --header-spacing 10 --header-left $BLOGNAME --header-right [title]
 --footer-right [page]"
EXTRA_OPTS="--image-quality 100"
TOC_OPTS="toc --xsl-style-sheet wkhtmltopdf-toc.xsl"

OBJECTS="$SRC/boostspirit.html $SRC/c.html $SRC/cint.html $SRC/blog-post.html
 $SRC/subversion.html $SRC/cd-flac-mp3.html $SRC/boostspirit2.html
 $SRC/haskell.html $SRC/usb.html $SRC/subversion2.html
 $SRC/geant4-94-chargeexchangemc-aka-cexmc.html $SRC/f-spot.html $SRC/c2.html
 $SRC/9.html $SRC/vim-ctags-perl.html $SRC/tcl.html $SRC/haskell2.html
 $SRC/c-haskell.html $SRC/fedora-15-wicd.html $SRC/cern-root.html $SRC/vim.html
 $SRC/vim-2.html $SRC/firefox-6-thunderbird-6-fedora-14.html $SRC/ctags-c-c.html
 $SRC/make.html $SRC/texlive.html $SRC/ngrep.html $SRC/nginx.html
 $SRC/vim-taghighlight-tagbar.html $SRC/geant4-95.html $SRC/vim-powerline.html
 $SRC/nginx-i.html $SRC/nginx-ii.html $SRC/vim2.html
 $SRC/mate-compiz-fedora-17.html $SRC/nginx-myutil.html $SRC/blog-post2.html
 $SRC/shell-powerline.html $SRC/vim-vimwiki.html $SRC/diff.html
 $SRC/unicode-linux-fedora-17.html $SRC/powerline-linux.html $SRC/linux.html
 $SRC/vim-tex-minted.html $SRC/pdf-odp-ppt-latex.html
 $SRC/vim-xkb-switch-libcall.html $SRC/a-perl-script-for-gathering-blogger.html
 $SRC/fedora-17-fedora-18.html $SRC/vim3.html $SRC/c11-rvalue.html
 $SRC/tsung.html $SRC/c3.html $SRC/blog-post3.html"

$WKHTMLTOPDF $FH_OPTS $EXTRA_OPTS $TOC_OPTS $OBJECTS $RESULT
Я привожу его здесь только для того, чтобы показать, насколько гибок wkhtmltopdf: его опции перечислены в переменных FH_OPTS, EXTRA_OPTS и TOC_OPTS. Переменная OBJECTS представляет собой список всех файлов из директории articles/.

Результат оказался очень хорошим, предельно близким к оригиналу! После этого я на некоторое время успокоился, но затем меня снова начали терзать сомнения. В самом деле, несмотря на огромное количество опций wkhtmltopdf, отвечающих за внешний вид окончательного PDF документа, на выходе мы имеем примерно то же самое, что было в самом начале - груду неструктурированного мусора, которым невозможно управлять (редактировать, менять внешний вид структурных элементов и т.п.). Нужен другой формат, например tex.

Исследования показали, что есть такая программа pandoc, которая, судя по ее описанию, способна преобразовывать огромное количество форматов из одного в другой. В частности, она умеет выполнять преобразование из HTML в формат tex - то, что нужно! Установив pandoc, и опробовав его на одном из файлов в директории articles/, я увидел, что подсветка синтаксиса исходных кодов пропала. Я рассказывал, как я вставляю подсвеченный код в блог. По сути, он заворачивается внутрь тэгов <pre><tt> .. </tt></pre>, а конкретные цвета подставляет плагин TOhtml редактора vim на основании используемых для данного типа файла синтаксических правил. Кроме того, конвертор TOhtml заменяет пробелы на символы &nbsp;. Выяснилось, что pandoc не умеет подсвечивать составленные таким образом блоки HTML. Однако, он таки может подсвечивать исходный код, если внутри тэгов <pre> .. </pre> удалить все HTML тэги и символы неразрывного пробела, оставив голый исходный код, и снабдить тэг <pre> атрибутом class="тип_файла" или class="brush: тип_файла", где тип_файла - это синтаксический тип файла, например, cpp для C++. Просмотреть список поддерживаемых pandoc синтаксических типов файлов можно, введя команду
pandoc --version
Кроме того, вставленные когда-то хедеры и футеры HTML, оказались не нужны. Да и ссылки на изображения не нужны тоже. Поэтому я написал небольшой quick and dirty скрипт process_html.sh, который автоматизирует преобразование содержимого тэгов <pre>, удаляет хедэры и футеры HTML и ссылки на изображения.
#!/bin/bash 

CLASS=

while getopts :l: opt; do
  case $opt in
    l)  CLASS=$OPTARG ;;
    \?) echo "Invalid option: -$OPTARG" ;;
    :)  echo "Option -$OPTARG requires an argument."exit 1 ;;
  esac
done

shift $((OPTIND-1))

sed -e 's/\(^.*\)\?\(<\/\?pre>\)/\1\n\2\n/' "$@" |
sed -e 's/.*<\/\?html>\|<\/\?body>.*//; /<head>/,/<\/head>/d' \
    -e 's/<a href.*\+>\(<img[^>]\+src="\).*\(\/images[^"]\+"[^>]\+>\)/\1..\2/g' \
    -e '/^<tt>/,/^<\/tt>/s/<[^>]\+>//g; /^<pre>$/,/^<\/pre>$/s/&nbsp;/ /g' \
    -e "s/^<pre>$/<pre class=\"$CLASS\">/"
Я поместил этот скрипт в новую поддиректорию tex/, соседнюю с articles/ и images/. Единственная опция -l указывает значение для атрибута class тэга <pre>, то есть синтаксический тип исходных кодов в исходной странице HTML. Очевидно, на одной странице HTML могут находиться исходные коды разных синтаксических типов (например, на языках fortran и C++), поэтому этот скрипт нельзя рассматривать как полностью автоматический: ручная правка полученного результата может оказаться необходимой. Ну а в простейшем случае, достаточно выполнить
./process_html.sh -lcpp ../articles/c.html | pandoc --normalize -f html -o c.tex
чтобы из файла c.html получить файл c.tex. Файл c.tex не является законченным (standalone) документом tex. Его нужно вставить в шаблон. Я использовал следующий шаблон blog.tex:
\documentclass[12pt]{article}
% adjust page geometry
\usepackage[a4paper,vmargin={2cm,2cm},hmargin={2cm,2cm}]{geometry}

\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{amssymb,amsmath}
\usepackage{ifxetex,ifluatex}
\usepackage{fixltx2e} % provides \textsubscript
% use upquote if available, for straight quotes in verbatim environments
\IfFileExists{upquote.sty}{\usepackage{upquote}}{}
\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
  \usepackage[utf8]{inputenc}
\else % if luatex or xelatex
  \ifxetex
    \usepackage{mathspec}
    \usepackage{xltxtra,xunicode}
    \usepackage{fontspec} % enables loading of OpenType fonts
    \usepackage{polyglossia} % support for languages

    % fonts:
    \defaultfontfeatures{Scale=MatchLowercase,Mapping=tex-text}
    \setmainfont{DejaVu Sans}
    \setsansfont{DejaVu Sans}
    \setmonofont{DejaVu Sans Mono}

    % Russian/English document:
    \usepackage{xecyr}
  \else
    \usepackage{fontspec}
  \fi
  \defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase}
  \newcommand{\euro}{}
\fi
% use microtype if available
\IfFileExists{microtype.sty}{\usepackage{microtype}}{}
\usepackage{color}
\usepackage{xcolor}
\usepackage{fancyvrb}
\newcommand{\VerbBar}{|}
\newcommand{\VERB}{\Verb[commandchars=\\\{\}]}
\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}}
% Add ',fontsize=\small' for more characters per line
\usepackage{framed}
\definecolor{shadecolor}{rgb}{1.0, 1.0, 0.9}
\newenvironment{Shaded}{
  \begin{shaded}
    \scriptsize
}{\end{shaded}}
\newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{{#1}}}}
\newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{{#1}}}
\newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}}
\newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}}
\newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}}
\newcommand{\CharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}}
\newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}}
\newcommand{\CommentTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textit{{#1}}}}
\newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{{#1}}}
\newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}}
\newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.02,0.16,0.49}{{#1}}}
\newcommand{\RegionMarkerTok}[1]{{#1}}
\newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}}
\newcommand{\NormalTok}[1]{{#1}}
\usepackage{graphicx}
% Redefine \includegraphics so that, unless explicit options are
% given, the image width will not exceed the width of the page.
% Images get their normal width if they fit onto the page, but
% are scaled down if they would overflow the margins.
\makeatletter
\def\ScaleIfNeeded{%
  \ifdim\Gin@nat@width>\linewidth
    \linewidth
  \else
    \Gin@nat@width
  \fi
}
\makeatother
\let\Oldincludegraphics\includegraphics
{%
 \catcode`\@=11\relax%
 \gdef\includegraphics{\@ifnextchar[{\Oldincludegraphics}{\Oldincludegraphics[width=\ScaleIfNeeded]}}%
}%
\ifxetex
  \usepackage[setpagesize=false, % page size defined by xetex
              unicode=false, % unicode breaks when used with xetex
              xetex]{hyperref}
\else
  \usepackage[unicode=true]{hyperref}
\fi
\definecolor{linkcolor}{HTML}{AF8787}
\hypersetup{breaklinks=true,
            bookmarks=true,
            pdfauthor={},
            pdftitle={},
            colorlinks=true,
            citecolor=blue,
            urlcolor=blue,
            linkcolor=linkcolor,
            pdfborder={0 0 0}}
\urlstyle{same}  % don't use monospace font for urls
\setlength{\parindent}{0pt}
\setlength{\parskip}{6pt plus 2pt minus 1pt}
\setlength{\emergencystretch}{3em}  % prevent overfull lines
\setcounter{secnumdepth}{0}
\ifxetex
  \newfontfamily\cyrillicfont{DejaVu Sans}
  \setmainlanguage{russian} 
  \setotherlanguage[variant=american]{english}
\fi

%\usepackage[raggedright]{titlesec}

\author{}
\date{}

\begin{document}

\tableofcontents

\include{boostspirit}
\include{c}
\include{cint}
\include{blog-post}
\include{subversion}
\include{cd-flac-mp3}
\include{boostspirit2}
\include{9}
\include{tsung}

\end{document}
Здесь много разных сложных объявлений до начала собственно документа. Откуда я их взял? Исходный шаблон можно получить, просто запустив pandoc с опцией -s (или --standalone) на каком-нибудь HTML файле, в котором присутствует исходный код с тэгом <pre> и атрибутом class (без этого не сгенерируется объявление среды Shaded). Конечно же, я изменил получившийся таким способом шаблон, во-первых, удалив содержимое между тэгами \begin{document} и \end{document} и заменив его на объявление \tableofcontents и список файлов в формате tex для вставки в документ (здесь их пока только девять: восемь самых старых постов и один из новых), а во-вторых, добавив определения для генерации кириллических шрифтов xetex из шрифтов DejaVu. Кроме того, я изменил геометрию страницы (второе объявление в скрипте), определил цвет shadecolor, цвет linkcolor (для элементов оглавления) и действия внутри среды Shaded (использовать блок shaded и меньший размер шрифта scriptsize).

Я решил, что исходные коды будут помещены на светлом бежевом фоне shadecolor, а работа внутри интерактивных пользовательских оболочек отмечена темной полосой слева. Во втором случае, из предварительно обработанного программой process_html.sh файла в формате HTML следует в соответствующих местах удалить из тэга <pre> атрибут class, а в файле в формате tex, созданном pandoc, обрамить соответствующие места (они будут ограничиваться тэгами \begin{verbatim} и \end{verbatim}) тэгами
\begin{leftbar}
\scriptsize

...

\end{leftbar}
После описанных мероприятий со всеми девятью файлами и запуска
latexmk -xelatex blog.tex
я получил вот такой файл blog.pdf. Результат хороший (при том, что Google испортил цвета шрифтов и ухудшил разрешение картинок при вставке в Google Docs, в оригинальном документе PDF все выглядит очень прилично). Даже при таком малоавтоматизированном процессе это было довольно-таки быстро.

Update. Продолжив добавлять отдельные страницы в PDF документ, столкнулся с интересной проблемой. В некоторых моих статьях есть примеры исходного кода на VimL (если кто не знает, так называется скриптовый язык для vim). Так вот, pandoc не умеет подсвечивать VimL. Оказывается, для подсветки языков pandoc использует haskell библиотеку highlighting-kate, которая, как не трудно догадаться, использует определения синтаксиса языков из KDE-шного редактора Kate. Kate поддерживает синтаксис около 200 языков, но не поддерживает VimL! Зафайлил им баг по этому поводу.

Ситуация, однако, оказалась не безвыходной. Я уже писал про латеховский пакет minted, который использует питоновскую библиотеку pygments для подсветки исходного кода. В общем, я решил в случае с исходным кодом на VimL использовать minted. Для этого в файл blog.tex добавляем строку
\usepackage{minted}
, а в файле, обработанном process_html.sh, удаляем атрибут class из соответствующего тэга <pre>. Передаем, как и раньше, этот файл на обработку pandoc, открываем полученный файл tex в редакторе, и заменяем соответствующие открывающие и закрывающие тэги \begin{verbatim} и \end{verbatim} на
\begin{Shaded}
\begin{minted}{vim}

...

\end{minted}
\end{Shaded}
Пакет minted требует, чтобы компилятор был запущен с опцией --shell-escape, поэтому собираем blog.pdf командой
latexmk -pdf -pdflatex="xelatex --shell-escape %O %S" blog.tex
В качестве бонуса имеем полное совпадение стиля подсветки исходного кода VimL со стилем подсветки других синтаксических типов. Это потому, что pandoc для рендеринга подсветки по умолчанию использует стиль той же pygments! (Опция pandoc, отвечающая за стиль подсветки, --highlight-style, может принимать и другие значения, но по умолчанию она равна pygments).