feat: initial commit

This commit is contained in:
orion 2024-04-30 13:39:51 -05:00
commit 8c578b42bd
Signed by: orion
GPG Key ID: 6D4165AE4C928719
15 changed files with 1170 additions and 0 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
bower_components/
node_modules/
.pulp-cache/
output/
output-es/
generated-docs/
.psc-package/
.psc*
.purs*
.psa*
.spago

1
.tool-versions Normal file
View File

@ -0,0 +1 @@
purescript 0.15.16-1

BIN
bun.lockb Executable file

Binary file not shown.

16
jsconfig.json Normal file
View File

@ -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"]
}

10
package.json Normal file
View File

@ -0,0 +1,10 @@
{
"name": "purescript-csv-stream",
"dependencies": {
"csv-parse": "^5.5.5",
"csv-stringify": "^6.4.6"
},
"devDependencies": {
"typescript": "^5.4.5"
}
}

760
spago.lock Normal file
View File

@ -0,0 +1,760 @@
workspace:
packages:
csv-stream:
path: ./
dependencies:
- aff
- arrays
- bifunctors
- datetime
- effect
- either
- exceptions
- foldable-traversable
- foreign
- foreign-object
- integers
- lists
- maybe
- newtype
- node-event-emitter
- node-streams
- nullable
- numbers
- precise-datetime
- prelude
- record
- st
- strings
- tailrec
- transformers
- typelevel-prelude
- unsafe-coerce
test_dependencies:
- console
build_plan:
- aff
- arraybuffer-types
- arrays
- bifunctors
- console
- const
- contravariant
- control
- datetime
- decimals
- distributive
- effect
- either
- enums
- exceptions
- exists
- fixed-points
- foldable-traversable
- foreign
- foreign-object
- formatters
- functions
- functors
- gen
- identity
- integers
- invariant
- js-date
- lazy
- lists
- maybe
- newtype
- node-buffer
- node-event-emitter
- node-streams
- nonempty
- now
- nullable
- numbers
- ordered-collections
- orders
- parallel
- parsing
- partial
- precise-datetime
- prelude
- profunctor
- record
- refs
- safe-coerce
- st
- strings
- tailrec
- transformers
- tuples
- type-equality
- typelevel-prelude
- unfoldable
- unicode
- unsafe-coerce
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
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
bifunctors:
type: registry
version: 6.0.0
integrity: sha256-/gZwC9YhNxZNQpnHa5BIYerCGM2jeX9ukZiEvYxm5Nw=
dependencies:
- const
- either
- newtype
- prelude
- tuples
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
decimals:
type: registry
version: 7.1.0
integrity: sha256-DriR6lPEfFpjVv7e4JAQkr3ZLf0h17Qg2cAIrhxWV7w=
dependencies:
- maybe
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
fixed-points:
type: registry
version: 7.0.0
integrity: sha256-hTl5fzeG4mzAOFzEzAeNH7kJvJgYCH7x3v2NdX9pOE4=
dependencies:
- exists
- newtype
- prelude
- transformers
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
formatters:
type: registry
version: 7.0.0
integrity: sha256-5JaC9d2p0xoqJWjWxlHH19R4iJwFTBr4j7SlYcLgicE=
dependencies:
- datetime
- fixed-points
- lists
- numbers
- parsing
- prelude
- transformers
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
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
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-streams:
type: registry
version: 9.0.0
integrity: sha256-2n6dq7YWleTDmD1Kur/ul7Cn08IvWrScgPf+0PgX2TQ=
dependencies:
- aff
- effect
- either
- exceptions
- node-buffer
- node-event-emitter
- nullable
- prelude
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
parsing:
type: registry
version: 10.2.0
integrity: sha256-ZDIdMFAKkst57x6BVa1aUWJnS8smoZnXsZ339Aq1mPA=
dependencies:
- arrays
- control
- effect
- either
- enums
- foldable-traversable
- functions
- identity
- integers
- lazy
- lists
- maybe
- newtype
- nullable
- numbers
- partial
- prelude
- st
- strings
- tailrec
- transformers
- tuples
- unfoldable
- unicode
- unsafe-coerce
partial:
type: registry
version: 4.0.0
integrity: sha256-fwXerld6Xw1VkReh8yeQsdtLVrjfGiVuC5bA1Wyo/J4=
dependencies: []
precise-datetime:
type: registry
version: 7.0.0
integrity: sha256-F7tzZ7++Ihtg3xjumzwaHQvGQg61UtEAe5MWeOlTzRY=
dependencies:
- arrays
- datetime
- decimals
- either
- enums
- foldable-traversable
- formatters
- integers
- js-date
- lists
- maybe
- newtype
- numbers
- prelude
- strings
- tuples
- unicode
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
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
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
unicode:
type: registry
version: 6.0.0
integrity: sha256-QJnTVZpmihEAUiMeYrfkusVoziJWp2hJsgi9bMQLue8=
dependencies:
- foldable-traversable
- maybe
- strings
unsafe-coerce:
type: registry
version: 6.0.0
integrity: sha256-IqIYW4Vkevn8sI+6aUwRGvd87tVL36BBeOr0cGAE7t0=
dependencies: []

