在Python或Perl中使用模板化配置?
我正在尝试实现一个模板化的配置文件。虽然我更喜欢用Python,但如果有Perl的答案也可以。我在示例中使用了Perl。
我搜索了一些资料,找到了以下内容: - Python单一配置文件 - ConfigObj - Python配置文件生成器 - ePerl 但这些都没有解决我的问题。
我想生成一个主要是INI格式的配置文件(甚至没有部分):
# Comments
VAR1 = value1
EDITOR = vi
我需要从一个模板生成这个文件,模板中嵌入了一种脚本语言:
# Config:
MYPWD = <: `pwd` :>
在'<:'和':>'之间的文本将使用脚本语言(Python或Perl)。就像模板一样,它的输出会被捕获并插入到最终的文本中。示例中使用的模板化方法基本上是eperl,但如果有Python的实现我会更喜欢。
最后,定义的变量应该是可重用的:
# Config:
CODE_HOME = /some/path
CODE_BIN = <:=$CODE_HOME:>/bin
这是我读取的测试源文件:
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# platform.cfg
# This one variable
VAR =value
# this is a templated variable. The langage is perl, but could be python.
HELLO= <: print 'World' :>
# This is a multi-line code which should resolve to a single line value.
LONGER = <:
if (1) {
print "abc ";
}
$pwd = `/bin/pwd`;
chomp($pwd);
print $pwd;
:>
# Another one to test the carriage returns.
MULTIPLE = /<: print "proj" :>/<: print "tahiti":>/<:
print "pd/1/";
$system = `grep -w VAR platform.cfg | egrep -v 'print|platform.cfg' | cut -d = -f 2-`;
chomp($system);
print $system;
:>
# variables dependent from the previous variable definition
VAR1 = <: print $VAR :>1
# variables dependent from the previous variable definition
VAR2 = <: print $VAR1 :>2
# variables dependent from the previous variable definition
VAR3 = <: print $VAR2 :>3
# variables dependent from the previous variable definition
VAR4 = <: print $VAR3 :>4
# BTW, multi-line comments are significant
# and should be preserved as the documentation for the
# variable just below:
VAR5 = <: print $VAR4 :>5
VAR6 = <: print $VAR5 :>6
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
我希望从脚本中得到这个结果。我无法弄清楚如何让配置文件中定义的变量成为解释器的一部分?
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# platform.cfg
# This one variable
VAR =value
# this is a templated variable. The langage is perl, but could be python.
HELLO= World
# This is a multi-line code which should resolve to a single line value.
LONGER = abc /src/byop/CODE
# Another one to test the carriage returns.
MULTIPLE = /proj/tahiti/pd/1/value
# variables dependent from the previous variable definition
VAR1 = value1
# variables dependent from the previous variable definition
VAR2 = value12
# variables dependent from the previous variable definition
VAR3 = value123
# variables dependent from the previous variable definition
VAR4 = value1234
# BTW, multi-line comments are significant
# and should be preserved as the documentation for the
# variable just below:
VAR5 = value12345
VAR6 = value123456
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
谢谢你的建议。
2 个回答
我开发了一个叫做 Template::MasonLite 的工具,正是为了这个目的。我需要生成很多配置文件(在我的情况下是Apache的配置文件),而且希望这个脚本能在多个主机上运行,不需要额外的依赖。所以我开发了一个简化版的HTML::Mason,可以直接嵌入到我的脚本里(大约多了70行代码)。这个 Mason模板语法 使用Perl来处理逻辑(比如条件判断、循环等),而不是创造一种全新的语言。
Template::MasonLite并没有在CPAN上发布(只有上面的链接),因为我不想让CPAN上多出一个模板引擎,而且我也不确定这个模块应该叫什么名字。
我开发它的时候正在使用cfengine,但那个工具没有什么合理的模板功能。后来我换成了puppet,它自带了一个模板语言。我现在仍然用Template::MasonLite来 生成演示幻灯片。
如果你不介意使用不同的语法,有几个模板库可以选择,比如mako,它的风格很相似,还有Jinja2,也挺不错的。建议使用一些经过验证的库!如果你真的想自己实现一个模板库,这里有个起步的思路:
import re, StringIO, sys
def exec_block(block, variables):
"""Captures output of exec'd code block"""
code = compile(block.strip(), '<string>', 'exec')
_stdout, result = sys.stdout, StringIO.StringIO()
sys.stdout = sys.__stdout__ = result
exec(code, variables)
sys.stdout = sys.__stdout__ = _stdout
return result.getvalue()
def format_template(template):
"""Replaces code blocks with {0} for string formating later"""
def sub_blocks(matchobj):
"""re.sub function, adds match to blocks and replaces with {0}"""
blocks.append(matchobj.group(0)[2:-2].strip())
return '{0}'
blocks = []
template = re.sub(r'<:.+?:>', sub_blocks, template, flags=re.DOTALL).splitlines()
blocks.reverse()
return blocks, template
def render_template(blocks, template):
"""renders template, execs each code block and stores variables as we go"""
composed, variables = [], {}
for line in template:
if '{0}' in line:
replacement = exec_block(blocks.pop(), variables).strip()
line = line.format(replacement)
if not line.startswith('#') and '=' in line:
k, v = [x.strip() for x in line.split('=')]
variables[k] = v
composed.append(line)
return '\n'.join(composed)
if __name__ == '__main__':
import sys
with open(sys.argv[1]) as f:
blocks, template = format_template(f.read())
print rend_template(blocks, template)
这个基本上和上面说的差不多,只不过代码块是用Python写的。每次赋值只支持一个代码块,这其实是我觉得最好的方法。你可以给它一个配置文件,比如:
VAR = value
LONGER = <:
print 'something'
:>
VAR1 = <: print VAR :>1
# comment
VAR2 = <: print VAR1 :>2
VAR3 = <: print VAR2 :>3
VAR4 = <: print VAR3 :>4
然后它会执行每个代码块,把变量渲染出来:
VAR = value
LONGER = something
VAR1 = value1
# comment
VAR2 = value12
VAR3 = value123
VAR4 = value1234