# phylo

> File operations class

Latest version **1.0.0-rc.4** (published 2017-12-16) · MIT license · 0 weekly downloads

## Install

```sh
npm install phylo
pnpm add phylo
yarn add phylo
bun add phylo
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 1.0.0-rc.4 |
| Published | 2017-12-16 |
| First published | 2016-10-27 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | CommonJS |
| Node | >=8.9.0 |
| Dependencies | 5 |
| Known vulnerabilities | 0 (+3 in 2 direct dependencies) |
| Install scripts | no |
| GitHub stars | 0 |
| Author | dongryphon |
| Maintainers | dongryphon, israelroldan |
| Keywords | file, fs, glob, path |

## Links

- npm: https://www.npmjs.com/package/phylo
- Repository: https://github.com/dongryphon/phylo
- Homepage: https://github.com/dongryphon/phylo#readme
- Issues: https://github.com/dongryphon/phylo/issues
- npm.io page: https://npm.io/package/phylo

## Dependencies (5)

- [tmp](https://npm.io/package/tmp.md) 0.0.33
- [json5](https://npm.io/package/json5.md) ^0.5.1
- [which](https://npm.io/package/which.md) ^1.3.0
- [mkdirp](https://npm.io/package/mkdirp.md) ^0.5.1
- [rimraf](https://npm.io/package/rimraf.md) ^2.6.2

## Alternatives

- [unionfs](https://npm.io/package/unionfs.md) — 2.2M weekly downloads
- [path-starts-with](https://npm.io/package/path-starts-with.md) — 35.9K weekly downloads
- [redzip](https://npm.io/package/redzip.md) — 1.2K weekly downloads
- [vscode-anymatch](https://npm.io/package/vscode-anymatch.md) — 848 weekly downloads
- [@ledgerhq/coin-filecoin](https://npm.io/package/@ledgerhq/coin-filecoin.md) — 793 weekly downloads

## Recent versions

- 1.0.0-rc.4 (latest) — 2017-12-16
- 1.0.0-rc.3 — 2017-12-09
- 1.0.0-rc.2 — 2017-12-09
- 1.0.0-rc.1 — 2017-08-08
- 1.0.0-beta.7 — 2017-05-02
- 1.0.0-beta.6 — 2017-01-07
- 1.0.0-beta.5 — 2017-01-07
- 1.0.0-beta.4 — 2017-01-07
- 1.0.0-beta.3 — 2016-11-24
- 1.0.0-beta.2 — 2016-11-24
- 1.0.0-beta.1 — 2016-11-24
- 0.0.2 — 2016-11-16
- 0.0.1 — 2016-10-27

## README

# phylo

[![Build Status](https://travis-ci.org/dongryphon/phylo.svg?branch=master)](https://travis-ci.org/dongryphon/phylo)
[![Dependencies Status](https://david-dm.org/dongryphon/phylo/status.svg)](https://david-dm.org/dongryphon/phylo)
[![npm version](https://badge.fury.io/js/phylo.svg)](https://badge.fury.io/js/phylo)
[![MIT Licence](https://badges.frapsoft.com/os/mit/mit.svg?v=103)](https://opensource.org/licenses/mit-license.php)

Phylo (pronounced "File-o") is a File operations class designed for maximum
convenience and clarity of expression. The primary export of `phylo` is the
`File` class which is used to wrap a file-system path string.

Consider some examples:

![screenshot](screenshot.png)

The `root` value is determined 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 `pkgFile` 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.

The `load()` method will read the file and parse the contents into an object (since
the file is type `'.json'`).

If you like infinite loops, you can try this on Windows:

    var path = require('path');
    
    for (var d = process.cwd(); d; d = path.resolve(d, '..')) {
        // climb up...
    }

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 the above to the same idea using `File`:

    for (var d = File.cwd(); d; d = d.parent) {
        // climb up...
    }

## Conventions

The `File` API strives to be purely consistent on these points:

 - Methods that take path parameters accept `String` or `File` instances.
 - Methods that end in `Path` return a `String`. Otherwise they return a `File`
  instance (when paths are involved).
 - Asynchronous methods are named with the "async" prefix and return a Promise.
 - Callbacks passed to async methods can return immediate results or Promises.
 - As much as possible, exceptions and `null` return values are avoided. For
  example, `stat()` returns an object in all cases but that object may have an
  `error` property.
 - Where reasonable, objects are cached to avoid GC pressure. For example, things
  like access masks, file attributes, status errors, directory list modes, etc. are
  lazily cached as immutable (`Object.freeze()` enforced) instances and reused as
  needed.

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

Much of the functionality provided by the `File` class is in the form of "lexical"
path manipulation. These are only provided in synchronous form since they operate on
path strings (like the `path` module).

### Properties

Instances of `File` provide these **readonly** properties:

 - `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'`).
 - `parent` - The `File` for the parent directory (`null` at root).
 - `fspath` - The `path` string resolved for `'~'` (usable by `fs` or `path` modules)

