Skip to content

Commit

Permalink
feat: 🎸 implement first version of readFile() method
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Jun 20, 2023
1 parent e11a383 commit e046128
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 72 deletions.
31 changes: 23 additions & 8 deletions src/fsa-to-node/FsaNodeFs.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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');
Expand Down Expand Up @@ -41,11 +42,11 @@ export class FsaNodeFs implements FsCallbackApi {
return curr;
}

// private async getFile(path: string[], name: string, funcName?: string): Promise<fsa.IFileSystemFileHandle> {
// 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<fsa.IFileSystemFileHandle> {
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,
Expand Down Expand Up @@ -76,7 +77,21 @@ export class FsaNodeFs implements FsCallbackApi {
a?: opts.IReadFileOptions | string | misc.TCallback<misc.TDataOut>,
b?: misc.TCallback<misc.TDataOut>,
) => {
throw new Error('Not implemented');
const [opts, callback] = optsAndCbGenerator<opts.IReadFileOptions, misc.TDataOut>(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?) => {
Expand Down
9 changes: 9 additions & 0 deletions src/fsa-to-node/__tests__/FsaNodeFs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});

51 changes: 51 additions & 0 deletions src/node/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {constants} from "../constants";

// Default modes for opening files.
export const enum MODE {
FILE = 0o666,
Expand All @@ -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+'],
}
5 changes: 5 additions & 0 deletions src/node/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,8 @@ export const getRmdirOptions = (options): opts.IRmdirOptions => {

const getRmOpts = optsGenerator<opts.IOptions>(optsDefaults);
export const getRmOptsAndCb = optsAndCbGenerator<opts.IRmOptions, any>(getRmOpts);

const readFileOptsDefaults: opts.IReadFileOptions = {
flag: 'r',
};
export const getReadFileOptions = optsGenerator<opts.IReadFileOptions>(readFileOptsDefaults);
14 changes: 13 additions & 1 deletion src/node/util.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
}
71 changes: 8 additions & 63 deletions src/volume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<IReadFileOptions>(readFileOptsDefaults);

// Options for `fs.writeFile` and `fs.writeFileSync`
export interface IWriteFileOptions extends opts.IFileOptions {}
const writeFileDefaults: IWriteFileOptions = {
Expand All @@ -177,7 +122,7 @@ const getAppendFileOptsAndCb = optsAndCbGenerator<IAppendFileOptions, void>(getA
export interface IRealpathOptions {
encoding?: TEncodingExtended;
}
const realpathDefaults: IReadFileOptions = optsDefaults;
const realpathDefaults: opts.IReadFileOptions = optsDefaults;
const getRealpathOptions = optsGenerator<IRealpathOptions>(realpathDefaults);
const getRealpathOptsAndCb = optsAndCbGenerator<IRealpathOptions, TDataOut>(getRealpathOptions);

Expand Down Expand Up @@ -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<TDataOut>);
readFile(id: TFileId, options: IReadFileOptions | string, callback: TCallback<TDataOut>);
readFile(id: TFileId, a: TCallback<TDataOut> | IReadFileOptions | string, b?: TCallback<TDataOut>) {
const [opts, callback] = optsAndCbGenerator<IReadFileOptions, TCallback<TDataOut>>(getReadFileOptions)(a, b);
readFile(id: TFileId, options: opts.IReadFileOptions | string, callback: TCallback<TDataOut>);
readFile(id: TFileId, a: TCallback<TDataOut> | opts.IReadFileOptions | string, b?: TCallback<TDataOut>) {
const [opts, callback] = optsAndCbGenerator<opts.IReadFileOptions, TCallback<TDataOut>>(getReadFileOptions)(a, b);
const flagsNum = flagsToNumber(opts.flag);
this.wrapAsync(this.readFileBase, [id, flagsNum, opts.encoding], callback);
}
Expand Down

0 comments on commit e046128

Please sign in to comment.