clivo
Version:
Simple CLI tools library
209 lines (207 loc) • 5.8 kB
JavaScript
// src/cli.ts
function parseCli(params) {
const keyValues = {};
const args = params.args.slice(params.parseFrom || 2);
const nameByLetter = {};
const optionNames = /* @__PURE__ */ new Set();
const optionLetters = /* @__PURE__ */ new Set();
if (params.options != null) {
for (const option of params.options) {
if (optionNames.has(option.name)) {
throw new Error(`Duplicate option name: ${option.name}`);
}
optionNames.add(option.name);
if (option.letter) {
if (optionLetters.has(option.letter)) {
throw new Error(`Duplicate option letter: ${option.letter}`);
}
optionLetters.add(option.letter);
nameByLetter[option.letter] = option.name;
}
}
}
let followOptions = null;
const addValue = (key, value) => {
if (!keyValues[key]) {
keyValues[key] = [];
}
keyValues[key].push(value);
};
const addValues = (key, values) => {
if (!keyValues[key]) {
keyValues[key] = [];
}
if (values.length > 0) {
keyValues[key].push(...values);
} else {
keyValues[key].push("yes");
}
};
const addFollowOptions = (value) => {
if (followOptions == null) {
return;
}
if (params.equalSignValuesOnly && value != null) {
addValue("_", value);
return;
}
for (const key of followOptions) {
if (optionNames.has(key) || params.acceptUnspecifiedOptions) {
if (value != null) {
addValue(key, value);
} else if (keyValues[key] == null) {
addValue(key, "yes");
}
}
}
};
for (const arg of args) {
if (arg.startsWith("--")) {
if (followOptions != null) {
addFollowOptions();
}
const [key, ...values] = arg.slice(2).split("=");
followOptions = [key];
if (values.length > 0 && optionNames.has(key) || params.acceptUnspecifiedOptions) {
addValues(key, values);
}
} else if (arg.startsWith("-")) {
if (followOptions != null) {
addFollowOptions();
}
followOptions = null;
const [letters, ...values] = arg.slice(1).split("=");
for (const letter of letters) {
const key = nameByLetter[letter] || params.acceptUnspecifiedOptions && letter;
if (key) {
if (followOptions == null) {
followOptions = [];
}
if (values.length > 0) {
addValues(key, values);
} else {
followOptions.push(key);
}
}
}
} else if (followOptions != null) {
addFollowOptions(arg);
} else {
addValue("_", arg);
}
}
if (followOptions != null) {
addFollowOptions();
}
return keyValues;
}
// src/prompts.ts
import { EventEmitter } from "events";
import readline from "readline";
var emitter = new EventEmitter();
function listenClivoEvent(event, listener) {
emitter.on(event, listener);
}
function registerSignals(readlineInterface) {
const sigintListener = () => {
emitter.emit("clivoCancel");
readlineInterface.close();
};
readlineInterface.on("SIGINT", sigintListener);
}
async function promptOptions(message, choices) {
return new Promise(
(resolve, reject) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log(message);
choices.forEach((choice, index) => {
console.log(`${index + 1}. ${choice.label ?? choice.name}`);
});
registerSignals(rl);
rl.question("Select an option: ", (answer) => {
const choiceIndex = parseInt(answer, 10) - 1;
rl.close();
if (choiceIndex >= 0 && choiceIndex < choices.length) {
resolve(choices[choiceIndex]);
} else {
reject(new Error("Invalid option, please try again."));
}
});
}
).catch(() => promptOptions(message, choices));
}
async function promptText(message) {
return new Promise((resolve) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
registerSignals(rl);
rl.question(`${message}: `, (answer) => {
rl.close();
resolve(answer);
});
});
}
async function promptNumber(message) {
return new Promise(
(resolve, reject) => {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
registerSignals(rl);
rl.question(`${message}: `, (answer) => {
rl.close();
const number = parseFloat(answer);
if (!isNaN(number)) {
resolve(number);
} else {
reject(new Error("Invalid number, please try again."));
}
});
}
).catch(() => promptNumber(message));
}
async function promptWorkflow(message, workflow) {
console.log(message);
const results = [];
for (const step of workflow) {
if (step.type === "options" && step.choices) {
const choice = await promptOptions(step.message, step.choices);
results.push(choice);
} else if (step.type === "text") {
const text = await promptText(step.message);
results.push(text);
} else if (step.type === "number") {
const number = await promptNumber(step.message);
results.push(number);
}
}
return results;
}
async function promptMenu(message, menu) {
const choices = menu.map((item, index) => {
return {
label: item.label,
name: "act" + (index + 1)
};
});
const choice = await promptOptions(message, choices);
const selectedItem = menu.find((item) => item.label === choice.label);
if (selectedItem) {
await selectedItem.action();
}
}
export {
listenClivoEvent,
parseCli,
promptMenu,
promptNumber,
promptOptions,
promptText,
promptWorkflow
};