easypathutil
Advanced tools
Comparing version 1.2.3 to 1.2.4
{ | ||
"name": "easypathutil", | ||
"version": "1.2.3", | ||
"version": "1.2.4", | ||
"description": "Fluent filepaths, made simple.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
139
README.md
@@ -22,3 +22,3 @@ <div align="center"> | ||
npm install easypathutil@1.2.3 | ||
npm install easypathutil@1.2.4 | ||
@@ -35,3 +35,3 @@ ### Two-Part Motivation | ||
• Updated and Lightweight: Package size <7.5kB | ||
• Updated and Lightweight: Package size <10kB | ||
@@ -56,3 +56,3 @@ The tutorial below aims to demonstrate the core functionality of this package. | ||
**Quickstart Usage and Examples** | ||
#### **Quickstart Usage and Examples** | ||
@@ -65,3 +65,3 @@ const Builder = require('easypathutil'); | ||
**Fluent Interface Examples (chaining, constructor, .toString, [] vs ())** | ||
#### **Fluent Interface Examples (chaining, constructor, .toString, [] vs ())** | ||
@@ -80,2 +80,3 @@ // Using process.cwd() (root/home/projects/myfolder) as base | ||
Promise: somePromisePackage || global.Promise, | ||
filter: filepath => should_dive_folder(filepath), // This function checks recursive directory dives with a given filter. More below. | ||
}); | ||
@@ -92,3 +93,3 @@ | ||
**Going backwards and resetting to base path (.$back, .$reset)** | ||
#### **Going backwards and resetting to base path (.$back, .$reset)** | ||
@@ -104,3 +105,3 @@ // Reminder: myjsfile() is '/root/home/projects/myfolder/foo/bar/myjsfile.js' | ||
**Get file contents (.$readfile, .$readfilesync)** | ||
#### **Get file contents (.$readfile, .$readfilesync)** | ||
@@ -122,3 +123,3 @@ const myjsfilebuffer = myjsfile.$readfilesync; // $readfilesync property calls fs.readFileSync | ||
**Easy require (.$require, $require_default)** | ||
#### **Easy require (.$require, $require_default)** | ||
@@ -131,3 +132,3 @@ const imported = myjsfile.$require; // $require property wraps a require() around the target. | ||
**Load JSON without require (.$json)** | ||
#### **Load JSON without require (.$json)** | ||
@@ -137,3 +138,3 @@ const jsonfile = myfolder('jsonfile.json'); // Points to /root/home/projects/myfolder/jsonfile.json | ||
**Read directory recursively, returning an array of absolute paths to files (.$read_dir, .$read_dir_sync)** | ||
#### **Read directory recursively, returning an array of absolute paths to files (.$read_dir, .$read_dir_sync)** | ||
@@ -146,4 +147,102 @@ const filearray = myfolder.$read_dir_sync | ||
**New object shortcut (.$new, .$new_default)** | ||
**Advanced Feature: Recursive Dive Prevention** | ||
Please note that this feature is for more advanced users only who specifically have this need. You may skip down to the next section if you | ||
are reading directories only for files. | ||
Recall back to how to construct the object with options: | ||
const myfolder = new Builder('/root/home/projects/myfolder', { | ||
/* …other options… */ | ||
filter: filepath => should_dive_folder(filepath), // This function checks recursive directory dives with a given filter. More below. | ||
}); | ||
The filter function can be used to prevent recursive dives. This is useful if you want a list of *folders*. After all, if you wanted to | ||
filter the files returned, the fastest way would be read every file with a basic `myfolder.$readdirsync` and then apply a | ||
`.filter(e => e.endsWith('.mycustomextension')` to the array returned. However, what if you only wanted to read every folder underneath, | ||
say, /src? What if you had a situation where you had folders structured like: `/data/translations/en/data1.json`, | ||
`/data/translations/en/data2.json`, `/data/translations/es/data1.json`, etc, but you only wanted the folder locations, namely | ||
`/data/translations/en/`, `/data/translations/es/`, etc? | ||
**In comes the filter function!** | ||
The filter function filters *out* paths to which it returns true during recursion, and you must apply Array#filter to *keep* what you want. | ||
In our first example, you must create a new object like so: | ||
const array = Builder('/some/path/here' || myfolder() || process.cwd(), { | ||
filter: function filter(path) { return path.endsWith('.ext') && !this.get_stat_sync(path).directory; } | ||
}).$readdirsync | ||
.filter(e => e.endsWith('.ext')); | ||
This will return everything, files and folders, whos name ends with .ext | ||
Optionally, declare the function beforehand: | ||
function filter(path) { | ||
return !path.endsWith('.ext') && this.get_stat_sync(path).directory; | ||
} | ||
const array = Builder(absolutepath, { filter }).$readdirsync | ||
.filter(path => path.endsWith('.ext')); | ||
Don't want the files? You can chain Array#filter in nodejs | ||
const folders_only = array.filter(path => require('fs').statSync(path).isDirectory()); | ||
Or just filter once for better performance: | ||
const array = Builder(absolutepath, { filter }).$readdirsync | ||
.filter(path => path.endsWith('.ext') && require('fs').statSync(path).isDirectory()); | ||
You may specify also filter parameter as two seperate functions for synchronous and async versions of .$readdir and .$readdirsync | ||
const sync = function filter_sync(path) { | ||
return !path.endsWith('.ext') && this.get_stat_sync(path).directory; | ||
}; | ||
const async = async function filter_async(path) { | ||
if (path.endsWith('.ext')) return false; // Think about this line as: if the file or folder we are looking at ends with .ext, stop recursing. | ||
const { directory } = await this.get_stat(path); | ||
return directory; // If directory is true, the path refers to a directory, so keep recursing, in case such files or folders that match the above case reside in subfolders of the one we are currently looking at. | ||
}; | ||
const folder = Builder(absolutepath, { | ||
filter: { sync, async }, | ||
}); | ||
// Use the synchronous version: | ||
const array = folder.$readdirsync.filter(path => path.endsWith('.ext') && require('fs').statSync(path).isDirectory()); | ||
// Use the parallel/asynchronous version: | ||
const fs = require('fs'); | ||
// Don't forget, you do not need to use .endsWith! | ||
// path.startsWith, regular expressions, and many more also work, as the path string can be freely manipulated | ||
// This applies to the filter functions as well! | ||
const promises = await folder.$readdir.map(path => /\.ext$/.test(path) && new Promise((res, rej) => { | ||
fs.stat(path, (err, stats) => { | ||
if (err) return rej(err); | ||
if (stats.isDirectory()) return res(path); | ||
return res(false); | ||
}); | ||
}); | ||
const array = await Promise.all(promises).filter(_ => _); | ||
What about your second example with the translations? | ||
function filter(e) { return !e.includes('translations') && this.get_stat_sync(e).directory; }; | ||
const array = Builder(process.cwd(), { filter }).subfolder.data.$readdirsync; // Reminder: we can use the same Builder to get to the folder first! | ||
The const array will now contain an array with everything one-level deep into /subfolder/data/translations. In our example, these would be | ||
the two .../en and .../es folders | ||
Notice my usage of the keyword `function` when creating these filter functions. I have not used arrow functions (=> lambdas) because of my | ||
use of "this" (this.get_stat_sync). The filter function is bound to the library's ReadHelper objects, which contain several internal helper | ||
functions to help abstract the directory reading process away from the node fs module. You are free to use arrow functions when this binding | ||
functionality is not of use to you. | ||
Have a more specific use case that you don't believe this covers? Open an issue on this package's github repository (linked below)! | ||
#### **New object shortcut (.$new, .$new_default)** | ||
Before: | ||
@@ -161,3 +260,3 @@ | ||
**About .$stat** | ||
#### **File stats (.$stat)** | ||
@@ -187,3 +286,3 @@ // Get file stats synchronously instead of wrapping with fs.statSync with extra function calls | ||
**Existence of a file or folder (in operator, Reflect.has, etc)** | ||
#### **Existence of a file or folder (in operator, Reflect.has, etc)** | ||
@@ -194,3 +293,3 @@ const boolean_exists = 'foldername' in myfolder; | ||
**Version** | ||
#### **Version** | ||
@@ -203,4 +302,10 @@ const version = require('easypathutil').version; | ||
## Changelog | ||
### New in 1.2.4 | ||
• Introduced an advanced feature for synchronous and async recursive directory read filtering ("filter" parameter in constructor). | ||
Should you notice anything wrong with this, please do open a reproducible issue or pull request on this package's github repository | ||
(linked below)! | ||
### New in 1.2.3 | ||
• Fixed several bugs differentiating between sync and async versions of .$ properties. (i.e. file.$stat and file.$stat.sync) | ||
• Fixed async reading of folders | ||
@@ -224,7 +329,11 @@ | ||
#### Enjoy this package? | ||
Consider starring on [github](https://github.com/wzhouwzhou/easypathutil) and checking out some of my other work: | ||
Enjoy this package? Consider starring on [github](https://github.com/wzhouwzhou/easypathutil) and checking out some of my other work: | ||
[Youtube Search API](https://npmjs.com/ytsearcher) | ||
[Urban Dictionary](https://npmjs.com/easyurban) | ||
Need support? Send me an email at wzhouwzhou@gmail.com, or connect with me on Discord at https://discord.gg/jj5FzF7 (William Zhou#0001) | ||
Like what you're seeing? Consider helping to fund my education through https://paypal.me/wzhouwzhou |
@@ -10,3 +10,4 @@ const name = exports.name = '$back'; | ||
Promise: this._Promise, | ||
readdir_filter: this.readdir_filter, | ||
}, this.parts.slice(0, -1)); | ||
}; |
@@ -10,3 +10,4 @@ const name = exports.name = '$reset'; | ||
Promise: this._Promise, | ||
readdir_filter: this.readdir_filter, | ||
}); | ||
}; |
@@ -12,4 +12,6 @@ 'use strict'; | ||
Promise = global.Promise, | ||
readdir_filter = null, | ||
filter = null, | ||
} = {}, parts = []) { | ||
if (!(this instanceof PathBuilder)) return new PathBuilder(base, { JSON, path, fs, Promise }, parts); | ||
if (!(this instanceof PathBuilder)) return new PathBuilder(base, { JSON, path, fs, Promise, readdir_filter, filter }, parts); | ||
this.base = base; | ||
@@ -21,3 +23,4 @@ this.parts = parts; | ||
this._Promise = Promise; | ||
this.read_dir = new ReadHelper(this, { fs, path, Promise }); | ||
this._readdir_filter = readdir_filter || filter; | ||
this.read_dir = new ReadHelper(this, { fs, path, Promise, readdir_filter, filter }); | ||
@@ -30,3 +33,5 @@ // Perform basic checks on potentially user-defined functions | ||
if (typeof Reflect.get(this._path, 'join') !== 'function') throw new Error('Invalid path object, "join" function property missing!'); | ||
if (typeof this._readdir_filter !== 'object' && typeof this._readdir_filter !== 'function' && this._readdir_filter !== null) { | ||
throw new Error('Invalid readdir filter, readdir_filter must be a function or null'); | ||
} | ||
/*\ | ||
@@ -42,3 +47,3 @@ * The hard Promise safety check has been disabled due to the wide variety of Promise libraries out there. | ||
if (!arga) return this._path.join(this.base, ...this.parts); | ||
return new PathBuilder(this.base, { JSON, path, fs, Promise }, [...this.parts, arga.toString()]); | ||
return new PathBuilder(this.base, { JSON, path, fs, Promise, readdir_filter, filter }, [...this.parts, arga.toString()]); | ||
}).bind(this), { has: has.bind(this), get: get.bind(this) }); | ||
@@ -45,0 +50,0 @@ return proxy; |
@@ -6,3 +6,3 @@ /* eslint consistent-return: 0 */ | ||
const ReadHelper = class ReadHelper { | ||
constructor(builder, { fs, path, Promise } = {}) { | ||
constructor(builder, { fs, path, Promise, readdir_filter, filter } = {}) { | ||
this.builder = builder; | ||
@@ -12,2 +12,7 @@ this.fs = fs; | ||
this._Promise = Promise; | ||
this._readdir_filter = readdir_filter || filter; | ||
this.sync_filter = (this._readdir_filter ? this._readdir_filter.sync || this._readdir_filter : | ||
_path => this.get_stat_sync(_path).directory).bind(this); | ||
this.async_filter = (this._readdir_filter ? this._readdir_filter.async || this._readdir_filter.parallel || this._readdir_filter : | ||
_path => this.get_stat(_path).then(stat => stat.directory)).bind(this); | ||
this.load_proxy(); | ||
@@ -17,4 +22,4 @@ } | ||
_normalize(sync, filter) { | ||
if (sync === true && !filter) return path => this.get_stat_sync(path).directory; | ||
if (sync === false && !filter) return path => this.get_stat(path).then(stat => stat.directory); | ||
if (sync === true && !filter) return this.sync_filter; | ||
if (sync === false && !filter) return this.async_filter; | ||
@@ -110,32 +115,7 @@ let ref = filter || sync; | ||
const bigInt = !stringprop.toLowerCase().includes('legacy') && !stringprop.toLowerCase().includes('number'); | ||
return new Promise((res, rej) => this.fs.stat(path, { bigInt }, (err, stat) => { | ||
if (err) { | ||
if (err.code === 'ENOENT') return res(false); | ||
return rej(err); | ||
} | ||
const isDir = stat.isDirectory(), | ||
isFile = stat.isFile(), | ||
isBigInt = typeof stat.size === 'bigint'; // eslint-disable-line valid-typeof | ||
try { | ||
return res({ ...stat, | ||
isBigInt, | ||
isFile, file: isFile, | ||
isDir, folder: isDir, | ||
directory: isDir, | ||
isFolder: isDir, | ||
isDirectory: isDir, | ||
}); | ||
} catch (_err) { | ||
if (_err && _err instanceof SyntaxError) { | ||
return res(Object.assign(stat, { | ||
isBigInt, | ||
file: isFile, | ||
folder: isDir, | ||
directory: isDir, | ||
})); | ||
} | ||
return rej(_err); | ||
} | ||
})); | ||
try { | ||
return new Promise((res, rej) => this.fs.stat(path, { bigInt }, (err, stat) => this.get_stat_cb(err, stat, { res, rej }))); | ||
} catch (_node_version_error) { | ||
return new Promise((res, rej) => this.fs.stat(path, (err, stat) => this.get_stat_cb(err, stat, { res, rej }))); | ||
} | ||
} catch (err) { | ||
@@ -147,4 +127,36 @@ if (err && err.code === 'ENOENT') return this._Promise.resolve(false); | ||
get_stat_cb(err, stat, { res, rej }) { | ||
if (err) { | ||
if (err.code === 'ENOENT') return res(false); | ||
return rej(err); | ||
} | ||
const isDir = stat.isDirectory(), | ||
isFile = stat.isFile(), | ||
isBigInt = typeof stat.size === 'bigint'; // eslint-disable-line valid-typeof | ||
try { | ||
return res({ ...stat, | ||
isBigInt, | ||
isFile, file: isFile, | ||
isDir, folder: isDir, | ||
directory: isDir, | ||
isFolder: isDir, | ||
isDirectory: isDir, | ||
}); | ||
} catch (_err) { | ||
if (_err && _err instanceof SyntaxError) { | ||
return res(Object.assign(stat, { | ||
isBigInt, | ||
file: isFile, | ||
folder: isDir, | ||
directory: isDir, | ||
})); | ||
} | ||
return rej(_err); | ||
} | ||
} | ||
load_proxy() { | ||
const list = this.read_recurse_series(this.path.resolve(__dirname, '../deps/traps')); | ||
const list = this.read_recurse_series(this.path.resolve(__dirname, '../deps/traps'), | ||
e => this.get_stat_sync(e).directory); | ||
this.traps = []; | ||
@@ -151,0 +163,0 @@ for (const { condition, value } of list.map(require)) { |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
30484
319
324