@sil/args
Version:
Process CLI arguments
181 lines (145 loc) • 4.48 kB
text/typescript
import fs from "fs";
import path from "path";
import { camelCase } from "@sil/case";
import { ArgValue, ArgType } from "./types";
export const fixArg = (input: ArgValue): ArgValue => {
let value: ArgValue = input;
if (typeof input == "string") {
if (input == "false") value = false as boolean;
if (input == "true") value = true as boolean;
if (parseFloat(input) > -999) value = parseFloat(input) as number;
if (input.endsWith(',')) value = input.slice(0, -1);
}
return value;
};
export const parseArguments = (inputs: string[]): ArgType => {
const args = {};
let lastKey = "";
inputs.forEach((input) => {
if (input.indexOf("--") == 0) {
const key = input.replace("--", "");
lastKey = camelCase(key);
args[lastKey] = [];
} else if (Object.keys(args).length > 0) {
args[lastKey].push(fixArg(input));
}
});
Object.keys(args).map((key) => {
if (args[key].length == 0) {
args[key] = true;
}
if (args[key].length == 1) {
args[key] = args[key][0];
}
});
return args;
};
interface argOptions {
allowedKeys: string[];
removeKeys: string[];
type: {
[key in string]: 'string' | 'number' | 'array' | 'boolean'
}
}
interface Arguments {
[key: string]: any;
}
const defaultArgOptions: argOptions = {
allowedKeys: [],
removeKeys: [],
type: {}
}
const processAllowedKeys = (args: Arguments, options: argOptions): Arguments => {
const newArgs = { ...args };
Object.keys(newArgs).forEach((key) => {
if (!options.allowedKeys.includes(key)) {
delete newArgs[key];
}
});
return newArgs;
}
const processRemoveKeys = (args: Arguments, options: argOptions): Arguments => {
const newArgs = { ...args };
options.removeKeys.forEach((key) => {
delete newArgs[key];
});
return newArgs;
}
const processTypedKeys = (args: Arguments, options: argOptions): Arguments => {
const newArgs = { ...args };
Object.keys(args).forEach((key) => {
if (options.type[key] == 'number') {
if (typeof newArgs[key] == 'boolean') {
newArgs[key] = newArgs[key] ? 1 : 0;
} else {
newArgs[key] = parseFloat(newArgs[key] as string);
}
}
else if (options.type[key] == 'array') {
if (typeof newArgs[key] !== 'object') {
if (typeof newArgs[key] == 'string') {
newArgs[key] = [(newArgs[key] as string).split(',')].flat();
}
else {
newArgs[key] = [newArgs[key]].flat();
}
}
}
else if (options.type[key] == 'boolean') {
if (typeof newArgs[key] == 'string') {
newArgs[key] = newArgs[key] == 'true';
}
if (typeof newArgs[key] == 'number') {
if (newArgs[key] > 0) {
newArgs[key] = true;
} else {
newArgs[key] = false;
}
}
newArgs[key] = Boolean(newArgs[key])
}
else if (options.type[key] == 'string') {
newArgs[key] = newArgs[key].toString();
} else {
newArgs[key] = newArgs[key];
}
});
return newArgs;
}
export const processFiles = (args: Arguments, options?: argOptions): Arguments => {
const allowedDataFiles = ['html','txt','md','json','xml','js','ts','css','scss','sass','less','csv'];
const files = Object.keys(args).reduce((acc, key) => {
if (typeof args[key] == 'string' && args[key].includes('.') && allowedDataFiles.includes(args[key].split('.').pop() as string)) {
acc[key] = args[key];
}
return acc;
}, {} as { [key: string]: string });
const newArgs = { ...args };
Object.keys(files).forEach((key) => {
const file = files[key];
const filePath = path.join(process.cwd(), file);
if (fs.existsSync(filePath)) {
newArgs[`${key}Data`] = JSON.parse(fs.readFileSync(filePath, 'utf8'));
}
else {
throw new Error(`File ${filePath} does not exist`);
}
}
);
return newArgs;
}
export const getArgs = (args: Partial<argOptions> = {}): ArgType => {
const options = { ...defaultArgOptions, ...args };
let processedArgs = parseArguments(process.argv);
if (options.allowedKeys.length) {
processedArgs = processAllowedKeys(processedArgs, options);
}
if (options.removeKeys.length) {
processedArgs = processRemoveKeys(processedArgs, options);
}
if (Object.keys(options.type).length) {
processedArgs = processTypedKeys(processedArgs, options);
}
processedArgs = processFiles(processedArgs, options)
return processedArgs;
};