gen_code.py 11.4 KB
Newer Older
wanli's avatar
wanli committed
1 2
from pprint import pprint
from pathlib import Path
wanli's avatar
wanli committed
3
from difflib import Differ
4
import fs
wanli's avatar
wanli committed
5
import json
6 7
import os
import re
wanli's avatar
wanli committed
8 9
import hashlib
import time
10
import shutil
wanli's avatar
wanli committed
11
from jinja2 import Environment, FileSystemLoader
12
from resources.webcreator.log import logger
wanli's avatar
wanli committed
13

wanli's avatar
wanli committed
14 15 16 17 18 19
'''
流程:
一、将resources里面所有文件夹拷贝到当前目录下
二、解析json配置文件,遍历每一项生成/model/view/controller
三、自动运行项目
'''
wanli's avatar
wanli committed
20

wanli's avatar
wanli committed
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
# 比较两个文件内容是否相同,这里没有使用md5内容摘要算法,MD5算法的缺点是,针对大文件,计算耗时。虽然咱们这里不存在大文件,但是条条大路通罗马嘛,试试其他方法也挺好。
def cmp_file(file1, file2):
    f1 = os.stat(file1)
    f2 = os.stat(file2)

    if f1.st_size != f2.st_size:
        return False

    buf_size = 8 * 1024
    with open(file1, "rb") as fp1, open(file2, "rb") as fp2:
        while True:
            buf1 = fp1.read(buf_size)
            buf2 = fp2.read(buf_size)

            if buf1 != buf2:
                return False

            # 这里表示文件读完了
            if not buf1 or not buf2:
                return True


wanli's avatar
wanli committed
43 44 45 46 47 48 49 50 51 52
def compare_file(file1, file2):
    with open(file1) as f1,open(file2) as f2:
        content1 = f1.read().splitlines(keepends=True)
        content2 = f2.read().splitlines(keepends=True)

    d = Differ()
    result = d.compare(content1, content2)

    return list(result)

wanli's avatar
wanli committed
53 54 55 56 57 58 59 60 61
def cmp_md5(contnet1, content2):
    m1 = hashlib.md5()
    m1.update(bytearray(contnet1, 'utf-8'))

    m2 = hashlib.md5()
    m2.update(bytearray(content2, 'utf-8'))

    return m1.hexdigest() == m2.hexdigest()

62 63 64 65 66 67
# 将字符串首字母转换成大写字母
def convertFirstLetterUpper(text_str):
    # return text_str.capitalize()
    # print("////////////////////>>>>", text_str)
    return re.sub("([a-zA-Z])", lambda x: x.groups()[0].upper(), text_str, 1)

68 69 70 71 72 73 74 75 76 77 78 79 80
def convertDataType(t):
    if t == "Integer":
        return "int"
    elif t == "Float":
        return "float"
    elif t == "String":
        return "str"
    elif t == "Boolean":
        return "bool"

def getVariableString(var):
    return repr(var)

81 82 83
# ROOT = os.path.abspath(os.getcwd())
jinja_env = Environment(loader=FileSystemLoader(os.path.join(os.getcwd(), 'templates')))
jinja_env.filters['letterUpper'] = convertFirstLetterUpper
84 85 86
jinja_env.filters['getDataType'] = convertDataType
jinja_env.filters['getVariableString'] = getVariableString

87 88 89
input_dir = None
output_dir = None

wanli's avatar
wanli committed
90 91
events = []

92
def copyFiles(src_dir, dst_dir):
wanli's avatar
wanli committed
93
    if not os.path.exists(src_dir):
94
        logger.error("%s 目录不存在" % src_dir)
wanli's avatar
wanli committed
95 96 97 98 99 100 101 102 103 104
        return None

    # 复制文件之前需要判断文件是否存在
    if not os.path.exists(dst_dir):
        os.makedirs(dst_dir)

    # root 所指的是当前正在遍历的这个文件夹的本身的地址
    # dirs 是一个 list,内容是该文件夹中所有的目录的名字(不包括子目录)
    # files 同样是 list, 内容是该文件夹中所有的文件(不包括子目录)
    for root, dirs, files in os.walk(src_dir):
105 106
        save_path = Path(dst_dir)

wanli's avatar
wanli committed
107 108 109 110
        if os.path.basename(root) == "__pycache__":
            continue

        for file in files:
111 112 113 114 115 116 117
            name, suffix = os.path.splitext(file)
            # 判断目标文件夹是否存在同名文件
            # 如果是python文件,文件重命名
            # 其他文件,直接删除
            relative_path = Path(root).joinpath(file).resolve().relative_to(Path(src_dir))
            target_file = Path(dst_dir).joinpath(relative_path).resolve()
            if cmp_file(Path(root).joinpath(file).resolve().as_posix(), target_file.resolve().as_posix()):
