diff --git a/src/fsa-to-node/FsaNodeFs.ts b/src/fsa-to-node/FsaNodeFs.ts index a716cee07..2781ab946 100644 --- a/src/fsa-to-node/FsaNodeFs.ts +++ b/src/fsa-to-node/FsaNodeFs.ts @@ -1,6 +1,6 @@ import { createPromisesApi } from '../node/promises'; -import { getDefaultOptsAndCb, getMkdirOptions, getRmOptsAndCb, getRmdirOptions } from '../node/options'; -import { createError, genRndStr6, nullCheck, pathToFilename, validateCallback } from '../node/util'; +import { getDefaultOptsAndCb, getMkdirOptions, getReadFileOptions, getRmOptsAndCb, getRmdirOptions, optsAndCbGenerator } from '../node/options'; +import { createError, flagsToNumber, genRndStr6, nullCheck, pathToFilename, validateCallback } from '../node/util'; import { pathToLocation } from './util'; import { MODE } from '../node/constants'; import { strToEncoding } from '../encoding'; @@ -9,6 +9,7 @@ import type { FsCallbackApi, FsPromisesApi } from '../node/types'; import type * as misc from '../node/types/misc'; import type * as opts from '../node/types/options'; import type * as fsa from '../fsa/types'; +import {bufferToEncoding} from '../volume'; // const notImplemented: (...args: unknown[]) => unknown = () => { // throw new Error('Not implemented'); @@ -41,11 +42,11 @@ export class FsaNodeFs implements FsCallbackApi { return curr; } - // private async getFile(path: string[], name: string, funcName?: string): Promise { - // const dir = await this.getDir(path, false, funcName); - // const file = await dir.getFileHandle(name, { create: false }); - // return file; - // } + private async getFile(path: string[], name: string, funcName?: string): Promise { + const dir = await this.getDir(path, false, funcName); + const file = await dir.getFileHandle(name, { create: false }); + return file; + } public readonly open: FsCallbackApi['open'] = ( path: misc.PathLike, @@ -76,7 +77,21 @@ export class FsaNodeFs implements FsCallbackApi { a?: opts.IReadFileOptions | string | misc.TCallback, b?: misc.TCallback, ) => { - throw new Error('Not implemented'); + const [opts, callback] = optsAndCbGenerator(getReadFileOptions)(a, b); + const flagsNum = flagsToNumber(opts.flag); + if (typeof id === 'number') throw new Error('Not implemented'); + const filename = pathToFilename(id); + const [folder, name] = pathToLocation(filename); + return this.getFile(folder, name, 'readFile') + .then(file => file.getFile()) + .then(file => file.arrayBuffer()) + .then(data => { + const buffer = Buffer.from(data); + callback(null, bufferToEncoding(buffer, opts.encoding)); + }) + .catch(error => { + callback(error); + }); }; public readonly write: FsCallbackApi['write'] = (fd: number, a?, b?, c?, d?, e?) => { diff --git a/src/fsa-to-node/__tests__/FsaNodeFs.test.ts b/src/fsa-to-node/__tests__/FsaNodeFs.test.ts index f812677fd..f9b4ffb90 100644 --- a/src/fsa-to-node/__tests__/FsaNodeFs.test.ts +++ b/src/fsa-to-node/__tests__/FsaNodeFs.test.ts @@ -191,3 +191,12 @@ describe('.unlink()', () => { } }); }); + +describe('.readFile()', () => { + test('can read file contents', async () => { + const { fs } = setup({ folder: { file: 'test' }, 'empty-folder': null }); + const data = await fs.promises.readFile('/folder/file'); + expect(data.toString()).toBe('test'); + }); +}); + diff --git a/src/node/constants.ts b/src/node/constants.ts index fb27d7ac0..de127bd5b 100644 --- a/src/node/constants.ts +++ b/src/node/constants.ts @@ -1,3 +1,5 @@ +import {constants} from "../constants"; + // Default modes for opening files. export const enum MODE { FILE = 0o666, @@ -22,3 +24,52 @@ export const ERRSTR = { LENGTH: 'length must be an integer', POSITION: 'position must be an integer', }; + +const { + O_RDONLY, + O_WRONLY, + O_RDWR, + O_CREAT, + O_EXCL, + O_TRUNC, + O_APPEND, + O_SYNC, + O_DIRECTORY, + F_OK, + COPYFILE_EXCL, + COPYFILE_FICLONE_FORCE, +} = constants; + +// List of file `flags` as defined by Node. +export enum FLAGS { + // Open file for reading. An exception occurs if the file does not exist. + r = O_RDONLY, + // Open file for reading and writing. An exception occurs if the file does not exist. + 'r+' = O_RDWR, + // Open file for reading in synchronous mode. Instructs the operating system to bypass the local file system cache. + rs = O_RDONLY | O_SYNC, + sr = FLAGS.rs, + // Open file for reading and writing, telling the OS to open it synchronously. See notes for 'rs' about using this with caution. + 'rs+' = O_RDWR | O_SYNC, + 'sr+' = FLAGS['rs+'], + // Open file for writing. The file is created (if it does not exist) or truncated (if it exists). + w = O_WRONLY | O_CREAT | O_TRUNC, + // Like 'w' but fails if path exists. + wx = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, + xw = FLAGS.wx, + // Open file for reading and writing. The file is created (if it does not exist) or truncated (if it exists). + 'w+' = O_RDWR | O_CREAT | O_TRUNC, + // Like 'w+' but fails if path exists. + 'wx+' = O_RDWR | O_CREAT | O_TRUNC | O_EXCL, + 'xw+' = FLAGS['wx+'], + // Open file for appending. The file is created if it does not exist. + a = O_WRONLY | O_APPEND | O_CREAT, + // Like 'a' but fails if path exists. + ax = O_WRONLY | O_APPEND | O_CREAT | O_EXCL, + xa = FLAGS.ax, + // Open file for reading and appending. The file is created if it does not exist. + 'a+' = O_RDWR | O_APPEND | O_CREAT, + // Like 'a+' but fails if path exists. + 'ax+' = O_RDWR | O_APPEND | O_CREAT | O_EXCL, + 'xa+' = FLAGS['ax+'], +} diff --git a/src/node/options.ts b/src/node/options.ts index 00ca17830..b581f2d48 100644 --- a/src/node/options.ts +++ b/src/node/options.ts @@ -64,3 +64,8 @@ export const getRmdirOptions = (options): opts.IRmdirOptions => { const getRmOpts = optsGenerator(optsDefaults); export const getRmOptsAndCb = optsAndCbGenerator(getRmOpts); + +const readFileOptsDefaults: opts.IReadFileOptions = { + flag: 'r', +}; +export const getReadFileOptions = optsGenerator(readFileOptsDefaults); diff --git a/src/node/util.ts b/src/node/util.ts index cb9be4180..a24a7488b 100644 --- a/src/node/util.ts +++ b/src/node/util.ts @@ -1,4 +1,4 @@ -import { ERRSTR } from './constants'; +import { ERRSTR, FLAGS } from './constants'; import * as errors from '../internal/errors'; import type { FsCallbackApi } from './types'; import type * as misc from './types/misc'; @@ -146,3 +146,15 @@ export function genRndStr6(): string { if (str.length === 6) return str; else return genRndStr6(); } + +export function flagsToNumber(flags: misc.TFlags | undefined): number { + if (typeof flags === 'number') return flags; + + if (typeof flags === 'string') { + const flagsNum = FLAGS[flags]; + if (typeof flagsNum !== 'undefined') return flagsNum; + } + + // throw new TypeError(formatError(ERRSTR_FLAG(flags))); + throw new errors.TypeError('ERR_INVALID_OPT_VALUE', 'flags', flags); +} diff --git a/src/volume.ts b/src/volume.ts index 019b845b3..e34af350f 100644 --- a/src/volume.ts +++ b/src/volume.ts @@ -10,23 +10,23 @@ import { Readable, Writable } from 'stream'; import { constants } from './constants'; import { EventEmitter } from 'events'; import { TEncodingExtended, TDataOut, strToEncoding, ENCODING_UTF8 } from './encoding'; -import * as errors from './internal/errors'; import * as util from 'util'; import * as opts from './node/types/options'; import { createPromisesApi } from './node/promises'; -import { ERRSTR, MODE } from './node/constants'; +import { ERRSTR, FLAGS, MODE } from './node/constants'; import { getDefaultOpts, getDefaultOptsAndCb, getMkdirOptions, getOptions, + getReadFileOptions, getRmOptsAndCb, getRmdirOptions, optsAndCbGenerator, optsDefaults, optsGenerator, } from './node/options'; -import { validateCallback, modeToNumber, pathToFilename, nullCheck, createError, genRndStr6 } from './node/util'; +import { validateCallback, modeToNumber, pathToFilename, nullCheck, createError, genRndStr6, flagsToNumber } from './node/util'; import type { PathLike, symlink } from 'fs'; const resolveCrossPlatform = pathModule.resolve; @@ -90,70 +90,15 @@ const ERR_FS_EISDIR = 'ERR_FS_EISDIR'; // ---------------------------------------- Flags -// List of file `flags` as defined by Node. -export enum FLAGS { - // Open file for reading. An exception occurs if the file does not exist. - r = O_RDONLY, - // Open file for reading and writing. An exception occurs if the file does not exist. - 'r+' = O_RDWR, - // Open file for reading in synchronous mode. Instructs the operating system to bypass the local file system cache. - rs = O_RDONLY | O_SYNC, - sr = FLAGS.rs, - // Open file for reading and writing, telling the OS to open it synchronously. See notes for 'rs' about using this with caution. - 'rs+' = O_RDWR | O_SYNC, - 'sr+' = FLAGS['rs+'], - // Open file for writing. The file is created (if it does not exist) or truncated (if it exists). - w = O_WRONLY | O_CREAT | O_TRUNC, - // Like 'w' but fails if path exists. - wx = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, - xw = FLAGS.wx, - // Open file for reading and writing. The file is created (if it does not exist) or truncated (if it exists). - 'w+' = O_RDWR | O_CREAT | O_TRUNC, - // Like 'w+' but fails if path exists. - 'wx+' = O_RDWR | O_CREAT | O_TRUNC | O_EXCL, - 'xw+' = FLAGS['wx+'], - // Open file for appending. The file is created if it does not exist. - a = O_WRONLY | O_APPEND | O_CREAT, - // Like 'a' but fails if path exists. - ax = O_WRONLY | O_APPEND | O_CREAT | O_EXCL, - xa = FLAGS.ax, - // Open file for reading and appending. The file is created if it does not exist. - 'a+' = O_RDWR | O_APPEND | O_CREAT, - // Like 'a+' but fails if path exists. - 'ax+' = O_RDWR | O_APPEND | O_CREAT | O_EXCL, - 'xa+' = FLAGS['ax+'], -} - export type TFlagsCopy = | typeof constants.COPYFILE_EXCL | typeof constants.COPYFILE_FICLONE | typeof constants.COPYFILE_FICLONE_FORCE; -export function flagsToNumber(flags: TFlags | undefined): number { - if (typeof flags === 'number') return flags; - - if (typeof flags === 'string') { - const flagsNum = FLAGS[flags]; - if (typeof flagsNum !== 'undefined') return flagsNum; - } - - // throw new TypeError(formatError(ERRSTR_FLAG(flags))); - throw new errors.TypeError('ERR_INVALID_OPT_VALUE', 'flags', flags); -} - // ---------------------------------------- Options // General options with optional `encoding` property that most commands accept. -// Options for `fs.readFile` and `fs.readFileSync`. -export interface IReadFileOptions extends opts.IOptions { - flag?: string; -} -const readFileOptsDefaults: IReadFileOptions = { - flag: 'r', -}; -const getReadFileOptions = optsGenerator(readFileOptsDefaults); - // Options for `fs.writeFile` and `fs.writeFileSync` export interface IWriteFileOptions extends opts.IFileOptions {} const writeFileDefaults: IWriteFileOptions = { @@ -177,7 +122,7 @@ const getAppendFileOptsAndCb = optsAndCbGenerator(getA export interface IRealpathOptions { encoding?: TEncodingExtended; } -const realpathDefaults: IReadFileOptions = optsDefaults; +const realpathDefaults: opts.IReadFileOptions = optsDefaults; const getRealpathOptions = optsGenerator(realpathDefaults); const getRealpathOptsAndCb = optsAndCbGenerator(getRealpathOptions); @@ -972,16 +917,16 @@ export class Volume { return result; } - readFileSync(file: TFileId, options?: IReadFileOptions | string): TDataOut { + readFileSync(file: TFileId, options?: opts.IReadFileOptions | string): TDataOut { const opts = getReadFileOptions(options); const flagsNum = flagsToNumber(opts.flag); return this.readFileBase(file, flagsNum, opts.encoding as BufferEncoding); } readFile(id: TFileId, callback: TCallback); - readFile(id: TFileId, options: IReadFileOptions | string, callback: TCallback); - readFile(id: TFileId, a: TCallback | IReadFileOptions | string, b?: TCallback) { - const [opts, callback] = optsAndCbGenerator>(getReadFileOptions)(a, b); + readFile(id: TFileId, options: opts.IReadFileOptions | string, callback: TCallback); + readFile(id: TFileId, a: TCallback | opts.IReadFileOptions | string, b?: TCallback) { + const [opts, callback] = optsAndCbGenerator>(getReadFileOptions)(a, b); const flagsNum = flagsToNumber(opts.flag); this.wrapAsync(this.readFileBase, [id, flagsNum, opts.encoding], callback); }