UNPKG

@bearz/exec

Version:

The exec module makes it easy to spawn child_processes across different runtimes and different operating systems.

311 lines (310 loc) 10 kB
/** * The `splat` module provides a function to convert an object * to an array of command line arguments. * * @module */ import { dasherize } from "@bearz/strings/dasherize"; import { splitArguments } from "./split_arguments.js"; /** * Special keys in a splat object that use * symbols to avoid conflicts with other keys * and to provide a way to access the values. * * @example * ```ts * * const args = { * [SplatSymbols.command]: "run", * [SplatSymbols.arguments]: ["task"], * yes: true, * } * * splat(args); // ["run", "task", "--yes"] */ export const SplatSymbols = { command: Symbol("@@command"), /** * The key for positional arguments values * in a splat object. */ args: Symbol("@@args"), /** * The key for argument names in a splat object. */ argNames: Symbol("@@arg-names"), /** * The extra arguments key in a splat object. * Extra arguments use the '--' value on the * command line to separate the extra arguments * e.g. `command -- --option value` */ extraArgs: Symbol("--"), /** * The remaining arguments key in a splat object. * These are the arguments appended to the end of * the command line arguments, after the arguments * and options, but before the extra arguments. */ remainingArgs: Symbol("_"), }; const match = (array, value) => array.some((element) => (element instanceof RegExp ? element.test(value) : element === value)); /** * Converts an object to an `string[]` of command line arguments. * * @description * This is a modified version of the dargs npm package. Its useful for converting an object to an array of command line arguments * especially when using typescript interfaces to provide intellisense and type checking for command line arguments * for an executable or commands in an executable. * * The code https://github.com/sindresorhus/dargs which is under under MIT License. * The original code is Copyrighted under (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com) * @param object The object to convert. * @param options The {@linkcode SplatOptions} to use for the conversion. * @returns An array of command line arguments. * @example * ```ts * let args = splat({ foo: "bar" }); * console.log(args); // ["--foo", "bar"] * * args = splat({ * '*': ['foo', 'bar'], // positional arguments * foo: "bar", // option * yes: true, // flag * '_': ["baz"], // remaining arguments * '--': ["--baz"], // extra arguments * }) * * console.log(args); // ["foo", "bar", "--foo", "bar", "--yes", "baz", "--", "--baz"] * * args = splat({ * [SplatSymbols.command]: "run", * [SplatSymbols.arguments]: ["task1", "task2"], * yes: true * }); * * console.log(args); // ["run", "task", "task2" "--yes"] * * args = splat({ * "foo": "bar", * "test": "baz", * splat: { * argumentNames: ["foo"], * assign: "=", * } * }) * * console.log(args); // ["bar", "--foo=baz"] * * ``` */ export function splat(object, options) { const optionValues = []; const splatted = []; let remainingArgs = []; let extraArgs = []; if (object.splat) { options = { ...object.splat, ...options, }; delete object.splat; } options = { shortFlag: true, prefix: "--", ...options, }; let commands = []; let argumentNames = []; if (options.command) { if (typeof options.command === "string") { commands = splitArguments(options.command); } else { commands = options.command; } } if (options.argumentNames) { argumentNames = options.argumentNames; } if (object[SplatSymbols.argNames] && Array.isArray(object[SplatSymbols.argNames])) { argumentNames = object[SplatSymbols.argNames]; } if (object[SplatSymbols.command]) { if (typeof object[SplatSymbols.command] === "string") { commands = splitArguments(object[SplatSymbols.command]); } else if (Array.isArray(object[SplatSymbols.command])) { commands = object[SplatSymbols.command]; } } if (object[SplatSymbols.remainingArgs] && Array.isArray(object[SplatSymbols.remainingArgs])) { remainingArgs = object[SplatSymbols.remainingArgs]; } if (object[SplatSymbols.extraArgs] && Array.isArray(object[SplatSymbols.extraArgs])) { extraArgs = object[SplatSymbols.extraArgs]; } const makeArguments = (key, value) => { const prefix = options?.shortFlag && key.length === 1 ? "-" : options?.prefix; const theKey = options?.preserveCase ? key : dasherize(key); key = prefix + theKey; if (options?.assign) { optionValues.push(key + (value ? `${options.assign}${value}` : "")); } else { optionValues.push(key); if (value) { optionValues.push(String(value)); } } }; const makeAliasArg = (key, value) => { if (!key.startsWith("-") && !key.startsWith("/")) { key = "-" + key; } if (options?.assign) { optionValues.push(`${key}${options.assign}${value}`); } else { optionValues.push(`${key}`); if (value) { optionValues.push(String(value)); } } }; let isNoFlag = (_key) => { return false; }; if (options.noFlags !== undefined) { if (options.noFlagValues === undefined) { options.noFlagValues = { t: "true", f: "false" }; } if (Array.isArray(options.noFlags)) { isNoFlag = (key) => options.noFlags.includes(key); } else { isNoFlag = (_key) => true; } } let positionalArgs = []; if (object[SplatSymbols.args] && Array.isArray(object[SplatSymbols.args])) { positionalArgs = object[SplatSymbols.args]; } else if (argumentNames.length > 0) { positionalArgs.length = argumentNames.length; } for (let [key, value] of Object.entries(object)) { let pushArguments = makeArguments; if (typeof key === "symbol") { continue; } if (key === "*") { if (Array.isArray(value)) { positionalArgs.push(...value); } if (typeof value === "string") { positionalArgs.push(value); } continue; } if (argumentNames.length && argumentNames.includes(key)) { // ensure the order of the arguments let index = argumentNames.indexOf(key); if (value) { if (Array.isArray(value)) { for (const val of value) { positionalArgs[index++] = String(val); } continue; } positionalArgs[index] = String(value); } continue; } if (Array.isArray(options.excludes) && match(options.excludes, key)) { continue; } if (Array.isArray(options.includes) && !match(options.includes, key)) { continue; } if (typeof options.aliases === "object" && options.aliases[key]) { key = options.aliases[key]; pushArguments = makeAliasArg; } if (key === "--") { if (!Array.isArray(value)) { throw new TypeError(`Expected key \`--\` to be Array, got ${typeof value}`); } extraArgs = value; continue; } if (key === "_") { if (typeof value === "string") { remainingArgs = [value]; continue; } if (!Array.isArray(value)) { throw new TypeError(`Expected key \`_\` to be Array, got ${typeof value}`); } remainingArgs = value; continue; } if (value === true && !options.ignoreTrue) { if (isNoFlag(key)) { pushArguments(key, options.noFlagValues?.t); } else { pushArguments(key); } } if (value === false && !options.ignoreFalse) { if (isNoFlag(key)) { pushArguments(key, options.noFlagValues?.f); } else { pushArguments(`no-${key}`); } } if (typeof value === "string") { pushArguments(key, value); } if (typeof value === "number" && !Number.isNaN(value)) { pushArguments(key, String(value)); } if (typeof value === "bigint" && !Number.isNaN(value)) { pushArguments(key, String(value)); } if (Array.isArray(value)) { for (const arrayValue of value) { pushArguments(key, arrayValue); } } } if (commands.length) { splatted.push(...commands); } const normalizedArgs = []; // ensure the order of the arguments for (const arg of positionalArgs) { if (arg) { if (Array.isArray(arg)) { normalizedArgs.push(...arg.map((a) => String(a))); } else { normalizedArgs.push(String(arg)); } } } if (!options.appendArguments && normalizedArgs.length) { splatted.push(...normalizedArgs); } if (optionValues.length) { splatted.push(...optionValues); } if (options.appendArguments && normalizedArgs.length) { splatted.push(...normalizedArgs); } for (const argument of remainingArgs) { splatted.push(String(argument)); } if (extraArgs.length > 0) { splatted.push("--"); } for (const argument of extraArgs) { splatted.push(String(argument)); } return splatted; }