(Оригинал текста на английском языке находится здесь. Перевод на русский язык: Александр Бельченко, страница обновлена 19 мая 2008.)

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

Разделы:

Что делает эта утилита?

Cog преобразует файлы достаточно простым способом: он находит куски Python-кода, встроенные в файл, выполняет этот Python код, и затем вставляет результат работы в оригинальный файл. Файл может содержать любой текст вокруг участков Python-кода. Обычно это исходный код какой-нибудь программы.

Например, если пропустить следующий файл через Cog:

// This is my C++ file.
...
/*[[[cog
import cog
fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
for fn in fnames:
    cog.outl("void %s();" % fn)
]]]*/
//[[[end]]]
...

в результате получим нечто похожее на это:

// This is my C++ file.
...
/*[[[cog
import cog
fnames = ['DoSomething', 'DoAnotherThing', 'DoLastThing']
for fn in fnames:
    cog.outl("void %s();" % fn)
]]]*/
void DoSomething();
void DoAnotherThing();
void DoLastThing();
//[[[end]]]
...

Строки, которые содержат тройные квадратные скобки, являются разделительными (или маркерными). Строки между [[[cog и ]]] содержат код Python-генератора. Строки между ]]] и [[[end]]] содержат результат работы генератора.

Когда Cog начинает работать, он удаляет последний сгенерированный результат, затем выполняет код Python-генератора и записывает новый результат работы назад в файл. Все строки текста, находящиеся за пределами специальных маркеров, остаются нетронутыми.

Строки с Cog-маркерами могут содержать любой текст в дополнение к тройным квадратным скобкам. Это позволяет спрятать код Python-генератора в исходном файле. В вышеприведенном примере весь кусок Python-кода — это C++ комментарий. Таким образом Python-код может быть оставлен в тексте, в то время как файл будет восприниматься как обычный C++ код.

Дизайн

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

Поскольку маркерные строки адаптированы под синтаксис любого языка, то маркеры могут скрыть Python-код генератора внутри исходного файла. Это означает, что Cog-файлы могут быть включены в базу контроля версий, и при этом не нужно беспокоиться о том, чтобы хранить отдельно исходный файл с генератором и результирующий выходной файл. Это также означает, что вам не нужно менять сложившуюся методику компиляции-сборки проекта и т.д.

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

Cog позволяет вам использовать всю силу языка Python для генерации кода, без использования шаблонов — простых, но в то же время имеющих ограниченные возможности, инструментов.

Установка

Для работы Cog требуется Python 2.3 или более поздней версии. Чтобы воспользоваться дополнительными возможностями, вам могут понадобиться следующие пакеты:

  • path, если вы собираетесь запускать юнит тесты.
  • PyXML если вы собираетесь использовать handyxml.

Cog устанавливается при помощи стандартного установочного скрипта (Python distutils).

  1. Скачайте Cog, по любой из ссылок:
  2. Распакуйте архив с дистрибутивом во временный каталог.
  3. Запустите в каталоге с распакованным дистрибутивом скрипт setup.py:
    $ python setup.py install

По окончании установки в подкаталоге scripts для Python-скриптов должен появиться скрипт cog.py.

Лицензия

Cog распространяется согласно MIT лицензии. Используйте Cog, чтобы сделать этот мир чуточку лучше.

Как писать исходные файлы

Исходные файлы, предназначенные для обработки утилитой Cog, — это чаще всего обычные текстовые файлы, которые должны оставаться нетронутыми. Код Python-генератора в вашем исходном файле — это стандартный Python-код. Допускаются любые вариации использования Python для генерации необходимого текста в вашем файле. Каждый участок Python-кода (между строками [[[cog и ]]]) называется генератором, все генераторы будут выполнены по очереди.

Область вывода для каждого генератора (т.е. строки между ]]] и [[[end]]]) очищается, и на это место вставляется новый результат работы Python-генератора. Формат маркерных строк не имеет существенного значения, что позволяет адаптировать их к любому типу исходных файлов. Если строка содержит последовательности специальных символов, то вся строка интерпретируется как маркер. Например, любая из этих строк отмечает начало выполняемого Python-кода:

