From e0cf34f420e024fbcaeb6b32e9f6fdeac7144c59 Mon Sep 17 00:00:00 2001 From: Orion Kindel Date: Thu, 9 May 2024 17:21:15 -0500 Subject: [PATCH] fix: init --- .gitignore | 12 + .tool-versions | 1 + README.md | 81 +++ bun.lockb | Bin 0 -> 2057 bytes bun/fmt.js | 27 + bun/prepare.js | 34 ++ jsconfig.json | 16 + package.json | 11 + spago.lock | 903 +++++++++++++++++++++++++++++++ spago.yaml | 39 ++ src/Node.Stream.Object.js | 49 ++ src/Node.Stream.Object.purs | 168 ++++++ src/Pipes.CSV.Parse.purs | 1 + src/Pipes.Node.Stream.purs | 95 ++++ test/Test/Main.purs | 14 + test/Test/Pipes.Node.Stream.js | 4 + test/Test/Pipes.Node.Stream.purs | 158 ++++++ 17 files changed, 1613 insertions(+) create mode 100644 .gitignore create mode 100644 .tool-versions create mode 100644 README.md create mode 100755 bun.lockb create mode 100644 bun/fmt.js create mode 100644 bun/prepare.js create mode 100644 jsconfig.json create mode 100644 package.json create mode 100644 spago.lock create mode 100644 spago.yaml create mode 100644 src/Node.Stream.Object.js create mode 100644 src/Node.Stream.Object.purs create mode 100644 src/Pipes.CSV.Parse.purs create mode 100644 src/Pipes.Node.Stream.purs create mode 100644 test/Test/Main.purs create mode 100644 test/Test/Pipes.Node.Stream.js create mode 100644 test/Test/Pipes.Node.Stream.purs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..639514a --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +bower_components/ +node_modules/ +.pulp-cache/ +output/ +output-es/ +generated-docs/ +.psc-package/ +.psc* +.purs* +.psa* +.spago +.tmp/ diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..347e5e8 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +purescript 0.15.15 diff --git a/README.md b/README.md new file mode 100644 index 0000000..ee047d8 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +# purescript-csv-stream + +Type-safe bindings for the streaming API of `csv-parse` and `csv-stringify`. + +## Installing +```bash +spago install csv-stream +{bun|yarn|npm|pnpm} install csv-parse csv-stringify +``` + +## Examples +### Stream +```purescript +module Main where + +import Prelude + +import Effect (Effect) +import Effect.Class (liftEffect) +import Effect.Aff (launchAff_) +import Node.Stream (pipe) +import Node.Stream as Stream +import Node.Stream.CSV.Stringify as CSV.Stringify +import Node.Stream.CSV.Parse as CSV.Parse + +type MyCSVType1 = {a :: Int, b :: Int, bar :: String, baz :: Boolean} +type MyCSVType2 = {ab :: Int, bar :: String, baz :: Boolean} + +atob :: MyCSVType1 -> MyCSVType2 +atob {a, b, bar, baz} = {ab: a + b, bar, baz} + +myCSV :: String +myCSV = "a,b,bar,baz\n1,2,\"hello, world!\",true\n3,3,,f" + +main :: Effect Unit +main = launchAff_ do + parser <- liftEffect $ CSV.Parse.make {} + stringifier <- liftEffect $ CSV.Stringify.make {} + + input <- liftEffect $ Stream.readableFromString myCSV + liftEffect $ Stream.pipe input parser + + records <- CSV.Parse.readAll parser + liftEffect $ for_ records \r -> CSV.Stringify.write $ atob r + liftEffect $ Stream.end stringifier + + -- "ab,bar,baz\n3,\"hello, world!\",true\n6,,false" + csvString <- CSV.Stringify.readAll stringifier + pure unit +``` + +### Synchronous +```purescript +module Main where + +import Prelude + +import Effect (Effect) +import Effect.Class (liftEffect) +import Effect.Aff (launchAff_) +import Node.Stream (pipe) +import Node.Stream as Stream +import Node.Stream.CSV.Stringify as CSV.Stringify +import Node.Stream.CSV.Parse as CSV.Parse + +type MyCSVType1 = {a :: Int, b :: Int, bar :: String, baz :: Boolean} +type MyCSVType2 = {ab :: Int, bar :: String, baz :: Boolean} + +atob :: MyCSVType1 -> MyCSVType2 +atob {a, b, bar, baz} = {ab: a + b, bar, baz} + +myCSV :: String +myCSV = "a,b,bar,baz\n1,2,\"hello, world!\",true\n3,3,,f" + +main :: Effect Unit +main = launchAff_ do + records :: Array MyCSVType1 <- CSV.Parse.parse myCSV + -- "ab,bar,baz\n3,\"hello, world!\",true\n6,,false" + csvString <- CSV.Stringify.stringify (atob <$> records) + pure unit +``` diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..8cae2be5b35b3195192f9c6bd761d31d9994e44a GIT binary patch literal 2057 zcmY#Z)GsYA(of3F(@)JSQ%EY!;{sycoc!eMw9K4T-L(9o+{6;yG6OCq1_lPFZno1D|VY0Rml!i1OzM)ih%=-Zh-PjU<$x|Q3i$v z4IqmbNXr9hP9O~wW8ebPS3BeSR!kPOF--i%zwURBvF`5`H^1xnRL)mz(9v#9O=1L_ zKt2KT6$qFDu@lffHXsdh9Ec_c!1R{`660L^2Bnu}~SOM7Zr?A&*jMvtb%#oBb`HoBkv*SjeD zzlq{o?&-QUI-DP#ooC74srzaBM^B$^4|v|HPFwT!U$gD3(CG$OwnW!wA(;yccaW_t zRU4MPyEEm!(lsVYt3#hQ&R2e0cYD+S1uCy|7g)SI$T8RQrSS{SWSK3RS3ug!xA8w_tSwjV_HJW}uw%Yt*H@mn7!o=)sB}y^x~RR67MD1BK$us?_{69R(AG z#GK6Zy!<8zS_9>;dTk z=`Db&-v`$hZDbZ(Y5{b#8Qe}oR7bH#4G1FUR>m4QGR zGd&YMGX@3@XffphrBOq&I5oE{wMfskq9nB_FEJ;$q$o2l-A=&};W!Jp<4TH?fj(kj zIPhC!Y0;DkBA^NZB*uSlvCTW#!d)fB#i>O=(_$|xGzO)4|JVpr%L6R0S}aV@FjnyW ztAM(o5LyEO!yXt8hPZ-(p`f%VwK%ybv!FybxwuTXxTGjGF}I|$04fL)D@ZIVPK8PW z9iEw1nNdpstF$fOO91~z<0?h%3@*pGtx}teN literal 0 HcmV?d00001 diff --git a/bun/fmt.js b/bun/fmt.js new file mode 100644 index 0000000..29d0fe0 --- /dev/null +++ b/bun/fmt.js @@ -0,0 +1,27 @@ +/** @type {(parser: string, ps: string[]) => import("bun").Subprocess} */ +const prettier = (parser, ps) => + Bun.spawn(["bun", "x", "prettier", "--write", "--parser", parser, ...ps], { + stdout: "inherit", + stderr: "inherit", + }); + +const procs = [ + prettier("babel", ["./src/**/*.js", "./bun/**/*.js", "./.prettierrc.cjs"]), + prettier("json", ["./package.json", "./jsconfig.json"]), + Bun.spawn( + [ + "bun", + "x", + "purs-tidy", + "format-in-place", + "src/**/*.purs", + "test/**/*.purs", + ], + { + stdout: "inherit", + stderr: "inherit", + }, + ), +]; + +await Promise.all(procs.map((p) => p.exited)); diff --git a/bun/prepare.js b/bun/prepare.js new file mode 100644 index 0000000..4e60819 --- /dev/null +++ b/bun/prepare.js @@ -0,0 +1,34 @@ +import { readFile, writeFile } from "fs/promises"; +import { execSync } from "child_process"; + +let ver = process.argv[2]; +if (!ver) { + console.error(`tag required: bun bun/prepare.js v1.0.0`); + process.exit(1); +} else if (!/v\d+\.\d+\.\d+/.test(ver)) { + console.error(`invalid tag: ${ver}`); + process.exit(1); +} + +ver = (/\d+\.\d+\.\d+/.exec(ver) || [])[0] || ""; + +const pkg = await readFile("./package.json", "utf8"); +const pkgnew = pkg.replace(/"version": ".+"/, `"version": "v${ver}"`); +await writeFile("./package.json", pkgnew); + +const spago = await readFile("./spago.yaml", "utf8"); +const spagonew = spago.replace(/version: .+/, `version: '${ver}'`); +await writeFile("./spago.yaml", spagonew); + +const readme = await readFile("./README.md", "utf8"); +const readmenew = readme.replace( + /packages\/purescript-node-stream-pipes\/.+?\//g, + `/packages/purescript-node-stream-pipes/${ver}/`, +); +await writeFile("./README.md", readmenew); + +execSync(`git add spago.yaml package.json README.md`); +execSync(`git commit -m 'chore: prepare v${ver}'`); +execSync(`git tag v${ver}`); +execSync(`git push --tags`); +execSync(`git push --mirror github-mirror`); diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..f48b93c --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "types": ["bun-types"], + "lib": ["esnext"], + "target": "esnext", + "module": "esnext", + "moduleResolution": "bundler", + "moduleDetection": "force", + "jsx": "react", + "allowJs": true, + "checkJs": true, + "noEmit": true, + "strict": true + }, + "include": ["src/**/*.js", "bun/**/*.js"] +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..532ee5a --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "purescript-csv-stream", + "version": "v1.2.19", + "dependencies": { + "csv-parse": "^5.5.5", + "csv-stringify": "^6.4.6" + }, + "devDependencies": { + "typescript": "^5.4.5" + } +} diff --git a/spago.lock b/spago.lock new file mode 100644 index 0000000..e78dc8c --- /dev/null +++ b/spago.lock @@ -0,0 +1,903 @@ +workspace: + packages: + node-stream-pipes: + path: ./ + dependencies: + - aff: ">=7.1.0 <8.0.0" + - control: ">=6.0.0 <7.0.0" + - effect: ">=4.0.0 <5.0.0" + - either: ">=6.1.0 <7.0.0" + - exceptions: ">=6.0.0 <7.0.0" + - maybe: ">=6.0.0 <7.0.0" + - newtype: ">=5.0.0 <6.0.0" + - node-buffer: ">=9.0.0 <10.0.0" + - node-event-emitter: ">=3.0.0 <4.0.0" + - node-streams: ">=9.0.0 <10.0.0" + - pipes: ">=8.0.0 <9.0.0" + - prelude: ">=6.0.1 <7.0.0" + - tailrec: ">=6.1.0 <7.0.0" + - transformers: ">=6.0.0 <7.0.0" + - unsafe-coerce: ">=6.0.0 <7.0.0" + test_dependencies: + - console + - gen + - node-fs + - node-zlib + - quickcheck + - simple-json + - spec + build_plan: + - aff + - ansi + - arraybuffer-types + - arrays + - avar + - bifunctors + - catenable-lists + - console + - const + - contravariant + - control + - datetime + - distributive + - effect + - either + - enums + - exceptions + - exists + - foldable-traversable + - foreign + - foreign-object + - fork + - free + - functions + - functors + - gen + - identity + - integers + - invariant + - js-date + - lazy + - lcg + - lists + - maybe + - mmorph + - newtype + - node-buffer + - node-event-emitter + - node-fs + - node-path + - node-streams + - node-zlib + - nonempty + - now + - nullable + - numbers + - ordered-collections + - orders + - parallel + - partial + - pipes + - prelude + - profunctor + - quickcheck + - random + - record + - refs + - safe-coerce + - simple-json + - spec + - st + - strings + - tailrec + - transformers + - tuples + - type-equality + - typelevel-prelude + - unfoldable + - unsafe-coerce + - variant + extra_packages: {} +packages: + aff: + type: registry + version: 7.1.0 + integrity: sha256-7hOC6uQO9XBAI5FD8F33ChLjFAiZVfd4BJMqlMh7TNU= + dependencies: + - arrays + - bifunctors + - control + - datetime + - effect + - either + - exceptions + - foldable-traversable + - functions + - maybe + - newtype + - parallel + - prelude + - refs + - tailrec + - transformers + - unsafe-coerce + ansi: + type: registry + version: 7.0.0 + integrity: sha256-ZMB6HD+q9CXvn9fRCmJ8dvuDrOVHcjombL3oNOerVnE= + dependencies: + - foldable-traversable + - lists + - strings + arraybuffer-types: + type: registry + version: 3.0.2 + integrity: sha256-mQKokysYVkooS4uXbO+yovmV/s8b138Ws3zQvOwIHRA= + dependencies: [] + arrays: + type: registry + version: 7.3.0 + integrity: sha256-tmcklBlc/muUtUfr9RapdCPwnlQeB3aSrC4dK85gQlc= + dependencies: + - bifunctors + - control + - foldable-traversable + - functions + - maybe + - nonempty + - partial + - prelude + - safe-coerce + - st + - tailrec + - tuples + - unfoldable + - unsafe-coerce + avar: + type: registry + version: 5.0.0 + integrity: sha256-e7hf0x4hEpcygXP0LtvfvAQ49Bbj2aWtZT3gqM///0A= + dependencies: + - aff + - effect + - either + - exceptions + - functions + - maybe + bifunctors: + type: registry + version: 6.0.0 + integrity: sha256-/gZwC9YhNxZNQpnHa5BIYerCGM2jeX9ukZiEvYxm5Nw= + dependencies: + - const + - either + - newtype + - prelude + - tuples + catenable-lists: + type: registry + version: 7.0.0 + integrity: sha256-76vYENhwF4BWTBsjeLuErCH2jqVT4M3R1HX+4RwSftA= + dependencies: + - control + - foldable-traversable + - lists + - maybe + - prelude + - tuples + - unfoldable + console: + type: registry + version: 6.1.0 + integrity: sha256-CxmAzjgyuGDmt9FZW51VhV6rBPwR6o0YeKUzA9rSzcM= + dependencies: + - effect + - prelude + const: + type: registry + version: 6.0.0 + integrity: sha256-tNrxDW8D8H4jdHE2HiPzpLy08zkzJMmGHdRqt5BQuTc= + dependencies: + - invariant + - newtype + - prelude + contravariant: + type: registry + version: 6.0.0 + integrity: sha256-TP+ooAp3vvmdjfQsQJSichF5B4BPDHp3wAJoWchip6c= + dependencies: + - const + - either + - newtype + - prelude + - tuples + control: + type: registry + version: 6.0.0 + integrity: sha256-sH7Pg9E96JCPF9PIA6oQ8+BjTyO/BH1ZuE/bOcyj4Jk= + dependencies: + - newtype + - prelude + datetime: + type: registry + version: 6.1.0 + integrity: sha256-g/5X5BBegQWLpI9IWD+sY6mcaYpzzlW5lz5NBzaMtyI= + dependencies: + - bifunctors + - control + - either + - enums + - foldable-traversable + - functions + - gen + - integers + - lists + - maybe + - newtype + - numbers + - ordered-collections + - partial + - prelude + - tuples + distributive: + type: registry + version: 6.0.0 + integrity: sha256-HTDdmEnzigMl+02SJB88j+gAXDx9VKsbvR4MJGDPbOQ= + dependencies: + - identity + - newtype + - prelude + - tuples + - type-equality + effect: + type: registry + version: 4.0.0 + integrity: sha256-eBtZu+HZcMa5HilvI6kaDyVX3ji8p0W9MGKy2K4T6+M= + dependencies: + - prelude + either: + type: registry + version: 6.1.0 + integrity: sha256-6hgTPisnMWVwQivOu2PKYcH8uqjEOOqDyaDQVUchTpY= + dependencies: + - control + - invariant + - maybe + - prelude + enums: + type: registry + version: 6.0.1 + integrity: sha256-HWaD73JFLorc4A6trKIRUeDMdzE+GpkJaEOM1nTNkC8= + dependencies: + - control + - either + - gen + - maybe + - newtype + - nonempty + - partial + - prelude + - tuples + - unfoldable + exceptions: + type: registry + version: 6.0.0 + integrity: sha256-y/xTAEIZIARCE+50/u1di0ncebJ+CIwNOLswyOWzMTw= + dependencies: + - effect + - either + - maybe + - prelude + exists: + type: registry + version: 6.0.0 + integrity: sha256-A0JQHpTfo1dNOj9U5/Fd3xndlRSE0g2IQWOGor2yXn8= + dependencies: + - unsafe-coerce + foldable-traversable: + type: registry + version: 6.0.0 + integrity: sha256-fLeqRYM4jUrZD5H4WqcwUgzU7XfYkzO4zhgtNc3jcWM= + dependencies: + - bifunctors + - const + - control + - either + - functors + - identity + - maybe + - newtype + - orders + - prelude + - tuples + foreign: + type: registry + version: 7.0.0 + integrity: sha256-1ORiqoS3HW+qfwSZAppHPWy4/6AQysxZ2t29jcdUMNA= + dependencies: + - either + - functions + - identity + - integers + - lists + - maybe + - prelude + - strings + - transformers + foreign-object: + type: registry + version: 4.1.0 + integrity: sha256-q24okj6mT+yGHYQ+ei/pYPj5ih6sTbu7eDv/WU56JVo= + dependencies: + - arrays + - foldable-traversable + - functions + - gen + - lists + - maybe + - prelude + - st + - tailrec + - tuples + - typelevel-prelude + - unfoldable + fork: + type: registry + version: 6.0.0 + integrity: sha256-X7u0SuCvFbLbzuNEKLBNuWjmcroqMqit4xEzpQwAP7E= + dependencies: + - aff + free: + type: registry + version: 7.1.0 + integrity: sha256-JAumgEsGSzJCNLD8AaFvuX7CpqS5yruCngi6yI7+V5k= + dependencies: + - catenable-lists + - control + - distributive + - either + - exists + - foldable-traversable + - invariant + - lazy + - maybe + - prelude + - tailrec + - transformers + - tuples + - unsafe-coerce + functions: + type: registry + version: 6.0.0 + integrity: sha256-adMyJNEnhGde2unHHAP79gPtlNjNqzgLB8arEOn9hLI= + dependencies: + - prelude + functors: + type: registry + version: 5.0.0 + integrity: sha256-zfPWWYisbD84MqwpJSZFlvM6v86McM68ob8p9s27ywU= + dependencies: + - bifunctors + - const + - contravariant + - control + - distributive + - either + - invariant + - maybe + - newtype + - prelude + - profunctor + - tuples + - unsafe-coerce + gen: + type: registry + version: 4.0.0 + integrity: sha256-f7yzAXWwr+xnaqEOcvyO3ezKdoes8+WXWdXIHDBCAPI= + dependencies: + - either + - foldable-traversable + - identity + - maybe + - newtype + - nonempty + - prelude + - tailrec + - tuples + - unfoldable + identity: + type: registry + version: 6.0.0 + integrity: sha256-4wY0XZbAksjY6UAg99WkuKyJlQlWAfTi2ssadH0wVMY= + dependencies: + - control + - invariant + - newtype + - prelude + integers: + type: registry + version: 6.0.0 + integrity: sha256-sf+sK26R1hzwl3NhXR7WAu9zCDjQnfoXwcyGoseX158= + dependencies: + - maybe + - numbers + - prelude + invariant: + type: registry + version: 6.0.0 + integrity: sha256-RGWWyYrz0Hs1KjPDA+87Kia67ZFBhfJ5lMGOMCEFoLo= + dependencies: + - control + - prelude + js-date: + type: registry + version: 8.0.0 + integrity: sha256-6TVF4DWg5JL+jRAsoMssYw8rgOVALMUHT1CuNZt8NRo= + dependencies: + - datetime + - effect + - exceptions + - foreign + - integers + - now + lazy: + type: registry + version: 6.0.0 + integrity: sha256-lMsfFOnlqfe4KzRRiW8ot5ge6HtcU3Eyh2XkXcP5IgU= + dependencies: + - control + - foldable-traversable + - invariant + - prelude + lcg: + type: registry + version: 4.0.0 + integrity: sha256-h7ME5cthLfbgJOJdsZcSfFpwXsx4rf8YmhebU+3iSYg= + dependencies: + - effect + - integers + - maybe + - partial + - prelude + - random + lists: + type: registry + version: 7.0.0 + integrity: sha256-EKF15qYqucuXP2lT/xPxhqy58f0FFT6KHdIB/yBOayI= + dependencies: + - bifunctors + - control + - foldable-traversable + - lazy + - maybe + - newtype + - nonempty + - partial + - prelude + - tailrec + - tuples + - unfoldable + maybe: + type: registry + version: 6.0.0 + integrity: sha256-5cCIb0wPwbat2PRkQhUeZO0jcAmf8jCt2qE0wbC3v2Q= + dependencies: + - control + - invariant + - newtype + - prelude + mmorph: + type: registry + version: 7.0.0 + integrity: sha256-urZlZNNqGeQFe5D/ClHlR8QgGBNHTMFPtJ5S5IpflTQ= + dependencies: + - free + - functors + - transformers + newtype: + type: registry + version: 5.0.0 + integrity: sha256-gdrQu8oGe9eZE6L3wOI8ql/igOg+zEGB5ITh2g+uttw= + dependencies: + - prelude + - safe-coerce + node-buffer: + type: registry + version: 9.0.0 + integrity: sha256-PWE2DJ5ruBLCmeA/fUiuySEFmUJ/VuRfyrnCuVZBlu4= + dependencies: + - arraybuffer-types + - effect + - maybe + - nullable + - st + - unsafe-coerce + node-event-emitter: + type: registry + version: 3.0.0 + integrity: sha256-Qw0MjsT4xRH2j2i4K8JmRjcMKnH5z1Cw39t00q4LE4w= + dependencies: + - effect + - either + - functions + - maybe + - nullable + - prelude + - unsafe-coerce + node-fs: + type: registry + version: 9.1.0 + integrity: sha256-TzhvGdrwcM0bazDvrWSqh+M/H8GKYf1Na6aGm2Qg4+c= + dependencies: + - datetime + - effect + - either + - enums + - exceptions + - functions + - integers + - js-date + - maybe + - node-buffer + - node-path + - node-streams + - nullable + - partial + - prelude + - strings + - unsafe-coerce + node-path: + type: registry + version: 5.0.0 + integrity: sha256-pd82nQ+2l5UThzaxPdKttgDt7xlsgIDLpPG0yxDEdyE= + dependencies: + - effect + node-streams: + type: registry + version: 9.0.0 + integrity: sha256-2n6dq7YWleTDmD1Kur/ul7Cn08IvWrScgPf+0PgX2TQ= + dependencies: + - aff + - effect + - either + - exceptions + - node-buffer + - node-event-emitter + - nullable + - prelude + node-zlib: + type: registry + version: 0.4.0 + integrity: sha256-kYSajFQFzWVg71l5/y4w4kXdTr5EJoqyV3D2RqmAjQ4= + dependencies: + - aff + - effect + - either + - functions + - node-buffer + - node-streams + - prelude + - unsafe-coerce + nonempty: + type: registry + version: 7.0.0 + integrity: sha256-54ablJZUHGvvlTJzi3oXyPCuvY6zsrWJuH/dMJ/MFLs= + dependencies: + - control + - foldable-traversable + - maybe + - prelude + - tuples + - unfoldable + now: + type: registry + version: 6.0.0 + integrity: sha256-xZ7x37ZMREfs6GCDw/h+FaKHV/3sPWmtqBZRGTxybQY= + dependencies: + - datetime + - effect + nullable: + type: registry + version: 6.0.0 + integrity: sha256-yiGBVl3AD+Guy4kNWWeN+zl1gCiJK+oeIFtZtPCw4+o= + dependencies: + - effect + - functions + - maybe + numbers: + type: registry + version: 9.0.1 + integrity: sha256-/9M6aeMDBdB4cwYDeJvLFprAHZ49EbtKQLIJsneXLIk= + dependencies: + - functions + - maybe + ordered-collections: + type: registry + version: 3.2.0 + integrity: sha256-o9jqsj5rpJmMdoe/zyufWHFjYYFTTsJpgcuCnqCO6PM= + dependencies: + - arrays + - foldable-traversable + - gen + - lists + - maybe + - partial + - prelude + - st + - tailrec + - tuples + - unfoldable + orders: + type: registry + version: 6.0.0 + integrity: sha256-nBA0g3/ai0euH8q9pSbGqk53W2q6agm/dECZTHcoink= + dependencies: + - newtype + - prelude + parallel: + type: registry + version: 6.0.0 + integrity: sha256-VJbkGD0rAKX+NUEeBJbYJ78bEKaZbgow+QwQEfPB6ko= + dependencies: + - control + - effect + - either + - foldable-traversable + - functors + - maybe + - newtype + - prelude + - profunctor + - refs + - transformers + partial: + type: registry + version: 4.0.0 + integrity: sha256-fwXerld6Xw1VkReh8yeQsdtLVrjfGiVuC5bA1Wyo/J4= + dependencies: [] + pipes: + type: registry + version: 8.0.0 + integrity: sha256-kvfqGM4cPA/wCcBHbp5psouFw5dZGvku2462x7ZBwSY= + dependencies: + - aff + - lists + - mmorph + - prelude + - tailrec + - transformers + - tuples + prelude: + type: registry + version: 6.0.1 + integrity: sha256-o8p6SLYmVPqzXZhQFd2hGAWEwBoXl1swxLG/scpJ0V0= + dependencies: [] + profunctor: + type: registry + version: 6.0.1 + integrity: sha256-E58hSYdJvF2Qjf9dnWLPlJKh2Z2fLfFLkQoYi16vsFk= + dependencies: + - control + - distributive + - either + - exists + - invariant + - newtype + - prelude + - tuples + quickcheck: + type: registry + version: 8.0.1 + integrity: sha256-ZvpccKQCvgslTXZCNmpYW4bUsFzhZd/kQUr2WmxFTGY= + dependencies: + - arrays + - console + - control + - effect + - either + - enums + - exceptions + - foldable-traversable + - gen + - identity + - integers + - lazy + - lcg + - lists + - maybe + - newtype + - nonempty + - numbers + - partial + - prelude + - record + - st + - strings + - tailrec + - transformers + - tuples + - unfoldable + random: + type: registry + version: 6.0.0 + integrity: sha256-CJ611a35MPCE7XQMp0rdC6MCn76znlhisiCRgboAG+Q= + dependencies: + - effect + - integers + record: + type: registry + version: 4.0.0 + integrity: sha256-Za5U85bTRJEfGK5Sk4hM41oXy84YQI0I8TL3WUn1Qzg= + dependencies: + - functions + - prelude + - unsafe-coerce + refs: + type: registry + version: 6.0.0 + integrity: sha256-Vgwne7jIbD3ZMoLNNETLT8Litw6lIYo3MfYNdtYWj9s= + dependencies: + - effect + - prelude + safe-coerce: + type: registry + version: 2.0.0 + integrity: sha256-a1ibQkiUcbODbLE/WAq7Ttbbh9ex+x33VCQ7GngKudU= + dependencies: + - unsafe-coerce + simple-json: + type: registry + version: 9.0.0 + integrity: sha256-K3RJaThqsszTd+TEklzZmAdDqvIHWgXIfKqlsoykU1c= + dependencies: + - arrays + - exceptions + - foreign + - foreign-object + - nullable + - prelude + - record + - typelevel-prelude + - variant + spec: + type: registry + version: 7.6.0 + integrity: sha256-+merGdQbL9zWONbnt8S8J9afGJ59MQqGtS0qSd3yu4I= + dependencies: + - aff + - ansi + - arrays + - avar + - bifunctors + - control + - datetime + - effect + - either + - exceptions + - foldable-traversable + - fork + - identity + - integers + - lists + - maybe + - newtype + - now + - ordered-collections + - parallel + - pipes + - prelude + - refs + - strings + - tailrec + - transformers + - tuples + st: + type: registry + version: 6.2.0 + integrity: sha256-z9X0WsOUlPwNx9GlCC+YccCyz8MejC8Wb0C4+9fiBRY= + dependencies: + - partial + - prelude + - tailrec + - unsafe-coerce + strings: + type: registry + version: 6.0.1 + integrity: sha256-WssD3DbX4OPzxSdjvRMX0yvc9+pS7n5gyPv5I2Trb7k= + dependencies: + - arrays + - control + - either + - enums + - foldable-traversable + - gen + - integers + - maybe + - newtype + - nonempty + - partial + - prelude + - tailrec + - tuples + - unfoldable + - unsafe-coerce + tailrec: + type: registry + version: 6.1.0 + integrity: sha256-Xx19ECVDRrDWpz9D2GxQHHV89vd61dnXxQm0IcYQHGk= + dependencies: + - bifunctors + - effect + - either + - identity + - maybe + - partial + - prelude + - refs + transformers: + type: registry + version: 6.0.0 + integrity: sha256-Pzw40HjthX77tdPAYzjx43LK3X5Bb7ZspYAp27wksFA= + dependencies: + - control + - distributive + - effect + - either + - exceptions + - foldable-traversable + - identity + - lazy + - maybe + - newtype + - prelude + - tailrec + - tuples + - unfoldable + tuples: + type: registry + version: 7.0.0 + integrity: sha256-1rXgTomes9105BjgXqIw0FL6Fz1lqqUTLWOumhWec1M= + dependencies: + - control + - invariant + - prelude + type-equality: + type: registry + version: 4.0.1 + integrity: sha256-Hs9D6Y71zFi/b+qu5NSbuadUQXe5iv5iWx0226vOHUw= + dependencies: [] + typelevel-prelude: + type: registry + version: 7.0.0 + integrity: sha256-uFF2ph+vHcQpfPuPf2a3ukJDFmLhApmkpTMviHIWgJM= + dependencies: + - prelude + - type-equality + unfoldable: + type: registry + version: 6.0.0 + integrity: sha256-JtikvJdktRap7vr/K4ITlxUX1QexpnqBq0G/InLr6eg= + dependencies: + - foldable-traversable + - maybe + - partial + - prelude + - tuples + unsafe-coerce: + type: registry + version: 6.0.0 + integrity: sha256-IqIYW4Vkevn8sI+6aUwRGvd87tVL36BBeOr0cGAE7t0= + dependencies: [] + variant: + type: registry + version: 8.0.0 + integrity: sha256-SR//zQDg2dnbB8ZHslcxieUkCeNlbMToapvmh9onTtw= + dependencies: + - enums + - lists + - maybe + - partial + - prelude + - record + - tuples + - unsafe-coerce diff --git a/spago.yaml b/spago.yaml new file mode 100644 index 0000000..06b8f29 --- /dev/null +++ b/spago.yaml @@ -0,0 +1,39 @@ +package: + name: node-stream-pipes + publish: + version: '1.2.19' + license: 'GPL-3.0-or-later' + location: + githubOwner: 'cakekindel' + githubRepo: 'purescript-node-stream-pipes' + build: + strict: true + pedanticPackages: true + dependencies: + - aff: ">=7.1.0 <8.0.0" + - control: ">=6.0.0 <7.0.0" + - effect: ">=4.0.0 <5.0.0" + - either: ">=6.1.0 <7.0.0" + - exceptions: ">=6.0.0 <7.0.0" + - maybe: ">=6.0.0 <7.0.0" + - newtype: ">=5.0.0 <6.0.0" + - node-buffer: ">=9.0.0 <10.0.0" + - node-event-emitter: ">=3.0.0 <4.0.0" + - node-streams: ">=9.0.0 <10.0.0" + - pipes: ">=8.0.0 <9.0.0" + - prelude: ">=6.0.1 <7.0.0" + - tailrec: ">=6.1.0 <7.0.0" + - transformers: ">=6.0.0 <7.0.0" + - unsafe-coerce: ">=6.0.0 <7.0.0" + test: + main: Test.Main + dependencies: + - console + - gen + - node-fs + - node-zlib + - quickcheck + - simple-json + - spec +workspace: + extraPackages: {} diff --git a/src/Node.Stream.Object.js b/src/Node.Stream.Object.js new file mode 100644 index 0000000..c3b6a63 --- /dev/null +++ b/src/Node.Stream.Object.js @@ -0,0 +1,49 @@ +import Stream from "stream"; + +/** @type {(s: Stream.Readable | Stream.Transform) => () => boolean} */ +export const isReadableImpl = s => () => s.readable + +/** @type {(s: Stream.Writable | Stream.Readable) => () => boolean} */ +export const isClosedImpl = s => () => s.closed + +/** @type {(s: Stream.Writable | Stream.Transform) => () => boolean} */ +export const isWritableImpl = s => () => s.writable + +/** @type {(s: Stream.Readable | Stream.Transform) => () => boolean} */ +export const isReadableEndedImpl = s => () => s.readableEnded + +/** @type {(s: Stream.Writable | Stream.Transform) => () => boolean} */ +export const isWritableEndedImpl = s => () => s.writableEnded + +/** @type {(s: Stream.Writable | Stream.Transform) => () => void} */ +export const endImpl = (s) => () => s.end(); + +/** @type {(o: {ok: WriteResult, wouldBlock: WriteResult, closed: WriteResult}) => (s: Stream.Writable | Stream.Transform) => (a: unknown) => () => WriteResult} */ +export const writeImpl = ({ok, wouldBlock, closed}) => (s) => (a) => () => { + if (s.closed || s.writableEnded) { + return closed + } + + if (s.write(a)) { + return ok + } else { + return wouldBlock + } +} + +/** @type {(o: {just: (_a: unknown) => ReadResult, wouldBlock: ReadResult, closed: ReadResult}) => (s: Stream.Readable | Stream.Transform) => () => ReadResult} */ +export const readImpl = + ({ just, closed, wouldBlock }) => + (s) => + () => { + if (s.closed || s.readableEnded) { + return closed; + } + + const a = s.read(); + if (a === null) { + return wouldBlock; + } else { + return just(a); + } + }; diff --git a/src/Node.Stream.Object.purs b/src/Node.Stream.Object.purs new file mode 100644 index 0000000..30465b1 --- /dev/null +++ b/src/Node.Stream.Object.purs @@ -0,0 +1,168 @@ +module Node.Stream.Object where + +import Prelude + +import Data.Either (Either(..)) +import Effect (Effect) +import Effect.Aff (Aff, effectCanceler, makeAff) +import Effect.Class (liftEffect) +import Effect.Exception (Error) +import Effect.Uncurried (mkEffectFn1) +import Node.Buffer (Buffer) +import Node.EventEmitter (EventHandle(..)) +import Node.EventEmitter as Event +import Node.EventEmitter.UtilTypes (EventHandle0, EventHandle1) +import Node.Stream as Stream +import Unsafe.Coerce (unsafeCoerce) + +data ReadResult a + = ReadWouldBlock + | ReadClosed + | ReadJust a + +data WriteResult + = WriteWouldBlock + | WriteClosed + | WriteOk + +type ReadResultFFI a = { closed :: ReadResult a, wouldBlock :: ReadResult a, just :: a -> ReadResult a } +type WriteResultFFI = { closed :: WriteResult, wouldBlock :: WriteResult, ok :: WriteResult } + +foreign import data Writable :: Type -> Type +foreign import data Readable :: Type -> Type +foreign import data Transform :: Type -> Type -> Type + +foreign import endImpl :: forall s. s -> Effect Unit +foreign import writeImpl :: forall s a. WriteResultFFI -> s -> a -> Effect WriteResult +foreign import readImpl :: forall s a. ReadResultFFI a -> s -> Effect (ReadResult a) +foreign import isReadableImpl :: forall s. s -> Effect Boolean +foreign import isWritableImpl :: forall s. s -> Effect Boolean +foreign import isReadableEndedImpl :: forall s. s -> Effect Boolean +foreign import isWritableEndedImpl :: forall s. s -> Effect Boolean +foreign import isClosedImpl :: forall s. s -> Effect Boolean + +readResultFFI :: forall a. ReadResultFFI a +readResultFFI = {closed: ReadClosed, wouldBlock: ReadWouldBlock, just: ReadJust} + +writeResultFFI :: WriteResultFFI +writeResultFFI = {closed: WriteClosed, wouldBlock: WriteWouldBlock, ok: WriteOk} + +class Stream :: Type -> Constraint +class Stream s where + isClosed :: s -> Effect Boolean + +instance Stream (Readable a) where + isClosed = isClosedImpl +else instance Stream (Writable a) where + isClosed = isClosedImpl +else instance Stream (Transform a b) where + isClosed = isClosedImpl +else instance Stream s => Stream s where + isClosed s = isClosed s + +class Stream s <= Read s a | s -> a where + isReadable :: s -> Effect Boolean + isReadableEnded :: s -> Effect Boolean + read :: s -> Effect (ReadResult a) + +class Stream s <= Write s a | s -> a where + isWritable :: s -> Effect Boolean + isWritableEnded :: s -> Effect Boolean + write :: s -> a -> Effect WriteResult + end :: s -> Effect Unit + +instance Read (Readable a) a where + isReadable = isReadableImpl + isReadableEnded = isReadableEndedImpl + read = readImpl readResultFFI +else instance Read (Transform a b) b where + isReadable = isReadableImpl + isReadableEnded = isReadableEndedImpl + read = readImpl readResultFFI +else instance (Read s a) => Read s a where + isReadable = isReadableImpl + isReadableEnded = isReadableEndedImpl + read s = read s + +instance Write (Writable a) a where + isWritable = isWritableImpl + isWritableEnded = isWritableEndedImpl + write s = writeImpl writeResultFFI s + end = endImpl +else instance Write (Transform a b) a where + isWritable = isWritableImpl + isWritableEnded = isWritableEndedImpl + write s = writeImpl writeResultFFI s + end = endImpl +else instance (Write s a) => Write s a where + isWritable = isWritableImpl + isWritableEnded = isWritableEndedImpl + write s a = write s a + end s = end s + +fromBufferReadable :: forall r. Stream.Readable r -> Readable Buffer +fromBufferReadable = unsafeCoerce + +fromBufferTransform :: Stream.Duplex -> Transform Buffer Buffer +fromBufferTransform = unsafeCoerce + +fromBufferWritable :: forall r. Stream.Writable r -> Writable Buffer +fromBufferWritable = unsafeCoerce + +fromStringReadable :: forall r. Stream.Readable r -> Readable String +fromStringReadable = unsafeCoerce + +fromStringTransform :: Stream.Duplex -> Transform String String +fromStringTransform = unsafeCoerce + +fromStringWritable :: forall r. Stream.Writable r -> Writable String +fromStringWritable = unsafeCoerce + +awaitReadableOrClosed :: forall s a. Read s a => s -> Aff Unit +awaitReadableOrClosed s = do + closed <- liftEffect $ isClosed s + ended <- liftEffect $ isReadableEnded s + readable <- liftEffect $ isReadable s + when (not ended && not closed && not readable) $ makeAff \res -> do + cancelClose <- Event.once closeH (res $ Right unit) s + cancelError <- Event.once errorH (res <<< Left) s + cancelReadable <- flip (Event.once readableH) s do + cancelClose + cancelError + res $ Right unit + pure $ effectCanceler do + cancelReadable + cancelClose + cancelError + +awaitWritableOrClosed :: forall s a. Write s a => s -> Aff Unit +awaitWritableOrClosed s = do + closed <- liftEffect $ isClosed s + ended <- liftEffect $ isWritableEnded s + writable <- liftEffect $ isWritable s + when (not closed && not ended && not writable) $ makeAff \res -> do + cancelClose <- Event.once closeH (res $ Right unit) s + cancelError <- Event.once errorH (res <<< Left) s + cancelDrain <- flip (Event.once drainH) s do + cancelClose + cancelError + res $ Right unit + pure $ effectCanceler do + cancelDrain + cancelClose + cancelError + +readableH :: forall s a. Read s a => EventHandle0 s +readableH = EventHandle "readable" identity + +drainH :: forall s a. Write s a => EventHandle0 s +drainH = EventHandle "drain" identity + +closeH :: forall s. Stream s => EventHandle0 s +closeH = EventHandle "close" identity + +errorH :: forall s. Stream s => EventHandle1 s Error +errorH = EventHandle "error" mkEffectFn1 + +endH :: forall s a. Write s a => EventHandle0 s +endH = EventHandle "end" identity diff --git a/src/Pipes.CSV.Parse.purs b/src/Pipes.CSV.Parse.purs new file mode 100644 index 0000000..e460df4 --- /dev/null +++ b/src/Pipes.CSV.Parse.purs @@ -0,0 +1 @@ +module Pipes.CSV.Parse where diff --git a/src/Pipes.Node.Stream.purs b/src/Pipes.Node.Stream.purs new file mode 100644 index 0000000..3dbc5e3 --- /dev/null +++ b/src/Pipes.Node.Stream.purs @@ -0,0 +1,95 @@ +module Pipes.Node.Stream where + +import Prelude + +import Control.Alternative (empty) +import Control.Monad.Maybe.Trans (MaybeT(..), runMaybeT) +import Control.Monad.Rec.Class (whileJust) +import Control.Monad.Trans.Class (lift) +import Data.Maybe (Maybe(..)) +import Data.Newtype (wrap) +import Effect.Aff (Aff, delay) +import Effect.Aff.Class (liftAff) +import Effect.Class (liftEffect) +import Node.Stream.Object as O +import Pipes (await, yield) +import Pipes.Core (Consumer, Pipe, Producer) +import Pipes.Internal (Proxy) +import Pipes.Internal as P.I + +type ProxyFFI :: Type -> Type -> Type -> Type -> Type -> Type -> Type +type ProxyFFI a' a b' b r pipe = + { pure :: r -> pipe + , request :: a' -> (a -> pipe) -> pipe + , respond :: b -> (b' -> pipe) -> pipe + } + +proxyFFI :: forall m a' a b' b r. ProxyFFI a' a b' b r (Proxy a' a b' b m r) +proxyFFI = { pure: P.I.Pure, request: P.I.Request, respond: P.I.Respond } + +fromReadable :: forall s a. O.Read s a => s -> Producer (Maybe a) Aff Unit +fromReadable r = whileJust do + liftAff $ delay $ wrap 0.0 + a <- liftEffect $ O.read r + case a of + O.ReadWouldBlock -> do + lift $ O.awaitReadableOrClosed r + pure $ Just unit + O.ReadClosed -> do + yield Nothing + pure Nothing + O.ReadJust a' -> do + yield $ Just a' + pure $ Just unit + +fromWritable :: forall s a. O.Write s a => s -> Consumer (Maybe a) Aff Unit +fromWritable w = do + whileJust $ runMaybeT do + liftAff $ delay $ wrap 0.0 + a <- MaybeT await + res <- liftEffect $ O.write w a + case res of + O.WriteClosed -> empty + O.WriteOk -> pure unit + O.WriteWouldBlock -> do + liftAff $ O.awaitWritableOrClosed w + pure unit + liftEffect $ O.end w + +fromTransform :: forall a b. O.Transform a b -> Pipe (Maybe a) (Maybe b) Aff Unit +fromTransform t = + let + read' {exitOnWouldBlock} = + whileJust $ runMaybeT do + liftAff $ delay $ wrap 0.0 + res <- liftEffect $ O.read t + case res of + O.ReadWouldBlock -> + if exitOnWouldBlock then do + empty + else do + liftAff $ O.awaitReadableOrClosed t + pure unit + O.ReadJust b -> do + lift $ yield $ Just b + pure unit + O.ReadClosed -> do + lift $ yield Nothing + empty + in do + whileJust $ runMaybeT do + liftAff $ delay $ wrap 0.0 + + a <- MaybeT await + writeRes <- liftEffect $ O.write t a + + lift $ read' {exitOnWouldBlock: true} + + case writeRes of + O.WriteOk -> pure unit + O.WriteClosed -> empty + O.WriteWouldBlock -> do + liftAff $ O.awaitWritableOrClosed t + pure unit + liftEffect $ O.end t + read' {exitOnWouldBlock: false} diff --git a/test/Test/Main.purs b/test/Test/Main.purs new file mode 100644 index 0000000..e7b57c7 --- /dev/null +++ b/test/Test/Main.purs @@ -0,0 +1,14 @@ +module Test.Main where + +import Prelude + +import Data.Maybe (Maybe(..)) +import Effect (Effect) +import Effect.Aff (launchAff_) +import Test.Pipes.Node.Stream as Test.Pipes.Node.Stream +import Test.Spec.Reporter (consoleReporter, specReporter) +import Test.Spec.Runner (defaultConfig, runSpec') + +main :: Effect Unit +main = launchAff_ $ runSpec' (defaultConfig { timeout = Nothing }) [ specReporter ] do + Test.Pipes.Node.Stream.spec diff --git a/test/Test/Pipes.Node.Stream.js b/test/Test/Pipes.Node.Stream.js new file mode 100644 index 0000000..3d4c083 --- /dev/null +++ b/test/Test/Pipes.Node.Stream.js @@ -0,0 +1,4 @@ +import Stream from 'stream' + +/** @type {(a: Array) => Stream.Readable}*/ +export const readableFromArray = a => Stream.Readable.from(a) diff --git a/test/Test/Pipes.Node.Stream.purs b/test/Test/Pipes.Node.Stream.purs new file mode 100644 index 0000000..dcd3341 --- /dev/null +++ b/test/Test/Pipes.Node.Stream.purs @@ -0,0 +1,158 @@ +module Test.Pipes.Node.Stream where + +import Prelude + +import Control.Monad.Error.Class (liftEither, try) +import Control.Monad.Morph (hoist) +import Control.Monad.Trans.Class (lift) +import Data.Array as Array +import Data.Bifunctor (lmap) +import Data.Foldable (fold, intercalate) +import Data.List ((:)) +import Data.List as List +import Data.Maybe (Maybe(..), fromMaybe) +import Data.Newtype (wrap) +import Data.String.Gen (genAlphaString) +import Data.Traversable (for_, traverse) +import Data.Tuple (fst) +import Data.Tuple.Nested (type (/\), (/\)) +import Effect (Effect) +import Effect.Aff (Aff, bracket, delay) +import Effect.Class (liftEffect) +import Effect.Exception (error) +import Node.Buffer (Buffer) +import Node.Buffer as Buffer +import Node.Encoding (Encoding(..)) +import Node.FS.Stream as FS.Stream +import Node.FS.Sync as FS +import Node.Stream.Object as O +import Node.Zlib as Zlib +import Pipes (yield, (>->)) +import Pipes.Core (Consumer, Producer, Pipe, runEffect) +import Pipes.Node.Stream as S +import Pipes.Prelude as Pipe +import Simple.JSON (class ReadForeign, class WriteForeign, readJSON, writeJSON) +import Test.QuickCheck.Arbitrary (arbitrary) +import Test.QuickCheck.Gen (randomSample', randomSampleOne, resize) +import Test.Spec (Spec, around, describe, it) +import Test.Spec.Assertions (shouldEqual) + +foreign import readableFromArray :: forall @a. Array a -> O.Readable a + +str2buf :: Pipe (Maybe String) (Maybe Buffer) Aff Unit +str2buf = hoist liftEffect $ Pipe.mapM (traverse $ flip Buffer.fromString UTF8) + +buf2str :: Pipe (Maybe Buffer) (Maybe String) Aff Unit +buf2str = hoist liftEffect $ Pipe.mapM (traverse $ Buffer.toString UTF8) + +buf2hex :: Pipe (Maybe Buffer) (Maybe String) Aff Unit +buf2hex = hoist liftEffect $ Pipe.mapM (traverse $ Buffer.toString Hex) + +jsonStringify :: forall a. WriteForeign a => Pipe (Maybe a) (Maybe String) Aff Unit +jsonStringify = Pipe.map (map writeJSON) + +jsonParse :: forall @a. ReadForeign a => Pipe (Maybe String) (Maybe a) Aff Unit +jsonParse = Pipe.mapM (traverse (liftEither <<< lmap (error <<< show) <<< readJSON)) + +writer :: String -> Effect (Consumer (Maybe Buffer) Aff Unit) +writer a = S.fromWritable <$> O.fromBufferWritable <$> FS.Stream.createWriteStream a + +reader :: String -> Effect (Producer (Maybe Buffer) Aff Unit) +reader a = S.fromReadable <$> O.fromBufferReadable <$> FS.Stream.createReadStream a + +tmpFile :: (String -> Aff Unit) -> Aff Unit +tmpFile f = tmpFiles (f <<< fst) + +tmpFiles :: (String /\ String -> Aff Unit) -> Aff Unit +tmpFiles = + let + acq = do + randa <- liftEffect $ randomSampleOne $ resize 10 genAlphaString + randb <- liftEffect $ randomSampleOne $ resize 10 genAlphaString + void $ try $ liftEffect $ FS.mkdir ".tmp" + pure $ ("tmp." <> randa) /\ ("tmp." <> randb) + rel (a /\ b) = liftEffect (try (FS.rm a) *> void (try $ FS.rm b)) + in + bracket acq rel + +spec :: Spec Unit +spec = + describe "Test.Pipes.Node.Stream" do + describe "Readable" do + describe "Readable.from()" do + it "empty" do + vals <- List.catMaybes <$> (Pipe.toListM $ S.fromReadable $ readableFromArray @{ foo :: String } []) + vals `shouldEqual` List.Nil + it "singleton" do + vals <- List.catMaybes <$> (Pipe.toListM $ S.fromReadable $ readableFromArray @{ foo :: String } [ { foo: "1" } ]) + vals `shouldEqual` ({ foo: "1" } : List.Nil) + it "many elements" do + let exp = (\n -> { foo: show n }) <$> Array.range 0 100 + vals <- List.catMaybes <$> (Pipe.toListM $ S.fromReadable $ readableFromArray exp) + vals `shouldEqual` (List.fromFoldable exp) + describe "Writable" $ around tmpFile do + describe "fs.WriteStream" do + it "pipe to file" \p -> do + w <- S.fromWritable <$> O.fromBufferWritable <$> liftEffect (FS.Stream.createWriteStream p) + let + source = do + buf <- liftEffect $ Buffer.fromString "hello" UTF8 + yield $ Just buf + yield Nothing + runEffect $ source >-> w + contents <- liftEffect $ FS.readTextFile UTF8 p + contents `shouldEqual` "hello" + it "async pipe to file" \p -> do + w <- S.fromWritable <$> O.fromBufferWritable <$> liftEffect (FS.Stream.createWriteStream p) + let + source = do + yield $ Just "hello, " + lift $ delay $ wrap 5.0 + yield $ Just "world!" + lift $ delay $ wrap 5.0 + yield $ Just " " + lift $ delay $ wrap 5.0 + yield $ Just "this is a " + lift $ delay $ wrap 5.0 + yield $ Just "test." + yield Nothing + runEffect $ source >-> str2buf >-> w + contents <- liftEffect $ FS.readTextFile UTF8 p + contents `shouldEqual` "hello, world! this is a test." + it "chained pipes" \p -> do + let + obj = do + str :: String <- genAlphaString + num :: Int <- arbitrary + stuff :: Array String <- arbitrary + pure {str, num, stuff} + objs <- liftEffect $ randomSample' 1 obj + let + exp = fold (writeJSON <$> objs) + objs' = for_ (Just <$> objs) yield *> yield Nothing + w <- liftEffect $ writer p + runEffect $ objs' >-> jsonStringify >-> str2buf >-> w + contents <- liftEffect $ FS.readTextFile UTF8 p + contents `shouldEqual` exp + describe "Transform" do + it "gzip" do + let + json = do + yield $ Just $ writeJSON {foo: "bar"} + yield Nothing + exp = "1f8b0800000000000003ab564acbcf57b2524a4a2c52aa0500eff52bfe0d000000" + gzip <- S.fromTransform <$> O.fromBufferTransform <$> liftEffect (Zlib.toDuplex <$> Zlib.createGzip) + outs :: List.List String <- List.catMaybes <$> Pipe.toListM (json >-> str2buf >-> gzip >-> buf2hex) + fold outs `shouldEqual` exp + around tmpFiles + $ it "file >-> gzip >-> file >-> gunzip" \(a /\ b) -> do + liftEffect $ FS.writeTextFile UTF8 a $ writeJSON [1, 2, 3, 4] + areader <- liftEffect $ reader a + bwriter <- liftEffect $ writer b + gzip <- S.fromTransform <$> O.fromBufferTransform <$> liftEffect (Zlib.toDuplex <$> Zlib.createGzip) + runEffect $ areader >-> gzip >-> bwriter + + gunzip <- S.fromTransform <$> O.fromBufferTransform <$> liftEffect (Zlib.toDuplex <$> Zlib.createGunzip) + breader <- liftEffect $ reader b + nums <- Pipe.toListM (breader >-> gunzip >-> buf2str >-> jsonParse @(Array Int) >-> Pipe.mapFoldable (fromMaybe [])) + Array.fromFoldable nums `shouldEqual` [1, 2, 3, 4]