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
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();
41 for ( const arg of process.argv.slice(2) ) {
42 const pos = arg.indexOf('=');
47 name = arg.slice(0, pos);
48 value = arg.slice(pos+1);
50 args.set(name, value);
55 /******************************************************************************/
57 function expandTemplate(wd, parts) {
59 const reInclude = /^%include +(.+):(.+)%\s+/gm;
60 const trim = text => trimSublist(text);
61 for ( const part of parts ) {
62 if ( typeof part !== 'string' ) {
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}`);
76 out.push({ file: `${fpath}` }),
77 `! *** ${repo}:${fpath} ***`,
78 fs.readFile(`${wd}/${fpath}`, { encoding: 'utf8' })
79 .then(text => fpath.includes('header') ? text : trim(text)),
81 expandedParts.add(fpath);
83 lastIndex = reInclude.lastIndex;
85 out.push(part.slice(lastIndex).trim());
90 /******************************************************************************/
92 function expandIncludeDirectives(wd, parts) {
94 const reInclude = /^!#include (.+)\s*/gm;
95 const trim = text => trimSublist(text);
97 for ( const part of parts ) {
98 if ( typeof part !== 'string' ) {
99 if ( typeof part === 'object' && part.file !== undefined ) {
100 parentPath = part.file;
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}`);
115 `! *** ${fpath} ***`,
116 fs.readFile(`${wd}/${fpath}`, { encoding: 'utf8' })
117 .then(text => fpath.includes('header') ? text : trim(text)),
119 expandedParts.add(fpath);
121 lastIndex = reInclude.lastIndex;
123 out.push(part.slice(lastIndex).trim());
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
135 text = text.trim().replace(/^(?:!\s+[^\r\n]+?(?:\r\n|\n))+/s, '');
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');
151 /******************************************************************************/
153 function assemble(parts) {
155 for ( const part of parts ) {
156 if ( typeof part !== 'string' ) { continue; }
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 === '' ) {
170 const outFile = commandLineArgs.get('out');
171 if ( typeof outFile !== 'string' || outFile === '' ) {
175 console.info(` Using template at ${inFile}`);
177 const inText = fs.readFile(`${workingDir}/${inFile}`, { encoding: 'utf8' });
179 let parts = [ inText ];
181 parts = await Promise.all(parts);
182 parts = expandTemplate(workingDir, parts);
183 } while ( parts.some(v => v instanceof Promise) );
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);
196 console.info(` Creating ${outFile}`);
198 fs.writeFile(outFile, afterText);