Cog
Created 10 November 2004, last updated 30 September 2020
Оригинал текста на английском языке находится здесь.
Перевод на русский язык: Александр Бельченко, страница обновлена 19 мая 2008.
Перевод обновлён decorator-factory 30 сентября 2020.
Cog — это инструмент для препроцессинга файлов. Он позволяет использовать фрагменты кода на Python для генерации желаемого текста.
Разделы:
- Что делает эта утилита?
- Дизайн
- Установка
- Как писать исходные файлы
- Модуль Cog
- Запуск Cog
- История
- Обратная связь
- Ссылки
Что делает эта утилита?
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. Строки между [[[cog и ]]] содержат код на Python. Строки между ]]] и [[[end]]] содержат результат работы генератора.
Когда Cog начинает работать, он удаляет последний сгенерированный результат, затем выполняет код на Python и записывает новый результат работы в файл. Все строки текста, находящиеся за пределами специальных маркеров, остаются нетронутыми.
Строки с Cog-маркерами могут содержать любой текст в дополнение к тройным квадратным скобкам. Это позволяет спрятать код Python-генератора в исходном файле. В вышеприведенном примере весь кусок Python-кода — это C++ комментарий. Таким образом Python-код может быть оставлен в тексте, в то время как файл будет восприниматься как обычный C++ код.
Дизайн
Cog спроектирован так, чтобы быть простым и удобным инструментом. Он записывает результат своей работы в исходный файл, сохраняя при этом встроенный код генератора. Это означает, что Cog может быть запущен любое число раз для одного и того же файла. Вместо того чтобы использовать отдельный исходный файл с кодом генератора и отдельный выходной файл, Cog в процессе работы использует только один файл, который является и генератором, и выходным файлом.
Поскольку маркерные строки адаптированы под синтаксис любого языка, то маркеры могут скрыть Python-код генератора внутри исходного файла. Это означает, что Cog-файлы могут быть включены в базу контроля версий, и при этом не нужно беспокоиться о том, чтобы хранить отдельно исходный файл с генератором и результирующий выходной файл. Это также означает, что вам не нужно менять сложившуюся методику компиляции-сборки проекта и т.д.
Я экспериментировал с различными инструментами, которые генерируют некоторый код по шаблону. И я замечал, что мне постоянно приходиться бороться с появлением нежелательных символов пробелов в генерируемом выходном файле, и что я мысленно преобразовывал некоторый воображаемый Python-код в его эквивалент в виде шаблона. Преимущества систем-шаблонизаторов (в которых большинство кода может быть задано литерально) исчезают, как только задача генерации кода становится все более и более сложной, и как только в процессе генерации требуется все больше логики.
Cog позволяет использовать все возможности Python для генерации текста без ограничений, которые накладывают шаблонизаторы.
Установка
Cog работает на Python версии 2.7, 3.5, 3.6, 3.7, 3.8 или Jython 2.5.
Cog устанавливается обычным образом, за исключением того, что имя пакета — “cogapp”, а не “cog”:
$ pip install cogapp
По окончании установки в подкаталоге scripts для Python-скриптов должен появиться скрипт cog.py. Cog также можно запустить через команду “cog”.
Лицензия
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]]]
Эта форма записи также может использоваться для простого импорта модуля. Исполняемые операторы верхнего уровня в таком модуле могут производить действия по генерации кода.
При желании можно изменить маркеры с помощью аргумента командной строки “--markers” (см. python3 -m cogapp -h для справки).
Если в одном и том же файле присутствует несколько вставок с генераторами, то они будут исполняться с использованием единого словаря глобальных имён (параметр “globals” для exec, доступный через вызов функции “globals()”). Таким образом, их поведение будет аналогичным тому, как если бы они все были в одном Python-модуле.
Cog старается соблюдать ваши правила по использованию пробелов. Ваш Python-код может иметь блочный отступ от левого края в соответствии с окружающим его текстом. При этом Cog также будет добавлять необходимые отступы в своем выводе, чтобы соблюсти эту закономерность. Весь вывод генератора собирается в единый блок текста, общий отступ для всех строк удаляется и затем блок текста выравнивается отступами согласно отступу самого cog-генератора. Это значит, что самый крайний слева непробельный символ в вашем выводе будет иметь такой же отступ, как и маркер начала кода cog-генератора. Остальные строки в вашем выводе будут сохранять свои отступы относительно друг друга.
Модуль Cog
Модуль Cog предоставляет вам для использования функции, при помощи которых можно выводить желаемые строки в ваш файл. Ниже приведено описание этих функций.
- cog.out(sOut=’’ [, dedent=False][, trimblanklines=False])
- Пишет текст в область вывода.
- sOut — это строковая переменная для вывода.
- Если аргумент dedent равен True, то все пробельные символы в начале строк в sOut удаляются перед добавлением к блоку вывода. Если аргумент trimblanklines равен True, то все пустые строки в начале и конце sOut удаляются перед добавлением к блоку вывода. Вместе, эти две опции облегчают использование многострочных строковых переменных:
- 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.out("""
These are lines I
want to write into my source file.
""", dedent=True, trimblankline=True)
В дистрибутив Cog включен модуль handyxml, поскольку я использую файлы данных в формате XML в своих собственных задачах генерации кода.
Запуск Cog
Запуск Cog выполняется из командной строки, аргументы передаются стандартным образом.
$ cog -h
cog - генерация исходных текстов при помощи встроенного Python-кода.
cog [OPTIONS] [INFILE | @FILELIST] ...
INFILE - имя входного файла, используйте '-' для чтения из stdin
FILELIST - имя текстового файла, который содержит набор имен файлов
или ссылки на другие @FILELIST.
OPTIONS:
-c Добавление контрольной суммы к генерируемому тексту
для защиты от случайных изменений.
-d Удаление кода генератора из выходного файла.
-D name=val Определение глобальной строки, доступной для использования
в коде вашего генератора.
-e Выдать предупреждение, если файл не содержит cog-кода.
-I PATH Добавить PATH к списку каталогов, в которых будет
производиться поиск модулей и файлов с данными.
-n ENCODING Кодировка, используемая для чтения и записи файлов.
-o OUTNAME Записать вывод в файл OUTNAME.
-p PROLOGUE Добавить в начало каждого отрывка код, например: -p "import math"
-r Записать вывод назад во входной файл.
-s STRING Ко всем сгенерированным строкам добавляется суффикс STRING.
-U Записывать в файл с Unix-концовками строк (только символ LF).
-w CMD Использовать команду CMD, если необходимо разрешить
запись в выходной файл.
Вместо %s в CMD будет подставлено имя файла.
-x Удалить все автоматически сгенерированные строки
без запуска генератора.
-z Маркер конца может быть опущен, предполагается, что он
находится в конце файла.
-v Напечатать версию Cog и выйти.
--verbosity=VERBOSITY
Управлять подробностью вывода. 2 (по умолчанию) выводит все файлы,
1 выводит только изменённые, 0 не выводит файлы.
--markers='START END END-OUTPUT'
Маркеры, используемые для нахождения кода на Python. Строка
Должна включать три значения, разделённые пробелами. Значение
По умолчанию -- '[[[cog ]] [[[end]]]'.
-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]]]
Разное
Опция -n позволяет изменить кодировку, которая используется при чтении и записи файлов.
Опция --verbose позволяет управлять количеством вывода, который производит cog. --verbose=2 — опция по умолчанию — с ней cog напечатает имя каждого файла, который он просмотрел, и укажет, изменил ли он его. С --verbose=1 cog напечатает только изменённые файлы. С --verbose=0 cog не напечатает информацию о файлах
Опция --markers позволяет управлять синтаксисом маркерных линий. Это полезно, если синтаксис файла не позволяет использовать разделители по умолчанию. Значение опции должно состоять из трёх частей, разделённых пробелами. Значение по умолчанию — “[[[cog ]]] [[[end]]]”
Флаг -x указывает утилите Cog удалить весь ранее сгенерированный текст без запуска кодогенераторов. Этот флаг позволяет вам удалить весь автоматически сгенерированный код из исходного файла.
Флаг -d указывает утилите Cog удалить все генераторы из выходного файла. Этот флаг позволяет вам генерировать текст в общедоступных файлах, не показывая генератор своим клиентам.
Флаг -U заставляет Cog записывать выходные файлы с использованием Unix символа перевода строки (LF) вместо концовок строк, принятых на конкретной платформе. Вы можете использовать этот флаг в ОС Windows для создания выходных файлов в Unix-стиле.
Флаг -I добавляет каталог к списку путей, в которых ведется поиск Python-модулей, и в которых ведется поиск файлов для обработки модулем handyxml.
Флаг -p позволяет указать преамбулу — фрагмент кода, который добавляется к коду на Python. Это позволяет убрать общие импорты из кода.
Флаг -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: