说实话,关于自定义扩展的开发,Jinja2的官方文档写得真心的简单。到目前为止网上可参考的资料也非常少,你必须得好好读下源码,还好依然有乐于奉献的大牛们分享了些文章来帮助我理解怎么开发扩展。本文我就完全借鉴网上前人的例子,来给大家演示一个Jinja2的自定义扩展的开发方法。
Pygments是Python提供语法高亮的工具,官网是trustauth.cn。我们在介绍Jinja2的自定义扩展时为什么要介绍Pygments呢?因为Jinja2的功能已经很强了,我一时半会想不出该开发哪个有用的扩展,写个没意义的扩展嘛,又怕误导了读者。恰巧网上找到了一位叫Larry的外国友人开发了一个基于Pygments的代码语法高亮扩展,感觉非常实用。他的代码使用了MIT License,那就我放心拿过来用了,不过还是要注明下这位Larry才是原创。
你需要先执行”pip install pygments”命令安装Pygments包。代码中用到Pygments的部分非常简单,主要就是调用”pygments.highlight( )”方法来生成HTML文档。Pygments强的地方是它不把样式写在HTML当中,这样就给了我们很大的灵活性。开始写扩展前,让我们预先通过代码
1 2 | from pygments . formatters import HtmlFormatter HtmlFormatter ( style = ‘vim’ ) . get_style_defs ( ‘.highlight’ ) |
生成样式内容并将其保存在”static/css/style.css”文件中。这个css文件就是用来高亮语法的。
想深入了解Pygments的朋友们,可以先把官方文档看一下。
我们在Flask应用目录下,创建一个”pygments_ext.py”文件,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | #coding:utf8 from jinja2 import nodes from jinja2 . ext import Extension from pygments import highlight from pygments . formatters import HtmlFormatter from pygments . lexers import guess_lexer , get_lexer_by_name # 创建一个自定义扩展类,继承jinja2.ext.Extension class PygmentsExtension ( Extension ) : # 定义该扩展的语句关键字,这里表示模板中的{% code %}语句会该扩展处理 tags = set ( [ ‘code’ ] ) def __init__ ( self , environment ) : # 初始化父类,必须这样写 super ( PygmentsExtension , self ) . __init__ ( environment ) # 在Jinja2的环境变量中添加属性, # 这样在Flask中,就可以用app.jinja_env.pygments来访问 environment . extend ( pygments = self , pygments_support = True ) # 重写jinja2.ext.Extension类的parse函数 # 这是处理模板中{% code %}语句的主程序 def parse ( self , parser ) : # 进入此函数时,即表示{% code %}标签被找到了 # 下面的代码会获取当前{% code %}语句在模板文件中的行号 lineno = next ( parser . stream ) . lineno # 获取{% code %}语句中的参数,比如我们调用{% code ‘python’ %}, # 这里就会返回一个jinja2.nodes.Const类型的对象,值为’python’ lang_type = parser . parse_expression ( ) # 将参数封装为列表 args = [ ] if lang_type is not None : args . append ( lang_type ) # 下面的代码可以支持两个参数,参数之间用逗号分隔,不过本例中用不到 # 这里先检查当前处理流的位置是不是个逗号,是的话就再获取一个参数 # 不是的话,就在参数列表最后加个空值对象 # if parser.stream.skip_if(‘comma’): # args.append(parser.parse_expression()) # else: # args.append(nodes.Const(None)) # 解析从{% code %}标志开始,到{% endcode %}为止中间的所有语句 # 将解析完后的内容存在body里,并将当前流位置移到{% endcode %}之后 body = parser . parse_statements ( [ ‘name:endcode’ ] , drop_needle = True ) # 返回一个CallBlock类型的节点,并将其之前取得的行号设置在该节点中 # 初始化CallBlock节点时,传入我们自定义的”_pygmentize”方法的调用, # 两个空列表,还有刚才解析后的语句内容body return nodes . CallBlock ( self . call_method ( ‘_pygmentize’ , args ) , [ ] , [ ] , body ) . set_lineno ( lineno ) # 这个自定义的内部函数,包含了本扩展的主要逻辑。 # 其实上面parse()函数内容,大部分扩展都可以重用 def _pygmentize ( self , lang_type , caller ) : # 初始化HTML格式器 formatter = HtmlFormatter ( linenos = ‘table’ ) # 获取{% code %}语句中的内容 # 这里caller()对应了上面调用CallBlock()时传入的body content = caller ( ) # 将模板语句中解析到了lang_type设置为我们要高亮的语言类型 # 如果这个变量不存在,则让Pygmentize猜测可能的语言类型 lexer = None if lang_type is None : lexer = guess_lexer ( content ) else : lexer = get_lexer_by_name ( lang_type ) # 将{% code %}语句中的内容高亮,即添加各种<span>, class等标签属性 return highlight ( content , lexer , formatter ) |
这段程序解释起来太麻烦,我就把注释都写在代码里了。总的来说,扩展中核心部分就在”parse()”函数里,而最关键的就是这个”parser”对象,它是一个”jinja2.parser.Parser”的对象。建议大家可以参考下它的源码。我们使用的主要方法有:
在”parse()”函数最后,我们创建了一个”nodes.CallBlock”的块节点对象,并将其返回。初始化时,我们先传入了”_pygmentize()”方法的调用;然后两个空列表分别对应了字段和属性,本例中用不到,所以设空;再传入解析后的语句块”body”。”CallBlock”节点初始化完后,还要记得将当前行号设置进去。接下来,我们对于语句块的所有操作,都可以写在”_pygmentize()”方法里了。
“_pygmentize()”里的内容我就不多介绍了,只需要记得声明这个方法时,最后一定要接收一个参数caller,它是个回调函数,可以获取之前创建”CallBlock”节点时传入的语句块内容。
扩展写完了,其实也没几行代码,就是注释多了点。现在我们在Flask应用代码中将其启用:
1 2 3 4 5 | from flask import Flask , render_template from pygments_ext import PygmentsExtension app = Flask ( __name__ ) app . jinja_env . add_extension ( PygmentsExtension ) |
然后让我们在模板中试一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | < head > < link rel = “stylesheet” href = “{{ url_for(‘static’, filename=’css/style.css’) }}” > < / head > < body > < p > A sample of JS code < / p > { % autoescape false % } { % code ‘javascript’ % } var name = ‘World’ ; function foo ( ) { console . log ( ‘Hello ‘ + name ) ; } { % endcode % } { % endautoescape % } < / body > |
运行下,页面上这段代码是不是有VIM的效果呀?这里我们引入了刚才创建在”static/css”目录下”style.css”样式文件,另外千万别忘了要将自动转义关掉,不然你会看到一堆的HTML标签。
另外提醒下大家,网上有文章说,对于单条语句,也就是不需要结束标志的语句,”parse()”函数里无需调用”nodes.CallBlock”,只需返回”return self.call_method(xxx)”即可。别相信他,看看源码就知道,这个方法返回的是一个”nodes.Expr”表达式对象,而”parse()”必须返回一个”nodes.Stmt”语句对象。
文章转载来自:trustauth.cn