### Methods

The methods that perform work on the path text and return `File` instances as a
result are:

 - `absolutify()` - Calls `path.resolve(this.path)`
 - `join()` - Joins all arguments using `path.join()`
 - `nativize()` - Make all separators native (`'\'` on Windows, `'/'` elsewhere)
 - `normalize()`- Calls `path.normalize(this.path)`
 - `relativize()`- Calls `path.relative()`
 - `resolve()`- Calls `path.resolve()` on all the arguments
 - `slashify()`- Make all separators `'/'` (Windows does understand them)
 - `terminate()` - Ensure there is a trailing separator
 - `unterminate()` - Ensure there is no trailing separator

To retrieve strings as a result, you can use these methods:

 - `absolutePath()` - Same as `absolutify` but returns a string
 - `joinPath()` - Same as `join` but returns a string
 - `nativePath()` - Same as `nativize` but returns a string
 - `normalizedPath()` - Same as `normalize` but returns a string
 - `relativePath()` - Same as `relativize` but returns a string
 - `resolvePath()` - Same as `resolve` but returns a string
 - `slashifiedPath()` - Same as `slashify` but returns a string
 - `terminatedPath()` - Same as `terminate` but returns a string
 - `unterminatedPath()` - Same as `unterminate` but returns a string

Some path operations perform I/O to the file-system and so provide both synchronous
and asynchronous versions.

 - `canonicalize()` - Calls `fs.realpathSync(this.path)` and returns a `File`
 - `canonicalPath()` - Same as `canonicalize` but returns a `String`

In asynchronous form:

 - `asyncCanonicalize()` - Same as `canonicalize` but Promises a `File`
 - `asyncCanonicalPath()` - Same as `asyncCanonicalize` but Promises a `String`

Canonicalization will result in `null` if there is no real file.

## Path Info and Comparison

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,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`)
 - `prefixes(o)` - Returns `true` if `this` is a path prefix of `o`. It is
  recommended to use `absolutify()` on both instances first to avoid confusion with
  `..` segments.

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, 'f')` 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

    var f1 = File.from('abc');
    var f2 = File.from('ABC');
    
    console.log(f1.equals(f2));
    
    > true   (on Windows and Mac)
    > false  (on Linux)

## File-System Information

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'`).
 - `exists()` - Returns `true` if the file exists.
 - `has(rel)` - Returns `true` if a file or folder exists at the `rel` path from this file.
 - `hasDir(rel)` - Returns `true` if a folder exists at the `rel` path from this file.
 - `hasFile(rel)` - Returns `true` if a file exists at the `rel` path from this file.
 - `isHidden()` - Returns `true` if this file does not exist or is hidden. Note that on
  Windows, hidden state is not based on a file name convention (".hidden") but is a bit
  stored in the file-system (see below).
 - `stat()` / `restat()` - Returns `fs.statSync(this.path)` (an `fs.Stats`). If the
  file does not exist (or some other error is encountered), this object will have an
  `error` property.
 - `statLink()` / `restatLink()` - Returns `fs.lstatSync(this.path)` (an `fs.Stats`). If
  the file does not exist (or some other error is encountered), this object will have an
  `error` property.

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.

