import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import * as iconv from 'iconv-lite'; import fileUtil = require("./../tool/FileUtil"); var exec = require('child_process').exec; /**自动生成代码类 */ export class AutoCodeEui { private rootpath: string;//项目路径 private curExmlName: string;//当前exml的文件名字 不含后缀 private curExmlPath: string = "";//当前exml的路径 private codeCreate: CreateCode;//代码生成类 /** * 自动生成代码类 * @param rootUrl 当前项目的根目录 如:D:\workspace\yscq * @param exmlName 当前exml文件的名字 可带客不带.exml 如ZHTaskSkin 或 ZHTaskSkin.exml * 特殊: -config 打开配置文件 * -module 打开模板目录 */ constructor(rootUrl: string, exmlName: string) { this.rootpath = rootUrl; this.codeCreate = new CreateCode(this.rootpath); if (exmlName.indexOf(".exml") != -1) exmlName = exmlName.replace(".exml", "") this.curExmlName = exmlName; if (exmlName == "-module") { this.euiOpenCodeModule(); } else if (exmlName == "-config") { this.euiOutCodeConfig(); } else if (exmlName == "-help") { console.log(` nodetool -eui -config 打开配置文件 nodetool -eui -module 打开模板文件夹 nodetool -eui -help 显示帮助 nodetool -eui 皮肤名 生成皮肤的代码 皮肤名 1. exml的相对路径如: nodetool -eui resource\assets\module\chat\ChatSkin.exml 2. exml的文件名 如: nodetool -eui ChatSkin 3. exml文件全名 如: nodetool -eui ChatSkin.exml 4. exml模糊匹配 如: nodetool -eui Chat* (慎用) 皮肤命名规则 1. 如果皮肤文件名中带有 MainPanel (功能的主界面) 的话 程序生成 : ViewUI(覆盖) MainView(只生成一次) Module(只生成一次) Mediator(只生成一次) Model(只生成一次) 2. 如果皮肤名中包含 Panel(弹窗界面) 程序生成: ViewUI(覆盖) Panel (只生成一次) 3. 如果皮肤名中包含 Render(列表项) 程序生成: RenderUI (覆盖) Render (只生成一次) 4. 如果皮肤名中包含 View(只是界面) 或者上述情况都不符合 程序生成: ViewUI (覆盖) 使用说明: 1.可以先输入 nodetool -eui -config 打开配置app.config.json 修改 auth为自己的名字 (如果不填默认使用计算机名) 2.在程序的跟目录下执行命令 如 D:\workspace\yscq> nodetool -eui Chat*Render `) } else { let exmlPath = path.join(this.rootpath, exmlName + ".exml"); if (!fs.existsSync(exmlPath))//如果不存在则去项目的resuoce中去找下 { let exmlDir = path.join(this.rootpath, "resource/"); if (fs.existsSync(exmlDir)) fileUtil.FileUtil.walkDir(exmlDir, this.checkFile, undefined, this); else console.log("资源目录不存在:" + exmlDir); } else { this.checkFile(exmlPath, false); } } } /**AutoCodeEui入口 */ public euiOutCode() { let filePath = this.curExmlPath;//.document.fileName; let fileName = filePath.substring(0, filePath.lastIndexOf("\\")); let ext = path.extname(filePath); if (ext != '.exml') { console.log("当前不是Eui文件" + ext); return; } let content = fs.readFileSync(filePath, 'utf-8'); let ids = this.findIds(content); // log("当前打开文件:"+ids.join(" ")); let euiinfo: EUIInfo = {}; euiinfo.path = filePath; euiinfo.content = content; euiinfo.ids = ids; let pathinfo = path.parse(filePath); let filename = euiinfo.fileName = pathinfo.name; let filearr = filename.match(/^(.*?)EuiSkin$/i); if (!filearr) { filearr = filename.match(/^(.*?)Skin$/i); } if (filearr) euiinfo.baseClsName = filearr[1]; else euiinfo.baseClsName = filename; let skinNameReg = /\s+class=["'](\w+)["']\s+/gi; let skinNameArr = skinNameReg.exec(content); if (skinNameArr && skinNameArr.length > 0) { euiinfo.skinName = skinNameArr[1]; } else { euiinfo.skinName = euiinfo.baseClsName; } euiinfo.baseClsName = euiinfo.baseClsName; let pathdirarr = path.normalize(pathinfo.dir).split(path.sep); euiinfo.parentDir = pathdirarr[pathdirarr.length - 1]; this.codeCreate.createCode(euiinfo); } /** * 查找exml中的ids 返回 IdInfo[] */ private findIds(content: string): IdInfo[] { let ids: IdInfo[] = []; let lines = content.split(/[\r\n(\r\n)]/); let nss: any = this.findNameSp(lines.join(" ")); let idexp = / id=\"(.*?)\"/ig; let uimodule = this.is_eui(content) ? "eui." : 'egret.gui.'; lines.forEach(line => { let temp = line.match(idexp); if (temp && temp.length > 0) { let clsDef = line.match(/<(.+?):(.+?) /); if (!clsDef || clsDef.length < 3) return; let clsMod: string; if (clsDef[1] == "e") { clsMod = uimodule; } else { clsMod = nss[clsDef[1]]; if (!clsMod) return; clsMod = clsMod.substring(0, clsMod.length - 1); } let clsName = clsDef[2]; let id = temp[0].replace(' id=', "").replace('"', '').replace('"', ''); ids.push({ name: id, module: clsMod, clsName: clsName }); } }) return ids; } /**查找命名空间 */ public findNameSp(text: string) { var map: any = {}; var names: any = text.match(/xmlns:(.+?)="(.+?)"/g); names.forEach((name: string) => { var result: any = name.match(/xmlns:(.+?)="(.+?)"/); if (result.length == 3) { map[result[1]] = map[result[2]]; } }); return map; } /**是否是eui配置 */ public is_eui(text: string): boolean { if (text.indexOf('xmlns:e="http://ns.egret.com/eui"') > 0) return true; return false; } /**打开EuiAutoCode 的配置文件 */ public euiOutCodeConfig() { console.log(this.codeCreate.getConfigPath()) exec('explorer.exe /select, ' + this.codeCreate.getConfigPath()); } public euiOpenCodeModule() { console.log(this.codeCreate.getModulePath()) exec('explorer.exe ' + this.codeCreate.getModulePath()); } public checkFile(url: string, needCheck = true) { let fileInfo = path.parse(url); let tempName = this.curExmlName; if (tempName.indexOf("*") != -1) { let reg = new RegExp(tempName.replace(/\*/gi, ".*?"), "gi"); if (reg.exec(fileInfo.name)) { tempName = fileInfo.name; } } if (fileInfo.name == tempName || !needCheck) { if (!needCheck)//只有在使用全路径的时候有效 this.curExmlName = fileInfo.name; this.curExmlPath = url; console.log("开始处理 " + fileInfo.name) this.euiOutCode(); } } } /** * 创建生成代码类 */ class CreateCode { private rootpath: string = "";//项目路径 private moduleCodePath: string = ""; private modulepath = path.join(__dirname, "./../../resource/autocodeeui", "module"); private configpath = path.join(__dirname, "./../../resource/autocodeeui/config/app.config.json") private config: any; constructor(rootpath: string) { this.rootpath = rootpath; } public getConfigPath() { return this.configpath; } public getModulePath() { return this.modulepath; } /** * 创建程序代码入口 */ public createCode(info: EUIInfo) { if (!fs.existsSync(this.configpath)) { console.log("请添加插件配置" + this.configpath) return; } let configtxt = fs.readFileSync(this.configpath, 'utf-8'); this.config = JSON.parse(configtxt); this.moduleCodePath = this.config.moduleCodePath; let varsDic: any = {}; varsDic["auth"] = this.config.auth || this.getHostName(); let date = new Date(); varsDic["time"] = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() + " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds(); varsDic['path'] = path.relative(this.rootpath, info.path); varsDic['fileName'] = info.fileName; varsDic['baseClsName'] = info.baseClsName; varsDic['skinName'] = info.skinName; let varids = ""; let interfaceIds = ""; let ids = info.ids; let createIdDic: { [id: number]: boolean } = {}; for (let i = 0; i < ids.length; i++) { let id: any = ids[i]; let preAdd = ""; if (createIdDic[id.name]) preAdd = "//"; varids += preAdd + "public " + id.name + ":" + id.module + id.clsName + ";\n\t"; interfaceIds += preAdd + id.name + "?:any;\n\t"; createIdDic[id.name] = true; } varsDic['varids'] = varids; varsDic['interfaceIds'] = interfaceIds; let create = this.findModule(this.config, info.fileName); let moduleIds: Array = create.usemodule.split(","); var keys = create.keyword.split("|"); for (let j = 0; j < keys.length; j++) { if (info.baseClsName.indexOf(keys[j]) != -1) { varsDic["shortName"] = info.baseClsName.replace(keys[j], ""); break; } } if (!varsDic["shortName"]) varsDic["shortName"] = info.baseClsName; varsDic["moduleID"] = this.getModuleID(varsDic["shortName"]); for (let i = 0; i < moduleIds.length; i++) { let mod: ModuleInfo = this.config.module[+moduleIds[i]]; if (!mod) { console.log("app.config.json当前没有配置" + moduleIds[i] + "对应的配置") continue; } this.createFileByModuld(mod, info, varsDic); } } private getHostName() { let name = os.hostname(); let buff = new Buffer(name, "binary"); name = iconv.decode(buff, "gbk"); return name; } /**根据module名字生成ModuleID 生成规则 moduleName原始字符中大写前加上_并把所有字符转成大写 */ public getModuleID(moduleName: string) { let idkey: string = ""; for (let i = 0; i < moduleName.length; i++) { let char = moduleName[i].toLocaleUpperCase(); if (char == moduleName[i]) { if (idkey) idkey = idkey + "_"; } idkey += char; } return idkey; } /**根据eui的文件名找到对应处理的CreateInfo 如果没有找到则用配置中默认的 */ public findModule(config: AppConfig, fileName: string) { let creates = config.create; for (let i = 0; i < creates.length; i++) { let create = creates[i]; var keys = create.keyword.split("|"); for (let j = 0; j < keys.length; j++) { if (fileName.indexOf(keys[j]) != -1) { return create; } } } return config.create[config.defaultcreate]; } /**根据ModuleInfo 生成对应的文件 */ public createFileByModuld(modinfo: ModuleInfo, info: EUIInfo, varsDic: any) { let keyReg = /\[(.*?)\]/gi; let keyArr = keyReg.exec(modinfo.outdir); let outpath = path.join(this.rootpath, this.moduleCodePath + info.parentDir + "/" + modinfo.outdir + "/" + varsDic["shortName"] + modinfo.name + "." + modinfo.fileType); if (keyArr) { outpath = path.join(this.rootpath, modinfo.outdir.replace(keyArr[0], keyArr[1]).trim(), varsDic["shortName"] + modinfo.name + "." + modinfo.fileType); } let exists = fs.existsSync(outpath); if (!modinfo.override && exists) { // console.log("文件已存在不用生成") console.log("文件已存在不用生成"); return; } let areaDic: any = {}; if (exists)//如果存在保护域则先记录保护域的内容 { let reg = /\/\*+area(\d+)--start\*+\/([\s\S]*)\/\*+area\1--end\*+\//gi; let oldContent = fs.readFileSync(outpath, 'utf-8'); let rect; while (rect = reg.exec(oldContent)) { areaDic[rect[1]] = rect[2]; } } let viewcode: string = this.createCodeTxt(path.join(this.modulepath, modinfo.file), info, varsDic); if (exists)//将保护域的内容在重新放到生成的文件中 { for (let key in areaDic) { let reg = new RegExp("(\\/\\*+area" + key + "--start\\*+\\/)([\\s\\S]*)(\\/\\*+area" + key + "--end\\*+\\/)", "gi"); viewcode = viewcode.replace(reg, "$1" + areaDic[key] + "$3"); } } this.saveFile(outpath, viewcode); console.log("创建成功" + outpath) } /**保存文件, 如果文件夹不存在则会创建文件夹 */ public saveFile(path: string, data: string) { this.checkOrCreateDir(path); fs.writeFileSync(path, data, { encoding: "utf-8" }); } /**生成模板替换后的文本*/ public createCodeTxt(moduleFilePath: string, info: EUIInfo, varDic: any): string { if (!fs.existsSync(moduleFilePath)) { console.log("当前文件不存在" + moduleFilePath) return ""; } let content = fs.readFileSync(moduleFilePath, 'utf-8'); var keyReg = /\$\{(.*?)\}/gi; let keyArr; while (keyArr = keyReg.exec(content)) { content = content.split(keyArr[0]).join(varDic[keyArr[1]]); } return content; } /**创建新的文件夹 */ public checkOrCreateDir(filePath: string) { filePath = path.normalize(filePath); let arr = path.parse(filePath).dir.split(path.sep); if (!arr || arr.length == 0) return; let dirpath = arr[0]; for (let i = 1; i < arr.length; i++) { dirpath = dirpath + path.sep + arr[i]; if (!fs.existsSync(dirpath)) { fs.mkdirSync(dirpath); } } } } /**Eui中id变量信息 */ interface IdInfo { /**变量名字 */ name: string; /**变量模块名 */ module: string; /**变量类名 */ clsName: string; } /** * Eui文件的基本信息 */ interface EUIInfo { /**文件路径 */ path?: string; /**文件内容 */ content?: string; /** 基本解析后的数据内容 */ /**eui文件中的所有id的信息 IdInfo[]*/ ids?: IdInfo[]; /**当前 exml文件的目录 */ parentDir?: string; /**当前 exml的文件名 不包含文件的后缀*/ fileName?: string; /** 导出类的基本名字 一般用于生成对应功能的类 ` 如${baseClsName}View ${baseClsName}Mediator` */ baseClsName?: string; /**配置的class对应的skin名字 */ skinName?: string; } ///////////项目配置 /**项目配置信息 */ interface AppConfig { auth: string; defaultcreate: number; create: CreateInfo[]; module: { [id: number]: ModuleInfo }; moduleCodePath: string; } /**创建模板的信息 */ interface CreateInfo { /**文件名中包含的特殊字符串, 如果多个可以用 | 分开 */ keyword: string; /**使用那些模板生成 多个可以用,分开 */ usemodule: string; } /**每个具体模板的信息 */ interface ModuleInfo { /**模板的标识id */ id: number; /**模板的名字 生成类名时 基础名字$baseClsName)加上name 作为类名 如要改动最好也检查下对应的模板文件(写死的)中*/ name: string; /**模板文件名 在module/路径下 */ file: string; /** 生成文件时 在对应模块文件下新建的文件夹名字*/ outdir: string; /** 是否覆盖 true 每次生成都覆盖 false 如果有了就不生成了 */ override: boolean; /**生成的文件后缀名 */ fileType: string; } export function run(rootUrl: string, exmlName: string): void { new AutoCodeEui(rootUrl, exmlName); console.log("执行完毕"); }