0.0.1 • Published 6 years ago

template-generator-r v0.0.1

Weekly downloads
2
License
ISC
Repository
github
Last release
6 years ago

模板生成器是什么?

读取消息配置文件(XML或者JSON),以及不同语言的模板文件(文本),最终输出该语言的代码文件。我们可以简单地理解为:模板文件(语言相关部分) + 消息配置(语言无关部分) => 输出代码。

适用范围是什么?

适合于多端之间通讯协议的制定、WIKI文档的生成等。

目录结构

配置文件简单说明

config.json

以json形式保存当前语言特殊的配置项,包括类型对照表、模板作用域、黑白名单。

  • types:一个数组,用来配置当前语言所用到的各个数据类型的对照表,结构如下
    • from:数据类型转换前名称,如“double”,支持正则表达式匹配;
    • to:数据类型转换后名称,如“number”,支持正则表达式替换;
    • class:表示数据类型所属类别,目前可能的值有“basic”、“array”、“map”、“custom”。
  • templates:一个数组,用来说明各个模板文件的作用域
    • field:作用域范围,直接对应消息配置中的作用域,另外还有一个值“global”,表示作用域是全局的(即包含所有作用域);
    • file:表示所需的模板文件名;
    • saveName:表示生成的文件名称,可以使用变量以生成不同文件名,文件名的变量域和模板中的变量域相同。
  • include:白名单,可选,如果配置了则消息生成器只会生成白名单中声明的消息配置文件;
  • exclude:黑名单,可选,如果配置了则消息生成器会忽略黑名单中声明的消息配置文件;
  • 如果某个消息配置文件在黑名单和白名单都存在,则优先以白名单为准;
  • 举例:
{
  "types": [
    {
      "from": "byte",
      "to": "number",
      "class": "basic"
    },
    {
      "from": "short",
      "to": "number",
      "class": "basic"
    },
    {
      "from": "int",
      "to": "number",
      "class": "basic"
    },
    {
      "from": "long",
      "to": "string",
      "class": "basic"
    },
    {
      "from": "float",
      "to": "number",
      "class": "basic"
    },
    {
      "from": "double",
      "to": "number",
      "class": "basic"
    },
    {
      "from": "string",
      "to": "string",
      "class": "basic"
    },
    {
      "from": "boolean",
      "to": "boolean",
      "class": "basic"
    },
    {
      "from": "any",
      "to": "any",
      "class": "basic"
    },
    {
      "from": "(\\w+)\\[\\]",
      "to": "$1[]",
      "class": "array"
    },
    {
      "from": "{(\\w+):(\\w+)}",
      "to": "{[key:$1]:$2}",
      "class": "map"
    }
  ],
  "templates": [
    {
      "field": "global",
      "file": "MessageType.tpl",
      "saveName": "net/MessageType.ts"
    },
    {
      "field": "message",
      "file": "message.tpl",
      "saveName": "net/$a-{field}/$a-{name}Message.ts"
    },
    {
      "field": "response",
      "file": "type.tpl",
      "saveName": "net/$a-{field}/$a-{name}Response.ts"
    },
    {
      "field": "type",
      "file": "type.tpl",
      "saveName": "net/$a-{field}/$a-{name}.ts"
    }
  ],
  "include": [
	"abc.xml",
	"xyz.json"
  ],
  "exclude": [
	"extra.xml",
	"xyz.json"(这条会被忽略,因为白名单里也有)
  ]
}

模板文件

