@cspell/cspell-tools 9.5.0 → 9.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin.mjs +1 -1
- package/cspell-tools.config.schema.json +29 -0
- package/dist/app.d.mts +35 -0
- package/dist/app.mjs +292 -0
- package/dist/build-OgMPaXPZ.mjs +1403 -0
- package/dist/index.d.mts +35 -0
- package/dist/index.mjs +3 -0
- package/package.json +10 -9
- package/dist/AppOptions.d.ts +0 -31
- package/dist/AppOptions.js +0 -2
- package/dist/FeatureFlags/FeatureFlags.d.ts +0 -34
- package/dist/FeatureFlags/FeatureFlags.js +0 -94
- package/dist/FeatureFlags/index.d.ts +0 -3
- package/dist/FeatureFlags/index.js +0 -3
- package/dist/FeatureFlags/parseFlags.d.ts +0 -3
- package/dist/FeatureFlags/parseFlags.js +0 -20
- package/dist/app.d.ts +0 -4
- package/dist/app.js +0 -86
- package/dist/build.d.ts +0 -12
- package/dist/build.js +0 -36
- package/dist/compile.d.ts +0 -5
- package/dist/compile.js +0 -41
- package/dist/compiler/CompileOptions.d.ts +0 -35
- package/dist/compiler/CompileOptions.js +0 -2
- package/dist/compiler/CompilerDefinitions.d.ts +0 -2
- package/dist/compiler/CompilerDefinitions.js +0 -2
- package/dist/compiler/Reader.d.ts +0 -3
- package/dist/compiler/Reader.js +0 -24
- package/dist/compiler/SourceReader.d.ts +0 -28
- package/dist/compiler/SourceReader.js +0 -44
- package/dist/compiler/WordsCollection.d.ts +0 -20
- package/dist/compiler/WordsCollection.js +0 -3
- package/dist/compiler/compile.d.ts +0 -26
- package/dist/compiler/compile.js +0 -270
- package/dist/compiler/createCompileRequest.d.ts +0 -4
- package/dist/compiler/createCompileRequest.js +0 -84
- package/dist/compiler/createWordsCollection.d.ts +0 -10
- package/dist/compiler/createWordsCollection.js +0 -111
- package/dist/compiler/index.d.ts +0 -4
- package/dist/compiler/index.js +0 -3
- package/dist/compiler/legacyLineToWords.d.ts +0 -4
- package/dist/compiler/legacyLineToWords.js +0 -20
- package/dist/compiler/logWithTimestamp.d.ts +0 -3
- package/dist/compiler/logWithTimestamp.js +0 -5
- package/dist/compiler/logger.d.ts +0 -4
- package/dist/compiler/logger.js +0 -9
- package/dist/compiler/readers/ReaderOptions.d.ts +0 -19
- package/dist/compiler/readers/ReaderOptions.js +0 -2
- package/dist/compiler/readers/readHunspellFiles.d.ts +0 -3
- package/dist/compiler/readers/readHunspellFiles.js +0 -57
- package/dist/compiler/readers/readTextFile.d.ts +0 -3
- package/dist/compiler/readers/readTextFile.js +0 -20
- package/dist/compiler/readers/regHunspellFile.d.ts +0 -2
- package/dist/compiler/readers/regHunspellFile.js +0 -2
- package/dist/compiler/readers/textFileReader.d.ts +0 -3
- package/dist/compiler/readers/textFileReader.js +0 -11
- package/dist/compiler/readers/trieFileReader.d.ts +0 -3
- package/dist/compiler/readers/trieFileReader.js +0 -16
- package/dist/compiler/splitCamelCaseIfAllowed.d.ts +0 -5
- package/dist/compiler/splitCamelCaseIfAllowed.js +0 -65
- package/dist/compiler/streamSourceWordsFromFile.d.ts +0 -3
- package/dist/compiler/streamSourceWordsFromFile.js +0 -6
- package/dist/compiler/text.d.ts +0 -10
- package/dist/compiler/text.js +0 -28
- package/dist/compiler/wordListCompiler.d.ts +0 -21
- package/dist/compiler/wordListCompiler.js +0 -205
- package/dist/compiler/wordListParser.d.ts +0 -61
- package/dist/compiler/wordListParser.js +0 -184
- package/dist/compiler/writeTextToFile.d.ts +0 -2
- package/dist/compiler/writeTextToFile.js +0 -16
- package/dist/config/config.d.ts +0 -200
- package/dist/config/config.js +0 -2
- package/dist/config/configDefaults.d.ts +0 -9
- package/dist/config/configDefaults.js +0 -9
- package/dist/config/configUtils.d.ts +0 -5
- package/dist/config/configUtils.js +0 -14
- package/dist/config/index.d.ts +0 -4
- package/dist/config/index.js +0 -3
- package/dist/config/normalizeConfig.d.ts +0 -8
- package/dist/config/normalizeConfig.js +0 -13
- package/dist/gzip/compressFiles.d.ts +0 -16
- package/dist/gzip/compressFiles.js +0 -42
- package/dist/gzip/gzip.d.ts +0 -3
- package/dist/gzip/gzip.js +0 -10
- package/dist/gzip/index.d.ts +0 -3
- package/dist/gzip/index.js +0 -3
- package/dist/shasum/checksum.d.ts +0 -7
- package/dist/shasum/checksum.js +0 -19
- package/dist/shasum/index.d.ts +0 -3
- package/dist/shasum/index.js +0 -3
- package/dist/shasum/shasum.d.ts +0 -38
- package/dist/shasum/shasum.js +0 -150
- package/dist/test/TestHelper.d.ts +0 -53
- package/dist/test/TestHelper.js +0 -121
- package/dist/test/console.d.ts +0 -9
- package/dist/test/console.js +0 -34
- package/dist/test/escapeRegEx.d.ts +0 -7
- package/dist/test/escapeRegEx.js +0 -9
- package/dist/test/normalizeOutput.d.ts +0 -3
- package/dist/test/normalizeOutput.js +0 -20
- package/dist/types.d.ts +0 -7
- package/dist/types.js +0 -2
- package/dist/util/errors.d.ts +0 -6
- package/dist/util/errors.js +0 -11
- package/dist/util/globP.d.ts +0 -7
- package/dist/util/globP.js +0 -7
- package/dist/util/index.d.ts +0 -2
- package/dist/util/index.js +0 -4
- package/dist/util/textRegex.d.ts +0 -3
- package/dist/util/textRegex.js +0 -109
|
@@ -0,0 +1,1403 @@
|
|
|
1
|
+
import { promises } from "node:fs";
|
|
2
|
+
import fs, { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import fsPath, { resolve, sep } from "node:path";
|
|
5
|
+
import * as Trie from "cspell-trie-lib";
|
|
6
|
+
import { COMPOUND_FIX, FORBID_PREFIX, createDictionaryLineParser, decodeTrie, encodeTrieDataToBTrie, parseDictionary } from "cspell-trie-lib";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import { gunzip, gunzipSync, gzip } from "node:zlib";
|
|
9
|
+
import { opAppend, opCombine, opConcatMap, opFilter, opMap, pipe } from "@cspell/cspell-pipe/sync";
|
|
10
|
+
import * as HR from "hunspell-reader";
|
|
11
|
+
import { uniqueFilter } from "hunspell-reader";
|
|
12
|
+
import { cosmiconfig } from "cosmiconfig";
|
|
13
|
+
import { pipeAsync, toArray } from "@cspell/cspell-pipe";
|
|
14
|
+
import { opAwaitAsync, opMapAsync } from "@cspell/cspell-pipe/operators";
|
|
15
|
+
import { createHash } from "node:crypto";
|
|
16
|
+
import { Buffer } from "node:buffer";
|
|
17
|
+
|
|
18
|
+
//#region src/gzip/compressFiles.ts
|
|
19
|
+
const gzip$1 = promisify(gzip);
|
|
20
|
+
const gunzip$1 = promisify(gunzip);
|
|
21
|
+
let OSFlags = /* @__PURE__ */ function(OSFlags$1) {
|
|
22
|
+
OSFlags$1[OSFlags$1["auto"] = -1] = "auto";
|
|
23
|
+
OSFlags$1[OSFlags$1["FAT"] = 0] = "FAT";
|
|
24
|
+
OSFlags$1[OSFlags$1["Unix"] = 3] = "Unix";
|
|
25
|
+
OSFlags$1[OSFlags$1["HPFS"] = 6] = "HPFS";
|
|
26
|
+
OSFlags$1[OSFlags$1["MACOS"] = 7] = "MACOS";
|
|
27
|
+
OSFlags$1[OSFlags$1["NTFS"] = 11] = "NTFS";
|
|
28
|
+
return OSFlags$1;
|
|
29
|
+
}({});
|
|
30
|
+
const OSSystemIDOffset = 9;
|
|
31
|
+
async function compressFile(file, os) {
|
|
32
|
+
if (file.endsWith(".gz")) return file;
|
|
33
|
+
const targetFile = file + ".gz";
|
|
34
|
+
await writeFile(targetFile, await compress(await readFile(file), os));
|
|
35
|
+
return targetFile;
|
|
36
|
+
}
|
|
37
|
+
async function compress(buf, os) {
|
|
38
|
+
return fixOSSystemID(await gzip$1(buf), os);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Compresses the data if it is not already compressed.
|
|
42
|
+
* @param data
|
|
43
|
+
* @returns
|
|
44
|
+
*/
|
|
45
|
+
async function compressIfNeeded(data) {
|
|
46
|
+
return isGZipped(data) ? data : compress(data);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Checks if the data is GZipped.
|
|
50
|
+
* @param data
|
|
51
|
+
* @returns true if the data is GZipped
|
|
52
|
+
*/
|
|
53
|
+
function isGZipped(data) {
|
|
54
|
+
if (typeof data === "string") return false;
|
|
55
|
+
return data[0] === 31 && data[1] === 139;
|
|
56
|
+
}
|
|
57
|
+
function fixOSSystemID(zBuf, os = OSFlags.Unix) {
|
|
58
|
+
zBuf[OSSystemIDOffset] = os === OSFlags.auto ? zBuf[OSSystemIDOffset] : os;
|
|
59
|
+
return zBuf;
|
|
60
|
+
}
|
|
61
|
+
async function decompress(buf, encoding) {
|
|
62
|
+
const dBuf = gunzip$1(buf);
|
|
63
|
+
if (!encoding) return dBuf;
|
|
64
|
+
return (await dBuf).toString(encoding);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/util/writeFile.ts
|
|
69
|
+
/**
|
|
70
|
+
* Writes a file to disk and compress as needed.
|
|
71
|
+
*
|
|
72
|
+
* Strings will be encoded as UTF-8.
|
|
73
|
+
*
|
|
74
|
+
* Compression logic:
|
|
75
|
+
* - If the data is a string and the filePath and in `.gz` it is compressed.
|
|
76
|
+
* - If the data is a Uint8Array and the filePath ends in `.gz` the first
|
|
77
|
+
*
|
|
78
|
+
* @param filePath - path to write the file to
|
|
79
|
+
* @param data - data to write
|
|
80
|
+
*/
|
|
81
|
+
async function writeFile$1(filePath, data) {
|
|
82
|
+
const buf = typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
83
|
+
const toWrite = filePath.endsWith(".gz") ? await compressIfNeeded(buf) : buf;
|
|
84
|
+
return fs.writeFile(filePath, toWrite);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/compiler/readers/regHunspellFile.ts
|
|
89
|
+
const regHunspellFile = /\.(dic|aff)$/i;
|
|
90
|
+
|
|
91
|
+
//#endregion
|
|
92
|
+
//#region src/compiler/readers/readHunspellFiles.ts
|
|
93
|
+
const DEDUPE_SIZE = 1e3;
|
|
94
|
+
async function readHunspellFiles(filename, options) {
|
|
95
|
+
const dicFile = filename.replace(regHunspellFile, ".dic");
|
|
96
|
+
const affFile = filename.replace(regHunspellFile, ".aff");
|
|
97
|
+
const reader = await HR.IterableHunspellReader.createFromFiles(affFile, dicFile);
|
|
98
|
+
reader.maxDepth = options.maxDepth !== void 0 ? options.maxDepth : reader.maxDepth;
|
|
99
|
+
let trie;
|
|
100
|
+
function lines() {
|
|
101
|
+
return pipe(reader.seqAffWords(), _mapAffWords, dedupeAndSort);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
filename,
|
|
105
|
+
type: "Hunspell",
|
|
106
|
+
size: reader.dic.length,
|
|
107
|
+
get lines() {
|
|
108
|
+
return lines();
|
|
109
|
+
},
|
|
110
|
+
toTrie: () => {
|
|
111
|
+
if (trie) return trie;
|
|
112
|
+
trie = parseDictionary(lines(), {
|
|
113
|
+
stripCaseAndAccents: false,
|
|
114
|
+
optimize: true
|
|
115
|
+
});
|
|
116
|
+
return trie;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function* dedupeAndSort(words) {
|
|
121
|
+
const buffer = /* @__PURE__ */ new Set();
|
|
122
|
+
function flush() {
|
|
123
|
+
const result = [...buffer].sort();
|
|
124
|
+
buffer.clear();
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
for (const word of words) {
|
|
128
|
+
buffer.add(word);
|
|
129
|
+
if (buffer.size >= DEDUPE_SIZE) yield* flush();
|
|
130
|
+
}
|
|
131
|
+
yield* flush();
|
|
132
|
+
}
|
|
133
|
+
function* _mapAffWords(affWords) {
|
|
134
|
+
const hasSpecial = /[~+!]/;
|
|
135
|
+
for (const affWord of affWords) {
|
|
136
|
+
const { word, flags } = affWord;
|
|
137
|
+
if (hasSpecial.test(word)) continue;
|
|
138
|
+
const compound = flags.isCompoundForbidden ? "" : COMPOUND_FIX;
|
|
139
|
+
const forbid = flags.isForbiddenWord ? FORBID_PREFIX : "";
|
|
140
|
+
if (!forbid) {
|
|
141
|
+
if (flags.canBeCompoundBegin || flags.isCompoundPermitted) yield word + compound;
|
|
142
|
+
if (flags.canBeCompoundEnd || flags.isCompoundPermitted) yield compound + word;
|
|
143
|
+
if (flags.canBeCompoundMiddle || flags.isCompoundPermitted) yield compound + word + compound;
|
|
144
|
+
if (!flags.isOnlyAllowedInCompound) yield word;
|
|
145
|
+
} else yield forbid + word;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/compiler/readers/readTextFile.ts
|
|
151
|
+
const isGzFile$1 = /\.gz$/;
|
|
152
|
+
function readTextFile(filename) {
|
|
153
|
+
return readFile$2(filename).then((buffer) => {
|
|
154
|
+
return new TextDecoder("utf-8").decode(buffer);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function readFile$2(filename) {
|
|
158
|
+
return promises.readFile(filename).then(async (buffer) => isGzFile$1.test(filename) ? decompress(buffer) : buffer);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/compiler/readers/textFileReader.ts
|
|
163
|
+
async function textFileReader$1(filename) {
|
|
164
|
+
const words = (await readTextFile(filename)).split("\n").map((s) => s.trim());
|
|
165
|
+
let trie;
|
|
166
|
+
return {
|
|
167
|
+
filename,
|
|
168
|
+
type: "TextFile",
|
|
169
|
+
size: words.length,
|
|
170
|
+
lines: words,
|
|
171
|
+
toTrie: () => {
|
|
172
|
+
if (trie) return trie;
|
|
173
|
+
trie = parseDictionary(words, {
|
|
174
|
+
stripCaseAndAccents: false,
|
|
175
|
+
optimize: true
|
|
176
|
+
});
|
|
177
|
+
return trie;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/compiler/readers/trieFileReader.ts
|
|
184
|
+
async function trieFileReader(filename) {
|
|
185
|
+
return TrieFileReader.create(filename);
|
|
186
|
+
}
|
|
187
|
+
var TrieFileReader = class TrieFileReader {
|
|
188
|
+
#trie;
|
|
189
|
+
type = "Trie";
|
|
190
|
+
filename;
|
|
191
|
+
constructor(filename, trie) {
|
|
192
|
+
this.filename = filename;
|
|
193
|
+
this.#trie = trie;
|
|
194
|
+
}
|
|
195
|
+
get size() {
|
|
196
|
+
return this.#trie.size;
|
|
197
|
+
}
|
|
198
|
+
get lines() {
|
|
199
|
+
return this.#trie.words();
|
|
200
|
+
}
|
|
201
|
+
hasWord(word, caseSensitive) {
|
|
202
|
+
return this.#trie.hasWord(word, caseSensitive);
|
|
203
|
+
}
|
|
204
|
+
toTrie() {
|
|
205
|
+
return this.#trie;
|
|
206
|
+
}
|
|
207
|
+
static async create(filename) {
|
|
208
|
+
const rawBuffer = await readFile(filename);
|
|
209
|
+
return new TrieFileReader(filename, decodeTrie(filename.endsWith(".gz") ? gunzipSync(rawBuffer) : rawBuffer));
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
//#endregion
|
|
214
|
+
//#region src/compiler/Reader.ts
|
|
215
|
+
const readers = [
|
|
216
|
+
{
|
|
217
|
+
test: /\.trie\b/,
|
|
218
|
+
method: trieFileReader
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
test: /\.btrie\b/,
|
|
222
|
+
method: trieFileReader
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
test: regHunspellFile,
|
|
226
|
+
method: readHunspellFiles
|
|
227
|
+
}
|
|
228
|
+
];
|
|
229
|
+
function findMatchingReader(filename, options) {
|
|
230
|
+
for (const reader of readers) if (reader.test.test(filename)) return reader.method(filename, options);
|
|
231
|
+
return textFileReader$1(filename);
|
|
232
|
+
}
|
|
233
|
+
async function createReader(filename, options) {
|
|
234
|
+
const baseReader = await findMatchingReader(filename, options);
|
|
235
|
+
return Object.assign(baseReader, { [Symbol.iterator]: () => baseReader.lines[Symbol.iterator]() });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
//#endregion
|
|
239
|
+
//#region src/compiler/bTrie.ts
|
|
240
|
+
async function generateBTrieFromFile(file, options) {
|
|
241
|
+
console.log(`Processing file: ${file}`);
|
|
242
|
+
const btrie = await createBTrieFromFile(file, options);
|
|
243
|
+
const outFile = bTrieFileName(file, options);
|
|
244
|
+
await mkdir(fsPath.dirname(outFile), { recursive: true });
|
|
245
|
+
await writeFile$1(outFile, btrie);
|
|
246
|
+
console.log(`Written BTrie to: ${outFile}`);
|
|
247
|
+
}
|
|
248
|
+
async function generateBTrieFromFiles(files, options) {
|
|
249
|
+
console.log(`Generating BTrie for ${files.length} file(s).`);
|
|
250
|
+
for (const file of files) await generateBTrieFromFile(file, options);
|
|
251
|
+
}
|
|
252
|
+
function bTrieFileName(inputFilename, options) {
|
|
253
|
+
let filename = inputFilename;
|
|
254
|
+
filename = filename.replace(/\.gz$/i, "");
|
|
255
|
+
filename = filename.replace(/\.(aff|dic|txt|b?trie)$/i, "");
|
|
256
|
+
filename = filename + `.btrie`;
|
|
257
|
+
if (options.compress) filename += ".gz";
|
|
258
|
+
if (options.output) {
|
|
259
|
+
const base = fsPath.basename(filename);
|
|
260
|
+
filename = fsPath.join(fsPath.resolve(options.output), base);
|
|
261
|
+
}
|
|
262
|
+
return filename;
|
|
263
|
+
}
|
|
264
|
+
async function createBTrieFromFile(file, buildOptions) {
|
|
265
|
+
return encodeTrieDataToBTrie((await createReader(file, {})).toTrie().data, buildOptions);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
//#endregion
|
|
269
|
+
//#region src/bTrie.ts
|
|
270
|
+
function generateBTrie(files, options) {
|
|
271
|
+
return generateBTrieFromFiles(files, options);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
//#endregion
|
|
275
|
+
//#region src/config/configUtils.ts
|
|
276
|
+
function isFilePath(source) {
|
|
277
|
+
return typeof source === "string";
|
|
278
|
+
}
|
|
279
|
+
function isFileSource(source) {
|
|
280
|
+
if (!source || isFilePath(source)) return false;
|
|
281
|
+
return source.filename !== void 0;
|
|
282
|
+
}
|
|
283
|
+
function isFileListSource(source) {
|
|
284
|
+
if (!source || isFilePath(source)) return false;
|
|
285
|
+
return source.listFile !== void 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
//#endregion
|
|
289
|
+
//#region src/config/normalizeConfig.ts
|
|
290
|
+
function normalizeConfig(cfg) {
|
|
291
|
+
if (!cfg) return cfg;
|
|
292
|
+
const dir = path.dirname(cfg.filepath);
|
|
293
|
+
const config = cfg.config;
|
|
294
|
+
return {
|
|
295
|
+
config: {
|
|
296
|
+
...config,
|
|
297
|
+
rootDir: path.resolve(dir, config.rootDir || ".")
|
|
298
|
+
},
|
|
299
|
+
filepath: cfg.filepath
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
//#endregion
|
|
304
|
+
//#region src/shasum/checksum.ts
|
|
305
|
+
function calcChecksum(buf, alg = "SHA1") {
|
|
306
|
+
const hash = createHash(alg);
|
|
307
|
+
hash.update(buf);
|
|
308
|
+
return hash.digest("hex");
|
|
309
|
+
}
|
|
310
|
+
async function calcFileChecksum(filename, alg) {
|
|
311
|
+
return calcChecksum(await readFile(filename), alg);
|
|
312
|
+
}
|
|
313
|
+
async function checkFile(checksum, filename, alg) {
|
|
314
|
+
return await calcFileChecksum(filename, alg) === checksum;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
//#endregion
|
|
318
|
+
//#region src/util/errors.ts
|
|
319
|
+
function toError(err) {
|
|
320
|
+
if (isError(err)) return err;
|
|
321
|
+
return /* @__PURE__ */ new Error(`${err}`);
|
|
322
|
+
}
|
|
323
|
+
function isError(err) {
|
|
324
|
+
if (err instanceof Error) return true;
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
//#endregion
|
|
329
|
+
//#region src/util/index.ts
|
|
330
|
+
function isDefined$1(a) {
|
|
331
|
+
return !!a || a !== null && a !== void 0;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
//#endregion
|
|
335
|
+
//#region src/shasum/shasum.ts
|
|
336
|
+
async function shasumFile(filename, root) {
|
|
337
|
+
try {
|
|
338
|
+
return `${await calcFileChecksum(resolve(root || ".", filename))} ${filename}`;
|
|
339
|
+
} catch {
|
|
340
|
+
return Promise.reject(`shasum: ${filename}: Unable to read file.`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
*
|
|
345
|
+
* @param filename - name of checksum file
|
|
346
|
+
* @param files - optional list of files to check
|
|
347
|
+
* @param root - optional root, default cwd.
|
|
348
|
+
*/
|
|
349
|
+
async function checkShasumFile(filename, files, root) {
|
|
350
|
+
files = !files ? files : files.length ? files : void 0;
|
|
351
|
+
const shaFiles = await readAndParseShasumFile(filename);
|
|
352
|
+
const filesToCheck = !files ? shaFiles.map(({ filename: filename$1 }) => filename$1) : files;
|
|
353
|
+
const mapNameToChecksum = new Map(shaFiles.map((r) => [normalizeFilename(r.filename), r.checksum]));
|
|
354
|
+
const resolvedRoot = resolve(root || ".");
|
|
355
|
+
const results = await Promise.all(filesToCheck.map(normalizeFilename).map((filename$1) => {
|
|
356
|
+
return tryToCheckFile(filename$1, resolvedRoot, mapNameToChecksum.get(filename$1));
|
|
357
|
+
}));
|
|
358
|
+
return {
|
|
359
|
+
passed: !results.some((v) => !v.passed),
|
|
360
|
+
results
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
async function tryToCheckFile(filename, root, checksum) {
|
|
364
|
+
if (!checksum) return {
|
|
365
|
+
filename,
|
|
366
|
+
passed: false,
|
|
367
|
+
error: /* @__PURE__ */ new Error("Missing Checksum.")
|
|
368
|
+
};
|
|
369
|
+
const file = resolve(root, filename);
|
|
370
|
+
try {
|
|
371
|
+
return {
|
|
372
|
+
filename,
|
|
373
|
+
passed: await checkFile(checksum, file)
|
|
374
|
+
};
|
|
375
|
+
} catch {
|
|
376
|
+
return {
|
|
377
|
+
filename,
|
|
378
|
+
passed: false,
|
|
379
|
+
error: /* @__PURE__ */ new Error("Failed to read file.")
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const regLine = /([a-f0-9]{40,}) {2}(.*)/;
|
|
384
|
+
async function readAndParseShasumFile(filename) {
|
|
385
|
+
return parseShasumFile(await readFile(resolve(filename), "utf8"));
|
|
386
|
+
}
|
|
387
|
+
function parseShasumFile(content) {
|
|
388
|
+
return content.split(/\r?\n|\r/g).map(parseLine).filter(isDefined$1);
|
|
389
|
+
function parseLine(line, index) {
|
|
390
|
+
const m = line.match(regLine);
|
|
391
|
+
const lineNumber = index + 1;
|
|
392
|
+
if (!m) {
|
|
393
|
+
if (line.trim()) throw new Error(`Failed to parse line ${lineNumber} of checksum file.`);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
return {
|
|
397
|
+
checksum: m[1],
|
|
398
|
+
filename: m[2],
|
|
399
|
+
lineNumber
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
async function reportChecksumForFiles(files, options) {
|
|
404
|
+
const root = options.root;
|
|
405
|
+
const filesToCheck = await resolveFileList(files, options.listFile);
|
|
406
|
+
let numFailed = 0;
|
|
407
|
+
return {
|
|
408
|
+
report: (await Promise.all(filesToCheck.map((file) => shasumFile(file, root).catch((e) => {
|
|
409
|
+
++numFailed;
|
|
410
|
+
if (typeof e !== "string") throw e;
|
|
411
|
+
return e;
|
|
412
|
+
})))).join("\n"),
|
|
413
|
+
passed: !numFailed
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
async function reportCheckChecksumFile(filename, files, options) {
|
|
417
|
+
const root = options.root;
|
|
418
|
+
const results = (await checkShasumFile(filename, await resolveFileList(files, options.listFile), root)).results;
|
|
419
|
+
const lines = results.map(({ filename: filename$1, passed: passed$1, error }) => `${filename$1}: ${passed$1 ? "OK" : "FAILED"} ${error ? "- " + error.message : ""}`.trim());
|
|
420
|
+
const withErrors = results.filter((a) => !a.passed);
|
|
421
|
+
const passed = !withErrors.length;
|
|
422
|
+
if (!passed) lines.push(`shasum: WARNING: ${withErrors.length} computed checksum${withErrors.length > 1 ? "s" : ""} did NOT match`);
|
|
423
|
+
return {
|
|
424
|
+
report: lines.join("\n"),
|
|
425
|
+
passed
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
async function resolveFileList(files, listFile) {
|
|
429
|
+
files = files || [];
|
|
430
|
+
listFile = listFile || [];
|
|
431
|
+
const setOfFiles = new Set(files);
|
|
432
|
+
const pending = listFile.map((filename) => readFile(filename, "utf8"));
|
|
433
|
+
for await (const content of pending) content.split("\n").map((a) => a.trim()).filter((a) => a).forEach((file) => setOfFiles.add(file));
|
|
434
|
+
return [...setOfFiles].map(normalizeFilename);
|
|
435
|
+
}
|
|
436
|
+
async function calcUpdateChecksumForFiles(filename, files, options) {
|
|
437
|
+
const root = options.root || ".";
|
|
438
|
+
const filesToCheck = await resolveFileList(files, options.listFile);
|
|
439
|
+
const currentEntries = (await readAndParseShasumFile(filename).catch((err) => {
|
|
440
|
+
const e = toError(err);
|
|
441
|
+
if (e.code !== "ENOENT") throw e;
|
|
442
|
+
return [];
|
|
443
|
+
})).map((entry) => ({
|
|
444
|
+
...entry,
|
|
445
|
+
filename: normalizeFilename(entry.filename)
|
|
446
|
+
}));
|
|
447
|
+
const entriesToUpdate = new Set([...filesToCheck, ...currentEntries.map((e) => e.filename)]);
|
|
448
|
+
const mustExist = new Set(filesToCheck);
|
|
449
|
+
const checksumMap = new Map(currentEntries.map(({ filename: filename$1, checksum }) => [filename$1, checksum]));
|
|
450
|
+
for (const file of entriesToUpdate) try {
|
|
451
|
+
const checksum = await calcFileChecksum(resolve(root, file));
|
|
452
|
+
checksumMap.set(file, checksum);
|
|
453
|
+
} catch (e) {
|
|
454
|
+
if (mustExist.has(file) || toError(e).code !== "ENOENT") throw e;
|
|
455
|
+
checksumMap.delete(file);
|
|
456
|
+
}
|
|
457
|
+
return [...checksumMap].map(([filename$1, checksum]) => ({
|
|
458
|
+
filename: filename$1,
|
|
459
|
+
checksum
|
|
460
|
+
})).sort((a, b) => a.filename < b.filename ? -1 : 1).map((e) => `${e.checksum} ${e.filename}`).join("\n") + "\n";
|
|
461
|
+
}
|
|
462
|
+
async function updateChecksumForFiles(filename, files, options) {
|
|
463
|
+
const content = await calcUpdateChecksumForFiles(filename, files, options);
|
|
464
|
+
await writeFile(filename, content);
|
|
465
|
+
return {
|
|
466
|
+
passed: true,
|
|
467
|
+
report: content
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function normalizeFilename(filename) {
|
|
471
|
+
return filename.split(sep).join("/");
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
//#endregion
|
|
475
|
+
//#region src/util/textRegex.ts
|
|
476
|
+
const regExMatchRegExParts = /^\s*\/([\s\S]*?)\/([gimuxy]*)\s*$/;
|
|
477
|
+
function stringToRegExp(pattern, defaultFlags = "", forceFlags = "") {
|
|
478
|
+
if (pattern instanceof RegExp) return pattern;
|
|
479
|
+
try {
|
|
480
|
+
const [, pat, flag] = [...pattern.match(regExMatchRegExParts) || [
|
|
481
|
+
"",
|
|
482
|
+
pattern.trim(),
|
|
483
|
+
defaultFlags
|
|
484
|
+
], forceFlags];
|
|
485
|
+
if (pat) {
|
|
486
|
+
const regPattern = flag.includes("x") ? removeVerboseFromRegExp(pat) : pat;
|
|
487
|
+
const flags = [...new Set(forceFlags + flag)].join("").replaceAll(/[^gimuy]/g, "");
|
|
488
|
+
return new RegExp(regPattern, flags);
|
|
489
|
+
}
|
|
490
|
+
} catch {}
|
|
491
|
+
}
|
|
492
|
+
const SPACES = {
|
|
493
|
+
" ": true,
|
|
494
|
+
"\n": true,
|
|
495
|
+
"\r": true,
|
|
496
|
+
" ": true
|
|
497
|
+
};
|
|
498
|
+
/**
|
|
499
|
+
* Remove all whitespace and comments from a regexp string. The format follows Pythons Verbose.
|
|
500
|
+
* Note: this is a best attempt. Special cases for comments: `#` and spaces should be proceeded with a `\`
|
|
501
|
+
*
|
|
502
|
+
* All space must be proceeded by a `\` or in a character class `[]`
|
|
503
|
+
*
|
|
504
|
+
* @param pattern - the pattern to clean
|
|
505
|
+
*/
|
|
506
|
+
function removeVerboseFromRegExp(pattern) {
|
|
507
|
+
function escape(acc) {
|
|
508
|
+
if (pattern[acc.idx] !== "\\") return void 0;
|
|
509
|
+
const next = pattern[++acc.idx];
|
|
510
|
+
acc.idx++;
|
|
511
|
+
if (next === "#") {
|
|
512
|
+
acc.result += "#";
|
|
513
|
+
return acc;
|
|
514
|
+
}
|
|
515
|
+
if (!(next in SPACES)) {
|
|
516
|
+
acc.result += "\\" + next;
|
|
517
|
+
return acc;
|
|
518
|
+
}
|
|
519
|
+
acc.result += next;
|
|
520
|
+
if (next === "\r" && pattern[acc.idx] === "\n") {
|
|
521
|
+
acc.result += "\n";
|
|
522
|
+
acc.idx++;
|
|
523
|
+
}
|
|
524
|
+
return acc;
|
|
525
|
+
}
|
|
526
|
+
function braces(acc) {
|
|
527
|
+
const char = pattern[acc.idx];
|
|
528
|
+
if (char !== "[") return void 0;
|
|
529
|
+
acc.result += char;
|
|
530
|
+
acc.idx++;
|
|
531
|
+
let escCount = 0;
|
|
532
|
+
while (acc.idx < pattern.length) {
|
|
533
|
+
const char$1 = pattern[acc.idx];
|
|
534
|
+
acc.result += char$1;
|
|
535
|
+
acc.idx++;
|
|
536
|
+
if (char$1 === "]" && !(escCount & 1)) break;
|
|
537
|
+
escCount = char$1 === "\\" ? escCount + 1 : 0;
|
|
538
|
+
}
|
|
539
|
+
return acc;
|
|
540
|
+
}
|
|
541
|
+
function spaces(acc) {
|
|
542
|
+
if (!(pattern[acc.idx] in SPACES)) return void 0;
|
|
543
|
+
acc.idx++;
|
|
544
|
+
return acc;
|
|
545
|
+
}
|
|
546
|
+
function comments(acc) {
|
|
547
|
+
if (pattern[acc.idx] !== "#") return void 0;
|
|
548
|
+
while (acc.idx < pattern.length && pattern[acc.idx] !== "\n") acc.idx++;
|
|
549
|
+
return acc;
|
|
550
|
+
}
|
|
551
|
+
function copy(acc) {
|
|
552
|
+
const char = pattern[acc.idx++];
|
|
553
|
+
acc.result += char;
|
|
554
|
+
return acc;
|
|
555
|
+
}
|
|
556
|
+
const reducers = [
|
|
557
|
+
escape,
|
|
558
|
+
braces,
|
|
559
|
+
spaces,
|
|
560
|
+
comments,
|
|
561
|
+
copy
|
|
562
|
+
];
|
|
563
|
+
const result = {
|
|
564
|
+
idx: 0,
|
|
565
|
+
result: ""
|
|
566
|
+
};
|
|
567
|
+
while (result.idx < pattern.length) for (const r of reducers) if (r(result)) break;
|
|
568
|
+
return result.result;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
//#endregion
|
|
572
|
+
//#region src/compiler/WordsCollection.ts
|
|
573
|
+
const defaultAllowedSplitWords = Object.freeze({
|
|
574
|
+
size: 0,
|
|
575
|
+
has: () => true
|
|
576
|
+
});
|
|
577
|
+
const defaultExcludeWordsCollection = Object.freeze({
|
|
578
|
+
size: 0,
|
|
579
|
+
has: () => false
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
//#endregion
|
|
583
|
+
//#region src/compiler/createWordsCollection.ts
|
|
584
|
+
var AllowedSplitWordsImpl = class {
|
|
585
|
+
collection;
|
|
586
|
+
size;
|
|
587
|
+
constructor(collection) {
|
|
588
|
+
this.collection = collection;
|
|
589
|
+
this.size = collection.size;
|
|
590
|
+
}
|
|
591
|
+
has(word, caseSensitive) {
|
|
592
|
+
return !this.size || this.collection.has(word, caseSensitive);
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
async function createAllowedSplitWordsFromFiles(files) {
|
|
596
|
+
if (!files || !files.length) return defaultAllowedSplitWords;
|
|
597
|
+
return new AllowedSplitWordsImpl(await createWordsCollectionFromFiles(files));
|
|
598
|
+
}
|
|
599
|
+
function buildHasFn(dict) {
|
|
600
|
+
function has(word, caseSensitive) {
|
|
601
|
+
const r = dict.hasWord(word, true);
|
|
602
|
+
if (r || caseSensitive) return r;
|
|
603
|
+
const lc = word.toLowerCase();
|
|
604
|
+
if (lc === word) return false;
|
|
605
|
+
return dict.hasWord(lc, true);
|
|
606
|
+
}
|
|
607
|
+
return has;
|
|
608
|
+
}
|
|
609
|
+
async function readFile$1(filename) {
|
|
610
|
+
return await createReader(filename, {});
|
|
611
|
+
}
|
|
612
|
+
function readersToCollection(readers$1) {
|
|
613
|
+
const dictReaders = readers$1.filter(isDictionaryReader).map(dictReaderToCollection);
|
|
614
|
+
const nonDictCollection = lineReadersToCollection(readers$1.filter((a) => !isDictionaryReader(a)));
|
|
615
|
+
const collections = [...dictReaders, nonDictCollection];
|
|
616
|
+
return {
|
|
617
|
+
size: collections.reduce((s, a) => s + a.size, 0),
|
|
618
|
+
has: (word, caseSensitive) => collections.some((a) => a.has(word, caseSensitive))
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
const cache = /* @__PURE__ */ new WeakMap();
|
|
622
|
+
async function createWordsCollectionFromFiles(files) {
|
|
623
|
+
files = toFilePathArray(files);
|
|
624
|
+
const cached = cache.get(files);
|
|
625
|
+
if (cached) return cached;
|
|
626
|
+
const collection = readersToCollection(await Promise.all(files.map((file) => readFile$1(file))));
|
|
627
|
+
cache.set(files, collection);
|
|
628
|
+
return collection;
|
|
629
|
+
}
|
|
630
|
+
function isDictionaryReader(reader) {
|
|
631
|
+
return "hasWord" in reader && !!reader.hasWord;
|
|
632
|
+
}
|
|
633
|
+
function dictReaderToCollection(reader) {
|
|
634
|
+
return {
|
|
635
|
+
size: reader.size,
|
|
636
|
+
has: buildHasFn(reader)
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
function lineReadersToCollection(readers$1) {
|
|
640
|
+
function* words() {
|
|
641
|
+
for (const reader of readers$1) yield* reader.lines;
|
|
642
|
+
}
|
|
643
|
+
const dict = parseDictionary(words(), { stripCaseAndAccents: false });
|
|
644
|
+
return {
|
|
645
|
+
size: dict.size,
|
|
646
|
+
has: buildHasFn(dict)
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
function toFilePathArray(filePathOrArray) {
|
|
650
|
+
return Array.isArray(filePathOrArray) ? filePathOrArray : [filePathOrArray];
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
//#endregion
|
|
654
|
+
//#region src/compiler/logWithTimestamp.ts
|
|
655
|
+
const logWithTimestamp = (message, ...optionalParams) => {
|
|
656
|
+
console.log(`${(/* @__PURE__ */ new Date()).toISOString()} ${message}`, ...optionalParams);
|
|
657
|
+
};
|
|
658
|
+
|
|
659
|
+
//#endregion
|
|
660
|
+
//#region src/config/configDefaults.ts
|
|
661
|
+
const defaultCompileSourceOptions = {
|
|
662
|
+
maxDepth: void 0,
|
|
663
|
+
split: false,
|
|
664
|
+
keepRawCase: false,
|
|
665
|
+
allowedSplitWords: void 0,
|
|
666
|
+
storeSplitWordsAsCompounds: false,
|
|
667
|
+
minCompoundLength: 4
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
//#endregion
|
|
671
|
+
//#region src/compiler/text.ts
|
|
672
|
+
const regExSplitWords = /([\p{Ll}])([\p{Lu}])/gu;
|
|
673
|
+
const regExSplitWords2 = /(\p{Lu})(\p{Lu}\p{Ll})/gu;
|
|
674
|
+
const regExpIsLetter = /^\p{L}\p{M}{0,2}$/u;
|
|
675
|
+
/**
|
|
676
|
+
* Split camelCase words into an array of strings.
|
|
677
|
+
*/
|
|
678
|
+
function splitCamelCaseWord(word) {
|
|
679
|
+
return word.replaceAll(regExSplitWords, "$1|$2").replaceAll(regExSplitWords2, "$1|$2").replaceAll(/[\d_]+/g, "|").split("|").filter((a) => !!a);
|
|
680
|
+
}
|
|
681
|
+
function isSingleLetter(c) {
|
|
682
|
+
return regExpIsLetter.test(c);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
//#endregion
|
|
686
|
+
//#region src/compiler/splitCamelCaseIfAllowed.ts
|
|
687
|
+
const regExpSpaceOrDash = /[- ]+/g;
|
|
688
|
+
const regExpIsNumber = /^\d+$/;
|
|
689
|
+
function splitCamelCaseIfAllowed(word, allowedWords, keepCase, compoundPrefix, minCompoundLength$1) {
|
|
690
|
+
const split = [...splitCamelCase(word)];
|
|
691
|
+
if (split.length === 1) return adjustCases(split, allowedWords, keepCase);
|
|
692
|
+
if (split.some((w) => isUnknown(w, allowedWords))) return [word];
|
|
693
|
+
const wordIndexes = calcWordIndex(word, split);
|
|
694
|
+
const adjusted = adjustCases(split, allowedWords, keepCase);
|
|
695
|
+
return !compoundPrefix ? adjusted : adjusted.map((w, i) => {
|
|
696
|
+
const { px, sx } = wordIndexes[i];
|
|
697
|
+
const canCompound = w.length >= minCompoundLength$1;
|
|
698
|
+
const lc = w.toLowerCase();
|
|
699
|
+
const p = canCompound && isSingleLetter(px) ? compoundPrefix : "";
|
|
700
|
+
const s = canCompound && isSingleLetter(sx) ? compoundPrefix : "";
|
|
701
|
+
if (lc.length < 4 || allowedWords.has(w, true)) return p + w + s;
|
|
702
|
+
return p + lc + s;
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
function adjustCases(words, allowedWords, keepCase) {
|
|
706
|
+
return words.map((w) => adjustCase(w, allowedWords, keepCase));
|
|
707
|
+
}
|
|
708
|
+
function adjustCase(word, allowedWords, keepCase) {
|
|
709
|
+
const lc = word.toLowerCase();
|
|
710
|
+
if (!allowedWords.has(lc, true)) return word;
|
|
711
|
+
if (lc === word) return word;
|
|
712
|
+
if (word.slice(1).toLowerCase() === word.slice(1)) return lc;
|
|
713
|
+
if (!keepCase && word.toUpperCase() === word) return word.toLowerCase();
|
|
714
|
+
return word;
|
|
715
|
+
}
|
|
716
|
+
function isUnknown(word, allowedWords) {
|
|
717
|
+
if (word === "ERROR") return !allowedWords.has(word, false);
|
|
718
|
+
return !allowedWords.has(word, false);
|
|
719
|
+
}
|
|
720
|
+
function splitCamelCase(word) {
|
|
721
|
+
const splitWords = splitCamelCaseWord(word).filter((word$1) => !regExpIsNumber.test(word$1));
|
|
722
|
+
if (splitWords.length > 1 && regExpSpaceOrDash.test(word)) return splitWords.flatMap((w) => w.split(regExpSpaceOrDash));
|
|
723
|
+
return splitWords;
|
|
724
|
+
}
|
|
725
|
+
function calcWordIndex(word, words) {
|
|
726
|
+
let i = 0;
|
|
727
|
+
return words.map((w) => {
|
|
728
|
+
const j = word.indexOf(w, i);
|
|
729
|
+
const k = j + w.length;
|
|
730
|
+
const wIndex = {
|
|
731
|
+
word: w,
|
|
732
|
+
i: j,
|
|
733
|
+
px: word[j - 1] || "",
|
|
734
|
+
sx: word[k] || ""
|
|
735
|
+
};
|
|
736
|
+
i = k;
|
|
737
|
+
return wIndex;
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
//#endregion
|
|
742
|
+
//#region src/compiler/legacyLineToWords.ts
|
|
743
|
+
const regNonWord = /[^\p{L}\p{M}' _\d]+/giu;
|
|
744
|
+
const regExpRepeatChars = /(.)\1{5}/i;
|
|
745
|
+
const minCompoundLength = defaultCompileSourceOptions.minCompoundLength;
|
|
746
|
+
function legacyLineToWords(line, keepCase, allowedSplitWords) {
|
|
747
|
+
const wordGroups = line.replaceAll(regNonWord, "|").split("|");
|
|
748
|
+
const _minCompoundLength = minCompoundLength;
|
|
749
|
+
return pipe(wordGroups, opConcatMap((a) => a.split(regExpSpaceOrDash)), opConcatMap((a) => splitCamelCaseIfAllowed(a, allowedSplitWords, keepCase, "", _minCompoundLength)), opMap((a) => a.trim()), opFilter((a) => !!a), opFilter((s) => !regExpRepeatChars.test(s)));
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
//#endregion
|
|
753
|
+
//#region src/compiler/wordListParser.ts
|
|
754
|
+
function normalizeTargetWords(options) {
|
|
755
|
+
const lineParser = createDictionaryLineParser({
|
|
756
|
+
stripCaseAndAccents: options.generateNonStrict,
|
|
757
|
+
stripCaseAndAccentsOnForbidden: true,
|
|
758
|
+
keepOptionalCompoundCharacter: true
|
|
759
|
+
});
|
|
760
|
+
return opCombine(...[
|
|
761
|
+
opFilter((a) => !!a),
|
|
762
|
+
lineParser,
|
|
763
|
+
options.sort ? createInlineBufferedSort(1e4) : void 0,
|
|
764
|
+
opFilter(uniqueFilter(1e4)),
|
|
765
|
+
options.filter ? opFilter(options.filter) : void 0
|
|
766
|
+
].filter(isDefined));
|
|
767
|
+
}
|
|
768
|
+
function isDefined(v) {
|
|
769
|
+
return v !== void 0;
|
|
770
|
+
}
|
|
771
|
+
function createInlineBufferedSort(bufferSize = 1e3) {
|
|
772
|
+
function* inlineBufferedSort(lines) {
|
|
773
|
+
const buffer = [];
|
|
774
|
+
for (const line of lines) {
|
|
775
|
+
buffer.push(line);
|
|
776
|
+
if (buffer.length >= bufferSize) {
|
|
777
|
+
buffer.sort();
|
|
778
|
+
yield* buffer;
|
|
779
|
+
buffer.length = 0;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
buffer.sort();
|
|
783
|
+
yield* buffer;
|
|
784
|
+
}
|
|
785
|
+
return inlineBufferedSort;
|
|
786
|
+
}
|
|
787
|
+
const commentCharacter = "#";
|
|
788
|
+
const _defaultOptions = {
|
|
789
|
+
keepCase: true,
|
|
790
|
+
legacy: false,
|
|
791
|
+
split: false,
|
|
792
|
+
splitKeepBoth: false,
|
|
793
|
+
allowedSplitWords: {
|
|
794
|
+
has: () => true,
|
|
795
|
+
size: 0
|
|
796
|
+
},
|
|
797
|
+
storeSplitWordsAsCompounds: defaultCompileSourceOptions.storeSplitWordsAsCompounds,
|
|
798
|
+
minCompoundLength: defaultCompileSourceOptions.minCompoundLength
|
|
799
|
+
};
|
|
800
|
+
const defaultParseDictionaryOptions = Object.freeze(_defaultOptions);
|
|
801
|
+
const cSpellToolDirective = "cspell-tools:";
|
|
802
|
+
/**
|
|
803
|
+
* Normalizes a dictionary words based upon prefix / suffixes.
|
|
804
|
+
* Case insensitive versions are also generated.
|
|
805
|
+
* @param options - defines prefixes used when parsing lines.
|
|
806
|
+
* @returns words that have been normalized.
|
|
807
|
+
*/
|
|
808
|
+
function createParseFileLineMapper(options) {
|
|
809
|
+
const _options = options || _defaultOptions;
|
|
810
|
+
const { splitKeepBoth = _defaultOptions.splitKeepBoth, allowedSplitWords = _defaultOptions.allowedSplitWords, storeSplitWordsAsCompounds, minCompoundLength: minCompoundLength$1 = _defaultOptions.minCompoundLength } = _options;
|
|
811
|
+
let { legacy = _defaultOptions.legacy } = _options;
|
|
812
|
+
let { split = _defaultOptions.split, keepCase = legacy ? false : _defaultOptions.keepCase } = _options;
|
|
813
|
+
const compoundFix = storeSplitWordsAsCompounds ? "+" : "";
|
|
814
|
+
function isString(line) {
|
|
815
|
+
return typeof line === "string";
|
|
816
|
+
}
|
|
817
|
+
function trim(line) {
|
|
818
|
+
return line.trim();
|
|
819
|
+
}
|
|
820
|
+
function removeComments(line) {
|
|
821
|
+
const idx = line.indexOf(commentCharacter);
|
|
822
|
+
if (idx < 0) return line;
|
|
823
|
+
const idxDirective = line.indexOf(cSpellToolDirective, idx);
|
|
824
|
+
if (idxDirective >= 0) {
|
|
825
|
+
const flags = line.slice(idxDirective).split(/[\s,;]/g).map((s) => s.trim()).filter((a) => !!a);
|
|
826
|
+
for (const flag of flags) switch (flag) {
|
|
827
|
+
case "split":
|
|
828
|
+
split = true;
|
|
829
|
+
break;
|
|
830
|
+
case "no-split":
|
|
831
|
+
split = false;
|
|
832
|
+
break;
|
|
833
|
+
case "keep-case":
|
|
834
|
+
keepCase = true;
|
|
835
|
+
legacy = false;
|
|
836
|
+
break;
|
|
837
|
+
case "no-keep-case":
|
|
838
|
+
keepCase = false;
|
|
839
|
+
break;
|
|
840
|
+
case "legacy":
|
|
841
|
+
keepCase = false;
|
|
842
|
+
legacy = true;
|
|
843
|
+
break;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
return line.slice(0, idx).trim();
|
|
847
|
+
}
|
|
848
|
+
function filterEmptyLines(line) {
|
|
849
|
+
return !!line;
|
|
850
|
+
}
|
|
851
|
+
const regNonWordOrDigit = /[^\p{L}\p{M}'\w-]+/giu;
|
|
852
|
+
function splitLine(line) {
|
|
853
|
+
line = line.replace(/#.*/, "");
|
|
854
|
+
line = line.trim();
|
|
855
|
+
line = line.replaceAll(/\bU\+[0-9A-F]{4}\b/gi, "|");
|
|
856
|
+
line = line.replaceAll(/\\U[0-9A-F]{4}/gi, "|");
|
|
857
|
+
line = line.replaceAll(regNonWordOrDigit, "|");
|
|
858
|
+
line = line.replaceAll(/'(?=\|)/g, "");
|
|
859
|
+
line = line.replace(/'$/, "");
|
|
860
|
+
line = line.replaceAll(/(?<=\|)'/g, "");
|
|
861
|
+
line = line.replace(/^'/, "");
|
|
862
|
+
line = line.replaceAll(/\s*\|\s*/g, "|");
|
|
863
|
+
line = line.replaceAll(/[|]+/g, "|");
|
|
864
|
+
line = line.replace(/^\|/, "");
|
|
865
|
+
line = line.replace(/\|$/, "");
|
|
866
|
+
return line.split("|").map((a) => a.trim()).filter((a) => !!a).filter((a) => !/^[0-9_-]+$/.test(a)).filter((a) => !/^0[xo][0-9A-F]+$/i.test(a));
|
|
867
|
+
}
|
|
868
|
+
function splitWordIntoWords(word) {
|
|
869
|
+
return splitCamelCaseIfAllowed(word, allowedSplitWords, keepCase, compoundFix, minCompoundLength$1);
|
|
870
|
+
}
|
|
871
|
+
function* splitWords(lines) {
|
|
872
|
+
for (const line of lines) {
|
|
873
|
+
if (legacy) {
|
|
874
|
+
yield* legacyLineToWords(line, keepCase, allowedSplitWords);
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
if (split) {
|
|
878
|
+
const words = splitLine(line);
|
|
879
|
+
yield* !allowedSplitWords.size ? words : words.flatMap((word) => splitWordIntoWords(word));
|
|
880
|
+
if (!splitKeepBoth) continue;
|
|
881
|
+
}
|
|
882
|
+
yield line.replaceAll(/["]/g, "");
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
function* unique(lines) {
|
|
886
|
+
const known = /* @__PURE__ */ new Set();
|
|
887
|
+
for (const line of lines) {
|
|
888
|
+
if (known.has(line)) continue;
|
|
889
|
+
known.add(line);
|
|
890
|
+
yield line;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function* splitLines$1(paragraphs) {
|
|
894
|
+
for (const paragraph of paragraphs) yield* paragraph.split("\n");
|
|
895
|
+
}
|
|
896
|
+
return opCombine(opFilter(isString), splitLines$1, opMap(removeComments), splitWords, opMap(trim), opFilter(filterEmptyLines), unique);
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Normalizes a dictionary words based upon prefix / suffixes.
|
|
900
|
+
* Case insensitive versions are also generated.
|
|
901
|
+
* @param lines - one word per line
|
|
902
|
+
* @param _options - defines prefixes used when parsing lines.
|
|
903
|
+
* @returns words that have been normalized.
|
|
904
|
+
*/
|
|
905
|
+
function parseFileLines(lines, options) {
|
|
906
|
+
return createParseFileLineMapper(options)(typeof lines === "string" ? [lines] : lines);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
//#endregion
|
|
910
|
+
//#region src/compiler/SourceReader.ts
|
|
911
|
+
async function createSourceReader(filename, options) {
|
|
912
|
+
const reader = await createReader(filename, options);
|
|
913
|
+
if (reader.type !== "TextFile") return {
|
|
914
|
+
words: splitLines(reader.lines, options),
|
|
915
|
+
get size() {
|
|
916
|
+
return reader.size;
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
return textFileReader(reader, options);
|
|
920
|
+
}
|
|
921
|
+
function splitLines(lines, options) {
|
|
922
|
+
if (!options.splitWords) return lines;
|
|
923
|
+
function* split() {
|
|
924
|
+
const regNonWordOrDigit = /[^\p{L}\p{M}'\w-]+/giu;
|
|
925
|
+
for (const line of lines) yield* line.split(regNonWordOrDigit);
|
|
926
|
+
}
|
|
927
|
+
return split();
|
|
928
|
+
}
|
|
929
|
+
async function textFileReader(reader, options) {
|
|
930
|
+
const { legacy, splitWords: split, allowedSplitWords, storeSplitWordsAsCompounds, minCompoundLength: minCompoundLength$1 } = options;
|
|
931
|
+
const parseOptions = {
|
|
932
|
+
legacy,
|
|
933
|
+
split,
|
|
934
|
+
splitKeepBoth: void 0,
|
|
935
|
+
keepCase: void 0,
|
|
936
|
+
allowedSplitWords,
|
|
937
|
+
storeSplitWordsAsCompounds,
|
|
938
|
+
minCompoundLength: minCompoundLength$1
|
|
939
|
+
};
|
|
940
|
+
const words = [...parseFileLines(reader.lines, parseOptions)];
|
|
941
|
+
return {
|
|
942
|
+
size: words.length,
|
|
943
|
+
words
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
//#endregion
|
|
948
|
+
//#region src/compiler/streamSourceWordsFromFile.ts
|
|
949
|
+
async function streamSourceWordsFromFile(filename, options) {
|
|
950
|
+
return (await createSourceReader(filename, options)).words;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
//#endregion
|
|
954
|
+
//#region src/compiler/logger.ts
|
|
955
|
+
const defaultLogger = console.log;
|
|
956
|
+
let log = defaultLogger;
|
|
957
|
+
function setLogger(logger) {
|
|
958
|
+
log = logger ?? defaultLogger;
|
|
959
|
+
}
|
|
960
|
+
function getLogger() {
|
|
961
|
+
return log;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
//#endregion
|
|
965
|
+
//#region src/compiler/writeTextToFile.ts
|
|
966
|
+
const isGzFile = /\.gz$/;
|
|
967
|
+
async function writeTextToFile(filename, data, useGzCompress) {
|
|
968
|
+
const dataStr = typeof data === "string" ? data : Array.isArray(data) ? data.join("") : [...data].join("");
|
|
969
|
+
const hasGzExt = isGzFile.test(filename);
|
|
970
|
+
const useGz = useGzCompress ?? hasGzExt;
|
|
971
|
+
if (useGz && !hasGzExt) filename += ".gz";
|
|
972
|
+
const buf = Buffer.from(dataStr, "utf8");
|
|
973
|
+
const buffer = useGz ? await compress(buf) : buf;
|
|
974
|
+
await promises.writeFile(filename, buffer);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
//#endregion
|
|
978
|
+
//#region src/compiler/wordListCompiler.ts
|
|
979
|
+
const mkdirp = async (p) => {
|
|
980
|
+
await mkdir(p, { recursive: true });
|
|
981
|
+
};
|
|
982
|
+
const wordListHeader = `
|
|
983
|
+
# cspell-tools: keep-case no-split`;
|
|
984
|
+
const wordListHeaderLines = wordListHeader.split("\n").map((a) => a.trim());
|
|
985
|
+
function createWordListCompiler(options) {
|
|
986
|
+
return (lines) => {
|
|
987
|
+
const finalLines = normalize(lines, options);
|
|
988
|
+
const directivesLines = (options.dictionaryDirectives ?? []).map((a) => `# cspell-dictionary: ${a}`);
|
|
989
|
+
return pipe([
|
|
990
|
+
...wordListHeaderLines,
|
|
991
|
+
...directivesLines,
|
|
992
|
+
""
|
|
993
|
+
], opAppend(finalLines), opMap((a) => a + "\n"));
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
function normalize(lines, options) {
|
|
997
|
+
const filter = normalizeTargetWords(options);
|
|
998
|
+
const iter = pipe(options.removeDuplicates ? removeDuplicates(lines) : lines, filter);
|
|
999
|
+
if (!options.sort) return iter;
|
|
1000
|
+
return [...new Set(iter)].sort();
|
|
1001
|
+
}
|
|
1002
|
+
function stripCompoundAFix(word) {
|
|
1003
|
+
return word.replaceAll("*", "").replaceAll("+", "");
|
|
1004
|
+
}
|
|
1005
|
+
function* removeDuplicates(words) {
|
|
1006
|
+
const wordSet = new Set(words);
|
|
1007
|
+
const wordForms = /* @__PURE__ */ new Map();
|
|
1008
|
+
for (const word of wordSet) {
|
|
1009
|
+
const lc = stripCompoundAFix(word.toLowerCase());
|
|
1010
|
+
const forms = wordForms.get(lc) ?? [];
|
|
1011
|
+
forms.push(word);
|
|
1012
|
+
wordForms.set(lc, forms);
|
|
1013
|
+
}
|
|
1014
|
+
for (const forms of wordForms.values()) {
|
|
1015
|
+
if (forms.length <= 1) {
|
|
1016
|
+
yield* forms;
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
const mForms = removeDuplicateForms(forms);
|
|
1020
|
+
if (mForms.size <= 1) {
|
|
1021
|
+
for (const form of mForms.values()) yield* form;
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
const lc = [...mForms.keys()][0].toLowerCase();
|
|
1025
|
+
const lcForm = mForms.get(lc);
|
|
1026
|
+
if (!lcForm) {
|
|
1027
|
+
for (const form of mForms.values()) yield* form;
|
|
1028
|
+
continue;
|
|
1029
|
+
}
|
|
1030
|
+
mForms.delete(lc);
|
|
1031
|
+
const sLcForms = new Set(lcForm);
|
|
1032
|
+
yield* lcForm;
|
|
1033
|
+
if (sLcForms.has("*" + lc + "*")) continue;
|
|
1034
|
+
for (const forms$1 of mForms.values()) for (const form of forms$1) {
|
|
1035
|
+
if (sLcForms.has(form.toLowerCase())) continue;
|
|
1036
|
+
yield form;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* solo
|
|
1042
|
+
* optional_prefix*
|
|
1043
|
+
* optional_suffix*
|
|
1044
|
+
* required_prefix+
|
|
1045
|
+
* required_suffix+
|
|
1046
|
+
*/
|
|
1047
|
+
var Flags = /* @__PURE__ */ function(Flags$1) {
|
|
1048
|
+
Flags$1[Flags$1["base"] = 0] = "base";
|
|
1049
|
+
Flags$1[Flags$1["none"] = 1] = "none";
|
|
1050
|
+
Flags$1[Flags$1["both"] = 2] = "both";
|
|
1051
|
+
Flags$1[Flags$1["pfx"] = 4] = "pfx";
|
|
1052
|
+
Flags$1[Flags$1["sfx"] = 8] = "sfx";
|
|
1053
|
+
Flags$1[Flags$1["all"] = 15] = "all";
|
|
1054
|
+
return Flags$1;
|
|
1055
|
+
}(Flags || {});
|
|
1056
|
+
function applyFlags(word, flags) {
|
|
1057
|
+
if (flags === Flags.none) return [word];
|
|
1058
|
+
if (flags === Flags.all) return ["*" + word + "*"];
|
|
1059
|
+
if (flags === Flags.both) return ["+" + word + "+"];
|
|
1060
|
+
if (flags === Flags.pfx) return [word + "+"];
|
|
1061
|
+
if (flags === Flags.sfx) return ["+" + word];
|
|
1062
|
+
if (flags === (Flags.none | Flags.sfx)) return ["*" + word];
|
|
1063
|
+
if (flags === (Flags.none | Flags.pfx)) return [word + "*"];
|
|
1064
|
+
if (flags === (Flags.none | Flags.pfx | Flags.sfx)) return [word + "*", "*" + word];
|
|
1065
|
+
if (flags === (Flags.none | Flags.both)) return ["*" + word + "*"];
|
|
1066
|
+
if (flags === (Flags.none | Flags.both | Flags.sfx)) return [word, "+" + word + "*"];
|
|
1067
|
+
if (flags === (Flags.none | Flags.both | Flags.pfx)) return [word, "*" + word + "+"];
|
|
1068
|
+
if (flags === (Flags.both | Flags.pfx)) return ["*" + word + "+"];
|
|
1069
|
+
if (flags === (Flags.both | Flags.sfx)) return ["+" + word + "*"];
|
|
1070
|
+
if (flags === (Flags.both | Flags.pfx | Flags.sfx)) return ["+" + word + "*", "*" + word + "+"];
|
|
1071
|
+
return ["+" + word, word + "+"];
|
|
1072
|
+
}
|
|
1073
|
+
function removeDuplicateForms(forms) {
|
|
1074
|
+
function flags(word, flag = 0) {
|
|
1075
|
+
const canBePrefix = word.endsWith("*");
|
|
1076
|
+
const mustBePrefix = !canBePrefix && word.endsWith("+");
|
|
1077
|
+
const isPrefix = canBePrefix || mustBePrefix;
|
|
1078
|
+
const canBeSuffix = word.startsWith("*");
|
|
1079
|
+
const mustBeSuffix = !canBeSuffix && word.startsWith("+");
|
|
1080
|
+
const isSuffix = canBeSuffix || mustBeSuffix;
|
|
1081
|
+
if (canBePrefix && canBeSuffix) return flag | Flags.all;
|
|
1082
|
+
if (mustBePrefix && mustBeSuffix) return flag | Flags.both;
|
|
1083
|
+
if (!isPrefix && !isSuffix) return flag | Flags.none;
|
|
1084
|
+
flag |= isPrefix && !isSuffix ? Flags.pfx : 0;
|
|
1085
|
+
flag |= isSuffix && !isPrefix ? Flags.sfx : 0;
|
|
1086
|
+
flag |= canBePrefix && !mustBeSuffix ? Flags.none : 0;
|
|
1087
|
+
flag |= canBeSuffix && !mustBePrefix ? Flags.none : 0;
|
|
1088
|
+
return flag;
|
|
1089
|
+
}
|
|
1090
|
+
const m = /* @__PURE__ */ new Map();
|
|
1091
|
+
for (const form of forms) {
|
|
1092
|
+
const k = stripCompoundAFix(form);
|
|
1093
|
+
m.set(k, flags(form, m.get(k)));
|
|
1094
|
+
}
|
|
1095
|
+
return new Map([...m.entries()].map(([form, flag]) => {
|
|
1096
|
+
return [form, applyFlags(form, flag)];
|
|
1097
|
+
}));
|
|
1098
|
+
}
|
|
1099
|
+
async function createTargetFile(destFilename, seq, compress$1) {
|
|
1100
|
+
const rel$1 = path.relative(process.cwd(), destFilename).replaceAll(path.sep, "/");
|
|
1101
|
+
getLogger()(`Writing to file ${rel$1}${compress$1 ? ".gz" : ""}`);
|
|
1102
|
+
await mkdirp(path.dirname(destFilename));
|
|
1103
|
+
await writeTextToFile(destFilename, seq, compress$1);
|
|
1104
|
+
}
|
|
1105
|
+
function createTrieCompiler(options) {
|
|
1106
|
+
return (words) => {
|
|
1107
|
+
const log$1 = getLogger();
|
|
1108
|
+
log$1("Reading Words into Trie");
|
|
1109
|
+
const base = options.base ?? 32;
|
|
1110
|
+
const version = options.trie4 ? 4 : options.trie3 ? 3 : 1;
|
|
1111
|
+
const root = Trie.buildTrie(words).root;
|
|
1112
|
+
log$1("Reduce duplicate word endings");
|
|
1113
|
+
const trie = Trie.consolidate(root);
|
|
1114
|
+
log$1("Trie compilation complete");
|
|
1115
|
+
return Trie.serializeTrie(trie, {
|
|
1116
|
+
base,
|
|
1117
|
+
comment: "Built by cspell-tools.",
|
|
1118
|
+
version
|
|
1119
|
+
});
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
//#endregion
|
|
1124
|
+
//#region src/compiler/compile.ts
|
|
1125
|
+
async function compile(request, options) {
|
|
1126
|
+
const { targets } = request;
|
|
1127
|
+
const rootDir = path.resolve(request.rootDir || ".");
|
|
1128
|
+
const cwd = options?.cwd;
|
|
1129
|
+
const targetOptions = {
|
|
1130
|
+
sort: request.sort,
|
|
1131
|
+
generateNonStrict: request.generateNonStrict,
|
|
1132
|
+
removeDuplicates: request.removeDuplicates
|
|
1133
|
+
};
|
|
1134
|
+
const conditional = options?.conditionalBuild || false;
|
|
1135
|
+
const checksumFile = resolveChecksumFile(request.checksumFile || conditional, rootDir);
|
|
1136
|
+
const dictionaryDirectives = request.dictionaryDirectives;
|
|
1137
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
1138
|
+
for (const target of targets) {
|
|
1139
|
+
if (!(options?.filter?.(target) ?? true)) continue;
|
|
1140
|
+
(await compileTarget({
|
|
1141
|
+
...targetOptions,
|
|
1142
|
+
...target
|
|
1143
|
+
}, request, {
|
|
1144
|
+
rootDir,
|
|
1145
|
+
cwd,
|
|
1146
|
+
conditional,
|
|
1147
|
+
checksumFile,
|
|
1148
|
+
dictionaryDirectives
|
|
1149
|
+
})).forEach((dep) => dependencies.add(dep));
|
|
1150
|
+
}
|
|
1151
|
+
if (checksumFile && dependencies.size) {
|
|
1152
|
+
logWithTimestamp("%s", `Update checksum: ${checksumFile}`);
|
|
1153
|
+
await updateChecksumForFiles(checksumFile, [...dependencies], { root: path.dirname(checksumFile) });
|
|
1154
|
+
}
|
|
1155
|
+
logWithTimestamp(`Complete.`);
|
|
1156
|
+
}
|
|
1157
|
+
function resolveChecksumFile(checksumFile, root) {
|
|
1158
|
+
const cFilename = typeof checksumFile === "string" && checksumFile || checksumFile && "./checksum.txt" || void 0;
|
|
1159
|
+
return cFilename && path.resolve(root, cFilename);
|
|
1160
|
+
}
|
|
1161
|
+
async function compileTarget(target, options, compileOptions) {
|
|
1162
|
+
logWithTimestamp(`Start compile: ${target.name}`);
|
|
1163
|
+
const { rootDir, cwd, checksumFile, conditional } = compileOptions;
|
|
1164
|
+
const { format, sources, trieBase, sort = true, generateNonStrict = false, excludeWordsFrom = [], excludeWordsNotFoundIn = [], excludeWordsMatchingRegex } = target;
|
|
1165
|
+
let targetDirectory = target.targetDirectory ?? cwd ?? process.cwd();
|
|
1166
|
+
targetDirectory = targetDirectory.replace("${cwd}", cwd ?? process.cwd());
|
|
1167
|
+
targetDirectory = path.resolve(rootDir, targetDirectory);
|
|
1168
|
+
const dictionaryDirectives = target.dictionaryDirectives ?? compileOptions.dictionaryDirectives;
|
|
1169
|
+
const removeDuplicates$1 = target.removeDuplicates ?? false;
|
|
1170
|
+
const excludeFromFilter = await createExcludeFilter(excludeWordsFrom);
|
|
1171
|
+
const includeFromFilter = await createIncludeFilter(excludeWordsNotFoundIn);
|
|
1172
|
+
const excludeRegexFilter = createExcludeRegexFilter(excludeWordsMatchingRegex);
|
|
1173
|
+
const excludeFilter = (word) => {
|
|
1174
|
+
return excludeFromFilter(word) && includeFromFilter(word) && excludeRegexFilter(word);
|
|
1175
|
+
};
|
|
1176
|
+
const name = normalizeTargetName(target.name);
|
|
1177
|
+
const useTrie = format.startsWith("trie");
|
|
1178
|
+
const generateCompressed = target.compress ?? false;
|
|
1179
|
+
const generateUncompressed = target.keepUncompressed ?? false;
|
|
1180
|
+
const genSet = /* @__PURE__ */ new Set();
|
|
1181
|
+
genSet.add(generateCompressed);
|
|
1182
|
+
if (generateUncompressed) genSet.add(false);
|
|
1183
|
+
const filename = resolveTarget(name, targetDirectory, useTrie);
|
|
1184
|
+
const filesToProcess = await toArray(pipeAsync(readSourceList(sources, rootDir), opMapAsync((src) => readFileSource(src, options)), opAwaitAsync()));
|
|
1185
|
+
const normalizer = normalizeTargetWords({
|
|
1186
|
+
sort: useTrie || sort,
|
|
1187
|
+
generateNonStrict,
|
|
1188
|
+
filter: excludeFilter,
|
|
1189
|
+
dictionaryDirectives
|
|
1190
|
+
});
|
|
1191
|
+
const checksumRoot = checksumFile && path.dirname(checksumFile) || rootDir;
|
|
1192
|
+
const deps = [...calculateDependencies(filename + (generateCompressed ? ".gz" : ""), filesToProcess, [...excludeWordsFrom, ...excludeWordsNotFoundIn], checksumRoot)];
|
|
1193
|
+
if (conditional && checksumFile) {
|
|
1194
|
+
if ((await checkShasumFile(checksumFile, deps, checksumRoot).catch(() => void 0))?.passed) {
|
|
1195
|
+
logWithTimestamp(`Skip ${target.name}, nothing changed.`);
|
|
1196
|
+
return [];
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
async function action(words, dst) {
|
|
1200
|
+
const data = iterableToString(pipe(words, normalizer, useTrie ? createTrieCompiler({
|
|
1201
|
+
base: trieBase,
|
|
1202
|
+
trie3: format === "trie3",
|
|
1203
|
+
trie4: format === "trie4"
|
|
1204
|
+
}) : createWordListCompiler({
|
|
1205
|
+
sort,
|
|
1206
|
+
generateNonStrict,
|
|
1207
|
+
dictionaryDirectives,
|
|
1208
|
+
removeDuplicates: removeDuplicates$1
|
|
1209
|
+
})));
|
|
1210
|
+
for (const compress$1 of genSet) await createTargetFile(dst, data, compress$1);
|
|
1211
|
+
}
|
|
1212
|
+
await processFiles({
|
|
1213
|
+
action,
|
|
1214
|
+
filesToProcess,
|
|
1215
|
+
mergeTarget: filename
|
|
1216
|
+
});
|
|
1217
|
+
if (target.bTrie) await generateBTrieFromFile(filename, {
|
|
1218
|
+
compress: true,
|
|
1219
|
+
optimize: true,
|
|
1220
|
+
useStringTable: true,
|
|
1221
|
+
...typeof target.bTrie === "object" ? target.bTrie : {}
|
|
1222
|
+
});
|
|
1223
|
+
logWithTimestamp(`Done compile: ${target.name}`);
|
|
1224
|
+
return deps;
|
|
1225
|
+
}
|
|
1226
|
+
function calculateDependencies(targetFile, filesToProcess, extraDependencyFiles, rootDir) {
|
|
1227
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
1228
|
+
addDependency(targetFile);
|
|
1229
|
+
extraDependencyFiles?.forEach((f) => addDependency(f));
|
|
1230
|
+
filesToProcess.forEach((f) => addDependency(f.src));
|
|
1231
|
+
return dependencies;
|
|
1232
|
+
function addDependency(filename) {
|
|
1233
|
+
const rel$1 = path.relative(rootDir, filename);
|
|
1234
|
+
dependencies.add(rel$1);
|
|
1235
|
+
dependencies.add(rel$1.replace(/\.aff$/, ".dic"));
|
|
1236
|
+
dependencies.add(rel$1.replace(/\.dic$/, ".aff"));
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
function rel(filePath) {
|
|
1240
|
+
return path.relative(process.cwd(), filePath);
|
|
1241
|
+
}
|
|
1242
|
+
async function processFiles({ action, filesToProcess, mergeTarget }) {
|
|
1243
|
+
const toProcess = filesToProcess;
|
|
1244
|
+
const dst = mergeTarget;
|
|
1245
|
+
await action(pipe(toProcess, opMap((ftp) => {
|
|
1246
|
+
const { src } = ftp;
|
|
1247
|
+
logWithTimestamp("Process \"%s\" to \"%s\"", rel(src), rel(dst));
|
|
1248
|
+
return ftp;
|
|
1249
|
+
}), opConcatMap(function* (ftp) {
|
|
1250
|
+
yield* ftp.words;
|
|
1251
|
+
logWithTimestamp("Done processing %s", rel(ftp.src));
|
|
1252
|
+
}), logProgress()), dst);
|
|
1253
|
+
logWithTimestamp("Done \"%s\"", rel(dst));
|
|
1254
|
+
}
|
|
1255
|
+
function resolveTarget(name, directory, useTrie) {
|
|
1256
|
+
const filename = name + (useTrie && ".trie" || ".txt");
|
|
1257
|
+
return path.resolve(directory, filename);
|
|
1258
|
+
}
|
|
1259
|
+
function readSourceList(sources, rootDir) {
|
|
1260
|
+
async function* mapSrc() {
|
|
1261
|
+
for (const src of sources) {
|
|
1262
|
+
if (isFilePath(src)) {
|
|
1263
|
+
yield { filename: path.resolve(rootDir, src) };
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
if (isFileSource(src)) {
|
|
1267
|
+
yield {
|
|
1268
|
+
...src,
|
|
1269
|
+
filename: path.resolve(rootDir, src.filename)
|
|
1270
|
+
};
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
if (isFileListSource(src)) {
|
|
1274
|
+
const { listFile, ...rest } = src;
|
|
1275
|
+
const absListFile = path.resolve(rootDir, listFile);
|
|
1276
|
+
const listFileDir = path.dirname(absListFile);
|
|
1277
|
+
const files = await readFileList(absListFile);
|
|
1278
|
+
for (const filename of files) yield {
|
|
1279
|
+
...rest,
|
|
1280
|
+
filename: path.resolve(listFileDir, filename)
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
return mapSrc();
|
|
1286
|
+
}
|
|
1287
|
+
async function readFileList(fileList) {
|
|
1288
|
+
return (await readTextFile(fileList)).split("\n").map((a) => a.trim()).filter((a) => !!a);
|
|
1289
|
+
}
|
|
1290
|
+
async function readFileSource(fileSource, sourceOptions) {
|
|
1291
|
+
const { filename, keepRawCase = sourceOptions.keepRawCase || false, split = sourceOptions.split || false, maxDepth, storeSplitWordsAsCompounds, minCompoundLength: minCompoundLength$1 } = fileSource;
|
|
1292
|
+
const legacy = split === "legacy";
|
|
1293
|
+
const readerOptions = {
|
|
1294
|
+
maxDepth,
|
|
1295
|
+
legacy,
|
|
1296
|
+
splitWords: legacy ? false : split,
|
|
1297
|
+
keepCase: keepRawCase,
|
|
1298
|
+
allowedSplitWords: await createAllowedSplitWordsFromFiles(fileSource.allowedSplitWords || sourceOptions.allowedSplitWords),
|
|
1299
|
+
storeSplitWordsAsCompounds,
|
|
1300
|
+
minCompoundLength: minCompoundLength$1
|
|
1301
|
+
};
|
|
1302
|
+
logWithTimestamp(`Reading ${path.basename(filename)}`);
|
|
1303
|
+
const stream = await streamSourceWordsFromFile(filename, readerOptions);
|
|
1304
|
+
logWithTimestamp(`Done reading ${path.basename(filename)}`);
|
|
1305
|
+
return {
|
|
1306
|
+
src: filename,
|
|
1307
|
+
words: stream
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
function normalizeTargetName(name) {
|
|
1311
|
+
return name.replace(/((\.txt|\.dic|\.aff|\.trie)(\.gz)?)?$/, "").replaceAll(/[^\p{L}\p{M}.\w\\/-]/gu, "_");
|
|
1312
|
+
}
|
|
1313
|
+
function logProgress(freq = 1e5) {
|
|
1314
|
+
function* logProgress$1(iter) {
|
|
1315
|
+
const _freq = freq;
|
|
1316
|
+
let count = 0;
|
|
1317
|
+
for (const v of iter) {
|
|
1318
|
+
++count;
|
|
1319
|
+
if (!(count % _freq)) logWithTimestamp("Progress: Words Processed - %s", count.toLocaleString());
|
|
1320
|
+
yield v;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
return logProgress$1;
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* @param excludeWordsFrom - List of files to read words from.
|
|
1327
|
+
* @returns a function that returns true if the word is not in the exclude list.
|
|
1328
|
+
*/
|
|
1329
|
+
async function createExcludeFilter(excludeWordsFrom) {
|
|
1330
|
+
if (!excludeWordsFrom || !excludeWordsFrom.length) return () => true;
|
|
1331
|
+
const excludeWords = await createWordsCollectionFromFiles(excludeWordsFrom);
|
|
1332
|
+
return (word) => !excludeWords.has(word, word.toUpperCase() !== word);
|
|
1333
|
+
}
|
|
1334
|
+
/**
|
|
1335
|
+
* @param includeWordsFrom - List of files to read words from.
|
|
1336
|
+
* @returns a function that returns true if the word is in the include list.
|
|
1337
|
+
*/
|
|
1338
|
+
async function createIncludeFilter(includeWordsFrom) {
|
|
1339
|
+
if (!includeWordsFrom || !includeWordsFrom.length) return () => true;
|
|
1340
|
+
const excludeWords = await createWordsCollectionFromFiles(includeWordsFrom);
|
|
1341
|
+
return (word) => excludeWords.has(word, word.toUpperCase() !== word);
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* @param excludeWordsMatchingRegex - List of regex patterns to exclude.
|
|
1345
|
+
* @returns a function that returns true if the word does not match any of the regex patterns.
|
|
1346
|
+
*/
|
|
1347
|
+
function createExcludeRegexFilter(excludeWordsMatchingRegex) {
|
|
1348
|
+
if (!excludeWordsMatchingRegex || !excludeWordsMatchingRegex.length) return () => true;
|
|
1349
|
+
const regexes = excludeWordsMatchingRegex.map((a) => stringToRegExp(a)).filter((a, i) => {
|
|
1350
|
+
if (a) return true;
|
|
1351
|
+
console.warn("Invalid regex: \"%s\"", excludeWordsMatchingRegex[i]);
|
|
1352
|
+
return false;
|
|
1353
|
+
});
|
|
1354
|
+
return (word) => !regexes.some((r) => r.test(word));
|
|
1355
|
+
}
|
|
1356
|
+
function iterableToString(iter) {
|
|
1357
|
+
return Array.isArray(iter) ? iter.join("") : [...iter].join("");
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
//#endregion
|
|
1361
|
+
//#region src/build.ts
|
|
1362
|
+
const moduleName = "cspell-tools";
|
|
1363
|
+
const searchPlaces = [
|
|
1364
|
+
`${moduleName}.config.json`,
|
|
1365
|
+
`${moduleName}.config.yaml`,
|
|
1366
|
+
`${moduleName}.config.yml`,
|
|
1367
|
+
"package.json"
|
|
1368
|
+
];
|
|
1369
|
+
async function build(targets, options) {
|
|
1370
|
+
const allowedTargets = new Set(targets || []);
|
|
1371
|
+
function filter(target) {
|
|
1372
|
+
return !allowedTargets.size || allowedTargets.has(target.name);
|
|
1373
|
+
}
|
|
1374
|
+
const searchDir = path.resolve(options.root || options.cwd || ".");
|
|
1375
|
+
const explorer = cosmiconfig(moduleName, {
|
|
1376
|
+
searchPlaces,
|
|
1377
|
+
stopDir: searchDir,
|
|
1378
|
+
transform: normalizeConfig
|
|
1379
|
+
});
|
|
1380
|
+
const config = await (options.config ? explorer.load(options.config) : explorer.search(searchDir));
|
|
1381
|
+
if (!config?.config) {
|
|
1382
|
+
console.error("root: %s", options.root);
|
|
1383
|
+
throw "cspell-tools.config not found.";
|
|
1384
|
+
}
|
|
1385
|
+
const configDir = path.dirname(config.filepath);
|
|
1386
|
+
await compile(normalizeRequest(config.config, path.resolve(configDir, options.root || configDir)), {
|
|
1387
|
+
filter,
|
|
1388
|
+
cwd: options.cwd,
|
|
1389
|
+
conditionalBuild: options.conditional || false
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
function normalizeRequest(buildInfo, root) {
|
|
1393
|
+
const { rootDir = root, targets = [], checksumFile } = buildInfo;
|
|
1394
|
+
return {
|
|
1395
|
+
rootDir: path.resolve(rootDir),
|
|
1396
|
+
targets,
|
|
1397
|
+
checksumFile
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
//#endregion
|
|
1402
|
+
export { reportCheckChecksumFile as a, toError as c, compressFile as d, logWithTimestamp as i, generateBTrie as l, compile as n, reportChecksumForFiles as o, setLogger as r, updateChecksumForFiles as s, build as t, OSFlags as u };
|
|
1403
|
+
//# sourceMappingURL=build-OgMPaXPZ.mjs.map
|