45
spago.yaml Normal file
View File

@ -0,0 +1,45 @@
package:
name: csv-stream
publish:
version: '1.0.0'
license: 'GPL-3.0-or-later'
location:
githubOwner: 'cakekindel'
githubRepo: 'purescript-csv-stream'
build:
strict: true
pedanticPackages: true
dependencies:
- aff: ">=7.1.0 <8.0.0"
- arrays: ">=7.3.0 <8.0.0"
- bifunctors: ">=6.0.0 <7.0.0"
- datetime: ">=6.1.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"
- foldable-traversable: ">=6.0.0 <7.0.0"
- foreign: ">=7.0.0 <8.0.0"
- foreign-object: ">=4.1.0 <5.0.0"
- integers: ">=6.0.0 <7.0.0"
- lists: ">=7.0.0 <8.0.0"
- maybe: ">=6.0.0 <7.0.0"
- newtype: ">=5.0.0 <6.0.0"
- node-event-emitter: ">=3.0.0 <4.0.0"
- node-streams: ">=9.0.0 <10.0.0"
- nullable: ">=6.0.0 <7.0.0"
- numbers: ">=9.0.1 <10.0.0"
- precise-datetime: ">=7.0.0 <8.0.0"
- prelude: ">=6.0.1 <7.0.0"
- record: ">=4.0.0 <5.0.0"
- st: ">=6.2.0 <7.0.0"
- strings: ">=6.0.1 <7.0.0"
- tailrec: ">=6.1.0 <7.0.0"
- transformers: ">=6.0.0 <7.0.0"
- typelevel-prelude: ">=7.0.0 <8.0.0"
- unsafe-coerce: ">=6.0.0 <7.0.0"
test:
main: Test.Main
dependencies:
- console
workspace:
extraPackages: {}

44
src/Data.CSV.Record.purs Normal file
View File

