Skip to content

Commit

Permalink
feat: 🎸 add rm() method
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Jun 16, 2023
1 parent 973af0a commit 29a7dc8
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 20 deletions.
37 changes: 30 additions & 7 deletions src/fsa-to-node/FsaNodeFs.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { createPromisesApi } from '../node/promises';
import { getDefaultOptsAndCb, getMkdirOptions, getRmdirOptions } from '../node/options';
import { getDefaultOptsAndCb, getMkdirOptions, getRmOptsAndCb, getRmdirOptions } from '../node/options';
import { createError, genRndStr6, nullCheck, pathToFilename, validateCallback } from '../node/util';
import { pathToLocation } from './util';
import { MODE } from '../node/constants';
import { strToEncoding } from '../encoding';
import {FsaToNodeConstants} from './constants';
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 {FsaToNodeConstants} from './constants';

const notImplemented: (...args: unknown[]) => unknown = () => {
throw new Error('Not implemented');
Expand Down Expand Up @@ -293,11 +293,34 @@ export class FsaNodeFs implements FsCallbackApi {
});
};

rm(path: misc.PathLike, callback: misc.TCallback<void>): void;
rm(path: misc.PathLike, options: opts.IRmOptions, callback: misc.TCallback<void>): void;
rm(path: misc.PathLike, a: misc.TCallback<void> | opts.IRmOptions, b?: misc.TCallback<void>): void {
throw new Error('Not implemented');
}
public readonly rm: FsCallbackApi['rm'] = (path: misc.PathLike, a: misc.TCallback<void> | opts.IRmOptions, b?: misc.TCallback<void>): void => {
const [options, callback] = getRmOptsAndCb(a, b);
const [folder, name] = pathToLocation(pathToFilename(path));
this.getDir(folder, false, 'rmdir')
.then(dir => dir.getDirectoryHandle(name).then(() => dir))
.then(dir => dir.removeEntry(name, {recursive: options.recursive ?? false}))
.then(() => callback(null), error => {
if (options.force) {
callback(null);
return;
}
if (error && typeof error === 'object') {
switch (error.name) {
case 'NotFoundError': {
const err = createError('ENOENT', 'rmdir', folder.join('/'));
callback(err);
return;
}
case 'InvalidModificationError': {
const err = createError('ENOTEMPTY', 'rmdir', folder.join('/'));
callback(err);
return;
}
}
}
callback(error);
});
};

fchmod(fd: number, mode: misc.TMode, callback: misc.TCallback<void>): void {
throw new Error('Not implemented');
Expand Down
49 changes: 49 additions & 0 deletions src/fsa-to-node/__tests__/FsaNodeFs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,52 @@ describe('.rmdir()', () => {
});
});
});

describe('.rm()', () => {
test('can remove an empty folder', async () => {
const { fs, mfs } = setup({ folder: { file: 'test' }, 'empty-folder': null });
await fs.promises.rm('/empty-folder');
expect(mfs.__vol.toJSON()).toStrictEqual({'/mountpoint/folder/file': 'test'});
});

test('throws when attempts to remove non-empty folder', async () => {
const { fs, mfs } = setup({ folder: { file: 'test' }, 'empty-folder': null });
try {
await fs.promises.rm('/folder');
throw new Error('Expected error');
} catch (error) {
expect(error.code).toBe('ENOTEMPTY');
expect(mfs.__vol.toJSON()).toStrictEqual({
'/mountpoint/folder/file': 'test',
'/mountpoint/empty-folder': null,
});
}
});

test('can remove non-empty directory recursively', async () => {
const { fs, mfs } = setup({ folder: { subfolder: {file: 'test'} }, 'empty-folder': null });
await fs.promises.rm('/folder', {recursive: true});
expect(mfs.__vol.toJSON()).toStrictEqual({
'/mountpoint/empty-folder': null,
});
});

test('throws if path does not exist', async () => {
const { fs, mfs } = setup({ folder: { subfolder: {file: 'test'} }, 'empty-folder': null });
try {
await fs.promises.rm('/lala/lulu', {recursive: true});
throw new Error('Expected error');
} catch (error) {
expect(error.code).toBe('ENOENT');
expect(mfs.__vol.toJSON()).toStrictEqual({
'/mountpoint/folder/subfolder/file': 'test',
'/mountpoint/empty-folder': null,
});
}
});

test('does not throw, if path does not exist, but "force" flag set', async () => {
const { fs } = setup({ folder: { subfolder: {file: 'test'} }, 'empty-folder': null });
await fs.promises.rm('/lala/lulu', {recursive: true, force: true});
});
});
3 changes: 3 additions & 0 deletions src/node/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,6 @@ const rmdirDefaults: opts.IRmdirOptions = {
export const getRmdirOptions = (options): opts.IRmdirOptions => {
return Object.assign({}, rmdirDefaults, options);
};

const getRmOpts = optsGenerator<opts.IOptions>(optsDefaults);
export const getRmOptsAndCb = optsAndCbGenerator<opts.IRmOptions, any>(getRmOpts);
18 changes: 5 additions & 13 deletions src/volume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
getDefaultOptsAndCb,
getMkdirOptions,
getOptions,
getRmOptsAndCb,
getRmdirOptions,
optsAndCbGenerator,
optsDefaults,
Expand Down Expand Up @@ -219,15 +220,6 @@ export interface IMkdirOptions {
recursive?: boolean;
}

export interface IRmOptions {
force?: boolean;
maxRetries?: number;
recursive?: boolean;
retryDelay?: number;
}
const getRmOpts = optsGenerator<opts.IOptions>(optsDefaults);
const getRmOptsAndCb = optsAndCbGenerator<IRmOptions, any>(getRmOpts);

// Options for `fs.readdir` and `fs.readdirSync`
export interface IReaddirOptions extends opts.IOptions {
withFileTypes?: boolean;
Expand Down Expand Up @@ -1883,7 +1875,7 @@ export class Volume {
this.wrapAsync(this.rmdirBase, [pathToFilename(path), opts], callback);
}

private rmBase(filename: string, options: IRmOptions = {}): void {
private rmBase(filename: string, options: opts.IRmOptions = {}): void {
const link = this.getResolvedLink(filename);
if (!link) {
// "stat" is used to match Node's native error message.
Expand All @@ -1898,13 +1890,13 @@ export class Volume {
this.deleteLink(link);
}

public rmSync(path: PathLike, options?: IRmOptions): void {
public rmSync(path: PathLike, options?: opts.IRmOptions): void {
this.rmBase(pathToFilename(path), options);
}

public rm(path: PathLike, callback: TCallback<void>): void;
public rm(path: PathLike, options: IRmOptions, callback: TCallback<void>): void;
public rm(path: PathLike, a: TCallback<void> | IRmOptions, b?: TCallback<void>): void {
public rm(path: PathLike, options: opts.IRmOptions, callback: TCallback<void>): void;
public rm(path: PathLike, a: TCallback<void> | opts.IRmOptions, b?: TCallback<void>): void {
const [opts, callback] = getRmOptsAndCb(a, b);
this.wrapAsync(this.rmBase, [pathToFilename(path), opts], callback);
}
Expand Down

0 comments on commit 29a7dc8

Please sign in to comment.