模板编辑器
模板编辑器
一直想着搞个代码生成器的工具,记得之前有写过这方面的文章,但是找不到了。现在重新记录一下。
另外,php是天然的模板引擎,用他来生成代码,非常的好用。要这样想,html,不就是用php动态来生产的吗?
思路
1、基于config,加载config,然后读模板、并渲染模板。为了简单,前期的时候,config可由手动填写,后期的话,基本表单,从web视图上动态的渲染模板。
2、关于渲染php的模板。
由于生成的代码也是php,这样就形成了渲染、最终代码两者之间,有冲突。由于渲染php模板,难面会和目标代码冲突,整体思路是,目标的php tag标记,先用其他的表示符号替换,这样,目标代码就不会执行。
在这里,不妨分为渲染阶段、跟目标代码执行阶段。
示例代码:
我见到过为了避免渲染文件,直接省略的 <?php的标记,这样确实避免执行了目标php代码,而整体代码比较简单。
具体,如html文件中,包含了php代码。如:
<?php echo $var; ?>
为了输出上面的标记,则:
<?php echo '<?php' ?>
总而言之,希望渲染阶段执行的代码,与目标代码之间不发生冲突。所以,凡是目标代码的标记,进行替换。替换规则如下:
<?php => <#PHP
?> => PHP#>
针对与简短标记
<? => <#%
?> => #%>
上面的替换,一般来说,不是很常见,理论上不会发生冲突。
由于替换逻辑,并不会解析语义,所以,算是暴力处理,所以要求不会冲突。
但是实际上发现,只需要替换成左半边的标记即可,就能让php代码不执行。降低难度。
通过只替换坐半边,渲染代码最好简短,否则,渲染期的代码跟目标代码易混合在一起。
3、关于模板的编写
不要一上来,就试图想着搞个多么高大上的引擎,我们先试图这样想,先能针对一般的模板,进行简单的替换。
后期的话,如果有必要,再针对复杂的地方,也换成模板渲染。
4、模式
config=>模板(简单变量替换)=>目标代码。
web界面=> config => 模板 => 目标代码
技术细节
方式
用配置 + 模板,生成代码。而配置本身,也可以用模板生成,然后自己手动修改。
循环
如下的循环,输出后,foreach标记的两行,其实没有任何输出的。而且注意副作用,它会消耗掉一个紧跟着的换行符号。理解起来,我们的代码片段,如果没有任何输出,不管它用几行,应该也不会产生输出。
type: number
<?php foreach(cfg('fields') as $k=>$v ): ?>
- in: query
name: <?=$k?><?=PHP_EOL?>
description: <?= is_array($v)?$v[0]:$v?><?=PHP_EOL?>
required: false
type: <?= is_array($v)?$v[1]:'string'?><?=PHP_EOL?>
<?php endforeach; ?>
- in: query
文末换行
正是由于前面的副作用,所以,我们要消除。所以呢,我们手动在php标记结尾的地方,增加换行符号。
description: 通过条件搜索,查询<?=cfg('cn_name')?><?=PHP_EOL?>
php灭活处理
如果要处理的模板本身就是php,那我们需要灭活。每次出现php标记的地方,都使用下面的方式来处理。
短标记也是一样。
省事的话,只处理开头的那个标记,即可。
<?='<?php'?>
namespace api\controllers\v1;
use api\controllers\BaseController;
use Yii;
use common\models\<?=cfg('model_name')?>;
use common\models\<?=cfg('model_name')?>Search;
比如对上面的短标记进行灭活处理
use common\models\<?='<?'?>=cfg('model_name')?>;
如果觉得冲突不好辨别,则自己定义变量,如 $ptag='<?'
方法
核心代码,就最后几行。
<?php
$cfg_file = $argv[1];
define('APP_ROOT',dirname(__FILE__));
function cfg($key=false,$default=''){
global $cfg_file;
static $config = [];
if($key===true){
return $config = require APP_ROOT."/cfg/$cfg_file";
}else if($key===false){
return $config;
}
return map_value($config,$key,$default);
}
/**
* 返回关联数组的索引。
* 如: \map_value($data,'user.age'); 返回 $data['user']['age']
*/
function map_value($map,$key,$default=''){
$keys = explode('.',$key);
foreach($keys as $curkey){
if(!isset($map[$curkey]) ){
return $default; //返回默认值
}
$map = $map[$curkey];
}
return $map;
}
cfg(true);
$table = cfg('table_name');
\ob_start();
include APP_ROOT.'/tpl/swagger.txt';
$content = \ob_get_clean();
file_put_contents($table.'_swagger.yaml',$content);
防止文件覆盖,对另外一种灭火处理
/**********************
* 生成控制器
* 2021-06-15 9:48
**********************/
function gen_controller(){
$model_name = cfg('model_name');
$fn = APP_ROOT.'/../cloud/api/controllers/v1/'.$model_name.'Controller.php';
if(!is_file($fn)){
\ob_start();
include APP_ROOT.'/tpl/controller.php';
$content = ob_get_clean();
$content = strtr($content,[
'<#PHP'=>'<?php',
'<#%'=>'<?',
]);
\file_put_contents($fn,$content);
echo "控制器文件:{$fn} 生成成功。\n";
}else{
echo "控制器文件:{$fn} 已存在,无法生成,请先删除,再生成。\n";
}
}
其他技术方案
其他语言,也会有相应的模板引擎。故,选择任意一个带引擎的,都可以实现用模板生成代码。
另外一种,简便方式的模板引擎。shell。
shell模板
仅考虑,替换模板中的变量,但不支持if、
方式1
直接cat,里面的变量会自动的注入。
方式2
使用sed来替换。
1、使用特殊的标记,比如{} ,或者直接唯一标记。
2、直接将里面的具体值,替换掉。这种呢,需要自己清楚的知道里面的值全部替换没有问题。
简单的判断,也能拥下面的方式来处理。
# 生成测试文本 seq 1 20 > test.txt # 然后手动的插入 --#< --#> 标记 # 表示上面的给删除掉。 # 用法,成对出现的,都能正常删除掉,如果少1个,则可能删除到结尾。 sed '/^--#</,/^--#>/d' test.txt