| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- // @ts-check
- import { readFileByLine } from './fetch-text-by-line';
- import { surgeDomainsetToClashDomainset, surgeRulesetToClashClassicalTextRuleset } from './clash';
- import picocolors from 'picocolors';
- import type { Span } from '../trace';
- import path from 'path';
- export async function compareAndWriteFile(span: Span, linesA: string[], filePath: string) {
- let isEqual = true;
- const file = Bun.file(filePath);
- const linesALen = linesA.length;
- if (!(await file.exists())) {
- console.log(`${filePath} does not exists, writing...`);
- isEqual = false;
- } else if (linesALen === 0) {
- console.log(`Nothing to write to ${filePath}...`);
- isEqual = false;
- } else {
- /* The `isEqual` variable is used to determine whether the content of a file is equal to the
- provided lines or not. It is initially set to `true`, and then it is updated based on different
- conditions: */
- isEqual = await span.traceChildAsync(`comparing ${filePath}`, async () => {
- let index = 0;
- for await (const lineB of readFileByLine(file)) {
- const lineA = linesA[index];
- index++;
- if (lineA == null) {
- // The file becomes smaller
- return false;
- }
- if (lineA[0] === '#' && lineB[0] === '#') {
- continue;
- }
- if (
- lineA[0] === '/'
- && lineA[1] === '/'
- && lineB[0] === '/'
- && lineB[1] === '/'
- && lineA[3] === '#'
- && lineB[3] === '#'
- ) {
- continue;
- }
- if (lineA !== lineB) {
- return false;
- }
- }
- if (index !== linesALen) {
- // The file becomes larger
- return false;
- }
- return true;
- });
- }
- if (isEqual) {
- console.log(picocolors.dim(`same content, bail out writing: ${filePath}`));
- return;
- }
- await span.traceChildAsync(`writing ${filePath}`, async () => {
- // if (linesALen < 10000) {
- return Bun.write(file, `${linesA.join('\n')}\n`);
- // }
- // const writer = file.writer();
- // for (let i = 0; i < linesALen; i++) {
- // writer.write(linesA[i]);
- // writer.write('\n');
- // }
- // return writer.end();
- });
- }
- export const withBannerArray = (title: string, description: string[] | readonly string[], date: Date, content: string[]) => {
- return [
- '#########################################',
- `# ${title}`,
- `# Last Updated: ${date.toISOString()}`,
- `# Size: ${content.length}`,
- ...description.map(line => (line ? `# ${line}` : '#')),
- '#########################################',
- ...content,
- '################## EOF ##################'
- ];
- };
- const collectType = (rule: string) => {
- let buf = '';
- for (let i = 0, len = rule.length; i < len; i++) {
- if (rule[i] === ',') {
- return buf;
- }
- buf += rule[i];
- }
- return null;
- };
- const defaultSortTypeOrder = Symbol('defaultSortTypeOrder');
- const sortTypeOrder: Record<string | typeof defaultSortTypeOrder, number> = {
- DOMAIN: 1,
- 'DOMAIN-SUFFIX': 2,
- 'DOMAIN-KEYWORD': 10,
- // experimental domain wildcard support
- 'DOMAIN-WILDCARD': 20,
- 'USER-AGENT': 30,
- 'PROCESS-NAME': 40,
- [defaultSortTypeOrder]: 50, // default sort order for unknown type
- AND: 100,
- OR: 100,
- 'IP-CIDR': 200,
- 'IP-CIDR6': 200
- };
- // sort DOMAIN-SUFFIX and DOMAIN first, then DOMAIN-KEYWORD, then IP-CIDR and IP-CIDR6 if any
- export const sortRuleSet = (ruleSet: string[]) => ruleSet
- .map((rule) => {
- const type = collectType(rule);
- return [type ? (type in sortTypeOrder ? sortTypeOrder[type] : sortTypeOrder[defaultSortTypeOrder]) : 10, rule] as const;
- })
- .sort((a, b) => a[0] - b[0])
- .map(c => c[1]);
- const MARK = 'this_ruleset_is_made_by_sukkaw.ruleset.skk.moe';
- export const createRuleset = (
- parentSpan: Span,
- title: string, description: string[] | readonly string[], date: Date, content: string[],
- type: 'ruleset' | 'domainset', surgePath: string, clashPath: string
- ) => parentSpan.traceChild(`create ruleset: ${path.basename(surgePath, path.extname(surgePath))}`).traceAsyncFn((childSpan) => {
- const surgeContent = withBannerArray(
- title, description, date,
- sortRuleSet(type === 'domainset'
- ? [MARK, ...content]
- : [`DOMAIN,${MARK}`, ...content])
- );
- const clashContent = childSpan.traceChildSync('convert incoming ruleset to clash', () => {
- let _clashContent;
- switch (type) {
- case 'domainset':
- _clashContent = [MARK, ...surgeDomainsetToClashDomainset(content)];
- break;
- case 'ruleset':
- _clashContent = [`DOMAIN,${MARK}`, ...surgeRulesetToClashClassicalTextRuleset(content)];
- break;
- default:
- throw new TypeError(`Unknown type: ${type as any}`);
- }
- return withBannerArray(title, description, date, _clashContent);
- });
- return Promise.all([
- compareAndWriteFile(childSpan, surgeContent, surgePath),
- compareAndWriteFile(childSpan, clashContent, clashPath)
- ]);
- });
|