Repository: ramhejazi/draxt Branch: master Commit: be2b0edd848b Files: 22 Total size: 215.3 KB Directory structure: gitextract_u4ieoxxi/ ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── .travis.yml ├── LICENSE ├── README.md ├── _config.yml ├── docs/ │ └── index.html ├── package.json ├── src/ │ ├── draxt.js │ ├── interfaces/ │ │ ├── Directory.js │ │ ├── File.js │ │ ├── Node.js │ │ ├── SymbolicLink.js │ │ └── index.js │ └── util.js └── test/ ├── draxt.js └── interfaces/ ├── Directory.js ├── File.js ├── Node.js └── SymbolicLink.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = tab indent_size = 2 charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false ================================================ FILE: .eslintrc.js ================================================ module.exports = { env: { commonjs: true, es6: true, node: true, }, globals: { console: true, }, extends: 'eslint:recommended', rules: { indent: ['error', 4], 'no-console': 0, 'linebreak-style': ['error', 'unix'], quotes: [1, 'single'], }, }; ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # nyc test coverage .nyc_output # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # next.js build output .next .DS_Store ================================================ FILE: .prettierrc ================================================ { "trailingComma": "es5", "printWidth": 100, "tabWidth": 4, "semi": true, "singleQuote": true } ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - '6' - '8' before_install: - if [[ `npm -v` == 2.* ]]; then npm install --global npm@3; fi after_success: npm run coverage ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 Ram Hejazi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================
draxt.js logo
draxt license npm-link draxt coverage status

`draxt` is a utility module for selecting and manipulating filesystem objects in a Node.js environment. It uses [glob] patterns as its "selector engine". `draxt` also provides several DOM-like interfaces representing filesystem objects which build on promisified APIs for the [`fs`] and [`fs-extra`] modules. **Example directory structure:** ``` /app/ ├── controllers/ │ └── index.js ├── public/ │ ├── script.js │ └── style.css └── views/ └── index.html ``` ```js const $ = require('draxt'); (async () => { // Select `/app` directory content and create a new `draxt` collection. const $app = await $('/app/**'); $app // Let's filter js files: .filter((node) => node.extension === 'js') // Now we have a new `draxt` collection with 2 nodes. .forEach(async (node, index, allNodes) => { // `node` is instance of `File` class. Because it's a file! console.log(node.pathName); // → '/app/controllers/index.js' for the first node! console.log(node instanceof $.File); // → `true` // Let's get contents of the node. `file.read` returns a promise object. const content = await node.read('utf8'); // Let's use some synchronous methods! node.appendSync('\na new line!') .chmodSync('765') // move the file into another directory! .appendToSync('/tmp'); // or `.moveToSync('/tmp')` console.log(node.pathName); // → '/hell/index.js' for the first node in the list! // get the parent directory of the node. // returns a `Directory` instance with the pathName of '/tmp'! const parentNode = node.parentSync(); // or `await node.parent()` // is the directory empty? console.log(parentNode.isEmptySync()); // → `false` }); })(); ``` **Key notes**: - `draxt` has only 2 dependencies: [`glob`] and [`fs-extra`] modules. - `draxt` uses `glob` patterns to select filesystem objects. - Each item in a `draxt` collection is an instance of a [`File`], [`Directory`], or [`SymbolicLink`] class, which is a subclass of [`Node`]. - Every asynchronous method has a synchronous version. E.g., [`node.siblingsSync()`] for [`node.siblings()`]. - `draxt` is a simple constructor function. You can extend/overwrite its methods via its `prototype` property (or its `fn` alias) or by using the [`draxt.extend`] method. ```js const draxt = require('draxt'); // Add a method (`images`) for filtering image files. draxt.fn.images = function() { const imgExtensions = ['jpeg', 'jpg', 'png', 'git', ...]; return this.filter(node => { return node.isFile() && imgExtensions.indexOf(node.extension) > -1; }); } ``` ## Install Installing via [npm]: ```bash $ npm i draxt ``` Via [yarn]: ```bash $ yarn add draxt ``` ## Docs - [`draxt` APIs][draxt-doc] - Interfaces - [`Node`] - [`File`] - [`Directory`] - [`SymbolicLink`] ## Test In the past, mock-fs was used for mocking test file system, but since the package is not compatible with newer versions of node.js, now regular linux cmds like `mkdir` and `echo` are used for creating test files and folders. The test fs structure are created in `/tmp` directory. That being said, for now, tests only work on Linux! ```bash $ npm run test ``` ## License [Licensed under MIT.][license] [repo]: https://github.com/ramhejazi/draxt [logo]: draxt-logo.jpg [license]: https://github.com/ramhejazi/draxt/blob/master/LICENSE [license-badge]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square [coverall]: https://coveralls.io/github/ramhejazi/draxt [coverall-badge]: https://img.shields.io/coveralls/github/ramhejazi/draxt.svg?style=flat-square [npm-link]: https://www.npmjs.com/package/draxt [npm-badge]: https://img.shields.io/npm/v/draxt.svg?style=flat-square [travis-link]: https://travis-ci.org/ramhejazi/draxt [travis-badge]: https://img.shields.io/travis/ramhejazi/draxt.svg?style=flat-square [deps-status-link]: https://david-dm.org/ramhejazi/draxt [deps-status-badge]: https://david-dm.org/ramhejazi/draxt.svg?style=flat-square [npm]: https://docs.npmjs.com/getting-started/what-is-npm [yarn]: https://yarnpkg.com/en/ [glob]: https://en.wikipedia.org/wiki/Glob_(programming) [`fs`]: https://nodejs.org/api/fs.html [`fs-extra`]: https://github.com/jprichardson/node-fs-extra [`glob`]: https://github.com/isaacs/node-glob [Pahlavi language]: https://en.wikipedia.org/wiki/Middle_Persian [draxt-doc]: https://ramhejazi.github.io/draxt#draxt [`Node`]: https://ramhejazi.github.io/draxt#interfaces-node [`File`]: https://ramhejazi.github.io/draxt#interfaces-file [`Directory`]: https://ramhejazi.github.io/draxt#interfaces-directory [`SymbolicLink`]: https://ramhejazi.github.io/draxt#interfaces-symboliclink [`draxt.extend`]: https://ramhejazi.github.io/draxt#draxt-extend [`node.siblingsSync()`]: https://ramhejazi.github.io/draxt#node-siblings [`node.siblings()`]: https://ramhejazi.github.io/draxt#node-siblings ================================================ FILE: _config.yml ================================================ theme: jekyll-theme-minimal ================================================ FILE: docs/index.html ================================================ draxt.js – NodeList-like/jQuery-like package for File System (node.js)

draxt license  draxt build state  draxt coverage status  dependencies status

Draxt is an open-source utility module for selecting and manipulating filesystem objects in a Node.js environment. It uses glob patterns as its "selector engine" to select filesytem objects (files, directories, ...).

Draxt also provides several DOM-like interfaces representing filesystem objects which build on promisified APIs for the fs and fs-extra.

Why?

Writing readble and maintable code by using node fs module is not an easy task. One usually has to write helper structures for doing basic and common tasks. Draxt is an attempt for faciliating this process. The library, in addition to wrappers for promisified fs and fs-extra module APIs, provides several DOM/jQuery-like APIs for selecting and manipulating files and directories and traversing filesystem.

Key Points

The project is hosted on GitHub. You can report bugs and discuss features on the issues page.

Example

// Let's use a familiar variable name!
const $ = require('draxt');

(async () => {
// Select `/app` directory contents and create a new `draxt` collection.
const $app = await $('/app/**');
$app
  // Let's filter js files:
  .filter(node => node.extension === 'js')
  // Now we have a new `draxt` collection with 2 nodes.
  .forEach(async (node, index, allNodes) => {
    // `node` is instance of `File` class. Because it's a file!
    console.log(node.pathName);
    // → '/app/controllers/index.js' for the first node!

    console.log(node instanceof $.File); // → `true`

    // Let's get contents of the node. `file.read` returns a promise object.
    const content = await node.read('utf8');

    // Let's use some synchronous methods!
    node.appendSync('\na new line!')
        .chmodSync('765')
        // move the file into another directory!
        .appendToSync('/hell') // or `.moveToSync('/hell')`

    console.log(node.pathName);
    // → '/hell/index.js' for the first node in the list!

    // get the parent directory of the node.
    // returns a `Directory` instance with the pathName of '/hell'!
    const parentNode = node.parentSync(); // or `await node.parent()`

    // is the directory empty?
    console.log(parentNode.isEmptySync()); // → `false`
});
})();

Installation

Queries (Globbing)

draxt module exports a constructor function. Whenever a string is passed as the first parameter to the function, parameters are passed to the glob package. The matching pathNames returned by the glob package, if any, are converted into an array of node instances. The returned value of draxt is a draxt instance (a draxt collection) which wraps an array of nodes. In this respect, draxt constructor function works like jQuery function, but unlike jQuery collections, draxt collections are not array-like objects. draxt collections store the array of nodes as a property named items. The length property of draxt collections is a getter for the collection's items.length property.

// Let's use a familiar variable name!
const $ = require('draxt')
// glob options
const globOptions = { dot: true }

// using await syntax in an async function context
(async () => {
  const draxtCollection = await $('/example_pattern/**', globOptions)
})()

// using promises
$('/example_pattern/**').then(draxtCollection => {
  // ...
})

// synchronous query
const draxtCollection = $.sync('/example_pattern/**', globOptions)
			

Draxt

draxt function is the main method of draxt package. The function can be called with new as a constructor function (not recommended) and without new as a factory function. draxt uses promisified glob package as it's selector engine. All query results of glob package are converted into one of Node's sub-class (File, Directory or SymbolicLink) instances by analyzing pathNames' fs.Stats object. The returned value of draxt is a draxt collection which to some extent works like jQuery collections but unlike jQuery collections it's not an array-like object. The collection items are stored as an array property (.items).

Syntax:

const draxtCollection = draxt([pattern, options])
  • pattern (string|array.<node>|node|draxt)  –  pattern parameter can contain several values:
    • string which is passed to glob package as glob pattern. In this case draxt returns a promise object representing a draxt collection/instance.
    • A Node or one it's sub-classes (File, Directory or SymbolicLink) instance. In this case a draxt collection containing the passed node is returned.
    • An array of node instances.
    • A draxt collection to clone (shallow).
    • undefined which returns an empty draxt collection.
  • options (object|string)  –  Options for glob package. The options parameter can also be a string representing a pathName which will be used as context for the query, similar to jQuery's $(selector, context) syntax.
promise.<draxt> | draxt

Static Methods:

syncdraxt.sync(pattern [, options])
  • pattern (string)  –  Glob pattern.
  • options (object)  –  Options for glob package.

Synchronously query the file system by using glob package and return a new draxt collection.

draxt An instance of draxt, a.k.a. a draxt collection.
source
extenddraxt.extend(methods)
  • methods (object)

Extend draxt by adding methods to it's prototype. Basically works like jQuery.fn.extend.

source

Methods:

adddraxtCollection.add(items)
  • items (node|array.<node>|draxt)  –  Instance of Node or array of nodes or a draxt collection.

Add node(s) to current draxt collection. Pre-exising nodes will not be added to the collection.

const draxtCollection = draxt();
draxtCollection.add(new Node('/pathName'));
draxtCollection.length // → 1
draxt An instance of draxt.
source
getdraxtCollection.get([index])
  • index (number)  –  Index of node in items collection.

Get one or all nodes from the draxt collection. With an index specified, .get(index) retrieves a single node otherwise retrives all the nodes (if any).

array.<node> | node | undefined
source
firstdraxtCollection.first()

Get the first node (if any) from the collection.

node | undefined
source
lastdraxtCollection.last()

Get the last node (if any) from the collection.

node | undefined
source
hasdraxtCollection.has(item)
  • item (string|node)  –  A Node instance or a pathName

Does the draxt collection has a node with specified pathName? Note that .has() method doesn't work by checking if collection has a specific Node instance. It checks whether collection has a node with the specified pathName.

// example fs structure
// └── app
//   ├── public
//   │   ├── script.js
//   │   └── style.css
const collection = draxtCollection.sync('/app/**');
draxtCollection.has('/app/public/script.js') // → true
draxtCollection.has(new Node('/app/public/script.js')) // → true
boolean
source
slicedraxtCollection.slice([begin, end])
  • begin (integer)  –  Zero-based index at which to begin extraction.
  • end (integer)  –  Zero-based index before which to end extraction. slice extracts up to but not including end.

Slice the collection and return a new Draxt collection. Uses Array.prototype.slice.

draxt A new draxt collection which contains sliced items.
source
filterdraxtCollection.filter(callback [, thisArg])
  • callback (function)  –  A function to execute for each node.
  • thisArg (any)  –  Value to use as this (i.e the reference Object) when executing callback.

Filter the collection's nodes and return a new draxt collection. Uses Array.prototype.filter.

draxt A new draxt collection which contains filtered items.
source
forEachdraxtCollection.forEach(callback [, thisArg])
  • callback (function)  –  A function to execute for each node.
  • thisArg (any)  –  Value to use as this (i.e the reference Object) when executing callback.

Iterate over the draxt collection and execute a function for each node. Uses Array.prototype.forEach.

draxt The current collection.
source
eachdraxtCollection.each()

Alias for draxt.forEach.

source
mapdraxtCollection.map(callback [, thisArg])
  • callback (function)  –  A function to execute for each node.
  • thisArg (any)  –  Value to use as this (i.e the reference Object) when executing callback.

Create an array with the results of calling a provided function on every node in the draxt collection. Uses Array.prototype.map.

array
source
mapAsyncdraxtCollection.mapAsync(fn [, thisArg])
  • fn (function)  –  A function to execute for each node.
  • thisArg (any)  –  Value to use as this (i.e the reference Object) when executing callback.

Asynchronous version of draxt.map. The results of mapped array is passed to Promise.all method.

promise
source
somedraxtCollection.some(fn)
  • fn (function)  –  A function to execute for each node.

Test whether at least one node in the collection passes the test implemented by the provided function. Uses Array.prototype.some.

boolean
source
sortdraxtCollection.sort(callback)
  • callback (function)  –  A function that defines the sort order.

