模板编辑器

模板编辑器

一直想着搞个代码生成器的工具,记得之前有写过这方面的文章,但是找不到了。现在重新记录一下。

另外,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