create-file.js 3.0 KB

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