diff --git a/README.md b/README.md index 198e8eb..641f2ba 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ Deno IO methods as valid Task monads perfect to write great point-free software [![GitHub licence](https://img.shields.io/github/license/sebastienfilion/functional-deno-io)](https://github.com/sebastienfilion/functional-deno-io/blob/v0.1.0/LICENSE) * [File System](#file-system) - * [Network](#network) # Usage @@ -17,29 +16,29 @@ the [Fantasy-land specifications](https://github.com/fantasyland/fantasy-land). ```js import { compose, chain, curry } from "https://x.nest.land/ramda@0.27.0/source/index.js"; -import Either from "https://deno.land/x/functional@v0.5.0/Either.js"; -import Task from "https://deno.land/x/functional@v0.5.0/Task.js"; -import { File } from "https://deno.land/x/functional_io@v0.1.0/types.js"; -import { close, copyFile, create } from "https://deno.land/x/functional_io@v0.1.0/fs.js"; +import Either from "https://deno.land/x/functional@v0.5.2/Either.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; +import { Buffer, File } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import { close, copyFile, create } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; -const copyToNewFile = curry( - (sourceFile, destinationFile) => +const writeNewFile = curry( + (buffer, destinationFile) => File.isOrThrow(sourceFile) && File.isOrThrow(destinationFile) && compose( chain(close), - chain(copyFile(sourceFile)), + chain(writeAll(buffer)), create )(destinationFile) ); -// Calling `copyToNewFile` results in an instance of `Task` keeping the function pure. +// Calling `writeNewFile` results in an instance of `Task` keeping the function pure. assert( - Task.is(copyToNewFile(File.fromPath(`${Deno.cwd()}/hoge`), File.fromPath(`${Deno.cwd()}/piyo`))) + Task.is(writeNewFile(File.fromPath(`${Deno.cwd()}/hoge`), Buffer.fromString("Hello Deno"))) ); // Finally, calling `Task#run` will call copy to the new file and return a promise -copyToNewFile(File.fromPath(`${Deno.cwd()}/hoge`), File.fromPath(`${Deno.cwd()}/piyo`)).run() +writeNewFile(File.fromPath(`${Deno.cwd()}/hoge`), Buffer.fromString("Hello Deno")).run() .then(container => { // The returned value should be an instance of `Either.Right` or `Either.Left` assert(Either.Right.is(container)); @@ -52,6 +51,63 @@ copyToNewFile(File.fromPath(`${Deno.cwd()}/hoge`), File.fromPath(`${Deno.cwd()}/ ## File System +**⚠️ Note** `Deno.cwd` is used in the following example; if you use `Deno.cwd` to compose your paths, your functions +are no longer pure. + +### `Buffer` type + +The `Buffer` type represents a data buffer. + +```js +import { BUffer } from "https://deno.land/x/functional_io@v0.2.0/types.js"; + +const container = Buffer(new Uint8Array([ 65, 66, 67, 68, 69 ])); +``` + +#### `Buffer#fromString` + +The method creates a `Buffer` from a string. + +```js +const container = Buffer.fromString("ABCDE"); +``` + +This implementation of Buffer is a valid [`Functor`](https://github.com/fantasyland/fantasy-land#functor). + +### `Directory` type + +The `Directory` type represents a directory on the file system. + +It's only property is its path. + +A Directory is a valid Location. + +```js +import { Directory } from "https://deno.land/x/functional_io@v0.2.0/types.js"; + +const container = Directory(`${Deno.cwd()}/hoge`); +``` + +This implementation of Directory is a valid [`Functor`](https://github.com/fantasyland/fantasy-land#functor) and +[`Monad`](https://github.com/fantasyland/fantasy-land#monad). + +### `File` type + +The `File` type represents a file on the file system. + +It has 3 properties: a path, a buffer and a rid. + +A File is a valid Location and Resource. + +```js +import { File } from "https://deno.land/x/functional_io@v0.2.0/types.js"; + +const container = File(`${Deno.cwd()}/hoge`, new Uint8Array([ 65, 66, 67, 68, 69 ]), 3); +``` + +This implementation of Directory is a valid [`Functor`](https://github.com/fantasyland/fantasy-land#functor) and +[`Monad`](https://github.com/fantasyland/fantasy-land#monad). + ### `chdir` [📕](https://doc.deno.land/builtin/stable#Deno.chdir) Change the current working directory to the specified path. @@ -59,9 +115,9 @@ Change the current working directory to the specified path. `chdir :: Directory a -> Task e Directory a` ```js -import { chdir } from "https://deno.land/x/functional_io@v0.1.0/fs.js"; -import { Directory } from "https://deno.land/x/functional_io@v0.1.0/types.js"; -import Task from "https://deno.land/x/functional@v0.5.0/Task.js"; +import { chdir } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { Directory } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; const container = chdir(Directory("..")); @@ -75,9 +131,9 @@ Changes the permission of a specific file/directory of specified path. Ignores t `chmod :: Number -> File a -> Task e File a` ```js -import { chmod } from "https://deno.land/x/functional_io@v0.1.0/fs.js"; -import { File } from "https://deno.land/x/functional_io@v0.1.0/types.js"; -import Task from "https://deno.land/x/functional@v0.5.0/Task.js"; +import { chmod } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { File } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; const container = chmod(0o000, File.fromPath(`${Deno.cwd()}/hoge`)); @@ -91,9 +147,9 @@ Change owner of a regular file or directory. This functionality is not available `chown :: Number -> Number -> File a -> Task e File a` ```js -import { chown } from "https://deno.land/x/functional_io@v0.1.0/fs.js"; -import { File } from "https://deno.land/x/functional_io@v0.1.0/types.js"; -import Task from "https://deno.land/x/functional@v0.5.0/Task.js"; +import { chown } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { File } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; const container = chown(null, null, File.fromPath(`${Deno.cwd()}/hoge`)); @@ -108,9 +164,9 @@ Closing a file when you are finished with it is important to avoid leaking resou `copy :: File a -> Task e File a` ```js -import { close } from "https://deno.land/x/functional_io@v0.1.0/fs.js"; -import { File } from "https://deno.land/x/functional_io@v0.1.0/types.js"; -import Task from "https://deno.land/x/functional@v0.5.0/Task.js"; +import { close } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { File } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; const container = close(File(`${Deno.cwd()}/hoge`, new Uint8Array([]), 3)); @@ -124,9 +180,9 @@ Copies from a source to a destination until either EOF (null) is read from the s `copy :: Options -> Buffer a -> Buffer b -> Task e Writer b` ```js -import { copy } from "https://deno.land/x/functional_io@v0.1.0/fs.js"; -import { Buffer } from "https://deno.land/x/functional_io@v0.1.0/types.js"; -import Task from "https://deno.land/x/functional@v0.5.0/Task.js"; +import { copy } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { Buffer } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; const container = copy({}, Buffer(new Uint8Array([ 65, 66, 67, 68, 69 ])), Buffer(new Uint8Array([]))); @@ -141,9 +197,9 @@ else overwriting. Fails if target path is a directory or is unwritable. `copyFile :: File a -> File b -> Task e File b` ```js -import { copyFile } from "https://deno.land/x/functional_io@v0.1.0/fs.js"; -import { File } from "https://deno.land/x/functional_io@v0.1.0/types.js"; -import Task from "https://deno.land/x/functional@v0.5.0/Task.js"; +import { copyFile } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { File } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; const container = copyFile(File.fromPath(`${Deno.cwd()}/hoge`), File.fromPath(`${Deno.cwd()}/piyo`)); @@ -157,9 +213,9 @@ Creates a file if none exists or truncates an existing file. `create :: File a -> Task e File a` ```js -import { create } from "https://deno.land/x/functional_io@v0.1.0/fs.js"; -import { File } from "https://deno.land/x/functional_io@v0.1.0/types.js"; -import Task from "https://deno.land/x/functional@v0.5.0/Task.js"; +import { create } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { File } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; const container = create(File.fromPath(`${Deno.cwd()}/hoge`)); @@ -173,8 +229,8 @@ Return a Directory representation of the current working directory. `cwd :: () -> Task e Directory a` ```js -import { cwd } from "https://deno.land/x/functional_io@v0.1.0/fs.js"; -import Task from "https://deno.land/x/functional@v0.5.0/Task.js"; +import { cwd } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; const container = cwd(); @@ -189,9 +245,9 @@ If the directory does not exist, it is created. The directory itself is not dele `emptyDir :: Directory a -> Task e Directory a` ```js -import { emptyDir } from "https://deno.land/x/functional_io@v0.1.0/fs.js"; -import { Directory } from "https://deno.land/x/functional_io@v0.1.0/types.js"; -import Task from "https://deno.land/x/functional@v0.5.0/Task.js"; +import { emptyDir } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { Directory } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; const container = emptyDir(Directory(`${Deno.cwd()}/hoge`)); @@ -205,9 +261,9 @@ Ensures that the directory exists. If the directory structure does not exist, it `ensureDir :: Directory a -> Task e Directory a` ```js -import { ensureDir } from "https://deno.land/x/functional_io@v0.1.0/fs.js"; -import { Directory } from "https://deno.land/x/functional_io@v0.1.0/types.js"; -import Task from "https://deno.land/x/functional@v0.5.0/Task.js"; +import { ensureDir } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { Directory } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; const container = emptyDir(Directory(`${Deno.cwd()}/hoge`)); @@ -219,15 +275,168 @@ assert(Task.is(container)); Test whether the given path exists by checking with the file system. If the file or directory doesn't exist, it will resolve to `Either.Left(null)`. -`exists :: File a|Directory a -> Task null File a|Directory a` +`exists :: Location a -> Task null Location a` ```js -import { exists } from "https://deno.land/x/functional_io@v0.1.0/fs.js"; -import { Directory } from "https://deno.land/x/functional_io@v0.1.0/types.js"; -import Task from "https://deno.land/x/functional@v0.5.0/Task.js"; +import { exists } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { Directory } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; const container = exists(Directory(`${Deno.cwd()}/hoge`)); +assert(Task.is(container)); +``` + +### `mkdir` [📕](https://deno.land/std@0.68.0/fs#mkdir) + +Creates a new directory with the specified path. + +`mkdir :: Options -> Directory a -> Task e Directory a` + +```js +import { mkdir } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { Directory } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; + +const container = mkdir({}, Directory(`${Deno.cwd()}/hoge`)); + +assert(Task.is(container)); +``` + +### `move` [📕](https://deno.land/std@0.68.0/fs#move) + +Moves a file or directory. + +`move :: Options -> String -> Location a -> Task e Location b` + +```js +import { move } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { Directory } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; + +const container = move({}, `${Deno.cwd()}/piyo`, Directory(`${Deno.cwd()}/hoge`)); + +assert(Task.is(container)); +``` + +### `open` [📕](https://doc.deno.land/builtin/stable#Deno.open) + +Open a file and resolve to an instance of File. The file does not need to previously exist if using the create or +createNew open options. It is the callers responsibility to close the file when finished with it. + +`open :: Options -> File a -> Task e File a` + +```js +import { open } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { Directory } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; + +const container = open({ read: true, write: true }, File.fromPath(`${Deno.cwd()}/hoge`)); + +assert(Task.is(container)); +``` + +### `read` [📕](https://doc.deno.land/builtin/stable#Deno.read) + +Read from a Resource given it has a non-zero raw buffer. + +`read :: Resource a -> Task e Resource a` + +```js +import { read } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { File } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; + +const container = read(File(`${Deno.cwd()}/hoge`, new Uint8Array(5), 3)); + +assert(Task.is(container)); +``` + +### `readAll` [📕](https://doc.deno.land/builtin/stable#Deno.readAll) + +Read from a Resource. + +`readAll :: Resource a -> Task e Resource a` + +```js +import { readAll } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { Buffer, File } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; + +const container = readAll(File(`${Deno.cwd()}/hoge`, new Uint8Array([]), 3)); + +assert(Task.is(container)); +``` + +### `rename` [📕](https://doc.deno.land/builtin/stable#Deno.rename) + +Renames a file or directory. + +`rename :: String -> Location a -> Task e Location b` + +```js +import { rename } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { Directory } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; + +const container = rename(`${Deno.cwd()}/piyo`, Directory(`${Deno.cwd()}/hoge`)); + +assert(Task.is(container)); +``` + +### `write` [📕](https://doc.deno.land/builtin/stable#Deno.write) + +Write to a Resource given it has a non-zero raw buffer. + +`write :: Resource a -> Task e Resource a` + +```js +import { write } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { File } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; + +const container = write( + File(`${Deno.cwd()}/hoge`, new Uint8Array([ 65, 66, 67, 68, 69 ]), _file.rid) +); + +assert(Task.is(container)); +``` + +### `writeAll` [📕](https://doc.deno.land/builtin/stable#Deno.writeAll) + +Write all to a Resource from a Buffer. + +`writeAll :: Buffer b -> Resource a -> Task e Resource b` + +```js +import { writeAll } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { Buffer, File } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; + +const container = writeAll( + Buffer(new Uint8Array([ 65, 66, 67, 68, 69 ])), + File(`${Deno.cwd()}/hoge`, new Uint8Array([]), _file.rid) +); + +assert(Task.is(container)); +``` + +### `writeFile` [📕](https://doc.deno.land/builtin/stable#Deno.writeFile) + +Write a File to the file system. + +`writeFile :: Options -> File a -> Task e File b` + +```js +import { writeFile } from "https://deno.land/x/functional_io@v0.2.0/fs.js"; +import { Buffer, File } from "https://deno.land/x/functional_io@v0.2.0/types.js"; +import Task from "https://deno.land/x/functional@v0.5.2/Task.js"; + +const container = writeFile( + {}, + File(`${Deno.cwd()}/hoge`, new Uint8Array([]), _file.rid) +); + assert(Task.is(container)); ``` diff --git a/integration_test/fs_test.js b/integration_test/fs_test.js index da2dcfe..a32f41a 100644 --- a/integration_test/fs_test.js +++ b/integration_test/fs_test.js @@ -1,18 +1,11 @@ +import { compose, chain, curry } from "https://x.nest.land/ramda@0.27.0/source/index.js"; import { assert, assertEquals } from "https://deno.land/std@0.65.0/testing/asserts.ts"; import { emptyDir as _emptyDir, - ensureDir as _ensureDir, - ensureSymlink as _ensureSymlink, - exists as _exists, - move as _move, - readJson as _readJson, - walk as _walk, - writeJson as _writeJson, + ensureDir as _ensureDir } from "https://deno.land/std@0.67.0/fs/mod.ts"; -import { chain, compose } from "https://x.nest.land/ramda@0.27.0/source/index.js"; -import { assertIsEquivalent } from "https://deno.land/x/functional@v0.4.2/asserts.js"; -import Either from "https://deno.land/x/functional@v0.4.2/Either.js" -import Task from "../../functional/library/Task.js" +import Either from "https://deno.land/x/functional@v0.5.2/Either.js" +import Task from "https://deno.land/x/functional@v0.5.2/Task.js" import { chdir, chmod, @@ -24,12 +17,18 @@ import { cwd, emptyDir, ensureDir, - ensureSymlink, exists, + mkdir, move, - readJson, - walk, - writeJson, + open, + read, + readAll, + readFile, + remove, + rename, + write, + writeAll, + writeFile } from "../library/fs.js" import { Buffer, Directory, File } from "../library/types.js"; @@ -292,7 +291,10 @@ Deno.test( `Either.Right(Directory("${Deno.cwd()}/dump"))` ); - await(!(await _exists(`${Deno.cwd()}/dump/hoge`))); + try { + await Deno.lstat(`${Deno.cwd()}/dump/hoge`); + assert(false, "Resource was not removed properly."); + } catch (error) {} } ); @@ -314,6 +316,9 @@ Deno.test( containerB.toString(), `Either.Right(Directory("${Deno.cwd()}/dump"))` ); + + const { isDirectory } = await Deno.lstat(`${Deno.cwd()}/dump`); + assert(isDirectory); } ); @@ -366,16 +371,37 @@ Deno.test( ); Deno.test( - "Scenario 1", + "Integration: mkdir", + async () => { + await Deno.remove(`${Deno.cwd()}/dump`); + + const containerA = mkdir({}, Directory(`${Deno.cwd()}/dump`)); + const promise = containerA.run(); + + assert(Task.is(containerA)); + assert(promise instanceof Promise); + + const containerB = await promise; + + assert(Either.Right.is(containerB)); + assertEquals( + containerB.toString(), + `Either.Right(Directory("${Deno.cwd()}/dump"))` + ); + + const { isDirectory } = await Deno.lstat(`${Deno.cwd()}/dump`); + assert(isDirectory); + } +); + +Deno.test( + "Integration: move", async () => { await _ensureDir(`${Deno.cwd()}/dump`); await _emptyDir(`${Deno.cwd()}/dump`); - await Deno.writeTextFile(`${Deno.cwd()}/dump/hoge`, "ABCDE"); - - const containerA = create(File.fromPath(`${Deno.cwd()}/dump/piyo`)) - .chain(copyFile(File.fromPath(`${Deno.cwd()}/dump/hoge`))) - .chain(close); + const _file = await Deno.create(`${Deno.cwd()}/dump/hoge`); + const containerA = move({}, `${Deno.cwd()}/dump/piyo`, File.fromPath(`${Deno.cwd()}/dump/hoge`)); const promiseA = containerA.run(); assert(Task.is(containerA)); @@ -383,40 +409,161 @@ Deno.test( const containerB = await promiseA; + assert(Either.Right.is(containerB)); assertEquals( containerB.toString(), `Either.Right(File("${Deno.cwd()}/dump/piyo", , ${containerB[$$value].rid}))` ); - const _file = await Deno.open(`${Deno.cwd()}/dump/piyo`); - const _buffer = await Deno.readAll(_file); + const { isFile } = await Deno.lstat(`${Deno.cwd()}/dump/piyo`); + assert(isFile); + Deno.close(_file.rid); + await Deno.remove(`${Deno.cwd()}/dump/piyo`); + } +); + +Deno.test( + "Integration: open", + async () => { + await _ensureDir(`${Deno.cwd()}/dump`); + await Deno.writeTextFile(`${Deno.cwd()}/dump/hoge`, "ABCDE"); + const containerA = open({ read: true, write: true }, File.fromPath(`${Deno.cwd()}/dump/hoge`)); + const promise = containerA.run(); + + assert(Task.is(containerA)); + assert(promise instanceof Promise); + + const containerB = await promise; + + assert(Either.Right.is(containerB)); assertEquals( - _buffer, - new Uint8Array([ 65, 66, 67, 68, 69 ]) + containerB.toString(), + `Either.Right(File("${Deno.cwd()}/dump/hoge", , ${containerB[$$value].rid}))` ); await Deno.remove(`${Deno.cwd()}/dump/hoge`); - await Deno.remove(`${Deno.cwd()}/dump/piyo`); + Deno.close(containerB[$$value].rid); } ); Deno.test( - "Scenario 1 (Fantasy-Land)", + "Integration: read", async () => { await _ensureDir(`${Deno.cwd()}/dump`); - await _emptyDir(`${Deno.cwd()}/dump`); await Deno.writeTextFile(`${Deno.cwd()}/dump/hoge`, "ABCDE"); + const _file = await Deno.open(`${Deno.cwd()}/dump/hoge`, { read: true, write: true }); + + const containerA = read(File(`${Deno.cwd()}/dump/hoge`, new Uint8Array(5), _file.rid)); + const promise = containerA.run(); + + assert(Task.is(containerA)); + assert(promise instanceof Promise); + + const containerB = await promise; + + assert(Either.Right.is(containerB)); + assertEquals( + containerB.toString(), + `Either.Right(File("${Deno.cwd()}/dump/hoge", 65,66,67,68,69, ${_file.rid}))` + ); + + await Deno.remove(`${Deno.cwd()}/dump/hoge`); + Deno.close(_file.rid); + } +); + +Deno.test( + "Integration: readAll", + async () => { + await _ensureDir(`${Deno.cwd()}/dump`); + await Deno.writeTextFile(`${Deno.cwd()}/dump/hoge`, "ABCDE"); + const _file = await Deno.open(`${Deno.cwd()}/dump/hoge`, { read: true, write: true }); + + const containerA = readAll(File(`${Deno.cwd()}/dump/hoge`, new Uint8Array([]), _file.rid)); + const promise = containerA.run(); + + assert(Task.is(containerA)); + assert(promise instanceof Promise); + + const containerB = await promise; + + assert(Either.Right.is(containerB)); + assertEquals( + containerB.toString(), + `Either.Right(File("${Deno.cwd()}/dump/hoge", 65,66,67,68,69, ${_file.rid}))` + ); + + await Deno.remove(`${Deno.cwd()}/dump/hoge`); + Deno.close(_file.rid); + } +); + +Deno.test( + "Integration: readFile", + async () => { + await _ensureDir(`${Deno.cwd()}/dump`); + await Deno.writeTextFile(`${Deno.cwd()}/dump/hoge`, "ABCDE"); + const _file = await Deno.open(`${Deno.cwd()}/dump/hoge`, { read: true, write: true }); + + const containerA = readFile(File(`${Deno.cwd()}/dump/hoge`, new Uint8Array([]), _file.rid)); + const promise = containerA.run(); + + assert(Task.is(containerA)); + assert(promise instanceof Promise); + + const containerB = await promise; + + assert(Either.Right.is(containerB)); + assertEquals( + containerB.toString(), + `Either.Right(File("${Deno.cwd()}/dump/hoge", 65,66,67,68,69, ${_file.rid}))` + ); + + await Deno.remove(`${Deno.cwd()}/dump/hoge`); + Deno.close(_file.rid); + } +); + +Deno.test( + "Integration: remove", + async () => { + await _ensureDir(`${Deno.cwd()}/dump`); + await _emptyDir(`${Deno.cwd()}/dump`); + const _file = await Deno.create(`${Deno.cwd()}/dump/hoge`); + + const containerA = remove({}, File.fromPath(`${Deno.cwd()}/dump/hoge`)); + const promiseA = containerA.run(); + + assert(Task.is(containerA)); + assert(promiseA instanceof Promise); + + const containerB = await promiseA; - const copyToNewFile = compose( - chain(close), - chain(copyFile(File.fromPath(`${Deno.cwd()}/dump/hoge`))), - create + assert(Either.Right.is(containerB)); + assertEquals( + containerB.toString(), + `Either.Right(File("${Deno.cwd()}/dump/hoge", , ${containerB[$$value].rid}))` ); - const containerA = copyToNewFile(File.fromPath(`${Deno.cwd()}/dump/piyo`)); + Deno.close(_file.rid); + try { + await Deno.lstat(`${Deno.cwd()}/dump/hoge`); + assert(false, "Resource was not removed properly."); + } catch (error) {} + } +); + +Deno.test( + "Integration: rename", + async () => { + await _ensureDir(`${Deno.cwd()}/dump`); + await _emptyDir(`${Deno.cwd()}/dump`); + const _file = await Deno.create(`${Deno.cwd()}/dump/hoge`); + + const containerA = rename(`${Deno.cwd()}/dump/piyo`, File.fromPath(`${Deno.cwd()}/dump/hoge`)); const promiseA = containerA.run(); assert(Task.is(containerA)); @@ -424,21 +571,168 @@ Deno.test( const containerB = await promiseA; + assert(Either.Right.is(containerB)); assertEquals( containerB.toString(), `Either.Right(File("${Deno.cwd()}/dump/piyo", , ${containerB[$$value].rid}))` ); - const _file = await Deno.open(`${Deno.cwd()}/dump/piyo`); - const _buffer = await Deno.readAll(_file); + const { isFile } = await Deno.lstat(`${Deno.cwd()}/dump/piyo`); + assert(isFile); + Deno.close(_file.rid); + await Deno.remove(`${Deno.cwd()}/dump/piyo`); + } +); +Deno.test( + "Integration: write", + async () => { + await _ensureDir(`${Deno.cwd()}/dump`); + const _file = await Deno.open(`${Deno.cwd()}/dump/hoge`, { create: true, read: true, write: true }); + + const containerA = write( + File(`${Deno.cwd()}/dump/hoge`, new Uint8Array([ 65, 66, 67, 68, 69 ]), _file.rid) + ); + const promise = containerA.run(); + + assert(Task.is(containerA)); + assert(promise instanceof Promise); + + const containerB = await promise; + + assert(Either.Right.is(containerB)); assertEquals( - _buffer, - new Uint8Array([ 65, 66, 67, 68, 69 ]) + containerB.toString(), + `Either.Right(File("${Deno.cwd()}/dump/hoge", 65,66,67,68,69, ${_file.rid}))` ); + Deno.close(_file.rid); + + { + const _file = await Deno.open(`${Deno.cwd()}/dump/hoge`, { create: true, read: true, write: true }); + + const _buffer = await Deno.readAll(_file); + + assertEquals( + _buffer, + new Uint8Array([ 65, 66, 67, 68, 69 ]) + ); + + Deno.close(_file.rid); + } + await Deno.remove(`${Deno.cwd()}/dump/hoge`); - await Deno.remove(`${Deno.cwd()}/dump/piyo`); + } +); + +Deno.test( + "Integration: writeAll", + async () => { + await _ensureDir(`${Deno.cwd()}/dump`); + const _file = await Deno.open(`${Deno.cwd()}/dump/hoge`, { create: true, read: true, write: true }); + + const containerA = writeAll( + Buffer(new Uint8Array([ 65, 66, 67, 68, 69 ])), + File(`${Deno.cwd()}/dump/hoge`, new Uint8Array([]), _file.rid) + ); + const promise = containerA.run(); + + assert(Task.is(containerA)); + assert(promise instanceof Promise); + + const containerB = await promise; + + assert(Either.Right.is(containerB)); + assertEquals( + containerB.toString(), + `Either.Right(File("${Deno.cwd()}/dump/hoge", 65,66,67,68,69, ${_file.rid}))` + ); + + Deno.close(_file.rid); + + { + const _file = await Deno.open(`${Deno.cwd()}/dump/hoge`, { create: true, read: true, write: true }); + + const _buffer = await Deno.readAll(_file); + + assertEquals( + _buffer, + new Uint8Array([ 65, 66, 67, 68, 69 ]) + ); + + Deno.close(_file.rid); + } + + await Deno.remove(`${Deno.cwd()}/dump/hoge`); + } +); + +Deno.test( + "Integration: writeFile", + async () => { + await _ensureDir(`${Deno.cwd()}/dump`); + const _file = await Deno.open(`${Deno.cwd()}/dump/hoge`, { create: true, read: true, write: true }); + + const containerA = writeFile( + {}, + File(`${Deno.cwd()}/dump/hoge`, new Uint8Array([ 65, 66, 67, 68, 69 ]), _file.rid) + ); + const promise = containerA.run(); + + assert(Task.is(containerA)); + assert(promise instanceof Promise); + + const containerB = await promise; + + assert(Either.Right.is(containerB)); + assertEquals( + containerB.toString(), + `Either.Right(File("${Deno.cwd()}/dump/hoge", 65,66,67,68,69, ${_file.rid}))` + ); + + Deno.close(_file.rid); + + { + const _file = await Deno.open(`${Deno.cwd()}/dump/hoge`, { create: true, read: true, write: true }); + + const _buffer = await Deno.readAll(_file); + + assertEquals( + _buffer, + new Uint8Array([ 65, 66, 67, 68, 69 ]) + ); + + Deno.close(_file.rid); + } + + await Deno.remove(`${Deno.cwd()}/dump/hoge`); + } +); + +Deno.test( + "Scenario 1", + async () => { + const writeNewFile = curry( + (destinationFile, buffer) => + File.isOrThrow(destinationFile) + && compose( + chain(close), + chain(writeAll(buffer)), + create + )(destinationFile) + ); + + assert( + Task.is(writeNewFile(File.fromPath(`${Deno.cwd()}/dump/hoge`), Buffer.fromString("Hello Deno"))) + ); + + return writeNewFile(File.fromPath(`${Deno.cwd()}/dump/hoge`), Buffer.fromString("Hello Deno")).run() + .then(container => { + assert(Either.Right.is(container)); + assert( + container.toString() === `Either.Right(File("${Deno.cwd()}/dump/hoge", 72,101,108,108,111,32,68,101,110,111, 31))` + ); + }); } ); \ No newline at end of file diff --git a/library/fs.js b/library/fs.js index 3e1a072..a3af992 100644 --- a/library/fs.js +++ b/library/fs.js @@ -1,4 +1,3 @@ -import { assert } from "https://deno.land/std@0.65.0/testing/asserts.ts"; import { emptyDir as _emptyDir, ensureDir as _ensureDir, @@ -6,16 +5,12 @@ import { exists as _exists, move as _move, readJson as _readJson, - walk as _walk, writeJson as _writeJson, } from "https://deno.land/std@0.67.0/fs/mod.ts"; -import { compose, concat, curry, prop } from "https://x.nest.land/ramda@0.27.0/source/index.js"; -import Either from "https://deno.land/x/functional@v0.4.2/Either.js" -import Task from "../../functional/library/Task.js" -import { stream } from "../../functional/library/utilities.js" -import { Buffer, Directory, File, FileSystemCollection } from "./types.js"; - -const $$value = Symbol.for("TypeValue"); +import { curry } from "https://x.nest.land/ramda@0.27.0/source/index.js"; +import Either from "https://deno.land/x/functional@v0.5.2/Either.js" +import Task from "https://deno.land/x/functional@v0.5.2/Task.js" +import { Buffer, Directory, File, Resource } from "./types.js"; // chdir :: Directory a -> Task e Directory a export const chdir = directory => Directory.isOrThrow(directory) @@ -43,8 +38,8 @@ export const close = file => File.isOrThrow(file) // copy :: Options -> Buffer a -> Buffer b -> Task e Writer b export const copy = curry( (options, readerBuffer, writerBuffer) => (Buffer.isOrThrow(readerBuffer) && Buffer.isOrThrow(writerBuffer)) - && Task.wrap(_ => Deno.copy(new Deno.Buffer(readerBuffer[$$value]), new Deno.Buffer(writerBuffer[$$value]), options)) - .map(_ => Buffer(readerBuffer[$$value])) + && Task.wrap(_ => Deno.copy(new Deno.Buffer(readerBuffer.raw), new Deno.Buffer(writerBuffer.raw), options)) + .map(_ => Buffer(readerBuffer.raw)) ); // copyFile :: File a -> File b -> Task e File b @@ -58,7 +53,7 @@ export const copyFile = curry( // create :: File a -> Task e File a export const create = file => File.isOrThrow(file) && Task.wrap(_ => Deno.create(file.path)) - .map(_file => File(file.path, new Uint8Array([]), _file.rid)); + .map(_file => File(file.path, new Uint8Array(file.raw.length), _file.rid)); // cwd :: () Task e Directory a export const cwd = () => Task.wrap(_ => Promise.resolve(Deno.cwd())) @@ -79,35 +74,22 @@ export const ensureSymlink = directory => Directory.isOrThrow(directory) && Task.wrap(_ => _ensureSymlink(directory.path)) .map(_ => Directory(directory.path)); -// exists :: File a|Directory a -> Task null File a|Directory a -export const exists = container => { - if (!Directory.is(container) && !File.is(container)) { - throw new Error(`Expected a Directory or a File but got a "${typeof container}"`); +// exists :: Location a -> Task null Location a +export const exists = location => { + if (!Directory.is(location) && !File.is(location)) { + throw new Error(`Expected a Directory or a File but got a "${typeof location}"`); } - return Task.wrap(_ => _exists(container.path)) + return Task.wrap(_ => _exists(location.path)) .map( fileExists => { if (!fileExists) return Either.Left(null); - return Directory.is(container) - ? Directory(container.path) - : File(container.path, container.raw, container.rid); + return location.constructor.from({ ...location }); } ) }; -// // lstat :: Directory a -> Task null Directory a -// export const lstat = container => (Directory.isOrThrow(container) || File.isOrThrow(container)) -// && Task.wrap(_ => Deno.lstat(container.path)) -// .map( -// fileExists => fileExists -// ? Either.Right( -// Directory.is(container) ? Directory(container.path) : File(container.path, container.raw, container.rid) -// ) -// : Either.Left(null) -// ); - // mkdir :: Options -> Directory a -> Task e Directory a export const mkdir = curry( (options, directory) => (Directory.isOrThrow(directory)) @@ -115,206 +97,79 @@ export const mkdir = curry( .map(_ => Directory(directory.path)) ); -// move :: Directory a -> String -> Task e Directory b +// move :: Options -> String -> Location a -> Task e Location b export const move = curry( - (directory, destinationPath) => Directory.isOrThrow(directory) - && Task.wrap(_ => _move(directory.path, destinationPath)) - .map(_ => Directory(destinationPath)) + (options, destinationPath, location) => location.hasOwnProperty("path") + && Task.wrap(_ => _move(location.path, destinationPath)) + .map(_ => location.constructor.from({ ...location, path: destinationPath })) ); // open :: Options -> File a -> Task e File a export const open = curry( - (options, file) => (File.isOrThrow(file)) + (options, file) => File.isOrThrow(file) && Task.wrap(_ => Deno.open(file.path, options)) - .map(_file => File.fromFile(_file)) + .map(_file => File(file.path, new Uint8Array([]), _file.rid)) ); -// readJson :: String -> Task e Object -export const readJson = filePath => Task.wrap(_ => _readJson(filePath)) - .map(JSONObject => ({ ...JSONObject })); - -// writeJson :: Options -> String -> Object -> Task e Object -export const writeJson = curry( - (options, filePath, JSONObject) => Task.wrap(_ => _writeJson(filePath, JSONObject, options)) - .map(_ => ({ ...JSONObject })) -); +// read :: Resource a -> Task e Resource a +export const read = resource => { + const _buffer = new Uint8Array(resource.raw.length); -// walk :: Directory a -> (File|Directory -> *) -> Task e FileSystemCollection -export const walk = curry( - (directory, unaryFunction) => - Directory.isOrThrow(directory) - && Task(_ => - stream( - (accumulator, entry) => - compose( - concat(accumulator), - unaryFunction, - x => console.debug(x) || x, - entry => - entry.isFile && File.fromPath(entry.path) - || entry.isDirectory && Directory(entry.path) - )(entry), - FileSystemCollection.empty(), - walk(directory.path) - ) - ) - .map(fileSystemCollection => Either.Right(fileSystemCollection)) -); - -Deno.test( - "emptyDir", - () => { - const containerA = emptyDir(Directory("/path/to/project")) - .then(directory => directory.map(path => path.replace(/(?!\/)[A-Za-z0-9_:\s]+$/, "hoge"))); - - const containerB = containerA.then(directory => directory.map(path => path.replace("hoge", "fuga"))); - - assert(Task.is(containerA)); - assert(Task.is(containerB)); - } -); - -Deno.test( - "ensureDir", - () => { - const containerA = ensureDir(Directory("/path/to/project")) - .then(directory => directory.map(path => path.replace(/(?!\/)[A-Za-z0-9_:\s]+$/, "hoge"))); - - const containerB = containerA.then(directory => directory.map(path => path.replace("hoge", "fuga"))); + return Resource.isOrThrow(resource) + && Task.wrap(_ => Deno.read(resource.rid, _buffer)) + .map(_ => resource.constructor.from({ ...resource, raw: _buffer })) +}; - assert(Task.is(containerA)); - assert(Task.is(containerB)); - } +// readAll :: Resource a -> Task e Resource a +export const readAll = resource => Resource.isOrThrow(resource) + && Task.wrap(_ => Deno.readAll(resource)) + .map(_buffer => resource.constructor.from({ ...resource, raw: _buffer })); + +// readFile :: File a -> Task e File a +export const readFile = file => File.isOrThrow(file) + && Task.wrap(_ => Deno.readFile(file.path)) + .map(_buffer => File(file.path, _buffer, file.rid)); + +// remove :: Options -> Location a -> Task e Location a +export const remove = curry( + (options, location) => location.hasOwnProperty("path") + && Task.wrap(_ => Deno.remove(location.path, options)) + .map(_ => location.constructor.from({ ...location })) ); -Deno.test( - "ensureSymlink", - () => { - const containerA = ensureSymlink(Directory("/path/to/project")) - .then(directory => directory.map(path => path.replace(/(?!\/)[A-Za-z0-9_:\s]+$/, "hoge"))); - - const containerB = containerA.then(directory => directory.map(path => path.replace("hoge", "fuga"))); - - assert(Task.is(containerA)); - assert(Task.is(containerB)); - } +// rename :: String -> Location a -> Task e Location b +export const rename = curry( + (destinationPath, location) => location.hasOwnProperty("path") + && Task.wrap(_ => Deno.rename(location.path, destinationPath)) + .map(_ => location.constructor.from({ ...location, path: destinationPath })) ); -// Deno.test( -// "exists", -// () => { -// const containerA = exists(Directory("/path/to/project")) -// .then(directory => directory.map(path => path.replace(/(?!\/)[A-Za-z0-9_:\s]+$/, "hoge"))); -// -// const containerB = containerA.then(directory => directory.map(path => path.replace("hoge", "fuga"))); -// -// assert(Task.is(containerA)); -// assert(Task.is(containerB)); -// } -// ); - -// Deno.test( -// "exists", -// () => { -// const containerA = exists(Directory("/path/to/project")) -// .then(directory => directory.map(path => path.replace(/(?!\/)[A-Za-z0-9_:\s]+$/, "hoge"))); -// -// const containerB = containerA.then(directory => directory.map(path => path.replace("hoge", "fuga"))); -// -// assert(Task.is(containerA)); -// assert(Task.is(containerB)); -// const promiseA = containerA.run(); -// const promiseB = containerB.run(); -// assert(promiseA instanceof Promise); -// assert(promiseB instanceof Promise); -// -// return Promise.all([ -// promiseA.then(x => { -// assert(Either.Right.is(x), x.toString()); -// assertEquals(x[$$value].path, "/path/to/hoge"); -// }), -// promiseB.then(x => { -// assert(Either.Right.is(x), x.toString()); -// assertEquals(x[$$value].path, "/path/to/fuga"); -// }) -// ]); -// } -// ); - -Deno.test( - "Walk", - async () => { - const containerA = walk( - Directory("/path/to/project"), - file => file.chain( - file => - file.constructor(file.path.replace(/(?!\/)[A-Za-z0-9_:\s]+$/, "hoge"), file.raw) - ) - ); - const containerB = containerA - .map(x => console.debug("MAP:", x) || x); +// readJson :: String -> Task e Object +export const readJson = filePath => Task.wrap(_ => _readJson(filePath)) + .map(JSONObject => ({ ...JSONObject })); - assert(Task.is(containerA)); - assert(Task.is(containerB)); - // const promiseA = containerA.run(); - // const promiseB = containerB.run(); - // assert(promiseA instanceof Promise); - // assert(promiseB instanceof Promise); - // - // return Promise.all( - // promiseA.then(x => { - // assert(FileSystemCollection.is(x), x.toString()); - // }), - // promiseB.then(x => { - // assert(FileSystemCollection.is(x), x.toString()); - // }) - // ); - } +// write :: Resource a -> Task e Resource a +export const write = resource => Resource.isOrThrow(resource) + && Task.wrap(_ => Deno.write(resource.rid, resource.raw)) + .map(_ => resource.constructor.from({ ...resource })); + +// writeAll :: Buffer b -> Resource a -> Task e Resource b +export const writeAll = curry( + (buffer, resource) => Resource.isOrThrow(resource) && Buffer.isOrThrow(buffer) + && Task.wrap(_ => + Deno.writeAll(resource, buffer.raw)) + .map(_ => resource.constructor.from({ ...resource, raw: buffer.raw })) ); -Deno.test( - "readJson", - () => { - const containerA = readJson("/path/to/project/piyo.json") - .then(prop("hoge")); - - const containerB = containerA.then(hoge => hoge.toUpperCase()); - - assert(Task.is(containerA)); - assert(Task.is(containerB)); - } +// writeFile :: File a -> Task e File a +export const writeFile = curry( + (options, file) => Resource.isOrThrow(file) + && Task.wrap(_ => Deno.writeFile(file.path, file.raw, options)) + .map(_ => file.constructor.from({ ...file })) ); -Deno.test( - "readJson", - () => { - const containerA = writeJson({}, "/path/to/project/piyo.json", { hoge: "fuga" }) - .then(prop("hoge")); - - const containerB = containerA.then(hoge => hoge.toUpperCase()); - - assert(Task.is(containerA)); - assert(Task.is(containerB)); - } +// writeJson :: Options -> String -> Object -> Task e Object +export const writeJson = curry( + (options, filePath, JSONObject) => Task.wrap(_ => _writeJson(filePath, JSONObject, options)) + .map(_ => ({ ...JSONObject })) ); - -// Deno.test( -// "----", -// () => { -// const IOContainer = x => IO.of(_ => console.debug("> !!!!!!!") || Promise.resolve(x * 2)); -// -// // Keep this -// const a = IOContainer(42).chain(x => IOContainer(x).map(x => x.then(y => y + 2))); -// console.debug("A", a); -// const d = a.map(x => x.then(y => y - 10)); -// const e = a.map(x => x.then(y => y - 13)); -// console.debug("D", d); -// const b = d.run(); -// const f = e.run(); -// console.debug("B", b); -// b.then(x => console.debug("BBBB", x)) -// f.then(x => console.debug("FFFF", x)) -// -// setTimeout(() => null, 1000); -// } -// ) diff --git a/library/network.js b/library/network.js index a0bc915..af3fcc3 100644 --- a/library/network.js +++ b/library/network.js @@ -17,7 +17,7 @@ export const connect = connection => Connection.isOrThrow(connection) // connectTls :: Connection a -> Task e Connection a export const connectTls = connection => Connection.isOrThrow(connection) && Task.wrap( - _ => Deno.connect( + _ => Deno.connectTls( { certFile: connection.certFile, hostname: connection.hostname, diff --git a/library/types.js b/library/types.js index f3081fb..3a2199d 100644 --- a/library/types.js +++ b/library/types.js @@ -1,49 +1,29 @@ import { factorizeType } from "https://deno.land/x/functional@v0.4.2/SumType.js"; +import Task from "../../functional/library/Task.js" const $$value = Symbol.for("TypeValue"); -export const Directory = factorizeType("Directory", [ "path" ]); - -Directory.isOrThrow = container => { - if (Directory.is(container)) return container; - else throw new Error(`Expected a Directory but got a "${typeof container}"`); -} - -// equals :: Directory a ~> Directory b -> Boolean -Directory.prototype.equals = Directory.prototype["fantasy-land/equals"] = function (container) { - - return this.path === container.path; -} -// chain :: Directory a ~> (a -> Directory b) -> Directory b -Directory.prototype.chain = Directory.prototype["fantasy-land/chain"] = function (unaryFunction) { - - return unaryFunction(this.path); -}; -// map :: Directory a ~> (a -> b) -> Directory b -Directory.prototype.map = Directory.prototype["fantasy-land/map"] = function (unaryFunction) { - - return Directory(unaryFunction(this.path)); -}; - -export const Buffer = factorizeType("Buffer", [ $$value ]); +export const Buffer = factorizeType("Buffer", [ "raw" ]); Buffer.isOrThrow = container => { - if (Buffer.is(container)) return container; + if (Buffer.is(container) || container.hasOwnProperty("raw") || Task.is(container)) return container; else throw new Error(`Expected a Buffer but got a "${typeof container}"`); } +Buffer.fromString = text => Buffer(new TextEncoder().encode(text)); + Buffer.of = buffer => Buffer(buffer); // map :: File a ~> (a -> b) -> File b Buffer.prototype.map = Buffer.prototype["fantasy-land/map"] = function (unaryFunction) { - return Buffer(unaryFunction(this[$$value])); + return Buffer(unaryFunction(this.raw)); }; // read :: Buffer a ~> Buffer a -> Task e Buffer a Buffer.prototype.read = function (buffer) { - return Task.wrap(_ => new Deno.Buffer(...this[$$value]).read(buffer)) + return Task.wrap(_ => new Deno.Buffer(...this.raw).read(buffer)) .map( byteCount => byteCount > 0 ? Buffer(new Uint8Array([ ...buffer ])) @@ -54,20 +34,43 @@ Buffer.prototype.read = function (buffer) { // write :: Buffer a ~> Buffer a -> Task e Buffer a Buffer.prototype.write = function (buffer) { - return Task.wrap(_ => new Deno.Buffer(...this[$$value]).write(buffer)) + return Task.wrap(_ => new Deno.Buffer(...this.raw).write(buffer)) .map( byteCount => byteCount > 0 - ? Buffer(new Uint8Array([ ...this[$$value], ...buffer ])) + ? Buffer(new Uint8Array([ ...this.raw, ...buffer ])) : Buffer(new Uint8Array([])) ); }; +export const Directory = factorizeType("Directory", [ "path" ]); + +Directory.isOrThrow = container => { + if (Directory.is(container) || Task.is(container)) return container; + else throw new Error(`Expected a Directory but got a "${typeof container}"`); +} + +// equals :: Directory a ~> Directory b -> Boolean +Directory.prototype.equals = Directory.prototype["fantasy-land/equals"] = function (container) { + + return this.path === container.path; +} +// chain :: Directory a ~> (a -> Directory b) -> Directory b +Directory.prototype.chain = Directory.prototype["fantasy-land/chain"] = function (unaryFunction) { + + return unaryFunction(this.path); +}; +// map :: Directory a ~> (a -> b) -> Directory b +Directory.prototype.map = Directory.prototype["fantasy-land/map"] = function (unaryFunction) { + + return Directory(unaryFunction(this.path)); +}; + export const File = factorizeType("File", [ "path", "raw", "rid" ]); File.fromPath = path => File(path, new Uint8Array([]), 0); File.isOrThrow = container => { - if (File.is(container)) return container; + if (File.is(container) || Task.is(container)) return container; else throw new Error(`Expected a File but got a "${typeof container}"`); } @@ -91,24 +94,14 @@ File.prototype.map = File.prototype["fantasy-land/map"] = function (unaryFunctio File.prototype.read = function (buffer) { if (this.rid <= 1) throw new Error(`Can't read from resource with ID "${this.rid}" as it's not a File.`); - return Task.wrap(_ => Deno.read(this.rid, buffer)) - .map( - byteCount => byteCount > 0 - ? Buffer(new Uint8Array([ ...buffer ])) - : Buffer(new Uint8Array([])) - ); + return Deno.read(this.rid, buffer); }; // write :: File a ~> Buffer a -> Task e File a File.prototype.write = function (buffer) { if (this.rid <= 1) throw new Error(`Can't write to resource with ID "${this.rid}" as it's not a File.`); - return Task.wrap(_ => Deno.write(this.rid, buffer.raw)) - .map( - byteCount => byteCount > 0 - ? File(this.path, new Uint8Array([ ...this.raw, ...buffer ]), this.rid) - : File(this.path, new Uint8Array([ ...this.raw ]), this.rid) - ); + return Deno.write(this.rid, buffer); }; export const FileSystemCollection = factorizeType("FileSystemCollection", [ $$value ]); @@ -138,4 +131,47 @@ FileSystemCollection.prototype.concat = FileSystemCollection["fantasy-land/conca FileSystemCollection.prototype.map = FileSystemCollection["fantasy-land/map"] = function (unaryFunction) { return FileSystemCollection(this[$$value].map(unaryFunction)); +}; + +export const Resource = factorizeType("Resource", [ "raw", "rid" ]); + +Resource.fromPath = path => Resource(path, new Uint8Array([]), 0); + +Resource.isOrThrow = container => { + if ( + Resource.is(container) + || container.hasOwnProperty("rid") && container.hasOwnProperty("raw") + || Task.is(container) + ) return container; + else throw new Error(`Expected a Resource but got a "${typeof container}"`); +} + +// empty :: Resource => () -> Resource a +Resource.empty = Resource.prototype.empty = Resource.prototype["fantasy-land/empty"] = () => + Resource("", new Uint8Array([]), 0); + +// chain :: Resource a ~> (a -> Resource b) -> Resource b +Resource.prototype.chain = Resource.prototype["fantasy-land/chain"] = function (unaryFunction) { + + return unaryFunction(this.path, this.raw); +}; + +// map :: Resource a ~> (a -> b) -> Resource b +Resource.prototype.map = Resource.prototype["fantasy-land/map"] = function (unaryFunction) { + + return Resource(unaryFunction(this.path), this.raw); +}; + +// read :: Resource a ~> Buffer a -> Task e Buffer a +Resource.prototype.read = function (buffer) { + if (this.rid <= 1) throw new Error(`Can't read from resource with ID "${this.rid}" as it's not a Resource.`); + + return Deno.read(this.rid, buffer); +}; + +// write :: Resource a ~> Buffer a -> Task e Resource a +Resource.prototype.write = function (buffer) { + if (this.rid <= 1) throw new Error(`Can't write to resource with ID "${this.rid}" as it's not a Resource.`); + + return Deno.write(this.rid, buffer); }; \ No newline at end of file