Learning Mako by Example
Mako is a powerful templating language built on top of Python. Mako is written by Mike Bayer. The development repository is on GitHub.
This page teaches Mako's most essential features through a series of examples. If you have questions about anything, or if you want to learn about other cool Mako features that are not covered here, refer to the official Mako docs.
Why Mako?
I need to explain why I chose Mako, rather than Jinja2, Cheetah, Django Templates, or others.
-
Very powerful, general. Can produce any kind of output.
-
Small feature set. Easy to teach it to my wife.
Text and Unicode
- text_example.makoEverything in this example is just text, so it goes directly to the output. Unicode is fully supported: 我喜欢看喜羊羊与灰太狼。 <p>HTML is just <b>text</b>.</p> *MarkDown* is also _just_ [text](http://itsjusttext.com/). def whatAboutCode(a,b,c): return 'Guess what -- code is text too!' (function() { console.log("It doesn't matter what programming language."); alert("It's all just text."); })();
- OutputEverything in this example is just text, so it goes directly to the output. Unicode is fully supported: 我喜欢看喜羊羊与灰太狼。 <p>HTML is just <b>text</b>.</p> *MarkDown* is also _just_ [text](http://itsjusttext.com/). def whatAboutCode(a,b,c): return 'Guess what -- code is text too!' (function() { console.log("It doesn't matter what programming language."); alert("It's all just text."); })();
Comments and <%text>
- comment_example.makoMako has two types of comments: ## A line comment starts with two '#' characters. ## Line comments can be intented. But you can't put comments at the end of lines. ## See? Here is a multi-line comment: <%doc> All these lines will not go to the output. </%doc> <%doc>You can also use <%doc> on a single line.</%doc> <%text> ## The <%text> tag tells Mako to just pass data directly to the output. ## This is sometimes useful when the data you want to produce ## conflicts with Mako syntax. ## Comments have no effect. All Mako syntax is disabled. <%doc>Same with multi-line comments.</%doc> ${"""You'll learn what these other features are supposed to do later. Right now, they do nothing because <%text> disables them."""} <%def name="F()">foo</%def> <% n=5 %> %if n>3: ${self.F()} %endif </%text> ## Finally, after the </%text> tag, Mako starts processing again.
- OutputMako has two types of comments: But you can't put comments at the end of lines. ## See? Here is a multi-line comment: ## The <%text> tag tells Mako to just pass data directly to the output. ## This is sometimes useful when the data you want to produce ## conflicts with Mako syntax. ## Comments have no effect. All Mako syntax is disabled. <%doc>Same with multi-line comments.</%doc> ${"""You'll learn what these other features are supposed to do later. Right now, they do nothing because <%text> disables them."""} <%def name="F()">foo</%def> <% n=5 %> %if n>3: ${self.F()} %endif
Escapes
- escape_example.makoMako only has two character escapes: * backslash+newline --> "" (empty string) (Enables you to prevent newlines from going to the output.) * '%%' at the beginning of a line --> '%' (A single '%' at the beginning of a line is interpreted as a flow control.) Examples: A back-slash at the end \ of a line will prevent \ the newline character \ from appearing in the \ output. %if True: # Flow Control lines start with a single '%'. %endif %% %% %% The first '%%' becomes '%'. %% %% %% Escape does not happen if the line is indented. The following escapes do NOT work: \\ $$ \$ \< \> \{ \} \( \) \! \@ \# \% \^ \& \* \( \) \= \+ \~ \' \" \? \<% foo='BAR' %> Code still runs. \${foo} Eval still occurs. ## If you want to escape anything other than a newline, you need to ## perform a string operation instead. You'll learn about the ## <% ... %> and ${...} operators later, but for now, pretend that ## you want to output a literal "${foo}", and you don't want Mako ## to interpret it. You *could* use the <%text> operator that we ## already learned about, or you could do something like this: ${'$'}{foo} ${'${foo}'} ${'%if True:'}
- OutputMako only has two character escapes: * backslash+newline --> "" (empty string) (Enables you to prevent newlines from going to the output.) * '%%' at the beginning of a line --> '%' (A single '%' at the beginning of a line is interpreted as a flow control.) Examples: A back-slash at the end of a line will prevent the newline character from appearing in the output. % %% %% The first '%%' becomes '%'. %% %% %% Escape does not happen if the line is indented. The following escapes do NOT work: \\ $$ \$ \< \> \{ \} \( \) \! \@ \# \% \^ \& \* \( \) \= \+ \~ \' \" \? \ Code still runs. \BAR Eval still occurs. ${foo} ${foo} %if True:
${...}
- eval_example.mako## ${...} performs an evaluation and puts the result in its place. ## The "..." can be any Python expression. <% foo='BAR' %> ${} ${'foo'} ${foo} ${2+3} The biggest number in the list is: ${max(1,2,3,4,5,4,3,2,1)}. ${'foo'.upper()}${ foo.lower() } ${ [1,2,3,4,5] } ${ {1:2, 3:4} } ${__import__('os').listdir('/')} <% class A(object): def __str__(self): return 'It uses __str__.' def __repr__(self): return 'It uses __repr__.' %> Does ${'${...}'} use __str__ or __repr__? ${A()}
- Outputfoo BAR 5 The biggest number in the list is: 5. FOObar [1, 2, 3, 4, 5] {1: 2, 3: 4} ['mnt', 'vmlinuz.old', 'sys', 'dev', 'home', 'initrd.img', 'usr', 'var', 'srv', 'tmp', 'lib', 'lost+found', 'root', 'run', 'initrd.img.old', 'vmlinuz', 'boot', 'sbin', 'media', 'bin', 'opt', 'lib64', 'cdrom', 'proc', 'etc'] Does ${...} use __str__ or __repr__? It uses __str__.
Flow Controls
- flow_example.mako## All Python flow controls are supported: ## if/elif/else, for, while, try/except, etc. ## You always need to use "%end..." to mark the end of blocks. <% n, foo = 5, 'BAR' %> ## Indentation doesn't matter at all. I just do it out of habit: <ul> %for i in range(n): <li> %if i == 3: Item Number Three: ${foo} <% break %> %else: Item #${i}: foo %endif </li> %endfor </ul> ## Look at the output that was produced by the loop. ## Notice that the 'break' command prevents the ## output of the final </li> tag, and also causes ## the </ul> to appear in a place that you ## probably didn't expect. ## Here's an example where I don't use indentation: <p>\ %try: ${open('/proc/version').read().strip()}\ %except: ERROR READING KERNEL VERSION\ %endtry </p>
- Output<ul> <li> Item #0: foo </li> <li> Item #1: foo </li> <li> Item #2: foo </li> <li> Item Number Three: BAR </ul> <p>Linux version 3.19.0-16-generic (buildd@komainu) (gcc version 4.9.2 (Ubuntu 4.9.2-10ubuntu13) ) #16-Ubuntu SMP Thu Apr 30 16:09:58 UTC 2015</p>
<% ... %>
- inline_exec_example.mako## The <% ... %> block performs "inline execution" of Python code. <% # This is normal Python code. def factorial(n): if n<=0: return 0 if n==1: return 1 return n*factorial(n-1) def times1000(n): return n*1000 items = [(n,factorial(n)) for n in range(10)] total = 0 %> %for (n,fact) in items: ## This code executes during each loop iteration: <% total += n %> factorial(${n})=${fact}, times1000(${n})=${times1000(n)} %endfor TOTAL = ${total}
- Outputfactorial(0)=0, times1000(0)=0 factorial(1)=1, times1000(1)=1000 factorial(2)=2, times1000(2)=2000 factorial(3)=6, times1000(3)=3000 factorial(4)=24, times1000(4)=4000 factorial(5)=120, times1000(5)=5000 factorial(6)=720, times1000(6)=6000 factorial(7)=5040, times1000(7)=7000 factorial(8)=40320, times1000(8)=8000 factorial(9)=362880, times1000(9)=9000 TOTAL = 45
<%! ... %>
- module_exec_example.mako## The <%! ... %> block performs "module execution" of Python code. ## It executes ONLY ONCE, when the generated Python module is loaded. ## IT DOES NOT EXECUTE WHERE IT APPEARS IN THE TEMPLATE. ## <%! ... %> is typically used for two things: ## * import of modules ## * definition of variables/functions/classes <%! import os, pyhpy inline, module = [], [] %> start <% inline.append('start') %> <%! module.append('start') %> %if False: if <% inline.append('if') %> <%! module.append('if') %> %elif True: elif <% inline.append('elif') %> <%! module.append('elif') %> %else: else <% inline.append('else') %> <%! module.append('else') %> %endif %for i in range(3): for <% inline.append('for') %> <%! module.append('for') %> %else: for-else <% inline.append('for-else') %> <%! module.append('for-else') %> %endfor %while True: while <% inline.append('while') %> <%! module.append('while') %> <% break %> %else: while-else <% inline.append('while-else') %> <%! module.append('while-else') %> %endwhile %try: try <% inline.append('try') %> <%! module.append('try') %> %except: except <% inline.append('except') %> <%! module.append('except') %> %else: try-else <% inline.append('try-else') %> <%! module.append('try-else') %> %finally: finally <% inline.append('finally') %> <%! module.append('finally') %> %endtry %with open('/proc/loadavg') as fobj: with <% inline.append('with') %> <%! module.append('with') %> ${fobj.read()} %endwith end <% inline.append('end') %> <%! module.append('end') %> inline execution = ${inline} module execution = ${module}
- Outputstart elif for for for for-else while try try-else finally with 0.24 0.42 0.38 3/1344 15682 end inline execution = ['start', 'elif', 'for', 'for', 'for', 'for-else', 'while', 'try', 'try-else', 'finally', 'with', 'end'] module execution = ['start', 'if', 'elif', 'else', 'for', 'for-else', 'while', 'while-else', 'try', 'except', 'try-else', 'finally', 'with', 'end']
<%def>
start
elif
for
for
for
for-else
while
try
try-else
finally
with
1.34 1.53 1.68 4/2210 21047
end
inline execution = ['start', 'elif', 'for', 'for', 'for', 'for-else', 'while', 'try', 'try-else', 'finally', 'with', 'end']
module execution = ['start', 'if', 'elif', 'else', 'for', 'for-else', 'while', 'while-else', 'try', 'except', 'try-else', 'finally', 'with', 'end']
- Output
- Output
- Simple text and unicode
- Comments, %text
- Newline escaping
- $ {...}
- if else
- for, while
- inline code
- module-level code
- accessible/overridable via self.attr (DO NOT MENTION. Encourages behavior that requres deep knowledge.)
- %block (REQUIRES TOO MUCH UNDERSTANDING, otherwise you can't see the difference between %def)
- %def
- escaping -- for html/url/markdown escaping/filtering, using the '|' pipe method and filter= method. Introduced here becasue module-level code is a good place for filters or imports of filters.
- inheritance
- self.body(), next.body()
- overriding defs and blocks
- capture()
- pyhpy fragile syntax!
- %include
- Better understanding of 'self', 'local', 'next', 'parent', 'UNDEFINED', and 'context'
- How to debug when things go wrong
- PyHPy features
- (provided in the _base.mako file of the example project): self.URL(), self.FS_ROOT()
- dependencies (## DEP) (MIGHT WANT TO CHANGE SYNTAX?)
- meta
- NOT SURE IF THIS IS THE RIGHT PAGE FOR THIS: Sane search paths.
- NOT SURE IF THIS IS THE RIGHT PAGE FOR THIS: error reporting
- IN MARKDOWN CHEAT SHEET: Mention double-space and equalSignHeader extensions
- Mention the featues we didn't cover. %block. %page. %namespace...