@kba-tools/genesis-tool
Version:
A forked repo from ConsenSys which helps to generate genesis block for Besu Network with xemptyBlockPeriod field.
138 lines (113 loc) • 4.32 kB
text/typescript
import { QuestionTree, AnswerMap, QuestionOption } from "./types/questions";
import readline, { ReadLine } from "readline";
export class QuestionRenderer {
private _questions: QuestionTree;
private _forceDefaults: boolean;
constructor(questions: QuestionTree, forceDefaults?: boolean) {
this._questions = questions;
this._forceDefaults = !!forceDefaults;
}
async render(): Promise<AnswerMap> {
const answers: AnswerMap = {};
const rl = readline.createInterface(process.stdin, process.stdout);
try {
let question: QuestionTree | undefined = this._questions;
while (question) {
if (question.options) {
question = await this._handleOptionsQuestion(rl, question, answers);
} else if (question.transformerValidator) {
question = await this._handleTransformerQuestion(rl, question, answers);
} else {
throw new Error(`BUG! Question '${question.name}' does not include multiple choice options or a free form input transformer.`);
}
}
} finally {
rl.close();
}
return answers;
}
async _handleOptionsQuestion(rl: ReadLine, question: QuestionTree, answers: AnswerMap): Promise<QuestionTree | undefined> {
let prompt = question.prompt + "\n";
if (!question.options) {
throw new Error(`BUG! Question '${question.name}' does not include any options from which to select`);
}
let defaultOption: QuestionOption | null = null;
for (let i = 1; i < question.options.length + 1; i++) {
const option = question.options[i - 1];
if (option.default && !defaultOption) {
defaultOption = option;
}
prompt += `\t${i}. ${option.label}\n`;
}
const rawInput = await this._askQuestion(rl, prompt);
const choice = parseInt(rawInput, 10);
if (choice >= 1 && choice <= question.options.length) {
const selectedOption = question.options[choice - 1]; // we're 1 indexed on the answers here
if (question.transformerValidator) {
return this._handleTransformerAnswer(
question,
selectedOption.value,
answers,
selectedOption.nextQuestion
);
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
answers[question.name] = selectedOption.value;
return selectedOption.nextQuestion;
}
} else if (rawInput === "" && defaultOption !== null) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
answers[question.name] = defaultOption.value;
return defaultOption.nextQuestion;
} else {
return question;
}
}
async _handleTransformerQuestion(rl: ReadLine, question: QuestionTree, answers: AnswerMap): Promise<QuestionTree | undefined> {
const prompt = question.prompt;
if (!question.transformerValidator) {
throw new Error(`BUG! Question '${question.name}' does not include a transformer to handle free form entry.`);
}
const rawInput = await this._askQuestion(rl, prompt);
return this._handleTransformerAnswer(question, rawInput.trim(), answers);
}
async _handleTransformerAnswer(
question: QuestionTree,
rawInput: any, // eslint-disable-line @typescript-eslint/explicit-module-boundary-types
answers: AnswerMap,
fallbackQuestion?: QuestionTree
): Promise<QuestionTree | undefined> {
if (!question.transformerValidator) {
throw new Error(`BUG! Question '${question.name}' does not include a transformer to handle free form entry.`);
}
try {
let nextQuestion = question.transformerValidator(rawInput, answers);
if (
nextQuestion &&
typeof (nextQuestion as Promise<QuestionTree | undefined>).then === 'function'
) {
nextQuestion = await nextQuestion;
}
return nextQuestion ? nextQuestion : fallbackQuestion;
} catch {
return question;
}
}
_askQuestion(rl: ReadLine, prompt: string): Promise<string> {
const forceDefaults = this._forceDefaults;
if (!prompt.endsWith("\n")) {
prompt += "\n";
}
return new Promise((resolve, reject) => {
try {
if (forceDefaults) {
resolve("");
} else {
rl.question(prompt, resolve);
}
} catch (err) {
reject(err);
}
});
}
}