create-file.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  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. export async function compareAndWriteFile(linesA: string[], filePath: string) {
  7. let isEqual = true;
  8. const file = Bun.file(filePath);
  9. const linesALen = linesA.length;
  10. if (!(await file.exists())) {
  11. console.log(`${filePath} does not exists, writing...`);
  12. isEqual = false;
  13. } else if (linesALen === 0) {
  14. console.log(`Nothing to write to ${filePath}...`);
  15. isEqual = false;
  16. } else {
  17. isEqual = await traceAsync(
  18. picocolors.gray(`Comparing ${filePath}`),
  19. 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. picocolors.gray
  52. );
  53. }
  54. if (isEqual) {
  55. console.log(picocolors.gray(`Same Content, bail out writing: ${filePath}`));
  56. return;
  57. }
  58. await traceAsync(picocolors.gray(`Writing ${filePath}`), async () => {
  59. if (linesALen < 10000) {
  60. return Bun.write(file, `${linesA.join('\n')}\n`);
  61. }
  62. const writer = file.writer();
  63. for (let i = 0; i < linesALen; i++) {
  64. writer.write(linesA[i]);
  65. writer.write('\n');
  66. }
  67. return writer.end();
  68. }, picocolors.gray);
  69. }
  70. export const withBannerArray = (title: string, description: string[] | readonly string[], date: Date, content: string[]) => {
  71. return [
  72. '#########################################',
  73. `# ${title}`,
  74. `# Last Updated: ${date.toISOString()}`,
  75. `# Size: ${content.length}`,
  76. ...description.map(line => (line ? `# ${line}` : '#')),
  77. '#########################################',
  78. ...content,
  79. '################## EOF ##################'
  80. ];
  81. };
  82. export const createRuleset = (
  83. title: string, description: string[] | readonly string[], date: Date, content: string[],
  84. type: 'ruleset' | 'domainset', surgePath: string, clashPath: string
  85. ) => {
  86. const surgeContent = withBannerArray(title, description, date, content);
  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. const clashContent = withBannerArray(title, description, date, _clashContent);
  99. return [
  100. compareAndWriteFile(surgeContent, surgePath),
  101. compareAndWriteFile(clashContent, clashPath)
  102. ];
  103. };