From 310c190bf389701a853cfb6864a779ef03b7bf79 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sun, 28 Jan 2024 11:47:42 -0500 Subject: [PATCH] feat: support subshells --- mod.test.ts | 41 +++++++++++++++++++++++++++++++++++++++++ src/shell.ts | 21 +++++++++++++++------ 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/mod.test.ts b/mod.test.ts index 7645916..fcd4ecf 100644 --- a/mod.test.ts +++ b/mod.test.ts @@ -1124,6 +1124,47 @@ Deno.test("piping in command", async () => { } }); +Deno.test("subshells", async () => { + { + const result = await $`(echo 1 && echo 2) | cat -`.text(); + assertEquals(result, "1\n2"); + } + { + const result = await $`(echo 1 && echo 2) && echo 3`.text(); + assertEquals(result, "1\n2\n3"); + } + { + const result = await $`(echo 1 && echo 2) || echo 3`.text(); + assertEquals(result, "1\n2"); + } + { + const result = await $`echo 1 && (echo 2 || echo 3)`.text(); + assertEquals(result, "1\n2"); + } + { + const result = await $`echo 1 && (echo 2 && echo 3) || echo 4`.text(); + assertEquals(result, "1\n2\n3"); + } + { + const result = await $`echo 1 && (echo 2 || echo 3) && echo 4`.text(); + assertEquals(result, "1\n2\n4"); + } + // exiting shouldn't exit the parent + { + const result = await $`echo 1 && (echo 2 && exit 0 && echo 3) && echo 4`.noThrow().text(); + assertEquals(result, "1\n2\n4"); + } + { + const result = await $`echo 1 && (echo 2 && exit 1 && echo 3) || echo 4`.noThrow().text(); + assertEquals(result, "1\n2\n4"); + } + // shouldn't change the environment either + { + const result = await $`echo 1 && (echo 2 && export VAR=5 && echo $VAR) && echo $VAR`.text(); + assertEquals(result, "1\n2\n5\n"); + } +}); + Deno.test("output redirects", async () => { await withTempDir(async (tempDir) => { // absolute diff --git a/src/shell.ts b/src/shell.ts index e359ac9..f84b569 100644 --- a/src/shell.ts +++ b/src/shell.ts @@ -49,7 +49,7 @@ export interface Command { redirect: Redirect | undefined; } -export type CommandInner = SimpleCommand | TaggedSequentialList; +export type CommandInner = SimpleCommand | Subshell; export interface SimpleCommand { kind: "simple"; @@ -81,8 +81,8 @@ export interface Quoted { value: WordPart[]; } -export interface TaggedSequentialList extends SequentialList { - kind: "sequentialList"; +export interface Subshell extends SequentialList { + kind: "subshell"; } export interface PipeSequence { @@ -873,9 +873,12 @@ function executeCommandInner(command: CommandInner, context: Context): Promise { + const result = await executeSequentialList(subshell, context); + // sub shells do not change the environment or cause an exit + return { code: result.code, changes: [] }; +} + async function pipeReaderToWritable(reader: Reader, writable: WritableStream, signal: AbortSignal) { const abortedPromise = new Promise((resolve) => { signal.addEventListener("abort", listener);