Skip to content

Commit

Permalink
feat: add relative option support for mkdir and mkdirSync
Browse files Browse the repository at this point in the history
Add support for `relative` option for `fs.mkdir` and `fs.mkdirSync` functions by using `vol.mkdirp` implementation. 

Make `vol.mkdirp` and `vol.mkdirpSync` functions deprecated.
  • Loading branch information
pizzafroide committed Nov 5, 2018
1 parent f879f9b commit 15aae89
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 70 deletions.
4 changes: 2 additions & 2 deletions docs/api-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ implemented (you have access to any file), basically `fs.access()` is a no-op.
- [x] `linkSync(existingPath, newPath)`
- [x] `lstat(path, callback)`
- [x] `lstatSync(path)`
- [x] `mkdir(path[, mode], callback)`
- [x] `mkdirSync(path[, mode])`
- [x] `mkdir(path[, options], callback)`
- [x] `mkdirSync(path[, options])`
- [x] `mkdtemp(prefix[, options], callback)`
- [x] `mkdtempSync(prefix[, options])`
- [x] `open(path, flags[, mode], callback)`
Expand Down
8 changes: 6 additions & 2 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,17 @@ vol.reset();
vol.toJSON(); // {}
```

#### `vol.mkdirp(path, callback)`
#### `vol.mkdirp(path[, mode], callback)`

Legacy interface, which now uses the `recursive` option of `vol.mkdir`.

Creates a directory tree recursively. `path` specifies a directory to
create and can be a string, `Buffer`, or an `URL` object. `callback` is
called on completion and may receive only one argument - an `Error` object.

#### `vol.mkdirpSync(path)`
#### `vol.mkdirpSync(path[, mode])`

Legacy interface, which now uses the `recursive` option of `vol.mkdirSync`.

A synchronous version of `vol.mkdirp()`. This method throws.

Expand Down
34 changes: 15 additions & 19 deletions src/__tests__/volume.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,7 @@ describe('volume', () => {
describe('.utimes(path, atime, mtime, callback)', () => {
xit('...', () => {});
});
describe('.mkdirSync(path[, mode])', () => {
describe('.mkdirSync(path[, options])', () => {
it('Create dir at root', () => {
const vol = new Volume;
vol.mkdirSync('/test');
Expand All @@ -845,9 +845,23 @@ describe('volume', () => {
expect(dir2.getNode().isDirectory()).toBe(true);
expect(dir2.getPath()).toBe('/dir1/dir2');
});
it('Create /dir1/dir2/dir3 recursively', () => {
const vol = new Volume;
vol.mkdirSync('/dir1/dir2/dir3', { recursive: true });
const dir1 = vol.root.getChild('dir1');
const dir2 = dir1.getChild('dir2');
const dir3 = dir2.getChild('dir3');
expect(dir1).toBeInstanceOf(Link);
expect(dir2).toBeInstanceOf(Link);
expect(dir3).toBeInstanceOf(Link);
expect(dir1.getNode().isDirectory()).toBe(true);
expect(dir2.getNode().isDirectory()).toBe(true);
expect(dir3.getNode().isDirectory()).toBe(true);
});
});
describe('.mkdir(path[, mode], callback)', () => {
xit('...');
xit('Create /dir1/dir2/dir3', () => { });
});
describe('.mkdtempSync(prefix[, options])', () => {
it('Create temp dir at root', () => {
Expand All @@ -860,24 +874,6 @@ describe('volume', () => {
describe('.mkdtemp(prefix[, options], callback)', () => {
xit('Create temp dir at root', () => {});
});
describe('.mkdirpSync(path[, mode])', () => {
it('Create /dir1/dir2/dir3', () => {
const vol = new Volume;
vol.mkdirpSync('/dir1/dir2/dir3');
const dir1 = vol.root.getChild('dir1');
const dir2 = dir1.getChild('dir2');
const dir3 = dir2.getChild('dir3');
expect(dir1).toBeInstanceOf(Link);
expect(dir2).toBeInstanceOf(Link);
expect(dir3).toBeInstanceOf(Link);
expect(dir1.getNode().isDirectory()).toBe(true);
expect(dir2.getNode().isDirectory()).toBe(true);
expect(dir3.getNode().isDirectory()).toBe(true);
});
});
describe('.mkdirp(path[, mode], callback)', () => {
xit('Create /dir1/dir2/dir3', () => {});
});
describe('.rmdirSync(path)', () => {
it('Remove single dir', () => {
const vol = new Volume;
Expand Down
114 changes: 67 additions & 47 deletions src/volume.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,21 @@ export interface IWatchOptions extends IOptions {
recursive?: boolean,
}

// Options for `fs.mkdir` and `fs.mkdirSync`
export interface IMkdirOptions {
mode?: TMode,
recursive?: boolean,
};
const mkdirDefaults: IMkdirOptions = {
mode: MODE.DIR,
recursive: false,
};
const getMkdirOptions = options => {
if (typeof options === 'number')
return extend({}, mkdirDefaults, { mode: options });
return extend({}, mkdirDefaults, options);
}



// ---------------------------------------- Utility functions
Expand Down Expand Up @@ -1725,19 +1740,63 @@ export class Volume {
dir.createChild(name, this.createNode(true, modeNum));
}

mkdirSync(path: TFilePath, mode?: TMode) {
const modeNum = modeToNumber(mode, 0o777);
/**
* Creates directory tree recursively.
* @param filename
* @param modeNum
*/
private mkdirpBase(filename: string, modeNum: number) {
const steps = filenameToSteps(filename);
let link = this.root;
for (let i = 0; i < steps.length; i++) {
const step = steps[i];

if (!link.getNode().isDirectory())
throwError(ENOTDIR, 'mkdir', link.getPath());

const child = link.getChild(step);
if (child) {
if (child.getNode().isDirectory()) link = child;
else throwError(ENOTDIR, 'mkdir', child.getPath());
} else {
link = link.createChild(step, this.createNode(true, modeNum));
}
}
}

mkdirSync(path: TFilePath, options?: TMode | IMkdirOptions) {
const opts = getMkdirOptions(options);
const modeNum = modeToNumber(opts.mode, 0o777);
const filename = pathToFilename(path);
this.mkdirBase(filename, modeNum);
if (opts.recursive)
this.mkdirpBase(filename, modeNum);
else
this.mkdirBase(filename, modeNum);
}

mkdir(path: TFilePath, callback: TCallback<void>);
mkdir(path: TFilePath, mode: TMode, callback: TCallback<void>);
mkdir(path: TFilePath, a: TCallback<void> | TMode, b?: TCallback<void>) {
const [mode, callback] = getArgAndCb<TMode, void>(a, b);
const modeNum = modeToNumber(mode, 0o777);
mkdir(path: TFilePath, mode: TMode | IMkdirOptions, callback: TCallback<void>);
mkdir(path: TFilePath, a: TCallback<void> | TMode | IMkdirOptions, b?: TCallback<void>) {
const [options, callback] = getArgAndCb<TMode | IMkdirOptions, void>(a, b);
const opts = getMkdirOptions(options);
const modeNum = modeToNumber(opts.mode, 0o777);
const filename = pathToFilename(path);
this.wrapAsync(this.mkdirBase, [filename, modeNum], callback);
if (opts.recursive)
this.wrapAsync(this.mkdirpBase, [filename, modeNum], callback);
else
this.wrapAsync(this.mkdirBase, [filename, modeNum], callback);
}

// legacy interface
mkdirpSync(path: TFilePath, mode?: TMode) {
this.mkdirSync(path, { mode, recursive: true });
}

mkdirp(path: TFilePath, callback: TCallback<void>);
mkdirp(path: TFilePath, mode: TMode, callback: TCallback<void>);
mkdirp(path: TFilePath, a: TCallback<void> | TMode, b?: TCallback<void>) {
const [mode, callback] = getArgAndCb<TMode, void>(a, b);
this.mkdir(path, { mode, recursive: true }, callback);
}

private mkdtempBase(prefix: string, encoding: TEncodingExtended, retry: number = 5): TDataOut {
Expand Down Expand Up @@ -1777,45 +1836,6 @@ export class Volume {
this.wrapAsync(this.mkdtempBase, [prefix, encoding], callback);
}

/**
* Creates directory tree recursively.
* @param filename
* @param modeNum
*/
private mkdirpBase(filename: string, modeNum: number) {
const steps = filenameToSteps(filename);
let link = this.root;
for(let i = 0; i < steps.length; i++) {
const step = steps[i];

if(!link.getNode().isDirectory())
throwError(ENOTDIR, 'mkdirp', link.getPath());

const child = link.getChild(step);
if(child) {
if(child.getNode().isDirectory()) link = child;
else throwError(ENOTDIR, 'mkdirp', child.getPath());
} else {
link = link.createChild(step, this.createNode(true, modeNum));
}
}
}

mkdirpSync(path: TFilePath, mode?: TMode) {
const modeNum = modeToNumber(mode, 0o777);
const filename = pathToFilename(path);
this.mkdirpBase(filename, modeNum);
}

mkdirp(path: TFilePath, callback: TCallback<void>);
mkdirp(path: TFilePath, mode: TMode, callback: TCallback<void>);
mkdirp(path: TFilePath, a: TCallback<void> | TMode, b?: TCallback<void>) {
const [mode, callback] = getArgAndCb<TMode, void>(a, b);
const modeNum = modeToNumber(mode, 0o777);
const filename = pathToFilename(path);
this.wrapAsync(this.mkdirpBase, [filename, modeNum], callback);
}

private rmdirBase(filename: string) {
const link = this.getLinkAsDirOrThrow(filename, 'rmdir');

Expand Down

0 comments on commit 15aae89

Please sign in to comment.