@ -0,0 +1,44 @@
module Data.CSV.Record where
import Prelude
import Control.Monad.Error.Class (liftMaybe)
import Control.Monad.Except (Except)
import Data.Array as Array
import Data.CSV (class ReadCSV, class WriteCSV, readCSV, writeCSV)
import Data.List.NonEmpty (NonEmptyList)
import Data.Maybe (fromMaybe)
import Data.Symbol (class IsSymbol)
import Foreign (ForeignError(..))
import Prim.Row (class Cons, class Lacks)
import Prim.RowList (class RowToList, Cons, Nil, RowList)
import Record as Record
import Type.Prelude (Proxy(..))
class WriteCSVRecord :: Row Type -> RowList Type -> Constraint
class RowToList r rl <= WriteCSVRecord r rl | rl -> r where
writeCSVRecord :: { | r } -> Array String
instance (RowToList r (Cons k v tailrl), IsSymbol k, WriteCSV v, Lacks k tail, Cons k v tail r, WriteCSVRecord tail tailrl) => WriteCSVRecord r (Cons k v tailrl) where
writeCSVRecord r = let
val = writeCSV $ Record.get (Proxy @k) r
tail = writeCSVRecord @tail @tailrl $ Record.delete (Proxy @k) r
in
[val] <> tail
instance WriteCSVRecord () Nil where
writeCSVRecord _ = []
class ReadCSVRecord :: Row Type -> RowList Type -> Constraint
class RowToList r rl <= ReadCSVRecord r rl | rl -> r where
readCSVRecord :: Array String -> Except (NonEmptyList ForeignError) { | r }
instance (RowToList r (Cons k v tailrl), IsSymbol k, ReadCSV v, Lacks k tail, Cons k v tail r, ReadCSVRecord tail tailrl) => ReadCSVRecord r (Cons k v tailrl) where
readCSVRecord vals = do
valraw <- liftMaybe (pure $ ForeignError "unexpected end of record") $ Array.head vals
val <- readCSV @v valraw
tail <- readCSVRecord @tail @tailrl (fromMaybe [] $ Array.tail vals)
pure $ Record.insert (Proxy @k) val tail
instance ReadCSVRecord () Nil where
readCSVRecord _ = pure {}

73
src/Data.CSV.purs Normal file
View File

@ -0,0 +1,73 @@
module Data.CSV where
import Prelude
import Control.Monad.Error.Class (liftMaybe, throwError)
import Control.Monad.Except (Except)
import Data.DateTime (DateTime)
import Data.Int as Int
import Data.List.NonEmpty (NonEmptyList)
import Data.Maybe (Maybe(..), maybe)
import Data.Newtype (unwrap)
import Data.Number.Format (toString) as Number
import Data.PreciseDateTime (fromDateTime, fromRFC3339String, toDateTimeLossy, toRFC3339String)
import Data.RFC3339String (RFC3339String(..))
import Data.String as String
import Foreign (ForeignError(..), readInt, readNumber, unsafeToForeign)
class ReadCSV a where
readCSV :: String -> Except (NonEmptyList ForeignError) a
class WriteCSV a where
writeCSV :: a -> String
instance ReadCSV Int where
readCSV = readInt <<< unsafeToForeign
instance ReadCSV Number where
readCSV = readNumber <<< unsafeToForeign
instance ReadCSV String where
readCSV = pure
instance ReadCSV DateTime where
readCSV s = map toDateTimeLossy $ liftMaybe (pure $ ForeignError $ "invalid ISO date string: " <> s) $ fromRFC3339String $ RFC3339String s
instance ReadCSV Boolean where
readCSV s =
let
inner "t" = pure true
inner "true" = pure true
inner "yes" = pure true
inner "y" = pure true
inner "1" = pure true
inner "f" = pure false
inner "false" = pure false
inner "no" = pure false
inner "n" = pure false
inner "0" = pure false
inner _ = throwError $ pure $ ForeignError $ "invalid boolean value: " <> s
in
inner $ String.toLower s
instance ReadCSV a => ReadCSV (Maybe a) where
readCSV "" = pure Nothing
readCSV s = Just <$> readCSV s
instance WriteCSV Int where
writeCSV = Int.toStringAs Int.decimal
instance WriteCSV Number where
writeCSV = Number.toString
instance WriteCSV String where
writeCSV = identity
instance WriteCSV DateTime where
writeCSV = unwrap <<< toRFC3339String <<< fromDateTime
instance WriteCSV Boolean where
writeCSV = show
instance WriteCSV a => WriteCSV (Maybe a) where
writeCSV = maybe "" writeCSV

View File

@ -0,0 +1,7 @@
import {parse} from 'csv-parse'
/** @type {(s: import('csv-parse').Options) => () => import('csv-parse').Parser} */
export const makeImpl = c => () => parse(c)
/** @type {(s: import('stream').Duplex) => () => string[] | null} */
export const readImpl = s => () => s.read();

View File

