create-file.js 3.1 KB

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