Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 97 additions & 45 deletions api/catter-c/capi.d.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,85 @@
export {};

/**
* Action to apply to a captured command.
*
* Possible values:
* - `"skip"`: Ignore the command in catter, but still execute the original command.
* - `"drop"`: Skip execution of the original command.
* - `"abort"`: Abort the whole execution and report an error.
* - `"modify"`: Replace the original command with a modified command.
*/
export type ActionType = "skip" | "drop" | "abort" | "modify";

/**
* Result returned from a command handler.
*/
export type Action = {
/**
* Replacement command data used when the action type is `"modify"`.
*/
data?: CommandData;

/**
* Action to apply to the captured command.
*/
type: ActionType;
};
export type Action =
| {
/**
* Ignore the command in catter, but still execute the original command.
*/
type: "skip";
}
| {
/**
* Skip execution of the original command.
*/
type: "drop";
}
| {
/**
* Abort the whole execution and report an error.
*/
type: "abort";
}
| {
/**
* Replace the original command with a modified command.
*/
type: "modify";

/**
* Replacement command data for the modified command.
*/
data: CommandData;
};

/**
* Execution event kind.
* Action discriminator extracted from {@link Action}.
*/
export type EventType = "finish" | "output";
export type ActionType = Action["type"];

/**
* Event emitted while a command is executing.
*/
export type ExecutionEvent = {
/**
* Standard output content for an `"output"` event.
*/
stdout?: string;

/**
* Standard error content for an `"output"` event.
*/
stderr?: string;
export type ExecutionEvent =
| {
/**
* Event category.
*/
type: "output";

/**
* Standard output content for an `"output"` event.
*/
stdout: string;

/**
* Standard error content for an `"output"` event.
*/
stderr: string;

/**
* Runtime-defined status code for this output event.
*/
code: number;
}
| {
/**
* Event category.
*/
type: "finish";

/**
* Final process exit code.
*/
code: number;
};

/**
* Process exit code. For non-finished output events, this is runtime-defined.
*/
code: number;

/**
* Event category.
*/
type: EventType;
};
/**
* Execution event discriminator extracted from {@link ExecutionEvent}.
*/
export type EventType = ExecutionEvent["type"];

/**
* Runtime capabilities exposed to the script.
Expand Down Expand Up @@ -172,12 +197,39 @@ export type CommandData = {
parent?: number;
};

/**
* Tagged command capture result passed to `service_on_command`.
*/
export type CommandCaptureResult =
| {
/**
* Indicates command capture succeeded.
*/
success: true;

/**
* Captured command payload.
*/
data: CommandData;
}
| {
/**
* Indicates command capture failed.
*/
success: false;

/**
* Failure details for command capture.
*/
error: CatterErr;
};

export function service_on_start(
cb: (config: CatterConfig) => CatterConfig,
): void;
export function service_on_finish(cb: (event: ExecutionEvent) => void): void;
export function service_on_command(
cb: (id: number, data: CommandData | CatterErr) => Action,
cb: (id: number, data: CommandCaptureResult) => Action,
): void;
export function service_on_execution(
cb: (id: number, event: ExecutionEvent) => void,
Expand Down
14 changes: 6 additions & 8 deletions api/src/scripts/cdb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,14 @@ export class CDB implements service.CatterService {
});
}

