Skip to content

Commit

Permalink
Merge branch 'main' into feat_which_command
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret committed Apr 6, 2024
2 parents 649abff + 2d5a6ba commit 99b94b5
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 22 deletions.
31 changes: 25 additions & 6 deletions mod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@ Deno.test("should throw when exit code is non-zero", async () => {
);
});

Deno.test("should error in the shell when the command can't be found", async () => {
const output = await $`nonexistentcommanddaxtest`.noThrow().stderr("piped");
assertEquals(output.code, 127);
assertEquals(output.stderr, "dax: nonexistentcommanddaxtest: command not found\n");
});

Deno.test("throws when providing an object that doesn't override toString", async () => {
{
const obj1 = {};
Expand Down Expand Up @@ -263,6 +269,11 @@ Deno.test("CommandBuilder#json()", async () => {
assertEquals(output, { test: 5 });
});

Deno.test("CommandBuilder#json('stderr')", async () => {
const output = await $`deno eval "console.error(JSON.stringify({ test: 5 }));"`.json("stderr");
assertEquals(output, { test: 5 });
});

Deno.test("stderrJson", async () => {
const output = await $`deno eval "console.error(JSON.stringify({ test: 5 }));"`.stderr("piped");
assertEquals(output.stderrJson, { test: 5 });
Expand Down Expand Up @@ -490,11 +501,9 @@ Deno.test("should not allow invalid command names", () => {

Deno.test("should unregister commands", async () => {
const builder = new CommandBuilder().unregisterCommand("export").noThrow();
await assertRejects(
async () => await builder.command("export somewhere"),
Error,
"Command not found: export",
);
const output = await builder.command("export somewhere").stderr("piped");
assertEquals(output.code, 127);
assertEquals(output.stderr, "dax: export: command not found\n");
});

Deno.test("sleep command", async () => {
Expand Down Expand Up @@ -1124,6 +1133,16 @@ Deno.test("command .lines()", async () => {
assertEquals(result, ["1", "2"]);
});

Deno.test("command .lines('stderr')", async () => {
const result = await $`deno eval "console.error(1); console.error(2)"`.lines("stderr");
assertEquals(result, ["1", "2"]);
});

Deno.test("command .lines('combined')", async () => {
const result = await $`deno eval "console.log(1); console.error(2)"`.lines("combined");
assertEquals(result, ["1", "2"]);
});

Deno.test("piping in command", async () => {
await withTempDir(async (tempDir) => {
const result = await $`echo 1 | cat - > output.txt`.cwd(tempDir).text();
Expand Down Expand Up @@ -1438,7 +1457,7 @@ Deno.test("shebang support", async (t) => {
.text();
},
Error,
"Command not found: deno run",
"Exited with code: 127",
);
});

Expand Down
31 changes: 18 additions & 13 deletions src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { symbols } from "./common.ts";
import { whichCommand } from "./commands/which.ts";

type BufferStdio = "inherit" | "null" | "streamed" | Buffer;
type StreamKind = "stdout" | "stderr" | "combined";

class Deferred<T> {
#create: () => T | Promise<T>;
Expand Down Expand Up @@ -492,12 +493,13 @@ export class CommandBuilder implements PromiseLike<CommandResult> {
* await $`echo 1`.quiet("stderr");
* ```
*/
quiet(kind: "stdout" | "stderr" | "both" = "both"): CommandBuilder {
quiet(kind: StreamKind | "both" = "combined"): CommandBuilder {
kind = kind === "both" ? "combined" : kind;
return this.#newWithState((state) => {
if (kind === "both" || kind === "stdout") {
if (kind === "combined" || kind === "stdout") {
state.stdout.kind = getQuietKind(state.stdout.kind);
}
if (kind === "both" || kind === "stderr") {
if (kind === "combined" || kind === "stderr") {
state.stderr.kind = getQuietKind(state.stderr.kind);
}
});
Expand Down Expand Up @@ -543,40 +545,43 @@ export class CommandBuilder implements PromiseLike<CommandResult> {
* const data = (await $`command`.quiet("stdout")).stdoutBytes;
* ```
*/
async bytes(): Promise<Uint8Array> {
return (await this.quiet("stdout")).stdoutBytes;
async bytes(kind: StreamKind): Promise<Uint8Array> {
const command = kind === "combined" ? this.quiet(kind).captureCombined() : this.quiet(kind);
return (await command)[`${kind}Bytes`];
}

/**
* Sets stdout as quiet, spawns the command, and gets stdout as a string without the last newline.
* Sets the provided stream (stdout by default) as quiet, spawns the command, and gets the stream as a string without the last newline.
* Can be used to get stdout, stderr, or both.
*
* Shorthand for:
*
* ```ts
* const data = (await $`command`.quiet("stdout")).stdout.replace(/\r?\n$/, "");
* ```
*/
async text(): Promise<string> {
return (await this.quiet("stdout")).stdout.replace(/\r?\n$/, "");
async text(kind: StreamKind = "stdout"): Promise<string> {
const command = kind === "combined" ? this.quiet(kind).captureCombined() : this.quiet(kind);
return (await command)[kind].replace(/\r?\n$/, "");
}

/** Gets the text as an array of lines. */
async lines(): Promise<string[]> {
const text = await this.text();
async lines(kind: StreamKind = "stdout"): Promise<string[]> {
const text = await this.text(kind);
return text.split(/\r?\n/g);
}

/**
* Sets stdout as quiet, spawns the command, and gets stdout as JSON.
* Sets stream (stdout by default) as quiet, spawns the command, and gets stream as JSON.
*
* Shorthand for:
*
* ```ts
* const data = (await $`command`.quiet("stdout")).stdoutJson;
* ```
*/
async json<TResult = any>(): Promise<TResult> {
return (await this.quiet("stdout")).stdoutJson;
async json<TResult = any>(kind: Exclude<StreamKind, "combined"> = "stdout"): Promise<TResult> {
return (await this.quiet(kind))[`${kind}Json`];
}

/** @internal */
Expand Down
10 changes: 7 additions & 3 deletions src/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,10 @@ async function executeUnresolvedCommand(
context: Context,
): Promise<ExecuteResult> {
const resolvedCommand = await resolveCommand(unresolvedCommand, context);
if (resolvedCommand === false) {
context.stderr.writeLine(`dax: ${unresolvedCommand.name}: command not found`);
return { code: 127 };
}
if (resolvedCommand.kind === "shebang") {
return executeUnresolvedCommand(resolvedCommand.command, [...resolvedCommand.args, ...commandArgs], context);
}
Expand Down Expand Up @@ -1113,7 +1117,7 @@ function pipeCommandPipeReaderToWriterSync(
}
}

type ResolvedCommand = ResolvedPathCommand | ResolvedShebangCommand;
type ResolvedCommand = ResolvedPathCommand | ResolvedShebangCommand | false;

interface ResolvedPathCommand {
kind: "path";
Expand Down Expand Up @@ -1141,7 +1145,7 @@ async function resolveCommand(unresolvedCommand: UnresolvedCommand, context: Con
// won't have a script with a shebang in it on Windows
const result = await getExecutableShebangFromPath(commandPath);
if (result === false) {
throw new Error(`Command not found: ${unresolvedCommand.name}`);
return false;
} else if (result != null) {
const args = await parseShebangArgs(result, context);
const name = args.shift()!;
Expand All @@ -1165,7 +1169,7 @@ async function resolveCommand(unresolvedCommand: UnresolvedCommand, context: Con

const commandPath = await whichFromContext(unresolvedCommand.name, context);
if (commandPath == null) {
throw new Error(`Command not found: ${unresolvedCommand.name}`);
return false;
}
return {
kind: "path",
Expand Down

0 comments on commit 99b94b5

Please sign in to comment.