|
| 1 | +""" |
| 2 | + :copyright: © 2019 by the Lin team. |
| 3 | + :license: MIT, see LICENSE for more details. |
| 4 | +""" |
| 5 | +import re |
| 6 | +from importlib import import_module |
| 7 | + |
| 8 | +import os |
| 9 | + |
| 10 | +from app.app import create_app |
| 11 | + |
| 12 | +""" |
| 13 | +插件初始化流程: |
| 14 | +1、输入要初始化的插件名称。(多个用空格隔开,*表示初始化所有) |
| 15 | +2、python依赖的自动检测和安装 |
| 16 | +2、将插件的配置写入到项目setting.py中 |
| 17 | +3、将model中的模型插入到数据库中 |
| 18 | +4、如果有需要,将初始数据插入到数据表中 |
| 19 | +""" |
| 20 | + |
| 21 | + |
| 22 | +class PluginInit: |
| 23 | + # 插件位置默认前缀 |
| 24 | + plugin_path = 'app.plugins' |
| 25 | + |
| 26 | + def __init__(self, name): |
| 27 | + self.app = create_app(register_all=False) |
| 28 | + self.name = name.strip() |
| 29 | + # 插件相关的路径信息,包含plugin_path(插件路径),plugin_config_path(插件的配置文件路径),plugin_info_path(插件的基本信息路径) |
| 30 | + self.path_info = dict() |
| 31 | + # 根据name生成path,写入到path_info属性中 |
| 32 | + self.generate_path() |
| 33 | + # 安装依赖 |
| 34 | + self.auto_install_rely() |
| 35 | + # 将插件的配置自动写入setting |
| 36 | + self.auto_write_setting() |
| 37 | + # 创建数据表,并且向表中插入模型中的一些初始数据 |
| 38 | + self.create_data() |
| 39 | + |
| 40 | + def generate_path(self): |
| 41 | + if self.name == '*': |
| 42 | + names = self.__get_all_plugins() |
| 43 | + else: |
| 44 | + names = self.name.split(" ") |
| 45 | + for name in names: |
| 46 | + exit('插件名称不能为空,请重试') if self.name == '' else print('正在初始化插件' + name + '...') |
| 47 | + self.path_info[name] = { |
| 48 | + 'plugin_path': self.plugin_path + '.' + name, |
| 49 | + 'plugin_config_path': self.plugin_path + '.' + name + '.config', |
| 50 | + 'plugin_info_path': self.plugin_path + '.' + name + '.info' |
| 51 | + } |
| 52 | + |
| 53 | + def auto_install_rely(self): |
| 54 | + from subprocess import CalledProcessError |
| 55 | + for name in self.path_info: |
| 56 | + filename = 'requirements.txt' |
| 57 | + file_path = self.app.config.root_path + '/plugins/' + name + '/' + filename |
| 58 | + success_msg = '安装' + name + '插件的依赖成功' |
| 59 | + fail_msg = name + '插件的依赖安装失败,请[手动安装依赖]: http://doc.7yue.pro/' |
| 60 | + if os.path.exists(file_path): |
| 61 | + if (os.path.getsize(file_path)) == 0: |
| 62 | + continue |
| 63 | + print('正在安装' + name + '插件的依赖...') |
| 64 | + |
| 65 | + try: |
| 66 | + # 使用try except来判断使用pip管理包还是pipenv管理包,首选pipenv |
| 67 | + ret = self.__execute_cmd(cmd='pipenv install -r ' + file_path) |
| 68 | + |
| 69 | + if ret: |
| 70 | + print(success_msg) |
| 71 | + else: |
| 72 | + exit(fail_msg) |
| 73 | + |
| 74 | + except CalledProcessError: |
| 75 | + try: |
| 76 | + ret = self.__execute_cmd(cmd='pip install -r ' + file_path) |
| 77 | + |
| 78 | + if ret: |
| 79 | + print(success_msg) |
| 80 | + else: |
| 81 | + exit(fail_msg) |
| 82 | + |
| 83 | + except Exception as e: |
| 84 | + exit((str(e)) + '\n' + fail_msg) |
| 85 | + |
| 86 | + except Exception as e: |
| 87 | + exit((str(e)) + '\n' + fail_msg) |
| 88 | + |
| 89 | + def auto_write_setting(self): |
| 90 | + print('正在自动写入配置文件...') |
| 91 | + setting_text = dict() |
| 92 | + for name, val in self.path_info.items(): |
| 93 | + try: |
| 94 | + info_mod = import_module(self.path_info[name]['plugin_info_path']) |
| 95 | + except ModuleNotFoundError as e: |
| 96 | + raise exit(str(e) + '\n未找到插件' + name + ',请检查您输入的插件名是否正确') |
| 97 | + |
| 98 | + res = self._generate_setting(name, info_mod) |
| 99 | + setting_text[name] = res |
| 100 | + |
| 101 | + # 正则匹配setting.py中的配置文件,将配置文件替换成新的setting_doc |
| 102 | + self.__update_setting(new_setting=setting_text) |
| 103 | + |
| 104 | + def create_data(self): |
| 105 | + print('正在创建基础数据...') |
| 106 | + for name, val in self.path_info.items(): |
| 107 | + # 调用插件__init__模块中的initial_data方法,创建初始的数据 |
| 108 | + try: |
| 109 | + plugin_module = import_module(self.path_info[name]['plugin_path'] + '.app.__init__') |
| 110 | + dir_info = dir(plugin_module) |
| 111 | + except ModuleNotFoundError as e: |
| 112 | + raise exit(str(e) + '\n未找到插件' + name + ',请检查您输入的插件名是否正确') |
| 113 | + if 'initial_data' in dir_info: |
| 114 | + # TODO 解决多次初始化数据重复添加的问题 |
| 115 | + plugin_module.initial_data() |
| 116 | + print('插件初始化成功') |
| 117 | + |
| 118 | + def _generate_setting(self, name, info_mod): |
| 119 | + info_mod_dic = info_mod.__dict__ |
| 120 | + ret = { |
| 121 | + 'path': self.path_info[name]['plugin_path'], |
| 122 | + 'enable': True, |
| 123 | + 'version': info_mod_dic.pop("__version__", '0.0.1') # info_mod_dic.__version__ |
| 124 | + } |
| 125 | + # 向setting_doc中写入插件的配置项 |
| 126 | + cfg_mod = import_module(self.path_info[name]['plugin_config_path']) |
| 127 | + dic = cfg_mod.__dict__ |
| 128 | + for key in dic.keys(): |
| 129 | + if not key.startswith('__'): |
| 130 | + ret[key] = dic[key] |
| 131 | + return ret |
| 132 | + |
| 133 | + def __update_setting(self, new_setting): |
| 134 | + # 得到现存的插件配置 |
| 135 | + old_setting = self.app.config.get('PLUGIN_PATH') |
| 136 | + final_setting = self.__cal_setting(new_setting, old_setting) |
| 137 | + |
| 138 | + sub_str = 'PLUGIN_PATH = ' + self.__format_setting(final_setting) |
| 139 | + |
| 140 | + setting_path = self.app.config.root_path + '/config/setting.py' |
| 141 | + with open(setting_path, 'r') as f: |
| 142 | + content = f.read() |
| 143 | + pattern = 'PLUGIN_PATH = \{([\s\S]*)\}+.*?' |
| 144 | + result = re.sub(pattern, sub_str, content) |
| 145 | + |
| 146 | + with open(setting_path, 'w+') as f: |
| 147 | + f.write(result) |
| 148 | + |
| 149 | + def __get_all_plugins(self): |
| 150 | + # 返回所有插件的目录名称 |
| 151 | + ret = [] |
| 152 | + path = self.app.config.root_path + '/plugins' |
| 153 | + for file in os.listdir(path=path): |
| 154 | + file_path = os.path.join(path, file) |
| 155 | + if os.path.isdir(file_path): |
| 156 | + ret.append(file) |
| 157 | + return ret |
| 158 | + |
| 159 | + @classmethod |
| 160 | + def __execute_cmd(cls, cmd): |
| 161 | + import subprocess |
| 162 | + code = subprocess.check_call(cmd, shell=True, stdout=subprocess.PIPE) |
| 163 | + if code == 0: |
| 164 | + return True |
| 165 | + elif code == 1: |
| 166 | + return False |
| 167 | + |
| 168 | + @classmethod |
| 169 | + def __format_setting(cls, setting): |
| 170 | + # 格式化setting字符串 |
| 171 | + setting_str = str(setting) |
| 172 | + ret = setting_str.replace('},', '},\n ').replace('{', '{\n ', 1) |
| 173 | + replace_reg = re.compile(r'\}$') |
| 174 | + ret = replace_reg.sub('\n}', ret) |
| 175 | + return ret |
| 176 | + |
| 177 | + @staticmethod |
| 178 | + def __cal_setting(new_setting, old_setting): |
| 179 | + # 将新旧的setting合并,返回一个字典 |
| 180 | + # 1、对比old和new,并且将这两个配置合并 |
| 181 | + # 2、如果新的存在,旧的不存在,就追加新的; |
| 182 | + # 3、如果旧的存在,新的不存在,就保留旧的; |
| 183 | + # 4、如果新旧都存在,那么在版本号相同的情况下,保留旧的配置项,否则新的配置覆盖旧的配置。 |
| 184 | + |
| 185 | + final_setting = dict() |
| 186 | + all_keys = new_setting.keys() | old_setting.keys() # 得到新旧配置的并集 |
| 187 | + |
| 188 | + for key in all_keys: |
| 189 | + if key not in old_setting.keys(): |
| 190 | + # 不存在,追加新的 |
| 191 | + final_setting[key] = new_setting[key] |
| 192 | + else: |
| 193 | + # 存在,对比版本号,看看是否需要更新 TODO 优化条件判断 |
| 194 | + if key not in new_setting: |
| 195 | + # 新的不存在 |
| 196 | + final_setting[key] = old_setting[key] |
| 197 | + else: |
| 198 | + # 新的存在 |
| 199 | + if new_setting[key]['version'] == old_setting[key]['version']: |
| 200 | + # 版本号相同,使用旧的配置 |
| 201 | + final_setting[key] = old_setting[key] |
| 202 | + else: |
| 203 | + # 版本号不同,更新配置为新的 |
| 204 | + final_setting[key] = new_setting[key] |
| 205 | + |
| 206 | + return final_setting |
| 207 | + |
| 208 | + |
| 209 | +if __name__ == '__main__': |
| 210 | + plugin_name = input('请输入要初始化的插件名,如果多个插件请使用空格分隔插件名,输入*表示初始化所有插件:\n') |
| 211 | + PluginInit(plugin_name) |
0 commit comments