In asynchronous form:

 - `asyncAccess()` - Promises a `File.Access`
 - `asyncCan(mode)` - Promises `true` or `false`.
 - `asyncExists()` - Promises `true` or `false`.
 - `asyncHas(rel)` - TODO
 - `asyncHasDir(rel)` - TODO
 - `asyncHasFile(rel)` - TODO
 - `asyncIsHidden()` - Promises `true` or `false`
 - `asyncStat()` / `asyncRestat()` - Promises an `fs.Stats` via `fs.stat()`
 - `asyncStatLink()` / `asyncRestatLink()` - Promises an `fs.Stats` via `fs.lstat()`

### File Status

The [`fs.Stat`](https://nodejs.org/api/fs.html#fs_class_fs_stats) structure is augmented
with an `attrib` property. This is an instance of `File.Attribute` and will have these
boolean properties:

 - `A` - Archive
 - `C` - Compressed
 - `E` - Encrypted
 - `H` - Hidden
 - `O` - Offline
 - `R` - Readonly
 - `S` - System

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
information on Windows. On other platforms, this object contains `false` values for all
of the above properties.

An `fs.Stat` object is cached on the `File` instance by the `stat()` family of methods
and a separate instance is cached on by the `statLink()` family. These are lazily
retrieved and then stored for future use. To get fresh copies, use the `restat()` family
of methods.

### File.Access

`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:

    try {
        let mode = fs.statSync(file).mode;
        
        if (mode & fs.constants.R_OK && mode & fs.constants.W_OK) {
            // file exists and is R and W
        }
    }
    catch (e) {
        // ignore... file does not exist
    }

Or using `File` and `File.Access`:

    if (file.access().rw) {
        // file exists and is R & W
    }

Alternatively, there is the `can()` method:

    if (file.can('rw')) {
        // file exists and is R & W
    }

When the file does not exist, or an error is encountered, the object returned by the
`access()` method will have an `error` property. Since the access bits are all `false`
in this case, this distinction if often unimportant (as above).

To check for errors:

    var acc = file.accecss();
    
    if (acc.rw) {
        // file exists and has R/W access
    }
    else if (acc.error === 'ENOENT') {
        // file does not exist...
    }
    else if (acc.error === 'EACCES' || acc.error === 'EPERM') {
        // access or permission error...
    }
    ...

There are a fixed set of immutable `File.Access` objects, one for each combination of
R, W and X permissions: `r`, `rw`, `rx`, `rwx`, `w`, `wx`, `x`. Each instance also has
these same properties as boolean values. The full set of properties is a bit larger:

 - `r` - True if `R_OK` is set.
 - `rw` - True if `R_OK` and `W_OK` are both set.
 - `rx` - True if `R_OK` and `X_OK` are both set.
 - `rwx` - True if `R_OK`, `W_OK` and `X_OK` are all set.
 - `w` - True if `W_OK` is set.
 - `wx` - True if `W_OK` and `X_OK` are both set.
 - `x` - True if `X_OK` is set.
 - `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'`

### Classification

It is often important to know if a file is a directory or other type of entity. This
information is fundamentally the business of the `stat()` family but for convenience is
also provided on the `File` instance:

 - `isDirectory(mode)`
 - `isFile(mode)`
 - `isBlockDevice(mode)`
 - `isCharacterDevice(mode)`
 - `isFIFO(mode)`
 - `isSocket(mode)`
 - `isSymbolicLink(mode)`

In addition, the following shorthand methods are also available:

 - `isDir(mode)` (alias for `isDirectory()`)
 - `isSymLink(mode)` (alias for `isSymbolicLink()`)

These are also available as async methods:

 - `asyncIsDir(mode)`
 - `asyncIsDirectory(mode)`
 - `asyncIsFile(mode)`
 - `asyncIsBlockDevice(mode)`
 - `asyncIsCharacterDevice(mode)`
 - `asyncIsFIFO(mode)`
 - `asyncIsSocket(mode)`
 - `asyncIsSymLink(mode)`
 - `asyncIsSymbolicLink(mode)`

The optional `mode` parameter can be `'l'` (lowercase-L) to use the `statLink()` (or
`asyncStatLink()`) method to determine the result.

Since the nature of a file seldom changes on a whim, these methods use the `stat()`
methods and their cached information. If this is undesired, these results can be
refreshed using the `restat()` family of methods.

## Directory Listing

You can get a directory listing of `File` objects using:

 - `list(mode, matcher)`
 - `asyncList(mode, matcher)`

The `mode` parameter is a string that consists of the following single letter codes
with the described meaning:

 - `A` - All files are listed, even hidden files. (default is `false`)
 - `d` - List only directories. (default is `false`)
 - `f` - List only files (non-directories). (default is `false`)
 - `l` - Cache the result of `statLink` for each file. (default is `false`)
 - `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`)
 - `w` - Indicates that Windows hidden flag alone determines hidden status
  (default is `false` so that files names starting with dots are hidden on all
  platforms).
 - `T` - Throw (or reject) on failure instead of returning (or resolving) `null`.

