pdf-tools/cli/index.ts
2025-05-07 13:07:47 -06:00

157 lines
4.5 KiB
TypeScript

import { getAsciiArt } from "../util/asciiArt.ts";
import { toCase } from "util/caseManagement.ts";
import { ArgParser } from "./argParser.ts";
import { colorize } from "./style.ts";
import { selectMenuInteractive } from "./selectMenu.ts";
import { TerminalBlock, TerminalLayout } from "./TerminalLayout.ts";
import { cliAlert, cliLog } from "./prompts.ts";
import type { ITool } from "../types.ts";
// Register tools here (filename, no extension)
const toolRegistry: [string, Promise<{ default: ITool }>][] = [
["checkCode", import("../tools/checkCode.ts")],
["fieldRename", import("../tools/fieldRename.ts")],
["listFormFields", import("../tools/listFormFields.ts")],
];
export class PdfToolsCli {
private tools: Map<string, ITool> = new Map();
private terminalLayout = new TerminalLayout();
closeMessage?: string;
private args = ArgParser.parse(Deno.args).setFlagDefs({
help: ["-h", "--help"],
banana: ["-b", "--banana"],
});
async importTools() {
for (const [name, toolfile] of toolRegistry) {
const t = await toolfile;
try {
if (t.default) {
this.tools.set(
toCase(name, "title"),
t.default,
);
}
} catch (e) {
cliLog(e + "\n", this.terminalLayout.getBlock("body"));
}
}
}
private async banana() {
const asciiArt = await getAsciiArt("banana");
this.closeMessage = colorize(asciiArt, "yellow");
}
private async help() {
this.terminalLayout.clear();
this.ensmallenHeader("Help");
const bodyBlock = this.terminalLayout.getBlock("body");
bodyBlock.clearAll();
await cliAlert("BearMetal PDF CLI\n", bodyBlock);
await this.embiggenHeader();
}
public async run() {
try {
await this.importTools();
const titleBlock = new TerminalBlock();
this.terminalLayout.register("title", titleBlock);
const bodyBlock = new TerminalBlock();
this.terminalLayout.register("body", bodyBlock);
if (this.args.getFlag("banana")) {
titleBlock.setFixedHeight(0);
await this.banana();
return;
}
if (this.args.getFlag("help") && !this.args.task) {
await this.help();
return;
} else if (this.args.nonFlags.length === 0 || !this.args.task) {
this.embiggenHeader();
await this.toolMenu();
} else {
const task = this.args.task;
await this.runTool(toCase(task, "title"));
}
} finally {
this.cleanup();
}
}
private cleanup() {
this.terminalLayout.clearAll();
Deno.stdin.setRaw(false);
if (this.closeMessage) console.log(this.closeMessage);
}
private async toolMenu() {
const tools = this.tools.keys().toArray();
const bodyBlock = this.terminalLayout.getBlock("body");
bodyBlock.clearAll();
bodyBlock.setPreserveHistory(false);
const selected = await selectMenuInteractive(
"Choose a tool",
tools.concat(["Help", "Exit"]),
{
terminalBlock: bodyBlock,
},
);
if (!selected) return;
if (selected === "Exit") {
return;
}
await this.runTool(selected);
await this.toolMenu();
}
private async runTool(toolName: string) {
if (toolName === "Help") {
return await this.help();
}
const tool = this.tools.get(toolName);
if (tool) {
this.ensmallenHeader(tool.name + " - " + tool.description);
const bodyBlock = this.terminalLayout.getBlock("body");
bodyBlock.clearAll();
tool.setBlock?.(bodyBlock);
if (this.args.getFlag("help")) {
await tool.help?.();
} else {
await tool.run(...this.args.taskArgs);
await tool.done?.();
}
await this.embiggenHeader();
} else {
this.closeMessage = "No tool found for " + toolName;
}
}
private ensmallenHeader(subtitle: string) {
this.terminalLayout.clear();
const titleBlock = this.terminalLayout.getBlock("title");
titleBlock.clear();
titleBlock.setFixedHeight(3);
titleBlock.setLines([
colorize("BearMetal PDF Tools", "porple"),
colorize(subtitle, "gray"),
"-=".repeat(Deno.consoleSize().columns / 2),
]);
}
private async embiggenHeader() {
const titleBlock = this.terminalLayout.getBlock("title");
titleBlock.clear();
const lines: string[] = [];
for (const t of ["bearmetal:porple", "pdftools:cyan"]) {
const [name, color] = t.split(":");
const asciiArt = await getAsciiArt(name);
lines.push(...colorize(asciiArt, color).split("\n"));
}
titleBlock.setFixedHeight(lines.length);
titleBlock.setLines(lines);
}
}