├── .eslintrc.json ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── package.json ├── src └── Node │ ├── FS.purs │ └── FS │ ├── Aff.purs │ ├── Async.js │ ├── Async.purs │ ├── Constants.js │ ├── Constants.purs │ ├── Perms.purs │ ├── Stats.js │ ├── Stats.purs │ ├── Stream.js │ ├── Stream.purs │ ├── Sync.js │ └── Sync.purs └── test ├── Main.purs ├── Streams.purs ├── Test.purs ├── TestAff.purs ├── TestAsync.purs └── fixtures └── readable.txt /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 9, 4 | "sourceType": "module" 5 | }, 6 | "extends": "eslint:recommended", 7 | "env": { 8 | "node": true 9 | }, 10 | "rules": { 11 | "strict": [2, "global"], 12 | "block-scoped-var": 2, 13 | "consistent-return": 2, 14 | "eqeqeq": [2, "smart"], 15 | "guard-for-in": 2, 16 | "no-caller": 2, 17 | "no-extend-native": 2, 18 | "no-loop-func": 2, 19 | "no-new": 2, 20 | "no-param-reassign": 2, 21 | "no-return-assign": 2, 22 | "no-unused-expressions": 2, 23 | "no-use-before-define": 2, 24 | "radix": [2, "always"], 25 | "indent": [2, 2], 26 | "quotes": [2, "double"], 27 | "semi": [2, "always"] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Description of the change** 2 | 3 | Clearly and concisely describe the purpose of the pull request. If this PR relates to an existing issue or change proposal, please link to it. Include any other background context that would help reviewers understand the motivation for this PR. 4 | 5 | --- 6 | 7 | **Checklist:** 8 | 9 | - [ ] Added the change to the changelog's "Unreleased" section with a reference to this PR (e.g. "- Made a change (#0000)") 10 | - [ ] Linked any existing issues or proposals that this pull request should close 11 | - [ ] Updated or added relevant documentation 12 | - [ ] Added a test for the contribution (if applicable) 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - uses: purescript-contrib/setup-purescript@main 16 | with: 17 | purescript: "unstable" 18 | purs-tidy: "latest" 19 | 20 | - uses: actions/setup-node@v3 21 | with: 22 | node-version: "lts/*" 23 | 24 | - name: Install dependencies 25 | run: | 26 | npm install -g bower 27 | npm install 28 | bower install --production 29 | 30 | - name: Build source 31 | run: npm run-script build 32 | 33 | - name: Run tests 34 | run: | 35 | bower install 36 | npm run-script test --if-present 37 | 38 | - name: Check formatting 39 | run: | 40 | purs-tidy check src test 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.* 2 | !/.gitignore 3 | !/.eslintrc.json 4 | !/.github/ 5 | /bower_components/ 6 | /node_modules/ 7 | /tmp/ 8 | /output/ 9 | package-lock.json 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 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). 4 | 5 | 6 | ## [Unreleased] 7 | 8 | Breaking changes: 9 | 10 | New features: 11 | 12 | Bugfixes: 13 | 14 | Other improvements: 15 | 16 | ## [v9.2.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v9.2.0) - 2024-06-23 17 | 18 | New features: 19 | - Add `lstat` to `Node.FS.Aff` (#85 by @artemisSystem) 20 | 21 | Bugfixes: 22 | - Fixed internal reference to `rmdir` that should have been `rm` (#83 by @MonoidMusician) 23 | 24 | ## [v9.1.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v9.1.0) - 2023-07-26 25 | 26 | Bugfixes: 27 | - Update `node-streams` to `v9.0.0` to fix FFI issues (#78 by @JordanMartinez) 28 | 29 | ## [v9.0.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v9.0.0) - 2023-07-19 30 | 31 | Breaking changes: 32 | - Remove `StatsObj` and reimplement bindings to `Stats` object (#76 by @JordanMartinez) 33 | 34 | Previously, one could write the following to get a value on the `Stats` object: 35 | ```purs 36 | getGid :: Stats -> Number 37 | getGid (Stats obj) = obj.gid 38 | ``` 39 | 40 | This record interface was removed as the underlying value is not a record 41 | that can be copied and modified as such. Now, one must call the corresponding function: 42 | ```purs 43 | getGid :: Stats -> Number 44 | getGid s = Stats.gid s 45 | ``` 46 | - Update `[fd]createReadStream`/`[fd]createWriteStream` to allow more options (#77 by @JordanMartinez) 47 | 48 | | Removes... | ...in favor of | 49 | | - | - | 50 | | `createReadStreamWith` | `createReadStream'` | 51 | | `fdCreateReadStreamWith` | `fdCreateReadStream'` | 52 | | `createWriteStreamWith` | `createWriteStream'` | 53 | | `fdCreateWriteStreamWith` | `fdCreateWriteStream'` | 54 | 55 | In the new APIs, all options are exposed and may require converting 56 | PureScript values to JavaScript ones. 57 | ```purs 58 | filePath # createWriteStream' 59 | { flags: fileFlagsToNode R 60 | , encoding: encodingToNode UTF8 61 | , mode: permsToInt Perms.all 62 | } 63 | ``` 64 | 65 | New features: 66 | - Integrate `node-fs-aff` into library (#75 by @JordanMartinez) 67 | 68 | Bugfixes: 69 | 70 | Other improvements: 71 | - Update `node-buffer` to next breaking release: `v9.0.0` (#74 by @JordanMartinez) 72 | - Update `node-streams` to next breaking release: `TODO` (#74 by @JordanMartinez) 73 | - Update CI actions to v3 (#74 by @JordanMartinez) 74 | - Update CI node to `lts/*` (#74 by @JordanMartinez) 75 | - Enforce `purs-tidy` format check in CI (#74 by @JordanMartinez) 76 | 77 | ## [v8.2.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v8.2.0) - 2023-03-23 78 | 79 | New features: 80 | - Add FFI for `access`, `copyFile` and `mkdtemp` (#73 by @JordanMartinez) 81 | 82 | ## [v8.1.1](https://github.com/purescript-node/purescript-node-fs/releases/tag/v8.1.1) - 2022-10-24 83 | 84 | Other improvements: 85 | - Use `EffectFn` throughout instead of unsafe `mkEffect` utility (#70 by @colinwahl) 86 | 87 | ## [v8.1.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v8.1.0) - 2022-06-10 88 | 89 | New features: 90 | - Add `lstat` (#66 by @artemisSystem) 91 | - Add rmdir', which takes an take options arg (#67 by @wclr) 92 | - Added rm and rm' version with and without options arg (#67 by @wclr) 93 | 94 | ## [v8.0.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v8.0.0) - 2022-04-29 95 | 96 | Breaking changes: 97 | - Remove `Async.exists` (#61 by @sigma-andex) 98 | - Update `mkdir` to take an options record arg, exposing `recursive` option (#53, #55, #58 by @JordanMartinez) 99 | To get back the old behavior of `mkdir'`, you would call `mkdir' { recursive: false, mode: mkPerms all all all }` 100 | 101 | New features: 102 | - Update project and deps to PureScript v0.15.0 (#59 by @JordanMartinez, @thomashoneyman, @sigma-andex) 103 | - Remove duplicate `node-buffer` from bower.json (@thomashoneyman) 104 | 105 | ## [v7.0.1](https://github.com/purescript-node/purescript-node-fs/releases/tag/v7.0.1) - 2022-04-27 106 | 107 | Due to an incorrectly-implemented breaking change, use v8.0.0 108 | 109 | ## [v7.0.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v7.0.0) - 2022-04-27 110 | 111 | Due to an incorrectly-implemented breaking change, use v8.0.0 112 | 113 | ## [v6.2.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v6.2.0) - 2022-01-10 114 | 115 | Breaking changes: 116 | 117 | New features: 118 | - Add bindings to `mkdir(path, { recursive: true })` via `mkdirRecursive` (#53, #55 by @JordanMartinez) 119 | 120 | Bugfixes: 121 | 122 | Other improvements: 123 | 124 | ## [v6.1.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v6.1.0) - 2021-05-06 125 | 126 | New features: 127 | - Exported `mkPerm` (#42 by @JordanMartinez) 128 | 129 | Other improvements: 130 | - Fixed warnings revealed by v0.14.1 PS release (#42 by @JordanMartinez) 131 | 132 | ## [v6.0.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v6.0.0) - 2021-02-26 133 | 134 | Breaking changes: 135 | - Added support for PureScript 0.14 and dropped support for all previous versions (#46) 136 | - Dropped deprecated `globals` dependency (#47) 137 | 138 | Other improvements: 139 | - Migrated CI to GitHub Actions, updated installation instructions to use Spago, and migrated from `jshint` to `eslint` (#45) 140 | - Added a changelog and pull request template (#49) 141 | 142 | ## [v5.0.1](https://github.com/purescript-node/purescript-node-fs/releases/tag/v5.0.1) - 2019-07-24 143 | 144 | - Relaxed upper bound on `node-buffer` 145 | 146 | ## [v5.0.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v5.0.0) - 2018-05-29 147 | 148 | Updates for 0.12 149 | 150 | ## [v4.0.1](https://github.com/purescript-node/purescript-node-fs/releases/tag/v4.0.1) - 2018-03-05 151 | 152 | - Raised upper bounds on `purescript-js-date` dependency (@justinwoo) 153 | 154 | ## [v4.0.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v4.0.0) - 2017-04-04 155 | 156 | Updates for 0.11 (@anilanar) 157 | 158 | ## [v3.0.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v3.0.0) - 2016-10-21 159 | 160 | - Updated dependencies 161 | 162 | ## [v2.0.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v2.0.0) - 2016-07-31 163 | 164 | - Updated dependencies 165 | 166 | ## [v1.0.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v1.0.0) - 2016-06-11 167 | 168 | Update for 1.0 core libraries and PureScript 0.9. 169 | 170 | ## [v0.11.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.11.0) - 2016-03-31 171 | 172 | Bump dependencies (`purescript-node-streams` → ~0.4.0). 173 | 174 | ## [v0.10.2](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.10.2) - 2016-03-31 175 | 176 | Update README 177 | 178 | ## [v0.10.1](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.10.1) - 2016-03-05 179 | 180 | - Fix exceptions thrown by `Node.FS.Sync.stat` being uncatchable 181 | 182 | ## [v0.10.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.10.0) - 2015-12-18 183 | 184 | - Add functions for accessing the filesystem using Node.js streams (#13, #26) 185 | 186 | ## [v0.9.2](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.9.2) - 2015-12-02 187 | 188 | - Fixed warnings (@thimoteus) 189 | 190 | ## [v0.9.1](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.9.1) - 2015-11-13 191 | 192 | - Add Show and Eq instances for FileFlags 193 | - Improved documentation 194 | 195 | ## [v0.9.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.9.0) - 2015-11-12 196 | 197 | - Added functions to `Node.FS.Async` for dealing with file descriptors asynchronously. (@timbod7, #21) 198 | - Change the `Show` instances for `SymlinkType` to obey the informal `Show` law of getting executable PureScript code out. 199 | - Added a new function `symlinkTypeToNode` for representing a `SymlinkType` as a `String` for use with Node.js APIs (this was previously the `Show` instance). 200 | - ~~Added a `Show` instance for `FileFlags`.~~ (this is in v0.9.1 because @hdgarrood forgot to do this) 201 | - Added a `fileFlagsToNode` function for representing a `FileFlags` value as a `String` for use with Node.js APIs. 202 | - The `FileDescriptor` and `FileFlags` types, and all relevant type synonyms, have been moved from `Node.FS.Sync` to `Node.FS`. 203 | - Update to version ^0.2.0 of `purescript-node-buffer` 204 | - Use `Int` instead of `Number` where appropriate in the following functions: 205 | - `chown` 206 | - `truncate` 207 | - `utimes` 208 | - All warnings have been fixed. (@timbod7) 209 | 210 | ## [v0.8.1](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.8.1) - 2015-08-14 211 | 212 | - Updated dependencies 213 | 214 | ## [v0.8.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.8.0) - 2015-08-14 215 | 216 | - Updated dependencies 217 | 218 | ## [v0.7.1](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.7.1) - 2015-07-06 219 | 220 | Fix type of `Async.exists`. 221 | 222 | ## [v0.6.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.6.0) - 2015-05-24 223 | 224 | - Use a proper type for permissions (@felixSchl) 225 | 226 | ## [v0.5.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.5.0) - 2015-04-07 227 | 228 | - Update dependencies 229 | 230 | ## [v0.4.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.4.0) - 2015-02-21 231 | 232 | **This release requires PureScript v0.6.8 or later** 233 | - Updated dependencies 234 | 235 | ## [v0.3.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.3.0) - 2015-01-29 236 | 237 | - Updated to use latest `purescript-datetime` 238 | 239 | ## [v0.2.1](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.2.1) - 2015-01-14 240 | 241 | - Added file descriptor features (@dysinger) 242 | 243 | ## [v0.2.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.2.0) - 2015-01-10 244 | 245 | - Update `purescript-foreign` dependency (@garyb) 246 | 247 | ## [v0.1.3](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.1.3) - 2014-11-18 248 | 249 | - Fix duplicate labels rows (@paf31) 250 | 251 | ## [v0.1.2](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.1.2) - 2014-10-14 252 | 253 | - Fixed dependency versions (@jdegoes) 254 | 255 | ## [v0.1.1](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.1.1) - 2014-09-10 256 | 257 | - Add `exists` (@joneshf) 258 | - Actions in `Async` no longer evaluate too early (@garyb / @paf31) 259 | 260 | ## [v0.1.0](https://github.com/purescript-node/purescript-node-fs/releases/tag/v0.1.0) - 2014-08-11 261 | 262 | Initial semver release. 263 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2015 PureScript 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purescript-node-fs 2 | 3 | [![Latest release](http://img.shields.io/github/release/purescript-node/purescript-node-fs.svg)](https://github.com/purescript-node/purescript-node-fs/releases) 4 | [![Build status](https://github.com/purescript-node/purescript-node-fs/workflows/CI/badge.svg?branch=master)](https://github.com/purescript-node/purescript-node-fs/actions?query=workflow%3ACI+branch%3Amaster) 5 | [![Pursuit](https://pursuit.purescript.org/packages/purescript-node-fs/badge)](https://pursuit.purescript.org/packages/purescript-node-fs) 6 | 7 | PureScript bindings to node's `fs` module. 8 | 9 | ## Installation 10 | 11 | ``` 12 | spago install node-fs 13 | ``` 14 | 15 | ## Documentation 16 | 17 | Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-node-fs). 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "purescript-node-fs", 3 | "homepage": "https://github.com/purescript-contrib/purescript-node-fs", 4 | "description": "Node.js file system operations", 5 | "keywords": [ 6 | "purescript" 7 | ], 8 | "license": "MIT", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/purescript-node/purescript-node-fs.git" 12 | }, 13 | "ignore": [ 14 | "**/.*", 15 | "bower_components", 16 | "node_modules", 17 | "tmp", 18 | "output", 19 | "bower.json", 20 | "package.json" 21 | ], 22 | "dependencies": { 23 | "purescript-datetime": "^6.0.0", 24 | "purescript-effect": "^4.0.0", 25 | "purescript-either": "^6.0.0", 26 | "purescript-enums": "^6.0.0", 27 | "purescript-exceptions": "^6.0.0", 28 | "purescript-functions": "^6.0.0", 29 | "purescript-integers": "^6.0.0", 30 | "purescript-js-date": "^8.0.0", 31 | "purescript-maybe": "^6.0.0", 32 | "purescript-node-buffer": "^9.0.0", 33 | "purescript-node-path": "^5.0.0", 34 | "purescript-node-streams": "^9.0.0", 35 | "purescript-nullable": "^6.0.0", 36 | "purescript-partial": "^4.0.0", 37 | "purescript-prelude": "^6.0.0", 38 | "purescript-strings": "^6.0.0", 39 | "purescript-unsafe-coerce": "^6.0.0" 40 | }, 41 | "devDependencies": { 42 | "purescript-console": "^6.0.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "clean": "rimraf output && rimraf .pulp-cache", 5 | "build": "eslint src && pulp build -- --censor-lib --strict", 6 | "test": "pulp test -- --censor-lib" 7 | }, 8 | "devDependencies": { 9 | "eslint": "^7.15.0", 10 | "pulp": "16.0.0-0", 11 | "purescript-psa": "^0.8.2", 12 | "rimraf": "^3.0.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Node/FS.purs: -------------------------------------------------------------------------------- 1 | module Node.FS 2 | ( FileDescriptor 3 | , FileMode 4 | , SymlinkType(..) 5 | , symlinkTypeToNode 6 | , BufferLength 7 | , BufferOffset 8 | , ByteCount 9 | , FilePosition 10 | , module Exports 11 | ) where 12 | 13 | import Prelude 14 | 15 | import Node.FS.Constants (FileFlags(..), fileFlagsToNode) as Exports 16 | 17 | foreign import data FileDescriptor :: Type 18 | 19 | type FileMode = Int 20 | type FilePosition = Int 21 | type BufferLength = Int 22 | type BufferOffset = Int 23 | type ByteCount = Int 24 | 25 | -- | Symlink varieties. 26 | data SymlinkType = FileLink | DirLink | JunctionLink 27 | 28 | -- | Convert a `SymlinkType` to a `String` in the format expected by the 29 | -- | Node.js filesystem API. 30 | symlinkTypeToNode :: SymlinkType -> String 31 | symlinkTypeToNode ty = case ty of 32 | FileLink -> "file" 33 | DirLink -> "dir" 34 | JunctionLink -> "junction" 35 | 36 | instance showSymlinkType :: Show SymlinkType where 37 | show FileLink = "FileLink" 38 | show DirLink = "DirLink" 39 | show JunctionLink = "JunctionLink" 40 | 41 | derive instance eqSymlinkType :: Eq SymlinkType 42 | -------------------------------------------------------------------------------- /src/Node/FS/Aff.purs: -------------------------------------------------------------------------------- 1 | module Node.FS.Aff 2 | ( access 3 | , access' 4 | , copyFile 5 | , copyFile' 6 | , mkdtemp 7 | , mkdtemp' 8 | , rename 9 | , truncate 10 | , chown 11 | , chmod 12 | , stat 13 | , lstat 14 | , link 15 | , symlink 16 | , readlink 17 | , realpath 18 | , realpath' 19 | , unlink 20 | , rmdir 21 | , rmdir' 22 | , rm 23 | , rm' 24 | , mkdir 25 | , mkdir' 26 | , readdir 27 | , utimes 28 | , readFile 29 | , readTextFile 30 | , writeFile 31 | , writeTextFile 32 | , appendFile 33 | , appendTextFile 34 | , fdOpen 35 | , fdRead 36 | , fdNext 37 | , fdWrite 38 | , fdAppend 39 | , fdClose 40 | ) where 41 | 42 | import Prelude 43 | 44 | import Data.DateTime (DateTime) 45 | import Data.Either (Either(..)) 46 | import Data.Maybe (Maybe) 47 | import Effect (Effect) 48 | import Effect.Aff (Aff, Error, makeAff, nonCanceler) 49 | import Node.Buffer (Buffer) 50 | import Node.Encoding (Encoding) 51 | import Node.FS as F 52 | import Node.FS.Async as A 53 | import Node.FS.Constants (AccessMode, CopyMode) 54 | import Node.FS.Perms (Perms) 55 | import Node.FS.Stats (Stats) 56 | import Node.Path (FilePath) 57 | 58 | toAff 59 | :: forall a 60 | . (A.Callback a -> Effect Unit) 61 | -> Aff a 62 | toAff p = makeAff \k -> p k $> nonCanceler 63 | 64 | toAff1 65 | :: forall a x 66 | . (x -> A.Callback a -> Effect Unit) 67 | -> x 68 | -> Aff a 69 | toAff1 f a = toAff (f a) 70 | 71 | toAff2 72 | :: forall a x y 73 | . (x -> y -> A.Callback a -> Effect Unit) 74 | -> x 75 | -> y 76 | -> Aff a 77 | toAff2 f a b = toAff (f a b) 78 | 79 | toAff3 80 | :: forall a x y z 81 | . (x -> y -> z -> A.Callback a -> Effect Unit) 82 | -> x 83 | -> y 84 | -> z 85 | -> Aff a 86 | toAff3 f a b c = toAff (f a b c) 87 | 88 | toAff5 89 | :: forall a w v x y z 90 | . (w -> v -> x -> y -> z -> A.Callback a -> Effect Unit) 91 | -> w 92 | -> v 93 | -> x 94 | -> y 95 | -> z 96 | -> Aff a 97 | toAff5 f a b c d e = toAff (f a b c d e) 98 | 99 | access :: String -> Aff (Maybe Error) 100 | access path = makeAff \k -> do 101 | A.access path (k <<< Right) 102 | pure nonCanceler 103 | 104 | access' :: String -> AccessMode -> Aff (Maybe Error) 105 | access' path mode = makeAff \k -> do 106 | A.access' path mode (k <<< Right) 107 | pure nonCanceler 108 | 109 | copyFile :: String -> String -> Aff Unit 110 | copyFile = toAff2 A.copyFile 111 | 112 | copyFile' :: String -> String -> CopyMode -> Aff Unit 113 | copyFile' = toAff3 A.copyFile' 114 | 115 | mkdtemp :: String -> Aff String 116 | mkdtemp = toAff1 A.mkdtemp 117 | 118 | mkdtemp' :: String -> Encoding -> Aff String 119 | mkdtemp' = toAff2 A.mkdtemp' 120 | 121 | -- | 122 | -- | Rename a file. 123 | -- | 124 | rename :: FilePath -> FilePath -> Aff Unit 125 | rename = toAff2 A.rename 126 | 127 | -- | 128 | -- | Truncates a file to the specified length. 129 | -- | 130 | truncate :: FilePath -> Int -> Aff Unit 131 | truncate = toAff2 A.truncate 132 | 133 | -- | 134 | -- | Changes the ownership of a file. 135 | -- | 136 | chown :: FilePath -> Int -> Int -> Aff Unit 137 | chown = toAff3 A.chown 138 | 139 | -- | 140 | -- | Changes the permissions of a file. 141 | -- | 142 | chmod :: FilePath -> Perms -> Aff Unit 143 | chmod = toAff2 A.chmod 144 | 145 | -- | 146 | -- | Gets file statistics. 147 | -- | 148 | stat :: FilePath -> Aff Stats 149 | stat = toAff1 A.stat 150 | 151 | -- | Gets file or symlink statistics. `lstat` is identical to `stat`, except 152 | -- | that if the `FilePath` is a symbolic link, then the link itself is stat-ed, 153 | -- | not the file that it refers to. 154 | lstat :: FilePath -> Aff Stats 155 | lstat = toAff1 A.lstat 156 | 157 | -- | 158 | -- | Creates a link to an existing file. 159 | -- | 160 | link :: FilePath -> FilePath -> Aff Unit 161 | link = toAff2 A.link 162 | 163 | -- | 164 | -- | Creates a symlink. 165 | -- | 166 | symlink 167 | :: FilePath 168 | -> FilePath 169 | -> F.SymlinkType 170 | -> Aff Unit 171 | symlink = toAff3 A.symlink 172 | 173 | -- | 174 | -- | Reads the value of a symlink. 175 | -- | 176 | readlink :: FilePath -> Aff FilePath 177 | readlink = toAff1 A.readlink 178 | 179 | -- | 180 | -- | Find the canonicalized absolute location for a path. 181 | -- | 182 | realpath :: FilePath -> Aff FilePath 183 | realpath = toAff1 A.realpath 184 | 185 | -- | 186 | -- | Find the canonicalized absolute location for a path using a cache object 187 | -- | for already resolved paths. 188 | -- | 189 | realpath' :: forall cache. FilePath -> { | cache } -> Aff FilePath 190 | realpath' = toAff2 A.realpath' 191 | 192 | -- | 193 | -- | Deletes a file. 194 | -- | 195 | unlink :: FilePath -> Aff Unit 196 | unlink = toAff1 A.unlink 197 | 198 | -- | 199 | -- | Deletes a directory. 200 | -- | 201 | rmdir :: FilePath -> Aff Unit 202 | rmdir = toAff1 A.rmdir 203 | 204 | -- | 205 | -- | Deletes a directory with options. 206 | -- | 207 | rmdir' :: FilePath -> { maxRetries :: Int, retryDelay :: Int } -> Aff Unit 208 | rmdir' = toAff2 A.rmdir' 209 | 210 | -- | 211 | -- | Deletes a file or directory. 212 | -- | 213 | rm :: FilePath -> Aff Unit 214 | rm = toAff1 A.rm 215 | 216 | -- | 217 | -- | Deletes a file or directory with options. 218 | -- | 219 | rm' :: FilePath -> { force :: Boolean, maxRetries :: Int, recursive :: Boolean, retryDelay :: Int } -> Aff Unit 220 | rm' = toAff2 A.rm' 221 | 222 | -- | 223 | -- | Makes a new directory. 224 | -- | 225 | mkdir :: FilePath -> Aff Unit 226 | mkdir = toAff1 A.mkdir 227 | 228 | -- | 229 | -- | Makes a new directory with all of its options. 230 | -- | 231 | mkdir' :: FilePath -> { recursive :: Boolean, mode :: Perms } -> Aff Unit 232 | mkdir' = toAff2 A.mkdir' 233 | 234 | -- | 235 | -- | Reads the contents of a directory. 236 | -- | 237 | readdir :: FilePath -> Aff (Array FilePath) 238 | readdir = toAff1 A.readdir 239 | 240 | -- | 241 | -- | Sets the accessed and modified times for the specified file. 242 | -- | 243 | utimes :: FilePath -> DateTime -> DateTime -> Aff Unit 244 | utimes = toAff3 A.utimes 245 | 246 | -- | 247 | -- | Reads the entire contents of a file returning the result as a raw buffer. 248 | -- | 249 | readFile :: FilePath -> Aff Buffer 250 | readFile = toAff1 A.readFile 251 | 252 | -- | 253 | -- | Reads the entire contents of a text file with the specified encoding. 254 | -- | 255 | readTextFile :: Encoding -> FilePath -> Aff String 256 | readTextFile = toAff2 A.readTextFile 257 | 258 | -- | 259 | -- | Writes a buffer to a file. 260 | -- | 261 | writeFile :: FilePath -> Buffer -> Aff Unit 262 | writeFile = toAff2 A.writeFile 263 | 264 | -- | 265 | -- | Writes text to a file using the specified encoding. 266 | -- | 267 | writeTextFile :: Encoding -> FilePath -> String -> Aff Unit 268 | writeTextFile = toAff3 A.writeTextFile 269 | 270 | -- | 271 | -- | Appends the contents of a buffer to a file. 272 | -- | 273 | appendFile :: FilePath -> Buffer -> Aff Unit 274 | appendFile = toAff2 A.appendFile 275 | 276 | -- | 277 | -- | Appends text to a file using the specified encoding. 278 | -- | 279 | appendTextFile :: Encoding -> FilePath -> String -> Aff Unit 280 | appendTextFile = toAff3 A.appendTextFile 281 | 282 | -- | Open a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_open_path_flags_mode_callback) 283 | -- | for details. 284 | fdOpen 285 | :: FilePath 286 | -> F.FileFlags 287 | -> Maybe F.FileMode 288 | -> Aff F.FileDescriptor 289 | fdOpen = toAff3 A.fdOpen 290 | 291 | -- | Read from a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_read_fd_buffer_offset_length_position_callback) 292 | -- | for details. 293 | fdRead 294 | :: F.FileDescriptor 295 | -> Buffer 296 | -> F.BufferOffset 297 | -> F.BufferLength 298 | -> Maybe F.FilePosition 299 | -> Aff F.ByteCount 300 | fdRead = toAff5 A.fdRead 301 | 302 | -- | Convenience function to fill the whole buffer from the current 303 | -- | file position. 304 | fdNext :: F.FileDescriptor -> Buffer -> Aff F.ByteCount 305 | fdNext = toAff2 A.fdNext 306 | 307 | -- | Write to a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_write_fd_buffer_offset_length_position_callback) 308 | -- | for details. 309 | fdWrite 310 | :: F.FileDescriptor 311 | -> Buffer 312 | -> F.BufferOffset 313 | -> F.BufferLength 314 | -> Maybe F.FilePosition 315 | -> Aff F.ByteCount 316 | fdWrite = toAff5 A.fdWrite 317 | 318 | -- | Convenience function to append the whole buffer to the current 319 | -- | file position. 320 | fdAppend :: F.FileDescriptor -> Buffer -> Aff F.ByteCount 321 | fdAppend = toAff2 A.fdAppend 322 | 323 | -- | Close a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_close_fd_callback) 324 | -- | for details. 325 | fdClose :: F.FileDescriptor -> Aff Unit 326 | fdClose = toAff1 A.fdClose 327 | -------------------------------------------------------------------------------- /src/Node/FS/Async.js: -------------------------------------------------------------------------------- 1 | export { 2 | access as accessImpl, 3 | copyFile as copyFileImpl, 4 | mkdtemp as mkdtempImpl, 5 | rename as renameImpl, 6 | truncate as truncateImpl, 7 | chown as chownImpl, 8 | chmod as chmodImpl, 9 | stat as statImpl, 10 | lstat as lstatImpl, 11 | link as linkImpl, 12 | symlink as symlinkImpl, 13 | readlink as readlinkImpl, 14 | realpath as realpathImpl, 15 | unlink as unlinkImpl, 16 | rmdir as rmdirImpl, 17 | rm as rmImpl, 18 | mkdir as mkdirImpl, 19 | readdir as readdirImpl, 20 | utimes as utimesImpl, 21 | readFile as readFileImpl, 22 | writeFile as writeFileImpl, 23 | appendFile as appendFileImpl, 24 | open as openImpl, 25 | read as readImpl, 26 | write as writeImpl, 27 | close as closeImpl 28 | } from "node:fs"; 29 | -------------------------------------------------------------------------------- /src/Node/FS/Async.purs: -------------------------------------------------------------------------------- 1 | module Node.FS.Async 2 | ( Callback 3 | , access 4 | , access' 5 | , copyFile 6 | , copyFile' 7 | , mkdtemp 8 | , mkdtemp' 9 | , rename 10 | , truncate 11 | , chown 12 | , chmod 13 | , lstat 14 | , stat 15 | , link 16 | , symlink 17 | , readlink 18 | , realpath 19 | , realpath' 20 | , unlink 21 | , rmdir 22 | , rmdir' 23 | , rm 24 | , rm' 25 | , mkdir 26 | , mkdir' 27 | , readdir 28 | , utimes 29 | , readFile 30 | , readTextFile 31 | , writeFile 32 | , writeTextFile 33 | , appendFile 34 | , appendTextFile 35 | , fdOpen 36 | , fdRead 37 | , fdNext 38 | , fdWrite 39 | , fdAppend 40 | , fdClose 41 | ) where 42 | 43 | import Prelude 44 | 45 | import Data.DateTime (DateTime) 46 | import Data.DateTime.Instant (fromDateTime, unInstant) 47 | import Data.Either (Either(..)) 48 | import Data.Int (round) 49 | import Data.Maybe (Maybe(..)) 50 | import Data.Nullable (Nullable, toMaybe, toNullable) 51 | import Data.Time.Duration (Milliseconds(..)) 52 | import Effect (Effect) 53 | import Effect.Exception (Error) 54 | import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn4, EffectFn6, mkEffectFn1, mkEffectFn2, runEffectFn2, runEffectFn3, runEffectFn4, runEffectFn6) 55 | import Node.Buffer (Buffer, size) 56 | import Node.Encoding (Encoding(..), encodingToNode) 57 | import Node.FS (FileDescriptor, ByteCount, FilePosition, BufferLength, BufferOffset, FileMode, SymlinkType, symlinkTypeToNode) 58 | import Node.FS.Constants (FileFlags, fileFlagsToNode, AccessMode, CopyMode, defaultAccessMode, defaultCopyMode) 59 | import Node.FS.Perms (Perms, permsToString, all, mkPerms) 60 | import Node.FS.Stats (Stats) 61 | import Node.Path (FilePath) 62 | 63 | type JSCallback a = EffectFn2 (Nullable Error) a Unit 64 | 65 | handleCallback :: forall a. Callback a -> JSCallback a 66 | handleCallback cb = mkEffectFn2 \err a -> case toMaybe err of 67 | Nothing -> cb (Right a) 68 | Just err' -> cb (Left err') 69 | 70 | -- | Type synonym for callback functions. 71 | type Callback a = Either Error a -> Effect Unit 72 | 73 | access :: FilePath -> (Maybe Error -> Effect Unit) -> Effect Unit 74 | access path = access' path defaultAccessMode 75 | 76 | access' :: FilePath -> AccessMode -> (Maybe Error -> Effect Unit) -> Effect Unit 77 | access' path mode cb = runEffectFn3 accessImpl path mode $ mkEffectFn1 \err -> do 78 | cb $ toMaybe err 79 | 80 | foreign import accessImpl :: EffectFn3 FilePath AccessMode (EffectFn1 (Nullable Error) Unit) Unit 81 | 82 | copyFile :: FilePath -> FilePath -> Callback Unit -> Effect Unit 83 | copyFile src dest = copyFile' src dest defaultCopyMode 84 | 85 | copyFile' :: FilePath -> FilePath -> CopyMode -> Callback Unit -> Effect Unit 86 | copyFile' src dest mode cb = runEffectFn4 copyFileImpl src dest mode (handleCallback cb) 87 | 88 | foreign import copyFileImpl :: EffectFn4 FilePath FilePath CopyMode (JSCallback Unit) Unit 89 | 90 | mkdtemp :: String -> Callback String -> Effect Unit 91 | mkdtemp prefix = mkdtemp' prefix UTF8 92 | 93 | mkdtemp' :: String -> Encoding -> Callback String -> Effect Unit 94 | mkdtemp' prefix encoding cb = runEffectFn3 mkdtempImpl prefix (encodingToNode encoding) (handleCallback cb) 95 | 96 | foreign import mkdtempImpl :: EffectFn3 FilePath String (JSCallback String) Unit 97 | 98 | foreign import renameImpl :: EffectFn3 FilePath FilePath (JSCallback Unit) Unit 99 | foreign import truncateImpl :: EffectFn3 FilePath Int (JSCallback Unit) Unit 100 | foreign import chownImpl :: EffectFn4 FilePath Int Int (JSCallback Unit) Unit 101 | foreign import chmodImpl :: EffectFn3 FilePath String (JSCallback Unit) Unit 102 | foreign import statImpl :: EffectFn2 FilePath (JSCallback Stats) Unit 103 | foreign import lstatImpl :: EffectFn2 FilePath (JSCallback Stats) Unit 104 | foreign import linkImpl :: EffectFn3 FilePath FilePath (JSCallback Unit) Unit 105 | foreign import symlinkImpl :: EffectFn4 FilePath FilePath String (JSCallback Unit) Unit 106 | foreign import readlinkImpl :: EffectFn2 FilePath (JSCallback FilePath) Unit 107 | foreign import realpathImpl :: forall cache. EffectFn3 FilePath { | cache } (JSCallback FilePath) Unit 108 | foreign import unlinkImpl :: EffectFn2 FilePath (JSCallback Unit) Unit 109 | foreign import rmdirImpl :: EffectFn3 FilePath { maxRetries :: Int, retryDelay :: Int } (JSCallback Unit) Unit 110 | foreign import rmImpl :: EffectFn3 FilePath { force :: Boolean, maxRetries :: Int, recursive :: Boolean, retryDelay :: Int } (JSCallback Unit) Unit 111 | foreign import mkdirImpl :: EffectFn3 FilePath { recursive :: Boolean, mode :: String } (JSCallback Unit) Unit 112 | foreign import readdirImpl :: EffectFn2 FilePath (JSCallback (Array FilePath)) Unit 113 | foreign import utimesImpl :: EffectFn4 FilePath Int Int (JSCallback Unit) Unit 114 | foreign import readFileImpl :: forall a opts. EffectFn3 FilePath { | opts } (JSCallback a) Unit 115 | foreign import writeFileImpl :: forall a opts. EffectFn4 FilePath a { | opts } (JSCallback Unit) Unit 116 | foreign import appendFileImpl :: forall a opts. EffectFn4 FilePath a { | opts } (JSCallback Unit) Unit 117 | foreign import openImpl :: EffectFn4 FilePath String (Nullable FileMode) (JSCallback FileDescriptor) Unit 118 | foreign import readImpl :: EffectFn6 FileDescriptor Buffer BufferOffset BufferLength (Nullable FilePosition) (JSCallback ByteCount) Unit 119 | foreign import writeImpl :: EffectFn6 FileDescriptor Buffer BufferOffset BufferLength (Nullable FilePosition) (JSCallback ByteCount) Unit 120 | foreign import closeImpl :: EffectFn2 FileDescriptor (JSCallback Unit) Unit 121 | 122 | -- | Renames a file. 123 | rename 124 | :: FilePath 125 | -> FilePath 126 | -> Callback Unit 127 | -> Effect Unit 128 | rename oldFile newFile cb = runEffectFn3 renameImpl oldFile newFile (handleCallback cb) 129 | 130 | -- | Truncates a file to the specified length. 131 | truncate 132 | :: FilePath 133 | -> Int 134 | -> Callback Unit 135 | -> Effect Unit 136 | truncate file len cb = runEffectFn3 truncateImpl file len (handleCallback cb) 137 | 138 | -- | Changes the ownership of a file. 139 | chown 140 | :: FilePath 141 | -> Int 142 | -> Int 143 | -> Callback Unit 144 | -> Effect Unit 145 | chown file uid gid cb = runEffectFn4 chownImpl file uid gid (handleCallback cb) 146 | 147 | -- | Changes the permissions of a file. 148 | chmod 149 | :: FilePath 150 | -> Perms 151 | -> Callback Unit 152 | -> Effect Unit 153 | chmod file perms cb = runEffectFn3 chmodImpl file (permsToString perms) (handleCallback cb) 154 | 155 | -- | Gets file statistics. 156 | stat 157 | :: FilePath 158 | -> Callback Stats 159 | -> Effect Unit 160 | stat file cb = runEffectFn2 statImpl file (handleCallback $ cb) 161 | 162 | -- | Gets file or symlink statistics. `lstat` is identical to `stat`, except 163 | -- | that if the `FilePath` is a symbolic link, then the link itself is stat-ed, 164 | -- | not the file that it refers to. 165 | lstat 166 | :: FilePath 167 | -> Callback Stats 168 | -> Effect Unit 169 | lstat file cb = runEffectFn2 lstatImpl file (handleCallback $ cb) 170 | 171 | -- | Creates a link to an existing file. 172 | link 173 | :: FilePath 174 | -> FilePath 175 | -> Callback Unit 176 | -> Effect Unit 177 | link src dst cb = runEffectFn3 linkImpl src dst (handleCallback cb) 178 | 179 | -- | Creates a symlink. 180 | symlink 181 | :: FilePath 182 | -> FilePath 183 | -> SymlinkType 184 | -> Callback Unit 185 | -> Effect Unit 186 | symlink src dest ty cb = runEffectFn4 symlinkImpl src dest (symlinkTypeToNode ty) (handleCallback cb) 187 | 188 | -- | Reads the value of a symlink. 189 | readlink 190 | :: FilePath 191 | -> Callback FilePath 192 | -> Effect Unit 193 | readlink path cb = runEffectFn2 readlinkImpl path (handleCallback cb) 194 | 195 | -- | Find the canonicalized absolute location for a path. 196 | realpath 197 | :: FilePath 198 | -> Callback FilePath 199 | -> Effect Unit 200 | realpath path cb = runEffectFn3 realpathImpl path {} (handleCallback cb) 201 | 202 | -- | Find the canonicalized absolute location for a path using a cache object 203 | -- | for already resolved paths. 204 | realpath' 205 | :: forall cache 206 | . FilePath 207 | -> { | cache } 208 | -> Callback FilePath 209 | -> Effect Unit 210 | realpath' path cache cb = runEffectFn3 realpathImpl path cache (handleCallback cb) 211 | 212 | -- | Deletes a file. 213 | unlink 214 | :: FilePath 215 | -> Callback Unit 216 | -> Effect Unit 217 | unlink file cb = runEffectFn2 unlinkImpl file (handleCallback cb) 218 | 219 | -- | Deletes a directory. 220 | rmdir 221 | :: FilePath 222 | -> Callback Unit 223 | -> Effect Unit 224 | rmdir path cb = rmdir' path { maxRetries: 0, retryDelay: 100 } cb 225 | 226 | -- | Deletes a directory with options. 227 | rmdir' 228 | :: FilePath 229 | -> { maxRetries :: Int, retryDelay :: Int } 230 | -> Callback Unit 231 | -> Effect Unit 232 | rmdir' path opts cb = runEffectFn3 rmdirImpl path opts (handleCallback cb) 233 | 234 | -- | Deletes a file or directory. 235 | rm 236 | :: FilePath 237 | -> Callback Unit 238 | -> Effect Unit 239 | rm path = rm' path { force: false, maxRetries: 100, recursive: false, retryDelay: 1000 } 240 | 241 | -- | Deletes a file or directory with options. 242 | rm' 243 | :: FilePath 244 | -> { force :: Boolean, maxRetries :: Int, recursive :: Boolean, retryDelay :: Int } 245 | -> Callback Unit 246 | -> Effect Unit 247 | rm' path opts cb = runEffectFn3 rmImpl path opts (handleCallback cb) 248 | 249 | -- | Makes a new directory. 250 | mkdir 251 | :: FilePath 252 | -> Callback Unit 253 | -> Effect Unit 254 | mkdir path = mkdir' path { recursive: false, mode: mkPerms all all all } 255 | 256 | -- | Makes a new directory with the specified permissions. 257 | mkdir' 258 | :: FilePath 259 | -> { recursive :: Boolean, mode :: Perms } 260 | -> Callback Unit 261 | -> Effect Unit 262 | mkdir' file { recursive, mode: perms } cb = runEffectFn3 mkdirImpl file { recursive, mode: permsToString perms } (handleCallback cb) 263 | 264 | -- | Reads the contents of a directory. 265 | readdir 266 | :: FilePath 267 | -> Callback (Array FilePath) 268 | -> Effect Unit 269 | readdir file cb = runEffectFn2 readdirImpl file (handleCallback cb) 270 | 271 | -- | Sets the accessed and modified times for the specified file. 272 | utimes 273 | :: FilePath 274 | -> DateTime 275 | -> DateTime 276 | -> Callback Unit 277 | -> Effect Unit 278 | utimes file atime mtime cb = runEffectFn4 utimesImpl file (fromDate atime) (fromDate mtime) (handleCallback cb) 279 | where 280 | fromDate date = ms (toEpochMilliseconds date) / 1000 281 | ms (Milliseconds n) = round n 282 | toEpochMilliseconds = unInstant <<< fromDateTime 283 | 284 | -- | Reads the entire contents of a file returning the result as a raw buffer. 285 | readFile 286 | :: FilePath 287 | -> Callback Buffer 288 | -> Effect Unit 289 | readFile file cb = runEffectFn3 readFileImpl file {} (handleCallback cb) 290 | 291 | -- | Reads the entire contents of a text file with the specified encoding. 292 | readTextFile 293 | :: Encoding 294 | -> FilePath 295 | -> Callback String 296 | -> Effect Unit 297 | readTextFile encoding file cb = runEffectFn3 readFileImpl file { encoding: show encoding } (handleCallback cb) 298 | 299 | -- | Writes a buffer to a file. 300 | writeFile 301 | :: FilePath 302 | -> Buffer 303 | -> Callback Unit 304 | -> Effect Unit 305 | writeFile file buff cb = runEffectFn4 writeFileImpl file buff {} (handleCallback cb) 306 | 307 | -- | Writes text to a file using the specified encoding. 308 | writeTextFile 309 | :: Encoding 310 | -> FilePath 311 | -> String 312 | -> Callback Unit 313 | -> Effect Unit 314 | writeTextFile encoding file buff cb = runEffectFn4 writeFileImpl file buff { encoding: show encoding } (handleCallback cb) 315 | 316 | -- | Appends the contents of a buffer to a file. 317 | appendFile 318 | :: FilePath 319 | -> Buffer 320 | -> Callback Unit 321 | -> Effect Unit 322 | appendFile file buff cb = runEffectFn4 appendFileImpl file buff {} (handleCallback cb) 323 | 324 | -- | Appends text to a file using the specified encoding. 325 | appendTextFile 326 | :: Encoding 327 | -> FilePath 328 | -> String 329 | -> Callback Unit 330 | -> Effect Unit 331 | appendTextFile encoding file buff cb = runEffectFn4 appendFileImpl file buff { encoding: show encoding } (handleCallback cb) 332 | 333 | -- | Open a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_open_path_flags_mode_callback) 334 | -- | for details. 335 | fdOpen 336 | :: FilePath 337 | -> FileFlags 338 | -> Maybe FileMode 339 | -> Callback FileDescriptor 340 | -> Effect Unit 341 | fdOpen file flags mode cb = runEffectFn4 openImpl file (fileFlagsToNode flags) (toNullable mode) (handleCallback cb) 342 | 343 | -- | Read from a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_read_fd_buffer_offset_length_position_callback) 344 | -- | for details. 345 | fdRead 346 | :: FileDescriptor 347 | -> Buffer 348 | -> BufferOffset 349 | -> BufferLength 350 | -> Maybe FilePosition 351 | -> Callback ByteCount 352 | -> Effect Unit 353 | fdRead fd buff off len pos cb = runEffectFn6 readImpl fd buff off len (toNullable pos) (handleCallback cb) 354 | 355 | -- | Convenience function to fill the whole buffer from the current 356 | -- | file position. 357 | fdNext 358 | :: FileDescriptor 359 | -> Buffer 360 | -> Callback ByteCount 361 | -> Effect Unit 362 | fdNext fd buff cb = do 363 | sz <- size buff 364 | fdRead fd buff 0 sz Nothing cb 365 | 366 | -- | Write to a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_write_fd_buffer_offset_length_position_callback) 367 | -- | for details. 368 | fdWrite 369 | :: FileDescriptor 370 | -> Buffer 371 | -> BufferOffset 372 | -> BufferLength 373 | -> Maybe FilePosition 374 | -> Callback ByteCount 375 | -> Effect Unit 376 | fdWrite fd buff off len pos cb = runEffectFn6 writeImpl fd buff off len (toNullable pos) (handleCallback cb) 377 | 378 | -- | Convenience function to append the whole buffer to the current 379 | -- | file position. 380 | fdAppend 381 | :: FileDescriptor 382 | -> Buffer 383 | -> Callback ByteCount 384 | -> Effect Unit 385 | fdAppend fd buff cb = do 386 | sz <- size buff 387 | fdWrite fd buff 0 sz Nothing cb 388 | 389 | -- | Close a file asynchronously. See the [Node Documentation](https://nodejs.org/api/fs.html#fs_fs_close_fd_callback) 390 | -- | for details. 391 | fdClose 392 | :: FileDescriptor 393 | -> Callback Unit 394 | -> Effect Unit 395 | fdClose fd cb = runEffectFn2 closeImpl fd (handleCallback cb) 396 | -------------------------------------------------------------------------------- /src/Node/FS/Constants.js: -------------------------------------------------------------------------------- 1 | import { constants } from "node:fs"; 2 | 3 | export const f_OK = constants.F_OK; 4 | 5 | export const r_OK = constants.R_OK; 6 | 7 | export const w_OK = constants.W_OK; 8 | 9 | export const x_OK = constants.X_OK; 10 | 11 | export const copyFile_EXCL = constants.COPYFILE_EXCL; 12 | 13 | export const copyFile_FICLONE = constants.COPYFILE_FICLONE; 14 | 15 | export const copyFile_FICLONE_FORCE = constants.COPYFILE_FICLONE_FORCE; 16 | 17 | export const appendCopyMode = (l, r) => l | r; 18 | -------------------------------------------------------------------------------- /src/Node/FS/Constants.purs: -------------------------------------------------------------------------------- 1 | module Node.FS.Constants where 2 | 3 | import Prelude 4 | 5 | import Data.Function.Uncurried (Fn2, runFn2) 6 | 7 | -- | the mode parameter passed to `access` and `accessSync`. 8 | foreign import data AccessMode :: Type 9 | 10 | -- | the file is visible to the calling process. 11 | -- | This is useful for determining if a file exists, but says nothing about rwx permissions. Default if no mode is specified. 12 | foreign import f_OK :: AccessMode 13 | 14 | -- | the file can be read by the calling process. 15 | foreign import r_OK :: AccessMode 16 | 17 | -- | the file can be written by the calling process. 18 | foreign import w_OK :: AccessMode 19 | 20 | -- | the file can be executed by the calling process. This has no effect on Windows (will behave like fs.constants.F_OK). 21 | foreign import x_OK :: AccessMode 22 | 23 | defaultAccessMode = f_OK :: AccessMode 24 | 25 | -- | A constant used in `copyFile`. 26 | foreign import data CopyMode :: Type 27 | 28 | -- | If present, the copy operation will fail with an error if the destination path already exists. 29 | foreign import copyFile_EXCL :: CopyMode 30 | 31 | -- | 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 a fallback copy mechanism is used. 32 | foreign import copyFile_FICLONE :: CopyMode 33 | 34 | -- | 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. 35 | foreign import copyFile_FICLONE_FORCE :: CopyMode 36 | 37 | defaultCopyMode = copyFile_EXCL :: CopyMode 38 | 39 | foreign import appendCopyMode :: Fn2 CopyMode CopyMode CopyMode 40 | 41 | instance Semigroup CopyMode where 42 | append l r = runFn2 appendCopyMode l r 43 | 44 | data FileFlags 45 | = R 46 | | R_PLUS 47 | | RS 48 | | RS_PLUS 49 | | W 50 | | WX 51 | | W_PLUS 52 | | WX_PLUS 53 | | A 54 | | AX 55 | | A_PLUS 56 | | AX_PLUS 57 | 58 | instance showFileFlags :: Show FileFlags where 59 | show R = "R" 60 | show R_PLUS = "R_PLUS" 61 | show RS = "RS" 62 | show RS_PLUS = "RS_PLUS" 63 | show W = "W" 64 | show WX = "WX" 65 | show W_PLUS = "W_PLUS" 66 | show WX_PLUS = "WX_PLUS" 67 | show A = "A" 68 | show AX = "AX" 69 | show A_PLUS = "A_PLUS" 70 | show AX_PLUS = "AX_PLUS" 71 | 72 | instance eqFileFlags :: Eq FileFlags where 73 | eq x y = show x == show y 74 | 75 | -- | Convert a `FileFlags` to a `String` in the format expected by the Node.js 76 | -- | filesystem API. 77 | fileFlagsToNode :: FileFlags -> String 78 | fileFlagsToNode ff = case ff of 79 | R -> "r" 80 | R_PLUS -> "r+" 81 | RS -> "rs" 82 | RS_PLUS -> "rs+" 83 | W -> "w" 84 | WX -> "wx" 85 | W_PLUS -> "w+" 86 | WX_PLUS -> "wx+" 87 | A -> "a" 88 | AX -> "ax" 89 | A_PLUS -> "a+" 90 | AX_PLUS -> "ax+" 91 | -------------------------------------------------------------------------------- /src/Node/FS/Perms.purs: -------------------------------------------------------------------------------- 1 | module Node.FS.Perms 2 | ( Perm 3 | , mkPerm 4 | , none 5 | , read 6 | , write 7 | , execute 8 | , all 9 | , Perms 10 | , mkPerms 11 | , permsAll 12 | , permsReadWrite 13 | , permsFromString 14 | , permsToString 15 | , permsToInt 16 | ) where 17 | 18 | import Prelude 19 | 20 | import Data.Enum (toEnum) 21 | import Data.Int (fromStringAs, octal) 22 | import Data.Maybe (Maybe(..), isNothing, fromJust) 23 | import Data.String (Pattern(Pattern), joinWith, drop, indexOf) 24 | import Data.String.CodeUnits (charAt, toCharArray) 25 | import Partial.Unsafe (unsafePartial) 26 | 27 | -- | A `Perm` value specifies what is allowed to be done with a particular 28 | -- | file by a particular class of user — that is, whether it is 29 | -- | readable, writable, and/or executable. It has a `Semiring` instance, which 30 | -- | allows you to combine permissions: 31 | -- | 32 | -- | - `(+)` adds `Perm` values together. For example, `read + write` means 33 | -- | "readable and writable". 34 | -- | - `(*)` masks permissions. It can be thought of as selecting only the 35 | -- | permissions that two `Perm` values have in common. For example: 36 | -- | `(read + write) * (write + execute) == write`. 37 | -- | 38 | -- | You can think also of a `Perm` value as a subset of the set 39 | -- | `{ read, write, execute }`; then, `(+)` and `(*)` represent set union and 40 | -- | intersection respectively. 41 | newtype Perm = Perm { r :: Boolean, w :: Boolean, x :: Boolean } 42 | 43 | derive newtype instance eqPerm :: Eq Perm 44 | 45 | instance ordPerm :: Ord Perm where 46 | compare (Perm { r: r1, w: w1, x: x1 }) (Perm { r: r2, w: w2, x: x2 }) = 47 | compare [ r1, w1, x1 ] [ r2, w2, x2 ] 48 | 49 | instance showPerm :: Show Perm where 50 | show p | p == none = "none" 51 | show p | p == all = "all" 52 | show (Perm { r: r, w: w, x: x }) = 53 | joinWith " + " ps 54 | where 55 | ps = 56 | (if r then [ "read" ] else []) 57 | <> (if w then [ "write" ] else []) 58 | <> 59 | (if x then [ "execute" ] else []) 60 | 61 | instance semiringPerm :: Semiring Perm where 62 | add (Perm { r: r0, w: w0, x: x0 }) (Perm { r: r1, w: w1, x: x1 }) = 63 | Perm { r: r0 || r1, w: w0 || w1, x: x0 || x1 } 64 | zero = Perm { r: false, w: false, x: false } 65 | mul (Perm { r: r0, w: w0, x: x0 }) (Perm { r: r1, w: w1, x: x1 }) = 66 | Perm { r: r0 && r1, w: w0 && w1, x: x0 && x1 } 67 | one = Perm { r: true, w: true, x: true } 68 | 69 | -- | Create a `Perm` value. The arguments represent the readable, writable, and 70 | -- | executable permissions, in that order. 71 | mkPerm :: Boolean -> Boolean -> Boolean -> Perm 72 | mkPerm r w x = Perm { r: r, w: w, x: x } 73 | 74 | -- | No permissions. This is the identity of the `Semiring` operation `(+)` 75 | -- | for `Perm`; that is, it is the same as `zero`. 76 | none :: Perm 77 | none = zero 78 | 79 | -- | The "readable" permission. 80 | read :: Perm 81 | read = Perm { r: true, w: false, x: false } 82 | 83 | -- | The "writable" permission. 84 | write :: Perm 85 | write = Perm { r: false, w: true, x: false } 86 | 87 | -- | The "executable" permission. 88 | execute :: Perm 89 | execute = Perm { r: false, w: false, x: true } 90 | 91 | -- | All permissions: readable, writable, and executable. This is the identity 92 | -- | of the `Semiring` operation `(*)` for `Perm`; that is, it is the same as 93 | -- | `one`. 94 | all :: Perm 95 | all = one 96 | 97 | -- | A `Perms` value includes all the permissions information about a 98 | -- | particular file or directory, by storing a `Perm` value for each of the 99 | -- | file owner, the group, and any other users. 100 | newtype Perms = Perms { u :: Perm, g :: Perm, o :: Perm } 101 | 102 | derive newtype instance eqPerms :: Eq Perms 103 | 104 | instance ordPerms :: Ord Perms where 105 | compare (Perms { u: u1, g: g1, o: o1 }) (Perms { u: u2, g: g2, o: o2 }) = 106 | compare [ u1, g1, o1 ] [ u2, g2, o2 ] 107 | 108 | instance showPerms :: Show Perms where 109 | show (Perms { u: u, g: g, o: o }) = 110 | "mkPerms " <> joinWith " " (f <$> [ u, g, o ]) 111 | where 112 | f perm = 113 | let 114 | str = show perm 115 | in 116 | if isNothing $ indexOf (Pattern " ") str then str 117 | else "(" <> str <> ")" 118 | 119 | -- | Attempt to parse a `Perms` value from a `String` containing an octal 120 | -- | integer. For example, 121 | -- | `permsFromString "0644" == Just (mkPerms (read + write) read read)`. 122 | permsFromString :: String -> Maybe Perms 123 | permsFromString = _perms <<< toCharArray <<< dropPrefix zeroChar 124 | where 125 | zeroChar = unsafePartial $ fromJust $ toEnum 48 126 | 127 | dropPrefix x xs 128 | | charAt 0 xs == Just x = drop 1 xs 129 | | otherwise = xs 130 | 131 | _perms [ u, g, o ] = 132 | mkPerms <$> permFromChar u 133 | <*> permFromChar g 134 | <*> permFromChar o 135 | _perms _ = Nothing 136 | 137 | permFromChar :: Char -> Maybe Perm 138 | permFromChar c = case c of 139 | '0' -> Just $ none 140 | '1' -> Just $ execute 141 | '2' -> Just $ write 142 | '3' -> Just $ write + execute 143 | '4' -> Just $ read 144 | '5' -> Just $ read + execute 145 | '6' -> Just $ read + write 146 | '7' -> Just $ read + write + execute 147 | _ -> Nothing 148 | 149 | -- | Create a `Perms` value. The arguments represent the owner's, group's, and 150 | -- | other users' permission sets, respectively. 151 | mkPerms :: Perm -> Perm -> Perm -> Perms 152 | mkPerms u g o = Perms { u: u, g: g, o: o } 153 | 154 | permsAll :: Perms 155 | permsAll = mkPerms all all all 156 | 157 | permsReadWrite :: Perms 158 | permsReadWrite = mkPerms all all none 159 | 160 | -- | Convert a `Perm` to an octal digit. For example: 161 | -- | 162 | -- | * `permToInt r == 4` 163 | -- | * `permToInt w == 2` 164 | -- | * `permToInt (r + w) == 6` 165 | permToInt :: Perm -> Int 166 | permToInt (Perm { r: r, w: w, x: x }) = 167 | (if r then 4 else 0) 168 | + (if w then 2 else 0) 169 | + (if x then 1 else 0) 170 | 171 | -- | Convert a `Perm` to an octal string, via `permToInt`. 172 | permToString :: Perm -> String 173 | permToString = show <<< permToInt 174 | 175 | -- | Convert a `Perms` value to an octal string, in a format similar to that 176 | -- | accepted by `chmod`. For example: 177 | -- | `permsToString (mkPerms (read + write) read read) == "0644"` 178 | permsToString :: Perms -> String 179 | permsToString (Perms { u: u, g: g, o: o }) = 180 | "0" 181 | <> permToString u 182 | <> permToString g 183 | <> permToString o 184 | 185 | -- | Convert a `Perms` value to an `Int`, via `permsToString`. 186 | permsToInt :: Perms -> Int 187 | permsToInt = unsafePartial $ fromJust <<< fromStringAs octal <<< permsToString 188 | -------------------------------------------------------------------------------- /src/Node/FS/Stats.js: -------------------------------------------------------------------------------- 1 | export { inspect as showStatsObj } from "util"; 2 | 3 | export const isBlockDeviceImpl = (s) => s.isBlockDevice(); 4 | export const isCharacterDeviceImpl = (s) => s.isCharacterDevice(); 5 | export const isDirectoryImpl = (s) => s.isDirectory(); 6 | export const isFIFOImpl = (s) => s.isFIFO(); 7 | export const isFileImpl = (s) => s.isFile(); 8 | export const isSocketImpl = (s) => s.isSocket(); 9 | export const isSymbolicLinkImpl = (s) => s.isSymbolicLink(); 10 | export const devImpl = (s) => s.dev; 11 | export const inodeImpl = (s) => s.ino; 12 | export const modeImpl = (s) => s.mode; 13 | export const nlinkImpl = (s) => s.nlink; 14 | export const uidImpl = (s) => s.uid; 15 | export const gidImpl = (s) => s.gid; 16 | export const rdevImpl = (s) => s.rdev; 17 | export const sizeImpl = (s) => s.size; 18 | export const blkSizeImpl = (s) => s.blkSize; 19 | export const blocksImpl = (s) => s.blocks; 20 | export const accessedTimeMsImpl = (s) => s.atimeMs; 21 | export const modifiedTimeMsImpl = (s) => s.mtimeMs; 22 | export const statusChangedTimeMsImpl = (s) => s.ctimeMs; 23 | export const birthtimeMsImpl = (s) => s.birthtimeMs; 24 | export const accessedTimeImpl = (s) => s.atime; 25 | export const modifiedTimeImpl = (s) => s.mtime; 26 | export const statusChangedTimeImpl = (s) => s.ctime; 27 | export const birthTimeImpl = (s) => s.birthtime; 28 | -------------------------------------------------------------------------------- /src/Node/FS/Stats.purs: -------------------------------------------------------------------------------- 1 | module Node.FS.Stats 2 | ( Stats 3 | , isFile 4 | , isDirectory 5 | , isBlockDevice 6 | , isCharacterDevice 7 | , isFIFO 8 | , isSocket 9 | , isSymbolicLink 10 | , dev 11 | , inode 12 | , mode 13 | , nlink 14 | , uid 15 | , gid 16 | , rdev 17 | , size 18 | , blkSize 19 | , blocks 20 | , accessedTimeMs 21 | , modifiedTimeMs 22 | , statusChangedTimeMs 23 | , birthtimeMs 24 | , accessedTime 25 | , modifiedTime 26 | , statusChangedTime 27 | , birthTime 28 | ) where 29 | 30 | import Prelude 31 | 32 | import Data.DateTime (DateTime) 33 | import Data.Function.Uncurried (Fn1, runFn1) 34 | import Data.JSDate (JSDate, toDateTime) 35 | import Data.Maybe (Maybe(..)) 36 | import Data.Time.Duration (Milliseconds) 37 | import Partial.Unsafe (unsafeCrashWith) 38 | 39 | foreign import data Stats :: Type 40 | 41 | foreign import showStatsObj :: Stats -> String 42 | 43 | instance showStats :: Show Stats where 44 | show s = "Stats " <> showStatsObj s 45 | 46 | isBlockDevice :: Stats -> Boolean 47 | isBlockDevice s = runFn1 isBlockDeviceImpl s 48 | 49 | foreign import isBlockDeviceImpl :: Fn1 (Stats) (Boolean) 50 | 51 | isCharacterDevice :: Stats -> Boolean 52 | isCharacterDevice s = runFn1 isCharacterDeviceImpl s 53 | 54 | foreign import isCharacterDeviceImpl :: Fn1 (Stats) (Boolean) 55 | 56 | -- | Returns true if the object describes a file system directory. 57 | -- | If the `fs.Stats`> object was obtained from `fs.lstat()`, 58 | -- | this method will always return `false``. This is because `fs.lstat()` 59 | -- | returns information about a symbolic link itself and not the path to which it resolves. 60 | isDirectory :: Stats -> Boolean 61 | isDirectory s = runFn1 isDirectoryImpl s 62 | 63 | foreign import isDirectoryImpl :: Fn1 (Stats) (Boolean) 64 | 65 | isFIFO :: Stats -> Boolean 66 | isFIFO s = runFn1 isFIFOImpl s 67 | 68 | foreign import isFIFOImpl :: Fn1 (Stats) (Boolean) 69 | 70 | isFile :: Stats -> Boolean 71 | isFile s = runFn1 isFileImpl s 72 | 73 | foreign import isFileImpl :: Fn1 (Stats) (Boolean) 74 | 75 | isSocket :: Stats -> Boolean 76 | isSocket s = runFn1 isSocketImpl s 77 | 78 | foreign import isSocketImpl :: Fn1 (Stats) (Boolean) 79 | 80 | isSymbolicLink :: Stats -> Boolean 81 | isSymbolicLink s = runFn1 isSymbolicLinkImpl s 82 | 83 | foreign import isSymbolicLinkImpl :: Fn1 (Stats) (Boolean) 84 | 85 | -- | The numeric identifier of the device containing the file. 86 | dev :: Stats -> Number 87 | dev s = runFn1 devImpl s 88 | 89 | foreign import devImpl :: Fn1 (Stats) (Number) 90 | 91 | -- | The file system specific "Inode" number for the file. 92 | inode :: Stats -> Number 93 | inode s = runFn1 inodeImpl s 94 | 95 | foreign import inodeImpl :: Fn1 (Stats) (Number) 96 | 97 | -- | A bit-field describing the file type and mode. 98 | mode :: Stats -> Number 99 | mode s = runFn1 modeImpl s 100 | 101 | foreign import modeImpl :: Fn1 (Stats) (Number) 102 | 103 | -- | The number of hard-links that exist for the file. 104 | nlink :: Stats -> Number 105 | nlink s = runFn1 nlinkImpl s 106 | 107 | foreign import nlinkImpl :: Fn1 (Stats) (Number) 108 | 109 | -- | The numeric user identifier of the user that owns the file (POSIX). 110 | uid :: Stats -> Number 111 | uid s = runFn1 uidImpl s 112 | 113 | foreign import uidImpl :: Fn1 (Stats) (Number) 114 | 115 | -- | The numeric group identifier of the group that owns the file (POSIX). 116 | gid :: Stats -> Number 117 | gid s = runFn1 gidImpl s 118 | 119 | foreign import gidImpl :: Fn1 (Stats) (Number) 120 | 121 | -- | A numeric device identifier if the file represents a device. 122 | rdev :: Stats -> Number 123 | rdev s = runFn1 rdevImpl s 124 | 125 | foreign import rdevImpl :: Fn1 (Stats) (Number) 126 | 127 | -- | The size of the file in bytes. 128 | -- | If the underlying file system does not support getting the size of the file, this will be 0. 129 | size :: Stats -> Number 130 | size s = runFn1 sizeImpl s 131 | 132 | foreign import sizeImpl :: Fn1 (Stats) (Number) 133 | 134 | -- | The file system block size for i/o operations. 135 | blkSize :: Stats -> Number 136 | blkSize s = runFn1 blkSizeImpl s 137 | 138 | foreign import blkSizeImpl :: Fn1 (Stats) (Number) 139 | 140 | -- | The number of blocks allocated for this file. 141 | blocks :: Stats -> Number 142 | blocks s = runFn1 blocksImpl s 143 | 144 | foreign import blocksImpl :: Fn1 (Stats) (Number) 145 | 146 | accessedTimeMs :: Stats -> Milliseconds 147 | accessedTimeMs s = runFn1 accessedTimeMsImpl s 148 | 149 | foreign import accessedTimeMsImpl :: Fn1 (Stats) (Milliseconds) 150 | 151 | modifiedTimeMs :: Stats -> Milliseconds 152 | modifiedTimeMs s = runFn1 modifiedTimeMsImpl s 153 | 154 | foreign import modifiedTimeMsImpl :: Fn1 (Stats) (Milliseconds) 155 | 156 | statusChangedTimeMs :: Stats -> Milliseconds 157 | statusChangedTimeMs s = runFn1 statusChangedTimeMsImpl s 158 | 159 | foreign import statusChangedTimeMsImpl :: Fn1 (Stats) (Milliseconds) 160 | 161 | birthtimeMs :: Stats -> Milliseconds 162 | birthtimeMs s = runFn1 birthtimeMsImpl s 163 | 164 | foreign import birthtimeMsImpl :: Fn1 (Stats) (Milliseconds) 165 | 166 | accessedTime :: Stats -> DateTime 167 | accessedTime s = case toDateTime $ runFn1 accessedTimeImpl s of 168 | Just d -> d 169 | Nothing -> unsafeCrashWith $ "Impossible: `accessedTime` returned invalid DateTime value." 170 | 171 | foreign import accessedTimeImpl :: Fn1 (Stats) (JSDate) 172 | 173 | modifiedTime :: Stats -> DateTime 174 | modifiedTime s = case toDateTime $ runFn1 modifiedTimeImpl s of 175 | Just d -> d 176 | Nothing -> unsafeCrashWith $ "Impossible: `modifiedTime` returned invalid DateTime value." 177 | 178 | foreign import modifiedTimeImpl :: Fn1 (Stats) (JSDate) 179 | 180 | statusChangedTime :: Stats -> DateTime 181 | statusChangedTime s = case toDateTime $ runFn1 statusChangedTimeImpl s of 182 | Just d -> d 183 | Nothing -> unsafeCrashWith $ "Impossible: `statusChangedTime` returned invalid DateTime value." 184 | 185 | foreign import statusChangedTimeImpl :: Fn1 (Stats) (JSDate) 186 | 187 | birthTime :: Stats -> DateTime 188 | birthTime s = case toDateTime $ runFn1 birthTimeImpl s of 189 | Just d -> d 190 | Nothing -> unsafeCrashWith $ "Impossible: `birthTime` returned invalid DateTime value." 191 | 192 | foreign import birthTimeImpl :: Fn1 (Stats) (JSDate) 193 | -------------------------------------------------------------------------------- /src/Node/FS/Stream.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | 3 | export const createReadStreamImpl = (path) => fs.createReadStream(path); 4 | export const createReadStreamOptsImpl = (path, opts) => fs.createReadStream(path, opts); 5 | export const fdCreateReadStreamImpl = (fd) => fs.createReadStream(null, { fd }); 6 | export const fdCreateReadStreamOptsImpl = (fd, opts) => fs.createReadStream(null, { ...opts, fd}); 7 | 8 | export const createWriteStreamImpl = (path) => fs.createWriteStream(path); 9 | export const createWriteStreamOptsImpl = (path, opts) => fs.createWriteStream(path, opts); 10 | export const fdCreateWriteStreamImpl = (fd) => fs.createWriteStream(null, { fd }); 11 | export const fdCreateWriteStreamOptsImpl = (fd, opts) => fs.createWriteStream(null, { ...opts, fd}); 12 | -------------------------------------------------------------------------------- /src/Node/FS/Stream.purs: -------------------------------------------------------------------------------- 1 | module Node.FS.Stream 2 | ( createWriteStream 3 | , WriteStreamOptions 4 | , createWriteStream' 5 | , fdCreateWriteStream 6 | , fdCreateWriteStream' 7 | , createReadStream 8 | , ReadStreamOptions 9 | , createReadStream' 10 | , fdCreateReadStream 11 | , fdCreateReadStream' 12 | ) where 13 | 14 | import Effect (Effect) 15 | import Effect.Uncurried (EffectFn1, EffectFn2, runEffectFn1, runEffectFn2) 16 | import Node.FS (FileDescriptor) 17 | import Node.Path (FilePath) 18 | import Node.Stream (Readable, Writable) 19 | import Prim.Row as Row 20 | 21 | -- | Create a Writable stream which writes data to the specified file, using 22 | -- | the default options. 23 | createWriteStream :: FilePath -> Effect (Writable ()) 24 | createWriteStream f = runEffectFn1 createWriteStreamImpl f 25 | 26 | foreign import createWriteStreamImpl :: EffectFn1 (FilePath) (Writable ()) 27 | 28 | type WriteStreamOptions = 29 | ( flags :: String 30 | , encoding :: String 31 | , mode :: Int 32 | , autoClose :: Boolean 33 | , emitClose :: Boolean 34 | , start :: Int 35 | ) 36 | 37 | -- | Create a Writable stream which writes data to the specified file. 38 | -- | Unused options should not be specified. Some options 39 | -- | (e.g. `flags`, `encoding`, and `mode`) should convert their 40 | -- | PureScript values to the corresponding JavaScript ones: 41 | -- | ``` 42 | -- | filePath # createWriteStream' 43 | -- | { flags: fileFlagsToNode R 44 | -- | , encoding: encodingToNode UTF8 45 | -- | , mode: permsToInt Perms.all 46 | -- | } 47 | -- | ``` 48 | createWriteStream' 49 | :: forall r trash 50 | . Row.Union r trash WriteStreamOptions 51 | => FilePath 52 | -> { | r } 53 | -> Effect (Writable ()) 54 | createWriteStream' f opts = runEffectFn2 createWriteStreamOptsImpl f opts 55 | 56 | foreign import createWriteStreamOptsImpl :: forall r. EffectFn2 (FilePath) ({ | r }) ((Writable ())) 57 | 58 | -- | Create a Writable stream which writes data to the specified file 59 | -- | descriptor, using the default options. 60 | fdCreateWriteStream :: FileDescriptor -> Effect (Writable ()) 61 | fdCreateWriteStream f = runEffectFn1 fdCreateWriteStreamImpl f 62 | 63 | foreign import fdCreateWriteStreamImpl :: EffectFn1 (FileDescriptor) (Writable ()) 64 | 65 | -- | Create a Writable stream which writes data to the specified file descriptor. 66 | -- | Unused options should not be specified. Some options 67 | -- | (e.g. `flags`, `encoding`, and `mode`) should convert their 68 | -- | PureScript values to the corresponding JavaScript ones: 69 | -- | ``` 70 | -- | filePath # fdCreateWriteStream' 71 | -- | { flags: fileFlagsToNode R 72 | -- | , encoding: encodingToNode UTF8 73 | -- | , mode: permsToInt Perms.all 74 | -- | } 75 | -- | ``` 76 | fdCreateWriteStream' 77 | :: forall r trash 78 | . Row.Union r trash WriteStreamOptions 79 | => FileDescriptor 80 | -> { | r } 81 | -> Effect (Writable ()) 82 | fdCreateWriteStream' f opts = runEffectFn2 fdCreateWriteStreamOptsImpl f opts 83 | 84 | foreign import fdCreateWriteStreamOptsImpl :: forall r. EffectFn2 (FileDescriptor) ({ | r }) (Writable ()) 85 | 86 | -- | Create a Readable stream which reads data to the specified file, using 87 | -- | the default options. 88 | createReadStream :: FilePath -> Effect (Readable ()) 89 | createReadStream p = runEffectFn1 createReadStreamImpl p 90 | 91 | foreign import createReadStreamImpl :: EffectFn1 (FilePath) (Readable ()) 92 | 93 | type ReadStreamOptions = 94 | ( flags :: String 95 | , encoding :: String 96 | , mode :: Int 97 | , autoClose :: Boolean 98 | , emitClose :: Boolean 99 | , start :: Int 100 | , end :: Int 101 | , highWaterMark :: Int 102 | ) 103 | 104 | -- | Create a Readable stream which reads data from the specified file. 105 | -- | Unused options should not be specified. Some options 106 | -- | (e.g. `flags`, `encoding`, and `mode`) should convert their 107 | -- | PureScript values to the corresponding JavaScript ones: 108 | -- | ``` 109 | -- | filePath # createReadStream' 110 | -- | { flags: fileFlagsToNode R 111 | -- | , encoding: encodingToNode UTF8 112 | -- | , mode: permsToInt Perms.all 113 | -- | } 114 | -- | ``` 115 | createReadStream' 116 | :: forall r trash 117 | . Row.Union r trash ReadStreamOptions 118 | => FilePath 119 | -> { | r } 120 | -> Effect (Readable ()) 121 | createReadStream' path opts = runEffectFn2 createReadStreamOptsImpl path opts 122 | 123 | foreign import createReadStreamOptsImpl :: forall r. EffectFn2 (FilePath) ({ | r }) ((Readable ())) 124 | 125 | -- | Create a Readable stream which reads data to the specified file 126 | -- | descriptor, using the default options. 127 | fdCreateReadStream :: FileDescriptor -> Effect (Readable ()) 128 | fdCreateReadStream f = runEffectFn1 fdCreateReadStreamImpl f 129 | 130 | foreign import fdCreateReadStreamImpl :: EffectFn1 (FileDescriptor) (Readable ()) 131 | 132 | -- | Create a Readable stream which reads data to the specified file descriptor. 133 | -- | Unused options should not be specified. Some options 134 | -- | (e.g. `flags`, `encoding`, and `mode`) should convert their 135 | -- | PureScript values to the corresponding JavaScript ones: 136 | -- | ``` 137 | -- | filePath # fdCreateReadStream' 138 | -- | { flags: fileFlagsToNode R 139 | -- | , encoding: encodingToNode UTF8 140 | -- | , mode: permsToInt Perms.all 141 | -- | } 142 | -- | ``` 143 | fdCreateReadStream' 144 | :: forall r trash 145 | . Row.Union r trash ReadStreamOptions 146 | => FileDescriptor 147 | -> { | r } 148 | -> Effect (Readable ()) 149 | fdCreateReadStream' f opts = runEffectFn2 fdCreateReadStreamOptsImpl f opts 150 | 151 | foreign import fdCreateReadStreamOptsImpl :: forall r. EffectFn2 (FileDescriptor) ({ | r }) ((Readable ())) 152 | 153 | -------------------------------------------------------------------------------- /src/Node/FS/Sync.js: -------------------------------------------------------------------------------- 1 | export { 2 | accessSync as accessImpl, 3 | copyFileSync as copyFileImpl, 4 | mkdtempSync as mkdtempImpl, 5 | renameSync as renameSyncImpl, 6 | truncateSync as truncateSyncImpl, 7 | chownSync as chownSyncImpl, 8 | chmodSync as chmodSyncImpl, 9 | statSync as statSyncImpl, 10 | lstatSync as lstatSyncImpl, 11 | linkSync as linkSyncImpl, 12 | symlinkSync as symlinkSyncImpl, 13 | readlinkSync as readlinkSyncImpl, 14 | realpathSync as realpathSyncImpl, 15 | unlinkSync as unlinkSyncImpl, 16 | rmdirSync as rmdirSyncImpl, 17 | rmSync as rmSyncImpl, 18 | mkdirSync as mkdirSyncImpl, 19 | readdirSync as readdirSyncImpl, 20 | utimesSync as utimesSyncImpl, 21 | readFileSync as readFileSyncImpl, 22 | writeFileSync as writeFileSyncImpl, 23 | appendFileSync as appendFileSyncImpl, 24 | existsSync as existsSyncImpl, 25 | openSync as openSyncImpl, 26 | readSync as readSyncImpl, 27 | writeSync as writeSyncImpl, 28 | fsyncSync as fsyncSyncImpl, 29 | closeSync as closeSyncImpl 30 | } from "node:fs"; 31 | -------------------------------------------------------------------------------- /src/Node/FS/Sync.purs: -------------------------------------------------------------------------------- 1 | module Node.FS.Sync 2 | ( access 3 | , access' 4 | , copyFile 5 | , copyFile' 6 | , mkdtemp 7 | , mkdtemp' 8 | , rename 9 | , truncate 10 | , chown 11 | , chmod 12 | , stat 13 | , lstat 14 | , link 15 | , symlink 16 | , readlink 17 | , realpath 18 | , realpath' 19 | , unlink 20 | , rmdir 21 | , rmdir' 22 | , rm 23 | , rm' 24 | , mkdir 25 | , mkdir' 26 | , readdir 27 | , utimes 28 | , readFile 29 | , readTextFile 30 | , writeFile 31 | , writeTextFile 32 | , appendFile 33 | , appendTextFile 34 | , exists 35 | , fdOpen 36 | , fdRead 37 | , fdNext 38 | , fdWrite 39 | , fdAppend 40 | , fdFlush 41 | , fdClose 42 | ) where 43 | 44 | import Prelude 45 | 46 | import Data.DateTime (DateTime) 47 | import Data.DateTime.Instant (fromDateTime, unInstant) 48 | import Data.Either (blush) 49 | import Data.Int (round) 50 | import Data.Maybe (Maybe(..)) 51 | import Data.Nullable (Nullable, toNullable) 52 | import Data.Time.Duration (Milliseconds(..)) 53 | import Effect (Effect) 54 | import Effect.Exception (Error, try) 55 | import Effect.Uncurried (EffectFn1, EffectFn2, EffectFn3, EffectFn5, runEffectFn1, runEffectFn2, runEffectFn3, runEffectFn5) 56 | import Node.Buffer (Buffer, size) 57 | import Node.Encoding (Encoding(..), encodingToNode) 58 | import Node.FS (FileDescriptor, ByteCount, FilePosition, BufferLength, BufferOffset, FileMode, SymlinkType, symlinkTypeToNode) 59 | import Node.FS.Constants (AccessMode, CopyMode, FileFlags, defaultAccessMode, defaultCopyMode, fileFlagsToNode) 60 | import Node.FS.Perms (Perms, permsToString, all, mkPerms) 61 | import Node.FS.Stats (Stats) 62 | import Node.Path (FilePath) 63 | 64 | access :: FilePath -> Effect (Maybe Error) 65 | access = flip access' defaultAccessMode 66 | 67 | access' :: FilePath -> AccessMode -> Effect (Maybe Error) 68 | access' path mode = do 69 | map blush $ try $ runEffectFn2 accessImpl path mode 70 | 71 | foreign import accessImpl :: EffectFn2 FilePath AccessMode (Maybe Error) 72 | 73 | copyFile :: FilePath -> FilePath -> Effect Unit 74 | copyFile src dest = runEffectFn3 copyFileImpl src dest defaultCopyMode 75 | 76 | copyFile' :: FilePath -> FilePath -> CopyMode -> Effect Unit 77 | copyFile' src dest mode = runEffectFn3 copyFileImpl src dest mode 78 | 79 | foreign import copyFileImpl :: EffectFn3 FilePath FilePath CopyMode Unit 80 | 81 | mkdtemp :: String -> Effect String 82 | mkdtemp prefix = mkdtemp' prefix UTF8 83 | 84 | mkdtemp' :: String -> Encoding -> Effect String 85 | mkdtemp' prefix encoding = runEffectFn2 mkdtempImpl prefix (encodingToNode encoding) 86 | 87 | foreign import mkdtempImpl :: EffectFn2 String String String 88 | 89 | foreign import renameSyncImpl :: EffectFn2 FilePath FilePath Unit 90 | foreign import truncateSyncImpl :: EffectFn2 FilePath Int Unit 91 | foreign import chownSyncImpl :: EffectFn3 FilePath Int Int Unit 92 | foreign import chmodSyncImpl :: EffectFn2 FilePath String Unit 93 | foreign import statSyncImpl :: EffectFn1 FilePath Stats 94 | foreign import lstatSyncImpl :: EffectFn1 FilePath Stats 95 | foreign import linkSyncImpl :: EffectFn2 FilePath FilePath Unit 96 | foreign import symlinkSyncImpl :: EffectFn3 FilePath FilePath String Unit 97 | foreign import readlinkSyncImpl :: EffectFn1 FilePath FilePath 98 | foreign import realpathSyncImpl :: forall cache. EffectFn2 FilePath { | cache } FilePath 99 | foreign import unlinkSyncImpl :: EffectFn1 FilePath Unit 100 | foreign import rmdirSyncImpl :: EffectFn2 FilePath { maxRetries :: Int, retryDelay :: Int } Unit 101 | foreign import rmSyncImpl :: EffectFn2 FilePath { force :: Boolean, maxRetries :: Int, recursive :: Boolean, retryDelay :: Int } Unit 102 | foreign import mkdirSyncImpl :: EffectFn2 FilePath { recursive :: Boolean, mode :: String } Unit 103 | foreign import readdirSyncImpl :: EffectFn1 FilePath (Array FilePath) 104 | foreign import utimesSyncImpl :: EffectFn3 FilePath Int Int Unit 105 | foreign import readFileSyncImpl :: forall a opts. EffectFn2 FilePath { | opts } a 106 | foreign import writeFileSyncImpl :: forall a opts. EffectFn3 FilePath a { | opts } Unit 107 | foreign import appendFileSyncImpl :: forall a opts. EffectFn3 FilePath a { | opts } Unit 108 | foreign import existsSyncImpl :: EffectFn1 FilePath Boolean 109 | foreign import openSyncImpl :: EffectFn3 FilePath String (Nullable FileMode) FileDescriptor 110 | foreign import readSyncImpl :: EffectFn5 FileDescriptor Buffer BufferOffset BufferLength (Nullable FilePosition) ByteCount 111 | foreign import writeSyncImpl :: EffectFn5 FileDescriptor Buffer BufferOffset BufferLength (Nullable FilePosition) ByteCount 112 | foreign import fsyncSyncImpl :: EffectFn1 FileDescriptor Unit 113 | foreign import closeSyncImpl :: EffectFn1 FileDescriptor Unit 114 | 115 | -- | Renames a file. 116 | rename :: FilePath -> FilePath -> Effect Unit 117 | rename oldFile newFile = runEffectFn2 renameSyncImpl oldFile newFile 118 | 119 | -- | Truncates a file to the specified length. 120 | truncate 121 | :: FilePath 122 | -> Int 123 | -> Effect Unit 124 | truncate file len = runEffectFn2 truncateSyncImpl file len 125 | 126 | -- | Changes the ownership of a file. 127 | chown 128 | :: FilePath 129 | -> Int 130 | -> Int 131 | -> Effect Unit 132 | chown file uid gid = runEffectFn3 chownSyncImpl file uid gid 133 | 134 | -- | Changes the permissions of a file. 135 | chmod 136 | :: FilePath 137 | -> Perms 138 | -> Effect Unit 139 | chmod file perms = runEffectFn2 chmodSyncImpl file (permsToString perms) 140 | 141 | -- | Gets file statistics. 142 | stat 143 | :: FilePath 144 | -> Effect Stats 145 | stat file = runEffectFn1 statSyncImpl file 146 | 147 | -- | Gets file or symlink statistics. `lstat` is identical to `stat`, except 148 | -- | that if the `FilePath` is a symbolic link, then the link itself is stat-ed, 149 | -- | not the file that it refers to. 150 | lstat 151 | :: FilePath 152 | -> Effect Stats 153 | lstat file = runEffectFn1 lstatSyncImpl file 154 | 155 | -- | Creates a link to an existing file. 156 | link 157 | :: FilePath 158 | -> FilePath 159 | -> Effect Unit 160 | link src dst = runEffectFn2 linkSyncImpl src dst 161 | 162 | -- | Creates a symlink. 163 | symlink 164 | :: FilePath 165 | -> FilePath 166 | -> SymlinkType 167 | -> Effect Unit 168 | symlink src dst ty = runEffectFn3 symlinkSyncImpl src dst (symlinkTypeToNode ty) 169 | 170 | -- | Reads the value of a symlink. 171 | readlink 172 | :: FilePath 173 | -> Effect FilePath 174 | readlink path = runEffectFn1 readlinkSyncImpl path 175 | 176 | -- | Find the canonicalized absolute location for a path. 177 | realpath 178 | :: FilePath 179 | -> Effect FilePath 180 | realpath path = runEffectFn2 realpathSyncImpl path {} 181 | 182 | -- | Find the canonicalized absolute location for a path using a cache object for 183 | -- | already resolved paths. 184 | realpath' 185 | :: forall cache 186 | . FilePath 187 | -> { | cache } 188 | -> Effect FilePath 189 | realpath' path cache = runEffectFn2 realpathSyncImpl path cache 190 | 191 | -- | Deletes a file. 192 | unlink 193 | :: FilePath 194 | -> Effect Unit 195 | unlink file = runEffectFn1 unlinkSyncImpl file 196 | 197 | -- | Deletes a directory. 198 | rmdir 199 | :: FilePath 200 | -> Effect Unit 201 | rmdir path = rmdir' path { maxRetries: 0, retryDelay: 100 } 202 | 203 | -- | Deletes a directory with options. 204 | rmdir' 205 | :: FilePath 206 | -> { maxRetries :: Int, retryDelay :: Int } 207 | -> Effect Unit 208 | rmdir' path opts = runEffectFn2 rmdirSyncImpl path opts 209 | 210 | -- | Deletes a file or directory. 211 | rm 212 | :: FilePath 213 | -> Effect Unit 214 | rm path = rm' path { force: false, maxRetries: 100, recursive: false, retryDelay: 1000 } 215 | 216 | -- | Deletes a file or directory with options. 217 | rm' 218 | :: FilePath 219 | -> { force :: Boolean, maxRetries :: Int, recursive :: Boolean, retryDelay :: Int } 220 | -> Effect Unit 221 | rm' path opts = runEffectFn2 rmSyncImpl path opts 222 | 223 | -- | Makes a new directory. 224 | mkdir 225 | :: FilePath 226 | -> Effect Unit 227 | mkdir path = mkdir' path { recursive: false, mode: mkPerms all all all } 228 | 229 | -- | Makes a new directory with the specified permissions. 230 | mkdir' 231 | :: FilePath 232 | -> { recursive :: Boolean, mode :: Perms } 233 | -> Effect Unit 234 | mkdir' file { recursive, mode: perms } = runEffectFn2 mkdirSyncImpl file { recursive, mode: permsToString perms } 235 | 236 | -- | Reads the contents of a directory. 237 | readdir 238 | :: FilePath 239 | -> Effect (Array FilePath) 240 | readdir file = runEffectFn1 readdirSyncImpl file 241 | 242 | -- | Sets the accessed and modified times for the specified file. 243 | utimes 244 | :: FilePath 245 | -> DateTime 246 | -> DateTime 247 | -> Effect Unit 248 | utimes file atime mtime = runEffectFn3 utimesSyncImpl file (fromDate atime) (fromDate mtime) 249 | where 250 | fromDate date = ms (toEpochMilliseconds date) / 1000 251 | ms (Milliseconds n) = round n 252 | toEpochMilliseconds = unInstant <<< fromDateTime 253 | 254 | -- | Reads the entire contents of a file returning the result as a raw buffer. 255 | readFile 256 | :: FilePath 257 | -> Effect Buffer 258 | readFile file = runEffectFn2 readFileSyncImpl file {} 259 | 260 | -- | Reads the entire contents of a text file with the specified encoding. 261 | readTextFile 262 | :: Encoding 263 | -> FilePath 264 | -> Effect String 265 | readTextFile encoding file = runEffectFn2 readFileSyncImpl file { encoding: show encoding } 266 | 267 | -- | Writes a buffer to a file. 268 | writeFile 269 | :: FilePath 270 | -> Buffer 271 | -> Effect Unit 272 | writeFile file buff = runEffectFn3 writeFileSyncImpl file buff {} 273 | 274 | -- | Writes text to a file using the specified encoding. 275 | writeTextFile 276 | :: Encoding 277 | -> FilePath 278 | -> String 279 | -> Effect Unit 280 | writeTextFile encoding file text = runEffectFn3 writeFileSyncImpl file text { encoding: show encoding } 281 | 282 | -- | Appends the contents of a buffer to a file. 283 | appendFile 284 | :: FilePath 285 | -> Buffer 286 | -> Effect Unit 287 | appendFile file buff = runEffectFn3 appendFileSyncImpl file buff {} 288 | 289 | -- | Appends text to a file using the specified encoding. 290 | appendTextFile 291 | :: Encoding 292 | -> FilePath 293 | -> String 294 | -> Effect Unit 295 | appendTextFile encoding file buff = runEffectFn3 appendFileSyncImpl file buff { encoding: show encoding } 296 | 297 | -- | Check if the path exists. 298 | exists 299 | :: FilePath 300 | -> Effect Boolean 301 | exists file = runEffectFn1 existsSyncImpl file 302 | 303 | -- | Open a file synchronously. See the [Node documentation](http://nodejs.org/api/fs.html#fs_fs_opensync_path_flags_mode) 304 | -- | for details. 305 | fdOpen 306 | :: FilePath 307 | -> FileFlags 308 | -> Maybe FileMode 309 | -> Effect FileDescriptor 310 | fdOpen file flags mode = runEffectFn3 openSyncImpl file (fileFlagsToNode flags) (toNullable mode) 311 | 312 | -- | Read from a file synchronously. See the [Node documentation](http://nodejs.org/api/fs.html#fs_fs_readsync_fd_buffer_offset_length_position) 313 | -- | for details. 314 | fdRead 315 | :: FileDescriptor 316 | -> Buffer 317 | -> BufferOffset 318 | -> BufferLength 319 | -> Maybe FilePosition 320 | -> Effect ByteCount 321 | fdRead fd buff off len pos = 322 | runEffectFn5 readSyncImpl fd buff off len (toNullable pos) 323 | 324 | -- | Convenience function to fill the whole buffer from the current 325 | -- | file position. 326 | fdNext 327 | :: FileDescriptor 328 | -> Buffer 329 | -> Effect ByteCount 330 | fdNext fd buff = do 331 | sz <- size buff 332 | fdRead fd buff 0 sz Nothing 333 | 334 | -- | Write to a file synchronously. See the [Node documentation](http://nodejs.org/api/fs.html#fs_fs_writesync_fd_buffer_offset_length_position) 335 | -- | for details. 336 | fdWrite 337 | :: FileDescriptor 338 | -> Buffer 339 | -> BufferOffset 340 | -> BufferLength 341 | -> Maybe FilePosition 342 | -> Effect ByteCount 343 | fdWrite fd buff off len pos = 344 | runEffectFn5 writeSyncImpl fd buff off len (toNullable pos) 345 | 346 | -- | Convenience function to append the whole buffer to the current 347 | -- | file position. 348 | fdAppend 349 | :: FileDescriptor 350 | -> Buffer 351 | -> Effect ByteCount 352 | fdAppend fd buff = do 353 | sz <- size buff 354 | fdWrite fd buff 0 sz Nothing 355 | 356 | -- | Flush a file synchronously. See the [Node documentation](http://nodejs.org/api/fs.html#fs_fs_fsyncsync_fd) 357 | -- | for details. 358 | fdFlush 359 | :: FileDescriptor 360 | -> Effect Unit 361 | fdFlush fd = runEffectFn1 fsyncSyncImpl fd 362 | 363 | -- | Close a file synchronously. See the [Node documentation](http://nodejs.org/api/fs.html#fs_fs_closesync_fd) 364 | -- | for details. 365 | fdClose 366 | :: FileDescriptor 367 | -> Effect Unit 368 | fdClose fd = runEffectFn1 closeSyncImpl fd 369 | -------------------------------------------------------------------------------- /test/Main.purs: -------------------------------------------------------------------------------- 1 | module Test.Main where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Test as Test 7 | import Test.Streams as Streams 8 | import TestAff as TestAff 9 | import TestAsync as TestAsync 10 | 11 | main :: Effect Unit 12 | main = do 13 | Test.main 14 | TestAsync.main 15 | Streams.main 16 | TestAff.main 17 | -------------------------------------------------------------------------------- /test/Streams.purs: -------------------------------------------------------------------------------- 1 | module Test.Streams where 2 | 3 | import Prelude 4 | 5 | import Effect (Effect) 6 | import Effect.Console (log) 7 | import Node.Encoding (Encoding(..)) 8 | import Node.EventEmitter (on_) 9 | import Node.FS.Stream (createWriteStream, createReadStream) 10 | import Node.FS.Sync as Sync 11 | import Node.Path as Path 12 | import Node.Stream as Stream 13 | 14 | main :: Effect Unit 15 | main = do 16 | let fp = Path.concat 17 | 18 | log "Testing streams" 19 | 20 | r <- createReadStream (fp [ "test", "Streams.purs" ]) 21 | w <- createWriteStream (fp [ "tmp", "Streams.purs" ]) 22 | 23 | _ <- Stream.pipe r w 24 | 25 | r # on_ Stream.endH do 26 | src <- Sync.readTextFile UTF8 (fp [ "test", "Streams.purs" ]) 27 | dst <- Sync.readTextFile UTF8 (fp [ "tmp", "Streams.purs" ]) 28 | 29 | if src == dst then log "all good" 30 | else log "not good" 31 | -------------------------------------------------------------------------------- /test/Test.purs: -------------------------------------------------------------------------------- 1 | module Test where 2 | 3 | import Prelude 4 | 5 | import Data.Either (Either(..), either) 6 | import Data.Maybe (Maybe(..), isNothing) 7 | import Data.Traversable (for_, traverse) 8 | import Effect (Effect) 9 | import Effect.Console (log) 10 | import Effect.Exception (Error, catchException, error, message, throw, throwException, try) 11 | import Node.Buffer as Buffer 12 | import Node.Encoding (Encoding(..)) 13 | import Node.FS (FileFlags(..), SymlinkType(..)) 14 | import Node.FS.Async as A 15 | import Node.FS.Constants (copyFile_EXCL, r_OK, w_OK) 16 | import Node.FS.Perms (mkPerms, permsAll) 17 | import Node.FS.Perms as Perms 18 | import Node.FS.Stats (statusChangedTime, accessedTime, modifiedTime, isSymbolicLink, isSocket, isFIFO, isCharacterDevice, isBlockDevice, isDirectory, isFile) 19 | import Node.FS.Sync (chmod) 20 | import Node.FS.Sync as S 21 | import Node.Path as Path 22 | import Unsafe.Coerce (unsafeCoerce) 23 | 24 | -- Cheat to allow `main` to type check. See also issue #5 in 25 | -- purescript-exceptions. 26 | catchException' 27 | :: forall a 28 | . (Error -> Effect a) 29 | -> Effect a 30 | -> Effect a 31 | catchException' = unsafeCoerce catchException 32 | 33 | main :: Effect Unit 34 | main = do 35 | let fp = Path.concat 36 | 37 | e <- S.exists (fp [ "test", "Test.purs" ]) 38 | log $ "Test.purs exists? " <> show e 39 | 40 | file <- S.readTextFile UTF8 (fp [ "test", "Test.purs" ]) 41 | log "\n\nreadTextFile sync result:" 42 | log $ file 43 | 44 | _ <- 45 | catchException' 46 | ( \err -> do 47 | log $ "Caught readTextFile error:\n" <> show err 48 | pure "" 49 | ) $ S.readTextFile UTF8 (fp [ "test", "does not exist" ]) 50 | 51 | -- If an error is thrown, it's probably EEXIST, so ignore it. Should 52 | -- really check this instead. 53 | catchException' (const (pure unit)) (S.mkdir "tmp") 54 | 55 | S.writeTextFile ASCII (fp [ "tmp", "Test.js" ]) "console.log('hello world')" 56 | S.rename (fp [ "tmp", "Test.js" ]) (fp [ "tmp", "Test1.js" ]) 57 | 58 | S.truncate (fp [ "tmp", "Test1.js" ]) 1000 59 | 60 | stats <- S.stat (fp [ "tmp", "Test1.js" ]) 61 | log "\n\nS.stat:" 62 | log "isFile:" 63 | log $ show $ isFile stats 64 | log "isDirectory:" 65 | log $ show $ isDirectory stats 66 | log "isBlockDevice:" 67 | log $ show $ isBlockDevice stats 68 | log "isCharacterDevice:" 69 | log $ show $ isCharacterDevice stats 70 | log "isFIFO:" 71 | log $ show $ isFIFO stats 72 | log "isSocket:" 73 | log $ show $ isSocket stats 74 | log "isSymbolicLink:" 75 | log $ show $ isSymbolicLink stats 76 | log "modifiedTime:" 77 | log $ show $ modifiedTime stats 78 | log "accessedTime:" 79 | log $ show $ accessedTime stats 80 | log "statusChangedTime:" 81 | log $ show $ statusChangedTime stats 82 | 83 | S.symlink (fp [ "tmp", "Test1.js" ]) (fp [ "tmp", "TestSymlink.js" ]) FileLink 84 | 85 | lstats <- S.lstat (fp [ "tmp", "TestSymlink.js" ]) 86 | log "\n\nS.lstat:" 87 | log "isSymbolicLink:" 88 | log $ show $ isSymbolicLink lstats 89 | 90 | S.unlink (fp [ "tmp", "TestSymlink.js" ]) 91 | 92 | A.rename (fp [ "tmp", "Test1.js" ]) (fp [ "tmp", "Test.js" ]) $ \x -> do 93 | log "\n\nrename result:" 94 | either (log <<< show) (log <<< show) x 95 | 96 | A.truncate (fp [ "tmp", "Test.js" ]) 10 $ \y -> do 97 | log "\n\ntruncate result:" 98 | either (log <<< show) (log <<< show) y 99 | 100 | A.readFile (fp [ "test", "Test.purs" ]) $ \mbuf -> do 101 | buf <- traverse Buffer.freeze mbuf 102 | log "\n\nreadFile result:" 103 | either (log <<< show) (log <<< show) buf 104 | 105 | A.readTextFile UTF8 (fp [ "test", "Test.purs" ]) $ \x -> do 106 | log "\n\nreadTextFile result:" 107 | either (log <<< show) log x 108 | 109 | A.stat (fp [ "test", "Test.purs" ]) $ \x -> do 110 | log "\n\nA.stat:" 111 | case x of 112 | Left err -> log $ "Error:" <> show err 113 | Right x' -> do 114 | log "isFile:" 115 | log $ show $ isFile x' 116 | log "isDirectory:" 117 | log $ show $ isDirectory x' 118 | log "isBlockDevice:" 119 | log $ show $ isBlockDevice x' 120 | log "isCharacterDevice:" 121 | log $ show $ isCharacterDevice x' 122 | log "isFIFO:" 123 | log $ show $ isFIFO x' 124 | log "isSocket:" 125 | log $ show $ isSocket x' 126 | log "isSymbolicLink:" 127 | log $ show $ isSymbolicLink x' 128 | log "modifiedTime:" 129 | log $ show $ modifiedTime x' 130 | log "accessedTime:" 131 | log $ show $ accessedTime x' 132 | log "statusChangedTime:" 133 | log $ show $ statusChangedTime x' 134 | 135 | A.symlink (fp [ "tmp", "Test.js" ]) (fp [ "tmp", "TestSymlink.js" ]) FileLink \u -> 136 | case u of 137 | Left err -> log $ "Error:" <> show err 138 | Right _ -> A.lstat (fp [ "tmp", "TestSymlink.js" ]) \s -> do 139 | log "\n\nA.lstat:" 140 | case s of 141 | Left err -> log $ "Error:" <> show err 142 | Right s' -> do 143 | log "isSymbolicLink:" 144 | log $ show $ isSymbolicLink s' 145 | 146 | A.unlink (fp [ "tmp", "TestSymlink.js" ]) \result -> do 147 | log "\n\nA.unlink result:" 148 | either (log <<< show) (\_ -> log "Success") result 149 | 150 | let fdFile = fp [ "tmp", "FD.json" ] 151 | fd0 <- S.fdOpen fdFile W (Just 420) 152 | buf0 <- Buffer.fromString "[ 42 ]" UTF8 153 | bytes0 <- S.fdAppend fd0 buf0 154 | S.fdFlush fd0 155 | S.fdClose fd0 156 | fd1 <- S.fdOpen fdFile R Nothing 157 | buf1 <- Buffer.create =<< Buffer.size buf0 158 | bytes1 <- S.fdNext fd1 buf1 159 | S.fdClose fd1 160 | 161 | log "statSync on a non-existing file should be catchable" 162 | r <- catchException' 163 | (const (pure true)) 164 | (S.stat "this-does-not-exist" *> pure false) 165 | unless r $ 166 | throwException (error "FS.Sync.stat should have thrown") 167 | 168 | log "access tests" 169 | mbNotExistsErr <- S.access "./test/not-exists.txt" 170 | when (isNothing mbNotExistsErr) do 171 | throw "`access \"./test/not-exists.txt\"` should produce error" 172 | 173 | let readableFixturePath = "./test/fixtures/readable.txt" 174 | chmod readableFixturePath $ mkPerms Perms.read Perms.read Perms.read 175 | 176 | mbErr <- S.access' readableFixturePath r_OK 177 | for_ mbErr \err -> do 178 | throw $ "`access \"" <> readableFixturePath <> "\" R_OK` should not produce error.\n" <> message err 179 | mbWriteErr <- S.access' readableFixturePath w_OK 180 | case mbWriteErr of 181 | Just _ -> pure unit 182 | Nothing -> throw $ "`access \"" <> readableFixturePath <> "\" W_OK` should produce error" 183 | 184 | log "copy tests" 185 | let outerTmpDir = "./test/node-fs-tests" 186 | S.mkdir' outerTmpDir { recursive: true, mode: permsAll } 187 | tempDir <- S.mkdtemp outerTmpDir 188 | S.mkdir' tempDir { recursive: true, mode: permsAll } 189 | let destReadPath = Path.concat [ tempDir, "readable.txt" ] 190 | S.copyFile readableFixturePath destReadPath 191 | unlessM (S.exists destReadPath) do 192 | throw $ destReadPath <> " does not exist after copy" 193 | 194 | copyErr <- try $ S.copyFile' readableFixturePath destReadPath copyFile_EXCL 195 | case copyErr of 196 | Left _ -> pure unit 197 | Right _ -> throw $ destReadPath <> " already exists, but copying a file to there did not throw an error with COPYFILE_EXCL option" 198 | 199 | -------------------------------------------------------------------------------- /test/TestAff.purs: -------------------------------------------------------------------------------- 1 | module TestAff where 2 | 3 | import Prelude 4 | 5 | import Data.Array (filterA) 6 | import Data.Maybe (maybe) 7 | import Data.String.CodeUnits (charAt, singleton) 8 | import Effect (Effect) 9 | import Effect.Aff (launchAff_) 10 | import Effect.Class (liftEffect) 11 | import Effect.Console (log) 12 | import Node.FS.Aff (stat, readdir) 13 | import Node.FS.Stats (isDirectory) 14 | 15 | main :: Effect Unit 16 | main = launchAff_ do 17 | files <- readdir "." 18 | files' <- flip filterA files \file -> do 19 | stat <- stat file 20 | pure $ isDirectory stat 21 | && (maybe false (singleton >>> (_ /= ".")) $ charAt 0 file) 22 | liftEffect $ log $ show files' 23 | -------------------------------------------------------------------------------- /test/TestAsync.purs: -------------------------------------------------------------------------------- 1 | module TestAsync where 2 | 3 | import Prelude (Unit, show, bind, discard, (<>), ($)) 4 | import Data.Either (Either(..)) 5 | import Data.Maybe (Maybe(..)) 6 | import Effect (Effect) 7 | import Effect.Console (log) 8 | import Node.FS (FileFlags(..)) 9 | import Node.FS.Async as A 10 | import Node.Path as FP 11 | import Node.Buffer as B 12 | 13 | -- exercise the file descriptor based async IO functions 14 | 15 | main :: Effect Unit 16 | main = do 17 | let 18 | path1 = FP.concat ([ "test", "TestAsync.purs" ]) 19 | path2 = FP.concat ([ "test", "TestAsync.purs.partial" ]) 20 | 21 | buf <- B.create 1000 22 | 23 | A.fdOpen path1 R Nothing $ \a -> case a of 24 | (Left err) -> log ("err:" <> show err) 25 | (Right fd) -> do 26 | log ("opened " <> path1) 27 | A.fdNext fd buf $ \b -> case b of 28 | (Left err) -> log ("err:" <> show err) 29 | (Right n8bits) -> do 30 | log ("read " <> show n8bits) 31 | A.fdOpen path2 W Nothing $ \c -> case c of 32 | (Left err) -> log ("err:" <> show err) 33 | (Right fd2) -> do 34 | log ("opened " <> path2) 35 | A.fdAppend fd2 buf $ \d -> case d of 36 | (Left err) -> log ("err:" <> show err) 37 | (Right nbytes) -> do 38 | log ("wrote " <> show nbytes) 39 | A.fdClose fd2 $ \e -> case e of 40 | (Left err) -> log ("err:" <> show err) 41 | (Right _) -> do 42 | log ("closed " <> path2) 43 | A.fdClose fd $ \f -> case f of 44 | (Left err) -> log ("err:" <> show err) 45 | (Right _) -> do 46 | log ("closed " <> path1) 47 | A.unlink path2 $ \g -> case g of 48 | (Left err) -> log ("err:" <> show err) 49 | (Right _) -> log ("unlinked " <> path2) 50 | -------------------------------------------------------------------------------- /test/fixtures/readable.txt: -------------------------------------------------------------------------------- 1 | foo 2 | --------------------------------------------------------------------------------