{ | ||
"name": "phylo", | ||
"version": "1.0.0-beta.1", | ||
"version": "1.0.0-beta.2", | ||
"description": "File operations class", | ||
@@ -5,0 +5,0 @@ "main": "File.js", |
@@ -11,3 +11,3 @@ 'use strict'; | ||
var re = gg.compile('**/*.txt'); | ||
var re = gg.compile('*.{txt,js}[abc]'); | ||
console.log(re); | ||
@@ -14,0 +14,0 @@ console.log('match:', re.exec('C:\\Program Files/foo.txt')); |
260
README.md
@@ -11,28 +11,31 @@ # phylo | ||
// Starting in cwd, climb up as needed until a directory | ||
// containing "package.json" is found and then load that | ||
// file to return an object. | ||
var root = File.cwd().up('.git'); | ||
var pkg = File.cwd().upToFile('package.json').load(); | ||
// Starting in cwd, climb up as needed to find the Git | ||
// VCS root directory (not the ".git" folder itself): | ||
var root = File.cwd().up('.git'); | ||
If you like infinite loops, try this on Windows: | ||
The `root` value is by looking for a directory with a `'.git'` file or folder in | ||
it, starting at `cwd` and climbing up as necessary. When that location is found, | ||
it is returned as a `File` object. Note, this is not the `'.git'` folder itself, | ||
but the folder that _contains_ the `'.git'` folder (that is, the VCS root). | ||
The `pkg` value is determined in a similar manner but with two differences. The | ||
first is that the location for which we are searching must contain a _file_ (not | ||
a _folder_) with the name `'package.json'`. Secondly, it is the `'package.json'` | ||
file that is returned as a `File` instance, not the location that contained it. | ||
If you like infinite loops, you can try this on Windows: | ||
var path = require('path'); | ||
for (var dir = process.cwd(); dir; dir = path.resolve(dir, '..')) { | ||
for (var d = process.cwd(); d; d = path.resolve(d, '..')) { | ||
// climb up... | ||
} | ||
This innocent loops works on Linux/Mac because `path.resolve('/', '..')` returns | ||
a falsey value. On Windows, however, `path.resolve('C:\\', '..')` returns... well | ||
`"C:\\"`. | ||
This innocent loop works on Linux and Mac OS X because `path.resolve('/', '..')` | ||
returns a falsy value. On Windows, however, `path.resolve('C:\\', '..')` returns... | ||
well `'C:\\'`! | ||
Compare to `File`: | ||
Compare the above to the same idea using `File`: | ||
for (var dir = File.cwd(); dir; dir = dir.parent) { | ||
for (var d = File.cwd(); d; d = d.parent) { | ||
// climb up... | ||
@@ -43,6 +46,2 @@ } | ||
It is intended that a `File` instance immutably describes a single path. What is | ||
(or is not) on disk at that location can change of course, but the description is | ||
constant. | ||
The `File` API strives to be purely consistent on these points: | ||
@@ -63,2 +62,21 @@ | ||
The conflict between Node.js `path` and `fs` API's is a major reason for these | ||
naming conventions. Consider: | ||
let s = path.join(process.cwd(), 'foo'); // sync | ||
fs.mkdir(s); // async! | ||
Using `File`: | ||
let f = File.cwd().join('foo'); // sync (of course); | ||
f.mkdir(); // also sync | ||
f.asyncMkdir().then(... // async obviously | ||
It is intended that a `File` instance immutably describes a single path. What is | ||
(or is not) on disk at that location can change of course, but the description is | ||
constant. | ||
## Path Manipulation | ||
@@ -75,6 +93,6 @@ | ||
- `path` - The path to the file as a `String` (passed to the `constructor`). | ||
- `extent` - The file's type as a `String` (e.g., "json"). | ||
- `name` - The file's name as a `String` (e.g., "package.json"). | ||
- `extent` - The file's type as a `String` (e.g., `'json'`). | ||
- `name` - The file's name as a `String` (e.g., `'package.json'`). | ||
- `parent` - The `File` for the parent directory (`null` at root). | ||
- `fspath` - The `path` string resolved for "~" (usable by `fs` or `path` modules) | ||
- `fspath` - The `path` string resolved for `'~'` (usable by `fs` or `path` modules) | ||
@@ -123,5 +141,14 @@ ### Methods | ||
Some useful information about a file path: | ||
- `isAbsolute()` - Returns `true` if the file an absolute path (`path.isAbsolute()`) | ||
- `isRelative()` - Returns `true` if the file a relative path (`path.isRelative()`) | ||
You can compare two paths in a few different ways: | ||
- `compare(o)` - Returns -1, 0 or 1 if `this` is less, equal or greater than `o` | ||
- `compare(o,first)` - Returns -1, 0 or 1 if `this` is less-than, equal or | ||
greater-than `o`. By default, directories sort before files (first = `'d'`). To | ||
instead group files before directories, pass `'f'`. To compare only paths, pass | ||
`false`. | ||
in which case files sort before directories. | ||
- `equals(o)` - Returns `true` if `this` is equal to `o` (`compare(o) === 0`) | ||
@@ -132,2 +159,8 @@ - `prefixes(o)` - Returns `true` if `this` is a path prefix of `o`. It is | ||
There are some static sort methods that can be used by `Array.sort()`: | ||
- `File.sorter` - Calls `f1.compare(f2, 'd')` to group directories before files. | ||
- `File.sorterFilesFirst` - Calls `f1.compare(f2, 'df)` to group files first. | ||
- `File.sorterByPath` - Calls `f1.compare(f2, false)` to sort only by path. | ||
File name comparisons are case-insensitive on Windows and Mac OS X, so we have | ||
@@ -143,15 +176,10 @@ | ||
Some useful information about a file path: | ||
- `isAbsolute()` - Returns `true` if the file an absolute path (`path.isAbsolute()`) | ||
- `isRelative()` - Returns `true` if the file a relative path (`path.isRelative()`) | ||
## File-System Information | ||
To get information about the file on disk: | ||
To get information about the file on disk (synchronously): | ||
- `access()` - Returns a `File.Access` object. If the file does not exist (or some | ||
other error is encountered), this object will have an `error` property. | ||
- `can(mode)` - Returns `true` if this exists with the desired access (`mode` is "r", | ||
"rw", "rwx", "w", "wx" or "x"). | ||
- `can(mode)` - Returns `true` if this exists with the desired access (`mode` is `'r'`, | ||
`'rw'`, `'rwx'`, `'w'`, `'wx'` or `'x'`). | ||
- `exists()` - Returns `true` if the file exists. | ||
@@ -171,4 +199,4 @@ - `has(rel)` - Returns `true` if a file or folder exists at the `rel` path from this file. | ||
The `error` property will be a value like `"ENOENT"` (for file/folder not found), and | ||
`"EACCES"` or `"EPERM"` for permission denied. These codes come directly from the | ||
The `error` property will be a value like `'ENOENT'` (for file/folder not found), and | ||
`'EACCES'` or `'EPERM'` for permission denied. These codes come directly from the | ||
underlying API. | ||
@@ -202,2 +230,11 @@ | ||
For example: | ||
if (File.cwd().join('package.json').stat().attrib.H) { | ||
// If the package.json file is hidden... (wat?) | ||
} | ||
Note, if there is no `'package.json'` file, the `stat()` method will return an object | ||
with an `error` property and an empty `attrib` object (it won't have `H` set). | ||
The [`fswin`](https://www.npmjs.com/package/fswin) module is used to retrieve this | ||
@@ -214,5 +251,5 @@ information on Windows. On other platforms, this object contains `false` values for all | ||
`File.Access` objects are succinct descriptions of read, write and execute permission | ||
masks. These replace the use of `fs.constants.R_OK`, `fs.constants.W_OK` and | ||
`fs.constants.X_OK`. For example: | ||
`File.Access` objects are descriptors of read, write and execute permission masks. | ||
These are much simpler to use than the `fs.constants.R_OK`, `fs.constants.W_OK` and | ||
`fs.constants.X_OK` bit-masks. For example: | ||
@@ -273,3 +310,3 @@ try { | ||
- `mask` - The combination of `fs.constants` flags `R_OK`, `W_OK` and/or `X_OK` | ||
- `name` - The string "r", "rw", "rx", "rwx", "w", "wx" or "x" | ||
- `name` - The string `'r'`, `'rw'`, `'rx'`, `'rwx'`, `'w'`, `'wx'` or `'x'` | ||
@@ -318,4 +355,4 @@ ### Classification | ||
- `list(mode)` | ||
- `asyncList(mode)` | ||
- `list(mode, matcher)` | ||
- `asyncList(mode, matcher)` | ||
@@ -330,2 +367,3 @@ The `mode` parameter is a string that consists of the following single letter codes | ||
- `o` - Order the items by `sorter`. (default is `true`) | ||
- `O` - Order the items by `sorterFilesFirst`. (default is `false`) | ||
- `s` - Cache the result of `stat` for each file. (default is `false`) | ||
@@ -358,2 +396,87 @@ - `w` - Indicates that Windows hidden flag alone determines hidden status | ||
The `matcher` can be a function to call for each candidate. This function receives | ||
the arguments `(name, file)`. For example: | ||
dir.list(name => { | ||
return name.endsWith('.txt'); | ||
}); | ||
dir.list((name, f) => { | ||
return f.extent === 'txt'; // f is a File instance | ||
}); | ||
The `matcher` can also be a `RegExp`: | ||
dir.list(/\.txt$/i); | ||
Lastly, `matcher` can be a "glob" (a shell-like wildcard). In this case, since this | ||
is also a string, the `mode` must be passed first: | ||
dir.list('Af', '*.txt'); | ||
### Globs | ||
The basic form of globs is a file name and extension pattern (like `'*.txt'`). The `'*'` | ||
character matches only file name characters and not path separators (`'/'` and `'\'` on | ||
Windows). | ||
Internally globs are converted into `RegExp` objects. The conversion of `'*.txt'` is | ||
platform-specific. For Linux, it is: | ||
/^[^/]*\.txt$/ | ||
On Windows, it converts to this: | ||
/^[^\\/]*\.txt$/i | ||
This is because Windows uses either `'/'` and `'\'` as path separators and filenames | ||
are case-insensitive. | ||
To match paths, you can use a "glob star" such as `'**/*.txt'`. This glob converts to | ||
this on Linux: | ||
/^(?:[^/]*(?:[/]|$))*[^/]*\.txt$/ | ||
Globs also support groups inside `'{'` and `'}'` such as: `'*.{txt,js}'`: | ||
/^[^/]*\.(txt|js)$/ | ||
A character set like `'*.{txt,js}[abc]'` converts to: | ||
/^[^/]*\.(txt|js)[abc]$/ | ||
### Explicit Glob Conversion | ||
The glob parser has some advanced options via the `File.glob()` method. The `File.glob()` | ||
method converts a glob string into a `RegExp`. This conversion can be customized using | ||
the second argument as the `options`. This string can contain any of these characters: | ||
- `C` - Case-sensitivity is manual (disables auto-detection by platform) | ||
- `G` - Greedy `'*'` expansion changes `'*'` to match path separators (i.e., `'/'`) | ||
- `S` - Simple pattern mode (disables grouping and character sets) | ||
All other characters are passed as the `RegExp` flags (e.g., `'i'` and `'g'`). | ||
The `'S'` options enables "simple" glob mode which disables groups and character sets. | ||
For example: | ||
dir.list(File.glob('*.{txt,js}', 'S')); | ||
== /^[^/]*\.{txt\,js}$/ | ||
This would be useful when dealing with files that have `'{'` in their name. | ||
To force case-sensitive comparison (e.g., on Windows): | ||
let re = File.glob('*.txt', 'C'); | ||
/^[^\\/]*\.txt$/ | ||
To force case-insensitive comparison (e.g., on Linux), you need to use `'C'` to make | ||
this a manual choice, and `'i'` to make the `RegExp` ignore case: | ||
let re = File.glob('*.txt', 'Ci'); | ||
/^[^/]*\.txt$/i | ||
## File-System Traversal | ||
@@ -381,3 +504,3 @@ | ||
// file is the parent directory that has ".git", not the ".git" | ||
// file is the parent directory that has '.git', not the '.git' | ||
// folder itself. The file may be File.cwd() or some parent. | ||
@@ -387,3 +510,3 @@ | ||
// git is the ".git" folder from perhaps File.cwd() or some other | ||
// git is the '.git' folder from perhaps File.cwd() or some other | ||
// parent folder. | ||
@@ -405,4 +528,4 @@ | ||
the sub-tree). Uses `walk(mode)` to descend the file-system. | ||
- `walk(mode, before, after)` - Calls `before` for all items that | ||
`list(mode)` generates recursively, then processes those items and | ||
- `walk(mode, matcher, before, after)` - Calls `before` for all items that | ||
`list(mode, matcher)` generates recursively, then processes those items and | ||
lastly calls `after`. Both `before` and `after` are optional but one | ||
@@ -422,7 +545,11 @@ should be provided. | ||
} | ||
The optional `matcher` can be a `String` or a `RegExp` and have the same meaning | ||
as with `list()`. The `matcher` cannot, however, be a function. This is because it | ||
would be ambiguous with `before` and would really offer no advantage over handling | ||
things in the `before` method anyway. | ||
The `state` object has the following members: | ||
- `at` - The current `File` being processed. | ||
- `previous` - The `File` previously passed to the handler. | ||
- `root` - The `File` used to start the descent. | ||
@@ -447,3 +574,3 @@ - `stack` - A `File[]` of instances starting with the `File` used to start things. | ||
- `asyncTips(mode, test)` | ||
- `asyncWalk(mode, before, after)` | ||
- `asyncWalk(mode, matcher, before, after)` | ||
@@ -630,2 +757,29 @@ The `test`, `before` and `after` handlers of the asynchronous methods | ||
## Removing Files and Folders | ||
To remove a file or empty folder, you can use `remove()`: | ||
file.remove(); | ||
Internally, `remove()` calls either `fs.unlinkSync()` or `fs.rmdirSync()`. | ||
A folder tree can be removed by passing the `'r'` option: | ||
dir.remove('r'); | ||
This will synchronously remove all children of `dir` and then remove `dir` itself. | ||
Internally, this is handled by [`rimraf`](https://www.npmjs.com/package/rimraf). | ||
The asynchronous form of `remove()` is: | ||
dir.asyncRemove().then(() => { | ||
// dir is gone if it was empty | ||
}); | ||
Or: | ||
dir.asyncRemove('r').then(() => { | ||
// dir and its children are gone | ||
}); | ||
## Static Methods | ||
@@ -646,6 +800,6 @@ | ||
string or the `path` property of the `File`). Similar to `from()`, the `path()` method | ||
returns `''` when passed `null`. That value is still "falsey" but won't throw null | ||
returns `''` when passed `null`. That value is still falsy but won't throw null | ||
reference errors if used. | ||
There is also `fspath()` that resolves `"~"` path elements: | ||
There is also `fspath()` that resolves `'~'` path elements: | ||
@@ -655,3 +809,3 @@ var s = File.fspath(file); | ||
If the argument is already a `String` it is simply returned (just like the `path()` | ||
method). If the string may contain `"~"` elements, the safe conversion would be: | ||
method). If the string may contain `'~'` elements, the safe conversion would be: | ||
@@ -726,3 +880,3 @@ var s = File.from(file).fspath; | ||
A common "pseudo" root folder for the user's home folder is `"~"`. One often sees | ||
A common pseudo-root folder for the user's home folder is `'~'`. One often sees | ||
paths like this: | ||
@@ -732,3 +886,3 @@ | ||
The `"~"` pseudo-root is recognized throughout `File` methods. It is resolved to the | ||
The `'~'` pseudo-root is recognized throughout `File` methods. It is resolved to the | ||
actual location using `absolutify()` or `canonicalize()` (or their other flavors). In | ||
@@ -739,3 +893,3 @@ other cases the pseudo-root is preserved. For example: | ||
console.log(dir.parent); // just "~" | ||
console.log(dir.parent); // just '~' | ||
console.log(dir.join('foo')); // ~/acme/foo | ||
@@ -747,3 +901,3 @@ | ||
In addition there is also the `"~~/"` pseudo-root that maps the the `profile()` directory | ||
In addition there is also the `'~~/'` pseudo-root that maps the the `profile()` directory | ||
instead of the raw homedir. | ||
@@ -750,0 +904,0 @@ |
Sorry, the diff of this file is too big to display
172984
3.64%3987
0.05%905
20.51%12
-7.69%