Some examples:

    // List non-hidden files/folders:
    dir.list();

    // lists all files/folders (including hidden):
    dir.list('A');

    // lists non-hidden files/folders and cache stat info:
    dir.list('s');

    // lists all files (no folders) and cache stat info:
    dir.list('Asf');

    // lists all files/folders and cache stat info but do not sort:
    dir.list('As-o');

The `s` option can be useful during an `asyncList()` operation to allow subsequent
use of the simpler, synchronous `stat()` method since it will use the cached stat
object.

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

### Ascent

To climb the file-system to find a parent folder that passes a `test` function or
has a particular file or folder relatively locatable from there:

 - `up(test)` - Starting at this, climb until `test` passes.
 - `upDir(rel)` - Use `up()` with `hasDir(rel)` as the `test`.
 - `upFile(rel)` - Use `up()` with `hasFile(rel)` as the `test`.

To climb the file-system and find a relatively locatable item:

 - `upTo(rel)` - Starting at this, climb until `has(rel)` is `true` and then return
  `join(rel)` from that location.
 - `upToDir(rel)` - Same as `upTo()` but using `hasDir(rel)` as the `test`.
 - `upToFile(rel)` - Same as `upTo()` but using `hasFile(rel)` as the `test`.

The different between these forms can be seen best by example:

    var file = File.cwd().up('.git');
    
    // file is the parent directory that has '.git', not the '.git'
    // folder itself. The file may be File.cwd() or some parent.
    
    var git = File.cwd().upTo('.git');
    
    // git is the '.git' folder from perhaps File.cwd() or some other
    // parent folder.

Asynchronous forms (TODO - not implemented yet):

 - `asyncUp(test)` - TODO
 - `asyncUpDir(rel)` - TODO
 - `asyncUpFile(rel)` - TODO
 - `asyncUpTo(rel)` - TODO
 - `asyncUpToDir(rel)` - TODO
 - `asyncUpToFile(rel)` - TODO