@ -0,0 +1,121 @@
module Node.Stream.CSV.Readable where
import Prelude
import Control.Monad.Error.Class (liftEither)
import Control.Monad.Except (runExcept)
import Control.Monad.Maybe.Trans (MaybeT(..), runMaybeT)
import Control.Monad.Rec.Class (whileJust)
import Control.Monad.ST.Global as ST
import Data.Array.ST as Array.ST
import Data.Bifunctor (lmap)
import Data.CSV.Record (class ReadCSVRecord, readCSVRecord)
import Data.Either (Either(..))
import Data.Maybe (Maybe(..))
import Data.Nullable (Nullable)
import Data.Nullable as Nullable
import Data.Traversable (for_)
import Effect (Effect)
import Effect.Aff (Aff, makeAff)
import Effect.Class (liftEffect)
import Effect.Exception (error)
import Effect.Uncurried (mkEffectFn1)
import Foreign (Foreign, unsafeToForeign)
import Foreign.Object (Object)
import Foreign.Object as Object
import Node.EventEmitter (EventHandle(..))
import Node.EventEmitter as Event
import Node.EventEmitter.UtilTypes (EventHandle1)
import Node.Stream (Read, Stream, Write)
import Node.Stream as Stream
import Prim.Row (class Union)
import Prim.RowList (class RowToList)
import Unsafe.Coerce (unsafeCoerce)
data CSVRead
-- | Stream transforming chunks of a CSV file
-- | into parsed purescript objects.
-- |
-- | The CSV contents may be piped into this stream
-- | as Buffer or String encoded chunks.
-- |
-- | Records can be read with `read` when `Node.Stream.readable`
-- | is true.
type CSVParser :: Row Type -> Row Type -> Type
type CSVParser a r = Stream (read :: Read, write :: Write, csv :: CSVRead | r)
-- | https://csv.js.org/parse/options/
type Config r =
( bom :: Boolean
, group_columns_by_name :: Boolean
, comment :: String
, comment_no_infix :: Boolean
, delimiter :: String
, encoding :: String
, escape :: String
, from :: Int
, from_line :: Int
, ignore_last_delimiters :: Boolean
, info :: Boolean
, max_record_size :: Int
, quote :: String
, raw :: Boolean
, record_delimiter :: String
, relax_column_count :: Boolean
, skip_empty_lines :: Boolean
, skip_records_with_empty_values :: Boolean
, skip_records_with_error :: Boolean
, to :: Int
, to_line :: Int
, trim :: Boolean
, ltrim :: Boolean
, rtrim :: Boolean
| r
)
make :: forall @r rl config missing extra. ReadCSVRecord r rl => Union config missing (Config extra) => { | config } -> Effect (CSVParser r ())
make = makeImpl <<< unsafeToForeign <<< Object.union (recordToForeign {columns: true, cast: false, cast_date: false}) <<< recordToForeign
-- | Reads a parsed record from the stream.
-- |
-- | Returns `Nothing` when either:
-- | - The internal buffer of parsed records has been exhausted, but there will be more (`Node.Stream.readable` and `Node.Stream.closed` are both `false`)
-- | - All records have been processed (`Node.Stream.closed` is `true`)
read :: forall @r rl a. RowToList r rl => ReadCSVRecord r rl => CSVParser r a -> Effect (Maybe { | r })
read stream = runMaybeT do
raw :: Array String <- MaybeT $ Nullable.toMaybe <$> readImpl stream
liftEither $ lmap (error <<< show) $ runExcept $ readCSVRecord @r @rl raw
-- | Collect all parsed records into an array
readAll :: forall @r rl a. RowToList r rl => ReadCSVRecord r rl => CSVParser r a -> Aff (Array { | r })
readAll stream = do
records <- liftEffect $ ST.toEffect $ Array.ST.new
whileJust do
isReadable <- liftEffect $ Stream.readable stream
when (not isReadable) $ makeAff \res -> mempty <* flip (Event.once Stream.readableH) stream $ res $ Right unit
liftEffect $ whileJust do
r <- read @r stream
for_ r \r' -> ST.toEffect $ Array.ST.push r' records
pure $ void r
isClosed <- liftEffect $ Stream.closed stream
pure $ if isClosed then Nothing else Just unit
liftEffect $ ST.toEffect $ Array.ST.unsafeFreeze records
-- | `data` event. Emitted when a CSV record has been parsed.
dataH :: forall r a. EventHandle1 (CSVParser r a) { | r }
dataH = EventHandle "data" mkEffectFn1
-- | FFI
foreign import makeImpl :: forall r. Foreign -> Effect (Stream r)
-- | FFI
foreign import readImpl :: forall r. Stream r -> Effect (Nullable (Array String))
-- | FFI
recordToForeign :: forall r. Record r -> Object Foreign
recordToForeign = unsafeCoerce

