Friday, December 20, 2013

mako二三事 -- Blocks二

二、命名Block

1. 命令Block的使用

相对匿名block,命名block的用途更广泛,不过命名block也有一些特有的属性,我们先来看看一段常用的代码:

<html>
<%block name="header">
    <head>
        <title>
            <%block name="title">Title</%block>
        </title>
    </head>
</%block>
<body>
</body>
</html>

这段代码中定义两个block,其中title内嵌在header中。不过在mako中,尽管他们的定义是有内嵌关系的,生成的block也是全局的。也就是说只要是命名block,在模板里面都是可以全局引用的。上面模板生成的代码如下:

def render_body(context,**pageargs):
    __M_caller = context.caller_stack._push_frame()
    try:
        __M_locals = __M_dict_builtin(pageargs=pageargs)
        def header():                   # 所有的block都是全局的,所以所有block都会在这里定义函数
            return render_header(context.locals_(__M_locals))
        def title():                        # 所有的block都是全局的,所以所有block都会在这里定义函数
            return render_title(context.locals_(__M_locals))
        __M_writer = context.writer()
        # SOURCE LINE 1
        __M_writer(u'\n\n')
        if 'parent' not in context._data or not hasattr(context._data['parent'], 'header'):
            context['self'].header(**pageargs)     # 如果有继承关系的话,提取最顶层的模板(self),然后调用其中的header block


        # SOURCE LINE 9
        __M_writer(u'\n\n\n
\n')        return ''
    finally:
        context.caller_stack._pop_frame()


def render_header(context,**pageargs):
    … ...


def render_title(context,**pageargs):
    … ...


2. 重复调用Block

一个block定义之后,可以在模板里面多次引用,引用方式跟函数调用一样。如:

<div name="page">
    <%block name="pagecontrol">
        <a href="">previous page
|        <a href="">next page
    </%block>

    <table>
        ## some content
    </table>

    ${pagecontrol()}
</div>

这段代码将在table上下都生成一个pagecontrol。

通过上面几个例子我们可以看到:
* Block定义是全局的,所以Block不能重名,即便是内嵌的Block也不能重名
* Block不像函数一样可以带参数
* Block 不能在def中、<% call>、<%namespacename:defname>等地方使用

Thursday, December 12, 2013

mako二三事 -- Blocks一

 除了函数,mako还提供了一种特殊的结构 — Block。在很大程度上,Block跟Def非常相似,但作为模板,需要构建灵活多变的页面结构,Block又有着一些Def所不具备的特殊特性。

一、Block的使用

block跟def最大的一个区别就是def在定义之后需要专门去调用它才会生效,block不需要,block定义之后就会默认在该定义处生效渲染。正因为这种特性,block就可以匿名使用。例如:

<html>
    <body>
        <%block>
            this is a block.
        </%block>
    </body>
</html> 

将会生成

<html>
    <body>
            this is a block.
    </body>
</html> 

该block的定义如果使用def来表达的话,等同于:

<html>
    <body>
        <%def name="testblock()">
            this is a block.
        </%def>${testblock()}
    </body>
</html>

尽管在用法上与def有所不同,def上的一些比较有用的参数还是可以在block上使用的,如:

<html>
    <body>
        <%block filter="h">
            this is some escaped html.
        </%block>
    </body>
</html>

<html>
    <body>
        <%block cached="True" cache_timeout="60">
            This content will be cached for 60 seconds.
        </%block>
    </body>
</html> 

% for i in range(1, 4):
    <%block>i is ${i}</%block>
% endfor

我们再来看一下第一段block模板生成的代码:

def render_body(context,**pageargs):
    __M_caller = context.caller_stack._push_frame()
    try:
        __M_locals = __M_dict_builtin(pageargs=pageargs)
        def __M_anon_4():                    # 跟def类似,block块在python中生成的代码也是一个内嵌函数
            __M_caller = context.caller_stack._push_frame()
            try:
                __M_writer = context.writer()
                # SOURCE LINE 4
                __M_writer(u'\n            this is a block.\n        ')
                return ''
            finally:
                context.caller_stack._pop_frame()
        __M_writer = context.writer()
        # SOURCE LINE 1
        __M_writer(u'\n\n    \n        ')
        __M_anon_4()               # 调用block
        # SOURCE LINE 6
        __M_writer(u'\n    
\n\n')        return ''
    finally:
        context.caller_stack._pop_frame() 

因为这里使用了匿名block,所以名字为anon加一个递增数字。同样的,匿名函数不支持其他地方调用,所哟没有生成一个render_XXX函数。

Tuesday, December 10, 2013

mako二三事 -- 函数四

九、def的其他的调用方式

mako中的函数不仅仅是函数,还有一种模板特有的调用方式。我们经常使用它来创建自定义标签:

1. 自定义标签

<%def name="buildtable()">
   

       
            ${caller.body()}
       
   
</%def>

<%self:buildtable>
    I am the table body.
</%self:buildtable> 

输出:

   

       

    I am the table body.

       
   
 
self:buildtable 这种用法是mako0.2.3之后引入的一种用法,通过这种方式,可以创建任意的定制标签。在这段代码中,需要注意的是:
a. ${caller.body()}表示读取调用者的body部分内容,这里就是    I am the table body.
b. self为namespace,这里的self表示本模块。也可以是其他包含所需函数的模块名


2. 带参数的标签

函数可以带参数执行,做成自定义标签之后,也一样可以带入需要的参数,如:

<%def name="lister(count)">
    % for x in range(count):
        ${caller.body()}
    % endfor
</%def>

<%self:lister count="${3}">
    hi
</%self:lister>

输出:
hi
hi
hi

这里的lister函数就带了一个叫count的参数,因此在调用的时候,使用count="${3}”传入该参数。这段代码需要注意的地方是这个函数需要的是一个整形,而参数的值需要用”"包含,因此我们使用了mako的表达式${3},这里表示传入了一个表达式,该表达式的值为3.


3. body参数

caller.body()输出的内容是调用者中间部分的模板内容,因此,我们也可以body传一个参数到这些模板内容里去。比如我们在body中传入一个参数caller.body(name=“cat"),那么在调用者模板内容中就可以用 hi ${name}来访问这个传入的参数。不过我们还需要在调用者那里也申明一下这个参数。例如:

<%def name="layoutdata(somedata)">
    <table>
    % for item in somedata:
        <tr>
        % for col in item:
            <td>${caller.body(col=col)}</td>
        % endfor
        </tr>
    % endfor
    </table>
</%def>

<%self:layoutdata somedata="${[[1,2,3],[4,5,6],[7,8,9]]}" args="col">\
Body data: ${col}\
</%self:layoutdata> 

输出:

    <table>
        <tr>
            <td>Body data: 1</td>
            <td>Body data: 2</td>
            <td>Body data: 3</td>
        </tr>
        <tr>
            <td>Body data: 4</td>
            <td>Body data: 5</td>
            <td>Body data: 6</td>
        </tr>
        <tr>
            <td>Body data: 7</td>
            <td>Body data: 8</td>
            <td>Body data: 9</td>
        </tr>
    </table>

这段代码中:
a. ${caller.body(col=col)}中定义了一个col的参数,将每次循环的col值传入其中。
b. args="col”申明了col参数
c. Body data: ${col}\使用col参数输出,这里的\表示模板不换行


4. 自定义方法

body是mako中内置的方法,其实我们还可以在调用者模板内容处自定义需要的函数供外面的函数使用。

<%def name="layout()">
    ## a layout def
    <div class="mainlayout">
        <div class="header">
            ${caller.header()}
        </div>

        <div class="sidebar">
            ${caller.sidebar()}
        </div>

        <div class="content">
            ${caller.body()}
        </div>
    </div>
</%def>

## calls the layout def
<%self:layout>
    <%def name="header()">
        I am the header
    </%def>
    <%def name="sidebar()">
        <ul>
            <li>sidebar 1</li>
            <li>sidebar 2</li>
        </ul>
    </%def>

        this is the body
</%self:layout> 

输出

    <div class="mainlayout">
       


        I am the header

        </div>

        <div class="sidebar">

        <ul>
            <li>sidebar 1</li>
            <li>sidebar 2</li>
        </ul>

        </div>

        <div class="content">




        this is the body

        </div>
    </div> 

在这个例子里,我们将调用者模板内容分成了三个部分,header、sidebar、以及剩下的body。因此我们就可以在layout函数中调用者三个函数。现在有模板特有的那种搭积木般的感觉了吧。只要把layout和调用者分别放入不同的文件中,layout就变成了一个页面框架的定义模板,不同的调用者可以使用这个公用的模板,而其中的内容各不相同。

mako二三事 -- 函数三

 八 、mako中def的一些特定参数

前面我们就提到过,mako的函数是一种增强性的函数,除了可以往函数中传递参数之外,还可以为该函数定义一些额外的参数。例如,

1. cache

我们可以为一个mako函数单独定义一个cache,设定cache过期时间。

<%def name="somedef()"cached="True" cache_timeout="60">
<%
    import time
%>
time = ${time.time()}
</%def>

${somedef()}


2. buffer

在写mako模板的时候,要牢牢记住的一点是:不论是否在函数内,模板内容都是直接写到输出的。也就是说,所有函数,不做特殊处理的话,它的返回值都是空字符串。这在平常可能不会注意到,但如果遇到类似如下的用法的时候就会有问题:

${" results " + somedef() + " more results"}

 上面的${}中是一个表达式,这在mako中是合法的。如果这里的somedef函数输出somedef's results字符串,那我们会得到如下结果:

somedef's results results more results

这是因为,在函数somedef运行时,其运行结果就已经写到 输出中了。然后,somedef返回'',最后" results " + ''+ " more results"结果求值之后输出,这就是上面这个结果。

有的时候,我们希望将somedef这个函数的运行结果先暂存起来,经过一定处理之后,再写入到输出中。因此,mako引入了buffered这个参数来解决这个问题。(注意,这里的buffer跟cache是完全不同的概念。)

<%def name="somedef()" buffered="True">
    somedef's results
</%def> 
${" results " + somedef() + " more results"} 

输出:

results somedef's results more results

我们来看一下这种方式生成的py文件:

def render_somedef(context):
    __M_caller = context.caller_stack._push_frame()
    try:
        context._push_buffer()
        __M_writer = context.writer()
        # SOURCE LINE 1
        __M_writer(u"\n    somedef's results\n")
    finally:
        __M_buf = context._pop_buffer()
        context.caller_stack._pop_frame()
    return __M_buf.getvalue() 

可以看到,该函数创建了一个暂存区,然后将其中的数据作为了return的返回值。

如果只是希望临时性的获取一下mako函数运行结果进行一些测试,而不是直接buffer起来,mako也提供了一个叫capture的方法。通过capture,可以截获一个普通的mako的运行结果,转成返回值。例如

<%def name="somedef(age=8, weight=100)">
    somedef's results ${age} ${weight}
</%def> 
${" results " + capture(somedef, 10, weight=120) + " more results"}  

输出:

results somedef's results 10 120 more results


3. filter

mako本身是一个模板,因此也就带了一系列的filter,通过这些filter的作用,可以很灵活的将数据转换成特定的格式。在mako定义函数时,也引入了filter概念。注意,因为需要filter,也就必须要截获函数的运行结果,因此,filter默认也带有buffer功能。例如

<%def name="foo()" filter="h, trim">
    this is bold
</%def>
${foo()}

输出为:

<b>this is bold</b> 

这里需要注意的是,filter是def的参数,不是foo的参数。这个filter仅仅作用于该函数的生成结果。


4. Decorating

除了filter之外,mako还提供了Decorating的功能,不过因为模板的特殊性,mako函数的Decorating跟python中的还是有很多区别的。例如

<%!
    def bar(fn):
        def decorate(context, *args, **kw):
            context.write("BAR")
            fn(*args, **kw)
            context.write("BAR")
            return 'mytest'
        return decorate
%>

<%def name="foo()" decorator="bar">
    this is foo
</%def>

${"abc"+foo()}

输出:

BAR
    this is foo
BARabcmytest 

注意:
a. mako里面所有的函数第一个参数都是context,所以 decorate函数的第一个参数必须是context。
b. 如果希望在decorate输出内容,可以使用context.write函数,这也是mako推荐的方式。
c. 这里的decorate应该返回'',像上示例中返回其他内容的话会加到输出结果中去,但要注意加入的位置。
d. 如果用template.get_def('somedef').render()之类的方法来渲染模板,则只会使用context.write的结果,抛弃return返回值。
>>> print template.get_def("foo").render()
BAR
    this is foo
BAR 

另外,capture函数也可以用于构建decorate,例如:
<%!
    def bar(fn):
        def decorate(context, *args, **kw):
            return "BAR" + runtime.capture(context, fn, *args, **kw) + "BAR"
        return decorate
%>

<%def name="foo()" decorator="bar">
    this is foo
</%def>

${foo()} 

Sunday, December 1, 2013

mako二三事 -- 函数二


五、调用其他模板中的函数

mako也能支持调用其他模板中的函数,因此,多个函数可以集合成一个mako函数库。不过mako仅支持顶层函数的跨模块调用,而且需要通过namespace标签来引入,例如,在mystuff.html中定义了一个函数account:

<%namespace name="mystuff" file="mystuff.html"/> 

如上定义之后,就可以使用

${mystuff.account()}

来调用该函数了。这种方式看起来跟python中的import XXX as XXX 是一致的。不过mako的实现还是不一样的,我们来看一下这个模板编译之后的代码:

def _mako_get_namespace(context, name):
    try:
        return context.namespaces[(__name__, name)]   # namespace缓存机制,如果已经存在,不需要再载入这个namespace
    except KeyError:
        _mako_generate_namespaces(context)
        return context.namespaces[(__name__, name)]
def _mako_generate_namespaces(context):
    # SOURCE LINE 3       # namespace语句对应的生成语句,每个namespace语句分别生成。
    ns = runtime.TemplateNamespace(u'mystuff', context._clean_inheritance_tokens(), templateuri=u'mystuff.html', callables=None,  calling_uri=_template_uri)
    context.namespaces[(__name__, u'mystuff')] = ns    # 将namespace缓存到context中

def render_body(context,**pageargs):
    __M_caller = context.caller_stack._push_frame()
    try:
        __M_locals = __M_dict_builtin(pageargs=pageargs)
        mystuff = _mako_get_namespace(context, 'mystuff’)      # 从namespace中取得该函数,即可使用
        __M_writer = context.writer()
        # SOURCE LINE 2
        __M_writer(u'\n')
        # SOURCE LINE 3
        __M_writer(u' \n')
        # SOURCE LINE 4
        __M_writer(unicode(mystuff.account()))    # 函数调用
        __M_writer(u'\n')
        return ''
    finally:
        context.caller_stack._pop_frame() 

python支持from XXXX import XX, XXX这种import方式,mako也支持类似方式,如:

<%namespace file="mystuff.html" import="foo, bar"/> 
<%namespace file="mystuff.html" import=“*"/>  


六、在python中调用模板函数

mako支持通过python程序来调用模板函数,这种方式在某些特定场合可以使用。如:

from mako.template import Template

template = Template("""
    <%def name="hi(name)">
        hi ${name}!
    </%def>

    <%def name="bye(name)">
        bye ${name}!
    </%def>
""")

print template.get_def("hi").render(name="ed")
print template.get_def("bye").render(name="ed") 


七、嵌套函数

mako也可以像python一样支持嵌套函数,而且其作用也跟python中的一模一样,编译成py文件后也是嵌套函数,因此其中变量的作用域也跟python中的类似。注意,内嵌的函数不能被其他模块引用。
<%
    x = 12
%>
<%def name="outer()">
    <%
        y = 15
    %>
    <%def name="inner()">
        inner, x is ${x}, y is ${y}
    </%def>
    ${inner()}
    outer, x is ${x}, y is ${y}
</%def>
${outer()} 

输出:
inner, x is 12, y is 15 
outer, x is 12, y is 15 

在上面代码中,还需要注意的一点是,这里的x并不会生成一个全局的变量,在模板里写的所有内容都会被包括在一个叫render_body的函数里面,如果需要在多个模板之间传递、共享一些数据需要使用特殊的技巧,这个在Context里面会讲到。