Skip to content

Commit

Permalink
feat: allow providing a PathRef for .stdin(...) (#211)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret authored Jan 25, 2024
1 parent 76b5042 commit f799ad0
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 18 deletions.
19 changes: 14 additions & 5 deletions mod.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -754,11 +754,11 @@ Deno.test("piping to stdin", async () => {

// string
{
const result =
await $`deno eval "const b = new Uint8Array(4); await Deno.stdin.read(b); await Deno.stdout.write(b);"`
.stdinText("test\n")
.text();
assertEquals(result, "test");
const command = $`deno eval "const b = new Uint8Array(4); await Deno.stdin.read(b); await Deno.stdout.write(b);"`
.stdinText("test\n");
// should support calling multiple times
assertEquals(await command.text(), "test");
assertEquals(await command.text(), "test");
}

// Uint8Array
Expand All @@ -778,6 +778,15 @@ Deno.test("piping to stdin", async () => {
.text();
assertEquals(result, "1\n2");
}

// PathRef
await withTempDir(async (tempDir) => {
const tempFile = tempDir.join("temp_file.txt");
const fileText = "1 testing this out\n".repeat(1_000);
tempFile.writeTextSync(fileText);
const output = await $`cat`.stdin(tempFile).text();
assertEquals(output, fileText.trim());
});
});

Deno.test("spawning a command twice that has stdin set to a Reader should error", async () => {
Expand Down
32 changes: 27 additions & 5 deletions src/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,24 @@ import { PathRef } from "./path.ts";

type BufferStdio = "inherit" | "null" | "streamed" | Buffer;

class Deferred<T> {
#create: () => T | Promise<T>;
constructor(create: () => T | Promise<T>) {
this.#create = create;
}

create() {
return this.#create();
}
}

interface CommandBuilderState {
command: string | undefined;
stdin: "inherit" | "null" | Box<Reader | ReadableStream<Uint8Array> | "consumed">;
stdin:
| "inherit"
| "null"
| Box<Reader | ReadableStream<Uint8Array> | "consumed">
| Deferred<ReadableStream<Uint8Array> | Reader>;
combinedStdoutStderr: boolean;
stdoutKind: ShellPipeWriterKind;
stderrKind: ShellPipeWriterKind;
Expand Down Expand Up @@ -254,12 +269,17 @@ export class CommandBuilder implements PromiseLike<CommandResult> {
* For this reason, if you are setting stdin to something other than "inherit" or
* "null", then it's recommended to set this each time you spawn a command.
*/
stdin(reader: ShellPipeReader | Uint8Array | ReadableStream<Uint8Array>): CommandBuilder {
stdin(reader: ShellPipeReader): CommandBuilder {
return this.#newWithState((state) => {
if (reader === "inherit" || reader === "null") {
state.stdin = reader;
} else if (reader instanceof Uint8Array) {
state.stdin = new Box(new Buffer(reader));
state.stdin = new Deferred(() => new Buffer(reader));
} else if (reader instanceof PathRef) {
state.stdin = new Deferred(async () => {
const file = await reader.open();
return file.readable;
});
} else {
state.stdin = new Box(reader);
}
Expand Down Expand Up @@ -612,7 +632,7 @@ export function parseAndSpawnCommand(state: CommandBuilderState) {
return new CommandChild(async (resolve, reject) => {
try {
const list = parseCommand(command);
const stdin = takeStdin();
const stdin = await takeStdin();
let code = await spawn(list, {
stdin: stdin instanceof ReadableStream ? readerFromStreamReader(stdin.getReader()) : stdin,
stdout,
Expand Down Expand Up @@ -668,7 +688,7 @@ export function parseAndSpawnCommand(state: CommandBuilderState) {
killSignalController,
});

function takeStdin() {
async function takeStdin() {
if (state.stdin instanceof Box) {
const stdin = state.stdin.value;
if (stdin === "consumed") {
Expand All @@ -680,6 +700,8 @@ export function parseAndSpawnCommand(state: CommandBuilderState) {
}
state.stdin.value = "consumed";
return stdin;
} else if (state.stdin instanceof Deferred) {
return await state.stdin.create();
} else {
return state.stdin;
}
Expand Down
2 changes: 1 addition & 1 deletion src/deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export * as fs from "https://deno.land/[email protected]/fs/mod.ts";
export { Buffer } from "https://deno.land/[email protected]/io/buffer.ts";
export { BufReader } from "https://deno.land/[email protected]/io/buf_reader.ts";
export * as path from "https://deno.land/[email protected]/path/mod.ts";
export { readAll } from "https://deno.land/[email protected]/streams/read_all.ts";
export { readAll } from "https://deno.land/[email protected]/io/read_all.ts";
export { readerFromStreamReader } from "https://deno.land/[email protected]/streams/reader_from_stream_reader.ts";
export { writeAll, writeAllSync } from "https://deno.land/[email protected]/io/write_all.ts";
export { outdent } from "https://deno.land/x/[email protected]/src/index.ts";
Expand Down
9 changes: 8 additions & 1 deletion src/pipes.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PathRef } from "./path.ts";
import { logger } from "./console/logger.ts";
import { Buffer, writeAllSync } from "./deps.ts";

Expand All @@ -15,7 +16,13 @@ export interface Closer {
close(): void;
}

export type ShellPipeReader = "inherit" | "null" | Reader;
export type ShellPipeReader =
| "inherit"
| "null"
| Reader
| ReadableStream<Uint8Array>
| Uint8Array
| PathRef;
/**
* The behaviour to use for a shell pipe.
* @value "inherit" - Sends the output directly to the current process' corresponding pipe (default).
Expand Down
3 changes: 2 additions & 1 deletion src/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ function withServer(action: (serverUrl: URL) => Promise<void>) {
const url = new URL(`http://${details.hostname}:${details.port}/`);
try {
await action(url);
await server.shutdown();
resolve();
} catch (err) {
await server.shutdown();
reject(err);
}
await server.shutdown();
},
}, (request) => {
const url = new URL(request.url);
Expand Down
17 changes: 12 additions & 5 deletions src/shell.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CommandPipeReader } from "../mod.ts";
import { KillSignal } from "./command.ts";
import { CommandContext, CommandHandler } from "./command_handler.ts";
import { getExecutableShebangFromPath, ShebangInfo } from "./common.ts";
Expand Down Expand Up @@ -200,7 +201,7 @@ function cloneEnv(env: Env) {
}

export class Context {
stdin: ShellPipeReader;
stdin: CommandPipeReader;
stdout: ShellPipeWriter;
stderr: ShellPipeWriter;
#env: Env;
Expand All @@ -209,7 +210,7 @@ export class Context {
#signal: KillSignal;

constructor(opts: {
stdin: ShellPipeReader;
stdin: CommandPipeReader;
stdout: ShellPipeWriter;
stderr: ShellPipeWriter;
env: Env;
Expand All @@ -226,6 +227,12 @@ export class Context {
this.#signal = opts.signal;
}

[Symbol.dispose]() {
if (this.stdin instanceof ReadableStream) {
this.stdin.cancel();
}
}

get signal() {
return this.#signal;
}
Expand Down Expand Up @@ -351,7 +358,7 @@ export function parseCommand(command: string) {
}

export interface SpawnOpts {
stdin: ShellPipeReader;
stdin: CommandPipeReader;
stdout: ShellPipeWriter;
stderr: ShellPipeWriter;
env: Record<string, string>;
Expand All @@ -364,7 +371,7 @@ export interface SpawnOpts {
export async function spawn(list: SequentialList, opts: SpawnOpts) {
const env = opts.exportEnv ? new RealEnv() : new ShellEnv();
initializeEnv(env, opts);
const context = new Context({
using context = new Context({
env,
commands: opts.commands,
stdin: opts.stdin,
Expand Down Expand Up @@ -649,7 +656,7 @@ async function executeCommandArgs(commandArgs: string[], context: Context): Prom
await stdinPromise;
}

async function writeStdin(stdin: ShellPipeReader, p: Deno.ChildProcess, signal: AbortSignal) {
async function writeStdin(stdin: CommandPipeReader, p: Deno.ChildProcess, signal: AbortSignal) {
if (typeof stdin === "string") {
return;
}
Expand Down

0 comments on commit f799ad0

Please sign in to comment.