├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── .travis.yml
├── LICENSE
├── README.md
├── _config.yml
├── docs
└── index.html
├── draxt-logo.jpg
├── draxt-logo.svg
├── package-lock.json
├── 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
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | indent_size = 2
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [*.md]
11 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | commonjs: true,
4 | es6: true,
5 | node: true,
6 | },
7 | globals: {
8 | console: true,
9 | },
10 | extends: 'eslint:recommended',
11 | rules: {
12 | indent: ['error', 4],
13 | 'no-console': 0,
14 | 'linebreak-style': ['error', 'unix'],
15 | quotes: [1, 'single'],
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 | .DS_Store
64 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "printWidth": 100,
4 | "tabWidth": 4,
5 | "semi": true,
6 | "singleQuote": true
7 | }
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - '6'
5 | - '8'
6 |
7 | before_install:
8 | - if [[ `npm -v` == 2.* ]]; then npm install --global npm@3; fi
9 |
10 | after_success: npm run coverage
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Ram Hejazi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | `draxt` is a utility module for selecting and manipulating filesystem objects in a Node.js environment.
10 | 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.
11 |
12 | **Example directory structure:**
13 |
14 | ```
15 | /app/
16 | ├── controllers/
17 | │ └── index.js
18 | ├── public/
19 | │ ├── script.js
20 | │ └── style.css
21 | └── views/
22 | └── index.html
23 | ```
24 |
25 | ```js
26 | const $ = require('draxt');
27 |
28 | (async () => {
29 | // Select `/app` directory content and create a new `draxt` collection.
30 | const $app = await $('/app/**');
31 | $app
32 | // Let's filter js files:
33 | .filter((node) => node.extension === 'js')
34 | // Now we have a new `draxt` collection with 2 nodes.
35 | .forEach(async (node, index, allNodes) => {
36 | // `node` is instance of `File` class. Because it's a file!
37 | console.log(node.pathName);
38 | // → '/app/controllers/index.js' for the first node!
39 |
40 | console.log(node instanceof $.File); // → `true`
41 |
42 | // Let's get contents of the node. `file.read` returns a promise object.
43 | const content = await node.read('utf8');
44 |
45 | // Let's use some synchronous methods!
46 | node.appendSync('\na new line!')
47 | .chmodSync('765')
48 | // move the file into another directory!
49 | .appendToSync('/tmp'); // or `.moveToSync('/tmp')`
50 |
51 | console.log(node.pathName);
52 | // → '/hell/index.js' for the first node in the list!
53 |
54 | // get the parent directory of the node.
55 | // returns a `Directory` instance with the pathName of '/tmp'!
56 | const parentNode = node.parentSync(); // or `await node.parent()`
57 |
58 | // is the directory empty?
59 | console.log(parentNode.isEmptySync()); // → `false`
60 | });
61 | })();
62 | ```
63 |
64 | **Key notes**:
65 |
66 | - `draxt` has only 2 dependencies: [`glob`] and [`fs-extra`] modules.
67 | - `draxt` uses `glob` patterns to select filesystem objects.
68 | - Each item in a `draxt` collection is an instance of a [`File`], [`Directory`], or [`SymbolicLink`] class, which is a subclass of [`Node`].
69 | - Every asynchronous method has a synchronous version. E.g., [`node.siblingsSync()`] for [`node.siblings()`].
70 | - `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.
71 |
72 | ```js
73 | const draxt = require('draxt');
74 | // Add a method (`images`) for filtering image files.
75 | draxt.fn.images = function() {
76 | const imgExtensions = ['jpeg', 'jpg', 'png', 'git', ...];
77 | return this.filter(node => {
78 | return node.isFile() && imgExtensions.indexOf(node.extension) > -1;
79 | });
80 | }
81 | ```
82 |
83 | ## Install
84 |
85 | Installing via [npm]:
86 |
87 | ```bash
88 | $ npm i draxt
89 | ```
90 |
91 | Via [yarn]:
92 |
93 | ```bash
94 | $ yarn add draxt
95 | ```
96 |
97 | ## Docs
98 |
99 | - [`draxt` APIs][draxt-doc]
100 | - Interfaces
101 | - [`Node`]
102 | - [`File`]
103 | - [`Directory`]
104 | - [`SymbolicLink`]
105 |
106 | ## Test
107 |
108 | In the past, mock-fs was used for mocking test file system, but since the package is not compatible with
109 | newer versions of node.js, now regular linux cmds like `mkdir` and `echo` are used for creating test files and
110 | folders. The test fs structure are created in `/tmp` directory. That being said, for now, tests only work on Linux!
111 |
112 | ```bash
113 | $ npm run test
114 | ```
115 |
116 | ## License
117 |
118 | [Licensed under MIT.][license]
119 |
120 | [repo]: https://github.com/ramhejazi/draxt
121 | [logo]: draxt-logo.jpg
122 | [license]: https://github.com/ramhejazi/draxt/blob/master/LICENSE
123 | [license-badge]: https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square
124 | [coverall]: https://coveralls.io/github/ramhejazi/draxt
125 | [coverall-badge]: https://img.shields.io/coveralls/github/ramhejazi/draxt.svg?style=flat-square
126 | [npm-link]: https://www.npmjs.com/package/draxt
127 | [npm-badge]: https://img.shields.io/npm/v/draxt.svg?style=flat-square
128 | [travis-link]: https://travis-ci.org/ramhejazi/draxt
129 | [travis-badge]: https://img.shields.io/travis/ramhejazi/draxt.svg?style=flat-square
130 | [deps-status-link]: https://david-dm.org/ramhejazi/draxt
131 | [deps-status-badge]: https://david-dm.org/ramhejazi/draxt.svg?style=flat-square
132 | [npm]: https://docs.npmjs.com/getting-started/what-is-npm
133 | [yarn]: https://yarnpkg.com/en/
134 | [glob]: https://en.wikipedia.org/wiki/Glob_(programming)
135 | [`fs`]: https://nodejs.org/api/fs.html
136 | [`fs-extra`]: https://github.com/jprichardson/node-fs-extra
137 | [`glob`]: https://github.com/isaacs/node-glob
138 | [Pahlavi language]: https://en.wikipedia.org/wiki/Middle_Persian
139 | [draxt-doc]: https://ramhejazi.github.io/draxt#draxt
140 | [`Node`]: https://ramhejazi.github.io/draxt#interfaces-node
141 | [`File`]: https://ramhejazi.github.io/draxt#interfaces-file
142 | [`Directory`]: https://ramhejazi.github.io/draxt#interfaces-directory
143 | [`SymbolicLink`]: https://ramhejazi.github.io/draxt#interfaces-symboliclink
144 | [`draxt.extend`]: https://ramhejazi.github.io/draxt#draxt-extend
145 | [`node.siblingsSync()`]: https://ramhejazi.github.io/draxt#node-siblings
146 | [`node.siblings()`]: https://ramhejazi.github.io/draxt#node-siblings
147 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | theme: jekyll-theme-minimal
--------------------------------------------------------------------------------
/draxt-logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ramhejazi/draxt/be2b0edd848ba338542f487812a22037b018dd3a/draxt-logo.jpg
--------------------------------------------------------------------------------
/draxt-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
112 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "draxt",
3 | "version": "1.3.0",
4 | "description": "jQuery/NodeList-like module for file system (nodejs)",
5 | "author": "Ram Hejazi",
6 | "main": "src/draxt.js",
7 | "scripts": {
8 | "test": "nyc --reporter=text --reporter=html ./node_modules/mocha/bin/mocha test/*"
9 | },
10 | "homepage": "https://github.com/ramhejazi/draxt",
11 | "license": "MIT",
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/ramhejazi/draxt.git"
15 | },
16 | "keywords": [
17 | "fs",
18 | "file",
19 | "file system",
20 | "directory",
21 | "folder",
22 | "read",
23 | "write",
24 | "move",
25 | "mv",
26 | "cp",
27 | "copy",
28 | "jquery",
29 | "node",
30 | "chainable",
31 | "collection",
32 | "utility",
33 | "plugin"
34 | ],
35 | "bugs": {
36 | "url": "https://github.com/ramhejazi/draxt/issues"
37 | },
38 | "engines": {
39 | "node": "20 || >=22"
40 | },
41 | "dependencies": {
42 | "fs-extra": "^11.2.0",
43 | "glob": "^11.0.0"
44 | },
45 | "devDependencies": {
46 | "chai": "^4.3.10",
47 | "mocha": "^9.2.2",
48 | "nyc": "^15.1.0"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/draxt.js:
--------------------------------------------------------------------------------
1 | /*eslint no-irregular-whitespace: ["error", { "skipComments": true }]*/
2 |
3 | const {Node} = require('./interfaces');
4 | const assign = Object.assign;
5 | const {getType} = require('./util');
6 | const define = Object.defineProperty;
7 | const lengthProps = {
8 | get() {
9 | return this.items.length;
10 | },
11 | enumerable: true,
12 | configurable: true,
13 | };
14 |
15 | /**
16 | * `draxt` function is the main method of `draxt` package. The function
17 | * can be called with `new` as a constructor function (not recommended) and
18 | * without `new` as a factory function. `draxt` uses promisified `glob` package as it's
19 | * selector engine. All query results of `glob` package are converted into one of `Node`'s sub-class
20 | * (`File`, `Directory` or `SymbolicLink`) instances by analyzing pathNames' `fs.Stats` object.
21 | * The returned value of `draxt` is a `draxt` collection
22 | * which to some extent works like jQuery collections but unlike jQuery collections it's not an array-like object.
23 | * The collection items are stored as an array property (`.items`).
24 | * @prop {array} items Items of the collection.
25 | * @prop {number} length Lenght of collection's items.
26 | * @param {string|array|node|draxt} [pattern] `pattern` parameter can contain several values:
27 | * - `string` which is passed to `glob` package as `glob` pattern.
28 | * In this case `draxt` returns a `promise` object representing a `draxt` collection/instance.
29 | * - A `Node` or one it's sub-classes (`File`, `Directory` or `SymbolicLink`) instance.
30 | * In this case a `draxt` collection containing the passed `node` is returned.
31 | * - An array of `node` instances.
32 | * - A `draxt` collection to clone (shallow).
33 | * - `undefined` which returns an empty `draxt` collection.
34 | * @param {object|string} [options] Options for `glob` package. The `options` parameter
35 | * can also be a string representing a pathName which will be used as context for the query,
36 | * similar to jQuery's `$(selector, context)` syntax.
37 | * @returns {promise|draxt}
38 | * @example
39 | * // /app
40 | * // ├── controllers/
41 | * // │ └── index.js
42 | * // ├── public/
43 | * // │ ├── script.js
44 | * // │ └── style.css
45 | * // └── views/
46 | * // └── index.njk/
47 | *
48 | * const draxt = require('draxt');
49 | * const Directory = draxt.Directory;
50 | * // Initialization with a glob pattern/selector.
51 | * // Which returns a `promise` object!
52 | * draxt('/app/**').then(draxtCollection => {
53 | * // draxtCollection: →
54 | * Draxt {
55 | * length: [Getter],
56 | * items: [
57 | * Directory { pathName: '/app', ... }
58 | * Directory { pathName: '/app/controllers', ... }
59 | * File { pathName: '/app/controllers/index.js', ... }
60 | * ...
61 | * ]
62 | * });
63 | * const anEmpyDraxtCollection = draxt();
64 | * // A `draxt` collection with length of `1`
65 | * // Which has a manually created `Directory` instance.
66 | * const draxtCollection = draxt(new Directory('/app'));
67 | */
68 | function Draxt(pattern, options = {}) {
69 | // If `this` is not a Draxt instance, create a Draxt instance.
70 | if (this instanceof Draxt !== true) {
71 | return new Draxt(...arguments);
72 | }
73 | // Define dynamic `length` property.
74 | define(this, 'length', lengthProps);
75 | // `items` refers to collection's node.
76 | this.items = [];
77 |
78 | if (getType(pattern) === 'undefined') {
79 | return this;
80 | }
81 |
82 | if (pattern instanceof Node || Array.isArray(pattern)) {
83 | this.add(pattern);
84 | return this;
85 | }
86 |
87 | if (pattern instanceof Draxt) {
88 | this.items = pattern.get().slice();
89 | return this;
90 | }
91 |
92 | return Node.query(pattern, options).then((items) => {
93 | return this.add(items);
94 | });
95 | }
96 |
97 | Node.Draxt = Draxt;
98 | Draxt.Node = Node;
99 | Draxt.File = Node.File;
100 | Draxt.Directory = Node.Directory;
101 | Draxt.SymbolicLink = Node.SymbolicLink;
102 | Draxt.fn = Draxt.prototype;
103 |
104 | /**
105 | * Synchronously query the file system by using `glob` package and
106 | * return a new `draxt` collection.
107 | * @param {string} pattern Glob pattern.
108 | * @param {object} [options] Options for `glob` package.
109 | * @returns {draxt} An instance of `draxt`, a.k.a. a _draxt collection_.
110 | */
111 | Draxt.sync = function () {
112 | const items = Node.querySync(...arguments);
113 | return new Draxt(items);
114 | };
115 |
116 | /**
117 | * Extend `draxt` by adding methods to it's `prototype`. Basically works like `jQuery.fn.extend`.
118 | * @param {object} methods
119 | */
120 | Draxt.extend = function (methods) {
121 | assign(Draxt.fn, methods);
122 | };
123 |
124 | /**
125 | * Add node(s) to current `draxt` collection.
126 | * Pre-exising nodes will not be added to the collection.
127 | * @param {node|array|draxt} items Instance of Node or array of nodes or a `draxt` collection.
128 | * @returns {draxt} An instance of `draxt`.
129 | * @example
130 | * const draxtCollection = draxt();
131 | * draxtCollection.add(new Node('/pathName'));
132 | * draxtCollection.length // → 1
133 | */
134 | Draxt.prototype.add = function (items) {
135 | if (items instanceof Draxt) {
136 | items = items.get();
137 | }
138 | const nodes = Array.isArray(items) ? items.slice() : [items];
139 | nodes.forEach((node) => {
140 | if (!(node instanceof Node)) {
141 | throw new Error(
142 | 'Invalid value for `items` parameter. `draxt` collection can only have Node instances. ' +
143 | 'The given value is a(n) ' +
144 | getType(node) +
145 | '!'
146 | );
147 | }
148 | const has = this.has(node);
149 | if (!has) this.items.push(node);
150 | });
151 | return this;
152 | };
153 |
154 | /**
155 | * Get one or all nodes from the `draxt` collection.
156 | * With an `index` specified, `.get(index)` retrieves a single node otherwise
157 | * retrives all the nodes (if any).
158 | * @param {number} [index] - Index of node in items collection.
159 | * @returns {array|node|undefined}
160 | */
161 | Draxt.prototype.get = function (index) {
162 | if (typeof index === 'undefined') {
163 | return this.items;
164 | }
165 | return this.items[index];
166 | };
167 |
168 | /**
169 | * Get the first node (if any) from the collection.
170 | * @returns {node|undefined}
171 | */
172 | Draxt.prototype.first = function () {
173 | return this.items[0];
174 | };
175 |
176 | /**
177 | * Get the last node (if any) from the collection.
178 | * @returns {node|undefined}
179 | */
180 | Draxt.prototype.last = function () {
181 | return this.items[this.items.length - 1];
182 | };
183 |
184 | /**
185 | * Does the `draxt` collection has a node with specified pathName?
186 | * Note that `.has()` method doesn't work by checking if collection has a specific
187 | * `Node` instance. It checks whether collection has a node with the specified
188 | * pathName.
189 | * @param {string|node} item A `Node` instance or a `pathName`
190 | * @returns {boolean}
191 | * @example
192 | * // example fs structure
193 | * // └── app
194 | * // ├── public
195 | * // │ ├── script.js
196 | * // │ └── style.css
197 | * const collection = draxtCollection.sync('/app/**');
198 | * draxtCollection.has('/app/public/script.js') // → true
199 | * draxtCollection.has(new Node('/app/public/script.js')) // → true
200 | */
201 | Draxt.prototype.has = function (item) {
202 | const pathName = item instanceof Node ? item.pathName : item;
203 | const found = this.items.some((node) => node.pathName === pathName);
204 | return found;
205 | };
206 |
207 | /**
208 | * Slice the collection and return a new `Draxt` collection.
209 | * Uses `Array.prototype.slice`.
210 | * @param {integer} [begin] Zero-based index at which to begin extraction.
211 | * @param {integer} [end] Zero-based index before which to end extraction. `slice` extracts up to but not including `end`.
212 | * @returns {draxt} A new `draxt` collection which contains sliced items.
213 | */
214 | Draxt.prototype.slice = function () {
215 | let sItems = this.items.slice(...arguments);
216 | return new Draxt(sItems);
217 | };
218 |
219 | /**
220 | * Filter the collection's nodes and return a new `draxt` collection.
221 | * Uses `Array.prototype.filter`.
222 | * @param {function} callback A function to execute for each node.
223 | * @param {any} [thisArg] Value to use as `this` (i.e the reference Object) when executing callback.
224 | * @returns {draxt} A new `draxt` collection which contains filtered items.
225 | */
226 | Draxt.prototype.filter = function () {
227 | let fItems = this.items.filter(...arguments);
228 | return new Draxt(fItems);
229 | };
230 |
231 | /**
232 | * Iterate over the `draxt` collection and execute a function for each
233 | * node. Uses `Array.prototype.forEach`.
234 | * @chainable
235 | * @param {function} callback A function to execute for each node.
236 | * @param {any} [thisArg] Value to use as `this` (i.e the reference Object) when executing callback.
237 | * @returns {draxt} The current collection.
238 | */
239 | Draxt.prototype.forEach = function () {
240 | this.items.forEach(...arguments);
241 | return this;
242 | };
243 |
244 | /**
245 | * Alias for `draxt.forEach`.
246 | */
247 | Draxt.prototype.each = Draxt.prototype.forEach;
248 |
249 | /**
250 | * Create an array with the results of calling a provided function on every
251 | * node in the `draxt` collection.
252 | * Uses `Array.prototype.map`.
253 | * @param {function} callback A function to execute for each node.
254 | * @param {any} [thisArg] Value to use as `this` (i.e the reference Object) when executing callback.
255 | * @returns {array}
256 | */
257 | Draxt.prototype.map = function () {
258 | return this.items.map(...arguments);
259 | };
260 |
261 | /**
262 | * Asynchronous version of `draxt.map`. The results of mapped array is passed
263 | * to `Promise.all` method.
264 | * @param {function} fn A function to execute for each node.
265 | * @param {any} [thisArg] Value to use as `this` (i.e the reference Object) when executing callback.
266 | * @returns {promise}
267 | */
268 | Draxt.prototype.mapAsync = function () {
269 | return Promise.all(this.items.map(...arguments));
270 | };
271 |
272 | /**
273 | * Test whether at least one node in the collection passes the test implemented
274 | * by the provided function.
275 | * Uses `Array.prototype.some`.
276 | * @param {function} fn A function to execute for each node.
277 | * @returns {boolean}
278 | */
279 | Draxt.prototype.some = function () {
280 | return this.items.some(...arguments);
281 | };
282 |
283 | /**
284 | * Sort the nodes of collection _in place_ and return the `draxt` collection.
285 | * Uses `Array.prototype.sort`.
286 | * @param {function} callback A function that defines the sort order.
287 | * @returns {draxt} Note that the collection is sorted _in place_, and no copy is made.
288 | */
289 | Draxt.prototype.sort = function (fn) {
290 | this.items.sort(fn);
291 | return this;
292 | };
293 |
294 | /**
295 | * Reverse the collection's nodes _in place_.
296 | * The first array element becomes the last, and the last array element becomes the first.
297 | * @returns {draxt}
298 | */
299 | Draxt.prototype.reverse = function () {
300 | this.items.reverse();
301 | return this;
302 | };
303 |
304 | /**
305 | * Filter directory nodes (instances of `Directory` class) and return a new
306 | * `draxt` collection.
307 | * @returns {draxt}
308 | */
309 | Draxt.prototype.directories = function () {
310 | return this.filter((el) => {
311 | return el.isDirectory();
312 | });
313 | };
314 |
315 | /**
316 | * Filter file nodes (instances of `File` class) and return a new `draxt` collection.
317 | * @returns {draxt}
318 | */
319 | Draxt.prototype.files = function () {
320 | return this.filter((el) => {
321 | return el.isFile();
322 | });
323 | };
324 |
325 | /**
326 | * Filter symbolic link nodes (instances of `SymbolicLink` class) and return a new `draxt` collection.
327 | * @returns {draxt}
328 | */
329 | Draxt.prototype.symlinks = function () {
330 | return this.filter((el) => {
331 | return el.isSymbolicLink();
332 | });
333 | };
334 |
335 | /**
336 | * Empty the `draxt` collection. This method doesn't affect file system!
337 | * @returns {draxt}
338 | */
339 | Draxt.prototype.empty = function () {
340 | this.items = [];
341 | return this;
342 | };
343 |
344 | /**
345 | * Remove node(s) from the current `draxt` collection by using `.pathName`s as the criterion.
346 | * @chainable
347 | * @param {draxt|node|array} node Accepts various paramters.
348 | * @return {draxt}
349 | */
350 | Draxt.prototype.drop = function (node) {
351 | let nodes;
352 | if (node instanceof Node) {
353 | nodes = [node];
354 | } else if (node instanceof Draxt) {
355 | nodes = node.get();
356 | } else if (getType(node) === 'string') {
357 | nodes = [node];
358 | } else if (Array.isArray(node)) {
359 | nodes = node;
360 | } else {
361 | throw new Error('Invalid paramter passed to `.drop()` method');
362 | }
363 | const pathNames = nodes.map((item) => {
364 | if (typeof item === 'string') {
365 | return item;
366 | }
367 | return item.pathName;
368 | });
369 | this.items = this.items.filter((node) => {
370 | return pathNames.indexOf(node.pathName) === -1;
371 | });
372 | return this;
373 | };
374 |
375 | module.exports = Draxt;
376 |
--------------------------------------------------------------------------------
/src/interfaces/Directory.js:
--------------------------------------------------------------------------------
1 | const Node = require('./Node'),
2 | {getType} = require('../util');
3 |
4 | const nodeProps = {
5 | nodeName: {value: 'Directory', writable: false, configurable: false, enumerable: true},
6 | NODE_TYPE: {value: 1, writable: false, configurable: false, enumerable: true},
7 | };
8 |
9 | /**
10 | * `Directory` class which extends the `Node` class is an interface representing pathNames
11 | * that their `fs.Stats`'s `.isDirectory()` method returns `true`.
12 | * @prop {string} nodeName Name of the node: `'Directory'`.
13 | * @prop {number} NODE_TYPE Code number for the node: `1`.
14 | */
15 | class Directory extends Node {
16 | /**
17 | * Construct a new node
18 | * @param {string} pathName Absolute pathName of the node
19 | * @param {object} [stats] Instance of `fs.Stats` for the node
20 | */
21 | constructor(pathName, stats) {
22 | super(pathName, stats);
23 | Object.defineProperties(this, nodeProps);
24 | }
25 |
26 | /**
27 | * Append/move passed directories into this directory node.
28 | * Uses `node.moveTo` which uses `fs-extra.move`.
29 | * @param {draxt|node|string|array} nodes Accepts various parameters:
30 | * - `draxt` collection.
31 | * - a node instance.
32 | * - pathNames of a file or directory.
33 | * - array of node instances.
34 | * - array of absolute pathNames of files/directories.
35 | * - a mixed array of nodePaths and absolute pathNames of files/directories.
36 | * @param {object} [options] Options for `fs-extra.move`.
37 | * @returns {promise} Promise representing array of moved nodes.
38 | */
39 | append(nodes, options) {
40 | nodes = this.constructor.__normalizeAppendNodes(nodes);
41 | const mvPromices = nodes.map((node) => {
42 | node = node instanceof Node ? node : new Node(node);
43 | return node.moveTo(this, options);
44 | });
45 | return Promise.all(mvPromices).then(() => this);
46 | }
47 |
48 | /**
49 | * Synchronous version of `directory.append`.
50 | * @chainable
51 | * @param {draxt|array|string} nodes
52 | * @param {object} [options] Options for `fs-extra.move`.
53 | * @returns {node} The `directory` node.
54 | */
55 | appendSync(nodes, options) {
56 | nodes = this.constructor.__normalizeAppendNodes(nodes);
57 | nodes.forEach((node) => {
58 | node = node instanceof Node ? node : new Node(node);
59 | return node.moveToSync(this, options);
60 | });
61 | return this;
62 | }
63 |
64 | /**
65 | * Asynchronously select children of the directory by using `glob` package and
66 | * return a `draxt` collection.
67 | * @param {string} [pattern='*'] Glob pattern relative to the directory. The pattern
68 | * is used against `baseName` of directory child nodes.
69 | * @param {object} [options] Options for `glob` package.
70 | * @returns {promise}
71 | */
72 | children() {
73 | const {rawQuery, toNodes, Draxt} = Node;
74 | let [options, filterFn] = this.__normalizeRelativeGlobOptions(...arguments);
75 | return rawQuery('*', options).then((items) => {
76 | if (filterFn) {
77 | items = items.filter(filterFn);
78 | }
79 | return toNodes(items).then(Draxt);
80 | });
81 | }
82 |
83 | /**
84 | * Synchronous version of `directory.children`.
85 | * @param {string} [selector] Optional selector
86 | * @param {object} [options] Options for glob package
87 | * @returns {draxt}
88 | */
89 | childrenSync() {
90 | const {rawQuerySync, toNodesSync, Draxt} = Node;
91 | let [_options, filterFn] = this.__normalizeRelativeGlobOptions(...arguments);
92 | let items = rawQuerySync('*', _options);
93 | if (filterFn) {
94 | items = items.filter(filterFn);
95 | }
96 | return new Draxt(toNodesSync(items));
97 | }
98 |
99 | /**
100 | * Ensures that a directory is empty. Deletes directory contents if the directory
101 | * is not empty. If the directory does not exist, it is created.
102 | * The directory itself is not deleted.
103 | * Wrapper for `fs-extra.emptyDir`.
104 | * @returns {promise}
105 | */
106 | empty() {
107 | return this.fs.emptyDir(this.pathName);
108 | }
109 |
110 | /**
111 | * Synchronous version of `directory.empty` method.
112 | * Wrapper for `fs-extra.emptyDirSync`.
113 | * @returns {node}
114 | */
115 | emptySync() {
116 | this.fs.emptyDirSync(this.pathName);
117 | return this;
118 | }
119 |
120 | /**
121 | * Asynchronously ensure directory exists.
122 | * Wrapper for `fs-extra.ensureDir`.
123 | *@returns {promise}
124 | */
125 | ensure() {
126 | return this.fs.ensureDir(this.pathName);
127 | }
128 |
129 | /**
130 | * Synchronously ensure directory exists.
131 | * Wrapper for `fs-extra.ensureDirSync`.
132 | * @returns {node}
133 | */
134 | ensureSync() {
135 | this.fs.ensureDirSync(this.pathName);
136 | return this;
137 | }
138 |
139 | /**
140 | * Is directory empty?
141 | * @returns {promise}
142 | */
143 | isEmpty() {
144 | return this.readdir().then((files) => {
145 | return files.length === 0;
146 | });
147 | }
148 |
149 | /**
150 | * Synchronous version of `directory.isEmpty` method.
151 | * @returns {boolean}
152 | */
153 | isEmptySync() {
154 | return this.readdirSync().length === 0;
155 | }
156 |
157 | /**
158 | * Find matching decendants of the directory node.
159 | * Uses `glob` package.
160 | * @param {string} pattern Glob pattern.
161 | * @param {object} options Options for `glob` package.
162 | * @returns {Promise}
163 | */
164 | find(selector, options) {
165 | const {Draxt} = Node;
166 | options = Node.__normalizeGlobOptions(options);
167 | options.cwd = this.pathName;
168 | return Node.query(selector, options).then((items) => {
169 | return new Draxt(items);
170 | });
171 | }
172 |
173 | /**
174 | * Synchronous version of `directory.find` method.
175 | * @param {string} selector
176 | * @param {object} options Options for `glob` package.
177 | * @returns {draxt}
178 | */
179 | findSync(selector, options) {
180 | const {Draxt} = Node;
181 | options = Node.__normalizeGlobOptions(options);
182 | options.cwd = this.pathName;
183 | const items = Node.querySync(selector, options);
184 | return new Draxt(items);
185 | }
186 |
187 | /**
188 | * Wrapper for promisified `fs.readdir`.
189 | * @param {string|object} options
190 | * @returns {promise}
191 | */
192 | readdir() {
193 | return this.fs.readdir(this.pathName, ...arguments);
194 | }
195 |
196 | /**
197 | * Wrapper for `fs.readdirSync`.
198 | * @param {string|object} options
199 | * @returns {array}
200 | */
201 | readdirSync() {
202 | return this.fs.readdirSync(this.pathName, ...arguments);
203 | }
204 |
205 | /**
206 | * Alias for `directory.readdir` method.
207 | * @param {string|object} options
208 | * @returns {promise}
209 | */
210 | read() {
211 | return this.readdir(...arguments);
212 | }
213 |
214 | /**
215 | * Alias for `directory.readdirSync` method.
216 | * @param {string|object} options
217 | * @returns {array}
218 | */
219 | readSync() {
220 | return this.readdirSync(...arguments);
221 | }
222 |
223 | /**
224 | * Wrapper for promisified `fs.rmdir`.
225 | * Deletes the directory, which must be empty.
226 | * @returns {promise}
227 | */
228 | rmdir() {
229 | return this.fs.rmdir(this.pathName);
230 | }
231 |
232 | /**
233 | * Wrapper for `fs.rmdirSync`.
234 | * Deletes the directory, which must be empty.
235 | * @chainable
236 | * @returns {node}
237 | */
238 | rmdirSync() {
239 | this.fs.rmdirSync(this.pathName);
240 | return this;
241 | }
242 |
243 | static __normalizeAppendNodes(nodes) {
244 | const {Draxt} = Node;
245 | if (nodes instanceof Draxt) {
246 | nodes = nodes.get();
247 | } else if (nodes instanceof Node) {
248 | nodes = [nodes];
249 | } else if (getType(nodes) === 'string') {
250 | nodes = [nodes];
251 | } else if (getType(nodes) !== 'array') {
252 | throw new Error(`Invalid parameter for \`nodes\` parameter: ${nodes}`);
253 | }
254 | return nodes;
255 | }
256 | }
257 |
258 | module.exports = Node.Directory = Directory;
259 |
--------------------------------------------------------------------------------
/src/interfaces/File.js:
--------------------------------------------------------------------------------
1 | const Node = require('./Node');
2 |
3 | const nodeProps = {
4 | nodeName: {value: 'File', writable: false, configurable: false, enumerable: true},
5 | NODE_TYPE: {value: 2, writable: false, configurable: false},
6 | };
7 |
8 | /**
9 | * `File` class which extends the `Node` class is an interface representing pathNames
10 | * that their `fs.Stats`'s `.isFile()` method returns `true`.
11 | * @prop {string} nodeName Name of the node: `'File'`.
12 | * @prop {number} NODE_TYPE Code number for the node: `2`.
13 | */
14 | class File extends Node {
15 | /**
16 | * Construct a new file.
17 | * @param {string} pathName Absolute pathName of the node
18 | * @param {object} [stats] Instance of `fs.Stats` for the node
19 | */
20 | constructor(pathName, stats) {
21 | super(pathName, stats);
22 | Object.defineProperties(this, nodeProps);
23 | }
24 |
25 | /**
26 | * Ensure the file node exists on file system.
27 | * Wrapper for `fs-extra.ensureFile`.
28 | * @returns {promise}
29 | */
30 | ensure() {
31 | return this.fs.ensureFile(this.pathName);
32 | }
33 |
34 | /**
35 | * Ensure the file node exists on file system synchronously.
36 | * Wrapper for `fs.ensureFileSync`.
37 | * @returns {node}
38 | */
39 | ensureSync() {
40 | this.fs.ensureFileSync(this.pathName);
41 | return this;
42 | }
43 |
44 | /**
45 | * Asynchronously append data to a file, creating the file if it does not yet exist. `data` can be a string or a Buffer.
46 | * Wrapper for `fs.appendFile`.
47 | * @returns {promise}
48 | */
49 | append() {
50 | return this.fs.appendFile(this.pathName, ...arguments);
51 | }
52 |
53 | /**
54 | * Wrapper for `fs.appendFileSync`.
55 | * @returns {node}
56 | */
57 | appendSync() {
58 | this.fs.appendFileSync(this.pathName, ...arguments);
59 | return this;
60 | }
61 |
62 | /**
63 | * Promisified wrapper for `fs.readFile`.
64 | * @returns {promise} Promise object representing contents of the file.
65 | */
66 | read() {
67 | return this.fs.readFile(this.pathName, ...arguments);
68 | }
69 |
70 | /**
71 | * Wrapper for `fs.readFileSync`.
72 | * @returns {any}
73 | */
74 | readSync() {
75 | return this.fs.readFileSync(this.pathName, ...arguments);
76 | }
77 |
78 | /**
79 | * Promisified wrapper for `fs.truncate`
80 | * @returns {promise}
81 | */
82 | truncate() {
83 | return this.fs.truncate(this.pathName, ...arguments);
84 | }
85 |
86 | /**
87 | * Wrapper for `fs.truncateSync`.
88 | * @returns {node}
89 | */
90 | truncateSync() {
91 | this.fs.truncateSync(this.pathName, ...arguments);
92 | return this;
93 | }
94 |
95 | /**
96 | * Promisified `fs.writeFile`
97 | * @returns {promise}
98 | */
99 | write() {
100 | return this.fs.writeFile(this.pathName, ...arguments);
101 | }
102 |
103 | /**
104 | * Wrapper for `fs.writeFileSync`.
105 | * @chainable
106 | * @returns {node}
107 | */
108 | writeSync() {
109 | this.fs.writeFileSync(this.pathName, ...arguments);
110 | return this;
111 | }
112 | }
113 |
114 | module.exports = Node.File = File;
115 |
--------------------------------------------------------------------------------
/src/interfaces/Node.js:
--------------------------------------------------------------------------------
1 | const path = require('path'),
2 | {glob, globSync} = require('glob'),
3 | fs = require('fs-extra'),
4 | minimatch = require('minimatch'),
5 | {getType} = require('../util'),
6 | assign = Object.assign;
7 | // Default properies which are used for detecting node type.
8 | const defaultNodeProps = {
9 | nodeName: {value: 'Node', writable: false, configurable: true, enumerable: true},
10 | NODE_TYPE: {value: 0, writable: false, configurable: true, enumerable: true},
11 | };
12 |
13 | /**
14 | * Node class is an interface that other classes representing
15 | * file system's nodes (like `File`, `Directory`, `SymbolicLink`, ...) inherit.
16 | * @prop {string} [nodeName='Node'] Default: `'Node'`. The name of constructor of the current node.
17 | * @prop {number} [NODE_TYPE=0] Default: `0`. Code number for the node.
18 | * @prop {string} pathName Absolute pathName of the node. Example: `'/app/readme.md'`.
19 | * @prop {string} baseName baseName of the node. Example: `'readme.md'`.
20 | * @prop {string} name Name of the node without the possible extension. Example `'readme'`.
21 | * @prop {string|undefined} extension Extension of the node without `.`. Example: `'js'`.
22 | * @prop {string} parentPath pathName of the parent directory of the node.
23 | * @prop {string} rootPath Root path of the file system.
24 | * @prop {object|undefined} _stats Cached instance of `fs.Stats` for the node.
25 | * @prop {object} fs Refers to `fs-extra` package.
26 | * @prop {object} glob Refers to `glob` package.
27 | */
28 | class Node {
29 | /**
30 | * Construct a new node
31 | * @param {string} pathName Absolute pathName of the node
32 | * @param {object} [stats] Instance of `fs.Stats` for the node
33 | */
34 | constructor(pathName, stats) {
35 | this._stats = stats;
36 | Object.defineProperties(this, defaultNodeProps);
37 | this._setPathParams(pathName);
38 | }
39 |
40 | /**
41 | * Parse the node's pathName by using `path.parse()` method
42 | * and set the corresponsing node's properties.
43 | * @returns {undefined}
44 | */
45 | _setPathParams(nodePathname) {
46 | const {root, dir, name, ext, base} = path.parse(nodePathname);
47 | this.pathName = nodePathname;
48 | this.baseName = base;
49 | this.name = name;
50 | this.extension = ext.slice(1);
51 | this.rootPath = root;
52 | this.parentPath = dir;
53 | }
54 |
55 | /**
56 | * Get the node's pathName.
57 | * @returns {string}
58 | */
59 | getPathName() {
60 | return this.pathName;
61 | }
62 |
63 | /**
64 | * Get the node's baseName.
65 | * @returns {string}
66 | */
67 | getBaseName() {
68 | return this.baseName;
69 | }
70 |
71 | /**
72 | * Get the node's extension.
73 | * @returns {string}
74 | */
75 | getExtension() {
76 | return this.extension;
77 | }
78 |
79 | /**
80 | * Get name of the node.
81 | * For `File` nodes the `name` property is the name of file without possible extension.
82 | * @returns {string}
83 | */
84 | getName() {
85 | return this.name;
86 | }
87 |
88 | /**
89 | * Get the node's parent directory pathName.
90 | * @returns {string}
91 | */
92 | getParentPath() {
93 | return this.parentPath;
94 | }
95 |
96 | /**
97 | * Get cached `fs.Stats` instance for the node. Returns `undefined` when there
98 | * is no cached stats for the node. This happens only when the node is created
99 | * manually by user without passing a stats object.
100 | * @returns {object|undefined}
101 | */
102 | getCachedStats() {
103 | return this._stats;
104 | }
105 |
106 | /**
107 | * Get a stat property's value from cached `fs.Stats` for the node.
108 | * The method returns `undefined` when there is no cached stats.
109 | * @param {string} propName
110 | * @example
111 | * // Get `blksize` property of fs.Stats instance cached for the node.
112 | * const node_ctime = node.getStatProp('blksize');
113 | * @returns {any}
114 | */
115 | getStatProp(propName) {
116 | return (this._stats || {})[propName];
117 | }
118 |
119 | /**
120 | * Get "access time" of the node. Returns `atime` property of the cached stats.
121 | * @returns {date}
122 | */
123 | getAccessTime() {
124 | return this.getStatProp('atime');
125 | }
126 |
127 | /**
128 | * Get "modified time" of the node. Returns `mtime` property of the cached stats.
129 | * @returns {date}
130 | */
131 | getModifiedTime() {
132 | return this.getStatProp('mtime');
133 | }
134 |
135 | /**
136 | * Get "birthday time" of the node. Returns `birthtime` property of the cached stats.
137 | * @returns {date}
138 | */
139 | getBirthTime() {
140 | return this.getStatProp('birthtime');
141 | }
142 |
143 | /**
144 | * Get "change time" of the node. Returns `ctime` property of the cached stats.
145 | * @returns {date}
146 | */
147 | getChangeTime() {
148 | return this.getStatProp('ctime');
149 | }
150 |
151 | /**
152 | * Get size of the node.
153 | * Size is simply the `size` property of the cached `fs.Stats` instance.
154 | * @returns {number}
155 | */
156 | getSize() {
157 | return this.getStatProp('size');
158 | }
159 |
160 | /**
161 | * Is the node a directory?
162 | * @returns {boolean}
163 | */
164 | isDirectory() {
165 | return this.nodeName === 'Directory';
166 | }
167 |
168 | /**
169 | * Is the node a file?
170 | * @returns {boolean}
171 | */
172 | isFile() {
173 | return this.nodeName === 'File';
174 | }
175 |
176 | /**
177 | * Is the node a symbolic link?
178 | * @returns {boolean}
179 | */
180 | isSymbolicLink() {
181 | return this.nodeName === 'SymbolicLink';
182 | }
183 |
184 | /**
185 | * Is the node a dot file? i.e. does the node's name begin with dot character.
186 | * @returns {boolean}
187 | */
188 | isDotFile() {
189 | return this.baseName[0] === '.';
190 | }
191 |
192 | /**
193 | * Asynchronously renew stats of the node. Uses `fs.lstat`.
194 | * @returns {promise} A fresh `fs.Stats` instance for the node.
195 | */
196 | renewStats() {
197 | return this.fs.lstat(this.pathName).then((stats) => {
198 | this._stats = stats;
199 | return stats;
200 | });
201 | }
202 |
203 | /**
204 | * Synchronously renew stats of the node. Uses `fs.lstatSync`.
205 | * @chainable
206 | * @returns {node}
207 | */
208 | renewStatsSync() {
209 | const stat = this.lstatSync(this.pathName);
210 | this._stats = stat;
211 | return this;
212 | }
213 |
214 | /**
215 | * Get octal representation of the node's permissions.
216 | * @returns {string}
217 | * @example
218 | * node.getOctalPermissions() // → "755"
219 | */
220 | getOctalPermissions() {
221 | return (this._stats.mode & 0o777).toString(8);
222 | }
223 |
224 | /**
225 | * Get permissions of the node for owner, group and others by converting `mode`
226 | * property of cached stats into an object.
227 | * @example
228 | * node.getPermissions()
229 | * // →
230 | * {
231 | * read: { owner: true, group: true, others: false },
232 | * write: { owner: true, group: true, others: false },
233 | * execute: { owner: true, group: true, others: false }
234 | * }
235 | * @returns {object}
236 | */
237 | getPermissions() {
238 | if (getType(this._stats) !== 'object') {
239 | throw new Error(
240 | 'No valid cached stats ofr this node. Run `.renewStats()` before calling this function!'
241 | );
242 | }
243 | // Logic taken from npm `mode-to-permissions` module
244 | const mode = this._stats.mode,
245 | owner = mode >> 6,
246 | group = (mode << 3) >> 6,
247 | others = (mode << 6) >> 6;
248 |
249 | return {
250 | read: {
251 | owner: !!(owner & 4),
252 | group: !!(group & 4),
253 | others: !!(others & 4),
254 | },
255 | write: {
256 | owner: !!(owner & 2),
257 | group: !!(group & 2),
258 | others: !!(others & 2),
259 | },
260 | execute: {
261 | owner: !!(owner & 1),
262 | group: !!(group & 1),
263 | others: !!(others & 1),
264 | },
265 | };
266 | }
267 |
268 | /**
269 | * Asynchronously tests a user's permissions for the file or directory.
270 | * Wrapper for promisified `fs.access`.
271 | * @param {integer} [mode=fs.constants.F_OK]
272 | * @returns {promise}
273 | * @example
274 | * // Check if the node is readable.
275 | * node.access(node.fs.constants.R_OK).then(() => {
276 | * // node is readable
277 | * }).catch(e => {
278 | * // node is not readable
279 | * });
280 | */
281 | access(mode) {
282 | return this.fs.access(this.pathName, mode);
283 | }
284 |
285 | /**
286 | * Wrapper for `fs.accessSync`.
287 | * @chainable
288 | * @param {integer} [mode=fs.constants.F_OK]
289 | * @returns {node} this
290 | */
291 | accessSync(mode) {
292 | this.fs.accessSync(this.pathName, mode);
293 | return this;
294 | }
295 |
296 | /**
297 | * Wrapper for promisified `fs.chmod`.
298 | * @param {integer} mode
299 | * @returns {promise}
300 | */
301 | chmod(mode) {
302 | return this.fs.chmod(this.pathName, mode);
303 | }
304 |
305 | /**
306 | * Wrapper for `fs.chmodSync`.
307 | * @chainable
308 | * @param {integer} mode
309 | * @returns {node} this
310 | */
311 | chmodSync(mode) {
312 | this.fs.chmodSync(this.pathName, mode);
313 | return this;
314 | }
315 |
316 | /**
317 | * Wrapper for promisified `fs.lchmod`.
318 | * @param {integer} mode
319 | * @returns {promise}
320 | */
321 | lchmod(mode) {
322 | return this.fs.lchmod(this.pathName, mode);
323 | }
324 |
325 | /**
326 | * Wrapper for `fs.lchmodSync`.
327 | * @chainable
328 | * @param {integer} mode
329 | * @returns {node}
330 | */
331 | lchmodSync(mode) {
332 | this.fs.lchmodSync(this.pathName, mode);
333 | return this;
334 | }
335 |
336 | /**
337 | * Wrapper for promisified `fs.chown`.
338 | * @param {integer} uid The user id
339 | * @param {integer} gid The group id
340 | * @returns {promise}
341 | */
342 | chown(uid, gid) {
343 | return this.fs.chown(this.pathName, uid, gid);
344 | }
345 |
346 | /**
347 | * Wrapper for `fs.chownSync`.
348 | * @chainable
349 | * @param {integer} uid The user id
350 | * @param {integer} gid The group id
351 | * @returns {node} The file node
352 | */
353 | chownSync(uid, gid) {
354 | this.fs.chownSync(this.pathName, uid, gid);
355 | return this;
356 | }
357 |
358 | /**
359 | * Wrapper for promisified `fs.lchown`.
360 | * @param {integer} uid The user id
361 | * @param {integer} gid The group id
362 | * @returns {promise}
363 | */
364 | lchown(uid, gid) {
365 | return this.fs.lchown(this.pathName, uid, gid);
366 | }
367 |
368 | /**
369 | * Wrapper for `fs.lchownSync`.
370 | * @chainable
371 | * @param {integer} uid The user id
372 | * @param {integer} gid The group id
373 | * @returns {node} The file node
374 | */
375 | lchownSync(uid, gid) {
376 | this.fs.lchownSync(this.pathName, uid, gid);
377 | return this;
378 | }
379 |
380 | /**
381 | * Does node exist on file system?
382 | * Uses `fs.access` instead of the deprecated `fs.exists` method.
383 | * @returns {promise}
384 | */
385 | exists() {
386 | return this.access(this.fs.constants.F_OK)
387 | .then(() => {
388 | return true;
389 | })
390 | .catch(() => false);
391 | }
392 |
393 | /**
394 | * Does node exist on file system?
395 | * Wrapper for `fs.existsSync`.
396 | * @returns {boolean}
397 | */
398 | existsSync() {
399 | return this.fs.existsSync(this.pathName);
400 | }
401 |
402 | /**
403 | * Wrapper for promisified `fs.stat`.
404 | * @returns {promise} Promise representing instance of `fs.Stats` for the node.
405 | */
406 | stat() {
407 | return this.fs.stat(this.pathName, ...arguments);
408 | }
409 |
410 | /**
411 | * Wrapper for `fs.statSync`.
412 | * @returns {object} Instance of `fs.Stats` for the node.
413 | */
414 | statSync() {
415 | return this.fs.statSync(this.pathName, ...arguments);
416 | }
417 |
418 | /**
419 | * Wrapper for promisified `fs.lstat`.
420 | * @returns {promise} Promise representing instance of `fs.Stats` for the node.
421 | */
422 | lstat() {
423 | return this.fs.lstat(this.pathName, ...arguments);
424 | }
425 |
426 | /**
427 | * Wrapper for `fs.lstatSync`.
428 | * @returns {object} Instance of `fs.Stats` for the node.
429 | */
430 | lstatSync() {
431 | return this.fs.lstatSync(this.pathName, ...arguments);
432 | }
433 |
434 | /**
435 | * Wrapper for promisified `fs.link`.
436 | * @param {string|Buffer|URL} newPath
437 | * @returns {Promise}
438 | */
439 | link(newPath) {
440 | return this.fs.link(this.pathName, newPath);
441 | }
442 |
443 | /**
444 | * Wrapper for `fs.linkSync`.
445 | * @chainable
446 | * @param {string|buffer|URL} newPath
447 | * @returns {node}
448 | */
449 | linkSync(newPath) {
450 | this.fs.linkSync(this.pathName, newPath);
451 | return this;
452 | }
453 |
454 | /**
455 | * Asynchronously rename node to the pathname provided as newPath.
456 | * In the case that `newPath` already exists, it will be overwritten.
457 | * Wrapper for promisified `fs.rename`.
458 | * @param newPath {string|Buffer|URL}
459 | * @returns {promise}
460 | */
461 | rename(newPath) {
462 | return this.fs.rename(this.pathName, ...arguments).then(() => {
463 | this._setPathParams(newPath);
464 | });
465 | }
466 |
467 | /**
468 | * Wrapper for `fs.renameSync`.
469 | * @chainable
470 | * @param newPath {string|Buffer|URL}
471 | * @returns {node}
472 | */
473 | renameSync(newPath) {
474 | this.fs.renameSync(this.pathName, newPath);
475 | this._setPathParams(newPath);
476 | return this;
477 | }
478 |
479 | /**
480 | * Wrapper for promisified `fs.utimes`.
481 | * @param atime {number|string|Date}
482 | * @param mtime {number|string|Date}
483 | * @returns {promise}
484 | */
485 | utimes() {
486 | return this.fs.utimes(this.pathName, ...arguments);
487 | }
488 |
489 | /**
490 | * Wrapper for `fs.utimesSync`.
491 | * @chainable
492 | * @param atime {number|string|Date}
493 | * @param mtime {number|string|Date}
494 | * @returns {node}
495 | */
496 | utimesSync() {
497 | this.fs.utimesSync(this.pathName, ...arguments);
498 | return this;
499 | }
500 |
501 | /**
502 | * Asynchronously copy the node. `Directory` instances can have contents. Like `cp -r`.
503 | * When directory doesn't exist, it's created!
504 | * Wrapper for `fs-extra.copy`.
505 | * @param {string} destPath Destination path.
506 | * @param {object} options Options for `fs-extra.copy`.
507 | * @returns {promise}
508 | * @example
509 | * // creating a `File` instance. `File` class extends the `Node` class!
510 | * const file = new File('/app/resources/style.css');
511 | * file.copy('/app/backup/backup_style.css').then(() => {
512 | * // file has been copied successfully!
513 | * }).catch(e => {
514 | * // There was an error!
515 | * });
516 | */
517 | copy() {
518 | return this.fs.copy(this.pathName, ...arguments);
519 | }
520 |
521 | /**
522 | * Wrapper for `fs-extra.copySync`.
523 | * @chainable
524 | * @param {string} destPath Destination path.
525 | * @param {object} options Options for `fs-extra.copySync`.
526 | * @returns {node}
527 | */
528 | copySync() {
529 | this.fs.copySync(this.pathName, ...arguments);
530 | return this;
531 | }
532 |
533 | /**
534 | * Move node to another location. `baseName` property of the node is joined
535 | * with `targetDir` param for resolving the final path for the node.
536 | * The method on success updates path-related properties of the node,
537 | * but node's cached stats (if any) is not refreshed!
538 | * For updating node's stats, user can call `node.renewStats()` or `node.renewStatsSync()`
539 | * methods after moving the node.
540 | * Uses `fs-extra.move`.
541 | * @param {object|string} targetDir `Directory` instance or absolute path of the target directory.
542 | * @param {object} options Options for `fs-extra.move`.
543 | * @return {promise}
544 | * @example
545 | * const node = new File('/app/resources/style.css');
546 | * const dir = new Directory('/app/target_dir');
547 | * node.moveTo(dir || '/app/target_dir').then(() => {
548 | * // node has been moved into '/app/target_dir' directory!
549 | * node.getPathName(); // → '/app/target_dir/style.css'
550 | * });
551 | */
552 | moveTo(targetDir, options) {
553 | const targetPath = this.__resolvePath(targetDir);
554 | if (typeof options === 'function') {
555 | throw new Error('`node.moveTo` doesn not accept a callback function!');
556 | }
557 | // Fix `fs.move` broken handling of optional parameters!
558 | const args = [this.pathName, targetPath];
559 | if (options) {
560 | args.push(options);
561 | }
562 | return this.fs.move(...args).then(() => {
563 | this._setPathParams(targetPath);
564 | return this;
565 | });
566 | }
567 |
568 | /**
569 | * Synchronous version of `node.moveTo`.
570 | * @chainable
571 | * @param {object|string} targetDir `Directory` instance or absolute path of the target directory.
572 | * @param {object} options Options for `fs-extra.move`.
573 | * @returns {node}
574 | */
575 | moveToSync(targetDir, options) {
576 | const targetPath = this.__resolvePath(targetDir);
577 | this.fs.moveSync(this.pathName, targetPath, options);
578 | this._setPathParams(targetPath);
579 | return this;
580 | }
581 |
582 | /**
583 | * Alias for `node.moveTo`.
584 | */
585 | appendTo() {
586 | return this.moveTo(...arguments);
587 | }
588 |
589 | /**
590 | * Alias for `node.moveToSync`.
591 | * @chainable
592 | * @returns {node}
593 | */
594 | appendToSync() {
595 | return this.moveToSync(...arguments);
596 | }
597 |
598 | /**
599 | * Asynchronously select siblings of the node.
600 | * Uses `glob` package.
601 | * @param {string} [patten='*'] Optional `glob` pattern.
602 | * @param {object} [options] Options for `glob` package.
603 | * @return {promise} Promise representing a `draxt` collection.
604 | */
605 | siblings() {
606 | const {rawQuery, toNodes, Draxt} = Node;
607 | let [_options, filterFn] = this.__normalizeRelativeGlobOptions(...arguments);
608 | _options.ignore.push(this.pathName);
609 | return rawQuery('*', _options).then((items) => {
610 | if (filterFn) {
611 | items = items.filter(filterFn);
612 | }
613 | return toNodes(items).then(Draxt);
614 | });
615 | }
616 |
617 | /**
618 | * Synchronously select siblings of the node.
619 | * Uses `glob` package.
620 | * @param {string} [pattern='*'] Optional `glob` pattern.
621 | * @param {object} [options] Options for `glob` package.
622 | * @return {draxt} A `draxt` collection.
623 | */
624 | siblingsSync() {
625 | const {rawQuerySync, toNodesSync, Draxt} = Node;
626 | let [_options, filterFn] = this.__normalizeRelativeGlobOptions(...arguments);
627 | _options.ignore.push(this.pathName);
628 | let items = rawQuerySync('*', _options);
629 | if (filterFn) {
630 | items = items.filter(filterFn);
631 | }
632 | return new Draxt(toNodesSync(items));
633 | }
634 |
635 | /**
636 | * Remove the node from file system! `Directory` nodes can have contents. Like `rm -rf`.
637 | * Wrapper for `fs-extra.remove`.
638 | * @returns {promise}
639 | */
640 | remove() {
641 | return this.fs.remove(this.pathName, ...arguments);
642 | }
643 |
644 | /**
645 | * Wrapper for `fs-extra.removeSync`.
646 | * @chainable
647 | * @returns {node}
648 | */
649 | removeSync() {
650 | this.fs.removeSync(this.pathName, ...arguments);
651 | return this;
652 | }
653 |
654 | /**
655 | * Asynchronously get parent directory node of the node. It's an async method
656 | * as it gets an instance of `fs.Stats` for the parent node asynchronously!
657 | * @returns {promise} Promise representing a `Directory` instance.
658 | * @example
659 | * const file = new File('/app/resources/style.css');
660 | * file.parent().then(dir => {
661 | * dir.isDirectory(); // → true
662 | * dir.getPathName(); // → '/app/resources'
663 | * });
664 | */
665 | parent() {
666 | const {Directory} = Node;
667 | return this.fs.lstat(this.parentPath).then((stats) => {
668 | return new Directory(this.parentPath, stats);
669 | });
670 | }
671 |
672 | /**
673 | * Synchronously get parent directory node of the node.
674 | * @returns {node} A `Directory` instance.
675 | */
676 | parentSync() {
677 | const {Directory} = Node;
678 | const stats = this.fs.lstatSync(this.parentPath);
679 | return new Directory(this.parentPath, stats);
680 | }
681 |
682 | /**
683 | * Asynchronously query the file system by using `glob` package.
684 | * @param {string} pattern Pattern for `glob` package.
685 | * @param {object} [options] Options for `glob` package.
686 | * @returns {promise} An array of pathNames.
687 | */
688 | static rawQuery(pattern, options) {
689 | options = Node.__normalizeGlobOptions(options);
690 | return glob(pattern, options);
691 | }
692 |
693 | /**
694 | * Synchronously query the file system by using `glob` package.
695 | * @param {string} pattern Pattern for `glob` package.
696 | * @param {object} [options] Options for `glob` package.
697 | * @returns {array} An array of pathNames.
698 | */
699 | static rawQuerySync(pattern, options) {
700 | options = Node.__normalizeGlobOptions(options);
701 | return globSync(pattern, options);
702 | }
703 |
704 | /**
705 | * Convert array of paths to array of node instances asynchronously!
706 | * A node instance can be an instance of `File`, `Directory` or `SymbolicLink`.
707 | * @param {array} pathNames Array of pathNames.
708 | * @returns {promise} Array of node instances.
709 | * @example
710 | * const pathNames = [
711 | * '/app/resources',
712 | * '/app/resources/style.css'
713 | * ];
714 | * Node.toNodes(pathNames).then(result => {
715 | * // result:
716 | * [
717 | * Directory { pathName: '/app/resources', ... },
718 | * File { pathName: '/app/resources/style.css', ... }
719 | * ]
720 | * });
721 | */
722 | static toNodes(pathNames) {
723 | const nItems = [];
724 | const ps = pathNames.map((item, i) => {
725 | return fs.lstat(item).then((stats) => {
726 | nItems[i] = Node.__statsToNode(item, stats);
727 | });
728 | });
729 | return Promise.all(ps).then(() => nItems);
730 | }
731 |
732 | /**
733 | * Convert array of paths to array of nodes synchronously!
734 | * A node instance can be instance of `File`, `Directory` or `SymbolicLink`.
735 | * @param {array} pathNames Array of paths
736 | * @returns {array} Array of node instances.
737 | */
738 | static toNodesSync(pathNames) {
739 | return pathNames.map((item) => {
740 | const stats = fs.lstatSync(item);
741 | return Node.__statsToNode(item, stats);
742 | });
743 | }
744 |
745 | /**
746 | * Create a node object by analyzing `fs.Stats` of a pathName.
747 | * @param {string} pathName Absolute pathName of the node.
748 | * @param {object} stats Instance of `fs.Stats` for the pathName.
749 | * @returns {node}
750 | */
751 | static __statsToNode(pathName, stats) {
752 | const {File, Directory, SymbolicLink} = Node;
753 | if (stats.isFile()) {
754 | return new File(pathName, stats);
755 | } else if (stats.isDirectory()) {
756 | return new Directory(pathName, stats);
757 | } else if (stats.isSymbolicLink()) {
758 | return new SymbolicLink(pathName, stats);
759 | } else {
760 | return new Node(pathName, stats);
761 | }
762 | }
763 |
764 | /**
765 | * Asynchronously query the file system by using `glob` package.
766 | * @param {string} pattern A `glob` pattern.
767 | * @param {object} [options] Options for `glob` package.
768 | * @returns {promise} Array of nodes.
769 | */
770 | static query() {
771 | const {rawQuery, toNodes} = Node;
772 | return rawQuery(...arguments).then(toNodes);
773 | }
774 |
775 | /**
776 | * Synchronously query the file system by using `glob` package.
777 | * @param {string} pattern
778 | * @param {object} [options] Options for `glob` package.
779 | * @returns {array} Array of nodes.
780 | */
781 | static querySync() {
782 | const {rawQuerySync, toNodesSync} = Node;
783 | return toNodesSync(rawQuerySync(...arguments));
784 | }
785 |
786 | /**
787 | * Normalize glob options. This function overrides some possible user-set
788 | * params like the `absolute` parameter.
789 | * @param {object} options
790 | * @returns {object} Normalized object
791 | */
792 | static __normalizeGlobOptions(options = {}) {
793 | const type = getType(options);
794 | if (type !== 'undefined' && ['string', 'object'].indexOf(type) === -1) {
795 | throw new Error('Optional `options` parameter must be either a string or an object!');
796 | }
797 | // query(pattern, context) syntax
798 | if (type === 'string') {
799 | options = {
800 | cwd: options,
801 | };
802 | }
803 |
804 | assign(options, {
805 | absolute: true,
806 | });
807 |
808 | return options;
809 | }
810 |
811 | /**
812 | * Normalize glob options for `Node#siblings` and `Node#siblingsSync` methods.
813 | * @param {string} [pattern] Optional pattern
814 | * @param {object} [options] Optional options
815 | * @return {array}
816 | */
817 | __normalizeRelativeGlobOptions(pattern, options = {}) {
818 | let filterFn;
819 | if (arguments.length === 1 && getType(pattern) === 'object') {
820 | options = pattern;
821 | pattern = undefined;
822 | }
823 | if (pattern && getType(pattern) !== 'string') {
824 | throw new Error('`pattern` parameter should be a string!');
825 | }
826 | if (getType(options) === 'string') {
827 | throw new Error('Relational queries do not accept `context` paramter!');
828 | }
829 | if (options && getType(options) !== 'object') {
830 | throw new Error('Invalid type for `options` parameter!');
831 | }
832 |
833 | // Convert `options.ignore` into an array (if it's not)
834 | let ignore = options.ignore;
835 | if (ignore) {
836 | if (!Array.isArray(ignore)) {
837 | ignore = [ignore];
838 | }
839 | } else {
840 | ignore = [];
841 | }
842 | options.ignore = ignore;
843 |
844 | // If `pattern` exists create a minimatch filter function
845 | if (pattern) {
846 | filterFn = minimatch.filter(pattern, {
847 | matchBase: true,
848 | dot: !!options.dot,
849 | });
850 | }
851 |
852 | const dirPath = this.nodeName === 'Directory' ? this.pathName : this.parentPath;
853 |
854 | assign(options, {
855 | cwd: dirPath,
856 | });
857 |
858 | return [options, filterFn];
859 | }
860 |
861 | /**
862 | * Make a pathName by joining `dir` parameter with node's `baseName`
863 | * @param {node|string} dir Instance of `Directory` class or a string pathName
864 | * @returns {string}
865 | */
866 | __resolvePath(dir) {
867 | const dirType = typeof dir;
868 | if (dirType === 'undefined') {
869 | throw new Error('`dir` parameter is required!');
870 | }
871 | const isDirectory = dir.nodeName === 'Directory';
872 | if (!isDirectory && dirType !== 'string') {
873 | throw new Error('`dir` parameter must be a string or instance of Directory class!');
874 | }
875 | const dirPath = isDirectory ? dir.pathName : dir;
876 |
877 | if (!path.isAbsolute(dirPath)) {
878 | throw new Error('`dir` must be an absolute path!');
879 | }
880 | return path.join(dirPath, this.baseName);
881 | }
882 | }
883 |
884 | Node.prototype.fs = fs;
885 | Node.prototype.glob = glob;
886 |
887 | module.exports = Node;
888 |
--------------------------------------------------------------------------------
/src/interfaces/SymbolicLink.js:
--------------------------------------------------------------------------------
1 | const Node = require('./Node'),
2 | nodeProps = {
3 | nodeName: {value: 'SymbolicLink', writable: false, configurable: false, enumerable: true},
4 | NODE_TYPE: {value: 3, writable: false, configurable: false, enumerable: true},
5 | };
6 |
7 | /**
8 | * `SymbolicLink` class which extends the `Node` class is an interface representing pathNames
9 | * that their `fs.Stats`'s `.isSymbolicLink()` method returns `true`.
10 | * @prop {string} nodeName Name of the node: `'SymbolicLink'`.
11 | * @prop {number} NODE_TYPE Code number for the node: `3`.
12 | */
13 | class SymbolicLink extends Node {
14 | /**
15 | * Construct a new symbolic link.
16 | * @param {string} pathName Absolute pathName of the node
17 | * @param {object} [stats] Instance of `fs.Stats` for the node
18 | */
19 | constructor(nodePath, stats) {
20 | super(nodePath, stats);
21 | Object.defineProperties(this, nodeProps);
22 | }
23 |
24 | /**
25 | * Is the symlink broken?
26 | * @returns {promise}
27 | */
28 | isBroken() {
29 | return this.readlink().then((linkPath) => {
30 | return this.exists(linkPath).then((ret) => !ret);
31 | });
32 | }
33 |
34 | /**
35 | * Synchronous version of `symbolicLink.isBroken` method.
36 | * @returns {boolean}
37 | */
38 | isBrokenSync() {
39 | return !this.existsSync(this.readlinkSync());
40 | }
41 |
42 | /**
43 | * Asynchronously read the value of the symbolic link.
44 | * Wrapper for `fs.readlink`.
45 | * @param {string|object} options Options for `fs.readlinkSync`.
46 | * @returns {promise}
47 | */
48 | readlink(options) {
49 | return this.fs.readlink(this.pathName, options);
50 | }
51 |
52 | /**
53 | * Synchronously read the value of the symbolic link.
54 | * Wrapper for `fs.readlinkSync`.
55 | * @param {string|object} options Options for `fs.readlinkSync`.
56 | * @returns {string|buffer}
57 | */
58 | readlinkSync(options) {
59 | return this.fs.readlinkSync(this.pathName, options);
60 | }
61 | }
62 |
63 | module.exports = Node.SymbolicLink = SymbolicLink;
64 |
--------------------------------------------------------------------------------
/src/interfaces/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | Node: require('./Node'),
3 | File: require('./File'),
4 | Directory: require('./Directory'),
5 | SymbolicLink: require('./SymbolicLink'),
6 | };
7 |
--------------------------------------------------------------------------------
/src/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A simple utility object
3 | */
4 | module.exports = {
5 | /**
6 | * Get type of a variable
7 | * @param {any} value
8 | * @returns {string}
9 | */
10 | getType(value) {
11 | return Object.prototype
12 | .toString
13 | .apply(value)
14 | .match(/\[object (\w+)\]/)[1]
15 | .toLowerCase();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/test/draxt.js:
--------------------------------------------------------------------------------
1 | /* global describe, it, beforeEach, afterEach*/
2 | const draxt = require('../src/draxt'),
3 | {expect} = require('chai'),
4 | {execSync} = require('child_process'),
5 | {Node} = draxt;
6 |
7 | const contentList = ['a.js', 'another_dir', 'another_example_file.md', 'b.md', 'example_file.md'];
8 | const mkfs = () => {
9 | const pre = `
10 | rm -r /tmp/draxt_test_dir
11 | mkdir /tmp/draxt_test_dir
12 | echo 'example content' > /tmp/draxt_test_dir/example_file.md
13 | echo 'example content' > /tmp/draxt_test_dir/another_example_file.md
14 | mkdir /tmp/draxt_test_dir/another_dir
15 | echo '...' > /tmp/draxt_test_dir/a.js
16 | ln -s /tmp/draxt_test_dir/another_example_file /tmp/draxt_test_dir/b.md
17 | `;
18 | execSync(pre);
19 | };
20 | describe('draxt', function () {
21 | beforeEach(function () {
22 | mkfs();
23 | });
24 |
25 | afterEach(function () {
26 | mkfs();
27 | });
28 |
29 | it('basic initialization', function () {
30 | expect(draxt()).to.be.instanceof(draxt);
31 | expect(draxt().length).to.eql(0);
32 | expect(() => draxt(['str'])).to.throw('Invalid value for `items` parameter');
33 | const items_one = [new Node('pathName')];
34 | const d_one = draxt(items_one);
35 | expect(d_one.length).to.eql(1);
36 | expect(d_one.items).to.eql(items_one);
37 | expect(draxt.fn).to.eql(draxt.prototype);
38 | });
39 |
40 | it('initialization with query', function () {
41 | return draxt('/tmp/draxt_test_dir/*').then((d) => {
42 | expect(d).to.be.instanceof(draxt);
43 | expect(d.length).to.eql(5);
44 | expect(draxt(d).items).to.eql(d.items);
45 | expect(draxt(d).length).to.eql(d.length);
46 | // make sure the `items` parameter has been cloned!
47 | expect(draxt(d).items === d.items).to.eql(false);
48 | });
49 | });
50 |
51 | it('.sync()', function () {
52 | const result = draxt.sync('/tmp/draxt_test_dir');
53 | expect(result).to.be.instanceof(draxt);
54 | expect(result.length).to.eql(1);
55 | expect(draxt.sync('/tmp/draxt_test_dir/*').length).to.eql(5);
56 | });
57 |
58 | it('.extend()', function () {
59 | const exampleFunc = function () {
60 | return 'exampleFunc';
61 | };
62 | draxt.extend({
63 | exampleFunc,
64 | });
65 | expect(draxt.prototype.exampleFunc).to.eql(exampleFunc);
66 | expect(draxt.fn.exampleFunc).to.eql(exampleFunc);
67 | });
68 |
69 | it('.add()', function () {
70 | const d = draxt();
71 | expect(d.length).to.eql(0);
72 | const node = new Node('pathName');
73 | d.add(node);
74 | expect(d.length).to.eql(1);
75 | // should not add a duplicate item!
76 | d.add(node);
77 | expect(d.length).to.eql(1);
78 | d.add(new Node('pathName'));
79 | expect(d.length).to.eql(1);
80 | d.add(new Node('pathName2'));
81 | expect(d.length).to.eql(2);
82 | const d2 = draxt([new Node('pathName3')]);
83 | d.add(d2);
84 | expect(d.length).to.eql(3);
85 | });
86 |
87 | it('.get()', function () {
88 | const d = draxt.sync('/tmp/draxt_test_dir/*');
89 | const node = d.get();
90 | expect(node).to.be.an('array');
91 | expect(node).to.be.eql(d.items);
92 | expect(d.get(0)).to.be.eql(d.items[0]);
93 | });
94 |
95 | it('.first() && .last()', function () {
96 | const d = draxt.sync('/tmp/draxt_test_dir/*');
97 | expect(d.first()).to.eql(d.items[0]);
98 | expect(d.last()).to.eql(d.items.pop());
99 | });
100 |
101 | it('.has()', function () {
102 | const d = draxt.sync('/tmp/draxt_test_dir/*');
103 | expect(d.has('/tmp/draxt_test_dir/another_dir')).to.eql(true);
104 | expect(d.has(new Node('/tmp/draxt_test_dir/another_dir'))).to.eql(true);
105 | expect(d.has('/tmp/draxt_test_dir/non_existent')).to.eql(false);
106 | });
107 |
108 | it('.slice()', function () {
109 | const d = draxt.sync('/tmp/draxt_test_dir/**');
110 | expect(d.length).to.eql(6);
111 | const d2 = d.slice(0, 4);
112 | expect(d2).to.be.instanceof(draxt);
113 | expect(d2.length).to.be.eql(4);
114 | const d3 = d.slice(-1);
115 | expect(d3.length).to.eql(1);
116 | expect(d3.get(0)).to.be.instanceof(Node);
117 | });
118 |
119 | it('.filter()', function () {
120 | const d = draxt.sync('/tmp/draxt_test_dir/**');
121 | const d2 = d.filter((node) => {
122 | return node.isFile();
123 | });
124 | expect(d2).to.be.instanceof(draxt);
125 | expect(d === d2).to.eql(false);
126 | expect(d2.length).to.eql(3);
127 | });
128 |
129 | it('.map()', function () {
130 | const d = draxt.sync('/tmp/draxt_test_dir/*');
131 | const res = d.map((node) => node.baseName);
132 | res.sort();
133 | expect(res.length).to.eql(d.length);
134 | expect(res).to.be.an('array');
135 | expect(res).to.eql(contentList);
136 | });
137 |
138 | it('.mapAsync', function () {
139 | const d = draxt.sync('/tmp/draxt_test_dir/*');
140 | const res = d.mapAsync((node) => {
141 | return new Promise((res) => {
142 | setTimeout(() => res(node.baseName), 30);
143 | });
144 | });
145 | expect(res).to.be.instanceof(Promise);
146 | return res.then((baseNames) => {
147 | baseNames.sort();
148 | expect(baseNames).to.eql(contentList);
149 | });
150 | });
151 |
152 | it('.each() && .forEach()', function () {
153 | const d = draxt.sync('/tmp/draxt_test_dir/*');
154 | expect(d.each).to.be.eql(d.forEach);
155 | const res = [];
156 | expect(d.forEach((node) => res.push(node.baseName))).to.eql(d);
157 | res.sort();
158 | expect(res).to.eql(contentList);
159 | });
160 |
161 | it('.some', function () {
162 | const d = draxt.sync('/tmp/draxt_test_dir/*');
163 | const res = d.some((node) => node.baseName === 'another_dir');
164 | const res2 = d.some((node) => node.baseName === 'non_existent');
165 | expect(res).to.eql(true);
166 | expect(res2).to.eql(false);
167 | });
168 |
169 | it('.sort() && .reverse()', function () {
170 | const d = draxt.sync('/tmp/draxt_test_dir/*');
171 | const originalNodesBackup = d.get().slice();
172 | expect(d.reverse().get()).to.eql(originalNodesBackup.reverse());
173 | });
174 |
175 | it('.directories()', function () {
176 | const d = draxt.sync('/tmp/draxt_test_dir/**');
177 | const dirs = d.directories();
178 | expect(dirs.length).to.eql(2);
179 | expect(dirs === d).to.eql(false);
180 | });
181 |
182 | it('.files()', function () {
183 | const d = draxt.sync('/tmp/draxt_test_dir/**');
184 | const files = d.files();
185 | expect(files.length).to.eql(3);
186 | expect(files === d).to.eql(false);
187 | });
188 |
189 | it('.symlinks()', function () {
190 | const d = draxt.sync('/tmp/draxt_test_dir/**');
191 | const symlinks = d.symlinks();
192 | expect(symlinks.length).to.eql(1);
193 | expect(symlinks).to.be.instanceof(draxt);
194 | });
195 |
196 | it('.empty()', function () {
197 | const d = draxt.sync('/tmp/draxt_test_dir/**');
198 | expect(d.length).to.eql(6);
199 | expect(d.empty()).to.eql(d);
200 | expect(d.length).to.eql(0);
201 | });
202 |
203 | it('.drop()', function () {
204 | const d = draxt.sync('/tmp/draxt_test_dir/**');
205 | expect(d.length).to.eql(6);
206 | d.drop('/tmp/draxt_test_dir/example_file.md');
207 | expect(d.length).to.eql(5);
208 | d.drop([new Node('/tmp/draxt_test_dir/another_example_file.md'), '/non_existent']);
209 | expect(d.length).to.eql(4);
210 | const d2 = draxt.sync('/tmp/draxt_test_dir/another_dir/*');
211 | d.drop(d2);
212 | expect(d.length).to.eql(4);
213 | d.drop(new Node('/tmp/draxt_test_dir'));
214 | expect(d.length).to.eql(3);
215 | expect(() => d.drop(new Date())).to.throw('Invalid paramter passed to');
216 | });
217 | });
218 |
--------------------------------------------------------------------------------
/test/interfaces/Directory.js:
--------------------------------------------------------------------------------
1 | /* global describe, beforeEach, it*/
2 | const Draxt = require('../../src/draxt');
3 | const {expect} = require('chai');
4 | const {execSync} = require('child_process');
5 | const {Directory, File} = Draxt.Node;
6 | const shouldNotPass = function () {
7 | throw new Error('should not pass!');
8 | };
9 |
10 | describe('Directory', function () {
11 | describe('initialization and basic methods', function () {
12 | const nodePath = '/fake/_fakepath';
13 | const stats = {};
14 | it('`new`', function () {
15 | const node = new Directory(nodePath, stats);
16 | expect(node.pathName).to.eql(nodePath);
17 | expect(node.extension).to.eql('');
18 | expect(node.name).to.eql('_fakepath');
19 | expect(node.baseName).to.eql('_fakepath');
20 | expect(node.parentPath).to.eql('/fake');
21 | expect(node.getCachedStats() === stats).to.eql(true);
22 | });
23 |
24 | it('path.parse methods', function () {
25 | const node = new Directory(nodePath, stats);
26 | expect(node.getPathName()).to.eql(nodePath);
27 | expect(node.getExtension()).to.eql('');
28 | expect(node.getName()).to.eql('_fakepath');
29 | expect(node.getBaseName()).to.eql('_fakepath');
30 | expect(node.getParentPath()).to.eql('/fake');
31 | });
32 | });
33 |
34 | describe('fs methods', function () {
35 | beforeEach(function () {
36 | const pre = `
37 | rm -r /tmp/fake_test_dir
38 | mkdir /tmp/fake_test_dir
39 | mkdir /tmp/fake_test_dir/empty_dir
40 | mkdir /tmp/fake_test_dir/empty_dir2
41 | mkdir /tmp/fake_test_dir/non_empty_dir
42 | mkdir /tmp/fake_test_dir/non_empty_dir/foo
43 | mkdir /tmp/fake_test_dir/non_empty_dir/.git
44 | mkdir /tmp/fake_test_dir/non_empty_dir2
45 | echo 'example content' > /tmp/fake_test_dir/example_file.md
46 | echo 'content' > /tmp/fake_test_dir/non_empty_dir/file.rb
47 | echo 'content' > /tmp/fake_test_dir/non_empty_dir2/file.md
48 | `;
49 | execSync(pre);
50 | });
51 |
52 | it('.rmdir() & .rmdirSync() & .ensure() & .ensureSync()', function () {
53 | const dir = new Directory('/tmp/fake_test_dir/empty_dir');
54 | const dir2 = new Directory('/tmp/fake_test_dir/non_empty_dir');
55 | expect(dir.existsSync()).to.eql(true);
56 | expect(dir.rmdirSync()).to.eql(dir);
57 | expect(dir.existsSync()).to.eql(false);
58 | // recreate the dir
59 | expect(dir.ensureSync()).to.eql(dir);
60 | // make sure the generated node is a directory
61 | expect(dir.renewStatsSync().isDirectory()).to.eql(true);
62 | expect(dir.existsSync()).to.eql(true);
63 |
64 | // rmdir should throw en error for non-empty dirs
65 | expect(function () {
66 | dir2.rmdirSync();
67 | }).to.throw('ENOTEMPTY');
68 |
69 | return dir.rmdir().then(function () {
70 | expect(dir.existsSync()).to.eql(false);
71 | return dir.ensure().then(function () {
72 | expect(dir.existsSync()).to.eql(true);
73 | expect(dir.renewStatsSync().isDirectory()).to.eql(true);
74 | return dir2
75 | .rmdir()
76 | .then(shouldNotPass)
77 | .catch((e) => {
78 | expect(e.message).to.have.string('ENOTEMPTY');
79 | });
80 | });
81 | });
82 | });
83 |
84 | it('.readdir() & .readdirSync() & .read() & .readSync()', function () {
85 | const dir = new Directory('/tmp/fake_test_dir/non_empty_dir');
86 | const expected = ['.git', 'file.rb', 'foo'];
87 | expect(dir.readSync()).to.eql(expected);
88 | return dir.read().then((ret) => {
89 | expect(ret).to.eql(expected);
90 | });
91 | });
92 |
93 | it('isEmpty() & .isEmptySync()', function () {
94 | const dir = new Directory('/tmp/fake_test_dir/empty_dir');
95 | const dir2 = new Directory('/tmp/fake_test_dir/non_empty_dir');
96 | expect(dir.isEmptySync()).to.eql(true);
97 | expect(dir2.isEmptySync()).to.eql(false);
98 | return dir.isEmpty().then((empty) => {
99 | expect(empty).to.eql(true);
100 | return dir2.isEmpty().then((empty) => {
101 | expect(empty).to.eql(false);
102 | });
103 | });
104 | });
105 |
106 | it('.empty() & .emptySync()', function () {
107 | const expected = ['.git', 'file.rb', 'foo'];
108 | const dir = new Directory('/tmp/fake_test_dir/non_empty_dir');
109 | const dir2 = new Directory('/tmp/fake_test_dir/non_empty_dir2');
110 | expect(dir.readSync()).to.eql(expected);
111 | expect(dir.emptySync()).to.eql(dir);
112 | expect(dir.readSync()).to.eql([]);
113 | expect(dir2.readSync()).to.eql(['file.md']);
114 | return dir2.empty().then(function () {
115 | expect(dir2.readSync()).to.eql([]);
116 | });
117 | });
118 |
119 | it('__normalizeAppendNodes', function () {
120 | const method = Directory.__normalizeAppendNodes;
121 | const d = Draxt([new File('file.ext'), new Directory('dirname')]);
122 | expect(method(d)).to.eql(d.items);
123 | const d2 = ['str', new File('doo')];
124 | expect(method(d2)).to.eql(d2);
125 | const d3 = new File('');
126 | expect(method(d3)).to.eql([d3]);
127 | expect(method('path')).to.eql(['path']);
128 | expect(method(d.items)).to.eql(d.items);
129 | expect(() => method(new Date())).to.throw();
130 | });
131 |
132 | it('.append() & .appendSync()', function () {
133 | const col = [
134 | new File('/tmp/fake_test_dir/non_empty_dir/file.rb'),
135 | '/tmp/fake_test_dir/non_empty_dir/foo',
136 | ];
137 | // the directory will be created!
138 | const dir = new Directory('/tmp/fake_test_dir/empty_dir');
139 | expect(dir.appendSync(col)).to.eql(dir);
140 | // expect node to have it's new path!
141 | expect(col[0].pathName).to.eql('/tmp/fake_test_dir/empty_dir/file.rb');
142 | expect(dir.readSync()).to.eql(['file.rb', 'foo']);
143 | expect(new Directory('/tmp/fake_test_dir/non_empty_dir').readSync()).to.eql(['.git']);
144 | // col2
145 | const col2 = Draxt([new File('/tmp/fake_test_dir/example_file.md')]);
146 | return dir.append(col2).then(function () {
147 | expect(col2.get(0).pathName).eql('/tmp/fake_test_dir/empty_dir/example_file.md');
148 | return dir.append('/tmp/fake_test_dir/non_empty_dir2');
149 | });
150 | });
151 |
152 | it('.children() & .childrenSync()', function () {
153 | const dir = new Directory('/tmp/fake_test_dir/non_empty_dir');
154 | const ret = dir.childrenSync();
155 | expect(ret).to.be.instanceof(Draxt);
156 | expect(ret.length).to.eql(2);
157 | const ret2 = dir.childrenSync('*.rb');
158 | expect(ret2.length).to.eql(1);
159 | expect(ret2.get(0)).to.be.instanceof(File);
160 | expect(ret2.get(0).baseName).to.eql('file.rb');
161 | const ret3 = dir.childrenSync({dot: true});
162 | expect(ret3.length).to.eql(3);
163 |
164 | return dir.children().then((set) => {
165 | expect(set).to.be.instanceof(Draxt);
166 | expect(set.length).to.eql(2);
167 | expect(set.get(0).baseName).to.eql('foo');
168 | return dir.children('f*').then((set2) => {
169 | expect(set2.length).to.eql(2);
170 | });
171 | });
172 | });
173 |
174 | it('.find() & .findSync()', function () {
175 | const dir = new Directory('/tmp/fake_test_dir');
176 | const ret = dir.findSync('*');
177 | expect(ret).to.be.instanceof(Draxt);
178 | expect(ret.length).to.eql(5);
179 | const ret2 = dir.findSync('**');
180 | expect(ret2.length).to.eql(9);
181 | const ret3 = dir.findSync('**', {dot: true});
182 | expect(ret3.length).to.eql(10);
183 | return dir.find('*').then((set) => {
184 | expect(set).to.be.instanceof(Draxt);
185 | expect(set.length).to.eql(5);
186 | return dir.find('**', {dot: true}).then((set2) => {
187 | expect(set2.length).to.eql(10);
188 | });
189 | });
190 | });
191 | });
192 | });
193 |
--------------------------------------------------------------------------------
/test/interfaces/File.js:
--------------------------------------------------------------------------------
1 | /* global describe, beforeEach, afterEach, it*/
2 | const {File} = require('../../src/draxt').Node;
3 | const {expect} = require('chai');
4 |
5 | describe('File', () => {
6 | describe('initialization', () => {
7 | it('new and .isFile() method', () => {
8 | const file = new File('/fake/fakepath/module.md', {});
9 | expect(file.isFile()).to.eql(true);
10 | expect(file.isDirectory()).to.eql(false);
11 | expect(file.isSymbolicLink()).to.eql(false);
12 | expect(file.NODE_TYPE).to.eql(2);
13 | expect(file.nodeName).to.eql('File');
14 | });
15 | });
16 |
17 | describe('fs methods', () => {
18 | beforeEach(() => {
19 | const {execSync} = require('child_process');
20 | const pre = `
21 | rm -r /tmp/fake_dir
22 | mkdir /tmp/fake_dir
23 | echo 'example content.' > /tmp/fake_dir/example_file.md
24 | echo 'example content.' > /tmp/fake_dir/another_example_file.md
25 | `;
26 | execSync(pre);
27 | });
28 |
29 | afterEach(() => {
30 | // vol.reset();
31 | });
32 |
33 | it('.read() && .readSync()', () => {
34 | const file = new File('/tmp/fake_dir/example_file.md', {});
35 | const content = file.readSync('utf8');
36 | const expectContent = 'example content.\n';
37 | expect(content).to.eql(expectContent);
38 | return file.read('utf8').then((content) => {
39 | expect(content).to.eql(expectContent);
40 | });
41 | });
42 |
43 | it('.append() && .appendSync()', () => {
44 | const file = new File('/tmp/fake_dir/example_file.md', {});
45 | const ret = file.appendSync(' appended content');
46 | expect(ret).to.eql(file);
47 | expect(file.readSync('utf8')).to.eql('example content.\n appended content');
48 | return file.append('. string').then(() => {
49 | return file.read('utf8').then((content) => {
50 | expect(content).to.eql('example content.\n appended content. string');
51 | });
52 | });
53 | });
54 |
55 | it('.write() && .writeSync()', () => {
56 | const file = new File('/tmp/fake_dir/example_file.md', {});
57 | const ret = file.writeSync('new content');
58 | expect(ret).to.eql(file);
59 | expect(file.readSync('utf8')).to.eql('new content');
60 | return file.write('new async written content', () => {
61 | return file.read('utf8').then((content) => {
62 | return expect(content).to.eql('new async written content');
63 | });
64 | });
65 | });
66 |
67 | it('.truncate() && .truncateSync()', () => {
68 | const file = new File('/tmp/fake_dir/example_file.md', {});
69 | const ret = file.truncateSync(4);
70 | expect(ret).to.eql(file);
71 | expect(file.readSync('utf8')).to.eql('exam');
72 | return file.truncate().then(() => {
73 | return file.read('utf8').then((content) => {
74 | return expect(content).to.eql('');
75 | });
76 | });
77 | });
78 |
79 | it('.ensure() && .ensureSync()', () => {
80 | const file = new File('/tmp/fake_dir/non_existent.md');
81 | const file2 = new File('/tmp/fake_dir/non_existent2.md');
82 | expect(file.existsSync()).to.eql(false);
83 | expect(file.ensureSync()).to.eql(file);
84 | expect(file.existsSync()).to.eql(true);
85 | // async example
86 | expect(file2.existsSync()).to.eql(false);
87 | return file2.ensure().then(() => {
88 | expect(file2.existsSync()).to.eql(true);
89 | });
90 | });
91 | });
92 | });
93 |
--------------------------------------------------------------------------------
/test/interfaces/Node.js:
--------------------------------------------------------------------------------
1 | /* global describe, it, beforeEach, afterEach*/
2 | const draxt = require('../../src/draxt'),
3 | {Node} = draxt,
4 | {Directory, File} = Node,
5 | {expect} = require('chai'),
6 | fs = require('fs-extra'),
7 | path = require('path'),
8 | isTravis = 'TRAVIS' in process.env && 'CI' in process.env,
9 | shouldNotPass = function () {
10 | throw new Error('should not pass!');
11 | };
12 |
13 | const mkfs = () => {
14 | const {execSync} = require('child_process');
15 | const pre = `
16 | rm -r /tmp/node_test_dir
17 | mkdir /tmp/node_test_dir
18 | mkdir /tmp/node_test_dir/another_dir
19 | mkdir /tmp/node_test_dir/another_dir/.dir
20 | echo 'example content.' > /tmp/node_test_dir/example_file.md
21 | echo 'example content.' > /tmp/node_test_dir/another_example_file.md
22 | echo '...' > /tmp/node_test_dir/another_dir/a.js
23 | echo '...' > /tmp/node_test_dir/another_dir/b.js
24 | echo '...' > /tmp/node_test_dir/another_dir/c.php
25 | echo '...' > /tmp/node_test_dir/another_dir/k.php
26 | echo '...' > /tmp/node_test_dir/another_dir/d.html
27 | echo '...' > /tmp/node_test_dir/another_dir/README.md
28 | echo '...' > /tmp/node_test_dir/another_dir/foo.rb
29 | echo '...' > /tmp/node_test_dir/another_dir/document.txt
30 | ln -s /tmp/node_test_dir/example_file.md /tmp/node_test_dir/another_dir/g.md
31 | `;
32 | execSync(pre);
33 | };
34 |
35 | describe('Node', function () {
36 | describe('initialization and basic methods', function () {
37 | const nodePath = '/fake/_fakepath/module.js';
38 | const stats = {};
39 | it('`new`', function () {
40 | const node = new Node(nodePath, stats);
41 | expect(node.pathName).to.eql(nodePath);
42 | expect(node.extension).to.eql('js');
43 | expect(node.name).to.eql('module');
44 | expect(node.baseName).to.eql('module.js');
45 | expect(node.parentPath).to.eql('/fake/_fakepath');
46 | expect(node.rootPath).to.eql('/');
47 | expect(node.getCachedStats() === stats).to.eql(true);
48 | });
49 |
50 | it('path.parse methods', function () {
51 | const node = new Node(nodePath, stats);
52 | expect(node.getPathName()).to.eql(nodePath);
53 | expect(node.getExtension()).to.eql('js');
54 | expect(node.getName()).to.eql('module');
55 | expect(node.getBaseName()).to.eql('module.js');
56 | expect(node.getParentPath()).to.eql('/fake/_fakepath');
57 | expect(node.isDotFile()).to.eql(false);
58 | node.baseName = '.git';
59 | expect(node.isDotFile()).to.eql(true);
60 | node._stats = undefined;
61 | expect(node.getStatProp('foo')).to.eql(undefined);
62 | });
63 | });
64 |
65 | describe('fs module methods', function () {
66 | beforeEach(function () {
67 | mkfs();
68 | });
69 |
70 | // afterEach(function () {
71 | // mockFs.restore();
72 | // });
73 |
74 | it('.getPermissions()', function () {
75 | const node = new Node('/tmp/node_test_dir/example_file.md');
76 | expect(() => node.getPermissions()).to.throw('renewStats');
77 | const perms = node.chmodSync('700').renewStatsSync().getPermissions();
78 | expect(perms).to.eql({
79 | read: {owner: true, group: false, others: false},
80 | write: {owner: true, group: false, others: false},
81 | execute: {owner: true, group: false, others: false},
82 | });
83 | const perms2 = node.chmodSync('777').renewStatsSync().getPermissions();
84 | expect(perms2).to.eql({
85 | read: {owner: true, group: true, others: true},
86 | write: {owner: true, group: true, others: true},
87 | execute: {owner: true, group: true, others: true},
88 | });
89 | });
90 |
91 | it('.getAccessTime() && .getBirthTime() && .getModifiedTime() && .getChangeTime()', function () {
92 | const node = new Node('/tmp/node_test_dir/example_file.md');
93 | node.renewStatsSync();
94 | expect(node.getBirthTime()).to.eql(node._stats.birthtime);
95 | expect(node.getAccessTime()).to.eql(node._stats.atime);
96 | expect(node.getChangeTime()).to.eql(node._stats.ctime);
97 | expect(node.getModifiedTime()).to.eql(node._stats.mtime);
98 | });
99 |
100 | it('.__resolvePath()', function () {
101 | const node = new Node('/tmp/node_test_dir/example_file.md');
102 | expect(node.__resolvePath('/foo/bar')).to.eql('/foo/bar/example_file.md');
103 | expect(node.__resolvePath('/foo/bar/')).to.eql('/foo/bar/example_file.md');
104 | expect(node.__resolvePath(new Directory('/foo/bar/'))).to.eql(
105 | '/foo/bar/example_file.md'
106 | );
107 | expect(() => node.__resolvePath('../bar/')).to.throw('absolute');
108 | expect(() => node.__resolvePath(new File('/foo/bar.js'))).to.throw('Directory');
109 | expect(() => node.__resolvePath()).to.throw('required');
110 | });
111 |
112 | it('.renewStats() && .renewStatsSync()', function () {
113 | const node = new Node('/tmp/node_test_dir/example_file.md');
114 | expect(node._stats).to.eql();
115 | expect(node.renewStatsSync()).to.eql(node);
116 | expect(node._stats).to.be.an('object');
117 | const oldCache = node._stats;
118 | node._stats = null;
119 | return node.renewStats().then(function () {
120 | expect(node._stats).to.be.an('object');
121 | expect(node._stats === oldCache).to.eql(false);
122 | expect(node.getSize()).to.eql(node._stats.size);
123 | });
124 | });
125 |
126 | it('.access() && .accessSync()', function () {
127 | const node = new Node('/tmp/node_test_dir/example_file.md', {});
128 | const node2 = new Node('/tmp/node_test_dir/does_not_exist.md', {});
129 | expect(node.accessSync()).to.eql(node);
130 | expect(() => node2.accessSync()).to.throw();
131 | return node.access().then(function () {
132 | return node2
133 | .access()
134 | .then(shouldNotPass)
135 | .catch((e) => {
136 | expect(e.message).to.include('ENOENT');
137 | });
138 | });
139 | });
140 |
141 | it('.chmod() && .chmodSync() && .lchmod() && .lchmodSync()', function () {
142 | const node = new Node('/tmp/node_test_dir/another_dir/a.js', {});
143 | expect(node.chmodSync('700')).to.eql(node);
144 | node.renewStatsSync();
145 | expect(node.getOctalPermissions()).to.eql('700');
146 | expect(node.lchmodSync('755')).to.eql(node);
147 | node.renewStatsSync();
148 | return node.chmod('755').then(function () {
149 | node.renewStatsSync();
150 | if (!isTravis) expect(node.getOctalPermissions()).to.eql('755');
151 | return node.lchmod('711').then(function () {
152 | node.renewStatsSync();
153 | });
154 | });
155 | });
156 |
157 | it('.chown() && .chownSync() && .lchown() && .lchownSync()', function () {
158 | const node = new Node('/tmp/node_test_dir/another_dir/g.md', {});
159 | expect(node.chownSync(1000, 1000)).to.eql(node);
160 | node.renewStatsSync();
161 | // Temporarily comment some tests that fail on travis environment!
162 | //
163 | // expect(node._stats.uid).to.eql(10);
164 | // expect(node._stats.gid).to.eql(11);
165 | // expect(node.lchownSync(1000, 1000)).to.eql(node);
166 | // node.renewStatsSync();
167 | // expect(node._stats.uid).to.eql(20);
168 | // expect(node._stats.gid).to.eql(22);
169 | return node.chown(1000, 1000).then(function () {
170 | // node.renewStatsSync();
171 | // expect(node._stats.uid).to.eql(30);
172 | // expect(node._stats.gid).to.eql(33);
173 | // This test fails probably because of the mockFs
174 | // return node.lchown(1000, 1000).then(function() {
175 | // node.renewStatsSync();
176 | // expect(node._stats.uid).to.eql(40);
177 | // expect(node._stats.gid).to.eql(44);
178 | // });
179 | });
180 | });
181 |
182 | it('.exists() && .existsSync()', function () {
183 | const node = new Node('/tmp/node_test_dir/example_file.md', {});
184 | const node2 = new Node('/tmp/node_test_dir/does_not_exist.md', {});
185 | expect(node.existsSync()).to.eql(true);
186 | expect(node2.existsSync()).to.eql(false);
187 | return node.exists().then((ret) => {
188 | expect(ret).to.eql(true);
189 | return node2.exists().then((ret2) => {
190 | expect(ret2).to.eql(false);
191 | });
192 | });
193 | });
194 |
195 | it('.stat() && .statSync() && .lstat() && .lstatSync() ', function () {
196 | const node = new Node('/tmp/node_test_dir/example_file.md', {});
197 | expect(node.statSync()).to.be.an('object');
198 | expect(node.lstatSync()).to.be.an('object');
199 | return node.stat().then(function (stats) {
200 | expect(stats).to.be.an('object');
201 | return node.lstat().then((stats2) => {
202 | expect(stats2).to.be.an('object');
203 | });
204 | });
205 | });
206 |
207 | it('.link() && .linkSync()', function () {
208 | const node = new Node('/tmp/node_test_dir/example_file.md', {});
209 | const nodeLink = new Node('/tmp/node_test_dir/example_file_link.md');
210 | const nodeLink2 = new Node('/tmp/node_test_dir/example_file_link2.md');
211 | // make sure new name for the file doesn't exist before linking
212 | expect(nodeLink.existsSync()).to.eql(false);
213 | expect(nodeLink2.existsSync()).to.eql(false);
214 | expect(node.linkSync(nodeLink.pathName)).to.eql(node);
215 | expect(nodeLink.existsSync()).to.eql(true);
216 | return node.link(nodeLink2.pathName).then(function () {
217 | expect(nodeLink2.existsSync()).to.eql(true);
218 | });
219 | });
220 |
221 | it('.rename() && .renameSync()', function () {
222 | const node = new Node('/tmp/node_test_dir/example_file.md', {});
223 | const renameSampleNode = new Node('/tmp/node_test_dir/another_dir/example_file.js', {});
224 | const renameSampleNodeAsync = new Node(
225 | '/tmp/node_test_dir/another_dir/new_name.md',
226 | {}
227 | );
228 | expect(renameSampleNode.existsSync()).to.eql(false);
229 | expect(renameSampleNodeAsync.existsSync()).to.eql(false);
230 | expect(node.renameSync(renameSampleNode.pathName)).to.eql(node);
231 | expect(renameSampleNode.existsSync()).to.eql(true);
232 | expect(node.pathName).to.eql(renameSampleNode.pathName);
233 | expect(node.getExtension()).to.eql('js');
234 | return node.rename(renameSampleNodeAsync.pathName).then(function () {
235 | expect(renameSampleNodeAsync.existsSync()).to.eql(true);
236 | expect(node.pathName).to.eql(renameSampleNodeAsync.pathName);
237 | });
238 | });
239 |
240 | it('.utimes() && .utimesSync()', function () {
241 | const node = new Node('/tmp/node_test_dir/example_file.md');
242 | const atime = 1529607246; // Thursday, June 21, 2018 6:54:06 PM
243 | const mtime = 1529520846; // Wednesday, June 20, 2018 6:54:06 PM
244 | expect(node.utimesSync(atime, mtime)).to.eql(node);
245 | node.renewStatsSync();
246 | if (!isTravis) {
247 | // for some reason these fails on travis! Probably because of mock-fs
248 | expect(node.getStatProp('atimeMs') / 1000).to.eql(atime);
249 | expect(node.getStatProp('mtimeMs') / 1000).to.eql(mtime);
250 | }
251 | return node.utimes(atime - 2000, mtime - 2000).then(function () {
252 | node.renewStatsSync();
253 | if (!isTravis) {
254 | // for some reason these fails on travis! Probably because of mock-fs
255 | expect(node.getStatProp('atimeMs') / 1000).to.eql(atime - 2000);
256 | expect(node.getStatProp('mtimeMs') / 1000).to.eql(mtime - 2000);
257 | }
258 | });
259 | });
260 | });
261 |
262 | describe('Utility methods', function () {
263 | beforeEach(function () {
264 | mkfs();
265 | });
266 |
267 | it('.__normalizeGlobOptions()', function () {
268 | const o1 = Node.__normalizeGlobOptions();
269 | expect(o1).to.eql({
270 | absolute: true,
271 | });
272 | const o2 = Node.__normalizeGlobOptions({absolute: false});
273 | expect(o2).to.eql({
274 | absolute: true,
275 | });
276 | expect(() => Node.__normalizeGlobOptions([])).to.throw(
277 | 'must be either a string or an object'
278 | );
279 | const o3 = Node.__normalizeGlobOptions('/path');
280 | expect(o3).to.eql({
281 | cwd: '/path',
282 | absolute: true,
283 | });
284 | });
285 |
286 | it('.__statsToNode()', function () {
287 | const mockStats = (type) => {
288 | return {
289 | isFile() {
290 | return type === 'File';
291 | },
292 | isSymbolicLink() {
293 | return type === 'SymbolicLink';
294 | },
295 | isDirectory() {
296 | return type === 'Directory';
297 | },
298 | };
299 | };
300 | [
301 | {path: '/node', type: 'Node', nodeType: 0},
302 | {path: '/directory', type: 'Directory', nodeType: 1},
303 | {path: '/file', type: 'File', nodeType: 2},
304 | {path: '/symlink', type: 'SymbolicLink', nodeType: 3},
305 | ].forEach((item) => {
306 | const node = Node.__statsToNode(item.path, mockStats(item.type));
307 | expect(node.pathName).to.eql(item.path);
308 | expect(node.nodeName).to.eql(item.type);
309 | expect(node.NODE_TYPE).to.eql(item.nodeType);
310 | });
311 | });
312 |
313 | it('.toNodes() && .toNodesSync()', function () {
314 | const paths = [
315 | '/tmp/node_test_dir/another_dir',
316 | '/tmp/node_test_dir/another_dir/g.md',
317 | '/tmp/node_test_dir/example_file.md',
318 | ];
319 | const nodes1 = Node.toNodesSync(paths);
320 | expect(nodes1).to.be.an('array');
321 | expect(nodes1[0]).to.be.instanceof(Node.Directory);
322 | expect(nodes1[1]).to.be.instanceof(Node.SymbolicLink);
323 | expect(nodes1[2]).to.be.instanceof(Node.File);
324 | nodes1.forEach((el, index) => {
325 | expect(el.pathName).to.eql(paths[index]);
326 | });
327 |
328 | return Node.toNodes(paths).then((nodes2) => {
329 | expect(nodes2[0]).to.be.instanceof(Node.Directory);
330 | expect(nodes2[1]).to.be.instanceof(Node.SymbolicLink);
331 | expect(nodes2[2]).to.be.instanceof(Node.File);
332 | nodes2.forEach((el, index) => {
333 | expect(el.pathName).to.eql(paths[index]);
334 | });
335 | });
336 | });
337 |
338 | (function () {
339 | const rawExpected1 = [
340 | '/tmp/node_test_dir/another_dir',
341 | '/tmp/node_test_dir/another_example_file.md',
342 | '/tmp/node_test_dir/example_file.md',
343 | ].sort();
344 | const rawExpected2 = [
345 | '/tmp/node_test_dir',
346 | '/tmp/node_test_dir/another_dir',
347 | '/tmp/node_test_dir/another_dir/a.js',
348 | '/tmp/node_test_dir/another_dir/b.js',
349 | '/tmp/node_test_dir/another_dir/c.php',
350 | '/tmp/node_test_dir/another_dir/d.html',
351 | '/tmp/node_test_dir/another_dir/document.txt',
352 | '/tmp/node_test_dir/another_dir/foo.rb',
353 | '/tmp/node_test_dir/another_dir/g.md',
354 | '/tmp/node_test_dir/another_dir/k.php',
355 | '/tmp/node_test_dir/another_dir/README.md',
356 | '/tmp/node_test_dir/another_example_file.md',
357 | '/tmp/node_test_dir/example_file.md',
358 | ].sort();
359 | it('.rawQuery() && .rawQuerySync()', function () {
360 | // result seem to be sorted by default
361 | let items1 = Node.rawQuerySync('*', '/tmp/node_test_dir');
362 | expect(items1.sort()).to.eql(rawExpected1);
363 | const items2 = Node.rawQuerySync('**', {
364 | cwd: '/tmp/node_test_dir',
365 | });
366 | expect(items2.sort()).to.eql(rawExpected2);
367 | return Node.rawQuery('*', '/tmp/node_test_dir').then((res1) => {
368 | expect(res1.sort()).to.eql(rawExpected1);
369 | return Node.rawQuery('**', '/tmp/node_test_dir').then((res2) => {
370 | expect(res2.sort()).to.eql(rawExpected2);
371 | });
372 | });
373 | });
374 |
375 | it('.query() && .querySync()', function () {
376 | const result1 = Node.querySync('*', '/tmp/node_test_dir');
377 | expect(result1).to.be.an('array');
378 | expect(result1.length).to.eql(rawExpected1.length);
379 | const result2 = Node.querySync('**', {
380 | cwd: '/tmp/node_test_dir',
381 | });
382 | expect(result2.length).to.eql(rawExpected2.length);
383 | expect(result2[0].pathName).to.eql(rawExpected2[0]);
384 | return Node.query('*', '/tmp/node_test_dir').then((res1) => {
385 | expect(res1.length).to.eql(res1.length);
386 | return Node.query('**', '/tmp/node_test_dir').then((res2) => {
387 | expect(res2.length).to.eql(rawExpected2.length);
388 | });
389 | });
390 | });
391 | })();
392 |
393 | it('.remove() && .removeSync()', function () {
394 | const node = new Node('/tmp/node_test_dir/example_file.md');
395 | const node2 = new Node('/tmp/node_test_dir/another_example_file.md');
396 | expect(node.existsSync()).to.eql(true);
397 | expect(node.removeSync()).to.eql(node);
398 | expect(node.existsSync()).to.eql(false);
399 |
400 | expect(node2.existsSync()).to.eql(true);
401 | return node2.remove().then(function () {
402 | expect(node2.existsSync()).to.eql(false);
403 | });
404 | });
405 |
406 | it('.parent() && .parentSync()', function () {
407 | const node = new Node('/tmp/node_test_dir/example_file.md');
408 | const parent = node.parentSync();
409 | expect(parent).to.be.instanceof(Directory);
410 | expect(parent.pathName).to.eql('/tmp/node_test_dir');
411 | return node.parent().then((parentAsync) => {
412 | expect(parentAsync).to.be.instanceof(Directory);
413 | expect(parentAsync.pathName).to.eql('/tmp/node_test_dir');
414 | });
415 | });
416 |
417 | it('.siblings() && .siblingsSync()', function () {
418 | const node = new Node('/tmp/node_test_dir/another_dir/c.php');
419 | const s1 = node.siblingsSync();
420 | expect(s1.length).to.eql(8);
421 | const exists = s1.some((el) => el.path === node.pathName);
422 | expect(exists).to.eql(false);
423 | const s2 = node.siblingsSync('*.php');
424 | expect(s2.length).to.eql(1);
425 | // Note: `*` doesn't
426 | const s3 = node.siblingsSync('*', {
427 | ignore: '*.php',
428 | dot: true,
429 | });
430 | expect(s3.length).to.eql(8);
431 | const s4 = node.siblingsSync({
432 | ignore: ['*.php'],
433 | });
434 | expect(s4.length).to.eql(7);
435 |
436 | // errors
437 | expect(() => node.siblingsSync([], {})).to.throw('should be a string');
438 | expect(() => node.siblingsSync('*', 'context')).to.throw('context');
439 | expect(() => node.siblingsSync('*', [])).to.throw('options');
440 | return node.siblings().then((ss1) => {
441 | expect(ss1.length).to.eql(8);
442 | return node
443 | .siblings('*.md', {
444 | ignore: 'README.md',
445 | })
446 | .then((ss2) => {
447 | expect(ss2.length).to.eql(1);
448 | expect(ss2).to.be.instanceof(draxt);
449 | expect(ss2.get(0).baseName).to.eql('g.md');
450 | });
451 | });
452 | });
453 |
454 | it('.moveTo() && .moveToSync() && .appendTo() && .appendToSync()', function () {
455 | const node = new Node('/tmp/node_test_dir/another_dir/c.php');
456 | expect(node.moveToSync('/tmp/node_test_dir/another_dir/.git')).to.eql(node);
457 | expect(node.pathName).to.eql('/tmp/node_test_dir/another_dir/.git/c.php');
458 | return node.moveTo('/tmp/node_test_dir').then(function () {
459 | expect(node.pathName).to.eql('/tmp/node_test_dir/c.php');
460 | expect(() => node.moveTo('/tmp/node_test_dir/', function () {})).to.throw(
461 | 'callback'
462 | );
463 | // @TODO: rewrite, test content had been changed.
464 | // expect(() => node.appendToSync('/tmp/node_test_dir/store')).to.throw(
465 | // 'dest already exists.'
466 | // );
467 | // return node
468 | // .appendTo('/tmp/node_test_dir/store', {overwrite: true})
469 | // .then(function () {
470 | // expect(node.pathName).to.eql('/tmp/node_test_dir/store/c.php');
471 | // expect(node.fs.readFileSync(node.pathName, 'utf8')).to.eql(
472 | // 'c.php content!'
473 | // );
474 | // });
475 | });
476 | });
477 | });
478 |
479 | /**
480 | * Exclude copy from others tests for creating example test files and directories
481 | * Reason: fs-extra is not fully compatible with mock-fs module!
482 | */
483 | describe('.copy() && .copySync()', function () {
484 | const nodePath = path.join(__dirname, '/test_dir/exmaple.node');
485 | const copyPath = path.join(__dirname, '/test_dir/non_existent/copied.php');
486 | const copyPath2 = path.join(__dirname, '/test_dir/copied.php');
487 | beforeEach(function () {
488 | fs.removeSync(path.join(__dirname, 'test_dir'));
489 | fs.ensureFileSync(nodePath);
490 | fs.writeFileSync(nodePath, 'example content!', 'utf8');
491 | });
492 | it('.copy() && .copySync', function () {
493 | const node = new Node(nodePath);
494 | expect(node.existsSync()).to.eql(true);
495 | expect(node.copySync(copyPath)).to.eql(node);
496 | expect(node.fs.readFileSync(copyPath, 'utf8')).to.eql('example content!');
497 | return node.copy(copyPath2).then(function () {
498 | expect(node.fs.readFileSync(copyPath2, 'utf8')).to.eql('example content!');
499 | });
500 | });
501 | afterEach(function () {
502 | fs.removeSync(path.join(__dirname, 'test_dir'));
503 | });
504 | });
505 | });
506 |
--------------------------------------------------------------------------------
/test/interfaces/SymbolicLink.js:
--------------------------------------------------------------------------------
1 | /* global describe, beforeEach, it*/
2 | const {SymbolicLink} = require('../../src/draxt').Node;
3 | const {expect} = require('chai');
4 |
5 | describe('SymbolicLink', () => {
6 | describe('initialization and basic methods', () => {
7 | const nodePath = '/fake/_fakepath';
8 | const stats = {};
9 | it('`new`', () => {
10 | const node = new SymbolicLink(nodePath, stats);
11 | expect(node.pathName).to.eql(nodePath);
12 | expect(node.extension).to.eql('');
13 | expect(node.name).to.eql('_fakepath');
14 | expect(node.baseName).to.eql('_fakepath');
15 | expect(node.parentPath).to.eql('/fake');
16 | expect(node.getCachedStats() === stats).to.eql(true);
17 | });
18 |
19 | it('path.parse methods', () => {
20 | const node = new SymbolicLink(nodePath, stats);
21 | expect(node.getPathName()).to.eql(nodePath);
22 | expect(node.getExtension()).to.eql('');
23 | expect(node.getName()).to.eql('_fakepath');
24 | expect(node.getBaseName()).to.eql('_fakepath');
25 | expect(node.getParentPath()).to.eql('/fake');
26 | });
27 | });
28 |
29 | describe('fs methods', () => {
30 | beforeEach(() => {
31 | const {execSync} = require('child_process');
32 | const pre = `
33 | rm -r /tmp/fake_dir
34 | mkdir /tmp/fake_dir
35 | mkdir /tmp/fake_dir/childdir
36 | echo 'example content.' > /tmp/fake_dir/example_file.md
37 | ln -s /tmp/fake_dir/example_file.md /tmp/fake_dir/childdir/sym1.md
38 | ln -s /tmp/fake_dir/non_existent.md /tmp/fake_dir/childdir/sym2
39 | `;
40 | execSync(pre);
41 | });
42 |
43 | // afterEach(() => {
44 | // vol.reset();
45 | // });
46 |
47 | it('.readlink() && .readlink()', () => {
48 | const sym1 = new SymbolicLink('/tmp/fake_dir/childdir/sym1.md');
49 | const sym2 = new SymbolicLink('/tmp/fake_dir/childdir/sym2');
50 | expect(sym1.readlinkSync()).to.eql('/tmp/fake_dir/example_file.md');
51 | return sym2.readlink().then((pathName) => {
52 | expect(pathName).to.eql('/tmp/fake_dir/non_existent.md');
53 | });
54 | });
55 |
56 | it('.isBroken() && .isBrokenSync()', () => {
57 | const sym1 = new SymbolicLink('/tmp/fake_dir/childdir/sym1.md');
58 | const sym2 = new SymbolicLink('/tmp/fake_dir/childdir/sym2');
59 | expect(sym1.isBrokenSync()).to.eql(false);
60 | return sym2.isBroken().then((ret) => {
61 | expect(ret).to.eql(true);
62 | });
63 | });
64 | });
65 | });
66 |
--------------------------------------------------------------------------------