@cspell/cspell-tools
Version:
Tools to assist with the development of cSpell
292 lines (283 loc) • 12.1 kB
JavaScript
import { a as reportCheckChecksumFile, c as toError, d as compressFile, i as logWithTimestamp, l as generateBTrie, n as compile, o as reportChecksumForFiles, r as setLogger, s as updateChecksumForFiles, t as build, u as OSFlags } from "./build-BGL2P0c2.mjs";
import { readFileSync } from "node:fs";
import { CommanderError, Option } from "commander";
import { writeFile } from "node:fs/promises";
import * as path from "node:path";
import { glob } from "glob";
import { opConcatMap, pipe } from "@cspell/cspell-pipe/sync";
import YAML from "yaml";
//#region src/util/globP.ts
function globP(pattern, options) {
return glob((Array.isArray(pattern) ? pattern : [pattern]).map((pattern) => pattern.replaceAll("\\", "/")), options);
}
//#endregion
//#region src/gzip/gzip.ts
/**
* GZip files matching the given globs.
* @param globs - array of globs to gzip
* @param os - optional OS flag for the gzip file
*/
async function gzipFiles(globs, os) {
const files = await globP(globs, { nodir: true });
for (const fileName of files) await compressFile(fileName, os);
}
//#endregion
//#region src/compiler/createCompileRequest.ts
function createCompileRequest(sourceFiles, options) {
options = { ...options };
options.maxDepth ??= options.max_depth;
const { maxDepth, split, keepRawCase, useLegacySplitter } = options;
return {
targets: calcTargets([...sourceFiles, ...(options.listFile || []).map((listFile) => ({ listFile }))], options),
maxDepth: parseNumber(maxDepth),
split: useLegacySplitter ? "legacy" : split,
keepRawCase
};
}
function calcTargets(sources, options) {
const { merge, output = ".", experimental = [] } = options;
const generateNonStrict = experimental.includes("compound") || void 0;
const format = calcFormat(options);
const sort = format === "plaintext" && options.sort || void 0;
if (merge) return [{
name: merge,
targetDirectory: output,
compress: options.compress,
format,
sources: sources.map(normalizeSource),
sort,
trieBase: parseNumber(options.trieBase),
generateNonStrict
}];
return sources.map((source) => {
return {
name: toTargetName(baseNameOfSource(source)),
targetDirectory: output,
compress: options.compress,
format,
sources: [normalizeSource(source)],
sort: options.sort,
trieBase: parseNumber(options.trieBase),
generateNonStrict
};
});
}
function calcFormat(options) {
return options.trie4 && "trie4" || options.trie3 && "trie3" || options.trie && "trie" || "plaintext";
}
function toTargetName(sourceFile) {
return path.basename(sourceFile).replace(/((\.txt|\.dic|\.aff|\.trie)(\.gz)?)?$/, "");
}
function parseNumber(s) {
const n = Number.parseInt(s ?? "");
return Number.isNaN(n) ? void 0 : n;
}
function baseNameOfSource(source) {
return typeof source === "string" ? source : isFileSource(source) ? source.filename : source.listFile;
}
function isFileSource(source) {
return typeof source !== "string" && source.filename !== void 0;
}
function normalizeSource(source) {
if (typeof source === "string") return normalizeSourcePath(source);
if (isFileSource(source)) return {
...source,
filename: normalizeSourcePath(source.filename)
};
return {
...source,
listFile: normalizeSourcePath(source.listFile)
};
}
function normalizeSourcePath(source) {
const cwd = process.cwd();
return path.relative(cwd, source).split("\\").join("/");
}
//#endregion
//#region src/config/config.ts
const configFileSchemaURL = "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/packages/cspell-tools/cspell-tools.config.schema.json";
//#endregion
//#region src/FeatureFlags/FeatureFlags.ts
let systemFeatureFlags;
/**
* Feature Flags are used to turn on/off features.
* These are primarily used before a feature has been fully released.
*/
var FeatureFlags = class FeatureFlags {
flags;
flagValues = /* @__PURE__ */ new Map();
constructor(flags = []) {
this.flags = new Map(flags.map((f) => [f.name, f]));
}
register(flagOrName, description) {
if (typeof flagOrName === "string") return this.register({
name: flagOrName,
description: description || ""
});
this.flags.set(flagOrName.name, flagOrName);
return this;
}
registerFeatures(flags) {
flags.forEach((flag) => this.register(flag));
return this;
}
getFlag(flag) {
return this.flagValues.get(flag);
}
getFlagBool(flag) {
return toBool(this.getFlag(flag));
}
setFlag(flag, value = true) {
if (!this.flags.has(flag)) throw new UnknownFeatureFlagError(flag);
this.flagValues.set(flag, toBool(value) ?? value);
return this;
}
getFlagInfo(flag) {
return this.flags.get(flag);
}
getFlags() {
return [...this.flags.values()];
}
getFlagValues() {
return new Map(this.flagValues);
}
reset() {
this.flagValues.clear();
return this;
}
help() {
const flags = [{
name: "Name",
description: "Description"
}, ...this.flags.values()].sort((a, b) => a.name < b.name ? -1 : 1);
const nameColWidth = flags.map((f) => f.name.length).reduce((a, b) => Math.max(a, b), 0) + 1;
return `Valid Flags:\n${flags.map((f) => `- ${f.name}${" ".repeat(nameColWidth - f.name.length)} ${f.description}`).join("\n")}`;
}
fork() {
const fork = new FeatureFlags([...this.flags.values()]);
for (const [key, value] of this.flagValues) fork.flagValues.set(key, value);
return fork;
}
};
var UnknownFeatureFlagError = class extends Error {
constructor(flag) {
super(`Unknown feature flag: ${flag}`);
this.flag = flag;
}
};
function getSystemFeatureFlags() {
return systemFeatureFlags || (systemFeatureFlags = createFeatureFlags());
}
function createFeatureFlags() {
return new FeatureFlags();
}
const boolValues = {
0: false,
1: true,
f: false,
false: false,
n: false,
no: false,
t: true,
true: true,
y: true,
yes: true
};
function toBool(value) {
if (typeof value !== "string") return value;
return boolValues[value.toLowerCase()];
}
//#endregion
//#region src/FeatureFlags/parseFlags.ts
const splitFlag = /[:=]/;
const leadingEql = /^=/;
function parseFlags(ff, flags) {
for (const flag of flags) {
const [name, value] = flag.replace(leadingEql, "").split(splitFlag, 2);
try {
ff.setFlag(name, value);
} catch (e) {
if (e instanceof UnknownFeatureFlagError) {
console.error(e.message);
console.error(ff.help());
}
throw e;
}
}
return ff;
}
//#endregion
//#region src/compile.ts
getSystemFeatureFlags().register("compound", "Enable compound dictionary sources.");
const defaultConfigFile = "cspell-tools.config.yaml";
const configFileHeader = `# yaml-language-server: $schema=${configFileSchemaURL}\n\n`;
async function processCompileAction(src, options, featureFlags) {
parseFlags(featureFlags || getSystemFeatureFlags(), options.experimental || []);
return useCompile(src, options);
}
async function useCompile(src, options) {
console.log("Compile:\n output: %s\n compress: %s\n files:\n %s", options.output || "default", options.compress ? "true" : "false", src.join("\n "));
if (options.listFile && options.listFile.length) console.log(" list files:\n %s", options.listFile.join("\n "));
console.log("\n\n");
const request = createCompileRequest([...pipe(await Promise.all(src.map((s) => globP(s))), opConcatMap((a) => a))], options);
return options.init ? initConfig(request) : compile(request);
}
async function initConfig(runConfig) {
const { $schema = configFileSchemaURL, ...cfg } = runConfig;
const config = {
$schema,
...cfg
};
const content = configFileHeader + YAML.stringify(config, void 0, 2);
console.log("Writing config file: %s", defaultConfigFile);
await writeFile(defaultConfigFile, content);
console.log(`Init complete.
To build, use:
cspell-tools-cli build
`);
}
//#endregion
//#region src/app.ts
const npmPackageRaw = readFileSync(new URL("../package.json", import.meta.url), "utf8");
const npmPackage = JSON.parse(npmPackageRaw);
setLogger(logWithTimestamp);
function collect(value, previous) {
return [...previous, value];
}
function addCompileOptions(compileCommand) {
return compileCommand.option("-o, --output <path>", "Specify the output directory, otherwise files are written back to the same location.").option("-n, --no-compress", "By default the files are GZipped, this will turn off GZ compression.").option("-m, --max_depth <limit>", "Maximum depth to apply suffix rules.").option("-M, --merge <target>", "Merge all files into a single target file (extensions are applied)").option("--split", "Split each line", void 0).option("--no-split", "Treat each line as a dictionary entry, do not split").option("--use-legacy-splitter", "Do not use legacy line splitter logic.").option("--keep-raw-case", "Do not normalize words before adding them to dictionary.").option("-x, --experimental <flag>", "Experimental flags, used for testing new concepts. Flags: compound", collect, []).option("--trie3", "Use file format trie3", false).option("--trie4", "Use file format trie4", false).option("--trie-base <number>", "Advanced: Set the trie base number. A value between 10 and 36").option("--list-file <filename...>", "Path to a file that contains the list of files to compile. A list file contains one file per line.").option("--init", "Create a build command `cspell-tools.config.yaml` file based upon the options given instead of building.");
}
async function run(program, argv, flags) {
async function handleGzip(files) {
try {
await gzipFiles(files, OSFlags.Unix);
} catch (error) {
const err = toError(error);
program.error(err.message);
}
}
async function shasum(files, options) {
const report = options.check ? await reportCheckChecksumFile(options.check, files, options) : options.update ? await updateChecksumForFiles(options.update, files, options) : await reportChecksumForFiles(files, options);
console.log("%s", report.report);
if (!report.passed) throw new CommanderError(1, "Failed Checksum", "One or more files had issues.");
}
program.exitOverride();
program.version(npmPackage.version);
addCompileOptions(program.command("compile [src...]").description("Compile words into a cspell dictionary files.")).option("--trie", "Compile into a trie file.", false).option("--no-sort", "Do not sort the result").action((src, options) => {
return processCompileAction(src, options, flags);
});
addCompileOptions(program.command("compile-trie [src...]").description("Compile words lists or Hunspell dictionary into trie files used by cspell.\nAlias of `compile --trie`")).action((src, options) => {
return processCompileAction(src, {
...options,
trie: true
}, flags);
});
program.command("build [targets...]").description("Build the targets defined in the run configuration.").option("-c, --config <path to run configuration>", "Specify the run configuration file.").option("--conditional", "Conditional build.").option("-r, --root <directory>", "Specify the run directory").action(build);
program.command("gzip <files...>").description("GZip files while keeping the original.").action(handleGzip);
program.command("btrie [files...]").description("Generate BTrie files from word list files.").option("-n, --no-compress", "By default the files are GZipped, this will turn off GZ compression.").option("--no-optimize", "Do not try to optimize.").option("--no-use-string-table", "Do not use a string table in the BTrie.").option("-o, --output <path>", "Specify the output directory, otherwise files are written back to the same location.").action(generateBTrie);
program.command("shasum [files...]").description("Calculate the checksum for files.").option("--list-file <list-file.txt...>", "Specify one or more files that contain paths of files to check.").option("-c, --check <checksum.txt>", "Verify the checksum of files against those stored in the checksum.txt file.").addOption(new Option("-u, --update <checksum.txt>", "Update checksums found in the file.").conflicts("--check")).option("-r, --root <root>", "Specify the root to use for relative paths. The current working directory is used by default.").action(shasum);
await program.parseAsync(argv);
}
//#endregion
export { run };
//# sourceMappingURL=app.mjs.map