create-file.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. // @ts-check
  2. const fs = require('fs');
  3. const { readFileByLine } = require('./fetch-remote-text-by-line');
  4. const { surgeDomainsetToClashDomainset, surgeRulesetToClashClassicalTextRuleset } = require('./clash');
  5. /**
  6. * @param {string[]} linesA
  7. * @param {string} filePath
  8. */
  9. async function compareAndWriteFile(linesA, filePath) {
  10. let isEqual = true;
  11. if (!fs.existsSync(filePath)) {
  12. console.log(`${filePath} does not exists, writing...`);
  13. isEqual = false;
  14. } else if (linesA.length === 0) {
  15. console.log(`Nothing to write to ${filePath}...`);
  16. isEqual = false;
  17. } else {
  18. let index = 0;
  19. for await (const lineB of readFileByLine(filePath)) {
  20. const lineA = linesA[index];
  21. index++;
  22. if (lineA === undefined) {
  23. // The file becomes smaller
  24. isEqual = false;
  25. break;
  26. }
  27. if (lineA[0] === '#' && lineB[0] === '#') {
  28. continue;
  29. }
  30. if (lineA !== lineB) {
  31. isEqual = false;
  32. break;
  33. }
  34. }
  35. if (index !== linesA.length) {
  36. isEqual = false;
  37. }
  38. }
  39. if (!isEqual) {
  40. const stream = fs.createWriteStream(filePath, { encoding: 'utf-8' });
  41. for (let i = 0, len = linesA.length; i < len; i++) {
  42. const p = writeToStream(stream, `${linesA[i]}\n`);
  43. if (p) {
  44. // eslint-disable-next-line no-await-in-loop -- backpressure, besides we only wait for drain
  45. await p;
  46. }
  47. }
  48. stream.end();
  49. } else {
  50. console.log(`Same Content, bail out writing: ${filePath}`);
  51. }
  52. }
  53. module.exports.compareAndWriteFile = compareAndWriteFile;
  54. /**
  55. * @param {import('fs').WriteStream} stream
  56. * @param {string} data
  57. */
  58. function writeToStream(stream, data) {
  59. if (!stream.write(data)) {
  60. return /** @type {Promise<void>} */(new Promise((resolve) => {
  61. stream.once('drain', resolve);
  62. }));
  63. }
  64. return null;
  65. }
  66. /**
  67. * @param {string} title
  68. * @param {string[]} description
  69. * @param {Date} date
  70. * @param {string[]} content
  71. * @returns {string[]}
  72. */
  73. const withBannerArray = (title, description, date, content) => {
  74. return [
  75. '########################################',
  76. `# ${title}`,
  77. `# Last Updated: ${date.toISOString()}`,
  78. `# Size: ${content.length}`,
  79. ...description.map(line => (line ? `# ${line}` : '#')),
  80. '########################################',
  81. ...content,
  82. '################# END ###################'
  83. ];
  84. };
  85. module.exports.withBannerArray = withBannerArray;
  86. /**
  87. * @param {string} title
  88. * @param {string[]} description
  89. * @param {Date} date
  90. * @param {string[]} content
  91. * @param {'ruleset' | 'domainset'} type
  92. * @param {string} surgePath
  93. * @param {string} clashPath
  94. */
  95. const createRuleset = (
  96. title, description, date, content,
  97. type, surgePath, clashPath
  98. ) => {
  99. const surgeContent = withBannerArray(title, description, date, content);
  100. let _clashContent;
  101. switch (type) {
  102. case 'domainset':
  103. _clashContent = surgeDomainsetToClashDomainset(content);
  104. break;
  105. case 'ruleset':
  106. _clashContent = surgeRulesetToClashClassicalTextRuleset(content);
  107. break;
  108. default:
  109. throw new TypeError(`Unknown type: ${type}`);
  110. }
  111. const clashContent = withBannerArray(title, description, date, _clashContent);
  112. return [
  113. compareAndWriteFile(surgeContent, surgePath),
  114. compareAndWriteFile(clashContent, clashPath)
  115. ];
  116. };
  117. module.exports.createRuleset = createRuleset;