Sources.ts 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. import { execSync } from 'child_process';
  2. import os from 'os';
  3. import fs from 'fs';
  4. import * as path from 'path';
  5. import Progress from '../util/Progress';
  6. import Analyzer from './Analyzer';
  7. type Z8ClassProperties = {
  8. static: boolean,
  9. type: string,
  10. key: string,
  11. value: string,
  12. inherited: boolean,
  13. dynamic: boolean,
  14. overridden: boolean,
  15. nearestParent?: string,
  16. nearestParentRoot?: string
  17. };
  18. type ClassMapEntity = {
  19. name: string,
  20. root: string,
  21. filePath: string,
  22. shortName?: string | null,
  23. extends: string | null,
  24. children: string[],
  25. parentsBranch: string[],
  26. mixins: string[],
  27. mixedIn: string[],
  28. statics: Z8ClassProperties[],
  29. properties: Z8ClassProperties[],
  30. dynamicProperties: Z8ClassProperties[],
  31. isPackage: false
  32. };
  33. type ClassMap = {
  34. [key: string]: ClassMapEntity
  35. };
  36. type PackageEntity = {
  37. name: string,
  38. root: string,
  39. isPackage: true
  40. };
  41. class Sources {
  42. private static repoNames: string[] = [];
  43. private static repoUrls: string[] = [];
  44. private static repoSourcePaths: string[] = [];
  45. private static repoSourceCopyPaths: string[] = [];
  46. private static repoSparseCheckoutPaths: string[] = [];
  47. private static repoCollectFromPaths: string[] = [];
  48. public static sourcesPath: string = `${os.homedir()}/.doczilla_js_docs`;
  49. public static sourcesCopyPath = `${Sources.sourcesPath}/JsSources`;
  50. public static dzAppPath = `${Sources.sourcesCopyPath}/DZApp.js`;
  51. public static classMapPath = `${Sources.sourcesCopyPath}/ClassMap.json`;
  52. public static ClassMap: ClassMap = {};
  53. public static Z8Locales: { [key: string]: { [key: string]: string } } = {};
  54. private static init(): void {
  55. // refresh
  56. Sources.sourcesPath = '';
  57. Sources.repoNames = [];
  58. Sources.repoUrls = [];
  59. Sources.repoSourcePaths = [];
  60. Sources.repoSourceCopyPaths = [];
  61. Sources.repoSparseCheckoutPaths = [];
  62. Sources.repoCollectFromPaths = [];
  63. const SourcesConfig = JSON.parse(fs.readFileSync(path.resolve(__dirname,'../config.json'), { encoding: 'utf-8' }).toString()).sources;
  64. Sources.sourcesPath = `${os.homedir()}/${SourcesConfig.assetsDir}`;
  65. for(const repoInfo of SourcesConfig.repos) {
  66. Sources.repoNames.push(repoInfo.name);
  67. Sources.repoUrls.push(repoInfo.url);
  68. Sources.repoSourcePaths.push(`${Sources.sourcesPath}/${repoInfo.name}`);
  69. Sources.repoSourceCopyPaths.push(`${Sources.sourcesCopyPath}/${repoInfo.name}`);
  70. Sources.repoSparseCheckoutPaths.push(repoInfo.sparseCheckout);
  71. Sources.repoCollectFromPaths.push(repoInfo.collectFrom);
  72. }
  73. }
  74. public static get(callback?: () => any): ClassMap {
  75. if(fs.existsSync(Sources.classMapPath)) {
  76. Sources.ClassMap = JSON.parse(fs.readFileSync(Sources.classMapPath).toString());
  77. Sources.repoNames = JSON.parse(fs.readFileSync(path.resolve(__dirname,'../config.json'), { encoding: 'utf-8' }).toString()).sources.repos.map((repo: { name: string; }) => repo.name);
  78. Analyzer.analyze(null, callback, true);
  79. } else {
  80. Sources.update(callback);
  81. }
  82. return Sources.ClassMap;
  83. }
  84. public static getShortenedClassMap(): { [key: string]: { shortName: string | null, filePath: string, name: string } } {
  85. const classList: { [key: string]: { shortName: string | null, filePath: string, name: string } } = {};
  86. Object.keys(Sources.ClassMap).map((cls) => {
  87. const scls = Sources.ClassMap[cls];
  88. classList[cls] = { name: cls, shortName: scls.shortName || null, filePath: scls.filePath };
  89. });
  90. return classList;
  91. }
  92. public static getRepoNames(): string[] {
  93. return Sources.repoNames;
  94. }
  95. public static time(): Date {
  96. return fs.statSync(Sources.classMapPath).birthtime;
  97. }
  98. public static findClass(className: string): ClassMapEntity | null {
  99. if(Sources.ClassMap[className])
  100. return Sources.ClassMap[className];
  101. const keys = Object.keys(Sources.ClassMap).filter((key) => {
  102. return Sources.ClassMap[key].shortName === className;
  103. });
  104. if(keys.length === 1)
  105. return Sources.ClassMap[keys[0]];
  106. return null;
  107. }
  108. public static findPackage(root: string, packageName: string): PackageEntity | null {
  109. const regex = /^(?!.*\.\.)(?!\.)[a-zA-Zа-яА-Я0-9.]+(?<!\.)$/;
  110. if(!regex.test(packageName))
  111. return null;
  112. const keys = Object.keys(Sources.ClassMap).filter((key) => {
  113. return key.startsWith(packageName) && key !== packageName && Sources.ClassMap[key].root === root;
  114. });
  115. if(keys.length > 0)
  116. return { name: packageName, root: root, isPackage: true };
  117. return null;
  118. }
  119. public static update(callback?: () => any, silent?: boolean): void {
  120. Sources.init();
  121. Sources.clone(silent);
  122. Sources.copy(silent);
  123. Sources.collect(silent);
  124. Analyzer.analyze(Sources.ClassMap, callback, silent);
  125. }
  126. private static clone(silent?: boolean): void {
  127. if(!fs.existsSync(Sources.sourcesPath))
  128. fs.mkdirSync(Sources.sourcesPath);
  129. const progress = silent ? null : Progress.start('Sources update, Step 1 [Clone/Pull]:', Sources.repoNames.length);
  130. for(let i = 0; i < Sources.repoNames.length; i++) {
  131. if(fs.existsSync(Sources.repoSourcePaths[i])) {
  132. execSync('git pull', { encoding: 'utf8', cwd: Sources.repoSourcePaths[i], stdio: [] });
  133. progress && progress.next();
  134. } else {
  135. execSync(Sources.getCloneCommand(Sources.repoUrls[i]), { encoding: 'utf8', cwd: Sources.sourcesPath, stdio: [] });
  136. execSync(`git sparse-checkout set ${Sources.repoSparseCheckoutPaths[i]}`, { encoding: 'utf8', cwd: Sources.repoSourcePaths[i], stdio: [] });
  137. progress && progress.next();
  138. }
  139. }
  140. progress && progress.finish();
  141. }
  142. private static copy(silent?: boolean): void {
  143. if(fs.existsSync(Sources.sourcesCopyPath))
  144. fs.rmSync(Sources.sourcesCopyPath, { recursive: true, force: true });
  145. fs.mkdirSync(Sources.sourcesCopyPath);
  146. const progress = silent ? null : Progress.start('Sources update, Step 2 [Copy]:', Sources.repoNames.length);
  147. for(let i = 0; i < Sources.repoNames.length; i++) {
  148. fs.mkdirSync(Sources.repoSourceCopyPaths[i]);
  149. fs.cpSync(`${Sources.repoSourcePaths[i]}/${Sources.repoCollectFromPaths[i]}`, Sources.repoSourceCopyPaths[i], { recursive: true });
  150. progress && progress.next();
  151. }
  152. progress && progress.finish();
  153. }
  154. private static collect(silent?: boolean): void {
  155. if(fs.existsSync(Sources.dzAppPath))
  156. fs.rmSync(Sources.dzAppPath, { force: true });
  157. Sources.ClassMap = {};
  158. const buildorders = [];
  159. let totalBuildordersLength = 0;
  160. for(let i = 0; i < Sources.repoNames.length; i++) {
  161. const bo = fs.readFileSync(`${Sources.repoSourceCopyPaths[i]}/.buildorder`, 'utf8').toString().replaceAll('\r', '').split('\n').filter((str) => { return str.length > 0; });
  162. buildorders.push(bo);
  163. totalBuildordersLength += bo.length;
  164. }
  165. const progress = silent ? null : Progress.start('Sources update, Step 3 [Collect]:', totalBuildordersLength);
  166. for(let i = 0; i < Sources.repoNames.length; i++) {
  167. Sources.processBuildorder(buildorders[i], Sources.repoSourceCopyPaths[i], Sources.repoNames[i], progress);
  168. }
  169. progress && progress.finish();
  170. }
  171. private static processBuildorder(buildorder: string[], sourcesRoot: string, pathPrefix: string, progress: Progress | null): void {
  172. const z8DefinePattern: RegExp = /Z8\.define\('([^']+)'/g;
  173. const dynamicPropertiesPattern: RegExp = /this\.([\wа-яА-Я_$]+)\s*=;/g;
  174. const dynamicConfigPropertiesPattern: RegExp = /this\.([\wа-яА-Я_$]+)/g;
  175. for(const filePath of buildorder) {
  176. const content = fs.readFileSync(`${sourcesRoot}/${filePath}`, 'utf8').toString();
  177. const defines: string[] = Array.from(content.matchAll(z8DefinePattern), match => match[1]);
  178. for(const define of defines) {
  179. const classRx = new RegExp(`Z8\\.define\\(\'${define}\',\\s*\\{(?:.|[\r\n])+?^\\}\\);?`, 'gm');
  180. const classContentRxArray = content.match(classRx); // change to `classContent = content.match(classRx)![0]` after `}\n);` issue fixed
  181. const classContent = classContentRxArray ? classContentRxArray[0] : '';
  182. const dynamicProperties = Sources.cleanDuplicates(Array.from(classContent.matchAll(dynamicPropertiesPattern), (match) => {
  183. return {
  184. static: false,
  185. type: 'undefined',
  186. key: match[1],
  187. value: '',
  188. inherited: false,
  189. dynamic: true,
  190. overridden: false
  191. };
  192. }));
  193. const dynamicConfigProperties = Sources.cleanDuplicates(Array.from(classContent.matchAll(dynamicConfigPropertiesPattern), (match) => {
  194. return {
  195. static: false,
  196. type: 'undefined',
  197. key: match[1],
  198. value: '',
  199. inherited: false,
  200. dynamic: true,
  201. overridden: false
  202. };
  203. }).filter(item1 => !dynamicProperties.some(item2 => item2.key === item1.key)));
  204. Sources.ClassMap[define] = { name: define, root: pathPrefix, filePath: `${pathPrefix}/${filePath}`, children: [], extends: null, parentsBranch: [], mixins: [], mixedIn: [], properties: dynamicProperties, dynamicProperties: dynamicConfigProperties, statics: [], isPackage: false };
  205. }
  206. fs.writeFileSync(Sources.dzAppPath, content + '\n', { flag: 'a+', encoding: 'utf8' });
  207. progress && progress.next();
  208. }
  209. }
  210. private static getCloneCommand(repoUrl: string): string {
  211. return `git clone --depth=1 --single-branch --branch dev --filter=blob:none --sparse ${repoUrl}`;
  212. }
  213. private static cleanDuplicates(array: Z8ClassProperties[]): Z8ClassProperties[] {
  214. for(let i = array.length - 1; i >= 0; i--) {
  215. const currentItem = array[i];
  216. const duplicateIndex = array.findIndex((item, index) => index !== i && item.key === currentItem.key);
  217. if (duplicateIndex !== -1) {
  218. array.splice(i, 1);
  219. }
  220. }
  221. return array;
  222. }
  223. }
  224. export { Sources, ClassMap, ClassMapEntity, PackageEntity, Z8ClassProperties };