diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a39bce1..025c44c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,31 +10,23 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: purescript-contrib/setup-purescript@main with: - purescript: "unstable" + purescript: "latest" purs-tidy: "latest" + spago: "unstable" - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: "lts/*" - - name: Install dependencies - run: | - npm install -g bower - npm install - bower install --production - - name: Build source - run: npm run-script build + run: spago build --censor-stats --strict --pedantic-packages - name: Run tests - run: | - bower install - npm run-script test --if-present + run: spago test --censor-stats --strict --pedantic-packages - - name: Check formatting - run: | - purs-tidy check src test + - name: Verify formatting + run: purs-tidy check src test diff --git a/CHANGELOG.md b/CHANGELOG.md index 0732021..1c26ba7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,13 +2,49 @@ Notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - ## [Unreleased] Breaking changes: New features: +- Add functions to `Node.FS.Async`, `Node.FS.Aff`, `Node.FS.Sync` + + - readdir' + - readdirBuffer + - readdirBuffer' + - readdirDirent + - readdirDirent' + - readdirDirentBuffer + - readdirDirentBuffer' + - cp + - cp' + - fchmod + - fchown + - fdatasync + - fstat + - fsync + - ftruncate + - futimes + - glob + - glob' + - globDirent + - globDirent' + - lchmod + - lchown + - lutimes + - opendir + - opendir' + - readv + - statfs + - writev + +- Deprecate `Node.FS.Sync.fdFlush`, `Node.FS.Sync.fsync` should be used instead + +- Add modules `Dir`, `Dir.Aff`, `Dirent` + +- Fix tests - it was creating lot of `./test/fixturesASDFSDF` directories, now - create in `tmp` like `./tmp/test/fixturesASDFSDF` + Bugfixes: Other improvements: @@ -16,22 +52,27 @@ Other improvements: ## [v9.2.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v9.2.0) - 2024-06-23 New features: + - Add `lstat` to `Node.FS.Aff` (#85 by @artemisSystem) Bugfixes: + - Fixed internal reference to `rmdir` that should have been `rm` (#83 by @MonoidMusician) ## [v9.1.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v9.1.0) - 2023-07-26 Bugfixes: + - Update `node-streams` to `v9.0.0` to fix FFI issues (#78 by @JordanMartinez) ## [v9.0.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v9.0.0) - 2023-07-19 Breaking changes: + - Remove `StatsObj` and reimplement bindings to `Stats` object (#76 by @JordanMartinez) Previously, one could write the following to get a value on the `Stats` object: + ```purs getGid :: Stats -> Number getGid (Stats obj) = obj.gid @@ -39,21 +80,24 @@ Breaking changes: This record interface was removed as the underlying value is not a record that can be copied and modified as such. Now, one must call the corresponding function: + ```purs getGid :: Stats -> Number getGid s = Stats.gid s ``` + - Update `[fd]createReadStream`/`[fd]createWriteStream` to allow more options (#77 by @JordanMartinez) - | Removes... | ...in favor of | - | - | - | - | `createReadStreamWith` | `createReadStream'` | - | `fdCreateReadStreamWith` | `fdCreateReadStream'` | - | `createWriteStreamWith` | `createWriteStream'` | + | Removes... | ...in favor of | + | ------------------------- | ---------------------- | + | `createReadStreamWith` | `createReadStream'` | + | `fdCreateReadStreamWith` | `fdCreateReadStream'` | + | `createWriteStreamWith` | `createWriteStream'` | | `fdCreateWriteStreamWith` | `fdCreateWriteStream'` | - - In the new APIs, all options are exposed and may require converting + + In the new APIs, all options are exposed and may require converting PureScript values to JavaScript ones. + ```purs filePath # createWriteStream' { flags: fileFlagsToNode R @@ -63,11 +107,13 @@ Breaking changes: ``` New features: + - Integrate `node-fs-aff` into library (#75 by @JordanMartinez) Bugfixes: Other improvements: + - Update `node-buffer` to next breaking release: `v9.0.0` (#74 by @JordanMartinez) - Update `node-streams` to next breaking release: `TODO` (#74 by @JordanMartinez) - Update CI actions to v3 (#74 by @JordanMartinez) @@ -77,16 +123,19 @@ Other improvements: ## [v8.2.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v8.2.0) - 2023-03-23 New features: -- Add FFI for `access`, `copyFile` and `mkdtemp` (#73 by @JordanMartinez) + +- Add FFI for `access`, `copyFile` and `mkdtemp` (#73 by @JordanMartinez) ## [v8.1.1](https://github.com/purescript-node/purescript-node-fs/releases/tag/v8.1.1) - 2022-10-24 Other improvements: + - Use `EffectFn` throughout instead of unsafe `mkEffect` utility (#70 by @colinwahl) ## [v8.1.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v8.1.0) - 2022-06-10 New features: + - Add `lstat` (#66 by @artemisSystem) - Add rmdir', which takes an take options arg (#67 by @wclr) - Added rm and rm' version with and without options arg (#67 by @wclr) @@ -94,11 +143,13 @@ New features: ## [v8.0.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v8.0.0) - 2022-04-29 Breaking changes: + - Remove `Async.exists` (#61 by @sigma-andex) - Update `mkdir` to take an options record arg, exposing `recursive` option (#53, #55, #58 by @JordanMartinez) To get back the old behavior of `mkdir'`, you would call `mkdir' { recursive: false, mode: mkPerms all all all }` New features: + - Update project and deps to PureScript v0.15.0 (#59 by @JordanMartinez, @thomashoneyman, @sigma-andex) - Remove duplicate `node-buffer` from bower.json (@thomashoneyman) @@ -115,6 +166,7 @@ Due to an incorrectly-implemented breaking change, use v8.0.0 Breaking changes: New features: + - Add bindings to `mkdir(path, { recursive: true })` via `mkdirRecursive` (#53, #55 by @JordanMartinez) Bugfixes: @@ -124,18 +176,22 @@ Other improvements: ## [v6.1.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v6.1.0) - 2021-05-06 New features: + - Exported `mkPerm` (#42 by @JordanMartinez) Other improvements: + - Fixed warnings revealed by v0.14.1 PS release (#42 by @JordanMartinez) ## [v6.0.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v6.0.0) - 2021-02-26 Breaking changes: + - Added support for PureScript 0.14 and dropped support for all previous versions (#46) - Dropped deprecated `globals` dependency (#47) Other improvements: + - Migrated CI to GitHub Actions, updated installation instructions to use Spago, and migrated from `jshint` to `eslint` (#45) - Added a changelog and pull request template (#49) @@ -230,6 +286,7 @@ Fix type of `Async.exists`. ## [v0.4.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.4.0) - 2015-02-21 **This release requires PureScript v0.6.8 or later** + - Updated dependencies ## [v0.3.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.3.0) - 2015-01-29 diff --git a/bower.json b/bower.json deleted file mode 100644 index 1fc30b8..0000000 --- a/bower.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "purescript-node-fs", - "homepage": "https://github.com/purescript-contrib/purescript-node-fs", - "description": "Node.js file system operations", - "keywords": [ - "purescript" - ], - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/purescript-node/purescript-node-fs.git" - }, - "ignore": [ - "**/.*", - "bower_components", - "node_modules", - "tmp", - "output", - "bower.json", - "package.json" - ], - "dependencies": { - "purescript-datetime": "^6.0.0", - "purescript-effect": "^4.0.0", - "purescript-either": "^6.0.0", - "purescript-enums": "^6.0.0", - "purescript-exceptions": "^6.0.0", - "purescript-functions": "^6.0.0", - "purescript-integers": "^6.0.0", - "purescript-js-date": "^8.0.0", - "purescript-maybe": "^6.0.0", - "purescript-node-buffer": "^9.0.0", - "purescript-node-path": "^5.0.0", - "purescript-node-streams": "^9.0.0", - "purescript-nullable": "^6.0.0", - "purescript-partial": "^4.0.0", - "purescript-prelude": "^6.0.0", - "purescript-strings": "^6.0.0", - "purescript-unsafe-coerce": "^6.0.0" - }, - "devDependencies": { - "purescript-console": "^6.0.0" - } -} diff --git a/package.json b/package.json index c08ce8f..8833a7e 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,13 @@ "private": true, "scripts": { "clean": "rimraf output && rimraf .pulp-cache", - "build": "eslint src && pulp build -- --censor-lib --strict", - "test": "pulp test -- --censor-lib" + "build": "eslint src && spago build --censor-stats --strict --pedantic-packages", + "test": "spago test --censor-stats --strict --pedantic-packages" }, "devDependencies": { - "eslint": "^7.15.0", - "pulp": "16.0.0-0", - "purescript-psa": "^0.8.2", - "rimraf": "^3.0.2" + "eslint": "^9.11.1", + "pulp": "16.0.2", + "purescript-psa": "^0.9.0", + "rimraf": "^6.0.1" } } diff --git a/spago.lock b/spago.lock new file mode 100644 index 0000000..1efc16e --- /dev/null +++ b/spago.lock @@ -0,0 +1,1366 @@ +{ + "workspace": { + "packages": { + "node-fs": { + "path": "./", + "core": { + "dependencies": [ + { + "aff": ">=8.0.0 <9.0.0" + }, + { + "arrays": ">=7.3.0 <8.0.0" + }, + { + "datetime": ">=6.1.0 <7.0.0" + }, + { + "effect": ">=4.0.0 <5.0.0" + }, + { + "either": ">=6.1.0 <7.0.0" + }, + { + "enums": ">=6.0.1 <7.0.0" + }, + { + "exceptions": ">=6.1.0 <7.0.0" + }, + { + "foreign": ">=7.0.0 <8.0.0" + }, + { + "functions": ">=6.0.0 <7.0.0" + }, + { + "integers": ">=6.0.0 <7.0.0" + }, + { + "js-date": ">=8.0.0 <9.0.0" + }, + { + "maybe": ">=6.0.0 <7.0.0" + }, + { + "node-buffer": ">=9.0.0 <10.0.0" + }, + { + "node-path": ">=5.0.0 <6.0.0" + }, + { + "node-streams": ">=9.0.0 <10.0.0" + }, + { + "nullable": ">=6.0.0 <7.0.0" + }, + { + "partial": ">=4.0.0 <5.0.0" + }, + { + "prelude": ">=6.0.1 <7.0.0" + }, + { + "refs": ">=6.0.0 <7.0.0" + }, + { + "strings": ">=6.0.1 <7.0.0" + }, + { + "tuples": ">=7.0.0 <8.0.0" + }, + { + "unsafe-coerce": ">=6.0.0 <7.0.0" + } + ], + "build_plan": [ + "aff", + "arraybuffer-types", + "arrays", + "bifunctors", + "const", + "contravariant", + "control", + "datetime", + "distributive", + "effect", + "either", + "enums", + "exceptions", + "exists", + "foldable-traversable", + "foreign", + "functions", + "functors", + "gen", + "identity", + "integers", + "invariant", + "js-date", + "lazy", + "lists", + "maybe", + "newtype", + "node-buffer", + "node-event-emitter", + "node-path", + "node-streams", + "nonempty", + "now", + "nullable", + "numbers", + "ordered-collections", + "orders", + "parallel", + "partial", + "prelude", + "profunctor", + "refs", + "safe-coerce", + "st", + "strings", + "tailrec", + "transformers", + "tuples", + "type-equality", + "unfoldable", + "unsafe-coerce" + ] + }, + "test": { + "dependencies": [ + "aff", + "arrays", + "assert", + "console", + "foldable-traversable", + "node-event-emitter" + ], + "build_plan": [ + "aff", + "arrays", + "assert", + "bifunctors", + "console", + "const", + "contravariant", + "control", + "datetime", + "distributive", + "effect", + "either", + "enums", + "exceptions", + "exists", + "foldable-traversable", + "functions", + "functors", + "gen", + "identity", + "integers", + "invariant", + "lazy", + "lists", + "maybe", + "newtype", + "node-event-emitter", + "nonempty", + "nullable", + "numbers", + "ordered-collections", + "orders", + "parallel", + "partial", + "prelude", + "profunctor", + "refs", + "safe-coerce", + "st", + "tailrec", + "transformers", + "tuples", + "type-equality", + "unfoldable", + "unsafe-coerce" + ] + } + } + }, + "package_set": { + "address": { + "registry": "60.5.0" + }, + "compiler": ">=0.15.15 <0.16.0", + "content": { + "abc-parser": "2.0.1", + "ace": "9.1.0", + "address-rfc2821": "0.1.1", + "aff": "8.0.0", + "aff-bus": "6.0.0", + "aff-coroutines": "9.0.0", + "aff-promise": "4.0.0", + "aff-retry": "2.0.0", + "affjax": "13.0.0", + "affjax-node": "1.0.0", + "affjax-web": "1.0.0", + "ansi": "7.0.0", + "apexcharts": "0.5.0", + "applicative-phases": "1.0.0", + "argonaut": "9.0.0", + "argonaut-aeson-generic": "0.4.1", + "argonaut-codecs": "9.1.0", + "argonaut-core": "7.0.0", + "argonaut-generic": "8.0.0", + "argonaut-traversals": "10.0.0", + "argparse-basic": "2.0.0", + "array-builder": "0.1.2", + "array-search": "0.6.0", + "arraybuffer": "13.2.0", + "arraybuffer-builder": "3.1.0", + "arraybuffer-types": "3.0.2", + "arrays": "7.3.0", + "arrays-extra": "0.6.1", + "arrays-zipper": "2.0.1", + "ask": "1.0.0", + "assert": "6.0.0", + "assert-multiple": "0.4.0", + "avar": "5.0.0", + "b64": "0.0.8", + "barbies": "1.0.1", + "barlow-lens": "0.9.0", + "bifunctors": "6.0.0", + "bigints": "7.0.1", + "bolson": "0.3.9", + "bookhound": "0.1.7", + "bower-json": "3.0.0", + "call-by-name": "4.0.1", + "canvas": "6.0.0", + "canvas-action": "9.0.0", + "cartesian": "1.0.6", + "catenable-lists": "7.0.0", + "cbor-stream": "1.3.0", + "chameleon": "1.0.0", + "chameleon-halogen": "1.0.3", + "chameleon-react-basic": "1.1.0", + "chameleon-styled": "2.5.0", + "chameleon-transformers": "1.0.0", + "channel": "1.0.0", + "checked-exceptions": "3.1.1", + "choku": "1.0.1", + "classless": "0.1.1", + "classless-arbitrary": "0.1.1", + "classless-decode-json": "0.1.1", + "classless-encode-json": "0.1.3", + "classnames": "2.0.0", + "codec": "6.1.0", + "codec-argonaut": "10.0.0", + "codec-json": "2.0.0", + "colors": "7.0.1", + "concur-core": "0.5.0", + "concur-react": "0.5.0", + "concurrent-queues": "3.0.0", + "console": "6.1.0", + "const": "6.0.0", + "contravariant": "6.0.0", + "control": "6.0.0", + "convertable-options": "1.0.0", + "coroutines": "7.0.0", + "css": "6.0.0", + "css-frameworks": "1.0.1", + "csv-stream": "2.3.0", + "data-mvc": "0.0.2", + "datetime": "6.1.0", + "datetime-parsing": "0.2.0", + "debounce": "0.1.0", + "debug": "6.0.2", + "decimals": "7.1.0", + "default-values": "1.0.1", + "deku": "0.9.23", + "deno": "0.0.5", + "dissect": "1.0.0", + "distributive": "6.0.0", + "dom-filereader": "7.0.0", + "dom-indexed": "12.0.0", + "dom-simple": "0.4.0", + "dotenv": "4.0.3", + "droplet": "0.6.0", + "dts": "1.0.0", + "dual-numbers": "1.0.3", + "dynamic-buffer": "3.0.1", + "echarts-simple": "0.0.1", + "effect": "4.0.0", + "either": "6.1.0", + "elmish": "0.13.0", + "elmish-enzyme": "0.1.1", + "elmish-hooks": "0.10.3", + "elmish-html": "0.9.0", + "elmish-testing-library": "0.3.2", + "email-validate": "7.0.0", + "encoding": "0.0.9", + "enums": "6.0.1", + "env-names": "0.4.0", + "error": "2.0.0", + "eta-conversion": "0.3.2", + "exceptions": "6.1.0", + "exists": "6.0.0", + "exitcodes": "4.0.0", + "expect-inferred": "3.0.0", + "ezfetch": "1.0.0", + "fahrtwind": "2.0.0", + "fallback": "0.1.0", + "fast-vect": "1.2.0", + "fetch": "4.1.0", + "fetch-argonaut": "1.0.1", + "fetch-core": "5.1.0", + "fetch-yoga-json": "1.1.0", + "ffi-simple": "0.5.1", + "fft-js": "0.1.0", + "filterable": "5.0.0", + "fix-functor": "0.1.0", + "fixed-points": "7.0.0", + "fixed-precision": "5.0.0", + "flame": "1.3.0", + "float32": "2.0.0", + "fmt": "0.2.1", + "foldable-traversable": "6.0.0", + "foldable-traversable-extra": "0.0.6", + "foreign": "7.0.0", + "foreign-object": "4.1.0", + "foreign-readwrite": "3.4.0", + "forgetmenot": "0.1.0", + "fork": "6.0.0", + "form-urlencoded": "7.0.0", + "formatters": "7.0.0", + "framer-motion": "1.0.1", + "free": "7.1.0", + "freeap": "7.0.0", + "freer-free": "0.0.1", + "freet": "7.0.0", + "functions": "6.0.0", + "functor1": "3.0.0", + "functors": "5.0.0", + "fuzzy": "0.4.0", + "gen": "4.0.0", + "generate-values": "1.0.1", + "generic-router": "0.0.1", + "geojson": "0.0.5", + "geometria": "2.2.0", + "gojs": "0.1.1", + "grain": "3.0.0", + "grain-router": "3.0.0", + "grain-virtualized": "3.0.0", + "graphs": "8.1.0", + "group": "4.1.1", + "halogen": "7.0.0", + "halogen-bootstrap5": "5.3.2", + "halogen-canvas": "1.0.0", + "halogen-css": "10.0.0", + "halogen-echarts-simple": "0.0.4", + "halogen-formless": "4.0.3", + "halogen-helix": "1.0.1", + "halogen-hooks": "0.6.3", + "halogen-hooks-extra": "0.9.0", + "halogen-infinite-scroll": "1.1.0", + "halogen-store": "0.5.4", + "halogen-storybook": "2.0.0", + "halogen-subscriptions": "2.0.0", + "halogen-svg-elems": "8.0.0", + "halogen-typewriter": "1.0.4", + "halogen-vdom": "8.0.0", + "halogen-vdom-string-renderer": "0.5.0", + "halogen-xterm": "2.0.0", + "heckin": "2.0.1", + "heterogeneous": "0.6.0", + "homogeneous": "0.4.0", + "http-methods": "6.0.0", + "httpurple": "4.0.0", + "huffman": "0.4.0", + "humdrum": "0.0.1", + "hyrule": "2.3.8", + "identity": "6.0.0", + "identy": "4.0.1", + "indexed-db": "1.0.0", + "indexed-monad": "3.0.0", + "int64": "3.0.0", + "integers": "6.0.0", + "interpolate": "5.0.2", + "intersection-observer": "1.0.1", + "invariant": "6.0.0", + "jarilo": "1.0.1", + "jelly": "0.10.0", + "jelly-router": "0.3.0", + "jelly-signal": "0.4.0", + "jest": "1.0.0", + "js-abort-controller": "1.0.0", + "js-bigints": "2.2.1", + "js-date": "8.0.0", + "js-fetch": "0.2.1", + "js-fileio": "3.0.0", + "js-intl": "1.0.4", + "js-iterators": "0.1.1", + "js-maps": "0.1.2", + "js-promise": "1.0.0", + "js-promise-aff": "1.0.0", + "js-timers": "6.1.0", + "js-uri": "3.1.0", + "jsdom": "1.0.0", + "json": "1.1.0", + "json-codecs": "5.0.0", + "justifill": "0.5.0", + "jwt": "0.0.9", + "labeled-data": "0.2.0", + "language-cst-parser": "0.14.0", + "lazy": "6.0.0", + "lazy-joe": "1.0.0", + "lcg": "4.0.0", + "leibniz": "5.0.0", + "leveldb": "1.0.1", + "liminal": "1.0.1", + "linalg": "6.0.0", + "lists": "7.0.0", + "literals": "1.0.2", + "logging": "3.0.0", + "logging-journald": "0.4.0", + "lumi-components": "18.0.0", + "machines": "7.0.0", + "maps-eager": "0.5.0", + "marionette": "1.0.0", + "marionette-react-basic-hooks": "0.1.1", + "marked": "0.1.0", + "matrices": "5.0.1", + "matryoshka": "1.0.0", + "maybe": "6.0.0", + "media-types": "6.0.0", + "meowclient": "1.0.0", + "midi": "4.0.0", + "milkis": "9.0.0", + "minibench": "4.0.1", + "mmorph": "7.0.0", + "monad-control": "5.0.0", + "monad-logger": "1.3.1", + "monad-loops": "0.5.0", + "monad-unlift": "1.0.1", + "monoid-extras": "0.0.1", + "monoidal": "0.16.0", + "morello": "0.4.0", + "mote": "3.0.0", + "motsunabe": "2.0.0", + "mvc": "0.0.1", + "mysql": "6.0.1", + "n3": "0.1.0", + "nano-id": "1.1.0", + "nanoid": "0.1.0", + "naturals": "3.0.0", + "nested-functor": "0.2.1", + "newtype": "5.0.0", + "nextjs": "0.1.1", + "nextui": "0.2.0", + "node-buffer": "9.0.0", + "node-child-process": "11.1.0", + "node-event-emitter": "3.0.0", + "node-execa": "5.0.0", + "node-fs": "9.2.0", + "node-glob-basic": "1.3.0", + "node-http": "9.1.0", + "node-http2": "1.1.1", + "node-human-signals": "1.0.0", + "node-net": "5.1.0", + "node-os": "5.1.0", + "node-path": "5.0.0", + "node-process": "11.2.0", + "node-readline": "8.1.1", + "node-sqlite3": "8.0.0", + "node-stream-pipes": "2.1.6", + "node-streams": "9.0.0", + "node-tls": "0.3.1", + "node-url": "7.0.1", + "node-zlib": "0.4.0", + "nonempty": "7.0.0", + "now": "6.0.0", + "npm-package-json": "2.0.0", + "nullable": "6.0.0", + "numberfield": "0.2.2", + "numbers": "9.0.1", + "oak": "3.1.1", + "oak-debug": "1.2.2", + "object-maps": "0.3.0", + "ocarina": "1.5.4", + "oooooooooorrrrrrrmm-lib": "0.0.1", + "open-colors-scales-and-schemes": "1.0.0", + "open-folds": "6.4.0", + "open-foreign-generic": "11.0.3", + "open-memoize": "6.2.0", + "open-mkdirp-aff": "1.2.0", + "open-pairing": "6.2.0", + "open-smolder": "12.0.2", + "options": "7.0.0", + "optparse": "5.0.1", + "ordered-collections": "3.2.0", + "ordered-set": "0.4.0", + "orders": "6.0.0", + "owoify": "1.2.0", + "pairs": "9.0.1", + "parallel": "7.0.0", + "parsing": "10.2.0", + "parsing-dataview": "3.2.4", + "partial": "4.0.0", + "pathy": "9.0.0", + "pha": "0.13.0", + "phaser": "0.7.0", + "phylio": "1.1.2", + "pipes": "8.0.0", + "pirates-charm": "0.0.1", + "pmock": "0.9.0", + "point-free": "1.0.0", + "pointed-list": "0.5.1", + "polymorphic-vectors": "4.0.0", + "posix-types": "6.0.0", + "postgresql": "2.0.19", + "precise": "6.0.0", + "precise-datetime": "7.0.0", + "prelude": "6.0.1", + "prettier-printer": "3.0.0", + "priority-queue": "0.1.2", + "profunctor": "6.0.1", + "profunctor-lenses": "8.0.0", + "protobuf": "4.3.0", + "psa-utils": "8.0.0", + "psci-support": "6.0.0", + "punycode": "1.0.0", + "qualified-do": "2.2.0", + "quantities": "12.2.0", + "quickcheck": "8.0.1", + "quickcheck-combinators": "0.1.3", + "quickcheck-laws": "7.0.0", + "quickcheck-utf8": "0.0.0", + "random": "6.0.0", + "rationals": "6.0.0", + "rdf": "0.1.0", + "react": "11.0.0", + "react-aria": "0.2.0", + "react-basic": "17.0.0", + "react-basic-classic": "3.0.0", + "react-basic-dnd": "10.1.0", + "react-basic-dom": "6.1.0", + "react-basic-dom-beta": "0.1.1", + "react-basic-emotion": "7.1.0", + "react-basic-hooks": "8.2.0", + "react-basic-storybook": "2.0.0", + "react-dom": "8.0.0", + "react-halo": "3.0.0", + "react-icons": "1.1.5", + "react-markdown": "0.1.0", + "react-testing-library": "4.0.1", + "react-virtuoso": "1.0.0", + "reactix": "0.6.1", + "read": "1.0.1", + "recharts": "1.1.0", + "record": "4.0.0", + "record-extra": "5.0.1", + "record-ptional-fields": "0.1.2", + "record-studio": "1.0.4", + "refs": "6.0.0", + "remotedata": "5.0.1", + "repr": "0.5.0", + "resize-observer": "1.0.0", + "resource": "2.0.1", + "resourcet": "1.0.0", + "result": "1.0.3", + "return": "0.2.0", + "ring-modules": "5.0.1", + "rito": "0.3.4", + "roman": "0.4.0", + "rough-notation": "1.0.2", + "routing": "11.0.0", + "routing-duplex": "0.7.0", + "run": "5.0.0", + "safe-coerce": "2.0.0", + "safely": "4.0.1", + "school-of-music": "1.3.0", + "selection-foldable": "0.2.0", + "selective-functors": "1.0.1", + "semirings": "7.0.0", + "signal": "13.0.0", + "simple-emitter": "3.0.1", + "simple-i18n": "2.0.1", + "simple-json": "9.0.0", + "simple-json-generics": "0.2.1", + "simple-ulid": "3.0.0", + "sized-matrices": "1.0.0", + "sized-vectors": "5.0.2", + "slug": "3.1.0", + "small-ffi": "4.0.1", + "soundfonts": "4.1.0", + "sparse-matrices": "2.0.1", + "sparse-polynomials": "3.0.1", + "spec": "8.1.0", + "spec-discovery": "8.4.0", + "spec-mocha": "5.1.1", + "spec-node": "0.0.2", + "spec-quickcheck": "5.0.2", + "spec-reporter-xunit": "0.7.1", + "splitmix": "2.1.0", + "ssrs": "1.0.0", + "st": "6.2.0", + "statistics": "0.3.2", + "strictlypositiveint": "1.0.1", + "string-parsers": "8.0.0", + "strings": "6.0.1", + "strings-extra": "4.0.0", + "stringutils": "0.0.12", + "substitute": "0.2.3", + "supply": "0.2.0", + "svg-parser": "3.0.0", + "systemd-journald": "0.3.0", + "tagged": "4.0.2", + "tailrec": "6.1.0", + "tecton": "0.2.1", + "tecton-halogen": "0.2.0", + "test-unit": "17.0.0", + "thermite": "6.3.1", + "thermite-dom": "0.3.1", + "these": "6.0.0", + "threading": "0.0.3", + "tldr": "0.0.0", + "toestand": "0.9.0", + "transformation-matrix": "1.0.1", + "transformers": "6.1.0", + "tree-rose": "4.0.2", + "ts-bridge": "4.0.0", + "tuples": "7.0.0", + "two-or-more": "1.0.0", + "type-equality": "4.0.1", + "typedenv": "2.0.1", + "typelevel": "6.0.0", + "typelevel-lists": "2.1.0", + "typelevel-peano": "1.0.1", + "typelevel-prelude": "7.0.0", + "typelevel-regex": "0.0.3", + "typelevel-rows": "0.1.0", + "typisch": "0.4.0", + "uint": "7.0.0", + "ulid": "3.0.1", + "uncurried-transformers": "1.1.0", + "undefined": "2.0.0", + "undefined-is-not-a-problem": "1.1.0", + "unfoldable": "6.0.0", + "unicode": "6.0.0", + "unique": "0.6.1", + "unlift": "1.0.1", + "unordered-collections": "3.1.0", + "unsafe-coerce": "6.0.0", + "unsafe-reference": "5.0.0", + "untagged-to-tagged": "0.1.4", + "untagged-union": "1.0.0", + "uri": "9.0.0", + "url-immutable": "1.0.0", + "uuid": "9.0.0", + "uuidv4": "1.0.0", + "validation": "6.0.0", + "variant": "8.0.0", + "variant-encodings": "2.0.0", + "vectorfield": "1.0.1", + "vectors": "2.1.0", + "versions": "7.0.0", + "visx": "0.0.2", + "web-clipboard": "6.0.0", + "web-cssom": "2.0.0", + "web-cssom-view": "0.1.0", + "web-dom": "6.0.0", + "web-dom-parser": "8.0.0", + "web-dom-xpath": "3.0.0", + "web-encoding": "3.0.0", + "web-events": "4.0.0", + "web-fetch": "4.0.1", + "web-file": "4.0.0", + "web-geometry": "0.1.0", + "web-html": "4.1.0", + "web-pointerevents": "2.0.0", + "web-proletarian": "1.0.0", + "web-promise": "3.2.0", + "web-resize-observer": "2.1.0", + "web-router": "1.0.0", + "web-socket": "4.0.0", + "web-storage": "5.0.0", + "web-streams": "4.0.0", + "web-touchevents": "4.0.0", + "web-uievents": "5.0.0", + "web-url": "2.0.0", + "web-workers": "1.1.0", + "web-xhr": "5.0.1", + "webextension-polyfill": "0.1.0", + "webgpu": "0.0.1", + "which": "2.0.0", + "xterm": "1.0.0", + "yoga-fetch": "1.0.1", + "yoga-json": "5.1.0", + "yoga-om": "0.1.0", + "yoga-postgres": "6.0.0", + "yoga-tree": "1.0.0", + "z3": "0.0.2", + "zipperarray": "2.0.0" + } + }, + "extra_packages": {} + }, + "packages": { + "aff": { + "type": "registry", + "version": "8.0.0", + "integrity": "sha256-5MmdI4+0RHBtSBy+YlU3/Cq4R5W2ih3OaRedJIrVHdk=", + "dependencies": [ + "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" + ] + }, + "assert": { + "type": "registry", + "version": "6.0.0", + "integrity": "sha256-hCSYcCw9kj3qujoDcriWhCdmrpPZoguSPDZhEMnTl3A=", + "dependencies": [ + "console", + "effect", + "prelude" + ] + }, + "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" + ] + }, + "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.1.0", + "integrity": "sha256-K0T89IHtF3vBY7eSAO7eDOqSb2J9kZGAcDN5+IKsF8E=", + "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" + ] + }, + "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-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" + ] + }, + "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": "7.0.0", + "integrity": "sha256-gUC9i4Txnx9K9RcMLsjujbwZz6BB1bnE2MLvw4GIw5o=", + "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": [] + }, + "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" + ] + }, + "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.1.0", + "integrity": "sha256-3Bm+Z6tsC/paG888XkywDngJ2JMos+JfOhRlkVfb7gI=", + "dependencies": [ + "control", + "distributive", + "effect", + "either", + "exceptions", + "foldable-traversable", + "identity", + "lazy", + "maybe", + "newtype", + "prelude", + "st", + "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": [] + }, + "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": [] + } + } +} diff --git a/spago.yaml b/spago.yaml new file mode 100644 index 0000000..526fb6d --- /dev/null +++ b/spago.yaml @@ -0,0 +1,44 @@ +package: + name: node-fs + publish: + license: MIT + version: 9.2.0 + location: + githubOwner: purescript-node + githubRepo: purescript-node-fs + dependencies: + - aff: ">=8.0.0 <9.0.0" + - arrays: ">=7.3.0 <8.0.0" + - datetime: ">=6.1.0 <7.0.0" + - effect: ">=4.0.0 <5.0.0" + - either: ">=6.1.0 <7.0.0" + - enums: ">=6.0.1 <7.0.0" + - exceptions: ">=6.1.0 <7.0.0" + - foreign: ">=7.0.0 <8.0.0" + - functions: ">=6.0.0 <7.0.0" + - integers: ">=6.0.0 <7.0.0" + - js-date: ">=8.0.0 <9.0.0" + - maybe: ">=6.0.0 <7.0.0" + - node-buffer: ">=9.0.0 <10.0.0" + - node-path: ">=5.0.0 <6.0.0" + - node-streams: ">=9.0.0 <10.0.0" + - nullable: ">=6.0.0 <7.0.0" + - partial: ">=4.0.0 <5.0.0" + - prelude: ">=6.0.1 <7.0.0" + - refs: ">=6.0.0 <7.0.0" + - strings: ">=6.0.1 <7.0.0" + - tuples: ">=7.0.0 <8.0.0" + - unsafe-coerce: ">=6.0.0 <7.0.0" + test: + main: Test.Main + dependencies: + - aff + - arrays + - assert + - console + - foldable-traversable + - node-event-emitter +workspace: + packageSet: + registry: 60.5.0 + extraPackages: {} diff --git a/src/Node/FS.purs b/src/Node/FS.purs index 2bb3c56..135321d 100644 --- a/src/Node/FS.purs +++ b/src/Node/FS.purs @@ -1,41 +1,4 @@ -module Node.FS - ( FileDescriptor - , FileMode - , SymlinkType(..) - , symlinkTypeToNode - , BufferLength - , BufferOffset - , ByteCount - , FilePosition - , module Exports - ) where - -import Prelude +module Node.FS (module Exports) where import Node.FS.Constants (FileFlags(..), fileFlagsToNode) as Exports - -foreign import data FileDescriptor :: Type - -type FileMode = Int -type FilePosition = Int -type BufferLength = Int -type BufferOffset = Int -type ByteCount = Int - --- | Symlink varieties. -data SymlinkType = FileLink | DirLink | JunctionLink - --- | Convert a `SymlinkType` to a `String` in the format expected by the --- | Node.js filesystem API. -symlinkTypeToNode :: SymlinkType -> String -symlinkTypeToNode ty = case ty of - FileLink -> "file" - DirLink -> "dir" - JunctionLink -> "junction" - -instance showSymlinkType :: Show SymlinkType where - show FileLink = "FileLink" - show DirLink = "DirLink" - show JunctionLink = "JunctionLink" - -derive instance eqSymlinkType :: Eq SymlinkType +import Node.FS.Types (BufferLength, BufferOffset, ByteCount, EncodingString, FileDescriptor, FileMode, FilePosition, SymlinkType(..), symlinkTypeToNode) as Exports diff --git a/src/Node/FS/Aff.purs b/src/Node/FS/Aff.purs index 8de7b8a..854d0ce 100644 --- a/src/Node/FS/Aff.purs +++ b/src/Node/FS/Aff.purs @@ -9,11 +9,12 @@ module Node.FS.Aff , truncate , chown , chmod - , stat , lstat + , stat , link , symlink , readlink + , readlinkBuffer , realpath , realpath' , unlink @@ -24,19 +25,61 @@ module Node.FS.Aff , mkdir , mkdir' , readdir + , readdir' + , readdirBuffer + , readdirBuffer' + , readdirDirent + , readdirDirent' + , readdirDirentBuffer + , readdirDirentBuffer' , utimes , readFile + , readFile' , readTextFile + , readTextFile' , writeFile + , writeFile' , writeTextFile + , writeTextFile' , appendFile + , appendFile' , appendTextFile , fdOpen , fdRead + , fdRead' , fdNext , fdWrite + , fdWrite' + , fdWriteString , fdAppend , fdClose + , cpFile + , cpFile' + , cpDir + , cpDir' + , fchmod + , fchown + , fdatasync + , fstat + , fsync + , ftruncate + , futimes + , glob + , glob' + , globDirent + , globDirent' + , lchmod + , lchown + , lutimes + -- , openAsBlob + , opendir + , opendir' + , readv + , statfs + -- , unwatchFile + -- , watch + -- , watchFile + , writev ) where import Prelude @@ -44,78 +87,41 @@ import Prelude import Data.DateTime (DateTime) import Data.Either (Either(..)) import Data.Maybe (Maybe) -import Effect (Effect) +import Data.Tuple (Tuple) import Effect.Aff (Aff, Error, makeAff, nonCanceler) import Node.Buffer (Buffer) import Node.Encoding (Encoding) -import Node.FS as F +import Node.FS.Types (BufferLength, BufferOffset, ByteCount, FileDescriptor, FileMode, FilePosition, SymlinkType) +import Node.FS.Internal.ToAff (toAff1, toAff2, toAff3, toAff4, toAff5) +import Node.FS.Options (AppendFileBufferOptions, CpDirOptions, CpFileOptions, FdReadOptions, FdWriteOptions, GlobDirentOptions, GlobFilePathOptions, MkdirOptions, OpendirOptions, ReadFileBufferOptions, ReadFileStringOptions, ReaddirBufferOptions, ReaddirDirentBufferOptions, ReaddirDirentOptions, ReaddirFilePathOptions, RealpathOptions, RmOptions, RmdirOptions, WriteFileBufferOptions, WriteFileStringOptions) +import Node.FS.Constants (AccessMode, CopyMode, FileFlags) import Node.FS.Async as A -import Node.FS.Constants (AccessMode, CopyMode) +import Node.FS.Dir (Dir) +import Node.FS.Dirent (Dirent, DirentNameBuffer, DirentNameString) import Node.FS.Perms (Perms) import Node.FS.Stats (Stats) import Node.Path (FilePath) -toAff - :: forall a - . (A.Callback a -> Effect Unit) - -> Aff a -toAff p = makeAff \k -> p k $> nonCanceler - -toAff1 - :: forall a x - . (x -> A.Callback a -> Effect Unit) - -> x - -> Aff a -toAff1 f a = toAff (f a) - -toAff2 - :: forall a x y - . (x -> y -> A.Callback a -> Effect Unit) - -> x - -> y - -> Aff a -toAff2 f a b = toAff (f a b) - -toAff3 - :: forall a x y z - . (x -> y -> z -> A.Callback a -> Effect Unit) - -> x - -> y - -> z - -> Aff a -toAff3 f a b c = toAff (f a b c) - -toAff5 - :: forall a w v x y z - . (w -> v -> x -> y -> z -> A.Callback a -> Effect Unit) - -> w - -> v - -> x - -> y - -> z - -> Aff a -toAff5 f a b c d e = toAff (f a b c d e) - -access :: String -> Aff (Maybe Error) +access :: FilePath -> Aff (Maybe Error) access path = makeAff \k -> do A.access path (k <<< Right) pure nonCanceler -access' :: String -> AccessMode -> Aff (Maybe Error) +access' :: FilePath -> AccessMode -> Aff (Maybe Error) access' path mode = makeAff \k -> do A.access' path mode (k <<< Right) pure nonCanceler -copyFile :: String -> String -> Aff Unit +copyFile :: FilePath -> FilePath -> Aff Unit copyFile = toAff2 A.copyFile -copyFile' :: String -> String -> CopyMode -> Aff Unit +copyFile' :: FilePath -> FilePath -> CopyMode -> Aff Unit copyFile' = toAff3 A.copyFile' -mkdtemp :: String -> Aff String +mkdtemp :: FilePath -> Aff FilePath mkdtemp = toAff1 A.mkdtemp -mkdtemp' :: String -> Encoding -> Aff String +mkdtemp' :: FilePath -> Encoding -> Aff FilePath mkdtemp' = toAff2 A.mkdtemp' -- | @@ -166,7 +172,7 @@ link = toAff2 A.link symlink :: FilePath -> FilePath - -> F.SymlinkType + -> SymlinkType -> Aff Unit symlink = toAff3 A.symlink @@ -176,6 +182,9 @@ symlink = toAff3 A.symlink readlink :: FilePath -> Aff FilePath readlink = toAff1 A.readlink +readlinkBuffer :: FilePath -> Aff Buffer +readlinkBuffer = toAff1 A.readlinkBuffer + -- | -- | Find the canonicalized absolute location for a path. -- | @@ -186,7 +195,7 @@ realpath = toAff1 A.realpath -- | Find the canonicalized absolute location for a path using a cache object -- | for already resolved paths. -- | -realpath' :: forall cache. FilePath -> { | cache } -> Aff FilePath +realpath' :: FilePath -> RealpathOptions -> Aff FilePath realpath' = toAff2 A.realpath' -- | @@ -204,7 +213,7 @@ rmdir = toAff1 A.rmdir -- | -- | Deletes a directory with options. -- | -rmdir' :: FilePath -> { maxRetries :: Int, retryDelay :: Int } -> Aff Unit +rmdir' :: FilePath -> RmdirOptions -> Aff Unit rmdir' = toAff2 A.rmdir' -- | @@ -216,7 +225,7 @@ rm = toAff1 A.rm -- | -- | Deletes a file or directory with options. -- | -rm' :: FilePath -> { force :: Boolean, maxRetries :: Int, recursive :: Boolean, retryDelay :: Int } -> Aff Unit +rm' :: FilePath -> RmOptions -> Aff Unit rm' = toAff2 A.rm' -- | @@ -228,7 +237,7 @@ mkdir = toAff1 A.mkdir -- | -- | Makes a new directory with all of its options. -- | -mkdir' :: FilePath -> { recursive :: Boolean, mode :: Perms } -> Aff Unit +mkdir' :: FilePath -> MkdirOptions -> Aff Unit mkdir' = toAff2 A.mkdir' -- | @@ -237,6 +246,39 @@ mkdir' = toAff2 A.mkdir' readdir :: FilePath -> Aff (Array FilePath) readdir = toAff1 A.readdir +-- | Reads the contents of a directory with options. +readdir' :: FilePath -> ReaddirFilePathOptions -> Aff (Array FilePath) +readdir' = toAff2 A.readdir' + +-- | Reads the contents of a directory and returns an Aff (Array Buffer). +readdirBuffer :: FilePath -> Aff (Array Buffer) +readdirBuffer = toAff1 A.readdirBuffer + +-- | Reads the contents of a directory with options and returns Aff (Array Buffer). +readdirBuffer' :: FilePath -> ReaddirBufferOptions -> Aff (Array Buffer) +readdirBuffer' = toAff2 A.readdirBuffer' + +-- | Reads the contents of a directory and returns an Aff (Array (Dirent DirentNameString)). +readdirDirent :: FilePath -> Aff (Array (Dirent DirentNameString)) +readdirDirent = toAff1 A.readdirDirent + +-- | Reads the contents of a directory with options and returns Aff (Array (Dirent DirentNameString)). +readdirDirent' :: FilePath -> ReaddirDirentOptions -> Aff (Array (Dirent DirentNameString)) +readdirDirent' = toAff2 A.readdirDirent' + +-- | Reads the contents of a directory. +readdirDirentBuffer + :: FilePath + -> Aff (Array (Dirent DirentNameBuffer)) +readdirDirentBuffer = toAff1 A.readdirDirentBuffer + +-- | Reads the contents of a directory. +readdirDirentBuffer' + :: FilePath + -> ReaddirDirentBufferOptions + -> Aff (Array (Dirent DirentNameBuffer)) +readdirDirentBuffer' = toAff2 A.readdirDirentBuffer' + -- | -- | Sets the accessed and modified times for the specified file. -- | @@ -249,30 +291,45 @@ utimes = toAff3 A.utimes readFile :: FilePath -> Aff Buffer readFile = toAff1 A.readFile +readFile' :: FilePath -> ReadFileBufferOptions -> Aff Buffer +readFile' = toAff2 A.readFile' + -- | -- | Reads the entire contents of a text file with the specified encoding. -- | readTextFile :: Encoding -> FilePath -> Aff String readTextFile = toAff2 A.readTextFile +readTextFile' :: FilePath -> ReadFileStringOptions -> Aff String +readTextFile' = toAff2 A.readTextFile' + -- | -- | Writes a buffer to a file. -- | writeFile :: FilePath -> Buffer -> Aff Unit writeFile = toAff2 A.writeFile +writeFile' :: FilePath -> Buffer -> WriteFileBufferOptions -> Aff Unit +writeFile' = toAff3 A.writeFile' + -- | -- | Writes text to a file using the specified encoding. -- | writeTextFile :: Encoding -> FilePath -> String -> Aff Unit writeTextFile = toAff3 A.writeTextFile +writeTextFile' :: FilePath -> String -> WriteFileStringOptions -> Aff Unit +writeTextFile' = toAff3 A.writeTextFile' + -- | -- | Appends the contents of a buffer to a file. -- | appendFile :: FilePath -> Buffer -> Aff Unit appendFile = toAff2 A.appendFile +appendFile' :: FilePath -> Buffer -> AppendFileBufferOptions -> Aff Unit +appendFile' = toAff3 A.appendFile' + -- | -- | Appends text to a file using the specified encoding. -- | @@ -283,44 +340,199 @@ appendTextFile = toAff3 A.appendTextFile -- | for details. fdOpen :: FilePath - -> F.FileFlags - -> Maybe F.FileMode - -> Aff F.FileDescriptor + -> FileFlags + -> Maybe FileMode + -> Aff FileDescriptor fdOpen = toAff3 A.fdOpen -- | Read from a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_read_fd_buffer_offset_length_position_callback) -- | for details. fdRead - :: F.FileDescriptor + :: FileDescriptor -> Buffer - -> F.BufferOffset - -> F.BufferLength - -> Maybe F.FilePosition - -> Aff F.ByteCount + -> BufferOffset + -> BufferLength + -> Maybe FilePosition + -> Aff ByteCount fdRead = toAff5 A.fdRead +-- | Read from a file asynchronously. See the [Node Documentation](https://nodejs.org/docs/latest/api/fs.html#fsreadfd-options-callback) +-- | for details. +fdRead' + :: FileDescriptor + -> FdReadOptions + -> Aff (Tuple ByteCount Buffer) +fdRead' = toAff2 A.fdRead' + -- | Convenience function to fill the whole buffer from the current -- | file position. -fdNext :: F.FileDescriptor -> Buffer -> Aff F.ByteCount +fdNext :: FileDescriptor -> Buffer -> Aff ByteCount fdNext = toAff2 A.fdNext -- | Write to a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_write_fd_buffer_offset_length_position_callback) -- | for details. fdWrite - :: F.FileDescriptor + :: FileDescriptor -> Buffer - -> F.BufferOffset - -> F.BufferLength - -> Maybe F.FilePosition - -> Aff F.ByteCount + -> BufferOffset + -> BufferLength + -> Maybe FilePosition + -> Aff ByteCount fdWrite = toAff5 A.fdWrite +-- | Write from a file asynchronously. See the [Node Documentation](https://nodejs.org/docs/latest/api/fs.html#fswritefd-options-callback) +-- | for details. +fdWrite' + :: FileDescriptor + -> Buffer + -> FdWriteOptions + -> Aff (Tuple ByteCount Buffer) +fdWrite' = toAff3 A.fdWrite' + +-- It is unsafe to use fs.write() multiple times on the same file without waiting for the callback. For this scenario, fs.createWriteStream() is recommended. +fdWriteString + :: FileDescriptor + -> String + -> Maybe FilePosition + -> Encoding + -> Aff (Tuple ByteCount String) +fdWriteString = toAff4 A.fdWriteString + -- | Convenience function to append the whole buffer to the current -- | file position. -fdAppend :: F.FileDescriptor -> Buffer -> Aff F.ByteCount +fdAppend :: FileDescriptor -> Buffer -> Aff ByteCount fdAppend = toAff2 A.fdAppend -- | Close a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_close_fd_callback) -- | for details. -fdClose :: F.FileDescriptor -> Aff Unit +fdClose :: FileDescriptor -> Aff Unit fdClose = toAff1 A.fdClose + +-- | Copy a file asynchronously using a `cp` command. +-- | See the [Node Documentation](https://nodejs.org/api/fs.html#fscpsrc-dest-options-callback) +-- | for details. +cpFile :: FilePath -> FilePath -> Aff Unit +cpFile = toAff2 A.cpFile + +cpFile' :: FilePath -> FilePath -> CpFileOptions -> Aff Unit +cpFile' = toAff3 A.cpFile' + +-- | Copy a directory asynchronously using a `cp` command with option `recursive = true`. +-- | See the [Node Documentation](https://nodejs.org/api/fs.html#fscpsrc-dest-options-callback) +-- | for details. +cpDir :: FilePath -> FilePath -> Aff Unit +cpDir = toAff2 A.cpDir + +cpDir' :: FilePath -> FilePath -> CpDirOptions -> Aff Unit +cpDir' = toAff3 A.cpDir' + +-- | Change permissions on a file descriptor. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fchmod_fd_mode_callback) +-- | for details. +fchmod :: FileDescriptor -> Perms -> Aff Unit +fchmod = toAff2 A.fchmod + +-- | Change ownership of a file descriptor. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fchown_fd_uid_gid_callback) +-- | for details. +fchown :: FileDescriptor -> Int -> Int -> Aff Unit +fchown = toAff3 A.fchown + +-- | Synchronize a file's in-core state with storage. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fdatasync_fd_callback) +-- | for details. +fdatasync :: FileDescriptor -> Aff Unit +fdatasync = toAff1 A.fdatasync + +-- | Get file status information. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fstat_fd_callback) +-- | for details. +fstat :: FileDescriptor -> Aff Stats +fstat = toAff1 A.fstat + +-- | Flushes a file descriptor to disk. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fsync_fd_callback) +-- | for details. +fsync :: FileDescriptor -> Aff Unit +fsync = toAff1 A.fsync + +-- | Truncate a file to a specified length. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_ftruncate_fd_len_callback) +-- | for details. +ftruncate :: FileDescriptor -> Int -> Aff Unit +ftruncate = toAff2 A.ftruncate + +-- | Change file timestamps for a file descriptor. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_futimes_fd_atime_mtime_callback) +-- | for details. +futimes :: FileDescriptor -> DateTime -> DateTime -> Aff Unit +futimes = toAff3 A.futimes + +-- | Perform pattern matching in file paths. See the [Node Documentation](https://nodejs.org/api/glob.html#globglob_pattern_options_callback) +-- | for details. +glob :: Array FilePath -> Aff (Array FilePath) +glob = toAff1 A.glob + +glob' :: Array FilePath -> GlobFilePathOptions -> Aff (Array FilePath) +glob' = toAff2 A.glob' + +globDirent :: Array FilePath -> Aff (Array (Dirent DirentNameString)) +globDirent = toAff1 A.globDirent + +globDirent' :: Array FilePath -> GlobDirentOptions -> Aff (Array (Dirent DirentNameString)) +globDirent' = toAff2 A.globDirent' + +-- | Change permissions on a symbolic link. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_lchmod_path_mode_callback) +-- | for details. +lchmod :: FilePath -> Perms -> Aff Unit +lchmod = toAff2 A.lchmod + +-- | Change ownership of a symbolic link. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_lchown_path_uid_gid_callback) +-- | for details. +lchown :: FilePath -> Int -> Int -> Aff Unit +lchown = toAff3 A.lchown + +-- | Change timestamps for a symbolic link. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_lutimes_path_atime_mtime_callback) +-- | for details. +lutimes :: FilePath -> DateTime -> DateTime -> Aff Unit +lutimes = toAff3 A.lutimes + +-- | Open a file as a blob. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_class_filehandle) +-- | for details. +-- openAsBlob :: FilePath -> Aff Blob +-- openAsBlob = toAff1 A.openAsBlob + +-- | Open a directory. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_opendir_path_options_callback) +-- | for details. +opendir :: FilePath -> Aff Dir +opendir = toAff1 A.opendir + +-- | Open a directory. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_opendir_path_options_callback) +-- | for details. +opendir' :: FilePath -> OpendirOptions -> Aff Dir +opendir' = toAff2 A.opendir' + +-- | Read from a file descriptor into a buffer array. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_readv_fd_buffers_position_callback) +-- | for details. +readv :: FileDescriptor -> Array Buffer -> Maybe FilePosition -> Aff (Tuple ByteCount (Array Buffer)) +readv = toAff3 A.readv + +-- | TODO: bigint, path Buffer Url +-- | Get file system statistics. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_statfs_path_callback) +-- | for details. +statfs :: FilePath -> Aff Stats +statfs = toAff1 A.statfs + +-- | TODO: implement +-- | Stop watching a file for changes. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_unwatchfile_filename_listener) +-- | for details. +-- unwatchFile :: FilePath -> Effect Unit +-- unwatchFile = toAff1 A.unwatchFile + +-- | Watch for changes in a file or directory. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener) +-- | for details. +-- watch :: FilePath -> (String -> Effect Unit) -> Effect Unit +-- watch = toAff2 A.watch + +-- | Watch for changes in a file and trigger a callback. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_watchfile_filename_options_listener) +-- | for details. +-- watchFile :: FilePath -> (Stats -> Stats -> Effect Unit) -> Effect Unit +-- watchFile = toAff2 A.watchFile + +-- | Write from an array of buffers to a file descriptor. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_writev_fd_buffers_position_callback) +-- | for details. +writev :: FileDescriptor -> Array Buffer -> Maybe FilePosition -> Aff (Tuple ByteCount (Array Buffer)) +writev = toAff3 A.writev diff --git a/src/Node/FS/Async.js b/src/Node/FS/Async.js index cf935da..018a675 100644 --- a/src/Node/FS/Async.js +++ b/src/Node/FS/Async.js @@ -24,5 +24,25 @@ export { open as openImpl, read as readImpl, write as writeImpl, - close as closeImpl + close as closeImpl, + cp as cpImpl, + fchmod as fchmodImpl, + fchown as fchownImpl, + fdatasync as fdatasyncImpl, + fstat as fstatImpl, + fsync as fsyncImpl, + ftruncate as ftruncateImpl, + futimes as futimesImpl, + glob as globImpl, + lchmod as lchmodImpl, + lchown as lchownImpl, + lutimes as lutimesImpl, + // openAsBlob as openAsBlobImpl, + opendir as opendirImpl, + readv as readvImpl, + statfs as statfsImpl, + // unwatchFile as unwatchFileImpl, + // watch as watchImpl, + // watchFile as watchFileImpl, + writev as writevImpl } from "node:fs"; diff --git a/src/Node/FS/Async.purs b/src/Node/FS/Async.purs index cfab806..2abb436 100644 --- a/src/Node/FS/Async.purs +++ b/src/Node/FS/Async.purs @@ -1,6 +1,5 @@ module Node.FS.Async - ( Callback - , access + ( access , access' , copyFile , copyFile' @@ -15,6 +14,7 @@ module Node.FS.Async , link , symlink , readlink + , readlinkBuffer , realpath , realpath' , unlink @@ -25,320 +25,491 @@ module Node.FS.Async , mkdir , mkdir' , readdir + , readdir' + , readdirBuffer + , readdirBuffer' + , readdirDirent + , readdirDirent' + , readdirDirentBuffer + , readdirDirentBuffer' , utimes , readFile + , readFile' , readTextFile + , readTextFile' , writeFile + , writeFile' , writeTextFile + , writeTextFile' , appendFile + , appendFile' , appendTextFile , fdOpen , fdRead + , fdRead' , fdNext , fdWrite + , fdWrite' + , fdWriteString , fdAppend , fdClose + , cpFile + , cpFile' + , cpDir + , cpDir' + , fchmod + , fchown + , fdatasync + , fstat + , fsync + , ftruncate + , futimes + , glob + , glob' + , globDirent + , globDirent' + , lchmod + , lchown + , lutimes + -- , openAsBlob + , opendir + , opendir' + , readv + , statfs + -- , unwatchFile + -- , watch + -- , watchFile + , writev ) where import Prelude import Data.DateTime (DateTime) -import Data.DateTime.Instant (fromDateTime, unInstant) -import Data.Either (Either(..)) -import Data.Int (round) import Data.Maybe (Maybe(..)) import Data.Nullable (Nullable, toMaybe, toNullable) -import Data.Time.Duration (Milliseconds(..)) +import Data.Tuple (Tuple) import Effect (Effect) import Effect.Exception (Error) -import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4, EffectFn6, mkEffectFn1, mkEffectFn2, runEffectFn2, runEffectFn3, runEffectFn4, runEffectFn6) +import Effect.Uncurried (EffectFn2, EffectFn3, EffectFn4, EffectFn5, EffectFn6, mkEffectFn1, runEffectFn2, runEffectFn3, runEffectFn4, runEffectFn5, runEffectFn6) import Node.Buffer (Buffer, size) import Node.Encoding (Encoding(..), encodingToNode) import Node.FS (FileDescriptor, ByteCount, FilePosition, BufferLength, BufferOffset, FileMode, SymlinkType, symlinkTypeToNode) -import Node.FS.Constants (FileFlags, fileFlagsToNode, AccessMode, CopyMode, defaultAccessMode, defaultCopyMode) -import Node.FS.Perms (Perms, permsToString, all, mkPerms) +import Node.FS.Constants (AccessMode, CopyMode, FileFlags, defaultAccessMode, defaultCopyMode, fileFlagsToNode) +import Node.FS.Dir (Dir) +import Node.FS.Dirent (Dirent, DirentNameBuffer, DirentNameString) +import Node.FS.Internal.Utils (datetimeToUnixEpochTimeInSeconds) +import Node.FS.Internal.Callbacks (Callback0, Callback1, JSCallback0, JSCallback1, JSCallback2, handleCallback0, handleCallback1, handleCallback1Tuple) +import Node.FS.Options (AppendFileBufferOptions, AppendFileOptionsInternal, AppendFileStringOptions, CpDirOptions, CpFileOptions, CpOptionsInternal, FdReadOptions, FdReadOptionsInternal, FdWriteOptions, FdWriteOptionsInternal, GlobDirentOptions, GlobFilePathOptions, GlobOptionsInternal, MkdirOptions, MkdirOptionsInternal, OpendirOptions, OpendirOptionsInternal, ReadFileBufferOptions, ReadFileOptionsInternal, ReadFileStringOptions, ReaddirBufferOptions, ReaddirDirentBufferOptions, ReaddirDirentOptions, ReaddirFilePathOptions, ReaddirOptionsInternal, RealpathOptions, RealpathOptionsInternal, RmOptions, RmdirOptions, WriteFileBufferOptions, WriteFileOptionsInternal, WriteFileStringOptions, appendFileBufferOptionsDefault, appendFileBufferOptionsToInternal, appendFileStringOptionsDefault, appendFileStringOptionsToInternal, cpDirOptionsDefault, cpDirOptionsToCpOptionsInternal, cpFileOptionsDefault, cpFileOptionsToCpOptionsInternal, fdReadOptionsToInternal, fdWriteOptionsToInternal, globDirentOptionsDefault, globDirentOptionsToInternal, globFilePathOptionsDefault, globFilePathOptionsToInternal, mkdirOptionsDefault, mkdirOptionsToInternal, opendirOptionsDefault, opendirOptionsToInternal, readFileBufferOptionsDefault, readFileBufferOptionsToInternal, readFileStringOptionsDefault, readFileStringOptionsToInternal, readdirBufferOptionsDefault, readdirBufferOptionsToInternal, readdirDirentBufferOptionsDefault, readdirDirentBufferOptionsToInternal, readdirDirentOptionsDefault, readdirDirentOptionsToInternal, readdirFilePathOptionsDefault, readdirFilePathOptionsToInternal, realpathOptionsDefault, realpathOptionsToInternal, rmOptionsDefault, rmdirOptionsDefault, writeFileBufferOptionsDefault, writeFileBufferOptionsToInternal, writeFileStringOptionsDefault, writeFileStringOptionsToInternal) +import Node.FS.Perms (Perms, permsToString) import Node.FS.Stats (Stats) +import Node.FS.Types (EncodingString) import Node.Path (FilePath) - -type JSCallback a = EffectFn2 (Nullable Error) a Unit - -handleCallback :: forall a. Callback a -> JSCallback a -handleCallback cb = mkEffectFn2 \err a -> case toMaybe err of - Nothing -> cb (Right a) - Just err' -> cb (Left err') - --- | Type synonym for callback functions. -type Callback a = Either Error a -> Effect Unit +import Unsafe.Coerce (unsafeCoerce) + +foreign import accessImpl :: EffectFn3 FilePath AccessMode JSCallback0 Unit +foreign import copyFileImpl :: EffectFn4 FilePath FilePath CopyMode JSCallback0 Unit +foreign import mkdtempImpl :: EffectFn3 FilePath FilePath (JSCallback1 FilePath) Unit +foreign import renameImpl :: EffectFn3 FilePath FilePath JSCallback0 Unit +foreign import truncateImpl :: EffectFn3 FilePath Int JSCallback0 Unit +foreign import chownImpl :: EffectFn4 FilePath Int Int JSCallback0 Unit +foreign import chmodImpl :: EffectFn3 FilePath String JSCallback0 Unit +foreign import statImpl :: EffectFn2 FilePath (JSCallback1 Stats) Unit +foreign import lstatImpl :: EffectFn2 FilePath (JSCallback1 Stats) Unit +foreign import linkImpl :: EffectFn3 FilePath FilePath JSCallback0 Unit +foreign import symlinkImpl :: EffectFn4 FilePath FilePath (Nullable String) JSCallback0 Unit +foreign import readlinkImpl :: EffectFn2 FilePath (JSCallback1 FilePath) Unit + +readlinkBufferImpl :: EffectFn3 FilePath String (JSCallback1 Buffer) Unit +readlinkBufferImpl = unsafeCoerce readlinkImpl + +foreign import realpathImpl :: EffectFn3 FilePath RealpathOptionsInternal (JSCallback1 FilePath) Unit +foreign import unlinkImpl :: EffectFn2 FilePath JSCallback0 Unit +foreign import rmdirImpl :: EffectFn3 FilePath RmdirOptions JSCallback0 Unit +foreign import rmImpl :: EffectFn3 FilePath RmOptions JSCallback0 Unit +foreign import mkdirImpl :: EffectFn3 FilePath MkdirOptionsInternal JSCallback0 Unit +-- if { withFileTypes: false, recursive: false } => ['Tidy'] +-- if { withFileTypes: false, recursive: true } => [ 'Tidy', 'Tidy/Codegen', 'Tidy/Codegen.purs', 'Tidy/Codegen/Class.purs', .. ] +foreign import readdirImpl :: forall filepathOrDirentOrBuffer. EffectFn3 FilePath ReaddirOptionsInternal (JSCallback1 (Array filepathOrDirentOrBuffer)) Unit +foreign import utimesImpl :: EffectFn4 FilePath Int Int JSCallback0 Unit +foreign import readFileImpl :: forall stringOrBuffer. EffectFn3 FilePath ReadFileOptionsInternal (JSCallback1 stringOrBuffer) Unit +foreign import writeFileImpl :: forall stringOrBuffer. EffectFn4 FilePath stringOrBuffer WriteFileOptionsInternal JSCallback0 Unit +foreign import appendFileImpl :: forall stringOrBuffer. EffectFn4 FilePath stringOrBuffer AppendFileOptionsInternal JSCallback0 Unit +foreign import openImpl :: EffectFn4 FilePath String (Nullable FileMode) (JSCallback1 FileDescriptor) Unit +foreign import readImpl :: EffectFn6 FileDescriptor Buffer BufferOffset BufferLength (Nullable FilePosition) (JSCallback1 ByteCount) Unit + +-- https://nodejs.org/docs/latest/api/fs.html#fsreadfd-options-callback +readWithOptionsImpl :: EffectFn3 FileDescriptor FdReadOptionsInternal (JSCallback2 ByteCount Buffer) Unit +readWithOptionsImpl = unsafeCoerce readImpl + +foreign import writeImpl :: EffectFn6 FileDescriptor Buffer BufferOffset BufferLength (Nullable FilePosition) (JSCallback1 ByteCount) Unit + +writeBufferWithOptionsImpl :: EffectFn4 FileDescriptor Buffer FdWriteOptionsInternal (JSCallback2 ByteCount Buffer) Unit +writeBufferWithOptionsImpl = unsafeCoerce writeImpl + +writeStringImpl :: EffectFn5 FileDescriptor String (Nullable FilePosition) EncodingString (JSCallback2 ByteCount String) Unit +writeStringImpl = unsafeCoerce writeImpl + +foreign import closeImpl :: EffectFn2 FileDescriptor JSCallback0 Unit +foreign import cpImpl :: EffectFn4 FilePath FilePath CpOptionsInternal JSCallback0 Unit +foreign import fchmodImpl :: EffectFn3 FileDescriptor String JSCallback0 Unit +foreign import fchownImpl :: EffectFn4 FileDescriptor Int Int JSCallback0 Unit +foreign import fdatasyncImpl :: EffectFn2 FileDescriptor JSCallback0 Unit +foreign import fstatImpl :: EffectFn2 FileDescriptor (JSCallback1 Stats) Unit +foreign import fsyncImpl :: EffectFn2 FileDescriptor JSCallback0 Unit +foreign import ftruncateImpl :: EffectFn3 FileDescriptor Int JSCallback0 Unit +foreign import futimesImpl :: EffectFn4 FileDescriptor Int Int JSCallback0 Unit +foreign import globImpl :: forall filepathOrDirent. EffectFn3 (Array FilePath) (GlobOptionsInternal filepathOrDirent) (JSCallback1 (Array filepathOrDirent)) Unit +foreign import lchmodImpl :: EffectFn3 FilePath String JSCallback0 Unit +foreign import lchownImpl :: EffectFn4 FilePath Int Int JSCallback0 Unit +foreign import lutimesImpl :: EffectFn4 FilePath Int Int JSCallback0 Unit +-- foreign import openAsBlobImpl :: EffectFn2 FilePath (Promise Blob) Unit +foreign import opendirImpl :: EffectFn3 FilePath OpendirOptionsInternal (JSCallback1 Dir) Unit +foreign import readvImpl :: EffectFn4 FileDescriptor (Array Buffer) (Nullable FilePosition) (JSCallback2 ByteCount (Array Buffer)) Unit +foreign import statfsImpl :: EffectFn2 FilePath (JSCallback1 Stats) Unit +-- foreign import unwatchFileImpl :: EffectFn1 FilePath Unit +-- foreign import watchImpl :: EffectFn2 FilePath (EffectFn1 String Unit) Unit +-- foreign import watchFileImpl :: EffectFn2 FilePath (EffectFn2 Stats Stats Unit) Unit +foreign import writevImpl :: EffectFn4 FileDescriptor (Array Buffer) (Nullable FilePosition) (JSCallback2 ByteCount (Array Buffer)) Unit access :: FilePath -> (Maybe Error -> Effect Unit) -> Effect Unit access path = access' path defaultAccessMode access' :: FilePath -> AccessMode -> (Maybe Error -> Effect Unit) -> Effect Unit -access' path mode cb = runEffectFn3 accessImpl path mode $ mkEffectFn1 \err -> do - cb $ toMaybe err +access' path mode cb = runEffectFn3 accessImpl path mode $ mkEffectFn1 (cb <<< toMaybe) -foreign import accessImpl :: EffectFn3 FilePath AccessMode (EffectFn1 (Nullable Error) Unit) Unit - -copyFile :: FilePath -> FilePath -> Callback Unit -> Effect Unit +copyFile :: FilePath -> FilePath -> Callback0 -> Effect Unit copyFile src dest = copyFile' src dest defaultCopyMode -copyFile' :: FilePath -> FilePath -> CopyMode -> Callback Unit -> Effect Unit -copyFile' src dest mode cb = runEffectFn4 copyFileImpl src dest mode (handleCallback cb) - -foreign import copyFileImpl :: EffectFn4 FilePath FilePath CopyMode (JSCallback Unit) Unit +copyFile' :: FilePath -> FilePath -> CopyMode -> Callback0 -> Effect Unit +copyFile' src dest mode cb = runEffectFn4 copyFileImpl src dest mode (handleCallback0 cb) -mkdtemp :: String -> Callback String -> Effect Unit +mkdtemp :: FilePath -> Callback1 FilePath -> Effect Unit mkdtemp prefix = mkdtemp' prefix UTF8 -mkdtemp' :: String -> Encoding -> Callback String -> Effect Unit -mkdtemp' prefix encoding cb = runEffectFn3 mkdtempImpl prefix (encodingToNode encoding) (handleCallback cb) - -foreign import mkdtempImpl :: EffectFn3 FilePath String (JSCallback String) Unit - -foreign import renameImpl :: EffectFn3 FilePath FilePath (JSCallback Unit) Unit -foreign import truncateImpl :: EffectFn3 FilePath Int (JSCallback Unit) Unit -foreign import chownImpl :: EffectFn4 FilePath Int Int (JSCallback Unit) Unit -foreign import chmodImpl :: EffectFn3 FilePath String (JSCallback Unit) Unit -foreign import statImpl :: EffectFn2 FilePath (JSCallback Stats) Unit -foreign import lstatImpl :: EffectFn2 FilePath (JSCallback Stats) Unit -foreign import linkImpl :: EffectFn3 FilePath FilePath (JSCallback Unit) Unit -foreign import symlinkImpl :: EffectFn4 FilePath FilePath String (JSCallback Unit) Unit -foreign import readlinkImpl :: EffectFn2 FilePath (JSCallback FilePath) Unit -foreign import realpathImpl :: forall cache. EffectFn3 FilePath { | cache } (JSCallback FilePath) Unit -foreign import unlinkImpl :: EffectFn2 FilePath (JSCallback Unit) Unit -foreign import rmdirImpl :: EffectFn3 FilePath { maxRetries :: Int, retryDelay :: Int } (JSCallback Unit) Unit -foreign import rmImpl :: EffectFn3 FilePath { force :: Boolean, maxRetries :: Int, recursive :: Boolean, retryDelay :: Int } (JSCallback Unit) Unit -foreign import mkdirImpl :: EffectFn3 FilePath { recursive :: Boolean, mode :: String } (JSCallback Unit) Unit -foreign import readdirImpl :: EffectFn2 FilePath (JSCallback (Array FilePath)) Unit -foreign import utimesImpl :: EffectFn4 FilePath Int Int (JSCallback Unit) Unit -foreign import readFileImpl :: forall a opts. EffectFn3 FilePath { | opts } (JSCallback a) Unit -foreign import writeFileImpl :: forall a opts. EffectFn4 FilePath a { | opts } (JSCallback Unit) Unit -foreign import appendFileImpl :: forall a opts. EffectFn4 FilePath a { | opts } (JSCallback Unit) Unit -foreign import openImpl :: EffectFn4 FilePath String (Nullable FileMode) (JSCallback FileDescriptor) Unit -foreign import readImpl :: EffectFn6 FileDescriptor Buffer BufferOffset BufferLength (Nullable FilePosition) (JSCallback ByteCount) Unit -foreign import writeImpl :: EffectFn6 FileDescriptor Buffer BufferOffset BufferLength (Nullable FilePosition) (JSCallback ByteCount) Unit -foreign import closeImpl :: EffectFn2 FileDescriptor (JSCallback Unit) Unit +mkdtemp' :: FilePath -> Encoding -> Callback1 FilePath -> Effect Unit +mkdtemp' prefix encoding cb = runEffectFn3 mkdtempImpl prefix (encodingToNode encoding) (handleCallback1 cb) -- | Renames a file. rename :: FilePath -> FilePath - -> Callback Unit + -> Callback0 -> Effect Unit -rename oldFile newFile cb = runEffectFn3 renameImpl oldFile newFile (handleCallback cb) +rename oldFile newFile cb = runEffectFn3 renameImpl oldFile newFile (handleCallback0 cb) -- | Truncates a file to the specified length. truncate :: FilePath -> Int - -> Callback Unit + -> Callback0 -> Effect Unit -truncate file len cb = runEffectFn3 truncateImpl file len (handleCallback cb) +truncate file len cb = runEffectFn3 truncateImpl file len (handleCallback0 cb) -- | Changes the ownership of a file. chown :: FilePath -> Int -> Int - -> Callback Unit + -> Callback0 -> Effect Unit -chown file uid gid cb = runEffectFn4 chownImpl file uid gid (handleCallback cb) +chown file uid gid cb = runEffectFn4 chownImpl file uid gid (handleCallback0 cb) -- | Changes the permissions of a file. chmod :: FilePath -> Perms - -> Callback Unit + -> Callback0 -> Effect Unit -chmod file perms cb = runEffectFn3 chmodImpl file (permsToString perms) (handleCallback cb) +chmod file perms cb = runEffectFn3 chmodImpl file (permsToString perms) (handleCallback0 cb) -- | Gets file statistics. stat :: FilePath - -> Callback Stats + -> Callback1 Stats -> Effect Unit -stat file cb = runEffectFn2 statImpl file (handleCallback $ cb) +stat file cb = runEffectFn2 statImpl file (handleCallback1 $ cb) -- | Gets file or symlink statistics. `lstat` is identical to `stat`, except -- | that if the `FilePath` is a symbolic link, then the link itself is stat-ed, -- | not the file that it refers to. lstat :: FilePath - -> Callback Stats + -> Callback1 Stats -> Effect Unit -lstat file cb = runEffectFn2 lstatImpl file (handleCallback $ cb) +lstat file cb = runEffectFn2 lstatImpl file (handleCallback1 $ cb) -- | Creates a link to an existing file. link :: FilePath -> FilePath - -> Callback Unit + -> Callback0 -> Effect Unit -link src dst cb = runEffectFn3 linkImpl src dst (handleCallback cb) +link src dst cb = runEffectFn3 linkImpl src dst (handleCallback0 cb) -- | Creates a symlink. symlink :: FilePath -> FilePath -> SymlinkType - -> Callback Unit + -> Callback0 -> Effect Unit -symlink src dest ty cb = runEffectFn4 symlinkImpl src dest (symlinkTypeToNode ty) (handleCallback cb) +symlink src dest ty cb = runEffectFn4 symlinkImpl src dest (symlinkTypeToNode ty) (handleCallback0 cb) -- | Reads the value of a symlink. readlink :: FilePath - -> Callback FilePath + -> Callback1 FilePath -> Effect Unit -readlink path cb = runEffectFn2 readlinkImpl path (handleCallback cb) +readlink path cb = runEffectFn2 readlinkImpl path (handleCallback1 cb) + +-- | Reads the value of a symlink, returns buffer. +readlinkBuffer + :: FilePath + -> Callback1 Buffer + -> Effect Unit +readlinkBuffer path cb = runEffectFn3 readlinkBufferImpl path "buffer" (handleCallback1 cb) -- | Find the canonicalized absolute location for a path. realpath :: FilePath - -> Callback FilePath + -> Callback1 FilePath -> Effect Unit -realpath path cb = runEffectFn3 realpathImpl path {} (handleCallback cb) +realpath path = realpath' path realpathOptionsDefault -- | Find the canonicalized absolute location for a path using a cache object -- | for already resolved paths. realpath' - :: forall cache - . FilePath - -> { | cache } - -> Callback FilePath + :: FilePath + -> RealpathOptions + -> Callback1 FilePath -> Effect Unit -realpath' path cache cb = runEffectFn3 realpathImpl path cache (handleCallback cb) +realpath' path options cb = runEffectFn3 realpathImpl path (realpathOptionsToInternal options) (handleCallback1 cb) -- | Deletes a file. unlink :: FilePath - -> Callback Unit + -> Callback0 -> Effect Unit -unlink file cb = runEffectFn2 unlinkImpl file (handleCallback cb) +unlink file cb = runEffectFn2 unlinkImpl file (handleCallback0 cb) -- | Deletes a directory. rmdir :: FilePath - -> Callback Unit + -> Callback0 -> Effect Unit -rmdir path cb = rmdir' path { maxRetries: 0, retryDelay: 100 } cb +rmdir path cb = rmdir' path rmdirOptionsDefault cb -- | Deletes a directory with options. rmdir' :: FilePath - -> { maxRetries :: Int, retryDelay :: Int } - -> Callback Unit + -> RmdirOptions + -> Callback0 -> Effect Unit -rmdir' path opts cb = runEffectFn3 rmdirImpl path opts (handleCallback cb) +rmdir' path opts cb = runEffectFn3 rmdirImpl path opts (handleCallback0 cb) -- | Deletes a file or directory. rm :: FilePath - -> Callback Unit + -> Callback0 -> Effect Unit -rm path = rm' path { force: false, maxRetries: 100, recursive: false, retryDelay: 1000 } +rm path = rm' path rmOptionsDefault -- | Deletes a file or directory with options. rm' :: FilePath - -> { force :: Boolean, maxRetries :: Int, recursive :: Boolean, retryDelay :: Int } - -> Callback Unit + -> RmOptions + -> Callback0 -> Effect Unit -rm' path opts cb = runEffectFn3 rmImpl path opts (handleCallback cb) +rm' path opts cb = runEffectFn3 rmImpl path opts (handleCallback0 cb) -- | Makes a new directory. mkdir :: FilePath - -> Callback Unit + -> Callback0 -> Effect Unit -mkdir path = mkdir' path { recursive: false, mode: mkPerms all all all } +mkdir path = mkdir' path mkdirOptionsDefault -- | Makes a new directory with the specified permissions. mkdir' :: FilePath - -> { recursive :: Boolean, mode :: Perms } - -> Callback Unit + -> MkdirOptions + -> Callback0 -> Effect Unit -mkdir' file { recursive, mode: perms } cb = runEffectFn3 mkdirImpl file { recursive, mode: permsToString perms } (handleCallback cb) +mkdir' file opts cb = runEffectFn3 mkdirImpl file (mkdirOptionsToInternal opts) (handleCallback0 cb) -- | Reads the contents of a directory. readdir :: FilePath - -> Callback (Array FilePath) + -> Callback1 (Array FilePath) -> Effect Unit -readdir file cb = runEffectFn2 readdirImpl file (handleCallback cb) +readdir file = readdir' file readdirFilePathOptionsDefault + +-- | Reads the contents of a directory. +readdir' + :: FilePath + -> ReaddirFilePathOptions + -> Callback1 (Array FilePath) + -> Effect Unit +readdir' file options cb = runEffectFn3 readdirImpl file (readdirFilePathOptionsToInternal options) (handleCallback1 cb) + +-- | Reads the contents of a directory. +readdirBuffer + :: FilePath + -> Callback1 (Array Buffer) + -> Effect Unit +readdirBuffer file = readdirBuffer' file readdirBufferOptionsDefault + +-- | Reads the contents of a directory. +readdirBuffer' + :: FilePath + -> ReaddirBufferOptions + -> Callback1 (Array Buffer) + -> Effect Unit +readdirBuffer' file options cb = runEffectFn3 readdirImpl file (readdirBufferOptionsToInternal options) (handleCallback1 cb) + +-- | Reads the contents of a directory. +readdirDirent + :: FilePath + -> Callback1 (Array (Dirent DirentNameString)) + -> Effect Unit +readdirDirent file = readdirDirent' file readdirDirentOptionsDefault + +-- | Reads the contents of a directory. +readdirDirent' + :: FilePath + -> ReaddirDirentOptions + -> Callback1 (Array (Dirent DirentNameString)) + -> Effect Unit +readdirDirent' file options cb = runEffectFn3 readdirImpl file (readdirDirentOptionsToInternal options) (handleCallback1 cb) + +-- | Reads the contents of a directory. +readdirDirentBuffer + :: FilePath + -> Callback1 (Array (Dirent DirentNameBuffer)) + -> Effect Unit +readdirDirentBuffer file = readdirDirentBuffer' file readdirDirentBufferOptionsDefault + +-- | Reads the contents of a directory. +readdirDirentBuffer' + :: FilePath + -> ReaddirDirentBufferOptions + -> Callback1 (Array (Dirent DirentNameBuffer)) + -> Effect Unit +readdirDirentBuffer' file options cb = runEffectFn3 readdirImpl file (readdirDirentBufferOptionsToInternal options) (handleCallback1 cb) -- | Sets the accessed and modified times for the specified file. utimes :: FilePath -> DateTime -> DateTime - -> Callback Unit + -> Callback0 -> Effect Unit -utimes file atime mtime cb = runEffectFn4 utimesImpl file (fromDate atime) (fromDate mtime) (handleCallback cb) - where - fromDate date = ms (toEpochMilliseconds date) / 1000 - ms (Milliseconds n) = round n - toEpochMilliseconds = unInstant <<< fromDateTime - --- | Reads the entire contents of a file returning the result as a raw buffer. -readFile - :: FilePath - -> Callback Buffer - -> Effect Unit -readFile file cb = runEffectFn3 readFileImpl file {} (handleCallback cb) +utimes file atime mtime cb = runEffectFn4 utimesImpl file (datetimeToUnixEpochTimeInSeconds atime) (datetimeToUnixEpochTimeInSeconds mtime) (handleCallback0 cb) -- | Reads the entire contents of a text file with the specified encoding. readTextFile :: Encoding -> FilePath - -> Callback String + -> Callback1 String -> Effect Unit -readTextFile encoding file cb = runEffectFn3 readFileImpl file { encoding: show encoding } (handleCallback cb) +readTextFile encoding file = readTextFile' file (readFileStringOptionsDefault { encoding = encoding }) --- | Writes a buffer to a file. -writeFile +readTextFile' :: FilePath - -> Buffer - -> Callback Unit + -> ReadFileStringOptions + -> Callback1 String + -> Effect Unit +readTextFile' file options cb = runEffectFn3 readFileImpl file (readFileStringOptionsToInternal options) (handleCallback1 cb) + +-- | Reads the entire contents of a file returning the result as a raw buffer. +readFile + :: FilePath + -> Callback1 Buffer + -> Effect Unit +readFile file = readFile' file readFileBufferOptionsDefault + +readFile' + :: FilePath + -> ReadFileBufferOptions + -> Callback1 Buffer -> Effect Unit -writeFile file buff cb = runEffectFn4 writeFileImpl file buff {} (handleCallback cb) +readFile' file options cb = runEffectFn3 readFileImpl file (readFileBufferOptionsToInternal options) (handleCallback1 cb) -- | Writes text to a file using the specified encoding. writeTextFile :: Encoding -> FilePath -> String - -> Callback Unit + -> Callback0 -> Effect Unit -writeTextFile encoding file buff cb = runEffectFn4 writeFileImpl file buff { encoding: show encoding } (handleCallback cb) +writeTextFile encoding file buff = writeTextFile' file buff (writeFileStringOptionsDefault { encoding = encoding }) --- | Appends the contents of a buffer to a file. -appendFile +writeTextFile' + :: FilePath + -> String + -> WriteFileStringOptions + -> Callback0 + -> Effect Unit +writeTextFile' file buff options cb = runEffectFn4 writeFileImpl file buff (writeFileStringOptionsToInternal options) (handleCallback0 cb) + +-- | Writes a buffer to a file. +writeFile :: FilePath -> Buffer - -> Callback Unit + -> Callback0 -> Effect Unit -appendFile file buff cb = runEffectFn4 appendFileImpl file buff {} (handleCallback cb) +writeFile file buff = writeFile' file buff writeFileBufferOptionsDefault + +writeFile' + :: FilePath + -> Buffer + -> WriteFileBufferOptions + -> Callback0 + -> Effect Unit +writeFile' file buff options cb = runEffectFn4 writeFileImpl file buff (writeFileBufferOptionsToInternal options) (handleCallback0 cb) -- | Appends text to a file using the specified encoding. appendTextFile :: Encoding -> FilePath -> String - -> Callback Unit + -> Callback0 + -> Effect Unit +appendTextFile encoding file buff = appendTextFile' file buff (appendFileStringOptionsDefault { encoding = encoding }) + +appendTextFile' + :: FilePath + -> String + -> AppendFileStringOptions + -> Callback0 + -> Effect Unit +appendTextFile' file buff options cb = runEffectFn4 appendFileImpl file buff (appendFileStringOptionsToInternal options) (handleCallback0 cb) + +-- | Appends a buffer to a file. +appendFile + :: FilePath + -> Buffer + -> Callback0 -> Effect Unit -appendTextFile encoding file buff cb = runEffectFn4 appendFileImpl file buff { encoding: show encoding } (handleCallback cb) +appendFile file buff = appendFile' file buff appendFileBufferOptionsDefault + +appendFile' + :: FilePath + -> Buffer + -> AppendFileBufferOptions + -> Callback0 + -> Effect Unit +appendFile' file buff options cb = runEffectFn4 appendFileImpl file buff (appendFileBufferOptionsToInternal options) (handleCallback0 cb) -- | Open a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_open_path_flags_mode_callback) -- | for details. fdOpen :: FilePath - -> FileFlags - -> Maybe FileMode - -> Callback FileDescriptor + -> FileFlags -- default 'r' + -> Maybe FileMode -- default '0o666', TODO: use Perms? + -> Callback1 FileDescriptor -> Effect Unit -fdOpen file flags mode cb = runEffectFn4 openImpl file (fileFlagsToNode flags) (toNullable mode) (handleCallback cb) +fdOpen file flags mode cb = runEffectFn4 openImpl file (fileFlagsToNode flags) (toNullable mode) (handleCallback1 cb) -- | Read from a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_read_fd_buffer_offset_length_position_callback) -- | for details. @@ -346,18 +517,27 @@ fdRead :: FileDescriptor -> Buffer -> BufferOffset - -> BufferLength - -> Maybe FilePosition - -> Callback ByteCount + -> BufferLength -- TODO: should be Maybe BufferLength + -> Maybe FilePosition -- If position is null or -1 , data will be read from the current file position + -> Callback1 ByteCount -> Effect Unit -fdRead fd buff off len pos cb = runEffectFn6 readImpl fd buff off len (toNullable pos) (handleCallback cb) +fdRead fd buff off len pos cb = runEffectFn6 readImpl fd buff off len (toNullable pos) (handleCallback1 cb) + +-- | Read from a file asynchronously. See the [Node Documentation](https://nodejs.org/docs/latest/api/fs.html#fsreadfd-options-callback) +-- | for details. +fdRead' + :: FileDescriptor + -> FdReadOptions + -> Callback1 (Tuple ByteCount Buffer) + -> Effect Unit +fdRead' fd options cb = runEffectFn3 readWithOptionsImpl fd (fdReadOptionsToInternal options) (handleCallback1Tuple cb) -- | Convenience function to fill the whole buffer from the current -- | file position. fdNext :: FileDescriptor -> Buffer - -> Callback ByteCount + -> Callback1 ByteCount -> Effect Unit fdNext fd buff cb = do sz <- size buff @@ -369,18 +549,38 @@ fdWrite :: FileDescriptor -> Buffer -> BufferOffset - -> BufferLength + -> BufferLength -- TODO: should be Maybe BufferLength -> Maybe FilePosition - -> Callback ByteCount + -> Callback1 ByteCount + -> Effect Unit +fdWrite fd buff off len pos cb = runEffectFn6 writeImpl fd buff off len (toNullable pos) (handleCallback1 cb) + +-- | Write from a file asynchronously. See the [Node Documentation](https://nodejs.org/docs/latest/api/fs.html#fswritefd-options-callback) +-- | for details. +fdWrite' + :: FileDescriptor + -> Buffer + -> FdWriteOptions + -> Callback1 (Tuple ByteCount Buffer) -> Effect Unit -fdWrite fd buff off len pos cb = runEffectFn6 writeImpl fd buff off len (toNullable pos) (handleCallback cb) +fdWrite' fd buffer options cb = runEffectFn4 writeBufferWithOptionsImpl fd buffer (fdWriteOptionsToInternal options) (handleCallback1Tuple cb) + +-- It is unsafe to use fs.write() multiple times on the same file without waiting for the callback. For this scenario, fs.createWriteStream() is recommended. +fdWriteString + :: FileDescriptor + -> String + -> Maybe FilePosition + -> Encoding + -> Callback1 (Tuple ByteCount String) + -> Effect Unit +fdWriteString fd string pos encoding cb = runEffectFn5 writeStringImpl fd string (toNullable pos) (encodingToNode encoding) (handleCallback1Tuple cb) -- | Convenience function to append the whole buffer to the current -- | file position. fdAppend :: FileDescriptor -> Buffer - -> Callback ByteCount + -> Callback1 ByteCount -> Effect Unit fdAppend fd buff cb = do sz <- size buff @@ -390,6 +590,140 @@ fdAppend fd buff cb = do -- | for details. fdClose :: FileDescriptor - -> Callback Unit + -> Callback0 -> Effect Unit -fdClose fd cb = runEffectFn2 closeImpl fd (handleCallback cb) +fdClose fd cb = runEffectFn2 closeImpl fd (handleCallback0 cb) + +-- | Copy a file asynchronously using a `cp` command. +-- | See the [Node Documentation](https://nodejs.org/api/fs.html#fscpsrc-dest-options-callback) +-- | for details. +cpFile :: FilePath -> FilePath -> Callback0 -> Effect Unit +cpFile src dest = cpFile' src dest cpFileOptionsDefault + +cpFile' :: FilePath -> FilePath -> CpFileOptions -> Callback0 -> Effect Unit +cpFile' src dest opts cb = runEffectFn4 cpImpl src dest (cpFileOptionsToCpOptionsInternal opts) (handleCallback0 cb) + +-- | Copy a directory asynchronously using a `cp` command with option `recursive = true`. +-- | See the [Node Documentation](https://nodejs.org/api/fs.html#fscpsrc-dest-options-callback) +-- | for details. +cpDir :: FilePath -> FilePath -> Callback0 -> Effect Unit +cpDir src dest = cpDir' src dest cpDirOptionsDefault + +cpDir' :: FilePath -> FilePath -> CpDirOptions -> Callback0 -> Effect Unit +cpDir' src dest opts cb = runEffectFn4 cpImpl src dest (cpDirOptionsToCpOptionsInternal opts) (handleCallback0 cb) + +-- | Change permissions on a file descriptor. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fchmod_fd_mode_callback) +-- | for details. +fchmod :: FileDescriptor -> Perms -> Callback0 -> Effect Unit +fchmod fd perms cb = runEffectFn3 fchmodImpl fd (permsToString perms) (handleCallback0 cb) + +-- | Change ownership of a file descriptor. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fchown_fd_uid_gid_callback) +-- | for details. +fchown :: FileDescriptor -> Int -> Int -> Callback0 -> Effect Unit +fchown fd uid gid cb = runEffectFn4 fchownImpl fd uid gid (handleCallback0 cb) + +-- | Synchronize a file's in-core state with storage. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fdatasync_fd_callback) +-- | for details. +fdatasync :: FileDescriptor -> Callback0 -> Effect Unit +fdatasync fd cb = runEffectFn2 fdatasyncImpl fd (handleCallback0 cb) + +-- | Get file status information. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fstat_fd_callback) +-- | for details. +fstat :: FileDescriptor -> Callback1 Stats -> Effect Unit +fstat fd cb = runEffectFn2 fstatImpl fd (handleCallback1 cb) + +-- | Flushes a file descriptor to disk. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fsync_fd_callback) +-- | for details. +fsync :: FileDescriptor -> Callback0 -> Effect Unit +fsync fd cb = runEffectFn2 fsyncImpl fd (handleCallback0 cb) + +-- | Truncate a file to a specified length. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_ftruncate_fd_len_callback) +-- | for details. +ftruncate :: FileDescriptor -> Int -> Callback0 -> Effect Unit +ftruncate fd len cb = runEffectFn3 ftruncateImpl fd len (handleCallback0 cb) + +-- | Change file timestamps for a file descriptor. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_futimes_fd_atime_mtime_callback) +-- | for details. +futimes :: FileDescriptor -> DateTime -> DateTime -> Callback0 -> Effect Unit +futimes fd atime mtime cb = runEffectFn4 futimesImpl fd (datetimeToUnixEpochTimeInSeconds atime) (datetimeToUnixEpochTimeInSeconds mtime) (handleCallback0 cb) + +-- | Perform pattern matching in file paths. See the [Node Documentation](https://nodejs.org/api/glob.html#globglob_pattern_options_callback) +-- | for details. +glob :: Array FilePath -> Callback1 (Array FilePath) -> Effect Unit +glob pattern = glob' pattern globFilePathOptionsDefault + +glob' :: Array FilePath -> GlobFilePathOptions -> Callback1 (Array FilePath) -> Effect Unit +glob' pattern options cb = runEffectFn3 globImpl pattern (globFilePathOptionsToInternal options) (handleCallback1 cb) + +globDirent :: Array FilePath -> Callback1 (Array (Dirent DirentNameString)) -> Effect Unit +globDirent pattern = globDirent' pattern globDirentOptionsDefault + +globDirent' :: Array FilePath -> GlobDirentOptions -> Callback1 (Array (Dirent DirentNameString)) -> Effect Unit +globDirent' pattern options cb = runEffectFn3 globImpl pattern (globDirentOptionsToInternal options) (handleCallback1 cb) + +-- | Change permissions on a symbolic link. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_lchmod_path_mode_callback) +-- | for details. +lchmod :: FilePath -> Perms -> Callback0 -> Effect Unit +lchmod path perms cb = runEffectFn3 lchmodImpl path (permsToString perms) (handleCallback0 cb) + +-- | Change ownership of a symbolic link. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_lchown_path_uid_gid_callback) +-- | for details. +lchown :: FilePath -> Int -> Int -> Callback0 -> Effect Unit +lchown path uid gid cb = runEffectFn4 lchownImpl path uid gid (handleCallback0 cb) + +-- | Change timestamps for a symbolic link. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_lutimes_path_atime_mtime_callback) +-- | for details. +lutimes + :: FilePath + -> DateTime + -> DateTime + -> Callback0 + -> Effect Unit +lutimes file atime mtime cb = runEffectFn4 lutimesImpl file (datetimeToUnixEpochTimeInSeconds atime) (datetimeToUnixEpochTimeInSeconds mtime) (handleCallback0 cb) + +-- | TODO: path - Buffer Url, returns Promise +-- | Open a file as a blob. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_class_filehandle) +-- | for details. +-- openAsBlob :: FilePath -> Promise Blob -> Effect Unit +-- openAsBlob path cb = runEffectFn2 openAsBlobImpl path (handleCallback1 cb) + +-- | Open a directory. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_opendir_path_options_callback) +-- | for details. +opendir' :: FilePath -> OpendirOptions -> Callback1 Dir -> Effect Unit +opendir' path options cb = runEffectFn3 opendirImpl path (opendirOptionsToInternal options) (handleCallback1 cb) + +-- | Open a directory. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_opendir_path_options_callback) +-- | for details. +-- | NOTE: encoding: 'buffer' is not supported, will throw error "TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received an instance of Buffer" +opendir :: FilePath -> Callback1 Dir -> Effect Unit +opendir path = opendir' path opendirOptionsDefault + +-- | Read from a file descriptor into a buffer array. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_readv_fd_buffers_position_callback) +-- | for details. +readv :: FileDescriptor -> Array Buffer -> Maybe FilePosition -> Callback1 (Tuple ByteCount (Array Buffer)) -> Effect Unit +readv fd buffers position cb = runEffectFn4 readvImpl fd buffers (toNullable position) (handleCallback1Tuple cb) + +-- | Get file system statistics. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_statfs_path_callback) +-- | for details. +statfs :: FilePath -> Callback1 Stats -> Effect Unit +statfs path cb = runEffectFn2 statfsImpl path (handleCallback1 cb) + +-- -- | Stop watching a file for changes. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_unwatchfile_filename_listener) +-- -- | for details. +-- unwatchFile :: FilePath -> Effect Unit +-- unwatchFile path = runEffectFn1 unwatchFileImpl path +-- +-- -- | Watch for changes in a file or directory. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener) +-- -- | for details. +-- watch :: FilePath -> (String -> Effect Unit) -> Effect Unit +-- watch path cb = runEffectFn2 watchImpl path (mkEffectFn1 cb) +-- +-- -- | Watch for changes in a file and trigger a callback. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_watchfile_filename_options_listener) +-- -- | for details. +-- watchFile :: FilePath -> (Stats -> Stats -> Effect Unit) -> Effect Unit +-- watchFile path cb = runEffectFn2 watchFileImpl path (mkEffectFn2 cb) + +-- | Write from an array of buffers to a file descriptor. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_writev_fd_buffers_position_callback) +-- | for details. +writev :: FileDescriptor -> Array Buffer -> Maybe FilePosition -> Callback1 (Tuple ByteCount (Array Buffer)) -> Effect Unit +writev fd buffers position cb = runEffectFn4 writevImpl fd buffers (toNullable position) (handleCallback1Tuple cb) diff --git a/src/Node/FS/Constants.js b/src/Node/FS/Constants.js index b71e93c..2cdddc7 100644 --- a/src/Node/FS/Constants.js +++ b/src/Node/FS/Constants.js @@ -1,17 +1,13 @@ import { constants } from "node:fs"; export const f_OK = constants.F_OK; - export const r_OK = constants.R_OK; - export const w_OK = constants.W_OK; - export const x_OK = constants.X_OK; +export const copyFile_NO_FLAGS = 0; export const copyFile_EXCL = constants.COPYFILE_EXCL; - export const copyFile_FICLONE = constants.COPYFILE_FICLONE; - export const copyFile_FICLONE_FORCE = constants.COPYFILE_FICLONE_FORCE; export const appendCopyMode = (l, r) => l | r; diff --git a/src/Node/FS/Constants.purs b/src/Node/FS/Constants.purs index c6bbcab..1d9c4e7 100644 --- a/src/Node/FS/Constants.purs +++ b/src/Node/FS/Constants.purs @@ -7,7 +7,7 @@ import Data.Function.Uncurried (Fn2, runFn2) -- | the mode parameter passed to `access` and `accessSync`. foreign import data AccessMode :: Type --- | the file is visible to the calling process. +-- | the file is visible to the calling process. -- | This is useful for determining if a file exists, but says nothing about rwx permissions. Default if no mode is specified. foreign import f_OK :: AccessMode @@ -25,6 +25,9 @@ defaultAccessMode = f_OK :: AccessMode -- | A constant used in `copyFile`. foreign import data CopyMode :: Type +-- | By default, `dest` is overwritten if it already exists. +foreign import copyFile_NO_FLAGS :: CopyMode + -- | If present, the copy operation will fail with an error if the destination path already exists. foreign import copyFile_EXCL :: CopyMode @@ -34,7 +37,7 @@ foreign import copyFile_FICLONE :: CopyMode -- | If present, the copy operation will attempt to create a copy-on-write reflink. If the underlying platform does not support copy-on-write, then the operation will fail with an error. foreign import copyFile_FICLONE_FORCE :: CopyMode -defaultCopyMode = copyFile_EXCL :: CopyMode +defaultCopyMode = copyFile_NO_FLAGS :: CopyMode foreign import appendCopyMode :: Fn2 CopyMode CopyMode CopyMode diff --git a/src/Node/FS/Dir.js b/src/Node/FS/Dir.js new file mode 100644 index 0000000..ce73b07 --- /dev/null +++ b/src/Node/FS/Dir.js @@ -0,0 +1,3 @@ +export { inspect as showDirObj } from "util"; + +export const path = (dir) => dir.path; diff --git a/src/Node/FS/Dir.purs b/src/Node/FS/Dir.purs new file mode 100644 index 0000000..bc2ca38 --- /dev/null +++ b/src/Node/FS/Dir.purs @@ -0,0 +1,16 @@ +module Node.FS.Dir where + +import Prelude + +import Node.Path (FilePath) + +-- Foreign imports for the Dir class +foreign import data Dir :: Type + +foreign import showDirObj :: Dir -> String + +instance Show Dir where + show s = showDirObj s + +-- | Get the path of this directory as was provided to fs.opendir(), fs.opendirSync(), or fsPromises.opendir(). +foreign import path :: Dir -> FilePath diff --git a/src/Node/FS/Dir/Aff.purs b/src/Node/FS/Dir/Aff.purs new file mode 100644 index 0000000..bd42a85 --- /dev/null +++ b/src/Node/FS/Dir/Aff.purs @@ -0,0 +1,64 @@ +module Node.FS.Dir.Aff where + +import Prelude + +import Data.Array as Array +import Data.Either (Either(..)) +import Data.Maybe (Maybe(..)) +import Effect (Effect) +import Effect.Aff (Aff, effectCanceler, finally, makeAff, nonCanceler) +import Effect.Class (liftEffect) +import Effect.Exception (Error) +import Effect.Ref (Ref) +import Effect.Ref as Ref +import Node.FS.Dir (Dir) +import Node.FS.Dir.Async as A +import Node.FS.Dirent (Dirent, DirentNameString) +import Node.FS.Internal.ToAff (toAff1) + +read :: Dir -> Aff (Maybe (Dirent DirentNameString)) +read = toAff1 A.read + +close :: Dir -> Aff Unit +close dir = makeAff \k -> do + A.close dir k + pure nonCanceler + +entries :: Dir -> Aff (Array (Dirent DirentNameString)) +entries dir = do + direntArrayRef <- liftEffect $ Ref.new [] + let + handleDirent :: Dirent DirentNameString -> Effect Unit + handleDirent dirent = Ref.modify_ (flip Array.snoc dirent) direntArrayRef + entriesIterate dir handleDirent + liftEffect $ Ref.read direntArrayRef + +-- | Implementation of `dir[Symbol.asyncIterator]()` +-- | Documentation - https://nodejs.org/docs/latest/api/fs.html#dirsymbolasynciterator +-- | Implementation - https://github.com/nodejs/node/blob/b2161d3a137e5a2582c71c798e140d2ba8f7c1d4/lib/internal/fs/dir.js#L257 +-- | +-- | Nodejs version ignores errors on read, doesnt ignore errors on close. +-- | Purescript version - the same (proved in tests). +-- | +-- | Possible errors: +-- | - if dir is closed already - `read` and `close` will throw "Directory handle was closed" +entriesIterate :: Dir -> ((Dirent DirentNameString) -> Effect Unit) -> Aff Unit +entriesIterate dir handleDirent = finally (close dir) $ makeAff \(k :: Either Error Unit -> Effect Unit) -> do + stopRef <- Ref.new false + go k stopRef + pure $ effectCanceler $ Ref.write true stopRef + where + go :: (Either Error Unit -> Effect Unit) -> Ref Boolean -> Effect Unit + go callback stopRef = do + stopped <- Ref.read stopRef + if stopped then do + callback $ Right unit + else A.read dir case _ of + Left error -> callback $ Left error + Right maybeDirent -> + case maybeDirent of + Nothing -> do + callback $ Right unit + Just dirent -> do + handleDirent dirent + go callback stopRef diff --git a/src/Node/FS/Dir/Async.js b/src/Node/FS/Dir/Async.js new file mode 100644 index 0000000..668c881 --- /dev/null +++ b/src/Node/FS/Dir/Async.js @@ -0,0 +1,2 @@ +export const closeImpl = (dir, callback) => dir.close(callback); +export const readImpl = (dir, callback) => dir.read(callback); diff --git a/src/Node/FS/Dir/Async.purs b/src/Node/FS/Dir/Async.purs new file mode 100644 index 0000000..95fb856 --- /dev/null +++ b/src/Node/FS/Dir/Async.purs @@ -0,0 +1,22 @@ +module Node.FS.Dir.Async (close, read) where + +import Prelude + +import Data.Maybe (Maybe) +import Data.Nullable (Nullable, toMaybe) +import Effect (Effect) +import Effect.Uncurried (EffectFn2, runEffectFn2) +import Node.FS.Dir (Dir) +import Node.FS.Dirent (Dirent, DirentNameString) +import Node.FS.Internal.Callbacks (Callback0, JSCallback0, JSCallback1, Callback1, handleCallback0, handleCallback1) + +foreign import closeImpl :: EffectFn2 Dir JSCallback0 Unit +foreign import readImpl :: EffectFn2 Dir (JSCallback1 (Nullable (Dirent DirentNameString))) Unit + +-- | Asynchronously close the directory's underlying resource handle. +close :: Dir -> Callback0 -> Effect Unit +close dir cb = runEffectFn2 closeImpl dir (handleCallback0 cb) + +-- | Asynchronously read the next directory entry. +read :: Dir -> (Callback1 (Maybe (Dirent DirentNameString))) -> Effect Unit +read dir cb = runEffectFn2 readImpl dir (handleCallback1 (cb <<< map toMaybe)) diff --git a/src/Node/FS/Dir/Sync.js b/src/Node/FS/Dir/Sync.js new file mode 100644 index 0000000..bba43b1 --- /dev/null +++ b/src/Node/FS/Dir/Sync.js @@ -0,0 +1,2 @@ +export const closeSyncImpl = (dir) => dir.closeSync(); +export const readSyncImpl = (dir) => dir.readSync(); diff --git a/src/Node/FS/Dir/Sync.purs b/src/Node/FS/Dir/Sync.purs new file mode 100644 index 0000000..dfe49e9 --- /dev/null +++ b/src/Node/FS/Dir/Sync.purs @@ -0,0 +1,21 @@ +module Node.FS.Dir.Sync (close, read) where + +import Prelude + +import Data.Maybe (Maybe) +import Data.Nullable (Nullable, toMaybe) +import Effect (Effect) +import Effect.Uncurried (EffectFn1, runEffectFn1) +import Node.FS.Dir (Dir) +import Node.FS.Dirent (Dirent, DirentNameString) + +foreign import closeSyncImpl :: EffectFn1 Dir Unit +foreign import readSyncImpl :: EffectFn1 Dir (Nullable (Dirent DirentNameString)) + +-- | Synchronously close the directory's underlying resource handle. +close :: Dir -> Effect Unit +close = runEffectFn1 closeSyncImpl + +-- | Synchronously read the next directory entry. +read :: Dir -> Effect (Maybe (Dirent DirentNameString)) +read dir = toMaybe <$> runEffectFn1 readSyncImpl dir diff --git a/src/Node/FS/Dirent.js b/src/Node/FS/Dirent.js new file mode 100644 index 0000000..1314aa9 --- /dev/null +++ b/src/Node/FS/Dirent.js @@ -0,0 +1,13 @@ +export { inspect as showDirentObj } from "util"; + +// Implemented here +// https://github.com/nodejs/node/blob/d881fcba86f72ff506eea53a5eca9a0ab2e4a02f/lib/internal/fs/utils.js#L160 +export const isBlockDevice = dirent => dirent.isBlockDevice(); +export const isCharacterDevice = dirent => dirent.isCharacterDevice(); +export const isDirectory = dirent => dirent.isDirectory(); +export const isFIFO = dirent => dirent.isFIFO(); +export const isFile = dirent => dirent.isFile(); +export const isSocket = dirent => dirent.isSocket(); +export const isSymbolicLink = dirent => dirent.isSymbolicLink(); +export const nameImpl = dirent => dirent.name; +export const parentPath = dirent => dirent.parentPath; diff --git a/src/Node/FS/Dirent.purs b/src/Node/FS/Dirent.purs new file mode 100644 index 0000000..3e21f15 --- /dev/null +++ b/src/Node/FS/Dirent.purs @@ -0,0 +1,83 @@ +module Node.FS.Dirent + ( Dirent + , DirentName + , DirentNameString + , DirentNameBuffer + , DirentType(..) + , getType + , class IsDirentNameForDirent + , name + , parentPath + ) where + +import Prelude + +import Node.Buffer (Buffer) +import Node.Path (FilePath) +import Partial.Unsafe (unsafeCrashWith) +import Data.Generic.Rep (class Generic) +import Data.Show.Generic (genericShow) + +data DirentName + +foreign import data DirentNameString :: DirentName +foreign import data DirentNameBuffer :: DirentName + +foreign import data Dirent :: DirentName -> Type + +foreign import isBlockDevice :: forall direntnametype. Dirent direntnametype -> Boolean +foreign import isCharacterDevice :: forall direntnametype. Dirent direntnametype -> Boolean +foreign import isDirectory :: forall direntnametype. Dirent direntnametype -> Boolean +foreign import isFIFO :: forall direntnametype. Dirent direntnametype -> Boolean +foreign import isFile :: forall direntnametype. Dirent direntnametype -> Boolean +foreign import isSocket :: forall direntnametype. Dirent direntnametype -> Boolean +foreign import isSymbolicLink :: forall direntnametype. Dirent direntnametype -> Boolean + +data DirentType + = DirentType_BlockDevice -- Dirent object describes a block device. + | DirentType_CharacterDevice -- Dirent object describes a character device. + | DirentType_Directory -- Dirent object describes a directory. + | DirentType_FIFO -- Dirent object describes a FIFO pipe. + | DirentType_File -- Dirent object describes a regular file. + | DirentType_Socket -- Dirent object describes a socket. + | DirentType_SymbolicLink -- Dirent object describes a symbolic link. + +derive instance Eq DirentType +derive instance Ord DirentType +derive instance Generic DirentType _ +instance Show DirentType where + show = genericShow + +getType :: forall direntnametype. Dirent direntnametype -> DirentType +getType dirent = + if isBlockDevice dirent then DirentType_BlockDevice + else if isCharacterDevice dirent then DirentType_CharacterDevice + else if isDirectory dirent then DirentType_Directory + else if isFIFO dirent then DirentType_FIFO + else if isFile dirent then DirentType_File + else if isSocket dirent then DirentType_Socket + else if isSymbolicLink dirent then DirentType_SymbolicLink + else unsafeCrashWith ("Impossible: unknown Dirent type for " <> show dirent) + +foreign import nameImpl :: forall direntnametype nametype. Dirent direntnametype -> nametype + +class IsDirentNameForDirent direntnametype stringOrBuffer | direntnametype -> stringOrBuffer where + -- | The file name that this object refers to. The type of this value is determined by the options.encoding passed to fs.readdir() or fs.readdirSync(). + name :: Dirent direntnametype -> stringOrBuffer + +instance IsDirentNameForDirent DirentNameString String where + name :: Dirent DirentNameString -> String + name = nameImpl +else instance IsDirentNameForDirent DirentNameBuffer Buffer where + name :: Dirent DirentNameBuffer -> Buffer + name = nameImpl + +-- | Get the parent directory path of the file this Dirent object refers to. +-- | Added in: v21.4.0, v20.12.0, v18.20.0 +-- | TODO: support for old node version? `Nullable FilePath`? +foreign import parentPath :: forall direntnametype. Dirent direntnametype -> FilePath + +foreign import showDirentObj :: forall direntnametype. Dirent direntnametype -> String + +instance Show (Dirent direntnametype) where + show s = showDirentObj s diff --git a/src/Node/FS/Internal/Callbacks.purs b/src/Node/FS/Internal/Callbacks.purs new file mode 100644 index 0000000..e578007 --- /dev/null +++ b/src/Node/FS/Internal/Callbacks.purs @@ -0,0 +1,34 @@ +module Node.FS.Internal.Callbacks where + +import Prelude + +import Data.Either (Either(..)) +import Data.Maybe (Maybe(..)) +import Data.Nullable (Nullable, toMaybe) +import Data.Tuple (Tuple(..)) +import Effect (Effect) +import Effect.Exception (Error) +import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, mkEffectFn1, mkEffectFn2, mkEffectFn3) + +type JSCallback0 = EffectFn1 (Nullable Error) Unit +type JSCallback1 a = EffectFn2 (Nullable Error) a Unit +type JSCallback2 a b = EffectFn3 (Nullable Error) a b Unit + +-- | Type synonym for callback functions. +type Callback0 = Either Error Unit -> Effect Unit -- TODO: maybe better Maybe Error -> Unit? +type Callback1 a = Either Error a -> Effect Unit + +handleCallback0 :: Callback0 -> JSCallback0 +handleCallback0 cb = mkEffectFn1 \err -> case toMaybe err of + Nothing -> cb (Right unit) + Just err' -> cb (Left err') + +handleCallback1 :: forall a. Callback1 a -> JSCallback1 a +handleCallback1 cb = mkEffectFn2 \err a -> case toMaybe err of + Nothing -> cb (Right a) + Just err' -> cb (Left err') + +handleCallback1Tuple :: forall a b. Callback1 (Tuple a b) -> JSCallback2 a b +handleCallback1Tuple cb = mkEffectFn3 \err a b -> case toMaybe err of + Nothing -> cb (Right (Tuple a b)) + Just err' -> cb (Left err') diff --git a/src/Node/FS/Internal/ToAff.purs b/src/Node/FS/Internal/ToAff.purs new file mode 100644 index 0000000..41d390c --- /dev/null +++ b/src/Node/FS/Internal/ToAff.purs @@ -0,0 +1,71 @@ +module Node.FS.Internal.ToAff where + +import Prelude + +import Effect (Effect) +import Effect.Aff (Aff, makeAff, nonCanceler) +import Node.FS.Internal.Callbacks (Callback1) + +toAff + :: forall a + . (Callback1 a -> Effect Unit) + -> Aff a +toAff p = makeAff \k -> p k $> nonCanceler + +toAff1 + :: forall a x + . (x -> Callback1 a -> Effect Unit) + -> x + -> Aff a +toAff1 f a = toAff (f a) + +toAff2 + :: forall a x y + . (x -> y -> Callback1 a -> Effect Unit) + -> x + -> y + -> Aff a +toAff2 f a b = toAff (f a b) + +toAff3 + :: forall a x y z + . (x -> y -> z -> Callback1 a -> Effect Unit) + -> x + -> y + -> z + -> Aff a +toAff3 f a b c = toAff (f a b c) + +toAff4 + :: forall a v x y z + . (v -> x -> y -> z -> Callback1 a -> Effect Unit) + -> v + -> x + -> y + -> z + -> Aff a +toAff4 f a b c d = toAff (f a b c d) + +toAff5 + :: forall a w v x y z + . (w -> v -> x -> y -> z -> Callback1 a -> Effect Unit) + -> w + -> v + -> x + -> y + -> z + -> Aff a +toAff5 f a b c d e = toAff (f a b c d e) + +toAff6 + :: forall a w v x y z t + . (w -> v -> x -> y -> z -> t -> Callback1 a -> Effect Unit) + -> w + -> v + -> x + -> y + -> z + -> t + -> Aff a +toAff6 f a b c d e t = toAff (f a b c d e t) + diff --git a/src/Node/FS/Internal/Undefinable.js b/src/Node/FS/Internal/Undefinable.js new file mode 100644 index 0000000..622ebdc --- /dev/null +++ b/src/Node/FS/Internal/Undefinable.js @@ -0,0 +1,12 @@ +/* eslint-disable no-eq-null, eqeqeq */ + +const undefinedImpl = undefined; +export { undefinedImpl as undefined }; + +export function undefinable(a, r, f) { + return a === undefined ? r : f(a); +} + +export function notUndefined(x) { + return x; +} diff --git a/src/Node/FS/Internal/Undefinable.purs b/src/Node/FS/Internal/Undefinable.purs new file mode 100644 index 0000000..668ce79 --- /dev/null +++ b/src/Node/FS/Internal/Undefinable.purs @@ -0,0 +1,55 @@ +-- | This module defines types and functions for working with undefinable types +-- | using the FFI. + +module Node.FS.Internal.Undefinable + ( Undefinable + , undefined + , notUndefined + , toMaybe + , toUndefinable + ) where + +import Prelude + +import Data.Eq (class Eq1) +import Data.Function (on) +import Data.Function.Uncurried (Fn3, runFn3) +import Data.Maybe (Maybe(..), maybe) +import Data.Ord (class Ord1) + +foreign import data Undefinable :: Type -> Type + +type role Undefinable representational + +-- | The undefined value. +foreign import undefined :: forall a. Undefinable a + +foreign import undefinable :: forall a r. Fn3 (Undefinable a) r (a -> r) r + +-- | Wrap a non-undefined value. +foreign import notUndefined :: forall a. a -> Undefinable a + +-- | Takes `Nothing` to `undefined`, and `Just a` to `a`. +toUndefinable :: forall a. Maybe a -> Undefinable a +toUndefinable = maybe undefined notUndefined + +-- | Represent `undefined` using `Maybe a` as `Nothing`. Note that this function +-- | can violate parametricity, as it inspects the runtime representation of +-- | its argument (see the warning about the pitfall of `Undefinable` above). +toMaybe :: forall a. Undefinable a -> Maybe a +toMaybe n = runFn3 undefinable n Nothing Just + +instance showUndefinable :: Show a => Show (Undefinable a) where + show = maybe "undefined" show <<< toMaybe + +instance eqUndefinable :: Eq a => Eq (Undefinable a) where + eq = eq `on` toMaybe + +instance eq1Undefinable :: Eq1 Undefinable where + eq1 = eq + +instance ordUndefinable :: Ord a => Ord (Undefinable a) where + compare = compare `on` toMaybe + +instance ord1Undefinable :: Ord1 Undefinable where + compare1 = compare diff --git a/src/Node/FS/Internal/Utils.purs b/src/Node/FS/Internal/Utils.purs new file mode 100644 index 0000000..9bf7e50 --- /dev/null +++ b/src/Node/FS/Internal/Utils.purs @@ -0,0 +1,14 @@ +module Node.FS.Internal.Utils where + +import Prelude + +import Data.DateTime (DateTime) +import Data.DateTime.Instant (fromDateTime, unInstant) +import Data.Int (round) +import Data.Time.Duration (Milliseconds(..)) + +datetimeToUnixEpochTimeInSeconds :: DateTime -> Int +datetimeToUnixEpochTimeInSeconds date = ms (toEpochMilliseconds date) / 1000 + where + ms (Milliseconds n) = round n + toEpochMilliseconds = unInstant <<< fromDateTime diff --git a/src/Node/FS/Options.purs b/src/Node/FS/Options.purs new file mode 100644 index 0000000..7ae95ed --- /dev/null +++ b/src/Node/FS/Options.purs @@ -0,0 +1,362 @@ +module Node.FS.Options where + +import Prelude + +import Data.Function.Uncurried (Fn2, mkFn2) +import Data.Maybe (Maybe(..)) +import Data.Nullable (Nullable, toNullable) +import Data.Nullable as Nullable +import Foreign (Foreign) +import Foreign as Foreign +import Node.Buffer (Buffer) +import Node.Encoding (Encoding(..), encodingToNode) +import Node.FS.Constants (CopyMode, FileFlags(..), copyFile_NO_FLAGS, fileFlagsToNode) +import Node.FS.Dirent (Dirent, DirentNameString) +import Node.FS.Internal.Undefinable (Undefinable) +import Node.FS.Internal.Undefinable as Undefinable +import Node.FS.Perms (Perms, all, mkPerms, permsToString, read, write) +import Node.FS.Types (BufferLength, BufferOffset, EncodingString, FilePosition) +import Node.Path (FilePath) + +type RmdirOptions = { maxRetries :: Int, retryDelay :: Int } + +rmdirOptionsDefault :: RmdirOptions +rmdirOptionsDefault = { maxRetries: 0, retryDelay: 100 } + +type RmOptions = { force :: Boolean, maxRetries :: Int, recursive :: Boolean, retryDelay :: Int } + +rmOptionsDefault :: RmOptions +rmOptionsDefault = { force: false, maxRetries: 100, recursive: false, retryDelay: 1000 } + +---------- + +type MkdirOptionsInternal = { recursive :: Boolean, mode :: String } +type MkdirOptions = { recursive :: Boolean, mode :: Perms } + +mkdirOptionsDefault :: MkdirOptions +mkdirOptionsDefault = { recursive: false, mode: mkPerms all all all } + +mkdirOptionsToInternal :: MkdirOptions -> MkdirOptionsInternal +mkdirOptionsToInternal { recursive, mode } = { recursive, mode: permsToString mode } + +--------- + +type RealpathOptionsInternal = { encoding :: EncodingString } +type RealpathOptions = { encoding :: Encoding } + +realpathOptionsDefault :: RealpathOptions +realpathOptionsDefault = { encoding: UTF8 } + +realpathOptionsToInternal :: RealpathOptions -> RealpathOptionsInternal +realpathOptionsToInternal { encoding } = { encoding: encodingToNode encoding } + +--------- +type ReaddirOptionsInternal = + { encoding :: Foreign -- encoding string or "buffer" + , recursive :: Boolean + , withFileTypes :: Boolean + } + +type ReaddirFilePathOptions = { recursive :: Boolean, encoding :: Encoding } + +readdirFilePathOptionsDefault :: ReaddirFilePathOptions +readdirFilePathOptionsDefault = { recursive: false, encoding: UTF8 } + +readdirFilePathOptionsToInternal :: ReaddirFilePathOptions -> ReaddirOptionsInternal +readdirFilePathOptionsToInternal { recursive, encoding } = { recursive, encoding: Foreign.unsafeToForeign $ encodingToNode encoding, withFileTypes: false } + +type ReaddirBufferOptions = { recursive :: Boolean } + +readdirBufferOptionsDefault :: ReaddirBufferOptions +readdirBufferOptionsDefault = { recursive: false } + +readdirBufferOptionsToInternal :: ReaddirBufferOptions -> ReaddirOptionsInternal +readdirBufferOptionsToInternal { recursive } = { recursive, encoding: Foreign.unsafeToForeign "buffer", withFileTypes: false } + +type ReaddirDirentOptions = { recursive :: Boolean, encoding :: Encoding } + +readdirDirentOptionsDefault :: ReaddirDirentOptions +readdirDirentOptionsDefault = { recursive: false, encoding: UTF8 } + +readdirDirentOptionsToInternal :: ReaddirDirentOptions -> ReaddirOptionsInternal +readdirDirentOptionsToInternal { recursive, encoding } = { recursive, encoding: Foreign.unsafeToForeign $ encodingToNode encoding, withFileTypes: true } + +type ReaddirDirentBufferOptions = { recursive :: Boolean } + +readdirDirentBufferOptionsDefault :: ReaddirDirentBufferOptions +readdirDirentBufferOptionsDefault = { recursive: false } + +readdirDirentBufferOptionsToInternal :: ReaddirDirentBufferOptions -> ReaddirOptionsInternal +readdirDirentBufferOptionsToInternal { recursive } = { recursive, encoding: Foreign.unsafeToForeign "buffer", withFileTypes: true } + +--------- +type ReadFileOptionsInternal = + { encoding :: Nullable String -- if null - returns Buffer, if encoding string - String https://nodejs.org/docs/latest/api/fs.html#fsreadfilepath-options-callback + , flag :: String + -- , signal :: Nullable AbortSignal + } + +type ReadFileStringOptions = { flag :: FileFlags, encoding :: Encoding } + +readFileStringOptionsDefault :: ReadFileStringOptions +readFileStringOptionsDefault = { flag: R, encoding: UTF8 } + +readFileStringOptionsToInternal :: ReadFileStringOptions -> ReadFileOptionsInternal +readFileStringOptionsToInternal { flag, encoding } = { flag: fileFlagsToNode flag, encoding: Nullable.notNull $ encodingToNode encoding } + +type ReadFileBufferOptions = { flag :: FileFlags } + +readFileBufferOptionsDefault :: ReadFileBufferOptions +readFileBufferOptionsDefault = { flag: R } + +readFileBufferOptionsToInternal :: ReadFileBufferOptions -> ReadFileOptionsInternal +readFileBufferOptionsToInternal { flag } = { flag: fileFlagsToNode flag, encoding: Nullable.null } + +--------- +type WriteFileOptionsInternal = + { encoding :: Nullable String -- The encoding option is ignored if data is a buffer + , mode :: String + , flag :: String -- See support of file system flags. Default: 'w'. + , flush :: Boolean -- If all data is successfully written to the file, and flush is true, fs.fsync() is used to flush the data. Default: false. + -- , signal :: Nullable AbortSignal -- allows aborting an in-progress writeFile + } + +type WriteFileStringOptions = + { encoding :: Encoding + , mode :: Perms + , flag :: FileFlags + , flush :: Boolean + } + +writeFileStringOptionsDefault :: WriteFileStringOptions +writeFileStringOptionsDefault = + { encoding: UTF8 + , mode: mkPerms (read + write) (read + write) (read + write) + , flag: W + , flush: false + } + +writeFileStringOptionsToInternal :: WriteFileStringOptions -> WriteFileOptionsInternal +writeFileStringOptionsToInternal { encoding, mode, flag, flush } = { encoding: Nullable.notNull $ encodingToNode encoding, mode: permsToString mode, flag: fileFlagsToNode flag, flush } + +type WriteFileBufferOptions = + { mode :: Perms + , flag :: FileFlags + , flush :: Boolean + } + +writeFileBufferOptionsDefault :: WriteFileBufferOptions +writeFileBufferOptionsDefault = + { mode: mkPerms (read + write) (read + write) (read + write) + , flag: W + , flush: false + } + +writeFileBufferOptionsToInternal :: WriteFileBufferOptions -> WriteFileOptionsInternal +writeFileBufferOptionsToInternal { mode, flag, flush } = { mode: permsToString mode, flag: fileFlagsToNode flag, flush, encoding: Nullable.null } + +--------- +type AppendFileOptionsInternal = + { encoding :: Nullable String -- The encoding option is ignored if data is a buffer + , mode :: String + , flag :: String -- See support of file system flags. Default: 'w'. + , flush :: Boolean -- If all data is successfully written to the file, and flush is true, fs.fsync() is used to flush the data. Default: false. + } + +type AppendFileStringOptions = + { encoding :: Encoding + , mode :: Perms + , flag :: FileFlags + , flush :: Boolean + } + +appendFileStringOptionsDefault :: AppendFileStringOptions +appendFileStringOptionsDefault = + { encoding: UTF8 + , mode: mkPerms (read + write) (read + write) (read + write) + , flag: A + , flush: false + } + +appendFileStringOptionsToInternal :: AppendFileStringOptions -> AppendFileOptionsInternal +appendFileStringOptionsToInternal { encoding, mode, flag, flush } = { encoding: Nullable.notNull $ encodingToNode encoding, mode: permsToString mode, flag: fileFlagsToNode flag, flush } + +type AppendFileBufferOptions = + { mode :: Perms + , flag :: FileFlags + , flush :: Boolean + } + +appendFileBufferOptionsDefault :: AppendFileBufferOptions +appendFileBufferOptionsDefault = + { mode: mkPerms (read + write) (read + write) (read + write) + , flag: A + , flush: false + } + +appendFileBufferOptionsToInternal :: AppendFileBufferOptions -> AppendFileOptionsInternal +appendFileBufferOptionsToInternal { mode, flag, flush } = { mode: permsToString mode, flag: fileFlagsToNode flag, flush, encoding: Nullable.null } + +--------- +type FdReadOptionsInternal = + { buffer :: Nullable Buffer -- Default: `Buffer.alloc(16384)` + , offset :: BufferOffset -- Default: `0` + , length :: Nullable BufferLength -- Default: buffer.byteLength - offset + , position :: Nullable FilePosition + } + +type FdReadOptions = + { buffer :: Maybe Buffer + , offset :: BufferOffset + , length :: Maybe BufferLength + , position :: Maybe FilePosition -- If position is null or -1 , data will be read from the current file position, and the file position will be updated. If position is a non-negative integer, the file position will be unchanged. + } + +fdReadOptionsDefault :: FdReadOptions +fdReadOptionsDefault = + { buffer: Nothing + , offset: 0 + , length: Nothing + , position: Nothing + } + +fdReadOptionsToInternal :: FdReadOptions -> FdReadOptionsInternal +fdReadOptionsToInternal { buffer, offset, length, position } = { buffer: Nullable.toNullable buffer, offset, length: Nullable.toNullable length, position: Nullable.toNullable position } + +--------- +type FdWriteOptionsInternal = + { offset :: BufferOffset -- Default: `0` + , length :: Nullable BufferLength -- Default: buffer.byteLength - offset + , position :: Nullable FilePosition + } + +type FdWriteOptions = + { offset :: BufferOffset + , length :: Maybe BufferLength + , position :: Maybe FilePosition -- If position is null or -1 , data will be write from the current file position, and the file position will be updated. If position is a non-negative integer, the file position will be unchanged. + } + +fdWriteOptionsDefault :: FdWriteOptions +fdWriteOptionsDefault = + { offset: 0 + , length: Nothing + , position: Nothing + } + +fdWriteOptionsToInternal :: FdWriteOptions -> FdWriteOptionsInternal +fdWriteOptionsToInternal { offset, length, position } = { offset, length: Nullable.toNullable length, position: Nullable.toNullable position } + +------------------- + +type CpOptionsInternal = + { dereference :: Boolean + , errorOnExist :: Boolean -- Whether to dereference symlinks + -- if null - will throw "TypeError [ERR_INVALID_ARG_TYPE]: The "options.filter" property must be of type function. Received null" + , filter :: Undefinable (Fn2 FilePath FilePath Boolean) + , force :: Boolean + , mode :: CopyMode -- Modifiers for copy operation + , preserveTimestamps :: Boolean -- Preserve timestamps from source + , recursive :: Boolean -- Copy directories recursively + , verbatimSymlinks :: Boolean -- Skip path resolution for symlinks + } + +data CpForce = CpForce_False | CpForce_TrueWithoutErrorOnExit | CpForce_TrueWithErrorOnExit + +type CpDirOptions = + { dereference :: Boolean + , filter :: Maybe (FilePath -> FilePath -> Boolean) + , force :: CpForce + , mode :: CopyMode + , preserveTimestamps :: Boolean + , verbatimSymlinks :: Boolean + } + +type CpFileOptions = + { dereference :: Boolean + , force :: CpForce + , mode :: CopyMode + , preserveTimestamps :: Boolean + , verbatimSymlinks :: Boolean + } + +cpDirOptionsDefault :: CpDirOptions +cpDirOptionsDefault = + { dereference: false + , filter: Nothing + , force: CpForce_TrueWithoutErrorOnExit + , mode: copyFile_NO_FLAGS + , preserveTimestamps: false + , verbatimSymlinks: false + } + +cpFileOptionsDefault :: CpFileOptions +cpFileOptionsDefault = + { dereference: false + , force: CpForce_TrueWithoutErrorOnExit + , mode: copyFile_NO_FLAGS + , preserveTimestamps: false + , verbatimSymlinks: false + } + +cpDirOptionsToCpOptionsInternal :: CpDirOptions -> CpOptionsInternal +cpDirOptionsToCpOptionsInternal opts = + { dereference: opts.dereference + , errorOnExist: case opts.force of + CpForce_TrueWithErrorOnExit -> true + _ -> false + , filter: Undefinable.toUndefinable $ map mkFn2 (opts.filter) + , force: case opts.force of + CpForce_False -> false + _ -> true + , mode: opts.mode + , preserveTimestamps: opts.preserveTimestamps + , recursive: true + , verbatimSymlinks: opts.verbatimSymlinks + } + +cpFileOptionsToCpOptionsInternal :: CpFileOptions -> CpOptionsInternal +cpFileOptionsToCpOptionsInternal opts = + { dereference: opts.dereference + , errorOnExist: case opts.force of + CpForce_TrueWithErrorOnExit -> true + _ -> false + , filter: Undefinable.undefined + , force: case opts.force of + CpForce_False -> false + _ -> true + , mode: opts.mode + , preserveTimestamps: opts.preserveTimestamps + , recursive: false + , verbatimSymlinks: opts.verbatimSymlinks + } + +------------------ + +type GlobOptionsInternal filepathOrDirent = { cwd :: Nullable FilePath, exclude :: Nullable (filepathOrDirent -> Boolean), withFileTypes :: Boolean } + +type GlobFilePathOptions = { cwd :: Maybe FilePath, exclude :: Maybe (FilePath -> Boolean) } + +globFilePathOptionsDefault :: GlobFilePathOptions +globFilePathOptionsDefault = { cwd: Nothing, exclude: Nothing } + +globFilePathOptionsToInternal :: GlobFilePathOptions -> GlobOptionsInternal FilePath +globFilePathOptionsToInternal { cwd, exclude } = { cwd: toNullable cwd, exclude: toNullable exclude, withFileTypes: false } + +type GlobDirentOptions = { cwd :: Maybe FilePath, exclude :: Maybe (Dirent DirentNameString -> Boolean) } + +globDirentOptionsDefault :: GlobDirentOptions +globDirentOptionsDefault = { cwd: Nothing, exclude: Nothing } + +globDirentOptionsToInternal :: GlobDirentOptions -> GlobOptionsInternal (Dirent DirentNameString) +globDirentOptionsToInternal { cwd, exclude } = { cwd: toNullable cwd, exclude: toNullable exclude, withFileTypes: true } + +------------------ + +type OpendirOptionsInternal = { encoding :: EncodingString, bufferSize :: Int, recursive :: Boolean } +type OpendirOptions = { encoding :: Encoding, bufferSize :: Int, recursive :: Boolean } + +opendirOptionsDefault :: OpendirOptions +opendirOptionsDefault = { bufferSize: 32, recursive: false, encoding: UTF8 } + +opendirOptionsToInternal :: OpendirOptions -> OpendirOptionsInternal +opendirOptionsToInternal { encoding, bufferSize, recursive } = { encoding: encodingToNode encoding, bufferSize, recursive } diff --git a/src/Node/FS/Sync.js b/src/Node/FS/Sync.js index acddc23..20237a0 100644 --- a/src/Node/FS/Sync.js +++ b/src/Node/FS/Sync.js @@ -1,7 +1,7 @@ -export { - accessSync as accessImpl, - copyFileSync as copyFileImpl, - mkdtempSync as mkdtempImpl, +export { + accessSync as accessSyncImpl, + copyFileSync as copyFileSyncImpl, + mkdtempSync as mkdtempSyncImpl, renameSync as renameSyncImpl, truncateSync as truncateSyncImpl, chownSync as chownSyncImpl, @@ -26,5 +26,20 @@ export { readSync as readSyncImpl, writeSync as writeSyncImpl, fsyncSync as fsyncSyncImpl, - closeSync as closeSyncImpl + closeSync as closeSyncImpl, + cpSync as cpSyncImpl, + fchmodSync as fchmodSyncImpl, + fchownSync as fchownSyncImpl, + fdatasyncSync as fdatasyncSyncImpl, + fstatSync as fstatSyncImpl, + ftruncateSync as ftruncateSyncImpl, + futimesSync as futimesSyncImpl, + globSync as globSyncImpl, + lchmodSync as lchmodSyncImpl, + lchownSync as lchownSyncImpl, + lutimesSync as lutimesSyncImpl, + opendirSync as opendirSyncImpl, + readvSync as readvSyncImpl, + statfsSync as statfsSyncImpl, + writevSync as writevSyncImpl } from "node:fs"; diff --git a/src/Node/FS/Sync.purs b/src/Node/FS/Sync.purs index 25fe268..57be331 100644 --- a/src/Node/FS/Sync.purs +++ b/src/Node/FS/Sync.purs @@ -9,11 +9,12 @@ module Node.FS.Sync , truncate , chown , chmod - , stat , lstat + , stat , link , symlink , readlink + , readlinkBuffer , realpath , realpath' , unlink @@ -24,68 +25,89 @@ module Node.FS.Sync , mkdir , mkdir' , readdir + , readdir' + , readdirBuffer + , readdirBuffer' + , readdirDirent + , readdirDirent' + , readdirDirentBuffer + , readdirDirentBuffer' , utimes , readFile + , readFile' , readTextFile + , readTextFile' , writeFile + , writeFile' , writeTextFile + , writeTextFile' , appendFile + , appendFile' , appendTextFile , exists , fdOpen , fdRead + , fdRead' , fdNext , fdWrite + , fdWrite' + , fdWriteString , fdAppend - , fdFlush , fdClose + , cpFile + , cpFile' + , cpDir + , cpDir' + , fchmod + , fchown + , fdatasync + , fstat + , fsync + , ftruncate + , futimes + , glob + , glob' + , globDirent + , globDirent' + , lchmod + , lchown + , lutimes + -- , openAsBlob + , opendir + , opendir' + , readv + , statfs + -- , unwatchFile + -- , watch + -- , watchFile + , writev ) where import Prelude import Data.DateTime (DateTime) -import Data.DateTime.Instant (fromDateTime, unInstant) import Data.Either (blush) -import Data.Int (round) import Data.Maybe (Maybe(..)) import Data.Nullable (Nullable, toNullable) -import Data.Time.Duration (Milliseconds(..)) import Effect (Effect) import Effect.Exception (Error, try) -import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn5, runEffectFn1, runEffectFn2, runEffectFn3, runEffectFn5) +import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4, EffectFn5, runEffectFn1, runEffectFn2, runEffectFn3, runEffectFn4, runEffectFn5) import Node.Buffer (Buffer, size) import Node.Encoding (Encoding(..), encodingToNode) -import Node.FS (FileDescriptor, ByteCount, FilePosition, BufferLength, BufferOffset, FileMode, SymlinkType, symlinkTypeToNode) import Node.FS.Constants (AccessMode, CopyMode, FileFlags, defaultAccessMode, defaultCopyMode, fileFlagsToNode) -import Node.FS.Perms (Perms, permsToString, all, mkPerms) +import Node.FS.Dir (Dir) +import Node.FS.Dirent (Dirent, DirentNameBuffer, DirentNameString) +import Node.FS.Internal.Utils (datetimeToUnixEpochTimeInSeconds) +import Node.FS.Options (AppendFileBufferOptions, AppendFileOptionsInternal, AppendFileStringOptions, CpDirOptions, CpFileOptions, CpOptionsInternal, FdReadOptions, FdReadOptionsInternal, FdWriteOptions, FdWriteOptionsInternal, GlobDirentOptions, GlobFilePathOptions, GlobOptionsInternal, MkdirOptions, MkdirOptionsInternal, OpendirOptions, OpendirOptionsInternal, ReadFileBufferOptions, ReadFileOptionsInternal, ReadFileStringOptions, ReaddirBufferOptions, ReaddirDirentBufferOptions, ReaddirDirentOptions, ReaddirFilePathOptions, ReaddirOptionsInternal, RealpathOptions, RealpathOptionsInternal, RmOptions, RmdirOptions, WriteFileBufferOptions, WriteFileOptionsInternal, WriteFileStringOptions, appendFileBufferOptionsDefault, appendFileBufferOptionsToInternal, appendFileStringOptionsDefault, appendFileStringOptionsToInternal, cpDirOptionsDefault, cpDirOptionsToCpOptionsInternal, cpFileOptionsDefault, cpFileOptionsToCpOptionsInternal, fdReadOptionsToInternal, fdWriteOptionsToInternal, globDirentOptionsDefault, globDirentOptionsToInternal, globFilePathOptionsDefault, globFilePathOptionsToInternal, mkdirOptionsDefault, mkdirOptionsToInternal, opendirOptionsDefault, opendirOptionsToInternal, readFileBufferOptionsDefault, readFileBufferOptionsToInternal, readFileStringOptionsDefault, readFileStringOptionsToInternal, readdirBufferOptionsDefault, readdirBufferOptionsToInternal, readdirDirentBufferOptionsDefault, readdirDirentBufferOptionsToInternal, readdirDirentOptionsDefault, readdirDirentOptionsToInternal, readdirFilePathOptionsDefault, readdirFilePathOptionsToInternal, realpathOptionsDefault, realpathOptionsToInternal, rmOptionsDefault, rmdirOptionsDefault, writeFileBufferOptionsDefault, writeFileBufferOptionsToInternal, writeFileStringOptionsDefault, writeFileStringOptionsToInternal) +import Node.FS.Perms (Perms, permsToString) import Node.FS.Stats (Stats) +import Node.FS.Types (BufferLength, BufferOffset, ByteCount, FileDescriptor, FileMode, FilePosition, SymlinkType, EncodingString, symlinkTypeToNode) import Node.Path (FilePath) +import Unsafe.Coerce (unsafeCoerce) -access :: FilePath -> Effect (Maybe Error) -access = flip access' defaultAccessMode - -access' :: FilePath -> AccessMode -> Effect (Maybe Error) -access' path mode = do - map blush $ try $ runEffectFn2 accessImpl path mode - -foreign import accessImpl :: EffectFn2 FilePath AccessMode (Maybe Error) - -copyFile :: FilePath -> FilePath -> Effect Unit -copyFile src dest = runEffectFn3 copyFileImpl src dest defaultCopyMode - -copyFile' :: FilePath -> FilePath -> CopyMode -> Effect Unit -copyFile' src dest mode = runEffectFn3 copyFileImpl src dest mode - -foreign import copyFileImpl :: EffectFn3 FilePath FilePath CopyMode Unit - -mkdtemp :: String -> Effect String -mkdtemp prefix = mkdtemp' prefix UTF8 - -mkdtemp' :: String -> Encoding -> Effect String -mkdtemp' prefix encoding = runEffectFn2 mkdtempImpl prefix (encodingToNode encoding) - -foreign import mkdtempImpl :: EffectFn2 String String String - +foreign import accessSyncImpl :: EffectFn2 FilePath AccessMode Unit +foreign import copyFileSyncImpl :: EffectFn3 FilePath FilePath CopyMode Unit +foreign import mkdtempSyncImpl :: EffectFn2 FilePath FilePath FilePath foreign import renameSyncImpl :: EffectFn2 FilePath FilePath Unit foreign import truncateSyncImpl :: EffectFn2 FilePath Int Unit foreign import chownSyncImpl :: EffectFn3 FilePath Int Int Unit @@ -93,24 +115,75 @@ foreign import chmodSyncImpl :: EffectFn2 FilePath String Unit foreign import statSyncImpl :: EffectFn1 FilePath Stats foreign import lstatSyncImpl :: EffectFn1 FilePath Stats foreign import linkSyncImpl :: EffectFn2 FilePath FilePath Unit -foreign import symlinkSyncImpl :: EffectFn3 FilePath FilePath String Unit +foreign import symlinkSyncImpl :: EffectFn3 FilePath FilePath (Nullable String) Unit foreign import readlinkSyncImpl :: EffectFn1 FilePath FilePath -foreign import realpathSyncImpl :: forall cache. EffectFn2 FilePath { | cache } FilePath + +readlinkBufferSyncImpl :: EffectFn2 FilePath String Buffer +readlinkBufferSyncImpl = unsafeCoerce readlinkSyncImpl + +foreign import realpathSyncImpl :: EffectFn2 FilePath RealpathOptionsInternal FilePath foreign import unlinkSyncImpl :: EffectFn1 FilePath Unit -foreign import rmdirSyncImpl :: EffectFn2 FilePath { maxRetries :: Int, retryDelay :: Int } Unit -foreign import rmSyncImpl :: EffectFn2 FilePath { force :: Boolean, maxRetries :: Int, recursive :: Boolean, retryDelay :: Int } Unit -foreign import mkdirSyncImpl :: EffectFn2 FilePath { recursive :: Boolean, mode :: String } Unit -foreign import readdirSyncImpl :: EffectFn1 FilePath (Array FilePath) +foreign import rmdirSyncImpl :: EffectFn2 FilePath RmdirOptions Unit +foreign import rmSyncImpl :: EffectFn2 FilePath RmOptions Unit +foreign import mkdirSyncImpl :: EffectFn2 FilePath MkdirOptionsInternal Unit +foreign import readdirSyncImpl :: forall filepathOrDirentOrBuffer. EffectFn2 FilePath ReaddirOptionsInternal (Array filepathOrDirentOrBuffer) foreign import utimesSyncImpl :: EffectFn3 FilePath Int Int Unit -foreign import readFileSyncImpl :: forall a opts. EffectFn2 FilePath { | opts } a -foreign import writeFileSyncImpl :: forall a opts. EffectFn3 FilePath a { | opts } Unit -foreign import appendFileSyncImpl :: forall a opts. EffectFn3 FilePath a { | opts } Unit +foreign import readFileSyncImpl :: forall stringOrBuffer. EffectFn2 FilePath ReadFileOptionsInternal stringOrBuffer +foreign import writeFileSyncImpl :: forall stringOrBuffer. EffectFn3 FilePath stringOrBuffer WriteFileOptionsInternal Unit +foreign import appendFileSyncImpl :: forall stringOrBuffer. EffectFn3 FilePath stringOrBuffer AppendFileOptionsInternal Unit foreign import existsSyncImpl :: EffectFn1 FilePath Boolean foreign import openSyncImpl :: EffectFn3 FilePath String (Nullable FileMode) FileDescriptor foreign import readSyncImpl :: EffectFn5 FileDescriptor Buffer BufferOffset BufferLength (Nullable FilePosition) ByteCount + +-- https://nodejs.org/docs/latest/api/fs.html#fsreadfd-options-callback +readWithOptionsSyncImpl :: EffectFn2 FileDescriptor FdReadOptionsInternal ByteCount +readWithOptionsSyncImpl = unsafeCoerce readSyncImpl + foreign import writeSyncImpl :: EffectFn5 FileDescriptor Buffer BufferOffset BufferLength (Nullable FilePosition) ByteCount -foreign import fsyncSyncImpl :: EffectFn1 FileDescriptor Unit + +-- https://nodejs.org/docs/latest/api/fs.html#fsreadfd-options-callback +writeBufferWithOptionsSyncImpl :: EffectFn3 FileDescriptor Buffer FdWriteOptionsInternal ByteCount +writeBufferWithOptionsSyncImpl = unsafeCoerce writeSyncImpl + +-- https://nodejs.org/docs/latest/api/fs.html#fsreadfd-options-callback +writeStringSyncImpl :: EffectFn4 FileDescriptor String (Nullable FilePosition) EncodingString ByteCount +writeStringSyncImpl = unsafeCoerce writeSyncImpl + foreign import closeSyncImpl :: EffectFn1 FileDescriptor Unit +foreign import cpSyncImpl :: EffectFn3 FilePath FilePath CpOptionsInternal Unit +foreign import fchmodSyncImpl :: EffectFn2 FileDescriptor String Unit +foreign import fchownSyncImpl :: EffectFn3 FileDescriptor Int Int Unit +foreign import fdatasyncSyncImpl :: EffectFn1 FileDescriptor Unit +foreign import fstatSyncImpl :: EffectFn1 FileDescriptor Stats +foreign import fsyncSyncImpl :: EffectFn1 FileDescriptor Unit +foreign import ftruncateSyncImpl :: EffectFn2 FileDescriptor Int Unit +foreign import futimesSyncImpl :: EffectFn3 FileDescriptor Int Int Unit +foreign import globSyncImpl :: forall filepathOrDirent. EffectFn2 (Array FilePath) (GlobOptionsInternal filepathOrDirent) (Array filepathOrDirent) +foreign import lchmodSyncImpl :: EffectFn2 FilePath String Unit +foreign import lchownSyncImpl :: EffectFn3 FilePath Int Int Unit +foreign import lutimesSyncImpl :: EffectFn3 FilePath Int Int Unit +foreign import opendirSyncImpl :: EffectFn2 FilePath OpendirOptionsInternal Dir +foreign import readvSyncImpl :: EffectFn3 FileDescriptor (Array Buffer) (Nullable FilePosition) ByteCount +foreign import statfsSyncImpl :: EffectFn1 FilePath Stats +foreign import writevSyncImpl :: EffectFn3 FileDescriptor (Array Buffer) (Nullable FilePosition) ByteCount + +access :: FilePath -> Effect (Maybe Error) +access = flip access' defaultAccessMode + +access' :: FilePath -> AccessMode -> Effect (Maybe Error) +access' path mode = map blush $ try $ runEffectFn2 accessSyncImpl path mode + +copyFile :: FilePath -> FilePath -> Effect Unit +copyFile src dest = runEffectFn3 copyFileSyncImpl src dest defaultCopyMode + +copyFile' :: FilePath -> FilePath -> CopyMode -> Effect Unit +copyFile' src dest mode = runEffectFn3 copyFileSyncImpl src dest mode + +mkdtemp :: String -> Effect String +mkdtemp prefix = mkdtemp' prefix UTF8 + +mkdtemp' :: String -> Encoding -> Effect String +mkdtemp' prefix encoding = runEffectFn2 mkdtempSyncImpl prefix (encodingToNode encoding) -- | Renames a file. rename :: FilePath -> FilePath -> Effect Unit @@ -173,20 +246,22 @@ readlink -> Effect FilePath readlink path = runEffectFn1 readlinkSyncImpl path +readlinkBuffer :: FilePath -> Effect Buffer +readlinkBuffer path = runEffectFn2 readlinkBufferSyncImpl path "buffer" + -- | Find the canonicalized absolute location for a path. realpath :: FilePath -> Effect FilePath -realpath path = runEffectFn2 realpathSyncImpl path {} +realpath path = realpath' path realpathOptionsDefault -- | Find the canonicalized absolute location for a path using a cache object for -- | already resolved paths. realpath' - :: forall cache - . FilePath - -> { | cache } + :: FilePath + -> RealpathOptions -> Effect FilePath -realpath' path cache = runEffectFn2 realpathSyncImpl path cache +realpath' path options = runEffectFn2 realpathSyncImpl path (realpathOptionsToInternal options) -- | Deletes a file. unlink @@ -198,12 +273,12 @@ unlink file = runEffectFn1 unlinkSyncImpl file rmdir :: FilePath -> Effect Unit -rmdir path = rmdir' path { maxRetries: 0, retryDelay: 100 } +rmdir path = rmdir' path rmdirOptionsDefault -- | Deletes a directory with options. rmdir' :: FilePath - -> { maxRetries :: Int, retryDelay :: Int } + -> RmdirOptions -> Effect Unit rmdir' path opts = runEffectFn2 rmdirSyncImpl path opts @@ -211,12 +286,12 @@ rmdir' path opts = runEffectFn2 rmdirSyncImpl path opts rm :: FilePath -> Effect Unit -rm path = rm' path { force: false, maxRetries: 100, recursive: false, retryDelay: 1000 } +rm path = rm' path rmOptionsDefault -- | Deletes a file or directory with options. rm' :: FilePath - -> { force :: Boolean, maxRetries :: Int, recursive :: Boolean, retryDelay :: Int } + -> RmOptions -> Effect Unit rm' path opts = runEffectFn2 rmSyncImpl path opts @@ -224,20 +299,66 @@ rm' path opts = runEffectFn2 rmSyncImpl path opts mkdir :: FilePath -> Effect Unit -mkdir path = mkdir' path { recursive: false, mode: mkPerms all all all } +mkdir path = mkdir' path mkdirOptionsDefault -- | Makes a new directory with the specified permissions. mkdir' :: FilePath - -> { recursive :: Boolean, mode :: Perms } + -> MkdirOptions -> Effect Unit -mkdir' file { recursive, mode: perms } = runEffectFn2 mkdirSyncImpl file { recursive, mode: permsToString perms } +mkdir' file opts = runEffectFn2 mkdirSyncImpl file (mkdirOptionsToInternal opts) -- | Reads the contents of a directory. readdir :: FilePath -> Effect (Array FilePath) -readdir file = runEffectFn1 readdirSyncImpl file +readdir file = readdir' file readdirFilePathOptionsDefault + +-- | Reads the contents of a directory. +readdir' + :: FilePath + -> ReaddirFilePathOptions + -> Effect (Array FilePath) +readdir' file options = runEffectFn2 readdirSyncImpl file (readdirFilePathOptionsToInternal options) + +-- | Reads the contents of a directory. +readdirBuffer + :: FilePath + -> Effect (Array Buffer) +readdirBuffer file = readdirBuffer' file readdirBufferOptionsDefault + +-- | Reads the contents of a directory. +readdirBuffer' + :: FilePath + -> ReaddirBufferOptions + -> Effect (Array Buffer) +readdirBuffer' file options = runEffectFn2 readdirSyncImpl file (readdirBufferOptionsToInternal options) + +-- | Reads the contents of a directory. +readdirDirent + :: FilePath + -> Effect (Array (Dirent DirentNameString)) +readdirDirent file = readdirDirent' file readdirDirentOptionsDefault + +-- | Reads the contents of a directory. +readdirDirent' + :: FilePath + -> ReaddirDirentOptions + -> Effect (Array (Dirent DirentNameString)) +readdirDirent' file options = runEffectFn2 readdirSyncImpl file (readdirDirentOptionsToInternal options) + +-- | Reads the contents of a directory. +readdirDirentBuffer + :: FilePath + -> Effect (Array (Dirent DirentNameBuffer)) +readdirDirentBuffer file = readdirDirentBuffer' file readdirDirentBufferOptionsDefault + +-- | Reads the contents of a directory. +readdirDirentBuffer' + :: FilePath + -> ReaddirDirentBufferOptions + -> Effect (Array (Dirent DirentNameBuffer)) +readdirDirentBuffer' file options = runEffectFn2 readdirSyncImpl file (readdirDirentBufferOptionsToInternal options) -- | Sets the accessed and modified times for the specified file. utimes @@ -245,31 +366,32 @@ utimes -> DateTime -> DateTime -> Effect Unit -utimes file atime mtime = runEffectFn3 utimesSyncImpl file (fromDate atime) (fromDate mtime) - where - fromDate date = ms (toEpochMilliseconds date) / 1000 - ms (Milliseconds n) = round n - toEpochMilliseconds = unInstant <<< fromDateTime - --- | Reads the entire contents of a file returning the result as a raw buffer. -readFile - :: FilePath - -> Effect Buffer -readFile file = runEffectFn2 readFileSyncImpl file {} +utimes file atime mtime = runEffectFn3 utimesSyncImpl file (datetimeToUnixEpochTimeInSeconds atime) (datetimeToUnixEpochTimeInSeconds mtime) -- | Reads the entire contents of a text file with the specified encoding. readTextFile :: Encoding -> FilePath -> Effect String -readTextFile encoding file = runEffectFn2 readFileSyncImpl file { encoding: show encoding } +readTextFile encoding file = readTextFile' file (readFileStringOptionsDefault { encoding = encoding }) --- | Writes a buffer to a file. -writeFile +readTextFile' :: FilePath - -> Buffer - -> Effect Unit -writeFile file buff = runEffectFn3 writeFileSyncImpl file buff {} + -> ReadFileStringOptions + -> Effect String +readTextFile' file options = runEffectFn2 readFileSyncImpl file (readFileStringOptionsToInternal options) + +-- | Reads the entire contents of a file returning the result as a raw buffer. +readFile + :: FilePath + -> Effect Buffer +readFile file = readFile' file readFileBufferOptionsDefault + +readFile' + :: FilePath + -> ReadFileBufferOptions + -> Effect Buffer +readFile' file options = runEffectFn2 readFileSyncImpl file (readFileBufferOptionsToInternal options) -- | Writes text to a file using the specified encoding. writeTextFile @@ -277,14 +399,28 @@ writeTextFile -> FilePath -> String -> Effect Unit -writeTextFile encoding file text = runEffectFn3 writeFileSyncImpl file text { encoding: show encoding } +writeTextFile encoding file buff = writeTextFile' file buff (writeFileStringOptionsDefault { encoding = encoding }) --- | Appends the contents of a buffer to a file. -appendFile +writeTextFile' + :: FilePath + -> String + -> WriteFileStringOptions + -> Effect Unit +writeTextFile' file buff options = runEffectFn3 writeFileSyncImpl file buff (writeFileStringOptionsToInternal options) + +-- | Writes a buffer to a file. +writeFile :: FilePath -> Buffer -> Effect Unit -appendFile file buff = runEffectFn3 appendFileSyncImpl file buff {} +writeFile file buff = writeFile' file buff writeFileBufferOptionsDefault + +writeFile' + :: FilePath + -> Buffer + -> WriteFileBufferOptions + -> Effect Unit +writeFile' file buff options = runEffectFn3 writeFileSyncImpl file buff (writeFileBufferOptionsToInternal options) -- | Appends text to a file using the specified encoding. appendTextFile @@ -292,12 +428,31 @@ appendTextFile -> FilePath -> String -> Effect Unit -appendTextFile encoding file buff = runEffectFn3 appendFileSyncImpl file buff { encoding: show encoding } +appendTextFile encoding file buff = appendTextFile' file buff (appendFileStringOptionsDefault { encoding = encoding }) --- | Check if the path exists. -exists +appendTextFile' + :: FilePath + -> String + -> AppendFileStringOptions + -> Effect Unit +appendTextFile' file buff options = runEffectFn3 appendFileSyncImpl file buff (appendFileStringOptionsToInternal options) + +-- | Appends a buffer to a file. +appendFile :: FilePath - -> Effect Boolean + -> Buffer + -> Effect Unit +appendFile file buff = appendFile' file buff appendFileBufferOptionsDefault + +appendFile' + :: FilePath + -> Buffer + -> AppendFileBufferOptions + -> Effect Unit +appendFile' file buff options = runEffectFn3 appendFileSyncImpl file buff (appendFileBufferOptionsToInternal options) + +-- | Check if the path exists. +exists :: FilePath -> Effect Boolean exists file = runEffectFn1 existsSyncImpl file -- | Open a file synchronously. See the [Node documentation](http://nodejs.org/api/fs.html#fs_fs_opensync_path_flags_mode) @@ -321,6 +476,14 @@ fdRead fdRead fd buff off len pos = runEffectFn5 readSyncImpl fd buff off len (toNullable pos) +-- | Read from a file synchronously. See the [Node Documentation](https://nodejs.org/docs/latest/api/fs.html#fsreadsyncfd-buffer-offset-length-position) +-- | for details. +fdRead' + :: FileDescriptor + -> FdReadOptions + -> Effect ByteCount +fdRead' fd options = runEffectFn2 readWithOptionsSyncImpl fd (fdReadOptionsToInternal options) + -- | Convenience function to fill the whole buffer from the current -- | file position. fdNext @@ -343,6 +506,24 @@ fdWrite fdWrite fd buff off len pos = runEffectFn5 writeSyncImpl fd buff off len (toNullable pos) +-- | Write from a file synchronously. See the [Node Documentation](https://nodejs.org/docs/latest/api/fs.html#fswritefd-options-callback) +-- | for details. +fdWrite' + :: FileDescriptor + -> Buffer + -> FdWriteOptions + -> Effect ByteCount +fdWrite' fd buffer options = runEffectFn3 writeBufferWithOptionsSyncImpl fd buffer (fdWriteOptionsToInternal options) + +-- It is unsafe to use fs.write() multiple times on the same file without waiting for the callback. For this scenario, fs.createWriteStream() is recommended. +fdWriteString + :: FileDescriptor + -> String + -> Maybe FilePosition + -> Encoding + -> Effect ByteCount +fdWriteString fd string pos encoding = runEffectFn4 writeStringSyncImpl fd string (toNullable pos) (encodingToNode encoding) + -- | Convenience function to append the whole buffer to the current -- | file position. fdAppend @@ -353,16 +534,125 @@ fdAppend fd buff = do sz <- size buff fdWrite fd buff 0 sz Nothing +-- | Close a file synchronously. See the [Node documentation](http://nodejs.org/api/fs.html#fs_fs_closesync_fd) +-- | for details. +fdClose :: FileDescriptor -> Effect Unit +fdClose fd = runEffectFn1 closeSyncImpl fd + +-- | Copy a file synchronously using a `cp` command. +-- | See the [Node Documentation](https://nodejs.org/api/fs.html#fscpsyncsrc-dest-options) +-- | for details. +cpFile :: FilePath -> FilePath -> Effect Unit +cpFile src dest = cpFile' src dest cpFileOptionsDefault + +cpFile' :: FilePath -> FilePath -> CpFileOptions -> Effect Unit +cpFile' src dest opts = runEffectFn3 cpSyncImpl src dest (cpFileOptionsToCpOptionsInternal opts) + +-- | Copy a directory synchronously using a `cp` command with option `recursive = true`. +-- | See the [Node Documentation](https://nodejs.org/api/fs.html#fscpsyncsrc-dest-options) +-- | for details. +cpDir :: FilePath -> FilePath -> Effect Unit +cpDir src dest = cpDir' src dest cpDirOptionsDefault + +cpDir' :: FilePath -> FilePath -> CpDirOptions -> Effect Unit +cpDir' src dest opts = runEffectFn3 cpSyncImpl src dest (cpDirOptionsToCpOptionsInternal opts) + +-- | Change permissions on a file descriptor. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fchmod_fd_mode_callback) +-- | for details. +fchmod :: FileDescriptor -> Perms -> Effect Unit +fchmod fd perms = runEffectFn2 fchmodSyncImpl fd (permsToString perms) + +-- | Change ownership of a file descriptor. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fchown_fd_uid_gid_callback) +-- | for details. +fchown :: FileDescriptor -> Int -> Int -> Effect Unit +fchown fd uid gid = runEffectFn3 fchownSyncImpl fd uid gid + +-- | Synchronize a file's in-core state with storage. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fdatasync_fd_callback) +-- | for details. +fdatasync :: FileDescriptor -> Effect Unit +fdatasync fd = runEffectFn1 fdatasyncSyncImpl fd + +-- | Get file status information. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_fstat_fd_callback) +-- | for details. +fstat :: FileDescriptor -> Effect Stats +fstat fd = runEffectFn1 fstatSyncImpl fd + -- | Flush a file synchronously. See the [Node documentation](http://nodejs.org/api/fs.html#fs_fs_fsyncsync_fd) -- | for details. -fdFlush - :: FileDescriptor - -> Effect Unit -fdFlush fd = runEffectFn1 fsyncSyncImpl fd +fsync :: FileDescriptor -> Effect Unit +fsync fd = runEffectFn1 fsyncSyncImpl fd --- | Close a file synchronously. See the [Node documentation](http://nodejs.org/api/fs.html#fs_fs_closesync_fd) +-- | Truncate a file to a specified length. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_ftruncate_fd_len_callback) -- | for details. -fdClose - :: FileDescriptor +ftruncate :: FileDescriptor -> Int -> Effect Unit +ftruncate fd len = runEffectFn2 ftruncateSyncImpl fd len + +-- | Change file timestamps for a file descriptor. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_futimes_fd_atime_mtime_callback) +-- | for details. +futimes :: FileDescriptor -> DateTime -> DateTime -> Effect Unit +futimes fd atime mtime = runEffectFn3 futimesSyncImpl fd (datetimeToUnixEpochTimeInSeconds atime) (datetimeToUnixEpochTimeInSeconds mtime) + +-- | Perform pattern matching in file paths. See the [Node Documentation](https://nodejs.org/api/glob.html#globglob_pattern_options_callback) +-- | for details. +glob :: Array FilePath -> Effect (Array FilePath) +glob pattern = glob' pattern globFilePathOptionsDefault + +glob' :: Array FilePath -> GlobFilePathOptions -> Effect (Array FilePath) +glob' pattern options = runEffectFn2 globSyncImpl pattern (globFilePathOptionsToInternal options) + +globDirent :: Array FilePath -> Effect (Array (Dirent DirentNameString)) +globDirent pattern = globDirent' pattern globDirentOptionsDefault + +globDirent' :: Array FilePath -> GlobDirentOptions -> Effect (Array (Dirent DirentNameString)) +globDirent' pattern options = runEffectFn2 globSyncImpl pattern (globDirentOptionsToInternal options) + +-- | Change permissions on a symbolic link. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_lchmod_path_mode_callback) +-- | for details. +lchmod :: FilePath -> Perms -> Effect Unit +lchmod path perms = runEffectFn2 lchmodSyncImpl path (permsToString perms) + +-- | Change ownership of a symbolic link. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_lchown_path_uid_gid_callback) +-- | for details. +lchown :: FilePath -> Int -> Int -> Effect Unit +lchown path uid gid = runEffectFn3 lchownSyncImpl path uid gid + +-- | Change timestamps for a symbolic link. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_lutimes_path_atime_mtime_callback) +-- | for details. +lutimes + :: FilePath + -> DateTime + -> DateTime -> Effect Unit -fdClose fd = runEffectFn1 closeSyncImpl fd +lutimes file atime mtime = runEffectFn3 lutimesSyncImpl file (datetimeToUnixEpochTimeInSeconds atime) (datetimeToUnixEpochTimeInSeconds mtime) + +-- | TODO: path - Buffer Url, returns Promise +-- | Open a file as a blob. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_class_filehandle) +-- | for details. +-- openAsBlob :: FilePath -> Promise Blob -> Effect Unit +-- openAsBlob path = runEffectFn2 openAsBlobSyncImpl path + +-- | Open a directory. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_opendir_path_options_callback) +-- | for details. +opendir' :: FilePath -> OpendirOptions -> Effect Dir +opendir' path options = runEffectFn2 opendirSyncImpl path (opendirOptionsToInternal options) + +-- | Open a directory. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_opendir_path_options_callback) +-- | for details. +-- | NOTE: encoding: 'buffer' is not supported, will throw error "TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received an instance of Buffer" +opendir :: FilePath -> Effect Dir +opendir path = opendir' path opendirOptionsDefault + +-- | Read from a file descriptor into a buffer array. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_readv_fd_buffers_position_callback) +-- | for details. +readv :: FileDescriptor -> Array Buffer -> Maybe FilePosition -> Effect ByteCount +readv fd buffers position = runEffectFn3 readvSyncImpl fd buffers (toNullable position) + +-- | Get file system statistics. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_statfs_path_callback) +-- | for details. +statfs :: FilePath -> Effect Stats +statfs = runEffectFn1 statfsSyncImpl + +-- | Write from an array of buffers to a file descriptor. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_writev_fd_buffers_position_callback) +-- | for details. +writev :: FileDescriptor -> Array Buffer -> Maybe FilePosition -> Effect ByteCount +writev fd buffers position = runEffectFn3 writevSyncImpl fd buffers (toNullable position) diff --git a/src/Node/FS/Types.purs b/src/Node/FS/Types.purs new file mode 100644 index 0000000..f035804 --- /dev/null +++ b/src/Node/FS/Types.purs @@ -0,0 +1,63 @@ +module Node.FS.Types where + +import Prelude + +import Data.Nullable (Nullable) +import Data.Nullable as Nullable + +foreign import data FileDescriptor :: Type + +type FileMode = Int +type FilePosition = Int +type BufferLength = Int +type BufferOffset = Int +type ByteCount = Int +type EncodingString = String + +-- newtype FileMode = FileMode Int +-- newtype FilePosition = FilePosition Int +-- newtype BufferLength = BufferLength Int +-- newtype BufferOffset = BufferOffset Int +-- newtype ByteCount = ByteCount Int +-- -- newtype EncodingString = EncodingString String +-- +-- derive newtype instance Eq FileMode +-- derive newtype instance Eq FilePosition +-- derive newtype instance Eq BufferLength +-- derive newtype instance Eq BufferOffset +-- derive newtype instance Eq ByteCount +-- -- derive newtype instance Eq EncodingString +-- +-- derive newtype instance Show FileMode +-- derive newtype instance Show FilePosition +-- derive newtype instance Show BufferLength +-- derive newtype instance Show BufferOffset +-- derive newtype instance Show ByteCount +-- -- derive newtype instance Show EncodingString +-- +-- derive newtype instance Ord FileMode +-- derive newtype instance Ord FilePosition +-- derive newtype instance Ord BufferLength +-- derive newtype instance Ord BufferOffset +-- derive newtype instance Ord ByteCount +-- -- derive newtype instance Ord EncodingString + +-- | Symlink varieties. +data SymlinkType = FileLink | DirLink | JunctionLink | AutodetectLink + +-- | Convert a `SymlinkType` to a `String` in the format expected by the +-- | Node.js filesystem API. +symlinkTypeToNode :: SymlinkType -> Nullable String +symlinkTypeToNode ty = case ty of + FileLink -> Nullable.notNull "file" + DirLink -> Nullable.notNull "dir" + JunctionLink -> Nullable.notNull "junction" + AutodetectLink -> Nullable.null + +instance Show SymlinkType where + show FileLink = "FileLink" + show DirLink = "DirLink" + show JunctionLink = "JunctionLink" + show AutodetectLink = "AutodetectLink" + +derive instance Eq SymlinkType diff --git a/test/Main.purs b/test/Main.purs deleted file mode 100644 index 95f1cea..0000000 --- a/test/Main.purs +++ /dev/null @@ -1,16 +0,0 @@ -module Test.Main where - -import Prelude - -import Effect (Effect) -import Test as Test -import Test.Streams as Streams -import TestAff as TestAff -import TestAsync as TestAsync - -main :: Effect Unit -main = do - Test.main - TestAsync.main - Streams.main - TestAff.main diff --git a/test/Test/Main.purs b/test/Test/Main.purs new file mode 100644 index 0000000..a45b14a --- /dev/null +++ b/test/Test/Main.purs @@ -0,0 +1,18 @@ +module Test.Main where + +import Prelude + +import Effect (Effect) +import Test.Node.FS.Sync as Sync +import Test.Node.FS.Streams as Streams +import Test.Node.FS.Aff as Aff +import Test.Node.FS.Async as Async +import Test.Node.FS.OpendirAndDir as OpendirAndDir + +main :: Effect Unit +main = do + Sync.main + Async.main + Streams.main + Aff.main + OpendirAndDir.main diff --git a/test/TestAff.purs b/test/Test/Node/FS/Aff.purs similarity index 94% rename from test/TestAff.purs rename to test/Test/Node/FS/Aff.purs index 4feea61..44bd244 100644 --- a/test/TestAff.purs +++ b/test/Test/Node/FS/Aff.purs @@ -1,4 +1,4 @@ -module TestAff where +module Test.Node.FS.Aff where import Prelude diff --git a/test/TestAsync.purs b/test/Test/Node/FS/Async.purs similarity index 90% rename from test/TestAsync.purs rename to test/Test/Node/FS/Async.purs index d44f790..a1b1d7d 100644 --- a/test/TestAsync.purs +++ b/test/Test/Node/FS/Async.purs @@ -1,4 +1,4 @@ -module TestAsync where +module Test.Node.FS.Async where import Prelude (Unit, show, bind, discard, (<>), ($)) import Data.Either (Either(..)) @@ -15,8 +15,8 @@ import Node.Buffer as B main :: Effect Unit main = do let - path1 = FP.concat ([ "test", "TestAsync.purs" ]) - path2 = FP.concat ([ "test", "TestAsync.purs.partial" ]) + path1 = FP.concat ([ "test", "Test", "Node", "FS", "Async.purs" ]) + path2 = FP.concat ([ "test", "Test", "Node", "FS", "Async.purs.partial" ]) buf <- B.create 1000 diff --git a/test/Test/Node/FS/OpendirAndDir.purs b/test/Test/Node/FS/OpendirAndDir.purs new file mode 100644 index 0000000..1d7a865 --- /dev/null +++ b/test/Test/Node/FS/OpendirAndDir.purs @@ -0,0 +1,116 @@ +module Test.Node.FS.OpendirAndDir where + +import Prelude + +import Data.Either (Either) +import Data.Maybe (Maybe) +import Data.String as String +import Effect (Effect) +import Effect.Aff (Aff, launchAff_, try) +import Effect.Class (liftEffect) +import Effect.Console (log) +import Effect.Exception (Error) +import Node.Encoding (Encoding(..)) +import Node.FS.Options (opendirOptionsDefault, rmOptionsDefault) +import Node.FS.Aff as A +import Node.FS.Dir.Aff (close, entries, read) +import Node.FS.Dirent (Dirent, DirentNameString) +import Node.FS.Perms (permsAll) +import Node.Path (FilePath) +import Node.Path as Path +import Test.Assert (assertEqual) + +outerTmpDir :: FilePath +outerTmpDir = Path.concat [ "tmp", "dir-entries-test" ] + +prepare :: Aff Unit +prepare = do + A.rm' outerTmpDir (rmOptionsDefault { recursive = true, force = true }) + A.mkdir' outerTmpDir { recursive: true, mode: permsAll } + A.writeTextFile UTF8 (Path.concat [ outerTmpDir, "1.txt" ]) "1" + A.writeTextFile UTF8 (Path.concat [ outerTmpDir, "2.txt" ]) "2" + A.mkdir $ Path.concat [ outerTmpDir, "dir1" ] + A.writeTextFile UTF8 (Path.concat [ outerTmpDir, "dir1", "3.txt" ]) "3" + A.writeTextFile UTF8 (Path.concat [ outerTmpDir, "dir1", "4.txt" ]) "4" + +test1 :: Aff Unit +test1 = do + dir <- A.opendir' outerTmpDir (opendirOptionsDefault { recursive = true }) + liftEffect $ assertEqual + { actual: show dir + , expected: "Dir {}" + } + files' <- entries dir + liftEffect $ assertEqual + { actual: show files' + , expected: + """[Dirent { + name: 'dir1', + parentPath: 'tmp/dir-entries-test', + path: 'tmp/dir-entries-test', + [Symbol(type)]: 2 +},Dirent { + name: '1.txt', + parentPath: 'tmp/dir-entries-test', + path: 'tmp/dir-entries-test', + [Symbol(type)]: 1 +},Dirent { + name: '2.txt', + parentPath: 'tmp/dir-entries-test', + path: 'tmp/dir-entries-test', + [Symbol(type)]: 1 +},Dirent { + name: '3.txt', + parentPath: 'tmp/dir-entries-test/dir1', + path: 'tmp/dir-entries-test/dir1', + [Symbol(type)]: 1 +},Dirent { + name: '4.txt', + parentPath: 'tmp/dir-entries-test/dir1', + path: 'tmp/dir-entries-test/dir1', + [Symbol(type)]: 1 +}]""" + } + liftEffect $ log $ show files' + try (entries dir) >>= \(eitherFile :: Either Error (Array (Dirent DirentNameString))) -> liftEffect $ assertEqual + { actual: String.take 74 $ show eitherFile + , expected: "(Left Error [ERR_DIR_CLOSED]: Directory handle was closed\n at #readImpl" + } + +test2 :: Aff Unit +test2 = do + dir <- A.opendir' outerTmpDir (opendirOptionsDefault { recursive = false }) + read dir >>= \file -> liftEffect $ assertEqual + { actual: show file + , expected: "(Just Dirent {\n name: 'dir1',\n parentPath: 'tmp/dir-entries-test',\n path: 'tmp/dir-entries-test',\n [Symbol(type)]: 2\n})" + } + read dir >>= \file -> liftEffect $ assertEqual + { actual: show file + , expected: "(Just Dirent {\n name: '1.txt',\n parentPath: 'tmp/dir-entries-test',\n path: 'tmp/dir-entries-test',\n [Symbol(type)]: 1\n})" + } + read dir >>= \file -> liftEffect $ assertEqual + { actual: show file + , expected: "(Just Dirent {\n name: '2.txt',\n parentPath: 'tmp/dir-entries-test',\n path: 'tmp/dir-entries-test',\n [Symbol(type)]: 1\n})" + } + read dir >>= \file -> liftEffect $ assertEqual + { actual: show file + , expected: "Nothing" + } + close dir >>= \file -> liftEffect $ assertEqual + { actual: show file + , expected: "unit" + } + try (close dir) >>= \(error :: Either Error Unit) -> liftEffect $ assertEqual + { actual: String.take 74 $ show error + , expected: "(Left Error [ERR_DIR_CLOSED]: Directory handle was closed\n at Dir.close" + } + try (read dir) >>= \(eitherFile :: Either Error (Maybe (Dirent DirentNameString))) -> liftEffect $ assertEqual + { actual: String.take 74 $ show eitherFile + , expected: "(Left Error [ERR_DIR_CLOSED]: Directory handle was closed\n at #readImpl" + } + +main :: Effect Unit +main = launchAff_ do + prepare + test1 + test2 diff --git a/test/Streams.purs b/test/Test/Node/FS/Streams.purs similarity index 74% rename from test/Streams.purs rename to test/Test/Node/FS/Streams.purs index eef4029..bce9971 100644 --- a/test/Streams.purs +++ b/test/Test/Node/FS/Streams.purs @@ -1,4 +1,4 @@ -module Test.Streams where +module Test.Node.FS.Streams where import Prelude @@ -17,13 +17,13 @@ main = do log "Testing streams" - r <- createReadStream (fp [ "test", "Streams.purs" ]) + r <- createReadStream (fp [ "test", "Test", "Node", "FS", "Streams.purs" ]) w <- createWriteStream (fp [ "tmp", "Streams.purs" ]) _ <- Stream.pipe r w r # on_ Stream.endH do - src <- Sync.readTextFile UTF8 (fp [ "test", "Streams.purs" ]) + src <- Sync.readTextFile UTF8 (fp [ "test", "Test", "Node", "FS", "Streams.purs" ]) dst <- Sync.readTextFile UTF8 (fp [ "tmp", "Streams.purs" ]) if src == dst then log "all good" diff --git a/test/Test.purs b/test/Test/Node/FS/Sync.purs similarity index 76% rename from test/Test.purs rename to test/Test/Node/FS/Sync.purs index 16a591a..7877b7f 100644 --- a/test/Test.purs +++ b/test/Test/Node/FS/Sync.purs @@ -1,4 +1,4 @@ -module Test where +module Test.Node.FS.Sync where import Prelude @@ -8,17 +8,20 @@ import Data.Traversable (for_, traverse) import Effect (Effect) import Effect.Console (log) import Effect.Exception (Error, catchException, error, message, throw, throwException, try) +import Effect.Exception as Error import Node.Buffer as Buffer import Node.Encoding (Encoding(..)) import Node.FS (FileFlags(..), SymlinkType(..)) import Node.FS.Async as A import Node.FS.Constants (copyFile_EXCL, r_OK, w_OK) +import Node.FS.Options (rmOptionsDefault) import Node.FS.Perms (mkPerms, permsAll) import Node.FS.Perms as Perms import Node.FS.Stats (statusChangedTime, accessedTime, modifiedTime, isSymbolicLink, isSocket, isFIFO, isCharacterDevice, isBlockDevice, isDirectory, isFile) import Node.FS.Sync (chmod) import Node.FS.Sync as S import Node.Path as Path +import Test.Assert (assertEqual') import Unsafe.Coerce (unsafeCoerce) -- Cheat to allow `main` to type check. See also issue #5 in @@ -34,10 +37,10 @@ main :: Effect Unit main = do let fp = Path.concat - e <- S.exists (fp [ "test", "Test.purs" ]) + e <- S.exists (fp [ "test", "Test", "Node", "FS", "Sync.purs" ]) log $ "Test.purs exists? " <> show e - file <- S.readTextFile UTF8 (fp [ "test", "Test.purs" ]) + file <- S.readTextFile UTF8 (fp [ "test", "Test", "Node", "FS", "Sync.purs" ]) log "\n\nreadTextFile sync result:" log $ file @@ -80,6 +83,7 @@ main = do log "statusChangedTime:" log $ show $ statusChangedTime stats + S.rm' (fp [ "tmp", "TestSymlink.js" ]) (rmOptionsDefault { force = true }) S.symlink (fp [ "tmp", "Test1.js" ]) (fp [ "tmp", "TestSymlink.js" ]) FileLink lstats <- S.lstat (fp [ "tmp", "TestSymlink.js" ]) @@ -97,16 +101,16 @@ main = do log "\n\ntruncate result:" either (log <<< show) (log <<< show) y - A.readFile (fp [ "test", "Test.purs" ]) $ \mbuf -> do + A.readFile (fp [ "test", "Test", "Node", "FS", "Sync.purs" ]) $ \mbuf -> do buf <- traverse Buffer.freeze mbuf log "\n\nreadFile result:" either (log <<< show) (log <<< show) buf - A.readTextFile UTF8 (fp [ "test", "Test.purs" ]) $ \x -> do + A.readTextFile UTF8 (fp [ "test", "Test", "Node", "FS", "Sync.purs" ]) $ \x -> do log "\n\nreadTextFile result:" either (log <<< show) log x - A.stat (fp [ "test", "Test.purs" ]) $ \x -> do + A.stat (fp [ "test", "Test", "Node", "FS", "Sync.purs" ]) $ \x -> do log "\n\nA.stat:" case x of Left err -> log $ "Error:" <> show err @@ -150,12 +154,12 @@ main = do let fdFile = fp [ "tmp", "FD.json" ] fd0 <- S.fdOpen fdFile W (Just 420) buf0 <- Buffer.fromString "[ 42 ]" UTF8 - bytes0 <- S.fdAppend fd0 buf0 - S.fdFlush fd0 + _bytes0 <- S.fdAppend fd0 buf0 + S.fsync fd0 S.fdClose fd0 fd1 <- S.fdOpen fdFile R Nothing buf1 <- Buffer.create =<< Buffer.size buf0 - bytes1 <- S.fdNext fd1 buf1 + _bytes2 <- S.fdNext fd1 buf1 S.fdClose fd1 log "statSync on a non-existing file should be catchable" @@ -182,7 +186,7 @@ main = do Nothing -> throw $ "`access \"" <> readableFixturePath <> "\" W_OK` should produce error" log "copy tests" - let outerTmpDir = "./test/node-fs-tests" + let outerTmpDir = "./tmp/test/node-fs-tests" S.mkdir' outerTmpDir { recursive: true, mode: permsAll } tempDir <- S.mkdtemp outerTmpDir S.mkdir' tempDir { recursive: true, mode: permsAll } @@ -190,9 +194,30 @@ main = do S.copyFile readableFixturePath destReadPath unlessM (S.exists destReadPath) do throw $ destReadPath <> " does not exist after copy" - copyErr <- try $ S.copyFile' readableFixturePath destReadPath copyFile_EXCL case copyErr of Left _ -> pure unit Right _ -> throw $ destReadPath <> " already exists, but copying a file to there did not throw an error with COPYFILE_EXCL option" + log "copy file using cpFile: ok" + let destReadPath2 = Path.concat [ tempDir, "readable2.txt" ] + S.cpFile readableFixturePath destReadPath2 + unlessM (S.exists destReadPath2) do + throw $ destReadPath2 <> " does not exist after copy" + + log "copy dir using cpDir: ok" + S.cpDir (tempDir <> "/") (tempDir <> "2") + + log "copy dir using cpFile: error - ERR_FS_EISDIR \"Recursive option not enabled\"" + let + cpFileShouldThrow_from = tempDir <> "/" + cpFileShouldThrow_to = tempDir <> "3" + cpFileError <- try $ S.cpFile cpFileShouldThrow_from cpFileShouldThrow_to + case cpFileError of + Left cpFileError' -> do + let + cpFileError'_message = Error.message cpFileError' + cpFileError'_code = (unsafeCoerce cpFileError' :: { code :: String }).code + assertEqual' "cpDirError'_message" { actual: cpFileError'_message, expected: "Recursive option not enabled, cannot copy a directory: " <> cpFileShouldThrow_from } + assertEqual' "cpDirError'_code" { actual: cpFileError'_code, expected: "ERR_FS_EISDIR" } + Right _ -> throw $ "cpFileShouldThrow: should have failed " <> show { cpFileShouldThrow_from, cpFileShouldThrow_to }