功能介绍
mkTpl是一个基于Python编写的模板引擎包. 初始版本发布于2017年, 当时开发该模板引擎是为了在嵌入式仪表开发过程中梳理软件配置, 以便可以生成配置代码, 避免手动整理配置参数, 极大提升开发工作效率. 后续开发过程中有几次不同程度的优化升级, 引入了用户字定义代码与函数功能, 拓展了模板引擎的应用范围, 目前mkTpl包已在多个项目中引用, 对开发效率有显著提升.
环境搭建
- 安装python version > 3.0以上版本
- 安装mkTpl软件包
- 安装需要使用的软件包(xlrd/PyYAML/xml等, 未使用可不安装)
代码实例
import mkTpl
ctx = {'测试1': 12345, '测试2': 67890}
template = {'test.tpl.c': ['./tpl', './gen', 'test.gen.c']}
# 实例化模板引擎
tplobj = mkTpl.mktpl()
# 加入数据库
tplobj.addlib('ctx', ctx)
# 导入模板, 生成代码
tplobj.setTemplate(template)
模板语法
在代码实例中字典template有一个值: test.tpl.c, 这里的test.tpl.c就是需要生成的代码模板, 只有导入了模板, 代码引擎才能根据模板和数据库导出用户需要的代码. 模板需要提供所在位置与生成代码存放位置以及生成代码文件名, 模板引擎实际不会关注生成的文件格式, 这里只依赖用户定义, 理论上可以生成任意用户定义的文本文件.
针对模板的格式有一定要求, 只有按照要求定义的模板, 模板引擎才能解析. 实际代码生成的过程可以理解成对文件的读写过程, 单独对文件读写大家应该都接触过, 如下实例:
# 以a+属性打开一个文件 text.c
file = open('text.c', a+)
# 读取文本内容
lines = file.readlines()
# 写入hello world!
file.write('hello world!')
file.close()
而模板引擎的作用就是解析模板, 生成文件读写的代码, 再运行生成的代码即可获得想要的最终目标代码. 这里很有意思, 代码并不是事先写好的, 而是模板引擎写的. Python是一门解释性的语言, 刚好可以完成上述的功能.
- 模板实例: test.tpl.c
// 1. 静态代码
void VfcMgr_vMain(void) {
// user code.
}
// 2. 可迭代模式
<%test_sb = ['test0', 'test1']%>
<%for i in range(0, len(test_sb)):%>
<% User Coding Symbol: @test_sb[i]%>
// 3. 单例模式
<%User Coding Symbol: test3%>
// 4. 参数导入模式
<%for key in ctx:%>
<% ~test coding: ${key}, ${ctx[key]}%>
- 生成代码: test.gen.c
// 1. 静态代码
void VfcMgr_vMain(void) {
// user code.
}
// 2. 可迭代模式
/**********************************************************************************************************************
* DO NOT CHANGE THIS COMMENT! << Start of user code implementation >> DO NOT CHANGE THIS COMMENT!
* Symbol: test0
*********************************************************************************************************************/
随意输入
/**********************************************************************************************************************
* DO NOT CHANGE THIS COMMENT! << End of user code implementation >> DO NOT CHANGE THIS COMMENT!
*********************************************************************************************************************/
/**********************************************************************************************************************
* DO NOT CHANGE THIS COMMENT! << Start of user code implementation >> DO NOT CHANGE THIS COMMENT!
* Symbol: test1
*********************************************************************************************************************/
随意输入
/**********************************************************************************************************************
* DO NOT CHANGE THIS COMMENT! << End of user code implementation >> DO NOT CHANGE THIS COMMENT!
*********************************************************************************************************************/
// 3. 单例模式
/**********************************************************************************************************************
* DO NOT CHANGE THIS COMMENT! << Start of user code implementation >> DO NOT CHANGE THIS COMMENT!
* Symbol: test3
*********************************************************************************************************************/
随意输入
/**********************************************************************************************************************
* DO NOT CHANGE THIS COMMENT! << End of user code implementation >> DO NOT CHANGE THIS COMMENT!
*********************************************************************************************************************/
// 4. 参数导入模式
test coding: 测试1, 12345
test coding: 测试2, 67890
函数
在<%%>
中定义函数, 以def
起始的代码会默认识别为函数定义, 函数直到出现一行静态代码结束. 在定义函数时, 函数存在一个默认参数info
, 在不使用输出文件功能时可不定义, 若定义参数info
使用的时候需要加上参数info
, 其他用户参数可自行定义, 实例如下:
函数定义:
<%def test_func(info):%>
<% print("hello world.")%>
<% ~write in output file: hello world.%>
函数使用:
<%test_func(info)%>
特殊标识 <%%>
模板语法动态模板代码完全支持python语法, 但动态代码需要使用<%%>
标记, 否则默认为静态代码.
example:
<%for key in ctx:%>
<% print('ctx key: %s'% key)%>
特殊标识: ~ (info.write)
example:
<%for key in ctx:%>
<% ~test coding: ${key}, ${ctx[key]}%>
or
<%for key in ctx:%>
<% info.write("test coding: %s, %s\n"%(key, ctx[key]))%>
实例中给出了两段代码, 实际结果是一样的, ~
与 info.write
拥有相同功能, ~
是优化后的表现, 使模板编辑更方便高效.
关键字: User Coding Symbol
User Coding Symbol
字段设计的时候参考了DaVinci
生成代码的功能, 生成代码的时候可以识别User Coding Symbol
字段, 在该字段注释内部用户可以编写自定义代码, 模板引擎会主动merge用户代码与生成的代码, 该功能的引入, 有效的扩大了mkTpl
的应用范围, 有了该功能后, 再不局限于设计之初的想法, 不但能应用于配置代码的生成, 也可应用于大量重复逻辑代码的编写中. 实际开发过程中往往会遇到结构类似, 但是细节上又存在一些不确定性的差异, 原来的模板引擎功能很难实现这种代码的整理, 引入User Coding Symbol
后, 使得该场景下的模板实现成为可能.
在使用User Coding Symbol
的时候需要注意, 实际有两种方式使用:
- 单例模式
<%User Coding Symbol: name%>
适用于单个User Coding Symbol
- 迭代模式
<%User Coding Symbol: @nameVar%>
适用于多个User Coding Symbol
特殊标识: ${}
${}
最常用, 变量的替换需要使用${}
标记, 变量放入大括号内, 变量之前可以带数字, 用于定义写入字符长度与对其功能(正负分别表示不同方向的对其).
<% ~test coding: ${10key}, ${-6ctx[key]}%>
使用实例
在仪表项目中, Warning
与VfcMgr
模块中应用mkTpl
做了部分代码生成工作, 以VfcMgr
为例, 应用过程如下:
在VFC
功能开发中, 有众多的Activator
, 每个都包含大量的信号逻辑判断功能, 为了解决开发者在这样巨量的逻辑中迷失, 引入yaml
配置逻辑功能, 所有相关的信号逻辑表达式全部在yaml
文件上统一配置. 再配合部分静态代码, 实现逻辑功能全配置, 不用用户单独编写额外代码.
有关LC -> VFC -> PNC之间的关系通过excel配置, 再由mkTpl加上模板一并导出关系配置表.
yaml配置实例:
ObjMntr:
PinToDrvForHmiCen:
# VFC Activation Criteria
# ( {PinToDrvCod}ChangeFromAnyToX (X=CodVld.Inact_Active) )
# OR( {PinToDrvCodHmiReq}ChangeToOther ) )
# AND{VehModMngtGlbSafe1}NotEqualToX(X=UsgModSts.UsgModSts1_UsgModAbdnd) )
# VFC Deactivation Criteria
# ( VFCTimeOutDelay [sec] = 3 )
Delay: 3000
Tx:
PinToDrvCodCodVld:
Type: uint8
Condition: [ Prev.PinToDrvCodCodVld != Curr.PinToDrvCodCodVld, Curr.PinToDrvCodCodVld == Inact_Active ]
PinToDrvCodHmiReq:
Type: PinToDrvCodHmiReq
Condition: [ Prev.PinToDrvCodHmiReq != Curr.PinToDrvCodHmiReq ]
LogicalExpression:
ActyLE0: CC_PinToDrvCodCodVld_Condition_0 && CC_PinToDrvCodCodVld_Condition_1
ActyLE1: CC_PinToDrvCodHmiReq_Condition_0
ActRule: LE_ActyLE0 || LE_ActyLE1
yaml软件模板:
/**
* Tx signals interface.
*/
<%sigsTab = []%>
<%for lc in mrkChgMap['ObjMntr']:%>
<% if 'Tx' in mrkChgMap['ObjMntr'][lc]:%>
<% for sg in mrkChgMap['ObjMntr'][lc]['Tx']:%>
<% a = False%>
<% if sg not in sigsTab:%>
<% sigsTab.append(sg)%>
<% if sg in sglib['sg']['tx']:%>
<% a = True%>
<% ~#define VfcMgr__Read_Write_${sg}() (SigSer_${sg})%>
<% for gp in sglib['gp']['tx']:%>
<% if sg in sglib['gp']['tx'][gp]:%>
<% a = True%>
<% if sglib['gp']['tx'][gp][sg]['index'] != -1:%>
<% ~#define VfcMgr__Read_Write_${sg}() (SigSer_${gp}[${sglib['gp']['tx'][gp][sg]['index']}])%>
<% else:%>
<% ~#define VfcMgr__Read_Write_${sg}() (SigSer_${gp}.${sg})%>
<% else:%>
<% a = True%>
<% if a == False:%>
<% print("Sg: %s is error."%sg)%>
...
yaml生成代码:
#define VfcMgr__nTimeoutDly_PinToDrvForHmiCen ( (uint16)(3000ul / VfcMgr__nMainCycleCntr) )
#define VfcMgr__Read_Write_PinToDrvCodCodVld() (SigSer_PinToDrvCod.PinToDrvCodCodVld)
#define VfcMgr__Read_Write_PinToDrvCodHmiReq() (SigSer_PinToDrvCodHmiReq)
#define VfcMgr__boPinToDrvForHmiCen_PinToDrvCodCodVld_Condition_0 ( VfcMgr__PinToDrvCodCodVld != VfcMgr__Read_Write_PinToDrvCodCodVld() )
#define VfcMgr__boPinToDrvForHmiCen_PinToDrvCodCodVld_Condition_1 ( VfcMgr__Read_Write_PinToDrvCodCodVld() == Inact_Active )
#define VfcMgr__boPinToDrvForHmiCen_PinToDrvCodHmiReq_Condition_0 ( VfcMgr__PinToDrvCodHmiReq != VfcMgr__Read_Write_PinToDrvCodHmiReq() )
#define VfcMgr__boPinToDrvForHmiCen_ActyLE0 ( VfcMgr__boPinToDrvForHmiCen_PinToDrvCodCodVld_Condition_0 && VfcMgr__boPinToDrvForHmiCen_PinToDrvCodCodVld_Condition_1 )
#define VfcMgr__boPinToDrvForHmiCen_ActyLE1 ( VfcMgr__boPinToDrvForHmiCen_PinToDrvCodHmiReq_Condition_0 )
#define VfcMgr__boActRule_PinToDrvForHmiCen ( VfcMgr__boPinToDrvForHmiCen_ActyLE0 || VfcMgr__boPinToDrvForHmiCen_ActyLE1 )
excel配置:
excel软件模板:
<%~const VfcMgr__tstLCsAttrs VfcMgr__rastLCsAttrs_Tab[VfcMgr_nenNrOfLCs] = {%>
<%for lc in VFCInfo:%>
<% u8VFCIndex = str(VFC2PNC[VFCInfo[lc]['VFCName']]['VFCID'] + 100)[1:]%>
<% u8PNCIndex = str(VFC2PNC[VFCInfo[lc]['VFCName']]['PNC'] + 100)[1:]%>
<% na = lc.replace('-', '')%>
<% na = na.replace(' ', '')%>
<% u8UsageMode = VFCInfo[lc]['Usagemode']%>
<% u8CarMode = VFCInfo[lc]['CarMode']%>
<% pfLCActyFnct = 'VfcMgr__v'+na+'Detect'%>
<% ~ {VfcMgr_nVFC${u8VFCIndex}, VfcMgr_nPNC${u8PNCIndex}, ${3u8UsageMode}, ${3u8CarMode}, ${-52pfLCActyFnct}}, /*!> ${lc} */%>
};
excel生成代码:
const VfcMgr__tstLCsAttrs VfcMgr__rastLCsAttrs_Tab[VfcMgr_nenNrOfLCs] = {
{VfcMgr_nVFC41, VfcMgr_nPNC16, 7, 31, VfcMgr__vV2XCtrlForHmiCenDetect }, /*!> V2XCtrlForHmiCen */
{VfcMgr_nVFC41, VfcMgr_nPNC16, 15, 31, VfcMgr__vDrvrHmiMgrPlatform1ForDrvrHmiDetect }, /*!> DrvrHmiMgrPlatform1ForDrvrHmi */
{VfcMgr_nVFC41, VfcMgr_nPNC16, 15, 31, VfcMgr__vDrvrHmiMgrPlatform2ForDrvrHmiDetect }, /*!> DrvrHmiMgrPlatform2ForDrvrHmi */
{VfcMgr_nVFC41, VfcMgr_nPNC16, 15, 31, VfcMgr__vInfotainmentModMgr_InfotainmentPushDetect }, /*!> InfotainmentModMgr_InfotainmentPush */
{VfcMgr_nVFC41, VfcMgr_nPNC16, 7, 9, VfcMgr__vVehSurrndgsVisnCtrlDetect }, /*!> VehSurrndgsVisnCtrl */
{VfcMgr_nVFC41, VfcMgr_nPNC16, 15, 9, VfcMgr__vVehSurrndgsVisnAddlDetect }, /*!> VehSurrndgsVisnAddl */
{VfcMgr_nVFC41, VfcMgr_nPNC16, 15, 31, VfcMgr__vFaceIdnMgrDetect }, /*!> FaceIdnMgr */
{VfcMgr_nVFC16, VfcMgr_nPNC17, 15, 31, VfcMgr__vLeReCenLockUnlockBtnDetect }, /*!> LeReCenLockUnlockBtn */
{VfcMgr_nVFC16, VfcMgr_nPNC17, 31, 31, VfcMgr__vRiReCenLockUnlockBtnDetect }, /*!> RiReCenLockUnlockBtn */
{VfcMgr_nVFC10, VfcMgr_nPNC18, 7, 31, VfcMgr__vExteriorLightShow_ExteriorLightingDetect }, /*!> ExteriorLightShow_ExteriorLighting */
{VfcMgr_nVFC10, VfcMgr_nPNC18, 15, 31, VfcMgr__vWelcomeLightDIYDetect }, /*!> WelcomeLightDIY */
{VfcMgr_nVFC21, VfcMgr_nPNC19, 31, 31, VfcMgr__vLePwrSldgDoorBtnDetect }, /*!> LePwrSldgDoorBtn */
{VfcMgr_nVFC21, VfcMgr_nPNC19, 31, 31, VfcMgr__vRiPwrSldgDoorBtnDetect }, /*!> RiPwrSldgDoorBtn */
{VfcMgr_nVFC21, VfcMgr_nPNC19, 15, 31, VfcMgr__vLockgCtrlForHmiCen_PowerClosuresDetect }, /*!> LockgCtrlForHmiCen_PowerClosures */
{VfcMgr_nVFC21, VfcMgr_nPNC19, 15, 31, VfcMgr__vPwrDoorCtrlForHmiCen_PowerClosuresDetect }, /*!> PwrDoorCtrlForHmiCen_PowerClosures */
{VfcMgr_nVFC21, VfcMgr_nPNC19, 15, 31, VfcMgr__vActvReSplrForHmiCenDetect }, /*!> ActvReSplrForHmiCen */
};