한지 v0.0.1
26151 단어 typescriptjavascriptnodecli
우리는 Inquirer, Enquirer, Prompts 및 Ink를 시도했지만 어떤 이유로 사용자 정의할 수 있는 단일 라이브러리가 없습니다. 모든 도서관은 이동할 공간 없이 특정 시나리오에 거의 초점을 맞추지 않습니다.
나는 라이브러리 내부를 파헤치는 데 시간을 보냈고 라이브러리의 핵심이 매우 단순하다는 것을 알게 되었습니다.
라이브러리 개발자로서 저는 비하인드 스토리의 95%를 처리할 수 있으며 사용자가 원하는 방식으로 텍스트를 렌더링하여 무한한 CLI 디자인을 위한 공간을 남겨둘 수 있습니다.
이것이
stdin
, stdout
및 readline
를 얻는 방법입니다. const stdin = process.stdin;
const stdout = process.stdout;
const readline = require("readline");
const rl = readline.createInterface({
input: stdin,
escapeCodeTimeout: 50,
});
readline.emitKeypressEvents(stdin, rl);
이제 모든 키 누르기 이벤트를 들을 수 있습니다.
// keystrokes are typed
type Key = {
sequence: string;
name: string | undefined;
ctrl: boolean;
meta: boolean;
shift: boolean;
};
const keypress = (str: string | undefined, key: Key) => {
// handle keypresses
}
stdin.on("keypress", keypress);
// whenever you're done, you just close readline
readline.close()
이제 텍스트를 렌더링하기 위해 stdout으로 출력합니다.
let previousText = "";
stdout.write(clear(previousText, stdout.columns));
stdout.write(string);
previousText = string;
// here's how you clear cli
const strip = (str: string) => {
const pattern = [
"[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
"(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))",
].join("|");
const RGX = new RegExp(pattern, "g");
return typeof str === "string" ? str.replace(RGX, "") : str;
};
const stringWidth = (str: string) => [...strip(str)].length;
export const clear = function (prompt: string, perLine: number) {
if (!perLine) return erase.line + cursor.to(0);
let rows = 0;
const lines = prompt.split(/\r?\n/);
for (let line of lines) {
rows += 1 + Math.floor(Math.max(stringWidth(line) - 1, 0) / perLine);
}
return erase.lines(rows);
};
디자인 없는 툴킷을 구축하는 동안 나는 여전히 사용자에게 가능한 한 많은 유틸리티를 제공하기를 원했기 때문에
select
와 같은 도메인 특정 종류의 입력에 대해 StateWrappers를 구현하기로 결정했습니다.이것이
select state wrapper
의 모습입니다. 아래는 간단한 문자열 배열을 위한 것으로, up
및 down
키 누르기를 처리하고 선택된 인덱스를 추적하고 범위를 벗어날 때마다 반복합니다.export class SelectState {
public selectedIdx = 0;
constructor(public readonly items: string[]) {}
consume(str: string | undefined, key: AnyKey): boolean {
if (!key) return false;
if (key.name === "down") {
this.selectedIdx = (this.selectedIdx + 1) % this.items.length;
return true;
}
if (key.name === "up") {
this.selectedIdx -= 1;
this.selectedIdx =
this.selectedIdx < 0 ? this.items.length - 1 : this.selectedIdx;
return true;
}
return false;
}
}
라이브러리 정의
Prompt
API입니다.export abstract class Prompt<RESULT> {
protected terminal: ITerminal | undefined;
protected requestLayout() {
this.terminal!.requestLayout();
}
attach(terminal: ITerminal) {
this.terminal = terminal;
this.onAttach(terminal);
}
detach(terminal: ITerminal) {
this.onDetach(terminal);
this.terminal = undefined;
}
onInput(str: string | undefined, key: AnyKey) {}
abstract result(): RESULT;
abstract onAttach(terminal: ITerminal): void;
abstract onDetach(terminal: ITerminal): void;
abstract render(status: "idle" | "submitted" | "aborted"): string;
}
이제 개발자로서 해야 할 일은
select
요소를 렌더링하는 방법을 정의하고 상태 및 키 누르기 관리에 대해 걱정하지 않고 라이브러리에 두고 필요할 때마다 사용자 정의 구현으로 교체하기만 하면 됩니다.export class Select extends Prompt<{ index: number; value: string }> {
private readonly data: SelectState;
constructor(items: string[]) {
super();
this.data = new SelectState(items);
}
onAttach(terminal: ITerminal) {
terminal.toggleCursor("hide");
}
onDetach(terminal: ITerminal) {
terminal.toggleCursor("show");
}
override onInput(str: string | undefined, key: any) {
super.onInput(str, key);
const invlidate = this.data.consume(str, key);
if (invlidate) {
this.requestLayout();
return;
}
}
render(status: "idle" | "submitted" | "aborted"): string {
if (status === "submitted" || status === "aborted") {
return "";
}
let text = "";
this.data.items.forEach((it, idx) => {
text +=
idx === this.data.selectedIdx ? `${color.green("❯ " + it)}` : ` ${it}`;
text += idx != this.data.items.length - 1 ? "\n" : "";
});
return text;
}
result() {
return {
index: this.data.selectedIdx,
value: this.data.items[this.data.selectedIdx]!,
};
}
}
이제 렌더링하고 사용자 입력을 기다립니다.
const result = await render(new Select(["user1", "user2" ...]))
나는 주말 동안 시간을 보내고 v0.0.1을 게시했습니다.
시도해 볼 수 있습니다 - https://www.npmjs.com/package/hanji
적절한 CTRL+C 지원 및 API 단순화와 함께 v0.0.2를 곧 중단할 예정입니다.
당신은 트위터에서 계속 지켜볼 수 있습니다-
Reference
이 문제에 관하여(한지 v0.0.1), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/_alexblokh/hanji-v001-4ago텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)