在Python或Perl中使用模板化配置?

2 投票
2 回答
583 浏览
提问于 2025-04-16 22:24

我正在尝试实现一个模板化的配置文件。虽然我更喜欢用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 个回答

4

我开发了一个叫做 Template::MasonLite 的工具,正是为了这个目的。我需要生成很多配置文件(在我的情况下是Apache的配置文件),而且希望这个脚本能在多个主机上运行,不需要额外的依赖。所以我开发了一个简化版的HTML::Mason,可以直接嵌入到我的脚本里(大约多了70行代码)。这个 Mason模板语法 使用Perl来处理逻辑(比如条件判断、循环等),而不是创造一种全新的语言。

Template::MasonLite并没有在CPAN上发布(只有上面的链接),因为我不想让CPAN上多出一个模板引擎,而且我也不确定这个模块应该叫什么名字。

我开发它的时候正在使用cfengine,但那个工具没有什么合理的模板功能。后来我换成了puppet,它自带了一个模板语言。我现在仍然用Template::MasonLite来 生成演示幻灯片

1

如果你不介意使用不同的语法,有几个模板库可以选择,比如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

撰写回答