{"id":18115298,"url":"https://github.com/wzhouwzhou/easypathutil","last_synced_at":"2025-04-14T10:40:27.815Z","repository":{"id":57218919,"uuid":"144628415","full_name":"wzhouwzhou/easypathutil","owner":"wzhouwzhou","description":"Fluent filepaths, made simple. Consise syntax for non-webpack/bundler-compliant projects. Develop branch is usually most up-to-date (for now, see feature/File_Writes)","archived":false,"fork":false,"pushed_at":"2021-01-03T05:43:03.000Z","size":123,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-07T00:45:52.319Z","etag":null,"topics":["builder","easypath","es6","filepath","fluent","fs","modern","path","pathbuilder","promise"],"latest_commit_sha":null,"homepage":"https://npmjs.com/easypathutil","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wzhouwzhou.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":null,"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":"https://paypal.me/wzhouwzhou"}},"created_at":"2018-08-13T20:08:44.000Z","updated_at":"2020-11-15T00:43:37.000Z","dependencies_parsed_at":"2022-08-28T23:22:29.047Z","dependency_job_id":null,"html_url":"https://github.com/wzhouwzhou/easypathutil","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wzhouwzhou%2Feasypathutil","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wzhouwzhou%2Feasypathutil/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wzhouwzhou%2Feasypathutil/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wzhouwzhou%2Feasypathutil/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wzhouwzhou","download_url":"https://codeload.github.com/wzhouwzhou/easypathutil/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248866213,"owners_count":21174497,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["builder","easypath","es6","filepath","fluent","fs","modern","path","pathbuilder","promise"],"created_at":"2024-11-01T03:09:28.382Z","updated_at":"2025-04-14T10:40:27.796Z","avatar_url":"https://github.com/wzhouwzhou.png","language":"JavaScript","funding_links":["https://paypal.me/wzhouwzhou"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n    \u003cbr /\u003e\n    \u003cp\u003e\n        \u003ca class=\"badge-align\" href=\"https://www.codacy.com/app/wzhouwzhou/easypathutil?utm_source=github.com\u0026amp;utm_medium=referral\u0026amp;utm_content=wzhouwzhou/easypathutil\u0026amp;utm_campaign=Badge_Grade\"\u003e\u003cimg src=\"https://api.codacy.com/project/badge/Grade/d54c94b0c32e45bc8046d2825eb474cb\"/\u003e\u003c/a\u003e\n        \u003ca href=\"https://www.npmjs.com/package/easypathutil\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/easypathutil.svg\" alt=\"NPM version\" /\u003e\u003c/a\u003e\n        \u003ca href=\"https://www.npmjs.com/package/easypathutil\"\u003e\u003cimg src=\"https://img.shields.io/npm/dt/easypathutil.svg\" alt=\"NPM downloads\" /\u003e\u003c/a\u003e\n        \u003ca href=\"https://david-dm.org/wzhouwzhou/easypathutil\"\u003e\u003cimg src=\"https://img.shields.io/david/wzhouwzhou/easypathutil.svg\" alt=\"Dependencies\" /\u003e\u003c/a\u003e\n        \u003ca href=\"https://paypal.me/wzhouwzhou\"\u003e\u003cimg src=\"https://img.shields.io/badge/donate-paypal-009cde.svg\" alt=\"Paypal\" /\u003e\u003c/a\u003e\n    \u003c/p\u003e\n    \u003cp\u003e\n        \u003ca href=\"https://nodei.co/npm/easypathutil/\"\u003e\u003cimg src=\"https://nodei.co/npm/easypathutil.png?stars=true\u0026downloads=true\"\u003e\u003c/a\u003e\n    \u003c/p\u003e\n\u003c/div\u003e\n\n# Easypathutil\n## Fluent filepaths, made simple.\n\nThe modern (es6) way to specify file paths and perform quick file system operations, with no third party dependencies.\n\n## One Step Installation:\n\n    npm install easypathutil@1.2.4\n\n### Two-Part Motivation\n• Avoid a nesting problem of excessive '../../../../../foo/bar' when you can use a fluent object in projects with a more invariant file structure.\n\n• Export the PathBuilder as an npm module to eliminate the need for the above point when attempting to use a PathBuilder.\n\n### Goals/Why use Easypathutil\n• This package hopes to make your paths easier to follow for deeply nested files.\n\n• Easily check for existence of or load a file or folder, read, get stats, or require.\n\n• Updated and Lightweight: Package size \u003c10kB\n\nThe tutorial below aims to demonstrate the core functionality of this package.\n\n### Show me in action\nThree files: /data/users.json, /classes/A.js and /a/b/c/d/e/nested.js\n\nnested.js:\n**Before:**\n\n    const file = require('fs').readFileSync(require('path').join(__dirname, '../../../../data/users.json'));\n    const json = JSON.parse(file);\n    const default_object = new (require(require('path').join(__dirname, '../../../../classes/A')).default);\n\n**After:**\n\n    const cwd = require('easypathutil')();\n    const json = cwd.data['users.json'].$json;\n    const default_object = cwd.classes.A.$new_default;\n\n#### **Quickstart Usage and Examples**\n\n    const Builder = require('easypathutil');\n    const cwd = new Builder;\n    cwd() === process.cwd() // true\n\nThe `new` keyword is optional, a builder can be retrieved simply with Builder() as well.\n\n#### **Fluent Interface Examples (chaining, constructor, .toString, [] vs ())**\n\n    // Using process.cwd() (root/home/projects/myfolder) as base\n    const myfolder = Builder();\n\n    // Or provide the full path:\n    const myfolder = new Builder('/root/home/projects/myfolder');\n\n    // Or (for more advanced users) provide custom JSON, path and fs object:\n    const myfolder = new Builder('/root/home/projects/myfolder', {\n      JSON: someJsonPackage || global.JSON,\n      fs: someFsPackage || require('fs'),\n      path: somePathPackage || require('path'),\n      Promise: somePromisePackage || global.Promise,\n      filter: filepath =\u003e should_dive_folder(filepath), // This function checks recursive directory dives with a given filter. More below.\n    });\n\n    const myfolderstring = myfolder(); // '/root/home/projects/myfolder'\n    const samefolderstring = myfolder.toString(); // toString property turns it back into the path string\n    const anotherfolderstring = myfolder.anotherfolder(); // '/root/home/projects/myfolder/anotherfolder'\n    const myjsfile = myfolder.foo['bar']('myjsfile.js'); // Access a file named \"myjsfile.js\" in .../myfolder/foo/bar/\n    const samejsfile = myfolder('foo').bar['myjsfile.js']; // Emphasising ability to interchange [] and () for strings instead of dot notation.\n\n    const pathstring = myjsfile(); // '/root/home/projects/myfolder/foo/bar/myjsfile.js'\n    myjsfile() === samejsfile(); // true\n\n#### **Going backwards and resetting to base path (.$back, .$reset)**\n\n    // Reminder: myjsfile() is '/root/home/projects/myfolder/foo/bar/myjsfile.js'\n    const barfolder = myjsfile.$back; // $back property goes \"back\" one level.\n    const barfolderpath = barfolder(); // '/root/home/projects/myfolder/foo/bar'\n    const baz = myjsfile.$back.baz; // $back can be chained\n    const bazpath = baz(); // '/root/home/projects/myfolder/foo/bar/baz'\n\n    const myfolder2 myjsfile.$reset // $reset property resets the builder back to the base path.\n    myfolder2() === myfolder(); // true, both are '/root/home/projects/myfolder'\n\n#### **Get file contents (.$readfile, .$readfilesync)**\n\n    const myjsfilebuffer = myjsfile.$readfilesync; // $readfilesync property calls fs.readFileSync\n    const myjsfilebufferpromise = myjsfile.$readfile // $readfile returns a promise for async retrieval\n\n    // The following will hence be true or resolve to true:\n    myjsfilebuffer !== await myjsfilebufferpromise\n    myjsfilebuffer.toString() === (await myjsfilebufferpromise).toString()\n    myjsfilebufferpromise.then(filedata =\u003e filedata !== myjsfilebuffer)\n    myjsfilebufferpromise.then(filedata =\u003e filedata.toString() === myjsfilebuffer.toString())\n\n    // optional \".\" or \"_\" and case insensitive\n    myjsfile.$read_file, myjsfile.$read_file_sync\n    myjsfile.$readfile, myjsfile.$readfile_sync\n    myjsfile['$readFile'], myjsfile['$readFileSync']\n    myjsfile('$read_File'), myjsfile('$readFile_Sync')\n\n#### **Easy require (.$require, $require_default)**\n\n    const imported = myjsfile.$require; // $require property wraps a require() around the target.\n    const defaultimport = myjsfile.$require_default; // Attempts an \"interop require default\" style default import\n    imported.default === defaultimport // true if myjsfile points to an esModule. Note: imported.default will Always work, regardless if it is an esModule (you can stick to .$require_default to be safe, unless you know what you are doing).\n\nAliases: $require_default, $requiredefault, $requireDefault, etc, optional \".\" or \"\\_\" and case insensitive\n\n#### **Load JSON without require (.$json)**\n\n    const jsonfile = myfolder('jsonfile.json'); // Points to /root/home/projects/myfolder/jsonfile.json\n    const parsedjson = jsonfile.$json // Aliases: .$json, .$toJson, .$JSON, .$to_json, etc, optional \".\" or \"_\" and case insensitive\n\n#### **Read directory recursively, returning an array of absolute paths to files (.$read_dir, .$read_dir_sync)**\n\n    const filearray = myfolder.$read_dir_sync\n    myfolder.$read_dir.then(filearray2 =\u003e {\n      // same array contents as filearray\n    });\n    // Aliases .$readdir, .$readDirsync, etc. as always, \".\" or \"_\" are optional and case insensitive\n\n**Advanced Feature: Recursive Dive Prevention**\n\nPlease 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\nare reading directories only for files. \n\nRecall back to how to construct the object with options:\n\n    const myfolder = new Builder('/root/home/projects/myfolder', {\n      /* …other options… */\n      filter: filepath =\u003e should_dive_folder(filepath), // This function checks recursive directory dives with a given filter. More below.\n    });\n\nThe 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\nfilter the files returned, the fastest way would be read every file with a basic `myfolder.$readdirsync` and then apply a\n`.filter(e =\u003e e.endsWith('.mycustomextension')` to the array returned. However, what if you only wanted to read every folder underneath,\nsay, /src? What if you had a situation where you had folders structured like: `/data/translations/en/data1.json`,\n`/data/translations/en/data2.json`, `/data/translations/es/data1.json`, etc, but you only wanted the folder locations, namely\n`/data/translations/en/`, `/data/translations/es/`, etc?\n\n**In comes the filter function!**\n\nThe filter function filters *out* paths to which it returns true during recursion, and you must apply Array#filter to *keep* what you want.\n\nIn our first example, you must create a new object like so:\n\n        const array = Builder('/some/path/here' || myfolder() || process.cwd(), {\n          filter: function filter(path) { return path.endsWith('.ext') \u0026\u0026 !this.get_stat_sync(path).directory; }\n        }).$readdirsync\n          .filter(e =\u003e e.endsWith('.ext'));\n\nThis will return everything, files and folders, whos name ends with .ext\n\n        Optionally, declare the function beforehand:\n        function filter(path) {\n          return !path.endsWith('.ext') \u0026\u0026 this.get_stat_sync(path).directory;\n        }\n\n        const array = Builder(absolutepath, { filter }).$readdirsync\n          .filter(path =\u003e path.endsWith('.ext'));\n\nDon't want the files? You can chain Array#filter in nodejs\n\n        const folders_only = array.filter(path =\u003e require('fs').statSync(path).isDirectory()); \n\nOr just filter once for better performance:\n\n        const array = Builder(absolutepath, { filter }).$readdirsync\n          .filter(path =\u003e path.endsWith('.ext') \u0026\u0026 require('fs').statSync(path).isDirectory());\n\nYou may specify also filter parameter as two seperate functions for synchronous and async versions of .$readdir and .$readdirsync\n\n        const sync = function filter_sync(path) {\n           return !path.endsWith('.ext') \u0026\u0026 this.get_stat_sync(path).directory;\n        };\n        \n        const async = async function filter_async(path) {\n          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.\n          const { directory } = await this.get_stat(path);\n          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. \n        };\n        \n        const folder = Builder(absolutepath, {\n          filter: { sync, async },\n        });\n\n        // Use the synchronous version:\n        const array = folder.$readdirsync.filter(path =\u003e path.endsWith('.ext') \u0026\u0026 require('fs').statSync(path).isDirectory());\n\n        // Use the parallel/asynchronous version:\n        const fs = require('fs');\n\n        // Don't forget, you do not need to use .endsWith!\n        // path.startsWith, regular expressions, and many more also work, as the path string can be freely manipulated\n        // This applies to the filter functions as well!  \n        const promises = await folder.$readdir.map(path =\u003e /\\.ext$/.test(path) \u0026\u0026 new Promise((res, rej) =\u003e {\n            fs.stat(path, (err, stats) =\u003e {\n                if (err) return rej(err);\n                if (stats.isDirectory()) return res(path);\n                return res(false);\n            });\n        });\n        const array = await Promise.all(promises).filter(_ =\u003e _);\n\nWhat about your second example with the translations?\n\n        function filter(e) { return !e.includes('translations') \u0026\u0026 this.get_stat_sync(e).directory; };  \n        const array = Builder(process.cwd(), { filter }).subfolder.data.$readdirsync; // Reminder: we can use the same Builder to get to the folder first!\n\nThe const array will now contain an array with everything one-level deep into /subfolder/data/translations. In our example, these would be\nthe two .../en and .../es folders \n\nNotice my usage of the keyword `function` when creating these filter functions. I have not used arrow functions (=\u003e lambdas) because of my\nuse of \"this\" (this.get_stat_sync). The filter function is bound to the library's ReadHelper objects, which contain several internal helper\nfunctions to help abstract the directory reading process away from the node fs module. You are free to use arrow functions when this binding\nfunctionality is not of use to you. \n\nHave a more specific use case that you don't believe this covers? Open an issue on this package's github repository (linked below)!\n\n#### **New object shortcut (.$new, .$new_default)**\n\nBefore:\n\n    const object = new require('../../path/to/myjsfile');\n    const defaultobject = new (require('../../path/to/myjsfile').default);\n\nAfter (with myjsfile as myfolder.foo.bar.myjsfile):\n\n    const object2 = myjsfile.$new; // .$new creates a new instance of the result of .$require\n    const defaultobject2 = myjsfile.$new_default; // .$new_default and aliases create new instances of .$require_default\n\nAliases: $newDefault, $newdefault, etc, optional \".\" or \"\\_\" and case insensitive\n\n#### **File stats (.$stat)**\n\n    // Get file stats synchronously instead of wrapping with fs.statSync with extra function calls\n    const myjsfilestat = myjsfile.$stat // $stat property returns an object containing file stats.\n\n**myjsfilestat ($stat) contains three custom properties: isBigInt, file, and folder.**\n\n• myjsfilestat.file is true when the item is a file. Aliases: $stat.isFile\n\n• myjsfilestat.folder is true when the item is a folder. Aliases: $stat.isFolder, $stat.dir, $stat.isDir, $stat.directory, $stat.isDirectory\n\n• isBigInt tells you if the data such as size in the object is using bigints instead of numbers. This helps clarify ambiguity regarding node versions and bigint support\n\n**You can force legacy/number for data (.$stat_legacy, .$stat_number)**\n\n    const myjsfilestatlegacy = myjsfile.$stat_legacy // .$statLegacy, .$statNumber, optional . or _ and case insensitive\n\n**Thus, $stat.size as well as any other property that relies on legacy or bigint/number conversion should always be the same:**\n\n    // All these statements should be true\n    Number(myjsfilestat.size) === myjsfilestatlegacy.size\n    Number(myjsfilestat.blocks) === myjsfilestatlegacy.blocks\n    myjsfilestat.isFile === myjsfilestatlegacy.file\n    myjsfilestat.dir === myjsfilestatlegacy.isDirectory\n\n#### **Existence of a file or folder (in operator, Reflect.has, etc)**\n\n    const boolean_exists = 'foldername' in myfolder;\n    const boolean_exists2 = Reflect.has(myfolder, 'filename.extension');\n    const boolean_exists3 = 'subfoldername' in Object.create(myfolder);\n\n#### **Version**\n\n    const version = require('easypathutil').version;\n    version === require('easypathutil').VERSION;\n\nThis package adapts as needs arise, and although it has been tested on some versions of node v8 and v10, problems may still occur.\n\n## Changelog\n### New in 1.2.4\n• Introduced an advanced feature for synchronous and async recursive directory read filtering (\"filter\" parameter in constructor).\n\nShould you notice anything wrong with this, please do open a reproducible issue or pull request on this package's github repository\n(linked below)!\n### New in 1.2.3\n• Fixed several bugs differentiating between sync and async versions of .$ properties. (i.e. file.$stat and file.$stat.sync)\n\n• Fixed async reading of folders\n\n### New in 1.2.2\n• Fixed a bug relating to Promise not being loaded into the ReadHelper, causing async operations to fail\n\n### New in 1.2.1\n• Fixed a bug relating to a new loader implemented in 1.2.0 causing crashes\n\n### New in 1.2.0\n• Completely refactored internals that power the fluent API\n\n• Provide your own Promise library\n\n### New in 1.1.0\n• Provide your own JSON, path, or fs objects\n\n• More reliable path support (slash vs backslash)\n\n#### Enjoy this package?\nConsider starring on [github](https://github.com/wzhouwzhou/easypathutil) and checking out some of my other work:\n\n[Youtube Search API](https://npmjs.com/ytsearcher)\n\n[Urban Dictionary](https://npmjs.com/easyurban)\n\nNeed support? Send me an email at wzhouwzhou@gmail.com, or connect with me on Discord at https://discord.gg/jj5FzF7 (William Zhou#0001)\n\nLike what you're seeing? Consider helping to fund my education through https://paypal.me/wzhouwzhou  \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwzhouwzhou%2Feasypathutil","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwzhouwzhou%2Feasypathutil","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwzhouwzhou%2Feasypathutil/lists"}