From 88399a3af07036bc8a2fde3d69ac78dfe53fd705 Mon Sep 17 00:00:00 2001 From: Ardis Lu Date: Thu, 21 Aug 2025 20:27:37 -0700 Subject: [PATCH] Add example for `stdin` and `stdout` usage --- examples/README.md | 2 ++ examples/data/ex1_8-in.csv | 6 ++++++ examples/data/ex1_8-out.csv | 6 ++++++ examples/ex1_8.js | 20 ++++++++++++++++++++ test/examples.test.js | 18 ++++++++++++++++-- 5 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 examples/data/ex1_8-in.csv create mode 100644 examples/data/ex1_8-out.csv create mode 100644 examples/ex1_8.js diff --git a/examples/README.md b/examples/README.md index a78fd6f..607878a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -28,6 +28,8 @@ These examples demonstrate the most common, expected usage. [Example 1.7: Abort a transformation](./ex1_7.js) ([input](./data/ex1_7-in.csv), [output](./data/ex1_7-out.csv)) +[Example 1.8: Read from `stdin` and write to `stdout`](./ex1_8.js) ([input](./data/ex1_8-in.csv), [output](./data/ex1_8-out.csv)) + ### Section 2: Error strategies These examples demonstrate using the `onError` option to handle transformations that may throw errors. diff --git a/examples/data/ex1_8-in.csv b/examples/data/ex1_8-in.csv new file mode 100644 index 0000000..4bd7c6a --- /dev/null +++ b/examples/data/ex1_8-in.csv @@ -0,0 +1,6 @@ +columnA,columnB +1,1 +100,100 +223423,455947 +348553,692708 +536368,676147 \ No newline at end of file diff --git a/examples/data/ex1_8-out.csv b/examples/data/ex1_8-out.csv new file mode 100644 index 0000000..3b79d13 --- /dev/null +++ b/examples/data/ex1_8-out.csv @@ -0,0 +1,6 @@ +columnA,columnB +2,2 +101,101 +223424,455948 +348554,692709 +536369,676148 diff --git a/examples/ex1_8.js b/examples/ex1_8.js new file mode 100644 index 0000000..9d3490e --- /dev/null +++ b/examples/ex1_8.js @@ -0,0 +1,20 @@ +// ## Example 1.8: Read from `stdin` and write to `stdout` +// +// Wrap Node.js's `process.stdin` and `process.stdout` to handle the `stdin` and `stdout` streams. +// This usage is helpful for integrating csvio within complex shell scripts. + +import { stdin, stdout } from 'node:process'; +import { Readable, Writable } from 'node:stream'; +import { TextDecoderStream } from 'node:stream/web'; +import { CSVReader, CSVTransformer, CSVWriter } from '../src/index.js'; + +// Do not `await`, the pipe just runs indefinitely. The shell script will close this process. +Readable.toWeb(stdin) + .pipeThrough(new TextDecoderStream()) // CSVReader expects text, bring your own text decoder. + .pipeThrough(new CSVReader()) + .pipeThrough(new CSVTransformer(r => r.map(v => Number(v) + 1))) + .pipeThrough(new CSVWriter()) // No need to encode on this end, CSVWriter already outputs text. + .pipeTo(Writable.toWeb(stdout)); + +// Basic usage example: +// cat ./examples/data/ex1_8-in.csv | node ./examples/ex1_8.js > ./examples/data/ex1_8-out.csv diff --git a/test/examples.test.js b/test/examples.test.js index a39e2f9..8e49070 100644 --- a/test/examples.test.js +++ b/test/examples.test.js @@ -1,7 +1,10 @@ import { suite, after, test } from 'node:test'; import { ok } from 'node:assert/strict'; +import { createReadStream, createWriteStream } from 'node:fs'; import { mkdir, cp, opendir, readFile, rm } from 'node:fs/promises'; -import { pathToFileURL } from 'node:url'; +import { pathToFileURL, fileURLToPath } from 'node:url'; +import { spawn } from 'node:child_process'; +import { finished } from 'node:stream/promises'; import { createTempFolder, assertConsole } from './utils.js'; @@ -28,13 +31,14 @@ for await (const file of files) { tests.push({ name, code: new URL(`${name}.js`, tempExamples), + input: new URL(`./data/${name}-in.csv`, tempExamples), output: new URL(`./data/${name}-out.csv`, tempExamples) }); } suite('examples', { concurrency: true }, () => { after(async () => await rm(dir, { recursive: true, force: true })); - for (const { name, code, output } of tests) { + for (const { name, code, input, output } of tests) { if (name === 'ex1_6') { test(name, { concurrency: true }, async (t) => { assertConsole(t, { log: 1 }); @@ -44,6 +48,16 @@ suite('examples', { concurrency: true }, () => { ok(actual.equals(expected)); }); } + else if (name === 'ex1_8') { + test(name, { concurrency: true }, async () => { + const expected = await readFile(output); + const child = spawn(process.execPath, [fileURLToPath(code)]); + createReadStream(input).pipe(child.stdin); // Equivalent to `cat ${input} | child` + await finished(child.stdout.pipe(createWriteStream(output))); // Equivalent to `child > ${output}` + const actual = await readFile(output); + ok(actual.equals(expected)); + }); + } else { test(name, { concurrency: true }, async () => { const expected = await readFile(output); -- 2.47.3