Sort the nodes of collection in place and return the draxt collection. Uses Array.prototype.sort.

draxt Note that the collection is sorted in place, and no copy is made.
source
reversedraxtCollection.reverse()

Reverse the collection's nodes in place. The first array element becomes the last, and the last array element becomes the first.

draxt
source
directoriesdraxtCollection.directories()

Filter directory nodes (instances of Directory class) and return a new draxt collection.

draxt
source
filesdraxtCollection.files()

Filter file nodes (instances of File class) and return a new draxt collection.

draxt
source
emptydraxtCollection.empty()

Empty the draxt collection. This method doesn't affect file system!

draxt
source
dropdraxtCollection.drop(node)
  • node (draxt|node|array.<(node|string)>)  –  Accepts various paramters.

Remove node(s) from the current draxt collection by using .pathNames as the criterion.

draxt
source

Interfaces

Draxt introduces several DOM-like interfaces representing fileysystem objects. There are several interfaces. File for files, Directory for directories and SymbolicLink for symbolic links. These interfaces extends the base Node interface.

Just like DOM interfaces, draxt interfaces has their own set of unique methods. As an example, a directory instance has .find() method but a file instance doesn't.

Node

Node class is an interface that other classes representing file system's nodes (like File, Directory, SymbolicLink, ...) inherit.

Syntax:

const node = new Node(pathName [, stats]);

Properties

Static Methods:

rawQueryNode.rawQuery(pattern [, options])
  • pattern (string)  –  Pattern for glob package.
  • options (object)  –  Options for glob package.

Asynchronously query the file system by using glob package.

promise.<array> An array of pathNames.
source
rawQuerySyncNode.rawQuerySync(pattern [, options])
  • pattern (string)  –  Pattern for glob package.
  • options (object)  –  Options for glob package.

Synchronously query the file system by using glob package.

array An array of pathNames.
source
toNodesNode.toNodes(pathNames)
  • pathNames (array)  –  Array of pathNames.

Convert array of paths to array of node instances asynchronously! A node instance can be an instance of File, Directory or SymbolicLink.

const pathNames = [
  '/app/resources',
  '/app/resources/style.css'
];
Node.toNodes(pathNames).then(result => {
   // result:
   [
     Directory { pathName: '/app/resources', ... },
     File { pathName: '/app/resources/style.css', ... }
   ]
});
promise.<array> Array of node instances.
source
toNodesSyncNode.toNodesSync(pathNames)
  • pathNames (array)  –  Array of paths

Convert array of paths to array of nodes synchronously! A node instance can be instance of File, Directory or SymbolicLink.

array Array of node instances.
source
queryNode.query(pattern [, options])
  • pattern (string)  –  A glob pattern.
  • options (object)  –  Options for glob package.

Asynchronously query the file system by using glob package.

promise.<array> Array of nodes.
source
querySyncNode.querySync(pattern [, options])
  • pattern (string)
  • options (object)  –  Options for glob package.

Synchronously query the file system by using glob package.

array Array of nodes.
source

Methods

getPathNamenode.getPathName()

Get the node's pathName.

string
source
getBaseNamenode.getBaseName()

Get the node's baseName.

string
source
getExtensionnode.getExtension()

Get the node's extension.

string
source
getNamenode.getName()

Get name of the node. For File nodes the name property is the name of file without possible extension.

string
source
getParentPathnode.getParentPath()

Get the node's parent directory pathName.

string
source
getCachedStatsnode.getCachedStats()

Get cached fs.Stats instance for the node. Returns undefined when there is no cached stats for the node. This happens only when the node is created manually by user without passing a stats object.

object | undefined
source
getStatPropnode.getStatProp(propName)
  • propName (string)

Get a stat property's value from cached fs.Stats for the node. The method returns undefined when there is no cached stats.

// Get `blksize` property of fs.Stats instance cached for the node.
const node_ctime = node.getStatProp('blksize');
any
source
getAccessTimenode.getAccessTime()

Get "access time" of the node. Returns atime property of the cached stats.

date
source
getModifiedTimenode.getModifiedTime()

Get "modified time" of the node. Returns mtime property of the cached stats.

date
source
getBirthTimenode.getBirthTime()

Get "birthday time" of the node. Returns birthtime property of the cached stats.

date
source
getChangeTimenode.getChangeTime()

Get "change time" of the node. Returns ctime property of the cached stats.

date
source
getSizenode.getSize()

Get size of the node. Size is simply the size property of the cached fs.Stats instance.

number
source
isDirectorynode.isDirectory()

Is the node a directory?

boolean
source
isFilenode.isFile()

Is the node a file?

boolean
source
isDotFilenode.isDotFile()

Is the node a dot file? i.e. does the node's name begin with dot character.

boolean
source
renewStatsnode.renewStats()

Asynchronously renew stats of the node. Uses fs.lstat.

promise A fresh fs.Stats instance for the node.
source
renewStatsSyncnode.renewStatsSync()

Synchronously renew stats of the node. Uses fs.lstatSync.

node
source
getOctalPermissionsnode.getOctalPermissions()

Get octal representation of the node's permissions.

node.getOctalPermissions() // → "755"
string
source
getPermissionsnode.getPermissions()

Get permissions of the node for owner, group and others by converting mode property of cached stats into an object.

node.getPermissions()
// →
{
  read: { owner: true, group: true, others: false },
  write: { owner: true, group: true, others: false },
  execute: { owner: true, group: true, others: false }
}
object
source
accessnode.access([mode])
  • mode (integer)  Default: fs.constants.F_OK

Asynchronously tests a user's permissions for the file or directory. Wrapper for promisified fs.access.

// Check if the node is readable.
node.access(node.fs.constants.R_OK).then(() => {
  // node is readable
}).catch(e => {
  // node is not readable
});
promise
source
accessSyncnode.accessSync([mode])
  • mode (integer)  Default: fs.constants.F_OK

Wrapper for fs.accessSync.

node this
source
chmodnode.chmod(mode)
  • mode (integer)

Wrapper for promisified fs.chmod.

promise
source
chmodSyncnode.chmodSync(mode)
  • mode (integer)

Wrapper for fs.chmodSync.

node this
source
lchmodnode.lchmod(mode)
  • mode (integer)

Wrapper for promisified fs.lchmod.

promise
source
lchmodSyncnode.lchmodSync(mode)
  • mode (integer)

Wrapper for fs.lchmodSync.

node
source
chownnode.chown(uid, gid)
  • uid (integer)  –  The user id
  • gid (integer)  –  The group id

Wrapper for promisified fs.chown.

promise
source
chownSyncnode.chownSync(uid, gid)
  • uid (integer)  –  The user id
  • gid (integer)  –  The group id

Wrapper for fs.chownSync.

node The file node
source
lchownnode.lchown(uid, gid)
  • uid (integer)  –  The user id
  • gid (integer)  –  The group id

Wrapper for promisified fs.lchown.

promise
source
lchownSyncnode.lchownSync(uid, gid)
  • uid (integer)  –  The user id
  • gid (integer)  –  The group id

Wrapper for fs.lchownSync.

node The file node
source
existsnode.exists()

Does node exist on file system? Uses fs.access instead of the deprecated fs.exists method.

promise.<boolean>
source
existsSyncnode.existsSync()

Does node exist on file system? Wrapper for fs.existsSync.

boolean
source
statnode.stat()

Wrapper for promisified fs.stat.

promise Promise representing instance of fs.Stats for the node.
source
statSyncnode.statSync()

Wrapper for fs.statSync.

object Instance of fs.Stats for the node.
source
lstatnode.lstat()

Wrapper for promisified fs.lstat.

promise Promise representing instance of fs.Stats for the node.
source
lstatSyncnode.lstatSync()

Wrapper for fs.lstatSync.

object Instance of fs.Stats for the node.
source
linkSyncnode.linkSync(newPath)
  • newPath (string|buffer|URL)

Wrapper for fs.linkSync.

node
source
renamenode.rename(newPath)
  • newPath (string|Buffer|URL)

Asynchronously rename node to the pathname provided as newPath. In the case that newPath already exists, it will be overwritten. Wrapper for promisified fs.rename.

promise
source
renameSyncnode.renameSync(newPath)
  • newPath (string|Buffer|URL)

Wrapper for fs.renameSync.

node
source
utimesnode.utimes(atime, mtime)
  • atime (number|string|Date)
  • mtime (number|string|Date)

Wrapper for promisified fs.utimes.

promise
source
utimesSyncnode.utimesSync(atime, mtime)
  • atime (number|string|Date)
  • mtime (number|string|Date)

Wrapper for fs.utimesSync.

node
source
copynode.copy(destPath, options)
  • destPath (string)  –  Destination path.
  • options (object)  –  Options for fs-extra.copy.

Asynchronously copy the node. Directory instances can have contents. Like cp -r. When directory doesn't exist, it's created! Wrapper for fs-extra.copy.

// creating a `File` instance. `File` class extends the `Node` class!
const file = new File('/app/resources/style.css');
file.copy('/app/backup/backup_style.css').then(() => {
  // file has been copied successfully!
}).catch(e => {
  // There was an error!
});
promise
source
copySyncnode.copySync(destPath, options)
  • destPath (string)  –  Destination path.
  • options (object)  –  Options for fs-extra.copySync.

Wrapper for fs-extra.copySync.

node
source
moveTonode.moveTo(targetDir, options)
  • targetDir (object|string)  –  Directory instance or absolute path of the target directory.
  • options (object)  –  Options for fs-extra.move.

Move node to another location. baseName property of the node is joined with targetDir param for resolving the final path for the node. The method on success updates path-related properties of the node, but node's cached stats (if any) is not refreshed! For updating node's stats, user can call node.renewStats() or node.renewStatsSync() methods after moving the node. Uses fs-extra.move.

const node = new File('/app/resources/style.css');
const dir = new Directory('/app/target_dir');
node.moveTo(dir || '/app/target_dir').then(() => {
  // node has been moved into '/app/target_dir' directory!
  node.getPathName(); // → '/app/target_dir/style.css'
});
promise
source
moveToSyncnode.moveToSync(targetDir, options)
  • targetDir (object|string)  –  Directory instance or absolute path of the target directory.
  • options (object)  –  Options for fs-extra.move.

Synchronous version of node.moveTo.

node
source
appendTonode.appendTo()

Alias for node.moveTo.

source
appendToSyncnode.appendToSync()

Alias for node.moveToSync.

node
source
siblingsnode.siblings([patten, options])
  • patten (string)  Default: '*'  –  Optional glob pattern.
  • options (object)  –  Options for glob package.

Asynchronously select siblings of the node. Uses glob package.

promise.<draxt> Promise representing a draxt collection.
source
siblingsSyncnode.siblingsSync([pattern, options])
  • pattern (string)  Default: '*'  –  Optional glob pattern.
  • options (object)  –  Options for glob package.

Synchronously select siblings of the node. Uses glob package.

draxt A draxt collection.
source
removenode.remove()

Remove the node from file system! Directory nodes can have contents. Like rm -rf. Wrapper for fs-extra.remove.

promise
source
removeSyncnode.removeSync()

Wrapper for fs-extra.removeSync.

node
source
parentnode.parent()

Asynchronously get parent directory node of the node. It's an async method as it gets an instance of fs.Stats for the parent node asynchronously!

const file = new File('/app/resources/style.css');
file.parent().then(dir => {
  dir.isDirectory(); // → true
  dir.getPathName(); // → '/app/resources'
});
promise.<node> Promise representing a Directory instance.
source
parentSyncnode.parentSync()

Synchronously get parent directory node of the node.

node A Directory instance.
source

File

File class which extends the Node class is an interface representing pathNames that their fs.Stats's .isFile() method returns true.

Syntax:

const file = new File(pathName [, stats]);

Properties

Methods

ensurefile.ensure()

Ensure the file node exists on file system. Wrapper for fs-extra.ensureFile.

promise
source
ensureSyncfile.ensureSync()

Ensure the file node exists on file system synchronously. Wrapper for fs.ensureFileSync.

node
source
appendfile.append()

Asynchronously append data to a file, creating the file if it does not yet exist. data can be a string or a Buffer. Wrapper for fs.appendFile.

promise
source
appendSyncfile.appendSync()

Wrapper for fs.appendFileSync.

node
source
readfile.read()

Promisified wrapper for fs.readFile.

promise Promise object representing contents of the file.
source
readSyncfile.readSync()

Wrapper for fs.readFileSync.

any
source
truncatefile.truncate()

Promisified wrapper for fs.truncate

promise
source
truncateSyncfile.truncateSync()

Wrapper for fs.truncateSync.

node
source
writefile.write()

Promisified fs.writeFile

promise
source
writeSyncfile.writeSync()

Wrapper for fs.writeFileSync.

node
source

Inherited Static Methods

rawQuery rawQuerySync toNodes toNodesSync query querySync

Inherited Methods

getPathName getBaseName getExtension getName getParentPath getCachedStats getStatProp getAccessTime getModifiedTime getBirthTime getChangeTime getSize isDirectory isFile isSymbolicLink isDotFile renewStats renewStatsSync getOctalPermissions getPermissions access accessSync chmod chmodSync lchmod lchmodSync chown chownSync lchown lchownSync exists existsSync stat statSync lstat lstatSync link linkSync rename renameSync utimes utimesSync copy copySync moveTo moveToSync appendTo appendToSync siblings siblingsSync remove removeSync parent parentSync

Directory

Directory class which extends the Node class is an interface representing pathNames that their fs.Stats's .isDirectory() method returns true.

Syntax:

const directory = new Directory(pathName [, stats]);

Properties

Methods

appenddirectory.append(nodes [, options])
  • nodes (draxt|node|string|array.<(node|string)>)  –  Accepts various parameters:
    • draxt collection.
    • a node instance.
    • pathNames of a file or directory.
    • array of node instances.
    • array of absolute pathNames of files/directories.
    • a mixed array of nodePaths and absolute pathNames of files/directories.
  • options (object)  –  Options for fs-extra.move.

Append/move passed directories into this directory node. Uses node.moveTo which uses fs-extra.move.

promise.<nodes> Promise representing array of moved nodes.
source
appendSyncdirectory.appendSync(nodes [, options])
  • nodes (draxt|array.<(node|string)>|string)
  • options (object)  –  Options for fs-extra.move.