### Descent

 - `tips(mode, test)` - Returns a `File[]` of the top-most items passing the `test`.
  Once a match is found, no descent into that folder is made (hence, the "tips" of
  the sub-tree). Uses `walk(mode)` to descend the file-system.
 - `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
  should be provided.

The `walk` method's `before` and `after` handlers looks like this:

    function beforeOrAfter (file, state) {
        if (file.isDir() && ...) {
            return false;  // do not recurse into file (before only)
        }
        
        if (...) {
            state.stop = true;  // stop all further walking
        }
    }

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.
 - `root` - The `File` used to start the descent.
 - `stack` - A `File[]` of instances starting with the `File` used to start things.
 - `stop` - A boolean property that can be set to `true` to abort the `walk`.
 
The `tips` method's `test` looks like this:

    function test (file, state) {
        if (file.hasFile('package.json')) {
            return true; // file is a tip so gather it up and don't descend
        }
        
        return false; // keep going and/or descending
    }

The `state` parameter is the same as for the `walk` method.
 
Asynchronous forms:

 - `asyncTips(mode, test)`
 - `asyncWalk(mode, matcher, before, after)`

The `test`, `before` and `after` handlers of the asynchronous methods
accept the same parameters and return the same results as with the
synchronous forms. They can, alternatively, return a Promise if their
determination is also async.

## Reading and Writing Files

Basic file reading and decoding/parsing are provided by these methods:

 - `asyncLoad(options)` - Same as `load()` except a Promise is returned.
 - `load(options)` - Reads, decodes and parses the file according to 
  the provided `options`.

And serializing, encoding and writing is provided by:

 - `asyncSave(data, options)` - Same as `save()` except a Promise is
  returned.
 - `save(data, options)` - Serializes and encodes the data and writes
  it to this file using the specified `options`.

The act of loading a file consists initially of reading the data (obviously). To
get this part right, you need an `encoding` option which is tedious to setup in
the `fs` API, especially if the file name holds the clues you need.

Compare:

    var pkg = path.join(dir, 'package.json');

    var data = JSON.parse(fs.readfileSync(pkg, 'utf8'));

To loading using `File`:

    var pkg = dir.join('package.json');

    var data = pkg.load();

The basic advantage of the `File` approach is the error messages you get when
things go wrong. Using the first snippet you would get errors like these (based
on the parser used):

    Unexpected number in JSON at position 427

Using `load()` the message would be:

    Cannot parse ~/code/package.json: Unexpected number in JSON at position 427

With `File` there is hope in tracking down what has gone wrong.

When it is time to save the data, the process looks very symmetric:

    pkg.save(data);

Instead of the manual alternative:

    fs.writeFileSync(pkg, JSON.stringify(data, null, '    '), 'utf8');

**NOTE:** Unlike most of the `File` API, these methods throw exceptions (or reject
Promises) on failure.

### Predefined Readers

Readers are objects that manage options for reading and parsing files. The following
readers come predefined:

 - `bin` - An alias for `binary`.
 - `binary` - Reads a file as a buffer.
 - `json` - Extends the `text` reader and provides a `parse` method to
  deserialize JSON data. This uses the [`json5`](https://www.npmjs.com/package/json5)
  module to tolerate human friendly JSON.
 - `json:strict` - Extends `text` reader and uses strict `JSON.parse()`.
 - `text` - Reads a file as `utf8` encoding.
 - `txt` - An alias for `text`.

### Predefined Writers

Writers are objects that manage options for serializing and writing files. The
following writers come predefined:

 - `bin` - An alias for `binary`.
 - `binary` - Writes a file from a buffer.
 - `json` - Extends the `text` writer and provides a `serialize` method to
  write JSON data.
 - `json5` - Extends `json` writer and uses `json5.stringify()`.
 - `text` - Writes a file as `utf8` encoding. Accepts a `join` string option to
  join the data if the data is an array (of lines perhaps).
 - `txt` - An alias for `text`.

### Reader Options

The default reader is selected based on the file's type, but we can override this:
    
    var data = pkg.load('text'); // load as a simple text (not parsed)
    
Other options can be specified (e.g. to split by new-line):
    
    var data = pkg.load({
        type: 'text',
        split: /\n/g
    });

Readers support the following configuration properties:

 - `parse` - A function called to parse the file content. The method accepts two
  arguments: `data` and `reader`. The `data` parameter is the file's content and
  the `reader` is the fully configured `reader` instance.
 - `split` - An optional `RegExp` or `String` for a call to `String.split()`. This
  is used by the default `parse` method.

In addition to `reader` configuration, the `fs.readFile()` options can be supplied:

    var content = file.load({
        // The options object is passed directly to fs.readFile()
        options: {
            ...
        }
    });

The `encoding` can be specified in the `options` or directly to the `reader`:

    var content = file.load({
        encoding: 'utf16'
    });

    // Or on the fs options:
    
    var content = file.load({
        options: {
            encoding: 'utf16'
        }
    });

### Writer Options

The default writer is selected based on the file's type, but we can override this:
    
    pkg.save(data, 'text');
    
Other options can be specified (e.g. to join lines in an array with new-lines):
    
    pkg.save(data, {
        type: 'text',
        join: '\n'
    });

Writers support the following configuration properties:

 - `serialize` - A function called to convert the data and return what will be written
  to disk. The method accepts two arguments: `data` and `writer`. The `data` parameter
  is the raw file data and the `writer` is the fully configured `writer` instance.
 - `join` - An optional `String` for a call to `Array.join()` when file data is
  an array. This is used by the default `serialize` method.

The `json` writer also supports these properties:

 - `indent` maps to the `space` parameter for `JSON.stringify`.
 - `replacer` map to the `replacer` parameter for `JSON.stringify`.

In addition to `writer` configuration, the `fs.writeFile()` options can be supplied:

    file.save(data, {
        // The options object is passed directly to fs.writeFile()
        options: {
            ...
        }
    });

The `encoding` can be specified in the `options` or directly to the `writer`:

    file.save(data, {
        encoding: 'utf16'
    });

    // Or on the fs options:
    
    file.save(data, {
        options: {
            encoding: 'utf16'
        }
    });

## 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

The most useful static methods are for conversion.

    var file = File.from(dir);

Regardless if the value of `dir` above is a `String` or `File`, `file` is a `File`
instance. If `dir` is `null` or `''` then `file` will be `null`.

In reverse:

    var s = File.path(file);

The `path()` method accepts `String` or `File` and returns the path (the original
string or the `path` property of the `File`). Similar to `from()`, the `path()` method
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:

    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:

    var s = File.from(file).fspath;

### Utility Methods

 - `access(fs)` - Returns a `FileAccess` for the `File` or `String`.
 - `exists(fs)` - Returns true if the `File` or `String` exists.
 - `isDir(fs)` - Returns true if the `File` or `String` is an existing directory.
 - `isFile(fs)` - Returns true if the `File` or `String` is an existing file.
 - `join(fs...)` - Return `path.join()` on the `File` or `String` args as a `File`.
 - `joinPath(fs...)` - Return `path.join()` on the `File` or `String` args as a `String`.
 - `resolve(fs...)` - Return `path.resolve()` on the `File` or `String` args as a `File`.
 - `resolvePath(fs...)` - Return `path.resolve()` on the `File` or `String` args as a `String`.
 - `split(fs)`- Returns a `String[]` from the `File` or `String`.
 - `stat(fs)` - Returns the `stat()` for the `File` or `String`.
 - `sorter(fs1, fs2)` - Calls `File.from(fs1).compare(fs2)` (useful for sorting
  `File[]` and `String[]`).

There are no asynchronous forms of these utility methods since they wouldn't really
save much:

Since this is not provided:

    File.asyncExists(file).then(exists => {
        ...
    });

Instead just do this:

    File.from(file).asyncExists().then(exists => {
        ...
    });

## Locating Special Folders

 - `cwd()` - Wraps `process.cwd()` as a `File`.
 - `home()` - Wraps `os.homedir()` as a `File`.
 - `profile()` - Returns the platform-favored storage folder for app data.
 - `temp()` / `asyncTemp()` - Temporary folder for this application.
 - `which(name,opts)` - Searches the **PATH** for a program by name. The `opts` can be
  a replacement **PATH** or an object with a `path` property that is the replacement.
  The asynchronous form is `asyncWhich(name,opts)`.

### Temp

The `temp()` and `asyncTemp()` static methods use the [`tmp`](https://www.npmjs.com/package/tmp)
module to generate a temporary folder in the appropriate location for the platform.

When these methods are called with no `options` argument, they lazily
create (and cache for future requests) a single temporary folder.

### Profile

The `profile()` method handles the various OS preferences for storing application
data.

 - Windows: `C:\Users\Name\AppData\Roaming\Company`
 - Mac OS X: `/Users/Name/Library/Application Support/Company`
 - Linux: `/home/name/.local/share/data/company`
 - Default: `/home/name/.company`

The "Company" part can be passed as the argument to `profile()` but is better left to
the top-level application to set `File.COMPANY`.

    File.COMPANY = 'Acme';

Now all libraries that use `phylo` will automatically store their profile data in the
proper folder for the user-facing application. In such a scenario it would be wise to
use the module name in the filename to ensure no collisions occur.

### The Magic Tilde

A common pseudo-root folder for the user's home folder is `'~'`. One often sees
paths like this:

    var dir = new File('~/.acme');

The `'~'` pseudo-root is recognized throughout `File` methods. It is resolved to the
actual location using `absolutify()` or `canonicalize()` (or their other flavors). In
other cases the pseudo-root is preserved. For example:

    var dir = new File('~/.acme');
    
    console.log(dir.parent); // just '~'
    console.log(dir.join('foo'));  // ~/acme/foo

These `File` instances can be read using `load()` as well:

    var data = File.from('~/.acme/settings.json').load();

In addition there is also the `'~~/'` pseudo-root that maps the the `profile()` directory
instead of the raw homedir.

That is:

    File.COMPANY = 'Acme';

    console.log(File.from('~/foo').absolutePath());
    console.log(File.from('~~/foo').absolutePath());

    // Windows:
    > C:\Users\MyName\foo
    > C:\Users\MyName\AppData\Roaming\Acme\foo

    // Mac OS X:
    > /Users/MyName/foo
    > /Users/MyName/Library/Application Support/foo

## Finding Programs

The `which()` method can be used like the standard `which` shell command:

    let nodejs = File.which('node');

If the program is not found, `null` is returned. Other problems will result in an thrown
`Error`.

The asynchronous form is similar:

    File.asyncWhich('node').then(nodejs => {
        if (nodejs) {
            // found it
        }
        else {
            // not found
        }
    },
    e => {
        // some error
    });

You can also customize the search to your own list of directories:

    let app = File.which('app', [ '~/bin', '.' ]);

## Creating Directories

You can create a directory structure using the `mkdir()` method (or `asyncMkdir()`).
These methods create as many directory levels as needed to create the path described
by the `File` instance.

    var dir = File.from('~~/foo').mkdir();

The `mkdir()` method returns the `File` instance after creating the directory tree.

Unlike many other `File` methods, if `mkdir()` fails it will throw an `Error`.

## Credits

Phylo owes many thanks to the following modules and, of course, their authors and
maintainers (in alphabetic order):

 - fswin [npm](https://www.npmjs.com/package/fswin) / [GitHub](https://github.com/xxoo/node-fswin)
 - json5 [npm](https://www.npmjs.com/package/json5) / [GitHub](https://github.com/aseemk/json5)
 - mkdirp [npm](https://www.npmjs.com/package/mkdirp) / [GitHub](https://github.com/substack/node-mkdirp)
 - rimraf [npm](https://www.npmjs.com/package/rimraf) / [GitHub](https://github.com/isaacs/rimraf)
 - tmp [npm](https://www.npmjs.com/package/tmp) / [GitHub](https://github.com/raszi/node-tmp)
 - which [npm](https://www.npmjs.com/package/which) / [GitHub](https://github.com/isaacs/node-which)

## Conclusion

For some opinions on when to use async methods see [here](Async-vs-Sync.md).

Enjoy!

Copyright (c) 2016 [Donald Griffin](https://github.com/dongryphon)

---
_Source: https://npm.io/package/phylo · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