wanli's avatar
wanli committed
118
                continue
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
            if target_file.exists() and target_file.suffix in [".py"]:
                new_file = "{}.{}{}".format(name, time.strftime("%Y%m%d%H%M%S", time.localtime()), suffix)
                tmp_file = target_file.parent.joinpath(new_file)
                if tmp_file.exists():
                    tmp_file.unlink()
                target_file.rename(tmp_file)
                # os.rename(os.sep.join([root, file]), os.sep.join([root, new_file]))
            else:
                logger.info("delete file")
                logger.info(target_file)
                # os.unlink(os.sep.join([root, file]))

            src_file = Path(root).joinpath(file)
            relative_path = src_file.resolve().relative_to(Path(src_dir))
            save_path = Path(dst_dir).joinpath(relative_path)
            if save_path.is_file() and not save_path.parent.exists():
                save_path.parent.mkdir()
wanli's avatar
wanli committed
136

137 138
            # if not os.path.exists(save_path):
            #     os.makedirs(save_path)
wanli's avatar
wanli committed
139

140
            # 目标文件是否存在?
141 142 143
            shutil.copyfile(src_file, save_path)

    logger.info('copy files finished!')
144

wanli's avatar
wanli committed
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
def handleModuleConfig(config):
    # 处理每一项配置文件
    # 入口文件名
    # 实例对象,根据这个实例连接对应请求
    # 实例方法,需要根据实例方法生成事件函数
    '''
    '''
    pass

def handleModules(config):
    global output_dir
    # 遍历modules结构,判断文件是否存在,并且enable已经打开
    # 如果文件不存在,则直接报错
    # 本质上这是一个文件拷贝的操作
    for mod in config:
        p = Path(mod.get("config"))
        if not p.exists() or not mod.get("enable"):
            continue
        shutil.copyfile(mod.get("config"),  os.sep.join([output_dir, "controllers", p.name]))
        for d in mod.get("dependencies"):
            if Path(d).exists():
                shutil.copyfile(d, os.sep.join([output_dir, "controllers", Path(d).name]))
            else:
168
                logger.error("文件:%s 不存在")
wanli's avatar
wanli committed
169

170 171 172 173 174 175 176 177 178
def handleResources(config):
    # 处理路由页面
    # 遍历config文件,收集所有的action和name,action和name的首字母必须大写
    # 然后才生成路由配置页面
    target_file = os.sep.join(["views", "__init__.py"])
    handleRender(config, 'router.tpl', target_file)

def handleSignal(config):
    # 生成信号槽模块
wanli's avatar
wanli committed
179 180 181
    if config.get("framework").get("signal").get("regenerate"):
        target_file = os.sep.join(["application", "signal_manager.py"])
        handleRender(config.get("apis"), 'signal_manager.tpl', target_file)
182

wanli's avatar
wanli committed
183 184 185
    if config.get("framework").get("controllerInit").get("regenerate"):
        target_file = os.sep.join(["controllers", "__init__.py"])
        handleRender(config.get("apis"), 'signal_manager_init.tpl', target_file)
wanli's avatar
wanli committed
186

wanli's avatar
wanli committed
187
def handleModel(config, application):
wanli's avatar
wanli committed
188 189 190 191
    # 判断是否有model字段,没有直接退出
    if not config.get("model"):
        return None

wanli's avatar
wanli committed
192 193
    # 将所有有默认值的字段分为一组,没有默认值的字段分为另一组
    # 生成模板代码时,无默认值的字段在前,有默认值的字段字在后
wanli's avatar
wanli committed
194 195 196 197 198 199 200
    # 收集表字段信息
    fields = []
    extend = False
    for m in config.get("model").get("fields"):
        fields.append(m.get("name"))
        extend = True
        print(m)
wanli's avatar
wanli committed
201

wanli's avatar
wanli committed
202 203
    target_file = os.sep.join(["models", "{}.py".format(config.get("name"))])
    handleRender(config, 'model.tpl', target_file, fields=fields, extend=extend, application=application)
wanli's avatar
wanli committed
204 205 206 207 208
    # 多次复制时,会报文件已存在错误,因此要先删除
    target_file = os.sep.join(["models", "base.py"])
    if os.path.exists(target_file):
        os.remove(target_file)
    handleRender(config, 'base.tpl', target_file, application=application)
wanli's avatar
wanli committed
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228

def handleView(config):
    target_file = os.sep.join(["views", "{}.py".format(config.get("name"))])
    handleRender(config, 'view.tpl', target_file)

def handleController(config):
    # 根据模型字段自动从models列表中取出file name信息和class name信息
    target_file = os.sep.join(["controllers", "{}.py".format(config.get("name"))])
    handleRender(config, 'controller.tpl', target_file)