生成最终代码所使用的模板。

  • 语法:非命令语句可任意书写,命令语句被包在“$a-{}”结构的花括号内,例如“$a-{name}”表示最终代码中将用环境变量中的“name”字段的值替代这段语句,“$a-{for: i in 10}...$a-{end for}”表示将两个命令中间的“...”内容重复10次。详细语法规则请参考Ares项目的Template实现部分
  • 环境变量
    • 根据模板所绑定的作用域不同会有所区别,进而取到的变量也略有区别
      • global作用域:根节点为消息配置的最顶级,也就是config节点,为一个字典(哈希表),内包含所有自定义作用域。因为是根作用域,所以整个生成过程仅会调用一次该模板,此时如果想遍历所有message作用域下的消息配置则需要在模板中使用for命令;
      • 自定义作用域:根节点为消息配置中config节点下级的节点。因为根节点在某个作用域内部,因此无法获取到其他作用域下的配置。消息配置中每有一个具体的消息体都会调用一次该模板,例如根据如下配置,如果message作用域下有A、B、C三个消息配置,那么最终会根据message.tpl模板生成三个文件:
        • net/message/AMessage.ts
        • net/message/BMessage.ts
        • net/message/CMessage.ts
{
  "field": "message",
  "file": "message.tpl",
  "saveName": "net/$a-{field}/$a-{name}Message.ts"
}
  • 环境方法:为了书写模板的方便,提供几个工具性质的方法供调用
    • getConfigByName(field, name):通过作用域名和消息体名获取消息体的配置数据;
    • getCustomNames(fields):传入作用域数组,返回这些作用域中用到的自定义类型名称数组;
    • removeDuplicate(list):字符串数组去重;
    • transformType(type):传入数据类型名称(转换前),返回数据类型的定义。
  • 举例
/// <reference path="../../../../Libs/Freedom/vox/net/BasePBSocketCommand.ts"/>
$a-{for: msg in message}
/// <reference path="$a-{msg.field}/$a-{msg.name}Message.ts"/>
$a-{end for}
$a-{for: res in response}
$a-{if: res.protocol == "pbSocket"}
/// <reference path="$a-{res.field}/$a-{res.name}Response.ts"/>
$a-{end if}
$a-{end for}

/**
 * Created by TemplateGenerator.
 */
namespace net
{
	export class MessageType
	{
		/** 获取命令字典 */
		public static commandDict():{[name:string]:BaseNetCommandClass}
		{
			var dict:{[name:string]:BaseNetCommandClass} = {};
			$a-{for: msg in message}
			dict["$a-{msg.name}"] = net.message.$a-{msg.name}Command;
			$a-{end for}
			$a-{for: res in response}
			$a-{if: res.protocol == "pbSocket"}
			// 注册"$a-{res.comment}"返回对象
			vox.net.BasePBSocketCommand.registerResponse("$a-{res.name}", net.response.$a-{res.name}Response);
			$a-{end if}
			$a-{end for}
			return dict;
		}
		
		$a-{for: msg in message}
		/**
		 * $a-{msg.comment}
		 */
		public static $a-{msg.name}:string = "$a-{msg.name}";
		$a-{end for}
	}
	
    interface BaseNetCommandClass
    {
        new (...args):vox.net.BaseNetCommand;
    }
}

消息配置文件

用于描述消息结构的配置文件,和输出的语言无关。目前支持JSON和XML两种结构。

  • 基本结构说明
    • 根节点
      • 作用域节点
        • default节点(包含所有消息体都会继承的属性)
        • extra节点(一个列表,包含所有结构体不同的属性,可以覆盖继承的属性)
        • 算法:每有一个消息体配置,就生成一个default节点的副本,然后用extra节点覆盖该副本,从而生成一个完整的消息体配置。最终生成的作用域中并不包含default和extra这两个节点。
  • 属性支持简单的变量,变量作用域与该消息体所在作用域相同;
  • 属性支持赋值引用,运算符是“->”。如“response='response->$a-{name}'”表示response的值是response作用域下以变量“name”的值为key获取到的对象;
  • 自定义属性:消息体配置支持自定义属性,自定义的属性在模板中可以取到;
  • XML书写相对简洁,并且支持注释,因此推荐使用XML格式书写消息配置;
  • XML格式举例