//[[[cog
/* cog starts now: [[[cog */
-- [[[cog (this is cog Python code)
#if 0 // [[[cog

Cog также может использоваться в языках, в которых отсутствуют многострочные комментарии. Если все маркерные строки содержат некоторый текст перед тройной скобкой, и все строки кода генератора также содержат тот же текст в качестве префикса, то этот префикс удаляется перед выполнением кода Python-генератора. Например, имеется следующий SQL-файл:

--[[[cog
--   import cog
--   for table in ['customers', 'orders', 'suppliers']:
--      cog.outl("drop table %s;" % table)
--]]]
--[[[end]]]

который в результате работы Cog станет таким:

--[[[cog
--   import cog
--   for table in ['customers', 'orders', 'suppliers']:
--      cog.outl("drop table %s;" % table)
--]]]
drop table customers;
drop table orders;
drop table suppliers;
--[[[end]]]

Кроме того, для однострочных генераторов может быть использована компактная форма. Маркер начала кода, и маркер конца кода могут находиться на одной строке, и весь текст между этими маркерами будет рассматриваться как одиночная строка Python-кода:

// blah blah
//[[[cog import MyModule as m; m.generateCode() ]]]
//[[[end]]]

Эта форма записи также может использоваться для простого импорта модуля. Исполняемые операторы верхнего уровня в таком модуле могут производить действия по генерации кода.

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

Cog старается соблюдать ваши правила по использованию пробелов. Ваш Python-код может иметь блочный отступ от левого края в соответствии с окружающим его текстом. При этом Cog также будет добавлять необходимые отступы в своем выводе, чтобы соблюсти эту закономерность. Весь вывод генератора собирается в единый блок текста, общий отступ для всех строк удаляется и затем блок текста выравнивается отступами согласно отступу самого cog-генератора. Это значит, что самый крайний слева непробельный символ в вашем выводе будет иметь такой же отступ, как и маркер начала кода cog-генератора. Остальные строки в вашем выводе будут сохранять свои отступы относительно друг друга.

Модуль Cog

Модуль Cog предоставляет вам для использования функции, при помощи которых можно выводить желаемые строки в ваш файл. Ниже приведено описание этих функций.

cog.out(sOut='' [, dedent=False][, trimblanklines=False])
Пишет текст в область вывода.
sOut — это строковая переменная для вывода.
Если аргумент dedent равен Истине (True), то все пробельные символы в начале строк в sOut удаляются перед добавлением к блоку вывода. Если аргумент trimblanklines равен Истине (True), то все пустые строки в начале и конце sOut удаляются перед добавлением к блоку вывода. Вместе, эти две опции облегчают использование многострочных строковых переменных:
cog.out("""
    These are lines I
    want to write into my source file.
""", dedent=True, trimblankline=True)
cog.outl
Аналогично cog.out, но добавляет в конце перевод строки.
cog.msg(msg)
Печатает строку msg в консольном окне (поток stdout) с префиксом "Message: ".
cog.error(msg)
Возбуждает исключение с текстовым сообщением msg. В консольном окне выводится сообщение msg без вывода стека вызовов Python. Таким образом, программисты не знакомые с Python, которые используют ваш Cog-генератор, не будут пугаться длинного traceback-а.
cog.inFile
Атрибут, представляющий имя входного файла.
cog.outFile
Атрибут, представляющий имя выходного файла.
cog.firstLineNum
Атрибут, представляющий номер строки с первым Python-оператором в генераторе. Этот атрибут может при необходимости использоваться, чтобы различать два генератора в одном и том же входном файле.

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

Запуск Cog

Запуск Cog выполняется из командной строки, аргументы передаются стандартным образом.

cog - генерация исходных текстов при помощи встроенного Python-кода.

cog [OPTIONS] [INFILE | @FILELIST] ...

INFILE - имя входного файла.
FILELIST - имя текстового файла, который содержит набор имен файлов
или ссылки на другие @FILELIST.

OPTIONS:
    -c          Добавление контрольной суммы к генерируемому тексту
                для защиты от случайных изменений.
    -d          Удаление кода генератора из выходного файла.
    -D name=val Определение глобальной строки, доступной для использования 
                в коде вашего генератора.
    -e          Выдать предупреждение, если файл не содержит cog-кода.
    -I PATH     Добавить PATH к списку каталогов, в которых будет
                производиться поиск модулей и файлов с данными.
    -o OUTNAME  Записать вывод в файл OUTNAME.
    -r          Записать вывод назад во входной файл.
    -s STRING   Ко всем сгенерированным строкам добавляется суффикс STRING.
    -U          Записывать в файл с Unix-концовками строк (только символ LF).
    -w CMD      Использовать команду CMD, если необходимо разрешить
                запись в выходной файл.
                    Вместо %s в CMD будет подставлено имя файла.
    -x          Удалить все автоматически сгенерированные строки
                без запуска генератора.
    -z          Маркер [[[end]]] может быть опущен, предполагается, что он
                находится в конце файла.
    -v          Напечатать версию Cog и выйти.
    -h          Распечатать краткую справку.

Указанные в командной строке имена файлов используются в качестве входных файлов и обрабатываются Cog. Имена файлов также могут быть переданы в виде списка в текстовом файле, который передается как аргумент с префиксом @:

$ cog @files_to_cog.txt

Эти @-файлы могут быть вложенными, и каждая строка может содержать опции командной строки вместе с именем файла для обработки. Например, вы можете создать файл cogfiles.txt:

cogfiles.txt

# Файлы, предназначенные для обработки
mycode.cpp
myothercode.cpp
myschema.sql -s " --**cogged**"
readme.txt -s ""

и затем запустить Cog следующим образом:

cog -s " //**cogged**" @cogfiles.txt

В этом случае Cog будет обрабатывать 4 файла, используя синтаксис C++ для маркирования генерируемых строк во всех C++ файлах, SQL синтаксис для .sql файла, и вообще без маркеров в файле readme.txt.

В качестве другого примера рассмотрим файл cogfiles2.txt:

cogfiles2.txt

template.h -D thefile=data1.xml -o data1.h
template.h -D thefile=data2.xml -o data2.h

Если запустить Cog следующей командой:

cog -D version=3.4.1 @cogfiles2.txt

То Cog обработает файл template.h дважды, создав два файла data1.h и data2.h. Для каждой обработки будет определена переменная version со значением "3.4.1", но для первого запуска переменная thefile будет равна "data1.xml", а для второго запуска переменная thefile будет равна "data2.xml".

Перезапись файлов

Флаг -r указывает Cog о необходимости записи вывода назад во входной файл. Если входной файл защищен от записи (например, если он не был извлечен из системы контроля версий для редактирования), то при помощи флага -w можно указать команду, которая выполнит необходимые действия для разрешения записи в файл:

$ cog -r -w "p4 edit %s" @files_to_cog.txt

Установка глобальных переменных

Значения глобальных переменных могут быть установлены в командной строке при помощи ключа -D. Например, если запустить Cog следующим образом:

cog -D thefile=fooey.xml mycode.txt

то Cog начнет обработку файла mycode.txt, но предварительно определит глобальную переменную с именем thefile и присвоит ей значение "fooey.xml". Эта переменная затем может быть использована в ваших генераторах кода. Вы можете указывать ключ -D многократно в командной строке, определяя несколько глобальных переменных.

Значение всегда интерпретируется как Python-строка, для упрощения проблем с обработкой кавычек. Это означает что:

cog -D NUM_TO_DO=12

определит переменную NUM_TO_DO не как целое значение 12, а как строку "12". Эти два значения различаются по типу и не эквиваленты по значению в языке Python. Используйте код int(NUM_TO_DO) для приведения строки к числовому значению.

Добавление контрольной суммы к генерируемому тексту

Если Cog запущен с флагом -c, то генерируемый текст будет сопровождаться указанием его контрольной суммы:

--[[[cog
--   import cog
--   for i in range(10):
--      cog.out("%d " % i)
--]]]
0 1 2 3 4 5 6 7 8 9
--[[[end]]] (checksum: bd7715304529f66c4d3493e786bb0f1f)

Если сгенерированный код будет отредактирован разработчиком, которого не проинформировали о назначении кодогенератора, то при следующем запуске Cog обнаружит, что контрольная сумма не сошлась, и прекратит обработку, чтобы избежать перезаписывания отредактированного участка кода.

Добавление суффикса к генерируемым строкам

Для того, чтобы было проще отличать сгенерированные строки когда вы проводите поиск в файлах при помощи grep, ключ командной строки -s позволяет указать суффикс, который будет добавляться к каждой непустой строке, генерируемой Cog. Например, имеется следующий входной файл (mycode.txt):

mycode.txt

[[[cog
cog.outl('Three times:\n')
for i in range(3):
    cog.outl('This is line %d' % i)
]]]
[[[end]]]

Запуская Cog такой командой:

cog -s " //(generated)" mycode.txt

получим такой выходной файл:

[[[cog
cog.outl('Three times:\n')
for i in range(3):
    cog.outl('This is line %d' % i)
]]]
Three times: //(generated)

This is line 0 //(generated)
This is line 1 //(generated)
This is line 2 //(generated)
[[[end]]]

Разное

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

Флаг -d указывает утилите Cog удалить все генераторы из выходного файла. Этот флаг позволяет вам генерировать код в общедоступных файлах, но при этом вы можете не показывать генератор своим клиентам.

Флаг -U заставляет Cog записывать выходные файлы с использованием Unix символа перевода строки (LF) вместо концовок строк, принятых на конкретной платформе. Вы можете использовать этот флаг в ОС Windows для создания выходных файлов в Unix-стиле.

Флаг -I добавляет каталог к списку путей, в которых ведется поиск Python-модулей, и в которых ведется поиск файлов для обработки модулем handyxml.

Флаг -z позволяет опускать строку с маркером [[[end]]], при этом предполагается, что он находится в конце файла.

История

Список изменений в версиях Cog приведен на отдельной странице.

Обратная связь

Мне будет приятно услышать о ваших успехах или трудностях при использовании Cog. Оставьте комментарий здесь, или напишите мне небольшое письмо. Пожалуйста, пишите на английском языке.

Ссылки

Имеется также несколько других воплощений Cog-идей:

  • Argent — реализация на языке Ruby.
  • Precog — реализация на языке PHP.
  • PCG — реализация на языке Perl.
  • Templarian — похожий инструмент, также написан на Python.
  • Nocog — инструмент для автоматического определения тех файлов, которые должны обрабатываться при помощи Cog.

Возможно, вы захотите прочитать:

  • Cog: A Code Generation Tool Written in Python, я написал историю про утилиту Cog, как пример успешного применения Python.
  • Мой блог, на страницах которого я рассуждаю о программном обеспечении, детях и других интересных для меня вещах.

Comments

Add a comment:

name
email
Ignore this:
not displayed and no spam.
Leave this empty:
www
not searched.
 
Name and either email or www are required.
Don't put anything here:
Leave this empty:
URLs auto-link and some tags are allowed: <a><b><i><p><br><pre>.