Synchronous version of directory.append.

node The directory node.
source
childrendirectory.children([pattern, options])
  • pattern (string)  Default: '*'  –  Glob pattern relative to the directory. The pattern is used against baseName of directory child nodes.
  • options (object)  –  Options for glob package.

Asynchronously select children of the directory by using glob package and return a draxt collection.

promise.<draxt>
source
childrenSyncdirectory.childrenSync([selector, options])
  • selector (string)  –  Optional selector
  • options (object)  –  Options for glob package

Synchronous version of directory.children.

draxt
source
emptydirectory.empty()

Ensures that a directory is empty. Deletes directory contents if the directory is not empty. If the directory does not exist, it is created. The directory itself is not deleted. Wrapper for fs-extra.emptyDir.

promise
source
emptySyncdirectory.emptySync()

Synchronous version of directory.empty method. Wrapper for fs-extra.emptyDirSync.

node
source
ensuredirectory.ensure()

Asynchronously ensure directory exists. Wrapper for fs-extra.ensureDir.

promise
source
ensureSyncdirectory.ensureSync()

Synchronously ensure directory exists. Wrapper for fs-extra.ensureDirSync.

node
source
isEmptydirectory.isEmpty()

Is directory empty?

promise.<boolean>
source
isEmptySyncdirectory.isEmptySync()

Synchronous version of directory.isEmpty method.

boolean
source
finddirectory.find(pattern, options)
  • pattern (string)  –  Glob pattern.
  • options (object)  –  Options for glob package.

Find matching decendants of the directory node. Uses glob package.

Promise.<draxt>
source
findSyncdirectory.findSync(selector, options)
  • selector (string)
  • options (object)  –  Options for glob package.

Synchronous version of directory.find method.

draxt
source
readdirdirectory.readdir(options)
  • options (string|object)

Wrapper for promisified fs.readdir.

promise
source
readdirSyncdirectory.readdirSync(options)
  • options (string|object)

Wrapper for fs.readdirSync.

array
source
readdirectory.read(options)
  • options (string|object)

Alias for directory.readdir method.

promise
source
readSyncdirectory.readSync(options)
  • options (string|object)

Alias for directory.readdirSync method.

array
source
rmdirdirectory.rmdir()

Wrapper for promisified fs.rmdir. Deletes the directory, which must be empty.

promise
source
rmdirSyncdirectory.rmdirSync()

Wrapper for fs.rmdirSync. Deletes the directory, which must be empty.

node
source

Inherited Static Methods

rawQuery rawQuerySync toNodes toNodesSync query querySync

Inherited Methods

getPathName getBaseName getExtension getName getParentPath getCachedStats getStatProp getAccessTime getModifiedTime getBirthTime getChangeTime getSize isDirectory isFile isSymbolicLink isDotFile renewStats renewStatsSync getOctalPermissions getPermissions access accessSync chmod chmodSync lchmod lchmodSync chown chownSync lchown lchownSync exists existsSync stat statSync lstat lstatSync link linkSync rename renameSync utimes utimesSync copy copySync moveTo moveToSync appendTo appendToSync siblings siblingsSync remove removeSync parent parentSync

SymbolicLink class which extends the Node class is an interface representing pathNames that their fs.Stats's .isSymbolicLink() method returns true.

const symbolicLink = new SymbolicLink(pathName [, stats]);

Inherited Static Methods

rawQuery rawQuerySync toNodes toNodesSync query querySync

Inherited Methods