<config>
    <message>
        <default method="GET" response="response->$a-{name}" comment="" domainIndex="0"/>
        <extra>
            <!-- 获取平台活动列表接口 -->
            <item name="FetchFairyActivities" comment="获取平台活动列表接口" url="/wonderland/activity/fetchappactivities.vpage"/>
            <!-- 获取平台活动弹窗接口 -->
            <item name="FairyPopup" comment="获取平台活动弹窗接口" url="/wonderland/activity/popups.vpage">
				<field name="activityId" type="string" comment="游戏标识"/>
			</item>
        </extra>
    </message>

    <response>
        <default comment=""/>
        <extra>
            <!-- 获取平台活动列表接口 -->
            <item name="FetchFairyActivities" comment="获取平台活动列表接口">
                <field name="activities" type="FairyActivityVO[]" comment="平台活动列表"/>
            </item>
            <!-- 获取平台活动弹窗接口 -->
            <item name="FairyPopup" comment="获取平台活动弹窗接口">
                <field name="popups" type="FairyPopupVO[]" comment="平台活动弹窗列表"/>
            </item>
        </extra>
    </response>

    <type>
        <default comment=""/>
        <extra>
            <!-- 平台活动列表项 -->
            <item name="FairyActivityVO" comment="平台活动列表项">
                <field name="base" type="FairyActivityDetailVO" comment="基础信息,如果extra不单独约定则使用该项内容"/>
                <field name="extra" type="{string:FairyActivityDetailVO}" comment="活动白名单,以appName作为key,值是FairyActivityDetailVO类型"/>
            </item>
            <!-- 平台活动列表项详细数据 -->
            <item name="FairyActivityDetailVO" comment="平台活动列表项详细数据">
                <field name="name" type="string" comment="活动名称"/>
                <field name="icon" type="string" comment="活动图标地址"/>
                <field name="link" type="string" comment="活动跳转URL"/>
            </item>
            <!-- 平台活动弹窗数据 -->
            <item name="FairyPopupVO" comment="平台活动弹窗数据">
                <field name="id" type="int" comment="弹窗唯一标识"/>
                <field name="activityId" type="string" comment="活动ID"/>
                <field name="description" type="string" comment="描述"/>
                <field name="url" type="string" comment="图片地址"/>
                <field name="content" type="string" comment="文本内容"/>
                <field name="type" type="int" comment="弹窗类型  0 图片 1 文本"/>
                <field name="cycle" type="int" comment="0-一辈子一次	1-一天一次	2-每次登陆 一次"/>
                <field name="rank" type="int" comment="排序"/>
                <field name="startDatetime" type="int" comment="活动开始时间戳"/>
                <field name="endDatetime" type="int" comment="活动结束时间戳"/>
                <field name="extension" type="string" comment="json字符串,title:标题、determine_des:按钮文本、jump:跳转地址"/>
            </item>
        </extra>
    </type>
</config>
  • JSON格式举例
{
	"message": {
		"default": {
			"pkg": "net.message",
			"fieldPkg": "net.type",
			"comment": "",
			"method": "GET",
			"response": "response->$a-{name}",
			"domainIndex": 0
		},
		"extra": [
			{
				"name": "GetVideoList",
				"comment": "视频课程列表",
				"url": "/afenti/api/video/list.vpage",
				"method": "POST"
			}
		]
	},
	"response": {
		"default": {
			"comment": "",
			"pkg": "net.response",
			"fieldPkg": "net.type"
		},
		"extra": [
			{
				"name": "GetVideoList",
				"comment": "视频课程列表返回",
				"fields": [
					{
						"name": "hasOpened",
						"type": "boolean",
						"comment": "是否开通了服务"
					},
					{
						"name": "catalog",
						"type": "VideoListInfo[]",
						"comment": "视频目录"
					}
				]
			}
		]
	},
	"type": {
		"default": {
			"comment": "",
			"pkg": "net.type",
			"fieldPkg": "net.type"
		},
		"extra": [
			{
				"name": "VideoListInfo",
				"comment": "视频目录",
				"fields": [
					{
						"name": "id",
						"type": "string",
						"comment": "目录ID"
					},
					{
						"name": "name",
						"type": "string",
						"comment": "目录题目"
					},
					{
						"name": "lessons",
						"type": "VideoInfo[]",
						"comment": "课时详情列表"
					}
				]
			},
			{
				"name": "VideoInfo",
				"comment": "视频课程目录",
				"fields": [
					{
						"name": "id",
						"type": "string",
						"comment": "课时ID"
					},
					{
						"name": "name",
						"type": "string",
						"comment": "课时名称"
					},
					{
						"name": "imgUrl",
						"type": "string",
						"comment": "课时缩略图"
					},
					{
						"name": "isFree",
						"type": "boolean",
						"comment": "是否是免费视频"
					}
				]
			}
		]
	}
}