TemplateEngine.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Reflection;
  5. using System.Text;
  6. using UnityEngine;
  7. namespace ET.PackageManager.Editor
  8. {
  9. /// <summary>
  10. /// 简单的模板引擎, 主要用在编辑器上
  11. /// </summary>
  12. public class TemplateEngine
  13. {
  14. private static readonly Dictionary<string, string> g_templateCacheMap = new Dictionary<string, string>();
  15. /// <summary>
  16. /// 模板文件的基础路径
  17. /// eg: Assets/Ediotr/Config/Decoder/Template/
  18. /// 注意: 前面不要“/”,后面要“/”
  19. /// </summary>
  20. public static string TemplateBasePath;
  21. private const string m_StartSignFormat = "#region {0}开始";
  22. private const string m_EndSignFormat = "#endregion {0}结束";
  23. /// <summary>
  24. /// 处理模板
  25. /// </summary>
  26. /// <param name="templatePath"></param>
  27. /// <param name="valueDic"></param>
  28. /// <param name="valueDic2"></param>
  29. /// <param name="readTemplateCache"></param>
  30. /// <returns></returns>
  31. public static string Do(string templatePath, Dictionary<string, string> valueDic,
  32. Dictionary<string, string> valueDic2, bool readTemplateCache = true)
  33. {
  34. if (!string.IsNullOrEmpty(TemplateBasePath))
  35. {
  36. if (TemplateBasePath.Contains("Assets/") && templatePath.Contains("Assets/"))
  37. {
  38. templatePath = templatePath.Replace("Assets/", "");
  39. }
  40. templatePath = TemplateBasePath + templatePath;
  41. }
  42. string templateStr = null;
  43. if (readTemplateCache)
  44. {
  45. g_templateCacheMap.TryGetValue(templatePath, out templateStr);
  46. }
  47. if (templateStr == null)
  48. {
  49. string path = EditorHelper.GetProjPath(templatePath);
  50. try
  51. {
  52. templateStr = File.ReadAllText(path);
  53. }
  54. catch (Exception e)
  55. {
  56. Debug.LogError("读取文件失败: " + e);
  57. return null;
  58. }
  59. g_templateCacheMap[templatePath] = templateStr;
  60. }
  61. if (valueDic != null)
  62. {
  63. foreach (KeyValuePair<string, string> pair in valueDic)
  64. {
  65. templateStr = templateStr.Replace("${" + pair.Key + "}", pair.Value);
  66. }
  67. }
  68. if (valueDic2 != null)
  69. {
  70. foreach (KeyValuePair<string, string> pair in valueDic2)
  71. {
  72. templateStr = templateStr.Replace("${" + pair.Key + "}", pair.Value);
  73. }
  74. }
  75. return templateStr;
  76. }
  77. /// <summary>
  78. /// 处理列表类型模板
  79. /// </summary>
  80. /// <typeparam name="TItemType"></typeparam>
  81. /// <param name="itemTemplateStr"></param>
  82. /// <param name="list"></param>
  83. /// <param name="fieldOrPropNames"></param>
  84. /// <param name="afterProcessItem">后处理函数</param>
  85. /// <returns></returns>
  86. public static string DoList<TItemType>(string itemTemplateStr, TItemType[] list, string[] fieldOrPropNames,
  87. Func<string, TItemType, string> afterProcessItem = null)
  88. {
  89. Type itemType = typeof(TItemType);
  90. int len = list.Length;
  91. StringBuilder sb = new StringBuilder(len);
  92. for (int i = 0; i < len; i++)
  93. {
  94. TItemType item = list[i];
  95. string template = itemTemplateStr;
  96. foreach (string name in fieldOrPropNames)
  97. {
  98. string tName = "${" + name + "}";
  99. FieldInfo fi = itemType.GetField(name);
  100. if (fi != null)
  101. {
  102. template = template.Replace(tName, fi.GetValue(item).ToString());
  103. continue;
  104. }
  105. PropertyInfo pi = itemType.GetProperty(name);
  106. if (pi == null)
  107. {
  108. Debug.LogWarning($"no {name} fields or attributes in the {itemType}");
  109. continue;
  110. }
  111. template = template.Replace(tName, pi.GetValue(item, null).ToString());
  112. }
  113. if (afterProcessItem != null)
  114. {
  115. template = afterProcessItem(template, item);
  116. }
  117. sb.Append(template);
  118. }
  119. return sb.ToString();
  120. }
  121. /// <summary>
  122. /// 检查目标文件是否存在
  123. /// </summary>
  124. /// <param name="path"></param>
  125. /// <returns></returns>
  126. public static bool FileExists(string path)
  127. {
  128. try
  129. {
  130. path = EditorHelper.GetProjPath(path);
  131. return File.Exists(path);
  132. }
  133. catch (Exception e)
  134. {
  135. Debug.LogError("检查目标文件是否存在失败: path =" + path + ", err=" + e);
  136. return false;
  137. }
  138. }
  139. public static bool CreateCodeFile(string path, string templateName, Dictionary<string, string> valueDic)
  140. {
  141. templateName = templateName.Contains(".txt") ? templateName : templateName + ".txt";
  142. string clsStr = Do(templateName, valueDic, null, false);
  143. if (clsStr == null)
  144. {
  145. Debug.LogError("模板转化失败, templateName:" + templateName);
  146. return false;
  147. }
  148. try
  149. {
  150. path = EditorHelper.GetProjPath(path);
  151. string dir = Path.GetDirectoryName(path);
  152. if (dir == null)
  153. {
  154. Debug.LogError("dir == null");
  155. return false;
  156. }
  157. if (!Directory.Exists(dir))
  158. {
  159. Directory.CreateDirectory(dir);
  160. }
  161. File.WriteAllText(path, clsStr, Encoding.UTF8);
  162. Debug.Log(path + "创建成功");
  163. return true;
  164. }
  165. catch (Exception e)
  166. {
  167. Debug.LogError("创建代码文件失败: path =" + path + ", err=" + e);
  168. return false;
  169. }
  170. }
  171. /// <summary>
  172. /// 使用Unity #region 为标识的替换规则
  173. /// 在#region >> #endregion 这块区域内查找规则替换
  174. /// </summary>
  175. /// <param name="otherRetain">标记中的其他的是否保留</param>
  176. /// <returns></returns>
  177. private static bool RegionReplace(string path, KeyValuePair<string, string> pair, bool otherRetain = true)
  178. {
  179. //获取文件
  180. if (path != null)
  181. {
  182. StringBuilder stringBuilder = new StringBuilder();
  183. StreamReader streamReader = new StreamReader(path);
  184. string line = streamReader.ReadLine();
  185. bool isWrite = false; //标记中途是否检查到已经写入过这个字段 防止重复
  186. string startStr = string.Format(m_StartSignFormat, pair.Key);
  187. string endStr = string.Format(m_EndSignFormat, pair.Key);
  188. while (line != null)
  189. {
  190. if (line.IndexOf(startStr) > -1)
  191. {
  192. isWrite = true;
  193. stringBuilder.Append(line + "\n");
  194. }
  195. else if (line.IndexOf(endStr) > -1)
  196. {
  197. if (isWrite)
  198. {
  199. stringBuilder.Append($"{pair.Value}\n");
  200. isWrite = false;
  201. }
  202. stringBuilder.Append(line + "\n");
  203. }
  204. else
  205. {
  206. if (isWrite)
  207. {
  208. if (line.IndexOf(pair.Value) > -1)
  209. {
  210. isWrite = false;
  211. }
  212. if (otherRetain)
  213. {
  214. stringBuilder.Append(line + "\n");
  215. }
  216. }
  217. else
  218. {
  219. stringBuilder.Append(line + "\n");
  220. }
  221. }
  222. line = streamReader.ReadLine();
  223. }
  224. streamReader.Close();
  225. StreamWriter streamWriter = new StreamWriter(path);
  226. streamWriter.Write(stringBuilder.ToString());
  227. streamWriter.Close();
  228. }
  229. else
  230. {
  231. Debug.LogError($"没有读取到{path}文件,请检查是否生成");
  232. return false;
  233. }
  234. return true;
  235. }
  236. /// <summary>
  237. /// 重写文件
  238. /// 根据标识的范围重写
  239. /// </summary>
  240. /// <param name="path">精准路径 Assets/ ... XX.CS(扩展名)</param>
  241. /// <param name="valueDic">精准替换的范围与值</param>
  242. /// <param name="otherRetain">范围内的是否保留 默认保留 否则会覆盖</param>
  243. /// <returns></returns>
  244. public static bool OverrideCodeFile(string path, Dictionary<string, string> valueDic, bool otherRetain = true)
  245. {
  246. path = EditorHelper.GetProjPath(path);
  247. foreach (var pair in valueDic)
  248. {
  249. if (!RegionReplace(path, pair, otherRetain))
  250. {
  251. return false;
  252. }
  253. }
  254. return true;
  255. }
  256. /// <summary>
  257. /// 指定区域内进行检查写入
  258. /// 需要区域内不存在才写入
  259. /// </summary>
  260. private static bool RegionCheckReplace2(string path, string signKey, string checkContent, string valueContent,
  261. bool otherRetain = true)
  262. {
  263. //获取文件
  264. if (path != null)
  265. {
  266. StringBuilder stringBuilder = new StringBuilder();
  267. StreamReader streamReader = new StreamReader(path);
  268. string line = streamReader.ReadLine();
  269. bool checkExist = false; //检查是否已经存在 如果已经存在则不重写 否则需要重写
  270. bool isWrite = false; //标记中途是否检查到已经写入过这个字段 防止重复
  271. string startStr = string.Format(m_StartSignFormat, signKey);
  272. string endStr = string.Format(m_EndSignFormat, signKey);
  273. while (line != null)
  274. {
  275. if (line.IndexOf(startStr) > -1)
  276. {
  277. isWrite = true;
  278. stringBuilder.Append(line + "\n");
  279. }
  280. else if (line.IndexOf(endStr) > -1)
  281. {
  282. if (isWrite)
  283. {
  284. if (!checkExist)
  285. {
  286. stringBuilder.Append($"{valueContent}\n");
  287. }
  288. isWrite = false;
  289. }
  290. stringBuilder.Append(line + "\n");
  291. }
  292. else
  293. {
  294. if (isWrite)
  295. {
  296. if (line.IndexOf(checkContent) > -1)
  297. {
  298. checkExist = true;
  299. }
  300. if (otherRetain)
  301. {
  302. stringBuilder.Append(line + "\n");
  303. }
  304. }
  305. else
  306. {
  307. stringBuilder.Append(line + "\n");
  308. }
  309. }
  310. line = streamReader.ReadLine();
  311. }
  312. streamReader.Close();
  313. StreamWriter streamWriter = new StreamWriter(path);
  314. streamWriter.Write(stringBuilder.ToString());
  315. streamWriter.Close();
  316. }
  317. else
  318. {
  319. Debug.LogError($"没有读取到{path}文件,请检查是否生成");
  320. return false;
  321. }
  322. return true;
  323. }
  324. /// <summary>
  325. /// 指定区域内进行检查写入
  326. /// 需要区域内不存在才写入
  327. /// </summary>
  328. public static bool OverrideCheckCodeFile(string path,
  329. Dictionary<string, List<Dictionary<string, string>>> replaceDic,
  330. bool cover = false)
  331. {
  332. var clsStr = RegionCheckReplace(path, replaceDic, cover);
  333. if (clsStr == null)
  334. {
  335. Debug.LogError("模板转化失败, path:" + path);
  336. return false;
  337. }
  338. try
  339. {
  340. path = EditorHelper.GetProjPath(path);
  341. string dir = Path.GetDirectoryName(path);
  342. if (dir == null)
  343. {
  344. Debug.LogError("dir == null");
  345. return false;
  346. }
  347. if (!Directory.Exists(dir))
  348. {
  349. Directory.CreateDirectory(dir);
  350. }
  351. File.WriteAllText(path, clsStr, Encoding.UTF8);
  352. Debug.Log(path + "重写成功");
  353. return true;
  354. }
  355. catch (Exception e)
  356. {
  357. Debug.LogError("重写文件失败: path =" + path + ", err=" + e);
  358. return false;
  359. }
  360. }
  361. /// <summary>
  362. /// 指定区域内进行检查写入
  363. /// 需要区域内不存在才写入
  364. /// </summary>
  365. private static string RegionCheckReplace(string path,
  366. Dictionary<string, List<Dictionary<string, string>>> replaceDic,
  367. bool cover = false)
  368. {
  369. var templateStr = File.ReadAllText(path);
  370. foreach (var item in replaceDic)
  371. {
  372. var key = item.Key;
  373. var valueList = item.Value;
  374. var startStr = string.Format(m_StartSignFormat, key);
  375. var endStr = string.Format(m_EndSignFormat, key);
  376. var startIndex = templateStr.IndexOf(startStr, StringComparison.Ordinal);
  377. var endIndex = templateStr.IndexOf(endStr, StringComparison.Ordinal);
  378. if (startIndex <= -1 || endIndex <= startIndex)
  379. {
  380. Debug.LogError($"{path} 此文件没有检查到关键字 {key} 请手动添加 关键字不允许移除");
  381. continue;
  382. }
  383. var tempStr = templateStr.Substring(startIndex, endIndex - startIndex).Replace(" ", "");
  384. foreach (var data in valueList)
  385. {
  386. foreach (var ovDic in data)
  387. {
  388. var check = ovDic.Key.Replace(" ", "");
  389. var content = ovDic.Value;
  390. if (tempStr.IndexOf(check, StringComparison.Ordinal) <= -1)
  391. {
  392. if (cover)
  393. {
  394. //直接覆盖
  395. templateStr = templateStr.Substring(0, startIndex + startStr.Length) + content + templateStr.Substring(endIndex);
  396. }
  397. else
  398. {
  399. //添加到最后
  400. templateStr = templateStr.Insert(endIndex, content);
  401. }
  402. }
  403. }
  404. }
  405. }
  406. return templateStr;
  407. }
  408. }
  409. }