View File

@ -0,0 +1,7 @@
import {stringify} from 'csv-stringify'
/** @type {(c: import('csv-stringify').Options) => () => import('csv-stringify').Stringifier} */
export const makeImpl = c => () => stringify(c)
/** @type {(s: import('csv-stringify').Stringifier) => (vals: Array<string>) => () => void} */
export const writeImpl = s => vals => () => s.write(vals)

View File

@ -0,0 +1,60 @@
module Node.Stream.CSV.Writable where
import Prelude
import Data.CSV.Record (class WriteCSVRecord, writeCSVRecord)
import Data.String.Regex (Regex)
import Effect (Effect)
import Foreign (Foreign, unsafeToForeign)
import Foreign.Object (Object)
import Foreign.Object as Object
import Node.Stream (Read, Stream, Write)
import Prim.Row (class Union)
import Unsafe.Coerce (unsafeCoerce)
data CSVWrite
-- | Stream transforming rows of stringified CSV values
-- | to CSV-formatted rows.
-- |
-- | Write rows to the stream using `write`.
-- |
-- | Stringified rows are emitted on the `Readable` end as string
-- | chunks, meaning it can be treated as a `Node.Stream.Readable`
-- | that has had `setEncoding UTF8` invoked on it.
type CSVStringifier :: Row Type -> Row Type -> Type
type CSVStringifier a r = Stream (read :: Read, write :: Write, csv :: CSVWrite | r)
-- | https://csv.js.org/stringify/options/
type Config r =
( bom :: Boolean
, group_columns_by_name :: Boolean
, delimiter :: String
, record_delimiter :: String
, escape :: String
, escape_formulas :: Boolean
, header :: Boolean
, quote :: String
, quoted :: Boolean
, quoted_empty :: Boolean
, quoted_match :: Regex
, quoted_string :: Boolean
| r
)
foreign import makeImpl :: forall r. Foreign -> Effect (Stream r)
foreign import writeImpl :: forall r. Stream r -> Array String -> Effect Unit
recordToForeign :: forall r. Record r -> Object Foreign
recordToForeign = unsafeCoerce
-- | Create a CSVStringifier
make :: forall @r rl config missing extra. WriteCSVRecord r rl => Union config missing (Config extra) => { | config } -> Effect (CSVStringifier r ())
make = makeImpl <<< unsafeToForeign <<< Object.union (recordToForeign {columns: true, cast: false, cast_date: false}) <<< recordToForeign
-- | Write a record to a CSVStringifier.
-- |
-- | The record will be emitted on the `Readable` end
-- | of the stream as a string chunk.
write :: forall @r rl a. WriteCSVRecord r rl => CSVStringifier r a -> { | r } -> Effect Unit
write s = writeImpl s <<< writeCSVRecord @r @rl

2
src/Node.Stream.CSV.purs Normal file
View File

@ -0,0 +1,2 @@
module Node.Stream.CSV where

12
test/Test/Main.purs Normal file
View File

@ -0,0 +1,12 @@
module Test.Main where
import Prelude
import Effect (Effect)
import Effect.Class.Console (log)
main :: Effect Unit
main = do
log "🍕"
log "You should add some tests."