Skip to content

Commit

Permalink
feat: simple register command literal completion PoC
Browse files Browse the repository at this point in the history
  • Loading branch information
curtislarson committed Jan 3, 2023
1 parent 3a94986 commit cd24df6
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 9 deletions.
14 changes: 14 additions & 0 deletions mod.test.ts
Expand Up @@ -220,6 +220,20 @@ Deno.test("command builder should build", async () => {
}
});

Deno.test("command builder types", () => {
const builder = new CommandBuilder().registerCommand(
"true",
() => Promise.resolve({ kind: "continue", code: 0 }),
);

builder.command("true");

const builder2 = new CommandBuilder().registerCommands({
true: () => Promise.resolve({ kind: "continue", code: 0 }),
false: () => Promise.resolve({ kind: "continue", code: 1 }),
});
});

Deno.test("should handle boolean list 'or'", async () => {
{
const output = await $`deno eval 'Deno.exit(1)' || deno eval 'console.log(5)'`.text();
Expand Down
31 changes: 22 additions & 9 deletions src/command.ts
Expand Up @@ -40,6 +40,17 @@ interface CommandBuilderState {
timeout: number | undefined;
}

type LiteralUnion<LiteralType, BaseType extends null | undefined | string | number | boolean | symbol | bigint> =
| LiteralType
| (BaseType & Record<never, never>);

interface RegisterCommandBuilder<Commands extends string> {
registerCommand: <Command extends string>(
command: Command extends Commands ? never : Command,
handlerFn: CommandHandler,
) => RegisterCommandBuilder<Commands | Command>;
}

const textDecoder = new TextDecoder();

const builtInCommands = {
Expand All @@ -55,6 +66,7 @@ const builtInCommands = {
mv: mvCommand,
pwd: pwdCommand,
};
type BuildInCommandName = keyof typeof builtInCommands;

/**
* Underlying builder API for executing commands.
Expand All @@ -76,7 +88,8 @@ const builtInCommands = {
* console.log(await builder.env("MY_VAR", "6").text());
* ```
*/
export class CommandBuilder implements PromiseLike<CommandResult> {
export class CommandBuilder<Commands extends string = BuildInCommandName>
implements PromiseLike<CommandResult>, RegisterCommandBuilder<Commands> {
#state: Readonly<CommandBuilderState> = {
command: undefined,
stdin: "inherit",
Expand Down Expand Up @@ -111,8 +124,8 @@ export class CommandBuilder implements PromiseLike<CommandResult> {
};
}

#newWithState(action: (state: CommandBuilderState) => void): CommandBuilder {
const builder = new CommandBuilder();
#newWithState<Commands extends string>(action: (state: CommandBuilderState) => void): CommandBuilder<Commands> {
const builder = new CommandBuilder<Commands>();
const state = this.#getClonedState();
action(state);
builder.#state = state;
Expand Down Expand Up @@ -141,20 +154,20 @@ export class CommandBuilder implements PromiseLike<CommandResult> {
/**
* Register a command.
*/
registerCommand(command: string, handleFn: CommandHandler) {
registerCommand<Command extends string>(command: Command, handleFn: CommandHandler) {
validateCommandName(command);
return this.#newWithState((state) => {
return this.#newWithState<Commands | Command>((state) => {
state.commands[command] = handleFn;
});
}

/**
* Register multilple commands.
*/
registerCommands(commands: Record<string, CommandHandler>) {
let command: CommandBuilder = this;
registerCommands<Command extends string>(commands: Record<Command, CommandHandler>) {
let command: CommandBuilder<Commands | Command> = this;
for (const [key, value] of Object.entries(commands)) {
command = command.registerCommand(key, value);
command = command.registerCommand(key, value as CommandHandler);
}
return command;
}
Expand All @@ -169,7 +182,7 @@ export class CommandBuilder implements PromiseLike<CommandResult> {
}

/** Sets the raw command to execute. */
command(command: string | string[]) {
command(command: LiteralUnion<Commands, string> | string[]) {
return this.#newWithState((state) => {
if (typeof command === "string") {
state.command = command;
Expand Down

0 comments on commit cd24df6

Please sign in to comment.