onCommand(
id: number,
data: service.CommandData | service.CatterErr,
): service.Action {
if ("msg" in data) {
io.println(`CDB received error: ${data.msg}`);
onCommand(id: number, data: service.CommandCaptureResult): service.Action {
if (!data.success) {
io.println(`CDB received error: ${data.error.msg}`);
} else {
const compiler = identify_compiler(data.exe);
const command = data.data;
const compiler = identify_compiler(command.exe);
if (compiler !== "unknown") {
this.commandArray.push([compiler, data]);
this.commandArray.push([compiler, command]);
}
}
return {
Expand Down
38 changes: 24 additions & 14 deletions api/src/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type {
CatterConfig,
CatterErr,
CatterRuntime,
CommandCaptureResult,
CommandData,
EventType,
ExecutionEvent,
Expand All @@ -20,10 +21,9 @@ import type {
Action,
ActionType,
CatterConfig,
CatterErr,
CatterRuntime,
CommandData,
EventType,
CommandCaptureResult,
ExecutionEvent,
} from "catter-c";

Expand All @@ -37,9 +37,12 @@ import type {
* const kind = ActionKind[0]; // "skip"
* ```
*/
export const ActionKind = ["skip", "drop", "abort", "modify"] as const;

const _ActionKindTypeCheck: (typeof ActionKind)[number] = {} as ActionType;
export const ActionKind = [
"skip",
"drop",
"abort",
"modify",
] as const satisfies readonly ActionType[];

/**
* Supported execution event kinds.
Expand All @@ -51,9 +54,10 @@ const _ActionKindTypeCheck: (typeof ActionKind)[number] = {} as ActionType;
* const isOutputEvent = EventKind.includes("output");
* ```
*/
export const EventKind = ["finish", "output"] as const;

const _EventKindTypeCheck: (typeof EventKind)[number] = {} as EventType;
export const EventKind = [
"finish",
"output",
] as const satisfies readonly EventType[];

/**
* Callback group for subscribing to catter lifecycle and command events.
Expand Down Expand Up @@ -96,9 +100,9 @@ export interface CatterService {
* Called when catter captures a command.
*
* @param id - Unique command identifier that can be correlated with execution events.
* @param data - Captured command data, or a {@link CatterErr} when capture fails.
* @param data - Tagged command capture result with a `success` discriminator.
*/
onCommand: (id: number, data: CommandData | CatterErr) => Action;
onCommand: (id: number, data: CommandCaptureResult) => Action;

/**
* Called when a captured command emits execution events.
Expand Down Expand Up @@ -148,20 +152,26 @@ export function onFinish(cb: (event: ExecutionEvent) => void): void {
/**
* Registers a callback that handles each captured command.
*
* @param cb - Callback invoked for each command. The first argument is the stable command ID, and the second is either the captured command payload or a capture error.
* @param cb - Callback invoked for each command. The first argument is the stable command ID, and the second is a tagged capture result (`success: true` for command data, `success: false` for capture errors).
*
* @example
* ```typescript
* onCommand((id, data) => {
* if ("msg" in data) {
* if (!data.success) {
* return { type: "skip" };
* }
* return { type: "modify", data: { ...data, argv: [...data.argv, "--verbose"] } };
* return {
* type: "modify",
* data: {
* ...data.data,
* argv: [...data.data.argv, "--verbose"],
* },
* };
* });
* ```
*/
export function onCommand(
cb: (id: number, data: CommandData | CatterErr) => Action,
cb: (id: number, data: CommandCaptureResult) => Action,
): void {
service_on_command(cb);
}
Expand Down
18 changes: 9 additions & 9 deletions api/test/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,23 @@ service.register({
onCommand(id, data) {
debug.assertThrow(id === 7);

if ("msg" in data) {
debug.assertThrow(data.msg === "spawn failed");
if (!data.success) {
debug.assertThrow(data.error.msg === "spawn failed");
commandErrorBranchSeen = true;
return { type: "skip" };
}

debug.assertThrow(data.cwd === "/tmp");
debug.assertThrow(data.exe === "clang++");
debug.assertThrow(data.argv.length === 3);
debug.assertThrow(data.argv[2] === "-c");
debug.assertThrow(data.parent === 41);
debug.assertThrow(data.data.cwd === "/tmp");
debug.assertThrow(data.data.exe === "clang++");
debug.assertThrow(data.data.argv.length === 3);
debug.assertThrow(data.data.argv[2] === "-c");
debug.assertThrow(data.data.parent === 41);

return {
type: "modify",
data: {
...data,
argv: [...data.argv, serviceArg],
...data.data,
argv: [...data.data.argv, serviceArg],
},
};
},
Expand Down
Loading
Loading