Update list 20241123_170846
[Adblock-list-backups-Frellwits-filter-lists.git] / tools / make-easylist.mjs
blobb6483636ad58c8d689a80d1db6e2c2d158f64892
1 /*******************************************************************************
3     uBlock Origin - a browser extension to block requests.
4     Copyright (C) 2022-present Raymond Hill
6     This program is free software: you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation, either version 3 of the License, or
9     (at your option) any later version.
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
16     You should have received a copy of the GNU General Public License
17     along with this program.  If not, see {http://www.gnu.org/licenses/}.
19     Home: https://github.com/gorhill/uBlock
22 // jshint node:true, esversion:9
24 'use strict';
26 /******************************************************************************/
28 import fs from 'fs/promises';
29 import path from 'path';
30 import process from 'process';
32 /******************************************************************************/
34 const expandedParts = new Set();
36 /******************************************************************************/
38 const commandLineArgs = (( ) => {
39     const args = new Map();
40     let name, value;
41     for ( const arg of process.argv.slice(2) ) {
42         const pos = arg.indexOf('=');
43         if ( pos === -1 ) {
44             name = arg;
45             value = '';
46         } else {
47             name = arg.slice(0, pos);
48             value = arg.slice(pos+1);
49         }
50         args.set(name, value);
51     }
52     return args;
53 })();
55 /******************************************************************************/
57 function expandTemplate(wd, parts) {
58     const out = [];
59     const reInclude = /^%include +(.+):(.+)%\s+/gm;
60     const trim = text => trimSublist(text);
61     for ( const part of parts ) {
62         if ( typeof part !== 'string' ) {
63             out.push(part);
64             continue;
65         }
66         let lastIndex = 0;
67         for (;;) {
68             const match = reInclude.exec(part);
69             if ( match === null ) { break; }
70             out.push(part.slice(lastIndex, match.index).trim());
71             const repo = match[1].trim();
72             const fpath = `${match[2].trim()}`;
73             if ( expandedParts.has(fpath) === false ) {
74                 console.info(`  Inserting ${fpath}`);
75                 out.push(
76                     out.push({ file: `${fpath}` }),
77                     `! *** ${repo}:${fpath} ***`,
78                     fs.readFile(`${wd}/${fpath}`, { encoding: 'utf8' })
79                         .then(text => fpath.includes('header') ? text : trim(text)),
80                 );
81                 expandedParts.add(fpath);
82             }
83             lastIndex = reInclude.lastIndex;
84         }
85         out.push(part.slice(lastIndex).trim());
86     }
87     return out;
90 /******************************************************************************/
92 function expandIncludeDirectives(wd, parts) {
93     const out = [];
94     const reInclude = /^!#include (.+)\s*/gm;
95     const trim = text => trimSublist(text);
96     let parentPath = '';
97     for ( const part of parts ) {
98         if ( typeof part !== 'string' ) {
99             if ( typeof part === 'object' && part.file !== undefined ) {
100                 parentPath = part.file;
101             }
102             out.push(part);
103             continue;
104         }
105         let lastIndex = 0;
106         for (;;) {
107             const match = reInclude.exec(part);
108             if ( match === null ) { break; }
109             out.push(part.slice(lastIndex, match.index).trim());
110             const fpath = `${path.dirname(parentPath)}/${match[1].trim()}`;
111             if ( expandedParts.has(fpath) === false ) {
112                 console.info(`  Inserting ${fpath}`);
113                 out.push(
114                     { file: fpath },
115                     `! *** ${fpath} ***`,
116                     fs.readFile(`${wd}/${fpath}`, { encoding: 'utf8' })
117                         .then(text => fpath.includes('header') ? text : trim(text)),
118                 );
119                 expandedParts.add(fpath);
120             }
121             lastIndex = reInclude.lastIndex;
122         }
123         out.push(part.slice(lastIndex).trim());
124     }
125     return out;
128 /******************************************************************************/
130 function trimSublist(text) {
131     // Remove empty comment lines
132     text = text.replace(/^!\s*$(?:\r\n|\n)/gm, '');
133     // Remove sublist header information: the importing list will provide its
134     // own header.
135     text = text.trim().replace(/^(?:!\s+[^\r\n]+?(?:\r\n|\n))+/s, '');
136     return text;
139 /******************************************************************************/
141 function minify(text) {
142     // remove issue-related comments
143     text = text.replace(/^! https:\/\/.*?[\n\r]+/gm, '');
144     // remove empty lines
145     text = text.replace(/^[\n\r]+/gm, '');
146     // convert potentially present Windows-style newlines
147     text = text.replace(/\r\n/g, '\n');
148     return text;
151 /******************************************************************************/
153 function assemble(parts) {
154     const out = [];
155     for ( const part of parts ) {
156         if ( typeof part !== 'string' ) { continue; }
157         out.push(part);
158     }
159     return out.join('\n').trim() + '\n';
162 /******************************************************************************/
164 async function main() {
165     const workingDir = commandLineArgs.get('dir') || '.';
166     const inFile = commandLineArgs.get('in');
167     if ( typeof inFile !== 'string' || inFile === '' ) {
168         process.exit(1);
169     }
170     const outFile = commandLineArgs.get('out');
171     if ( typeof outFile !== 'string' || outFile === '' ) {
172         process.exit(1);
173     }
175     console.info(`  Using template at ${inFile}`);
177     const inText = fs.readFile(`${workingDir}/${inFile}`, { encoding: 'utf8' });
179     let parts = [ inText ];
180     do {
181         parts = await Promise.all(parts);
182         parts = expandTemplate(workingDir, parts);
183     } while ( parts.some(v => v instanceof Promise) );
185     do {
186         parts = await Promise.all(parts);
187         parts = expandIncludeDirectives(workingDir, parts);
188     } while ( parts.some(v => v instanceof Promise));
190     let afterText = assemble(parts);
192     if ( commandLineArgs.get('minify') !== undefined ) {
193         afterText = minify(afterText);
194     }
196     console.info(`  Creating ${outFile}`);
198     fs.writeFile(outFile, afterText);
201 main();