def handleRender(result, tpl, target_file, **kwargs):
    global output_dir
    # print("=========>", result.get("name"), "{}.py".format(result.get("name")))
    jinja_tpl = jinja_env.get_template(tpl)
    content = jinja_tpl.render({ "config": result, **kwargs })
    # print("############", output_dir)
    target_file = os.sep.join([output_dir, target_file])
    if not os.path.exists(os.path.dirname(target_file)):
        os.makedirs(os.path.dirname(target_file))
    
wanli's avatar
wanli committed
229 230 231 232 233 234 235 236 237 238 239
    # 这里需要比较目标文件夹是否已经存在同名文件,如果存在,则需要比较两个文件内容是否一致
    # 如果不一致时,则需要给旧的文件打上时间戳,作为备份文件

    # 具体流程:
    # 先将原来文件重命名,然后新的内容写入到重命名的文件中
    # 最后比较两个文件内容是否一致,如果一致,则删除重命名的那个旧的备份文件

    tmp_file = ""
    if os.path.exists(target_file) and os.stat(target_file).st_size > 0:
        n, e = os.path.splitext(target_file)
        tmp_file = "{}.{}{}".format(n, time.strftime("%Y%m%d%H%M%S", time.localtime()), e)
wanli's avatar
wanli committed
240 241
        if os.path.exists(tmp_file):
            os.remove(tmp_file)
wanli's avatar
wanli committed
242 243
        os.rename(target_file, tmp_file)

wanli's avatar
wanli committed
244 245 246
    with open(target_file, 'w', encoding='utf-8') as f:
        f.write(content)

wanli's avatar
wanli committed
247
    if len(tmp_file) > 0 and cmp_file(tmp_file, target_file) == True:
wanli's avatar
wanli committed
248 249
        os.remove(tmp_file)

wanli's avatar
wanli committed
250 251 252
def parseConfig(config):
    # 解析配置文件
    for cfg in config.get("apis"):
wanli's avatar
wanli committed
253 254
        if not cfg.get("enable"):
            continue
wanli's avatar
wanli committed
255 256 257
        handleModel(cfg, config.get("application"))
        handleView(cfg)
        handleController(cfg)
wanli's avatar
wanli committed
258
    # 全局配置
wanli's avatar
wanli committed
259
    handleResources(config.get("apis"))
wanli's avatar
wanli committed
260
    handleSignal(config)
wanli's avatar
wanli committed
261
    handleModules(config.get("modules"))
wanli's avatar
wanli committed
262 263 264 265 266 267 268

def readConfig():
    result = {}
    with open("config.json", "r+") as f:
        result = json.loads(f.read())
    return result

269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
# 备份数据库,判断目标文件夹下是否有.db文件,有的话,先备份到临时目录,待文件复制完成后,再放回原来位置
def backup_database():
    global output_dir
    target_dir = os.sep.join([os.path.dirname(output_dir), "tmp"])
    if not os.path.exists(target_dir):
        os.makedirs(target_dir)
    home_fs = fs.open_fs(output_dir)
    for file in home_fs.glob('*.db', namespaces=['details']):
        # print(file.path, os.path.normpath(os.sep.join([output_dir, file.path])))
        # copyFiles(os.path.normpath(os.sep.join([output_dir, file.path])), target_dir)
        shutil.copy(os.path.normpath(os.sep.join([output_dir, file.path])), target_dir)

# 恢复数据库,代码生成完成后,需要把之前复制的数据库文件,恢复到原来位置
def restore_database():
    global output_dir
    target_dir = os.sep.join([os.path.dirname(output_dir), "tmp"])

    if not os.path.exists(target_dir):
        return

    home_fs = fs.open_fs(target_dir)
    for file in home_fs.glob('*.db', namespaces=['details']):
        # copyFiles(os.path.normpath(os.sep.join([target_dir, file.path])), output_dir)
        shutil.copy(os.path.normpath(os.sep.join([target_dir, file.path])), output_dir)

    home_fs = fs.open_fs(target_dir)
    home_fs.removetree("/")
    if home_fs.exists("/"):
        os.removedirs(target_dir)

wanli's avatar
wanli committed
299 300 301 302 303
def run():
    global input_dir
    global output_dir
    input_dir = os.sep.join([os.getcwd(), "resources"])
    output_dir = os.sep.join([os.getcwd(), "build_out"])
304
    backup_database()
wanli's avatar
wanli committed
305
    # 复制文件到输出目录
306 307 308
    copyFiles(input_dir, output_dir)
    config = readConfig()
    parseConfig(config)
309
    restore_database()
wanli's avatar
wanli committed
310 311
    print("success ......")

wanli's avatar
wanli committed
312
if __name__ == "__main__":
wanli's avatar
wanli committed
313
    run()