create-file.ts 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. // @ts-check
  2. import { readFileByLine } from './fetch-text-by-line';
  3. import { surgeDomainsetToClashDomainset, surgeRulesetToClashClassicalTextRuleset } from './clash';
  4. import { traceAsync } from './trace-runner';
  5. import picocolors from 'picocolors';
  6. import type { Span } from '../trace';
  7. import path from 'path';
  8. export async function compareAndWriteFile(span: Span, linesA: string[], filePath: string) {
  9. let isEqual = true;
  10. const file = Bun.file(filePath);
  11. const linesALen = linesA.length;
  12. if (!(await file.exists())) {
  13. console.log(`${filePath} does not exists, writing...`);
  14. isEqual = false;
  15. } else if (linesALen === 0) {
  16. console.log(`Nothing to write to ${filePath}...`);
  17. isEqual = false;
  18. } else {
  19. isEqual = await span.traceChild(`comparing ${filePath}`).traceAsyncFn(async () => {
  20. let index = 0;
  21. for await (const lineB of readFileByLine(file)) {
  22. const lineA = linesA[index];
  23. index++;
  24. if (lineA == null) {
  25. // The file becomes smaller
  26. return false;
  27. }
  28. if (lineA[0] === '#' && lineB[0] === '#') {
  29. continue;
  30. }
  31. if (
  32. lineA[0] === '/'
  33. && lineA[1] === '/'
  34. && lineA[3] === '#'
  35. && lineB[0] === '/'
  36. && lineB[1] === '/'
  37. && lineB[3] === '#'
  38. ) {
  39. continue;
  40. }
  41. if (lineA !== lineB) {
  42. return false;
  43. }
  44. }
  45. if (index !== linesALen) {
  46. // The file becomes larger
  47. return false;
  48. }
  49. return true;
  50. });
  51. }
  52. if (isEqual) {
  53. console.log(picocolors.dim(`same content, bail out writing: ${filePath}`));
  54. return;
  55. }
  56. await span.traceChild(`writing ${filePath}`).traceAsyncFn(async () => {
  57. if (linesALen < 10000) {
  58. return Bun.write(file, `${linesA.join('\n')}\n`);
  59. }
  60. const writer = file.writer();
  61. for (let i = 0; i < linesALen; i++) {
  62. writer.write(linesA[i]);
  63. writer.write('\n');
  64. }
  65. return writer.end();
  66. });
  67. }
  68. export const withBannerArray = (title: string, description: string[] | readonly string[], date: Date, content: string[]) => {
  69. return [
  70. '#########################################',
  71. `# ${title}`,
  72. `# Last Updated: ${date.toISOString()}`,
  73. `# Size: ${content.length}`,
  74. ...description.map(line => (line ? `# ${line}` : '#')),
  75. '#########################################',
  76. ...content,
  77. '################## EOF ##################'
  78. ];
  79. };
  80. export const createRuleset = (
  81. parentSpan: Span,
  82. title: string, description: string[] | readonly string[], date: Date, content: string[],
  83. type: 'ruleset' | 'domainset', surgePath: string, clashPath: string
  84. ) => parentSpan.traceChild(`create ruleset: ${path.basename(surgePath, path.extname(surgePath))}`).traceAsyncFn((childSpan) => {
  85. const surgeContent = withBannerArray(title, description, date, content);
  86. const clashContent = childSpan.traceChild('convert incoming ruleset to clash').traceSyncFn(() => {
  87. let _clashContent;
  88. switch (type) {
  89. case 'domainset':
  90. _clashContent = surgeDomainsetToClashDomainset(content);
  91. break;
  92. case 'ruleset':
  93. _clashContent = surgeRulesetToClashClassicalTextRuleset(content);
  94. break;
  95. default:
  96. throw new TypeError(`Unknown type: ${type as any}`);
  97. }
  98. return withBannerArray(title, description, date, _clashContent);
  99. });
  100. return Promise.all([
  101. compareAndWriteFile(childSpan, surgeContent, surgePath),
  102. compareAndWriteFile(childSpan, clashContent, clashPath)
  103. ]);
  104. });