getPathName getBaseName getExtension getName getParentPath getCachedStats getStatProp getAccessTime getModifiedTime getBirthTime getChangeTime getSize isDirectory isFile isSymbolicLink isDotFile renewStats renewStatsSync getOctalPermissions getPermissions access accessSync chmod chmodSync lchmod lchmodSync chown chownSync lchown lchownSync exists existsSync stat statSync lstat lstatSync link linkSync rename renameSync utimes utimesSync copy copySync moveTo moveToSync appendTo appendToSync siblings siblingsSync remove removeSync parent parentSync
================================================ FILE: package.json ================================================ { "name": "draxt", "version": "1.3.0", "description": "jQuery/NodeList-like module for file system (nodejs)", "author": "Ram Hejazi", "main": "src/draxt.js", "scripts": { "test": "nyc --reporter=text --reporter=html ./node_modules/mocha/bin/mocha test/*" }, "homepage": "https://github.com/ramhejazi/draxt", "license": "MIT", "repository": { "type": "git", "url": "git+https://github.com/ramhejazi/draxt.git" }, "keywords": [ "fs", "file", "file system", "directory", "folder", "read", "write", "move", "mv", "cp", "copy", "jquery", "node", "chainable", "collection", "utility", "plugin" ], "bugs": { "url": "https://github.com/ramhejazi/draxt/issues" }, "engines": { "node": "20 || >=22" }, "dependencies": { "fs-extra": "^11.2.0", "glob": "^11.0.0" }, "devDependencies": { "chai": "^4.3.10", "mocha": "^9.2.2", "nyc": "^15.1.0" } } ================================================ FILE: src/draxt.js ================================================ /*eslint no-irregular-whitespace: ["error", { "skipComments": true }]*/ const {Node} = require('./interfaces'); const assign = Object.assign; const {getType} = require('./util'); const define = Object.defineProperty; const lengthProps = { get() { return this.items.length; }, enumerable: true, configurable: true, }; /** * `draxt` function is the main method of `draxt` package. The function * can be called with `new` as a constructor function (not recommended) and * without `new` as a factory function. `draxt` uses promisified `glob` package as it's * selector engine. All query results of `glob` package are converted into one of `Node`'s sub-class * (`File`, `Directory` or `SymbolicLink`) instances by analyzing pathNames' `fs.Stats` object. * The returned value of `draxt` is a `draxt` collection * which to some extent works like jQuery collections but unlike jQuery collections it's not an array-like object. * The collection items are stored as an array property (`.items`). * @prop {array} items Items of the collection. * @prop {number} length Lenght of collection's items. * @param {string|array|node|draxt} [pattern] `pattern` parameter can contain several values: * - `string` which is passed to `glob` package as `glob` pattern. * In this case `draxt` returns a `promise` object representing a `draxt` collection/instance. * - A `Node` or one it's sub-classes (`File`, `Directory` or `SymbolicLink`) instance. * In this case a `draxt` collection containing the passed `node` is returned. * - An array of `node` instances. * - A `draxt` collection to clone (shallow). * - `undefined` which returns an empty `draxt` collection. * @param {object|string} [options] Options for `glob` package. The `options` parameter * can also be a string representing a pathName which will be used as context for the query, * similar to jQuery's `$(selector, context)` syntax. * @returns {promise|draxt} * @example * // /app * // ├── controllers/ * // │   └── index.js * // ├── public/ * // │   ├── script.js * // │   └── style.css * // └── views/ * // └── index.njk/ * * const draxt = require('draxt'); * const Directory = draxt.Directory; * // Initialization with a glob pattern/selector. * // Which returns a `promise` object! * draxt('/app/**').then(draxtCollection => { * // draxtCollection: → * Draxt { * length: [Getter], * items: [ * Directory { pathName: '/app', ... } * Directory { pathName: '/app/controllers', ... } * File { pathName: '/app/controllers/index.js', ... } * ... * ] * }); * const anEmpyDraxtCollection = draxt(); * // A `draxt` collection with length of `1` * // Which has a manually created `Directory` instance. * const draxtCollection = draxt(new Directory('/app')); */ function Draxt(pattern, options = {}) { // If `this` is not a Draxt instance, create a Draxt instance. if (this instanceof Draxt !== true) { return new Draxt(...arguments); } // Define dynamic `length` property. define(this, 'length', lengthProps); // `items` refers to collection's node. this.items = []; if (getType(pattern) === 'undefined') { return this; } if (pattern instanceof Node || Array.isArray(pattern)) { this.add(pattern); return this; } if (pattern instanceof Draxt) { this.items = pattern.get().slice(); return this; } return Node.query(pattern, options).then((items) => { return this.add(items); }); } Node.Draxt = Draxt; Draxt.Node = Node; Draxt.File = Node.File; Draxt.Directory = Node.Directory; Draxt.SymbolicLink = Node.SymbolicLink; Draxt.fn = Draxt.prototype; /** * Synchronously query the file system by using `glob` package and * return a new `draxt` collection. * @param {string} pattern Glob pattern. * @param {object} [options] Options for `glob` package. * @returns {draxt} An instance of `draxt`, a.k.a. a _draxt collection_. */ Draxt.sync = function () { const items = Node.querySync(...arguments); return new Draxt(items); }; /** * Extend `draxt` by adding methods to it's `prototype`. Basically works like `jQuery.fn.extend`. * @param {object} methods */ Draxt.extend = function (methods) { assign(Draxt.fn, methods); }; /** * Add node(s) to current `draxt` collection. * Pre-exising nodes will not be added to the collection. * @param {node|array|draxt} items Instance of Node or array of nodes or a `draxt` collection. * @returns {draxt} An instance of `draxt`. * @example * const draxtCollection = draxt(); * draxtCollection.add(new Node('/pathName')); * draxtCollection.length // → 1 */ Draxt.prototype.add = function (items) { if (items instanceof Draxt) { items = items.get(); } const nodes = Array.isArray(items) ? items.slice() : [items]; nodes.forEach((node) => { if (!(node instanceof Node)) { throw new Error( 'Invalid value for `items` parameter. `draxt` collection can only have Node instances. ' + 'The given value is a(n) ' + getType(node) + '!' ); } const has = this.has(node); if (!has) this.items.push(node); }); return this; }; /** * Get one or all nodes from the `draxt` collection. * With an `index` specified, `.get(index)` retrieves a single node otherwise * retrives all the nodes (if any). * @param {number} [index] - Index of node in items collection. * @returns {array|node|undefined} */ Draxt.prototype.get = function (index) { if (typeof index === 'undefined') { return this.items; } return this.items[index]; }; /** * Get the first node (if any) from the collection. * @returns {node|undefined} */ Draxt.prototype.first = function () { return this.items[0]; }; /** * Get the last node (if any) from the collection. * @returns {node|undefined} */ Draxt.prototype.last = function () { return this.items[this.items.length - 1]; }; /** * Does the `draxt` collection has a node with specified pathName? * Note that `.has()` method doesn't work by checking if collection has a specific * `Node` instance. It checks whether collection has a node with the specified * pathName. * @param {string|node} item A `Node` instance or a `pathName` * @returns {boolean} * @example * // example fs structure * // └── app * // ├── public * // │ ├── script.js * // │ └── style.css * const collection = draxtCollection.sync('/app/**'); * draxtCollection.has('/app/public/script.js') // → true * draxtCollection.has(new Node('/app/public/script.js')) // → true */ Draxt.prototype.has = function (item) { const pathName = item instanceof Node ? item.pathName : item; const found = this.items.some((node) => node.pathName === pathName); return found; }; /** * Slice the collection and return a new `Draxt` collection. * Uses `Array.prototype.slice`. * @param {integer} [begin] Zero-based index at which to begin extraction. * @param {integer} [end] Zero-based index before which to end extraction. `slice` extracts up to but not including `end`. * @returns {draxt} A new `draxt` collection which contains sliced items. */ Draxt.prototype.slice = function () { let sItems = this.items.slice(...arguments); return new Draxt(sItems); }; /** * Filter the collection's nodes and return a new `draxt` collection. * Uses `Array.prototype.filter`. * @param {function} callback A function to execute for each node. * @param {any} [thisArg] Value to use as `this` (i.e the reference Object) when executing callback. * @returns {draxt} A new `draxt` collection which contains filtered items. */ Draxt.prototype.filter = function () { let fItems = this.items.filter(...arguments); return new Draxt(fItems); }; /** * Iterate over the `draxt` collection and execute a function for each * node. Uses `Array.prototype.forEach`. * @chainable * @param {function} callback A function to execute for each node. * @param {any} [thisArg] Value to use as `this` (i.e the reference Object) when executing callback. * @returns {draxt} The current collection. */ Draxt.prototype.forEach = function () { this.items.forEach(...arguments); return this; }; /** * Alias for `draxt.forEach`. */ Draxt.prototype.each = Draxt.prototype.forEach; /** * Create an array with the results of calling a provided function on every * node in the `draxt` collection. * Uses `Array.prototype.map`. * @param {function} callback A function to execute for each node. * @param {any} [thisArg] Value to use as `this` (i.e the reference Object) when executing callback. * @returns {array} */ Draxt.prototype.map = function () { return this.items.map(...arguments); }; /** * Asynchronous version of `draxt.map`. The results of mapped array is passed * to `Promise.all` method. * @param {function} fn A function to execute for each node. * @param {any} [thisArg] Value to use as `this` (i.e the reference Object) when executing callback. * @returns {promise} */ Draxt.prototype.mapAsync = function () { return Promise.all(this.items.map(...arguments)); }; /** * Test whether at least one node in the collection passes the test implemented * by the provided function. * Uses `Array.prototype.some`. * @param {function} fn A function to execute for each node. * @returns {boolean} */ Draxt.prototype.some = function () { return this.items.some(...arguments); }; /** * Sort the nodes of collection _in place_ and return the `draxt` collection. * Uses `Array.prototype.sort`. * @param {function} callback A function that defines the sort order. * @returns {draxt} Note that the collection is sorted _in place_, and no copy is made. */ Draxt.prototype.sort = function (fn) { this.items.sort(fn); return this; }; /** * Reverse the collection's nodes _in place_. * The first array element becomes the last, and the last array element becomes the first. * @returns {draxt} */ Draxt.prototype.reverse = function () { this.items.reverse(); return this; }; /** * Filter directory nodes (instances of `Directory` class) and return a new * `draxt` collection. * @returns {draxt} */ Draxt.prototype.directories = function () { return this.filter((el) => { return el.isDirectory(); }); }; /** * Filter file nodes (instances of `File` class) and return a new `draxt` collection. * @returns {draxt} */ Draxt.prototype.files = function () { return this.filter((el) => { return el.isFile(); }); }; /** * Filter symbolic link nodes (instances of `SymbolicLink` class) and return a new `draxt` collection. * @returns {draxt} */ Draxt.prototype.symlinks = function () { return this.filter((el) => { return el.isSymbolicLink(); }); }; /** * Empty the `draxt` collection. This method doesn't affect file system! * @returns {draxt} */ Draxt.prototype.empty = function () { this.items = []; return this; }; /** * Remove node(s) from the current `draxt` collection by using `.pathName`s as the criterion. * @chainable * @param {draxt|node|array} node Accepts various paramters. * @return {draxt} */ Draxt.prototype.drop = function (node) { let nodes; if (node instanceof Node) { nodes = [node]; } else if (node instanceof Draxt) { nodes = node.get(); } else if (getType(node) === 'string') { nodes = [node]; } else if (Array.isArray(node)) { nodes = node; } else { throw new Error('Invalid paramter passed to `.drop()` method'); } const pathNames = nodes.map((item) => { if (typeof item === 'string') { return item; } return item.pathName; }); this.items = this.items.filter((node) => { return pathNames.indexOf(node.pathName) === -1; }); return this; }; module.exports = Draxt; ================================================ FILE: src/interfaces/Directory.js ================================================ const Node = require('./Node'), {getType} = require('../util'); const nodeProps = { nodeName: {value: 'Directory', writable: false, configurable: false, enumerable: true}, NODE_TYPE: {value: 1, writable: false, configurable: false, enumerable: true}, }; /** * `Directory` class which extends the `Node` class is an interface representing pathNames * that their `fs.Stats`'s `.isDirectory()` method returns `true`. * @prop {string} nodeName Name of the node: `'Directory'`. * @prop {number} NODE_TYPE Code number for the node: `1`. */ class Directory extends Node { /** * Construct a new node * @param {string} pathName Absolute pathName of the node * @param {object} [stats] Instance of `fs.Stats` for the node */ constructor(pathName, stats) { super(pathName, stats); Object.defineProperties(this, nodeProps); } /** * Append/move passed directories into this directory node. * Uses `node.moveTo` which uses `fs-extra.move`. * @param {draxt|node|string|array} nodes Accepts various parameters: * - `draxt` collection. * - a node instance. * - pathNames of a file or directory. * - array of node instances. * - array of absolute pathNames of files/directories. * - a mixed array of nodePaths and absolute pathNames of files/directories. * @param {object} [options] Options for `fs-extra.move`. * @returns {promise} Promise representing array of moved nodes. */ append(nodes, options) { nodes = this.constructor.__normalizeAppendNodes(nodes); const mvPromices = nodes.map((node) => { node = node instanceof Node ? node : new Node(node); return node.moveTo(this, options); }); return Promise.all(mvPromices).then(() => this); } /** * Synchronous version of `directory.append`. * @chainable * @param {draxt|array|string} nodes * @param {object} [options] Options for `fs-extra.move`. * @returns {node} The `directory` node. */ appendSync(nodes, options) { nodes = this.constructor.__normalizeAppendNodes(nodes); nodes.forEach((node) => { node = node instanceof Node ? node : new Node(node); return node.moveToSync(this, options); }); return this; } /** * Asynchronously select children of the directory by using `glob` package and * return a `draxt` collection. * @param {string} [pattern='*'] Glob pattern relative to the directory. The pattern * is used against `baseName` of directory child nodes. * @param {object} [options] Options for `glob` package. * @returns {promise} */ children() { const {rawQuery, toNodes, Draxt} = Node; let [options, filterFn] = this.__normalizeRelativeGlobOptions(...arguments); return rawQuery('*', options).then((items) => { if (filterFn) { items = items.filter(filterFn); } return toNodes(items).then(Draxt); }); } /** * Synchronous version of `directory.children`. * @param {string} [selector] Optional selector * @param {object} [options] Options for glob package * @returns {draxt} */ childrenSync() { const {rawQuerySync, toNodesSync, Draxt} = Node; let [_options, filterFn] = this.__normalizeRelativeGlobOptions(...arguments); let items = rawQuerySync('*', _options); if (filterFn) { items = items.filter(filterFn); } return new Draxt(toNodesSync(items)); } /** * Ensures that a directory is empty. Deletes directory contents if the directory * is not empty. If the directory does not exist, it is created. * The directory itself is not deleted. * Wrapper for `fs-extra.emptyDir`. * @returns {promise} */ empty() { return this.fs.emptyDir(this.pathName); } /** * Synchronous version of `directory.empty` method. * Wrapper for `fs-extra.emptyDirSync`. * @returns {node} */ emptySync() { this.fs.emptyDirSync(this.pathName); return this; } /** * Asynchronously ensure directory exists. * Wrapper for `fs-extra.ensureDir`. *@returns {promise} */ ensure() { return this.fs.ensureDir(this.pathName); } /** * Synchronously ensure directory exists. * Wrapper for `fs-extra.ensureDirSync`. * @returns {node} */ ensureSync() { this.fs.ensureDirSync(this.pathName); return this; } /** * Is directory empty? * @returns {promise} */ isEmpty() { return this.readdir().then((files) => { return files.length === 0; }); } /** * Synchronous version of `directory.isEmpty` method. * @returns {boolean} */ isEmptySync() { return this.readdirSync().length === 0; } /** * Find matching decendants of the directory node. * Uses `glob` package. * @param {string} pattern Glob pattern. * @param {object} options Options for `glob` package. * @returns {Promise} */ find(selector, options) { const {Draxt} = Node; options = Node.__normalizeGlobOptions(options); options.cwd = this.pathName; return Node.query(selector, options).then((items) => { return new Draxt(items); }); } /** * Synchronous version of `directory.find` method. * @param {string} selector * @param {object} options Options for `glob` package. * @returns {draxt} */ findSync(selector, options) { const {Draxt} = Node; options = Node.__normalizeGlobOptions(options); options.cwd = this.pathName; const items = Node.querySync(selector, options); return new Draxt(items); } /** * Wrapper for promisified `fs.readdir`. * @param {string|object} options * @returns {promise} */ readdir() { return this.fs.readdir(this.pathName, ...arguments); } /** * Wrapper for `fs.readdirSync`. * @param {string|object} options * @returns {array} */ readdirSync() { return this.fs.readdirSync(this.pathName, ...arguments); } /** * Alias for `directory.readdir` method. * @param {string|object} options * @returns {promise} */ read() { return this.readdir(...arguments); } /** * Alias for `directory.readdirSync` method. * @param {string|object} options * @returns {array} */ readSync() { return this.readdirSync(...arguments); } /** * Wrapper for promisified `fs.rmdir`. * Deletes the directory, which must be empty. * @returns {promise} */ rmdir() { return this.fs.rmdir(this.pathName); } /** * Wrapper for `fs.rmdirSync`. * Deletes the directory, which must be empty. * @chainable * @returns {node} */ rmdirSync() { this.fs.rmdirSync(this.pathName); return this; } static __normalizeAppendNodes(nodes) { const {Draxt} = Node; if (nodes instanceof Draxt) { nodes = nodes.get(); } else if (nodes instanceof Node) { nodes = [nodes]; } else if (getType(nodes) === 'string') { nodes = [nodes]; } else if (getType(nodes) !== 'array') { throw new Error(`Invalid parameter for \`nodes\` parameter: ${nodes}`); } return nodes; } } module.exports = Node.Directory = Directory; ================================================ FILE: src/interfaces/File.js ================================================ const Node = require('./Node'); const nodeProps = { nodeName: {value: 'File', writable: false, configurable: false, enumerable: true}, NODE_TYPE: {value: 2, writable: false, configurable: false}, }; /** * `File` class which extends the `Node` class is an interface representing pathNames * that their `fs.Stats`'s `.isFile()` method returns `true`. * @prop {string} nodeName Name of the node: `'File'`. * @prop {number} NODE_TYPE Code number for the node: `2`. */ class File extends Node { /** * Construct a new file. * @param {string} pathName Absolute pathName of the node * @param {object} [stats] Instance of `fs.Stats` for the node */ constructor(pathName, stats) { super(pathName, stats); Object.defineProperties(this, nodeProps); } /** * Ensure the file node exists on file system. * Wrapper for `fs-extra.ensureFile`. * @returns {promise} */ ensure() { return this.fs.ensureFile(this.pathName); } /** * Ensure the file node exists on file system synchronously. * Wrapper for `fs.ensureFileSync`. * @returns {node} */ ensureSync() { this.fs.ensureFileSync(this.pathName); return this; } /** * Asynchronously append data to a file, creating the file if it does not yet exist. `data` can be a string or a Buffer. * Wrapper for `fs.appendFile`. * @returns {promise} */ append() { return this.fs.appendFile(this.pathName, ...arguments); } /** * Wrapper for `fs.appendFileSync`. * @returns {node} */ appendSync() { this.fs.appendFileSync(this.pathName, ...arguments); return this; } /** * Promisified wrapper for `fs.readFile`. * @returns {promise} Promise object representing contents of the file. */ read() { return this.fs.readFile(this.pathName, ...arguments); } /** * Wrapper for `fs.readFileSync`. * @returns {any} */ readSync() { return this.fs.readFileSync(this.pathName, ...arguments); } /** * Promisified wrapper for `fs.truncate` * @returns {promise} */ truncate() { return this.fs.truncate(this.pathName, ...arguments); } /** * Wrapper for `fs.truncateSync`. * @returns {node} */ truncateSync() { this.fs.truncateSync(this.pathName, ...arguments); return this; } /** * Promisified `fs.writeFile` * @returns {promise} */ write() { return this.fs.writeFile(this.pathName, ...arguments); } /** * Wrapper for `fs.writeFileSync`. * @chainable * @returns {node} */ writeSync() { this.fs.writeFileSync(this.pathName, ...arguments); return this; } } module.exports = Node.File = File; ================================================ FILE: src/interfaces/Node.js ================================================ const path = require('path'), {glob, globSync} = require('glob'), fs = require('fs-extra'), minimatch = require('minimatch'), {getType} = require('../util'), assign = Object.assign; // Default properies which are used for detecting node type. const defaultNodeProps = { nodeName: {value: 'Node', writable: false, configurable: true, enumerable: true}, NODE_TYPE: {value: 0, writable: false, configurable: true, enumerable: true}, }; /** * Node class is an interface that other classes representing * file system's nodes (like `File`, `Directory`, `SymbolicLink`, ...) inherit. * @prop {string} [nodeName='Node'] Default: `'Node'`. The name of constructor of the current node. * @prop {number} [NODE_TYPE=0] Default: `0`. Code number for the node. * @prop {string} pathName Absolute pathName of the node. Example: `'/app/readme.md'`. * @prop {string} baseName baseName of the node. Example: `'readme.md'`. * @prop {string} name Name of the node without the possible extension. Example `'readme'`. * @prop {string|undefined} extension Extension of the node without `.`. Example: `'js'`. * @prop {string} parentPath pathName of the parent directory of the node. * @prop {string} rootPath Root path of the file system. * @prop {object|undefined} _stats Cached instance of `fs.Stats` for the node. * @prop {object} fs Refers to `fs-extra` package. * @prop {object} glob Refers to `glob` package. */ class Node { /** * Construct a new node * @param {string} pathName Absolute pathName of the node * @param {object} [stats] Instance of `fs.Stats` for the node */ constructor(pathName, stats) { this._stats = stats; Object.defineProperties(this, defaultNodeProps); this._setPathParams(pathName); } /** * Parse the node's pathName by using `path.parse()` method * and set the corresponsing node's properties. * @returns {undefined} */ _setPathParams(nodePathname) { const {root, dir, name, ext, base} = path.parse(nodePathname); this.pathName = nodePathname; this.baseName = base; this.name = name; this.extension = ext.slice(1); this.rootPath = root; this.parentPath = dir; } /** * Get the node's pathName. * @returns {string} */ getPathName() { return this.pathName; } /** * Get the node's baseName. * @returns {string} */ getBaseName() { return this.baseName; } /** * Get the node's extension. * @returns {string} */ getExtension() { return this.extension; } /** * Get name of the node. * For `File` nodes the `name` property is the name of file without possible extension. * @returns {string} */ getName() { return this.name; } /** * Get the node's parent directory pathName. * @returns {string} */ getParentPath() { return this.parentPath; } /** * Get cached `fs.Stats` instance for the node. Returns `undefined` when there * is no cached stats for the node. This happens only when the node is created * manually by user without passing a stats object. * @returns {object|undefined} */ getCachedStats() { return this._stats; } /** * Get a stat property's value from cached `fs.Stats` for the node. * The method returns `undefined` when there is no cached stats. * @param {string} propName * @example * // Get `blksize` property of fs.Stats instance cached for the node. * const node_ctime = node.getStatProp('blksize'); * @returns {any} */ getStatProp(propName) { return (this._stats || {})[propName]; } /** * Get "access time" of the node. Returns `atime` property of the cached stats. * @returns {date} */ getAccessTime() { return this.getStatProp('atime'); } /** * Get "modified time" of the node. Returns `mtime` property of the cached stats. * @returns {date} */ getModifiedTime() { return this.getStatProp('mtime'); } /** * Get "birthday time" of the node. Returns `birthtime` property of the cached stats. * @returns {date} */ getBirthTime() { return this.getStatProp('birthtime'); } /** * Get "change time" of the node. Returns `ctime` property of the cached stats. * @returns {date} */ getChangeTime() { return this.getStatProp('ctime'); } /** * Get size of the node. * Size is simply the `size` property of the cached `fs.Stats` instance. * @returns {number} */ getSize() { return this.getStatProp('size'); } /** * Is the node a directory? * @returns {boolean} */ isDirectory() { return this.nodeName === 'Directory'; } /** * Is the node a file? * @returns {boolean} */ isFile() { return this.nodeName === 'File'; } /** * Is the node a symbolic link? * @returns {boolean} */ isSymbolicLink() { return this.nodeName === 'SymbolicLink'; } /** * Is the node a dot file? i.e. does the node's name begin with dot character. * @returns {boolean} */ isDotFile() { return this.baseName[0] === '.'; } /** * Asynchronously renew stats of the node. Uses `fs.lstat`. * @returns {promise} A fresh `fs.Stats` instance for the node. */ renewStats() { return this.fs.lstat(this.pathName).then((stats) => { this._stats = stats; return stats; }); } /** * Synchronously renew stats of the node. Uses `fs.lstatSync`. * @chainable * @returns {node} */ renewStatsSync() { const stat = this.lstatSync(this.pathName); this._stats = stat; return this; } /** * Get octal representation of the node's permissions. * @returns {string} * @example * node.getOctalPermissions() // → "755" */ getOctalPermissions() { return (this._stats.mode & 0o777).toString(8); } /** * Get permissions of the node for owner, group and others by converting `mode` * property of cached stats into an object. * @example * node.getPermissions() * // → * { * read: { owner: true, group: true, others: false }, * write: { owner: true, group: true, others: false }, * execute: { owner: true, group: true, others: false } * } * @returns {object} */ getPermissions() { if (getType(this._stats) !== 'object') { throw new Error( 'No valid cached stats ofr this node. Run `.renewStats()` before calling this function!' ); } // Logic taken from npm `mode-to-permissions` module const mode = this._stats.mode, owner = mode >> 6, group = (mode << 3) >> 6, others = (mode << 6) >> 6; return { read: { owner: !!(owner & 4), group: !!(group & 4), others: !!(others & 4), }, write: { owner: !!(owner & 2), group: !!(group & 2), others: !!(others & 2), }, execute: { owner: !!(owner & 1), group: !!(group & 1), others: !!(others & 1), }, }; } /** * Asynchronously tests a user's permissions for the file or directory. * Wrapper for promisified `fs.access`. * @param {integer} [mode=fs.constants.F_OK] * @returns {promise} * @example * // Check if the node is readable. * node.access(node.fs.constants.R_OK).then(() => { * // node is readable * }).catch(e => { * // node is not readable * }); */ access(mode) { return this.fs.access(this.pathName, mode); } /** * Wrapper for `fs.accessSync`. * @chainable * @param {integer} [mode=fs.constants.F_OK] * @returns {node} this */ accessSync(mode) { this.fs.accessSync(this.pathName, mode); return this; } /** * Wrapper for promisified `fs.chmod`. * @param {integer} mode * @returns {promise} */ chmod(mode) { return this.fs.chmod(this.pathName, mode); } /** * Wrapper for `fs.chmodSync`. * @chainable * @param {integer} mode * @returns {node} this */ chmodSync(mode) { this.fs.chmodSync(this.pathName, mode); return this; } /** * Wrapper for promisified `fs.lchmod`. * @param {integer} mode * @returns {promise} */ lchmod(mode) { return this.fs.lchmod(this.pathName, mode); } /** * Wrapper for `fs.lchmodSync`. * @chainable * @param {integer} mode * @returns {node} */ lchmodSync(mode) { this.fs.lchmodSync(this.pathName, mode); return this; } /** * Wrapper for promisified `fs.chown`. * @param {integer} uid The user id * @param {integer} gid The group id * @returns {promise} */ chown(uid, gid) { return this.fs.chown(this.pathName, uid, gid); } /** * Wrapper for `fs.chownSync`. * @chainable * @param {integer} uid The user id * @param {integer} gid The group id * @returns {node} The file node */ chownSync(uid, gid) { this.fs.chownSync(this.pathName, uid, gid); return this; } /** * Wrapper for promisified `fs.lchown`. * @param {integer} uid The user id * @param {integer} gid The group id * @returns {promise} */ lchown(uid, gid) { return this.fs.lchown(this.pathName, uid, gid); } /** * Wrapper for `fs.lchownSync`. * @chainable * @param {integer} uid The user id * @param {integer} gid The group id * @returns {node} The file node */ lchownSync(uid, gid) { this.fs.lchownSync(this.pathName, uid, gid); return this; } /** * Does node exist on file system? * Uses `fs.access` instead of the deprecated `fs.exists` method. * @returns {promise} */ exists() { return this.access(this.fs.constants.F_OK) .then(() => { return true; }) .catch(() => false); } /** * Does node exist on file system? * Wrapper for `fs.existsSync`. * @returns {boolean} */ existsSync() { return this.fs.existsSync(this.pathName); } /** * Wrapper for promisified `fs.stat`. * @returns {promise} Promise representing instance of `fs.Stats` for the node. */ stat() { return this.fs.stat(this.pathName, ...arguments); } /** * Wrapper for `fs.statSync`. * @returns {object} Instance of `fs.Stats` for the node. */ statSync() { return this.fs.statSync(this.pathName, ...arguments); } /** * Wrapper for promisified `fs.lstat`. * @returns {promise} Promise representing instance of `fs.Stats` for the node. */ lstat() { return this.fs.lstat(this.pathName, ...arguments); } /** * Wrapper for `fs.lstatSync`. * @returns {object} Instance of `fs.Stats` for the node. */ lstatSync() { return this.fs.lstatSync(this.pathName, ...arguments); } /** * Wrapper for promisified `fs.link`. * @param {string|Buffer|URL} newPath * @returns {Promise} */ link(newPath) { return this.fs.link(this.pathName, newPath); } /** * Wrapper for `fs.linkSync`. * @chainable * @param {string|buffer|URL} newPath * @returns {node} */ linkSync(newPath) { this.fs.linkSync(this.pathName, newPath); return this; } /** * Asynchronously rename node to the pathname provided as newPath. * In the case that `newPath` already exists, it will be overwritten. * Wrapper for promisified `fs.rename`. * @param newPath {string|Buffer|URL} * @returns {promise} */ rename(newPath) { return this.fs.rename(this.pathName, ...arguments).then(() => { this._setPathParams(newPath); }); } /** * Wrapper for `fs.renameSync`. * @chainable * @param newPath {string|Buffer|URL} * @returns {node} */ renameSync(newPath) { this.fs.renameSync(this.pathName, newPath); this._setPathParams(newPath); return this; } /** * Wrapper for promisified `fs.utimes`. * @param atime {number|string|Date} * @param mtime {number|string|Date} * @returns {promise} */ utimes() { return this.fs.utimes(this.pathName, ...arguments); } /** * Wrapper for `fs.utimesSync`. * @chainable * @param atime {number|string|Date} * @param mtime {number|string|Date} * @returns {node} */ utimesSync() { this.fs.utimesSync(this.pathName, ...arguments); return this; } /** * Asynchronously copy the node. `Directory` instances can have contents. Like `cp -r`. * When directory doesn't exist, it's created! * Wrapper for `fs-extra.copy`. * @param {string} destPath Destination path. * @param {object} options Options for `fs-extra.copy`. * @returns {promise} * @example * // creating a `File` instance. `File` class extends the `Node` class! * const file = new File('/app/resources/style.css'); * file.copy('/app/backup/backup_style.css').then(() => { * // file has been copied successfully! * }).catch(e => { * // There was an error! * }); */ copy() { return this.fs.copy(this.pathName, ...arguments); } /** * Wrapper for `fs-extra.copySync`. * @chainable * @param {string} destPath Destination path. * @param {object} options Options for `fs-extra.copySync`. * @returns {node} */ copySync() { this.fs.copySync(this.pathName, ...arguments); return this; } /** * Move node to another location. `baseName` property of the node is joined * with `targetDir` param for resolving the final path for the node. * The method on success updates path-related properties of the node, * but node's cached stats (if any) is not refreshed! * For updating node's stats, user can call `node.renewStats()` or `node.renewStatsSync()` * methods after moving the node. * Uses `fs-extra.move`. * @param {object|string} targetDir `Directory` instance or absolute path of the target directory. * @param {object} options Options for `fs-extra.move`. * @return {promise} * @example * const node = new File('/app/resources/style.css'); * const dir = new Directory('/app/target_dir'); * node.moveTo(dir || '/app/target_dir').then(() => { * // node has been moved into '/app/target_dir' directory! * node.getPathName(); // → '/app/target_dir/style.css' * }); */ moveTo(targetDir, options) { const targetPath = this.__resolvePath(targetDir); if (typeof options === 'function') { throw new Error('`node.moveTo` doesn not accept a callback function!'); } // Fix `fs.move` broken handling of optional parameters! const args = [this.pathName, targetPath]; if (options) { args.push(options); } return this.fs.move(...args).then(() => { this._setPathParams(targetPath); return this; }); } /** * Synchronous version of `node.moveTo`. * @chainable * @param {object|string} targetDir `Directory` instance or absolute path of the target directory. * @param {object} options Options for `fs-extra.move`. * @returns {node} */ moveToSync(targetDir, options) { const targetPath = this.__resolvePath(targetDir); this.fs.moveSync(this.pathName, targetPath, options); this._setPathParams(targetPath); return this; } /** * Alias for `node.moveTo`. */ appendTo() { return this.moveTo(...arguments); } /** * Alias for `node.moveToSync`. * @chainable * @returns {node} */ appendToSync() { return this.moveToSync(...arguments); } /** * Asynchronously select siblings of the node. * Uses `glob` package. * @param {string} [patten='*'] Optional `glob` pattern. * @param {object} [options] Options for `glob` package. * @return {promise} Promise representing a `draxt` collection. */ siblings() { const {rawQuery, toNodes, Draxt} = Node; let [_options, filterFn] = this.__normalizeRelativeGlobOptions(...arguments); _options.ignore.push(this.pathName); return rawQuery('*', _options).then((items) => { if (filterFn) { items = items.filter(filterFn); } return toNodes(items).then(Draxt); }); } /** * Synchronously select siblings of the node. * Uses `glob` package. * @param {string} [pattern='*'] Optional `glob` pattern. * @param {object} [options] Options for `glob` package. * @return {draxt} A `draxt` collection. */ siblingsSync() { const {rawQuerySync, toNodesSync, Draxt} = Node; let [_options, filterFn] = this.__normalizeRelativeGlobOptions(...arguments); _options.ignore.push(this.pathName); let items = rawQuerySync('*', _options); if (filterFn) { items = items.filter(filterFn); } return new Draxt(toNodesSync(items)); } /** * Remove the node from file system! `Directory` nodes can have contents. Like `rm -rf`. * Wrapper for `fs-extra.remove`. * @returns {promise} */ remove() { return this.fs.remove(this.pathName, ...arguments); } /** * Wrapper for `fs-extra.removeSync`. * @chainable * @returns {node} */ removeSync() { this.fs.removeSync(this.pathName, ...arguments); return this; } /** * Asynchronously get parent directory node of the node. It's an async method * as it gets an instance of `fs.Stats` for the parent node asynchronously! * @returns {promise} Promise representing a `Directory` instance. * @example * const file = new File('/app/resources/style.css'); * file.parent().then(dir => { * dir.isDirectory(); // → true * dir.getPathName(); // → '/app/resources' * }); */ parent() { const {Directory} = Node; return this.fs.lstat(this.parentPath).then((stats) => { return new Directory(this.parentPath, stats); }); } /** * Synchronously get parent directory node of the node. * @returns {node} A `Directory` instance. */ parentSync() { const {Directory} = Node; const stats = this.fs.lstatSync(this.parentPath); return new Directory(this.parentPath, stats); } /** * Asynchronously query the file system by using `glob` package. * @param {string} pattern Pattern for `glob` package. * @param {object} [options] Options for `glob` package. * @returns {promise} An array of pathNames. */ static rawQuery(pattern, options) { options = Node.__normalizeGlobOptions(options); return glob(pattern, options); } /** * Synchronously query the file system by using `glob` package. * @param {string} pattern Pattern for `glob` package. * @param {object} [options] Options for `glob` package. * @returns {array} An array of pathNames. */ static rawQuerySync(pattern, options) { options = Node.__normalizeGlobOptions(options); return globSync(pattern, options); } /** * Convert array of paths to array of node instances asynchronously! * A node instance can be an instance of `File`, `Directory` or `SymbolicLink`. * @param {array} pathNames Array of pathNames. * @returns {promise} Array of node instances. * @example * const pathNames = [ * '/app/resources', * '/app/resources/style.css' * ]; * Node.toNodes(pathNames).then(result => { * // result: * [ * Directory { pathName: '/app/resources', ... }, * File { pathName: '/app/resources/style.css', ... } * ] * }); */ static toNodes(pathNames) { const nItems = []; const ps = pathNames.map((item, i) => { return fs.lstat(item).then((stats) => { nItems[i] = Node.__statsToNode(item, stats); }); }); return Promise.all(ps).then(() => nItems); } /** * Convert array of paths to array of nodes synchronously! * A node instance can be instance of `File`, `Directory` or `SymbolicLink`. * @param {array} pathNames Array of paths * @returns {array} Array of node instances. */ static toNodesSync(pathNames) { return pathNames.map((item) => { const stats = fs.lstatSync(item); return Node.__statsToNode(item, stats); }); } /** * Create a node object by analyzing `fs.Stats` of a pathName. * @param {string} pathName Absolute pathName of the node. * @param {object} stats Instance of `fs.Stats` for the pathName. * @returns {node} */ static __statsToNode(pathName, stats) { const {File, Directory, SymbolicLink} = Node; if (stats.isFile()) { return new File(pathName, stats); } else if (stats.isDirectory()) { return new Directory(pathName, stats); } else if (stats.isSymbolicLink()) { return new SymbolicLink(pathName, stats); } else { return new Node(pathName, stats); } } /** * Asynchronously query the file system by using `glob` package. * @param {string} pattern A `glob` pattern. * @param {object} [options] Options for `glob` package. * @returns {promise} Array of nodes. */ static query() { const {rawQuery, toNodes} = Node; return rawQuery(...arguments).then(toNodes); } /** * Synchronously query the file system by using `glob` package. * @param {string} pattern * @param {object} [options] Options for `glob` package. * @returns {array} Array of nodes. */ static querySync() { const {rawQuerySync, toNodesSync} = Node; return toNodesSync(rawQuerySync(...arguments)); } /** * Normalize glob options. This function overrides some possible user-set * params like the `absolute` parameter. * @param {object} options * @returns {object} Normalized object */ static __normalizeGlobOptions(options = {}) { const type = getType(options); if (type !== 'undefined' && ['string', 'object'].indexOf(type) === -1) { throw new Error('Optional `options` parameter must be either a string or an object!'); } // query(pattern, context) syntax if (type === 'string') { options = { cwd: options, }; } assign(options, { absolute: true, }); return options; } /** * Normalize glob options for `Node#siblings` and `Node#siblingsSync` methods. * @param {string} [pattern] Optional pattern * @param {object} [options] Optional options * @return {array} */ __normalizeRelativeGlobOptions(pattern, options = {}) { let filterFn; if (arguments.length === 1 && getType(pattern) === 'object') { options = pattern; pattern = undefined; } if (pattern && getType(pattern) !== 'string') { throw new Error('`pattern` parameter should be a string!'); } if (getType(options) === 'string') { throw new Error('Relational queries do not accept `context` paramter!'); } if (options && getType(options) !== 'object') { throw new Error('Invalid type for `options` parameter!'); } // Convert `options.ignore` into an array (if it's not) let ignore = options.ignore; if (ignore) { if (!Array.isArray(ignore)) { ignore = [ignore]; } } else { ignore = []; } options.ignore = ignore; // If `pattern` exists create a minimatch filter function if (pattern) { filterFn = minimatch.filter(pattern, { matchBase: true, dot: !!options.dot, }); } const dirPath = this.nodeName === 'Directory' ? this.pathName : this.parentPath; assign(options, { cwd: dirPath, }); return [options, filterFn]; } /** * Make a pathName by joining `dir` parameter with node's `baseName` * @param {node|string} dir Instance of `Directory` class or a string pathName * @returns {string} */ __resolvePath(dir) { const dirType = typeof dir; if (dirType === 'undefined') { throw new Error('`dir` parameter is required!'); } const isDirectory = dir.nodeName === 'Directory'; if (!isDirectory && dirType !== 'string') { throw new Error('`dir` parameter must be a string or instance of Directory class!'); } const dirPath = isDirectory ? dir.pathName : dir; if (!path.isAbsolute(dirPath)) { throw new Error('`dir` must be an absolute path!'); } return path.join(dirPath, this.baseName); } } Node.prototype.fs = fs; Node.prototype.glob = glob; module.exports = Node; ================================================ FILE: src/interfaces/SymbolicLink.js ================================================ const Node = require('./Node'), nodeProps = { nodeName: {value: 'SymbolicLink', writable: false, configurable: false, enumerable: true}, NODE_TYPE: {value: 3, writable: false, configurable: false, enumerable: true}, }; /** * `SymbolicLink` class which extends the `Node` class is an interface representing pathNames * that their `fs.Stats`'s `.isSymbolicLink()` method returns `true`. * @prop {string} nodeName Name of the node: `'SymbolicLink'`. * @prop {number} NODE_TYPE Code number for the node: `3`. */ class SymbolicLink extends Node { /** * Construct a new symbolic link. * @param {string} pathName Absolute pathName of the node * @param {object} [stats] Instance of `fs.Stats` for the node */ constructor(nodePath, stats) { super(nodePath, stats); Object.defineProperties(this, nodeProps); } /** * Is the symlink broken? * @returns {promise} */ isBroken() { return this.readlink().then((linkPath) => { return this.exists(linkPath).then((ret) => !ret); }); } /** * Synchronous version of `symbolicLink.isBroken` method. * @returns {boolean} */ isBrokenSync() { return !this.existsSync(this.readlinkSync()); } /** * Asynchronously read the value of the symbolic link. * Wrapper for `fs.readlink`. * @param {string|object} options Options for `fs.readlinkSync`. * @returns {promise} */ readlink(options) { return this.fs.readlink(this.pathName, options); } /** * Synchronously read the value of the symbolic link. * Wrapper for `fs.readlinkSync`. * @param {string|object} options Options for `fs.readlinkSync`. * @returns {string|buffer} */ readlinkSync(options) { return this.fs.readlinkSync(this.pathName, options); } } module.exports = Node.SymbolicLink = SymbolicLink; ================================================ FILE: src/interfaces/index.js ================================================ module.exports = { Node: require('./Node'), File: require('./File'), Directory: require('./Directory'), SymbolicLink: require('./SymbolicLink'), }; ================================================ FILE: src/util.js ================================================ /** * A simple utility object */ module.exports = { /** * Get type of a variable * @param {any} value * @returns {string} */ getType(value) { return Object.prototype .toString .apply(value) .match(/\[object (\w+)\]/)[1] .toLowerCase(); } } ================================================ FILE: test/draxt.js ================================================ /* global describe, it, beforeEach, afterEach*/ const draxt = require('../src/draxt'), {expect} = require('chai'), {execSync} = require('child_process'), {Node} = draxt; const contentList = ['a.js', 'another_dir', 'another_example_file.md', 'b.md', 'example_file.md']; const mkfs = () => { const pre = ` rm -r /tmp/draxt_test_dir mkdir /tmp/draxt_test_dir echo 'example content' > /tmp/draxt_test_dir/example_file.md echo 'example content' > /tmp/draxt_test_dir/another_example_file.md mkdir /tmp/draxt_test_dir/another_dir echo '...' > /tmp/draxt_test_dir/a.js ln -s /tmp/draxt_test_dir/another_example_file /tmp/draxt_test_dir/b.md `; execSync(pre); }; describe('draxt', function () { beforeEach(function () { mkfs(); }); afterEach(function () { mkfs(); }); it('basic initialization', function () { expect(draxt()).to.be.instanceof(draxt); expect(draxt().length).to.eql(0); expect(() => draxt(['str'])).to.throw('Invalid value for `items` parameter'); const items_one = [new Node('pathName')]; const d_one = draxt(items_one); expect(d_one.length).to.eql(1); expect(d_one.items).to.eql(items_one); expect(draxt.fn).to.eql(draxt.prototype); }); it('initialization with query', function () { return draxt('/tmp/draxt_test_dir/*').then((d) => { expect(d).to.be.instanceof(draxt); expect(d.length).to.eql(5); expect(draxt(d).items).to.eql(d.items); expect(draxt(d).length).to.eql(d.length); // make sure the `items` parameter has been cloned! expect(draxt(d).items === d.items).to.eql(false); }); }); it('.sync()', function () { const result = draxt.sync('/tmp/draxt_test_dir'); expect(result).to.be.instanceof(draxt); expect(result.length).to.eql(1); expect(draxt.sync('/tmp/draxt_test_dir/*').length).to.eql(5); }); it('.extend()', function () { const exampleFunc = function () { return 'exampleFunc'; }; draxt.extend({ exampleFunc, }); expect(draxt.prototype.exampleFunc).to.eql(exampleFunc); expect(draxt.fn.exampleFunc).to.eql(exampleFunc); }); it('.add()', function () { const d = draxt(); expect(d.length).to.eql(0); const node = new Node('pathName'); d.add(node); expect(d.length).to.eql(1); // should not add a duplicate item! d.add(node); expect(d.length).to.eql(1); d.add(new Node('pathName')); expect(d.length).to.eql(1); d.add(new Node('pathName2')); expect(d.length).to.eql(2); const d2 = draxt([new Node('pathName3')]); d.add(d2); expect(d.length).to.eql(3); }); it('.get()', function () { const d = draxt.sync('/tmp/draxt_test_dir/*'); const node = d.get(); expect(node).to.be.an('array'); expect(node).to.be.eql(d.items); expect(d.get(0)).to.be.eql(d.items[0]); }); it('.first() && .last()', function () { const d = draxt.sync('/tmp/draxt_test_dir/*'); expect(d.first()).to.eql(d.items[0]); expect(d.last()).to.eql(d.items.pop()); }); it('.has()', function () { const d = draxt.sync('/tmp/draxt_test_dir/*'); expect(d.has('/tmp/draxt_test_dir/another_dir')).to.eql(true); expect(d.has(new Node('/tmp/draxt_test_dir/another_dir'))).to.eql(true); expect(d.has('/tmp/draxt_test_dir/non_existent')).to.eql(false); }); it('.slice()', function () { const d = draxt.sync('/tmp/draxt_test_dir/**'); expect(d.length).to.eql(6); const d2 = d.slice(0, 4); expect(d2).to.be.instanceof(draxt); expect(d2.length).to.be.eql(4); const d3 = d.slice(-1); expect(d3.length).to.eql(1); expect(d3.get(0)).to.be.instanceof(Node); }); it('.filter()', function () { const d = draxt.sync('/tmp/draxt_test_dir/**'); const d2 = d.filter((node) => { return node.isFile(); }); expect(d2).to.be.instanceof(draxt); expect(d === d2).to.eql(false); expect(d2.length).to.eql(3); }); it('.map()', function () { const d = draxt.sync('/tmp/draxt_test_dir/*'); const res = d.map((node) => node.baseName); res.sort(); expect(res.length).to.eql(d.length); expect(res).to.be.an('array'); expect(res).to.eql(contentList); }); it('.mapAsync', function () { const d = draxt.sync('/tmp/draxt_test_dir/*'); const res = d.mapAsync((node) => { return new Promise((res) => { setTimeout(() => res(node.baseName), 30); }); }); expect(res).to.be.instanceof(Promise); return res.then((baseNames) => { baseNames.sort(); expect(baseNames).to.eql(contentList); }); }); it('.each() && .forEach()', function () { const d = draxt.sync('/tmp/draxt_test_dir/*'); expect(d.each).to.be.eql(d.forEach); const res = []; expect(d.forEach((node) => res.push(node.baseName))).to.eql(d); res.sort(); expect(res).to.eql(contentList); }); it('.some', function () { const d = draxt.sync('/tmp/draxt_test_dir/*'); const res = d.some((node) => node.baseName === 'another_dir'); const res2 = d.some((node) => node.baseName === 'non_existent'); expect(res).to.eql(true); expect(res2).to.eql(false); }); it('.sort() && .reverse()', function () { const d = draxt.sync('/tmp/draxt_test_dir/*'); const originalNodesBackup = d.get().slice(); expect(d.reverse().get()).to.eql(originalNodesBackup.reverse()); }); it('.directories()', function () { const d = draxt.sync('/tmp/draxt_test_dir/**'); const dirs = d.directories(); expect(dirs.length).to.eql(2); expect(dirs === d).to.eql(false); }); it('.files()', function () { const d = draxt.sync('/tmp/draxt_test_dir/**'); const files = d.files(); expect(files.length).to.eql(3); expect(files === d).to.eql(false); }); it('.symlinks()', function () { const d = draxt.sync('/tmp/draxt_test_dir/**'); const symlinks = d.symlinks(); expect(symlinks.length).to.eql(1); expect(symlinks).to.be.instanceof(draxt); }); it('.empty()', function () { const d = draxt.sync('/tmp/draxt_test_dir/**'); expect(d.length).to.eql(6); expect(d.empty()).to.eql(d); expect(d.length).to.eql(0); }); it('.drop()', function () { const d = draxt.sync('/tmp/draxt_test_dir/**'); expect(d.length).to.eql(6); d.drop('/tmp/draxt_test_dir/example_file.md'); expect(d.length).to.eql(5); d.drop([new Node('/tmp/draxt_test_dir/another_example_file.md'), '/non_existent']); expect(d.length).to.eql(4); const d2 = draxt.sync('/tmp/draxt_test_dir/another_dir/*'); d.drop(d2); expect(d.length).to.eql(4); d.drop(new Node('/tmp/draxt_test_dir')); expect(d.length).to.eql(3); expect(() => d.drop(new Date())).to.throw('Invalid paramter passed to'); }); }); ================================================ FILE: test/interfaces/Directory.js ================================================ /* global describe, beforeEach, it*/ const Draxt = require('../../src/draxt'); const {expect} = require('chai'); const {execSync} = require('child_process'); const {Directory, File} = Draxt.Node; const shouldNotPass = function () { throw new Error('should not pass!'); }; describe('Directory', function () { describe('initialization and basic methods', function () { const nodePath = '/fake/_fakepath'; const stats = {}; it('`new`', function () { const node = new Directory(nodePath, stats); expect(node.pathName).to.eql(nodePath); expect(node.extension).to.eql(''); expect(node.name).to.eql('_fakepath'); expect(node.baseName).to.eql('_fakepath'); expect(node.parentPath).to.eql('/fake'); expect(node.getCachedStats() === stats).to.eql(true); }); it('path.parse methods', function () { const node = new Directory(nodePath, stats); expect(node.getPathName()).to.eql(nodePath); expect(node.getExtension()).to.eql(''); expect(node.getName()).to.eql('_fakepath'); expect(node.getBaseName()).to.eql('_fakepath'); expect(node.getParentPath()).to.eql('/fake'); }); }); describe('fs methods', function () { beforeEach(function () { const pre = ` rm -r /tmp/fake_test_dir mkdir /tmp/fake_test_dir mkdir /tmp/fake_test_dir/empty_dir mkdir /tmp/fake_test_dir/empty_dir2 mkdir /tmp/fake_test_dir/non_empty_dir mkdir /tmp/fake_test_dir/non_empty_dir/foo mkdir /tmp/fake_test_dir/non_empty_dir/.git mkdir /tmp/fake_test_dir/non_empty_dir2 echo 'example content' > /tmp/fake_test_dir/example_file.md echo 'content' > /tmp/fake_test_dir/non_empty_dir/file.rb echo 'content' > /tmp/fake_test_dir/non_empty_dir2/file.md `; execSync(pre); }); it('.rmdir() & .rmdirSync() & .ensure() & .ensureSync()', function () { const dir = new Directory('/tmp/fake_test_dir/empty_dir'); const dir2 = new Directory('/tmp/fake_test_dir/non_empty_dir'); expect(dir.existsSync()).to.eql(true); expect(dir.rmdirSync()).to.eql(dir); expect(dir.existsSync()).to.eql(false); // recreate the dir expect(dir.ensureSync()).to.eql(dir); // make sure the generated node is a directory expect(dir.renewStatsSync().isDirectory()).to.eql(true); expect(dir.existsSync()).to.eql(true); // rmdir should throw en error for non-empty dirs expect(function () { dir2.rmdirSync(); }).to.throw('ENOTEMPTY'); return dir.rmdir().then(function () { expect(dir.existsSync()).to.eql(false); return dir.ensure().then(function () { expect(dir.existsSync()).to.eql(true); expect(dir.renewStatsSync().isDirectory()).to.eql(true); return dir2 .rmdir() .then(shouldNotPass) .catch((e) => { expect(e.message).to.have.string('ENOTEMPTY'); }); }); }); }); it('.readdir() & .readdirSync() & .read() & .readSync()', function () { const dir = new Directory('/tmp/fake_test_dir/non_empty_dir'); const expected = ['.git', 'file.rb', 'foo']; expect(dir.readSync()).to.eql(expected); return dir.read().then((ret) => { expect(ret).to.eql(expected); }); }); it('isEmpty() & .isEmptySync()', function () { const dir = new Directory('/tmp/fake_test_dir/empty_dir'); const dir2 = new Directory('/tmp/fake_test_dir/non_empty_dir'); expect(dir.isEmptySync()).to.eql(true); expect(dir2.isEmptySync()).to.eql(false); return dir.isEmpty().then((empty) => { expect(empty).to.eql(true); return dir2.isEmpty().then((empty) => { expect(empty).to.eql(false); }); }); }); it('.empty() & .emptySync()', function () { const expected = ['.git', 'file.rb', 'foo']; const dir = new Directory('/tmp/fake_test_dir/non_empty_dir'); const dir2 = new Directory('/tmp/fake_test_dir/non_empty_dir2'); expect(dir.readSync()).to.eql(expected); expect(dir.emptySync()).to.eql(dir); expect(dir.readSync()).to.eql([]); expect(dir2.readSync()).to.eql(['file.md']); return dir2.empty().then(function () { expect(dir2.readSync()).to.eql([]); }); }); it('__normalizeAppendNodes', function () { const method = Directory.__normalizeAppendNodes; const d = Draxt([new File('file.ext'), new Directory('dirname')]); expect(method(d)).to.eql(d.items); const d2 = ['str', new File('doo')]; expect(method(d2)).to.eql(d2); const d3 = new File(''); expect(method(d3)).to.eql([d3]); expect(method('path')).to.eql(['path']); expect(method(d.items)).to.eql(d.items); expect(() => method(new Date())).to.throw(); }); it('.append() & .appendSync()', function () { const col = [ new File('/tmp/fake_test_dir/non_empty_dir/file.rb'), '/tmp/fake_test_dir/non_empty_dir/foo', ]; // the directory will be created! const dir = new Directory('/tmp/fake_test_dir/empty_dir'); expect(dir.appendSync(col)).to.eql(dir); // expect node to have it's new path! expect(col[0].pathName).to.eql('/tmp/fake_test_dir/empty_dir/file.rb'); expect(dir.readSync()).to.eql(['file.rb', 'foo']); expect(new Directory('/tmp/fake_test_dir/non_empty_dir').readSync()).to.eql(['.git']); // col2 const col2 = Draxt([new File('/tmp/fake_test_dir/example_file.md')]); return dir.append(col2).then(function () { expect(col2.get(0).pathName).eql('/tmp/fake_test_dir/empty_dir/example_file.md'); return dir.append('/tmp/fake_test_dir/non_empty_dir2'); }); }); it('.children() & .childrenSync()', function () { const dir = new Directory('/tmp/fake_test_dir/non_empty_dir'); const ret = dir.childrenSync(); expect(ret).to.be.instanceof(Draxt); expect(ret.length).to.eql(2); const ret2 = dir.childrenSync('*.rb'); expect(ret2.length).to.eql(1); expect(ret2.get(0)).to.be.instanceof(File); expect(ret2.get(0).baseName).to.eql('file.rb'); const ret3 = dir.childrenSync({dot: true}); expect(ret3.length).to.eql(3); return dir.children().then((set) => { expect(set).to.be.instanceof(Draxt); expect(set.length).to.eql(2); expect(set.get(0).baseName).to.eql('foo'); return dir.children('f*').then((set2) => { expect(set2.length).to.eql(2); }); }); }); it('.find() & .findSync()', function () { const dir = new Directory('/tmp/fake_test_dir'); const ret = dir.findSync('*'); expect(ret).to.be.instanceof(Draxt); expect(ret.length).to.eql(5); const ret2 = dir.findSync('**'); expect(ret2.length).to.eql(9); const ret3 = dir.findSync('**', {dot: true}); expect(ret3.length).to.eql(10); return dir.find('*').then((set) => { expect(set).to.be.instanceof(Draxt); expect(set.length).to.eql(5); return dir.find('**', {dot: true}).then((set2) => { expect(set2.length).to.eql(10); }); }); }); }); }); ================================================ FILE: test/interfaces/File.js ================================================ /* global describe, beforeEach, afterEach, it*/ const {File} = require('../../src/draxt').Node; const {expect} = require('chai'); describe('File', () => { describe('initialization', () => { it('new and .isFile() method', () => { const file = new File('/fake/fakepath/module.md', {}); expect(file.isFile()).to.eql(true); expect(file.isDirectory()).to.eql(false); expect(file.isSymbolicLink()).to.eql(false); expect(file.NODE_TYPE).to.eql(2); expect(file.nodeName).to.eql('File'); }); }); describe('fs methods', () => { beforeEach(() => { const {execSync} = require('child_process'); const pre = ` rm -r /tmp/fake_dir mkdir /tmp/fake_dir echo 'example content.' > /tmp/fake_dir/example_file.md echo 'example content.' > /tmp/fake_dir/another_example_file.md `; execSync(pre); }); afterEach(() => { // vol.reset(); }); it('.read() && .readSync()', () => { const file = new File('/tmp/fake_dir/example_file.md', {}); const content = file.readSync('utf8'); const expectContent = 'example content.\n'; expect(content).to.eql(expectContent); return file.read('utf8').then((content) => { expect(content).to.eql(expectContent); }); }); it('.append() && .appendSync()', () => { const file = new File('/tmp/fake_dir/example_file.md', {}); const ret = file.appendSync(' appended content'); expect(ret).to.eql(file); expect(file.readSync('utf8')).to.eql('example content.\n appended content'); return file.append('. string').then(() => { return file.read('utf8').then((content) => { expect(content).to.eql('example content.\n appended content. string'); }); }); }); it('.write() && .writeSync()', () => { const file = new File('/tmp/fake_dir/example_file.md', {}); const ret = file.writeSync('new content'); expect(ret).to.eql(file); expect(file.readSync('utf8')).to.eql('new content'); return file.write('new async written content', () => { return file.read('utf8').then((content) => { return expect(content).to.eql('new async written content'); }); }); }); it('.truncate() && .truncateSync()', () => { const file = new File('/tmp/fake_dir/example_file.md', {}); const ret = file.truncateSync(4); expect(ret).to.eql(file); expect(file.readSync('utf8')).to.eql('exam'); return file.truncate().then(() => { return file.read('utf8').then((content) => { return expect(content).to.eql(''); }); }); }); it('.ensure() && .ensureSync()', () => { const file = new File('/tmp/fake_dir/non_existent.md'); const file2 = new File('/tmp/fake_dir/non_existent2.md'); expect(file.existsSync()).to.eql(false); expect(file.ensureSync()).to.eql(file); expect(file.existsSync()).to.eql(true); // async example expect(file2.existsSync()).to.eql(false); return file2.ensure().then(() => { expect(file2.existsSync()).to.eql(true); }); }); }); }); ================================================ FILE: test/interfaces/Node.js ================================================ /* global describe, it, beforeEach, afterEach*/ const draxt = require('../../src/draxt'), {Node} = draxt, {Directory, File} = Node, {expect} = require('chai'), fs = require('fs-extra'), path = require('path'), isTravis = 'TRAVIS' in process.env && 'CI' in process.env, shouldNotPass = function () { throw new Error('should not pass!'); }; const mkfs = () => { const {execSync} = require('child_process'); const pre = ` rm -r /tmp/node_test_dir mkdir /tmp/node_test_dir mkdir /tmp/node_test_dir/another_dir mkdir /tmp/node_test_dir/another_dir/.dir echo 'example content.' > /tmp/node_test_dir/example_file.md echo 'example content.' > /tmp/node_test_dir/another_example_file.md echo '...' > /tmp/node_test_dir/another_dir/a.js echo '...' > /tmp/node_test_dir/another_dir/b.js echo '...' > /tmp/node_test_dir/another_dir/c.php echo '...' > /tmp/node_test_dir/another_dir/k.php echo '...' > /tmp/node_test_dir/another_dir/d.html echo '...' > /tmp/node_test_dir/another_dir/README.md echo '...' > /tmp/node_test_dir/another_dir/foo.rb echo '...' > /tmp/node_test_dir/another_dir/document.txt ln -s /tmp/node_test_dir/example_file.md /tmp/node_test_dir/another_dir/g.md `; execSync(pre); }; describe('Node', function () { describe('initialization and basic methods', function () { const nodePath = '/fake/_fakepath/module.js'; const stats = {}; it('`new`', function () { const node = new Node(nodePath, stats); expect(node.pathName).to.eql(nodePath); expect(node.extension).to.eql('js'); expect(node.name).to.eql('module'); expect(node.baseName).to.eql('module.js'); expect(node.parentPath).to.eql('/fake/_fakepath'); expect(node.rootPath).to.eql('/'); expect(node.getCachedStats() === stats).to.eql(true); }); it('path.parse methods', function () { const node = new Node(nodePath, stats); expect(node.getPathName()).to.eql(nodePath); expect(node.getExtension()).to.eql('js'); expect(node.getName()).to.eql('module'); expect(node.getBaseName()).to.eql('module.js'); expect(node.getParentPath()).to.eql('/fake/_fakepath'); expect(node.isDotFile()).to.eql(false); node.baseName = '.git'; expect(node.isDotFile()).to.eql(true); node._stats = undefined; expect(node.getStatProp('foo')).to.eql(undefined); }); }); describe('fs module methods', function () { beforeEach(function () { mkfs(); }); // afterEach(function () { // mockFs.restore(); // }); it('.getPermissions()', function () { const node = new Node('/tmp/node_test_dir/example_file.md'); expect(() => node.getPermissions()).to.throw('renewStats'); const perms = node.chmodSync('700').renewStatsSync().getPermissions(); expect(perms).to.eql({ read: {owner: true, group: false, others: false}, write: {owner: true, group: false, others: false}, execute: {owner: true, group: false, others: false}, }); const perms2 = node.chmodSync('777').renewStatsSync().getPermissions(); expect(perms2).to.eql({ read: {owner: true, group: true, others: true}, write: {owner: true, group: true, others: true}, execute: {owner: true, group: true, others: true}, }); }); it('.getAccessTime() && .getBirthTime() && .getModifiedTime() && .getChangeTime()', function () { const node = new Node('/tmp/node_test_dir/example_file.md'); node.renewStatsSync(); expect(node.getBirthTime()).to.eql(node._stats.birthtime); expect(node.getAccessTime()).to.eql(node._stats.atime); expect(node.getChangeTime()).to.eql(node._stats.ctime); expect(node.getModifiedTime()).to.eql(node._stats.mtime); }); it('.__resolvePath()', function () { const node = new Node('/tmp/node_test_dir/example_file.md'); expect(node.__resolvePath('/foo/bar')).to.eql('/foo/bar/example_file.md'); expect(node.__resolvePath('/foo/bar/')).to.eql('/foo/bar/example_file.md'); expect(node.__resolvePath(new Directory('/foo/bar/'))).to.eql( '/foo/bar/example_file.md' ); expect(() => node.__resolvePath('../bar/')).to.throw('absolute'); expect(() => node.__resolvePath(new File('/foo/bar.js'))).to.throw('Directory'); expect(() => node.__resolvePath()).to.throw('required'); }); it('.renewStats() && .renewStatsSync()', function () { const node = new Node('/tmp/node_test_dir/example_file.md'); expect(node._stats).to.eql(); expect(node.renewStatsSync()).to.eql(node); expect(node._stats).to.be.an('object'); const oldCache = node._stats; node._stats = null; return node.renewStats().then(function () { expect(node._stats).to.be.an('object'); expect(node._stats === oldCache).to.eql(false); expect(node.getSize()).to.eql(node._stats.size); }); }); it('.access() && .accessSync()', function () { const node = new Node('/tmp/node_test_dir/example_file.md', {}); const node2 = new Node('/tmp/node_test_dir/does_not_exist.md', {}); expect(node.accessSync()).to.eql(node); expect(() => node2.accessSync()).to.throw(); return node.access().then(function () { return node2 .access() .then(shouldNotPass) .catch((e) => { expect(e.message).to.include('ENOENT'); }); }); }); it('.chmod() && .chmodSync() && .lchmod() && .lchmodSync()', function () { const node = new Node('/tmp/node_test_dir/another_dir/a.js', {}); expect(node.chmodSync('700')).to.eql(node); node.renewStatsSync(); expect(node.getOctalPermissions()).to.eql('700'); expect(node.lchmodSync('755')).to.eql(node); node.renewStatsSync(); return node.chmod('755').then(function () { node.renewStatsSync(); if (!isTravis) expect(node.getOctalPermissions()).to.eql('755'); return node.lchmod('711').then(function () { node.renewStatsSync(); }); }); }); it('.chown() && .chownSync() && .lchown() && .lchownSync()', function () { const node = new Node('/tmp/node_test_dir/another_dir/g.md', {}); expect(node.chownSync(1000, 1000)).to.eql(node); node.renewStatsSync(); // Temporarily comment some tests that fail on travis environment! // // expect(node._stats.uid).to.eql(10); // expect(node._stats.gid).to.eql(11); // expect(node.lchownSync(1000, 1000)).to.eql(node); // node.renewStatsSync(); // expect(node._stats.uid).to.eql(20); // expect(node._stats.gid).to.eql(22); return node.chown(1000, 1000).then(function () { // node.renewStatsSync(); // expect(node._stats.uid).to.eql(30); // expect(node._stats.gid).to.eql(33); // This test fails probably because of the mockFs // return node.lchown(1000, 1000).then(function() { // node.renewStatsSync(); // expect(node._stats.uid).to.eql(40); // expect(node._stats.gid).to.eql(44); // }); }); }); it('.exists() && .existsSync()', function () { const node = new Node('/tmp/node_test_dir/example_file.md', {}); const node2 = new Node('/tmp/node_test_dir/does_not_exist.md', {}); expect(node.existsSync()).to.eql(true); expect(node2.existsSync()).to.eql(false); return node.exists().then((ret) => { expect(ret).to.eql(true); return node2.exists().then((ret2) => { expect(ret2).to.eql(false); }); }); }); it('.stat() && .statSync() && .lstat() && .lstatSync() ', function () { const node = new Node('/tmp/node_test_dir/example_file.md', {}); expect(node.statSync()).to.be.an('object'); expect(node.lstatSync()).to.be.an('object'); return node.stat().then(function (stats) { expect(stats).to.be.an('object'); return node.lstat().then((stats2) => { expect(stats2).to.be.an('object'); }); }); }); it('.link() && .linkSync()', function () { const node = new Node('/tmp/node_test_dir/example_file.md', {}); const nodeLink = new Node('/tmp/node_test_dir/example_file_link.md'); const nodeLink2 = new Node('/tmp/node_test_dir/example_file_link2.md'); // make sure new name for the file doesn't exist before linking expect(nodeLink.existsSync()).to.eql(false); expect(nodeLink2.existsSync()).to.eql(false); expect(node.linkSync(nodeLink.pathName)).to.eql(node); expect(nodeLink.existsSync()).to.eql(true); return node.link(nodeLink2.pathName).then(function () { expect(nodeLink2.existsSync()).to.eql(true); }); }); it('.rename() && .renameSync()', function () { const node = new Node('/tmp/node_test_dir/example_file.md', {}); const renameSampleNode = new Node('/tmp/node_test_dir/another_dir/example_file.js', {}); const renameSampleNodeAsync = new Node( '/tmp/node_test_dir/another_dir/new_name.md', {} ); expect(renameSampleNode.existsSync()).to.eql(false); expect(renameSampleNodeAsync.existsSync()).to.eql(false); expect(node.renameSync(renameSampleNode.pathName)).to.eql(node); expect(renameSampleNode.existsSync()).to.eql(true); expect(node.pathName).to.eql(renameSampleNode.pathName); expect(node.getExtension()).to.eql('js'); return node.rename(renameSampleNodeAsync.pathName).then(function () { expect(renameSampleNodeAsync.existsSync()).to.eql(true); expect(node.pathName).to.eql(renameSampleNodeAsync.pathName); }); }); it('.utimes() && .utimesSync()', function () { const node = new Node('/tmp/node_test_dir/example_file.md'); const atime = 1529607246; // Thursday, June 21, 2018 6:54:06 PM const mtime = 1529520846; // Wednesday, June 20, 2018 6:54:06 PM expect(node.utimesSync(atime, mtime)).to.eql(node); node.renewStatsSync(); if (!isTravis) { // for some reason these fails on travis! Probably because of mock-fs expect(node.getStatProp('atimeMs') / 1000).to.eql(atime); expect(node.getStatProp('mtimeMs') / 1000).to.eql(mtime); } return node.utimes(atime - 2000, mtime - 2000).then(function () { node.renewStatsSync(); if (!isTravis) { // for some reason these fails on travis! Probably because of mock-fs expect(node.getStatProp('atimeMs') / 1000).to.eql(atime - 2000); expect(node.getStatProp('mtimeMs') / 1000).to.eql(mtime - 2000); } }); }); }); describe('Utility methods', function () { beforeEach(function () { mkfs(); }); it('.__normalizeGlobOptions()', function () { const o1 = Node.__normalizeGlobOptions(); expect(o1).to.eql({ absolute: true, }); const o2 = Node.__normalizeGlobOptions({absolute: false}); expect(o2).to.eql({ absolute: true, }); expect(() => Node.__normalizeGlobOptions([])).to.throw( 'must be either a string or an object' ); const o3 = Node.__normalizeGlobOptions('/path'); expect(o3).to.eql({ cwd: '/path', absolute: true, }); }); it('.__statsToNode()', function () { const mockStats = (type) => { return { isFile() { return type === 'File'; }, isSymbolicLink() { return type === 'SymbolicLink'; }, isDirectory() { return type === 'Directory'; }, }; }; [ {path: '/node', type: 'Node', nodeType: 0}, {path: '/directory', type: 'Directory', nodeType: 1}, {path: '/file', type: 'File', nodeType: 2}, {path: '/symlink', type: 'SymbolicLink', nodeType: 3}, ].forEach((item) => { const node = Node.__statsToNode(item.path, mockStats(item.type)); expect(node.pathName).to.eql(item.path); expect(node.nodeName).to.eql(item.type); expect(node.NODE_TYPE).to.eql(item.nodeType); }); }); it('.toNodes() && .toNodesSync()', function () { const paths = [ '/tmp/node_test_dir/another_dir', '/tmp/node_test_dir/another_dir/g.md', '/tmp/node_test_dir/example_file.md', ]; const nodes1 = Node.toNodesSync(paths); expect(nodes1).to.be.an('array'); expect(nodes1[0]).to.be.instanceof(Node.Directory); expect(nodes1[1]).to.be.instanceof(Node.SymbolicLink); expect(nodes1[2]).to.be.instanceof(Node.File); nodes1.forEach((el, index) => { expect(el.pathName).to.eql(paths[index]); }); return Node.toNodes(paths).then((nodes2) => { expect(nodes2[0]).to.be.instanceof(Node.Directory); expect(nodes2[1]).to.be.instanceof(Node.SymbolicLink); expect(nodes2[2]).to.be.instanceof(Node.File); nodes2.forEach((el, index) => { expect(el.pathName).to.eql(paths[index]); }); }); }); (function () { const rawExpected1 = [ '/tmp/node_test_dir/another_dir', '/tmp/node_test_dir/another_example_file.md', '/tmp/node_test_dir/example_file.md', ].sort(); const rawExpected2 = [ '/tmp/node_test_dir', '/tmp/node_test_dir/another_dir', '/tmp/node_test_dir/another_dir/a.js', '/tmp/node_test_dir/another_dir/b.js', '/tmp/node_test_dir/another_dir/c.php', '/tmp/node_test_dir/another_dir/d.html', '/tmp/node_test_dir/another_dir/document.txt', '/tmp/node_test_dir/another_dir/foo.rb', '/tmp/node_test_dir/another_dir/g.md', '/tmp/node_test_dir/another_dir/k.php', '/tmp/node_test_dir/another_dir/README.md', '/tmp/node_test_dir/another_example_file.md', '/tmp/node_test_dir/example_file.md', ].sort(); it('.rawQuery() && .rawQuerySync()', function () { // result seem to be sorted by default let items1 = Node.rawQuerySync('*', '/tmp/node_test_dir'); expect(items1.sort()).to.eql(rawExpected1); const items2 = Node.rawQuerySync('**', { cwd: '/tmp/node_test_dir', }); expect(items2.sort()).to.eql(rawExpected2); return Node.rawQuery('*', '/tmp/node_test_dir').then((res1) => { expect(res1.sort()).to.eql(rawExpected1); return Node.rawQuery('**', '/tmp/node_test_dir').then((res2) => { expect(res2.sort()).to.eql(rawExpected2); }); }); }); it('.query() && .querySync()', function () { const result1 = Node.querySync('*', '/tmp/node_test_dir'); expect(result1).to.be.an('array'); expect(result1.length).to.eql(rawExpected1.length); const result2 = Node.querySync('**', { cwd: '/tmp/node_test_dir', }); expect(result2.length).to.eql(rawExpected2.length); expect(result2[0].pathName).to.eql(rawExpected2[0]); return Node.query('*', '/tmp/node_test_dir').then((res1) => { expect(res1.length).to.eql(res1.length); return Node.query('**', '/tmp/node_test_dir').then((res2) => { expect(res2.length).to.eql(rawExpected2.length); }); }); }); })(); it('.remove() && .removeSync()', function () { const node = new Node('/tmp/node_test_dir/example_file.md'); const node2 = new Node('/tmp/node_test_dir/another_example_file.md'); expect(node.existsSync()).to.eql(true); expect(node.removeSync()).to.eql(node); expect(node.existsSync()).to.eql(false); expect(node2.existsSync()).to.eql(true); return node2.remove().then(function () { expect(node2.existsSync()).to.eql(false); }); }); it('.parent() && .parentSync()', function () { const node = new Node('/tmp/node_test_dir/example_file.md'); const parent = node.parentSync(); expect(parent).to.be.instanceof(Directory); expect(parent.pathName).to.eql('/tmp/node_test_dir'); return node.parent().then((parentAsync) => { expect(parentAsync).to.be.instanceof(Directory); expect(parentAsync.pathName).to.eql('/tmp/node_test_dir'); }); }); it('.siblings() && .siblingsSync()', function () { const node = new Node('/tmp/node_test_dir/another_dir/c.php'); const s1 = node.siblingsSync(); expect(s1.length).to.eql(8); const exists = s1.some((el) => el.path === node.pathName); expect(exists).to.eql(false); const s2 = node.siblingsSync('*.php'); expect(s2.length).to.eql(1); // Note: `*` doesn't const s3 = node.siblingsSync('*', { ignore: '*.php', dot: true, }); expect(s3.length).to.eql(8); const s4 = node.siblingsSync({ ignore: ['*.php'], }); expect(s4.length).to.eql(7); // errors expect(() => node.siblingsSync([], {})).to.throw('should be a string'); expect(() => node.siblingsSync('*', 'context')).to.throw('context'); expect(() => node.siblingsSync('*', [])).to.throw('options'); return node.siblings().then((ss1) => { expect(ss1.length).to.eql(8); return node .siblings('*.md', { ignore: 'README.md', }) .then((ss2) => { expect(ss2.length).to.eql(1); expect(ss2).to.be.instanceof(draxt); expect(ss2.get(0).baseName).to.eql('g.md'); }); }); }); it('.moveTo() && .moveToSync() && .appendTo() && .appendToSync()', function () { const node = new Node('/tmp/node_test_dir/another_dir/c.php'); expect(node.moveToSync('/tmp/node_test_dir/another_dir/.git')).to.eql(node); expect(node.pathName).to.eql('/tmp/node_test_dir/another_dir/.git/c.php'); return node.moveTo('/tmp/node_test_dir').then(function () { expect(node.pathName).to.eql('/tmp/node_test_dir/c.php'); expect(() => node.moveTo('/tmp/node_test_dir/', function () {})).to.throw( 'callback' ); // @TODO: rewrite, test content had been changed. // expect(() => node.appendToSync('/tmp/node_test_dir/store')).to.throw( // 'dest already exists.' // ); // return node // .appendTo('/tmp/node_test_dir/store', {overwrite: true}) // .then(function () { // expect(node.pathName).to.eql('/tmp/node_test_dir/store/c.php'); // expect(node.fs.readFileSync(node.pathName, 'utf8')).to.eql( // 'c.php content!' // ); // }); }); }); }); /** * Exclude copy from others tests for creating example test files and directories * Reason: fs-extra is not fully compatible with mock-fs module! */ describe('.copy() && .copySync()', function () { const nodePath = path.join(__dirname, '/test_dir/exmaple.node'); const copyPath = path.join(__dirname, '/test_dir/non_existent/copied.php'); const copyPath2 = path.join(__dirname, '/test_dir/copied.php'); beforeEach(function () { fs.removeSync(path.join(__dirname, 'test_dir')); fs.ensureFileSync(nodePath); fs.writeFileSync(nodePath, 'example content!', 'utf8'); }); it('.copy() && .copySync', function () { const node = new Node(nodePath); expect(node.existsSync()).to.eql(true); expect(node.copySync(copyPath)).to.eql(node); expect(node.fs.readFileSync(copyPath, 'utf8')).to.eql('example content!'); return node.copy(copyPath2).then(function () { expect(node.fs.readFileSync(copyPath2, 'utf8')).to.eql('example content!'); }); }); afterEach(function () { fs.removeSync(path.join(__dirname, 'test_dir')); }); }); }); ================================================ FILE: test/interfaces/SymbolicLink.js ================================================ /* global describe, beforeEach, it*/ const {SymbolicLink} = require('../../src/draxt').Node; const {expect} = require('chai'); describe('SymbolicLink', () => { describe('initialization and basic methods', () => { const nodePath = '/fake/_fakepath'; const stats = {}; it('`new`', () => { const node = new SymbolicLink(nodePath, stats); expect(node.pathName).to.eql(nodePath); expect(node.extension).to.eql(''); expect(node.name).to.eql('_fakepath'); expect(node.baseName).to.eql('_fakepath'); expect(node.parentPath).to.eql('/fake'); expect(node.getCachedStats() === stats).to.eql(true); }); it('path.parse methods', () => { const node = new SymbolicLink(nodePath, stats); expect(node.getPathName()).to.eql(nodePath); expect(node.getExtension()).to.eql(''); expect(node.getName()).to.eql('_fakepath'); expect(node.getBaseName()).to.eql('_fakepath'); expect(node.getParentPath()).to.eql('/fake'); }); }); describe('fs methods', () => { beforeEach(() => { const {execSync} = require('child_process'); const pre = ` rm -r /tmp/fake_dir mkdir /tmp/fake_dir mkdir /tmp/fake_dir/childdir echo 'example content.' > /tmp/fake_dir/example_file.md ln -s /tmp/fake_dir/example_file.md /tmp/fake_dir/childdir/sym1.md ln -s /tmp/fake_dir/non_existent.md /tmp/fake_dir/childdir/sym2 `; execSync(pre); }); // afterEach(() => { // vol.reset(); // }); it('.readlink() && .readlink()', () => { const sym1 = new SymbolicLink('/tmp/fake_dir/childdir/sym1.md'); const sym2 = new SymbolicLink('/tmp/fake_dir/childdir/sym2'); expect(sym1.readlinkSync()).to.eql('/tmp/fake_dir/example_file.md'); return sym2.readlink().then((pathName) => { expect(pathName).to.eql('/tmp/fake_dir/non_existent.md'); }); }); it('.isBroken() && .isBrokenSync()', () => { const sym1 = new SymbolicLink('/tmp/fake_dir/childdir/sym1.md'); const sym2 = new SymbolicLink('/tmp/fake_dir/childdir/sym2'); expect(sym1.isBrokenSync()).to.eql(false); return sym2.isBroken().then((ret) => { expect(ret).to.eql(true); }); }); }); });