cli-argv-util
Version:
Simple utility to parse command line parameters and flags (arguments vector)
136 lines (134 loc) • 6.28 kB
JavaScript
//! cli-argv-util v1.5.3 ~~ https://github.com/center-key/cli-argv-util ~~ MIT License
import { execSync } from 'node:child_process';
import chalk from 'chalk';
import fs from 'node:fs';
import path from 'node:path';
import slash from 'slash';
const cliArgvUtil = {
version: '1.5.3',
assertOk(ok, message) {
if (!ok)
throw new Error(`[replacer-util] ${message}`);
},
readPackageJson() {
const pkgFile = 'package.json';
const getPkg = () => JSON.parse(fs.readFileSync(pkgFile, 'utf-8'));
const pkg = fs.existsSync(pkgFile) ? getPkg() : {};
const fixHiddenKeys = (pkgObj) => {
const unhide = (key) => {
const newKey = key.replace(/[@./]/g, '-');
if (!pkgObj[newKey])
pkgObj[newKey] = pkgObj[key];
};
Object.keys(pkgObj).forEach(unhide);
};
if (pkg.dependencies)
fixHiddenKeys(pkg.dependencies);
if (pkg.devDependencies)
fixHiddenKeys(pkg.devDependencies);
if (!pkg.cliConfig && pkg.replacerConfig)
pkg.cliConfig = pkg.replacerConfig;
return pkg;
},
escapers: [
{ regex: /{{apos}}/g, char: "'" },
{ regex: /{{bang}}/g, char: '!' },
{ regex: /{{close-curly}}/g, char: '}' },
{ regex: /{{equals}}/g, char: '=' },
{ regex: /{{gt}}/g, char: '>' },
{ regex: /{{hash}}/g, char: '#' },
{ regex: /{{lt}}/g, char: '<' },
{ regex: /{{open-curly}}/g, char: '{' },
{ regex: /{{pipe}}/g, char: '|' },
{ regex: /{{quote}}/g, char: '"' },
{ regex: /{{semi}}/g, char: ';' },
{ regex: /{{space}}/g, char: ' ' },
],
unescape(text, pkg) {
const macroPattern = /^{{macro:(.*)}}$/;
const macros = (pkg.cliConfig?.macros ?? {});
const macroName = text.match(macroPattern)?.[1];
const macroValue = macros[macroName];
const expandedText = macroName ? macroValue : text;
const missing = macroName && !macroValue;
cliArgvUtil.assertOk(!missing, `Macro "${macroName}" used but not defined in package.json`);
const replace = (flagValue, escaper) => flagValue.replace(escaper.regex, escaper.char);
return cliArgvUtil.escapers.reduce(replace, expandedText);
},
parse(validFlags) {
const pkg = cliArgvUtil.readPackageJson();
const toCamel = (token) => token.replace(/-./g, char => char[1].toUpperCase());
const toEntry = (pair) => [toCamel(pair[0]), pair[1]];
const toPair = (flag) => flag.replace(/^--/, '').split('=');
const unescape = (value) => !value ? undefined : cliArgvUtil.unescape(value, pkg);
const args = cliArgvUtil.unquoteArgs(process.argv.slice(2));
const pairs = args.filter(arg => /^--/.test(arg)).map(toPair);
const flagMapRaw = Object.fromEntries(pairs.map(toEntry));
const flagEntries = Object.entries(flagMapRaw);
const flagMap = Object.fromEntries(flagEntries.map(pair => [pair[0], unescape(pair[1])]));
const onEntries = validFlags.map(flag => [toCamel(flag), toCamel(flag) in flagMapRaw]);
const flagOn = Object.fromEntries(onEntries);
const invalidFlag = pairs.find(pair => !validFlags.includes(pair[0]))?.[0] ?? null;
const helpMsg = '\nValid flags are --' + validFlags.join(' --');
const rawParams = args.filter(arg => !/^--/.test(arg));
const params = rawParams.map(param => cliArgvUtil.unescape(param, pkg));
return {
flagMap: flagMap,
flagMapRaw: flagMapRaw,
flagOn: flagOn,
invalidFlag: invalidFlag,
invalidFlagMsg: invalidFlag ? 'Invalid flag: --' + invalidFlag + helpMsg : null,
params: params,
paramCount: params.length,
};
},
run(packageJson, posix) {
const name = Object.keys(packageJson.bin).sort()[0];
const command = process.platform === 'win32' ? posix.replaceAll('\\ ', '" "') : posix;
return execSync(command.replace(name, 'node bin/cli.js'), { stdio: 'inherit' });
},
readFolder(folder) {
return fs.readdirSync(folder, { recursive: true }).map(file => slash(String(file))).sort();
},
cleanPath(name) {
return slash(path.normalize(name)).trim().replace(/\/$/, '');
},
colorizePath(pathname) {
return chalk.green(path.dirname(pathname) + '/') + chalk.white(path.basename(pathname));
},
calcAncestor(sourceFile, targetFile) {
const index = Array.from(sourceFile).findIndex((char, i) => targetFile[i] !== char);
const substr = index === -1 ? sourceFile : sourceFile.substring(0, index);
const common = sourceFile.substring(0, substr.lastIndexOf('/'));
const len = common.length ? common.length + 1 : 0;
const source = sourceFile.substring(len);
const target = targetFile.substring(len);
const renamed = path.basename(sourceFile) !== path.basename(targetFile);
const filename = renamed ? null : path.basename(sourceFile);
const folder = path.dirname(target);
const dest = renamed ? target : (folder === '.' ? filename : folder + '/');
const color = {
common: common ? chalk.blue(common) + chalk.gray.bold(': ') : '',
source: chalk.white(source),
arrow: chalk.gray.bold('→'),
target: chalk.green(dest),
};
const output = `${color.source} ${color.arrow} ${color.target}`;
const message = color.common + output;
return { common, source, target, renamed, filename, output, message, color };
},
unquoteArgs(args) {
const unquote = (builder, nextArg) => {
const arg = nextArg.replace(/^'/, '').replace(/'$/, '');
const last = builder[1].length - 1;
if (builder[0])
builder[1][last] = `${builder[1][last]} ${arg}`;
else
builder[1].push(arg);
const quoteMode = (/^'/.test(nextArg) || builder[0]) && !/'$/.test(nextArg);
return [quoteMode, builder[1]];
};
return args.reduce(unquote, [false, []])[1];
},
};
export { cliArgvUtil };