create-file.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. // @ts-check
  2. import { readFileByLine } from './fetch-remote-text-by-line';
  3. import { surgeDomainsetToClashDomainset, surgeRulesetToClashClassicalTextRuleset } from './clash';
  4. export async function compareAndWriteFile(linesA: string[], filePath: string) {
  5. let isEqual = true;
  6. const file = Bun.file(filePath);
  7. const linesALen = linesA.length;
  8. if (!(await file.exists())) {
  9. console.log(`${filePath} does not exists, writing...`);
  10. isEqual = false;
  11. } else if (linesALen === 0) {
  12. console.log(`Nothing to write to ${filePath}...`);
  13. isEqual = false;
  14. } else {
  15. let index = 0;
  16. for await (const lineB of readFileByLine(file)) {
  17. const lineA = linesA[index];
  18. index++;
  19. if (typeof lineA !== 'string') {
  20. // The file becomes smaller
  21. isEqual = false;
  22. break;
  23. }
  24. if (lineA[0] === '#' && lineB[0] === '#') {
  25. continue;
  26. }
  27. if (lineA !== lineB) {
  28. isEqual = false;
  29. break;
  30. }
  31. }
  32. if (index !== linesALen) {
  33. // The file becomes larger
  34. isEqual = false;
  35. }
  36. }
  37. if (isEqual) {
  38. console.log(`Same Content, bail out writing: ${filePath}`);
  39. return;
  40. }
  41. console.log(`Writing ${filePath}...`);
  42. const start = Bun.nanoseconds();
  43. if (linesALen < 10000) {
  44. await Bun.write(file, `${linesA.join('\n')}\n`);
  45. } else {
  46. const writer = file.writer();
  47. for (let i = 0; i < linesALen; i++) {
  48. writer.write(linesA[i]);
  49. writer.write('\n');
  50. }
  51. writer.flush();
  52. await writer.end();
  53. }
  54. console.log(`Done writing ${filePath} in ${(Bun.nanoseconds() - start) / 1e6}ms`);
  55. }
  56. export const withBannerArray = (title: string, description: string[], date: Date, content: string[]) => {
  57. return [
  58. '########################################',
  59. `# ${title}`,
  60. `# Last Updated: ${date.toISOString()}`,
  61. `# Size: ${content.length}`,
  62. ...description.map(line => (line ? `# ${line}` : '#')),
  63. '########################################',
  64. ...content,
  65. '################# END ###################'
  66. ];
  67. };
  68. export const createRuleset = (
  69. title: string, description: string[], date: Date, content: string[],
  70. type: 'ruleset' | 'domainset', surgePath: string, clashPath: string
  71. ) => {
  72. const surgeContent = withBannerArray(title, description, date, content);
  73. let _clashContent;
  74. switch (type) {
  75. case 'domainset':
  76. _clashContent = surgeDomainsetToClashDomainset(content);
  77. break;
  78. case 'ruleset':
  79. _clashContent = surgeRulesetToClashClassicalTextRuleset(content);
  80. break;
  81. default:
  82. throw new TypeError(`Unknown type: ${type as any}`);
  83. }
  84. const clashContent = withBannerArray(title, description, date, _clashContent);
  85. return [
  86. compareAndWriteFile(surgeContent, surgePath),
  87. compareAndWriteFile(clashContent, clashPath)
  88. ];
  89. };