UNPKG

17.5 kBPlain TextView Raw
1import * as fs from 'fs';
2import * as path from 'path';
3import * as os from 'os';
4import * as iconv from 'iconv-lite';
5import fileUtil = require("./../tool/FileUtil");
6
7var exec = require('child_process').exec;
8
9/**自动生成代码类 */
10export class AutoCodeEui {
11 private rootpath: string;//项目路径
12 private curExmlName: string;//当前exml的文件名字 不含后缀
13 private curExmlPath: string = "";//当前exml的路径
14 private codeCreate: CreateCode;//代码生成类
15
16 /**
17 * 自动生成代码类
18 * @param rootUrl 当前项目的根目录 如:D:\workspace\yscq
19 * @param exmlName 当前exml文件的名字 可带客不带.exml 如ZHTaskSkin 或 ZHTaskSkin.exml
20 * 特殊: -config 打开配置文件
21 * -module 打开模板目录
22 */
23 constructor(rootUrl: string, exmlName: string) {
24 this.rootpath = rootUrl;
25 this.codeCreate = new CreateCode(this.rootpath);
26 if (exmlName.indexOf(".exml") != -1) exmlName = exmlName.replace(".exml", "")
27 this.curExmlName = exmlName;
28 if (exmlName == "-module") {
29 this.euiOpenCodeModule();
30 } else if (exmlName == "-config") {
31 this.euiOutCodeConfig();
32 } else if (exmlName == "-help") {
33 console.log(`
34nodetool -eui -config 打开配置文件
35nodetool -eui -module 打开模板文件夹
36nodetool -eui -help 显示帮助
37nodetool -eui 皮肤名 生成皮肤的代码
38 皮肤名
39 1. exml的相对路径如: nodetool -eui resource\assets\module\chat\ChatSkin.exml
40 2. exml的文件名 如: nodetool -eui ChatSkin
41 3. exml文件全名 如: nodetool -eui ChatSkin.exml
42 4. exml模糊匹配 如: nodetool -eui Chat* (慎用)
43
44 皮肤命名规则
45 1. 如果皮肤文件名中带有 MainPanel (功能的主界面) 的话 程序生成 :
46 ViewUI(覆盖)
47 MainView(只生成一次)
48 Module(只生成一次)
49 Mediator(只生成一次)
50 Model(只生成一次)
51 2. 如果皮肤名中包含 Panel(弹窗界面) 程序生成:
52 ViewUI(覆盖)
53 Panel (只生成一次)
54 3. 如果皮肤名中包含 Render(列表项) 程序生成:
55 RenderUI (覆盖)
56 Render (只生成一次)
57 4. 如果皮肤名中包含 View(只是界面) 或者上述情况都不符合 程序生成:
58 ViewUI (覆盖)
59
60 使用说明:
61 1.可以先输入 nodetool -eui -config 打开配置app.config.json 修改 auth为自己的名字 (如果不填默认使用计算机名)
62 2.在程序的跟目录下执行命令 如 D:\workspace\yscq> nodetool -eui Chat*Render
63 `)
64 } else {
65 let exmlPath = path.join(this.rootpath, exmlName + ".exml");
66 if (!fs.existsSync(exmlPath))//如果不存在则去项目的resuoce中去找下
67 {
68 let exmlDir = path.join(this.rootpath, "resource/");
69 if (fs.existsSync(exmlDir))
70 fileUtil.FileUtil.walkDir(exmlDir, this.checkFile, undefined, this);
71 else
72 console.log("资源目录不存在:" + exmlDir);
73 } else {
74 this.checkFile(exmlPath, false);
75 }
76 }
77 }
78 /**AutoCodeEui入口 */
79 public euiOutCode() {
80 let filePath = this.curExmlPath;//.document.fileName;
81 let fileName = filePath.substring(0, filePath.lastIndexOf("\\"));
82 let ext = path.extname(filePath);
83 if (ext != '.exml') {
84 console.log("当前不是Eui文件" + ext);
85 return;
86 }
87 let content = fs.readFileSync(filePath, 'utf-8');
88 let ids = this.findIds(content);
89 // log("当前打开文件:"+ids.join(" "));
90 let euiinfo: EUIInfo = {};
91 euiinfo.path = filePath;
92 euiinfo.content = content;
93 euiinfo.ids = ids;
94 let pathinfo = path.parse(filePath);
95 let filename = euiinfo.fileName = pathinfo.name;
96 let filearr = filename.match(/^(.*?)EuiSkin$/i);
97 if (!filearr) {
98 filearr = filename.match(/^(.*?)Skin$/i);
99 }
100 if (filearr)
101 euiinfo.baseClsName = filearr[1];
102 else
103 euiinfo.baseClsName = filename;
104
105 let skinNameReg = /\s+class=["'](\w+)["']\s+/gi;
106 let skinNameArr = skinNameReg.exec(content);
107 if (skinNameArr && skinNameArr.length > 0) {
108 euiinfo.skinName = skinNameArr[1];
109 } else {
110 euiinfo.skinName = euiinfo.baseClsName;
111 }
112 euiinfo.baseClsName = euiinfo.baseClsName;
113 let pathdirarr = path.normalize(pathinfo.dir).split(path.sep);
114 euiinfo.parentDir = pathdirarr[pathdirarr.length - 1];
115 this.codeCreate.createCode(euiinfo);
116 }
117 /**
118 * 查找exml中的ids 返回 IdInfo[]
119 */
120 private findIds(content: string): IdInfo[] {
121 let ids: IdInfo[] = [];
122 let lines = content.split(/[\r\n(\r\n)]/);
123 let nss: any = this.findNameSp(lines.join(" "));
124 let idexp = / id=\"(.*?)\"/ig;
125 let uimodule = this.is_eui(content) ? "eui." : 'egret.gui.';
126 lines.forEach(line => {
127 let temp = line.match(idexp);
128 if (temp && temp.length > 0) {
129 let clsDef = line.match(/<(.+?):(.+?) /);
130 if (!clsDef || clsDef.length < 3) return;
131 let clsMod: string;
132 if (clsDef[1] == "e") {
133 clsMod = uimodule;
134 } else {
135 clsMod = nss[clsDef[1]];
136 if (!clsMod) return;
137 clsMod = clsMod.substring(0, clsMod.length - 1);
138 }
139 let clsName = clsDef[2];
140 let id = temp[0].replace(' id=', "").replace('"', '').replace('"', '');
141 ids.push({ name: id, module: clsMod, clsName: clsName });
142 }
143 })
144 return ids;
145 }
146 /**查找命名空间 */
147 public findNameSp(text: string) {
148 var map: any = {};
149 var names: any = text.match(/xmlns:(.+?)="(.+?)"/g);
150 names.forEach((name: string) => {
151 var result: any = name.match(/xmlns:(.+?)="(.+?)"/);
152 if (result.length == 3) {
153 map[result[1]] = map[result[2]];
154 }
155 });
156 return map;
157 }
158 /**是否是eui配置 */
159 public is_eui(text: string): boolean {
160 if (text.indexOf('xmlns:e="http://ns.egret.com/eui"') > 0)
161 return true;
162 return false;
163
164 }
165
166 /**打开EuiAutoCode 的配置文件 */
167 public euiOutCodeConfig() {
168 console.log(this.codeCreate.getConfigPath())
169 exec('explorer.exe /select, ' + this.codeCreate.getConfigPath());
170 }
171
172 public euiOpenCodeModule() {
173 console.log(this.codeCreate.getModulePath())
174 exec('explorer.exe ' + this.codeCreate.getModulePath());
175 }
176
177 public checkFile(url: string, needCheck = true) {
178 let fileInfo = path.parse(url);
179 let tempName = this.curExmlName;
180 if (tempName.indexOf("*") != -1) {
181 let reg = new RegExp(tempName.replace(/\*/gi, ".*?"), "gi");
182 if (reg.exec(fileInfo.name)) {
183 tempName = fileInfo.name;
184 }
185 }
186 if (fileInfo.name == tempName || !needCheck) {
187 if (!needCheck)//只有在使用全路径的时候有效
188 this.curExmlName = fileInfo.name;
189 this.curExmlPath = url;
190 console.log("开始处理 " + fileInfo.name)
191 this.euiOutCode();
192 }
193 }
194}
195
196/**
197 * 创建生成代码类
198 */
199class CreateCode {
200 private rootpath: string = "";//项目路径
201 private moduleCodePath: string = "";
202 private modulepath = path.join(__dirname, "./../../resource/autocodeeui", "module");
203 private configpath = path.join(__dirname, "./../../resource/autocodeeui/config/app.config.json")
204 private config: any;
205
206 constructor(rootpath: string) {
207 this.rootpath = rootpath;
208 }
209
210 public getConfigPath() {
211 return this.configpath;
212 }
213
214 public getModulePath() {
215 return this.modulepath;
216 }
217
218 /**
219 * 创建程序代码入口
220 */
221 public createCode(info: EUIInfo) {
222 if (!fs.existsSync(this.configpath)) {
223 console.log("请添加插件配置" + this.configpath)
224 return;
225 }
226 let configtxt = fs.readFileSync(this.configpath, 'utf-8');
227 this.config = JSON.parse(configtxt);
228 this.moduleCodePath = this.config.moduleCodePath;
229 let varsDic: any = {};
230 varsDic["auth"] = this.config.auth || this.getHostName();
231 let date = new Date();
232 varsDic["time"] = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() + " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
233 varsDic['path'] = path.relative(this.rootpath, info.path);
234 varsDic['fileName'] = info.fileName;
235 varsDic['baseClsName'] = info.baseClsName;
236 varsDic['skinName'] = info.skinName;
237 let varids = "";
238 let interfaceIds = "";
239 let ids = info.ids;
240 let createIdDic: { [id: number]: boolean } = {};
241 for (let i = 0; i < ids.length; i++) {
242 let id: any = ids[i];
243 let preAdd = "";
244 if (createIdDic[id.name]) preAdd = "//";
245 varids += preAdd + "public " + id.name + ":" + id.module + id.clsName + ";\n\t";
246 interfaceIds += preAdd + id.name + "?:any;\n\t";
247 createIdDic[id.name] = true;
248 }
249 varsDic['varids'] = varids;
250 varsDic['interfaceIds'] = interfaceIds;
251 let create = this.findModule(this.config, info.fileName);
252 let moduleIds: Array<string> = create.usemodule.split(",");
253 var keys = create.keyword.split("|");
254 for (let j = 0; j < keys.length; j++) {
255 if (info.baseClsName.indexOf(keys[j]) != -1) {
256 varsDic["shortName"] = info.baseClsName.replace(keys[j], "");
257 break;
258 }
259 }
260 if (!varsDic["shortName"]) varsDic["shortName"] = info.baseClsName;
261 varsDic["moduleID"] = this.getModuleID(varsDic["shortName"]);
262 for (let i = 0; i < moduleIds.length; i++) {
263 let mod: ModuleInfo = this.config.module[+moduleIds[i]];
264 if (!mod) {
265 console.log("app.config.json当前没有配置" + moduleIds[i] + "对应的配置")
266 continue;
267 }
268 this.createFileByModuld(mod, info, varsDic);
269 }
270 }
271
272 private getHostName() {
273 let name = os.hostname();
274 let buff = new Buffer(name, "binary");
275 name = iconv.decode(buff, "gbk");
276 return name;
277 }
278
279 /**根据module名字生成ModuleID 生成规则 moduleName原始字符中大写前加上_并把所有字符转成大写 */
280 public getModuleID(moduleName: string) {
281 let idkey: string = "";
282 for (let i = 0; i < moduleName.length; i++) {
283 let char = moduleName[i].toLocaleUpperCase();
284 if (char == moduleName[i]) {
285 if (idkey)
286 idkey = idkey + "_";
287 }
288 idkey += char;
289 }
290 return idkey;
291 }
292
293 /**根据eui的文件名找到对应处理的CreateInfo 如果没有找到则用配置中默认的 */
294 public findModule(config: AppConfig, fileName: string) {
295 let creates = config.create;
296 for (let i = 0; i < creates.length; i++) {
297 let create = creates[i];
298 var keys = create.keyword.split("|");
299 for (let j = 0; j < keys.length; j++) {
300 if (fileName.indexOf(keys[j]) != -1) {
301 return create;
302 }
303 }
304 }
305 return config.create[config.defaultcreate];
306 }
307
308
309 /**根据ModuleInfo 生成对应的文件 */
310 public createFileByModuld(modinfo: ModuleInfo, info: EUIInfo, varsDic: any) {
311 let keyReg = /\[(.*?)\]/gi;
312 let keyArr = keyReg.exec(modinfo.outdir);
313 let outpath = path.join(this.rootpath, this.moduleCodePath + info.parentDir + "/" + modinfo.outdir + "/" + varsDic["shortName"] + modinfo.name + "." + modinfo.fileType);
314 if (keyArr) {
315 outpath = path.join(this.rootpath, modinfo.outdir.replace(keyArr[0], keyArr[1]).trim(), varsDic["shortName"] + modinfo.name + "." + modinfo.fileType);
316 }
317 let exists = fs.existsSync(outpath);
318 if (!modinfo.override && exists) {
319 // console.log("文件已存在不用生成")
320 console.log("文件已存在不用生成");
321 return;
322 }
323 let areaDic: any = {};
324
325 if (exists)//如果存在保护域则先记录保护域的内容
326 {
327 let reg = /\/\*+area(\d+)--start\*+\/([\s\S]*)\/\*+area\1--end\*+\//gi;
328 let oldContent = fs.readFileSync(outpath, 'utf-8');
329 let rect;
330 while (rect = reg.exec(oldContent)) {
331 areaDic[rect[1]] = rect[2];
332 }
333 }
334 let viewcode: string = this.createCodeTxt(path.join(this.modulepath, modinfo.file), info, varsDic);
335 if (exists)//将保护域的内容在重新放到生成的文件中
336 {
337 for (let key in areaDic) {
338 let reg = new RegExp("(\\/\\*+area" + key + "--start\\*+\\/)([\\s\\S]*)(\\/\\*+area" + key + "--end\\*+\\/)", "gi");
339 viewcode = viewcode.replace(reg, "$1" + areaDic[key] + "$3");
340 }
341 }
342 this.saveFile(outpath, viewcode);
343 console.log("创建成功" + outpath)
344 }
345
346 /**保存文件, 如果文件夹不存在则会创建文件夹 */
347 public saveFile(path: string, data: string) {
348 this.checkOrCreateDir(path);
349 fs.writeFileSync(path, data, { encoding: "utf-8" });
350 }
351
352 /**生成模板替换后的文本*/
353 public createCodeTxt(moduleFilePath: string, info: EUIInfo, varDic: any): string {
354 if (!fs.existsSync(moduleFilePath)) {
355 console.log("当前文件不存在" + moduleFilePath)
356 return "";
357 }
358 let content = fs.readFileSync(moduleFilePath, 'utf-8');
359 var keyReg = /\$\{(.*?)\}/gi;
360 let keyArr;
361 while (keyArr = keyReg.exec(content)) {
362 content = content.split(keyArr[0]).join(varDic[keyArr[1]]);
363 }
364 return content;
365 }
366
367
368 /**创建新的文件夹 */
369 public checkOrCreateDir(filePath: string) {
370 filePath = path.normalize(filePath);
371 let arr = path.parse(filePath).dir.split(path.sep);
372 if (!arr || arr.length == 0) return;
373 let dirpath = arr[0];
374 for (let i = 1; i < arr.length; i++) {
375 dirpath = dirpath + path.sep + arr[i];
376 if (!fs.existsSync(dirpath)) {
377 fs.mkdirSync(dirpath);
378 }
379 }
380 }
381}
382
383
384
385/**Eui中id变量信息 */
386interface IdInfo {
387 /**变量名字 */
388 name: string;
389 /**变量模块名 */
390 module: string;
391 /**变量类名 */
392 clsName: string;
393}
394
395/**
396 * Eui文件的基本信息
397 */
398interface EUIInfo {
399 /**文件路径 */
400 path?: string;
401 /**文件内容 */
402 content?: string;
403
404 /** 基本解析后的数据内容 */
405
406 /**eui文件中的所有id的信息 IdInfo[]*/
407 ids?: IdInfo[];
408 /**当前 exml文件的目录 */
409 parentDir?: string;
410 /**当前 exml的文件名 不包含文件的后缀*/
411 fileName?: string;
412 /** 导出类的基本名字 一般用于生成对应功能的类 ` 如${baseClsName}View ${baseClsName}Mediator` */
413 baseClsName?: string;
414 /**配置的class对应的skin名字 */
415 skinName?: string;
416}
417
418///////////项目配置
419
420/**项目配置信息 */
421interface AppConfig {
422 auth: string;
423 defaultcreate: number;
424 create: CreateInfo[];
425 module: { [id: number]: ModuleInfo };
426 moduleCodePath: string;
427}
428/**创建模板的信息 */
429interface CreateInfo {
430 /**文件名中包含的特殊字符串, 如果多个可以用 | 分开 */
431 keyword: string;
432 /**使用那些模板生成 多个可以用,分开 */
433 usemodule: string;
434}
435
436/**每个具体模板的信息 */
437interface ModuleInfo {
438 /**模板的标识id */
439 id: number;
440 /**模板的名字 生成类名时 基础名字$baseClsName)加上name 作为类名 如要改动最好也检查下对应的模板文件(写死的)中*/
441 name: string;
442 /**模板文件名 在module/路径下 */
443 file: string;
444 /** 生成文件时 在对应模块文件下新建的文件夹名字*/
445 outdir: string;
446 /** 是否覆盖 true 每次生成都覆盖 false 如果有了就不生成了 */
447 override: boolean;
448 /**生成的文件后缀名 */
449 fileType: string;
450}
451
452
453export function run(rootUrl: string, exmlName: string): void {
454 new AutoCodeEui(rootUrl, exmlName);
455 console.log("执行完毕");
456}
\No newline at end of file