From ea75093b01b3588e5b2444b3e9cdd66193d41ab9 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sun, 28 Jan 2024 19:13:31 -0500 Subject: [PATCH] docs: update for 0.38 (#233) --- README.md | 91 +++++++++++++++++++++++++++++++++++++++++++++++--- mod.test.ts | 14 ++++++++ mod.ts | 11 +++--- src/command.ts | 17 ++++++++-- 4 files changed, 121 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 281b7c7..a7ec284 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Cross platform shell tools for Deno inspired by [zx](https://github.com/google/z - Makes more code work on Windows. - Allows exporting the shell's environment to the current process. - Uses [deno_task_shell](https://github.com/denoland/deno_task_shell)'s parser. + - Has common commands built-in for better Windows support. 1. Minimal globals or global configuration. - Only a default instance of `$`, but it's not mandatory to use this. 1. No custom CLI. @@ -27,8 +28,8 @@ import $ from "https://deno.land/x/dax/mod.ts"; // run a command await $`echo 5`; // outputs: 5 -// more complex example outputting 1 to stdout and 2 to stderr -await $`echo 1 && deno eval 'console.error(2);'`; +// outputting 1 to stdout and running a sub process +await $`echo 1 && deno run main.ts`; // parallel await Promise.all([ @@ -57,8 +58,8 @@ console.log(result.prop); // 5 Get the result of stdout as bytes (makes stdout "quiet"): ```ts -const result = await $`echo 'test'`.bytes(); -console.log(result); // Uint8Array(5) [ 116, 101, 115, 116, 10 ] +const bytes = await $`gzip < file.txt`.bytes(); +console.log(bytes); ``` Get the result of stdout as a list of lines (makes stdout "quiet"): @@ -106,12 +107,31 @@ Piping to a `WritableStream`: await $`echo 1`.stdout(Deno.stderr.writable, { preventClose: true }); ``` -Or to a file: +To a file path: ```ts await $`echo 1`.stdout($.path("data.txt")); ``` +To a file: + +```ts +using file = $.path("data.txt").openSync({ write: true, create: true }); +await $`echo 1`.stdout(file); +``` + +From one command to another: + +```ts +const output = await $`echo foo && echo bar` + .pipe($`grep foo`) + .text(); + +// or using a pipe sequence +const output = await $`echo foo && echo bar | grep foo` + .text(); +``` + ### Providing arguments to a command Use an expression in a template literal to provide a single argument to a command: @@ -159,6 +179,37 @@ const finalText = await $`echo ${result}`.text(); console.log(finalText); // 1 ``` +#### JavaScript objects to redirects + +You can also provide JavaScript objects to shell output redirects: + +```ts +const buffer = new Uint8Array(2); +await $`echo 1 && (echo 2 > ${buffer}) && echo 3`; // 1\n3\n +console.log(buffer); // Uint8Array(2) [ 50, 10 ] (2\n) +``` + +Supported objects: `Uint8Array`, `PathRef`, `WritableStream`, function that returns a `WritableStream`, any object that implements `[$.symbols.writable](): WritableStream` + +Or input redirects: + +```ts +// strings +const data = "my data in a string"; +const bytes = await $`gzip < ${data}`; + +// paths +const path = $.path("file.txt"); +const bytes = await $`gzip < ${path}`; + +// requests (this example does not make the request until after 5 seconds) +const request = $.request("https://plugins.dprint.dev/info.json") + .showProgress(); // show a progress bar while downloading +const bytes = await $`sleep 5 && gzip < ${request}`.bytes(); +``` + +Supported objects: `string`, `Uint8Array`, `PathRef`, `RequestBuilder`, `ReadableStream`, function that returns a `ReadableStream`, any object that implements `[$.symbols.readable](): ReadableStream` + ### Providing stdin ```ts @@ -670,6 +721,17 @@ console.log(response.code); console.log(await response.json()); ``` +Requests can be piped to commands: + +```ts +const request = $.request("https://plugins.dprint.dev/info.json"); +await $`deno run main.ts`.stdin(request); + +// or as a redirect... this sleeps 5 seconds, then makes +// request and redirects the output to the command +await $`sleep 5 && deno run main.ts < ${request}`; +``` + See the [documentation on `RequestBuilder`](https://deno.land/x/dax/src/request.ts?s=RequestBuilder) for more details. It should be as flexible as `fetch`, but uses a builder API (ex. set headers via `.header(...)`). ### Showing progress @@ -703,6 +765,25 @@ await $`echo 1 && echo 2`; await $`echo 1 || echo 2`; ``` +Pipe sequences: + +```ts +await $`echo 1 | deno run main.ts`; +``` + +Redirects: + +```ts +await $`echo 1 > output.txt`; +const gzippedBytes = await $`gzip < input.txt`.bytes(); +``` + +Sub shells: + +```ts +await $`(echo 1 && echo 2) > output.txt`; +``` + Setting env var for command in the shell (generally you can just use `.env(...)` though): ```ts diff --git a/mod.test.ts b/mod.test.ts index 74797f4..dc2a6ed 100644 --- a/mod.test.ts +++ b/mod.test.ts @@ -1269,6 +1269,12 @@ Deno.test("input redirects with provided object", async () => { const output = await $`cat - < ${stream}`.text(); assertEquals(output, text); } + // string + { + const text = "testing".repeat(1000); + const output = await $`cat - < ${text}`.text(); + assertEquals(output, text); + } // bytes { const text = "testing".repeat(1000); @@ -1361,6 +1367,14 @@ Deno.test("output redirect with provided object", async () => { await $`echo 1 > ${() => writableStream}`; assertEquals(chunks, [new Uint8Array([49, 10])]); } + { + assertThrows( + () => $`echo 1 > ${"test.txt"}`, + Error, + "Failed resolving expression in command. Cannot provide strings to output " + + "redirects. Did you mean to provide a path instead via the `$.path(...)` API?", + ); + } }); Deno.test("shebang support", async (t) => { diff --git a/mod.ts b/mod.ts index ebd95f9..9d21aba 100644 --- a/mod.ts +++ b/mod.ts @@ -79,14 +79,15 @@ export type { * * Differences: * - * 1. Minimal globals or global configuration. - * - Only a default instance of `$`, but it's not mandatory to use this. - * 1. No custom CLI. * 1. Cross platform shell. * - Makes more code work on Windows. - * - Uses [deno_task_shell](https://github.com/denoland/deno_task_shell)'s parser. * - Allows exporting the shell's environment to the current process. - * 1. Good for application code in addition to use as a shell script replacement + * - Uses [deno_task_shell](https://github.com/denoland/deno_task_shell)'s parser. + * - Has common commands built-in for better Windows support. + * 1. Minimal globals or global configuration. + * - Only a default instance of `$`, but it's not mandatory to use this. + * 1. No custom CLI. + * 1. Good for application code in addition to use as a shell script replacement. * 1. Named after my cat. * * ## Example diff --git a/src/command.ts b/src/command.ts index 0107673..9e534c0 100644 --- a/src/command.ts +++ b/src/command.ts @@ -1252,8 +1252,17 @@ function templateInner( const expr = exprs[i]; const inputOrOutputRedirect = detectInputOrOutputRedirect(text); if (inputOrOutputRedirect === "<") { - if (typeof expr === "string" || expr instanceof PathRef) { + if (expr instanceof PathRef) { text += templateLiteralExprToString(expr, escape); + } else if (typeof expr === "string") { + handleReadableStream(() => + new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode(expr)); + controller.close(); + }, + }) + ); } else if (expr instanceof ReadableStream) { handleReadableStream(() => expr); } else if (expr?.[symbols.readable]) { @@ -1303,7 +1312,7 @@ function templateInner( throw new Error("Unsupported object provided to input redirect."); } } else if (inputOrOutputRedirect === ">") { - if (typeof expr === "string" || expr instanceof PathRef) { + if (expr instanceof PathRef) { text += templateLiteralExprToString(expr, escape); } else if (expr instanceof WritableStream) { handleWritableStream(() => expr); @@ -1349,6 +1358,10 @@ function templateInner( ); } }); + } else if (typeof expr === "string") { + throw new Error( + "Cannot provide strings to output redirects. Did you mean to provide a path instead via the `$.path(...)` API?", + ); } else { throw new Error("Unsupported object provided to output redirect."); }