phylo v1.0.0-rc.4
phylo
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:

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
StringorFileinstances. - Methods that end in
Pathreturn aString. Otherwise they return aFileinstance (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
nullreturn values are avoided. For example,stat()returns an object in all cases but that object may have anerrorproperty. - 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 obviouslyIt 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 aString(passed to theconstructor).extent- The file's type as aString(e.g.,'json').name- The file's name as aString(e.g.,'package.json').parent- TheFilefor the parent directory (nullat root).fspath- Thepathstring resolved for'~'(usable byfsorpathmodules)
Methods
The methods that perform work on the path text and return File instances as a
result are:
absolutify()- Callspath.resolve(this.path)join()- Joins all arguments usingpath.join()nativize()- Make all separators native ('\'on Windows,'/'elsewhere)normalize()- Callspath.normalize(this.path)relativize()- Callspath.relative()resolve()- Callspath.resolve()on all the argumentsslashify()- Make all separators'/'(Windows does understand them)terminate()- Ensure there is a trailing separatorunterminate()- Ensure there is no trailing separator
To retrieve strings as a result, you can use these methods:
absolutePath()- Same asabsolutifybut returns a stringjoinPath()- Same asjoinbut returns a stringnativePath()- Same asnativizebut returns a stringnormalizedPath()- Same asnormalizebut returns a stringrelativePath()- Same asrelativizebut returns a stringresolvePath()- Same asresolvebut returns a stringslashifiedPath()- Same asslashifybut returns a stringterminatedPath()- Same asterminatebut returns a stringunterminatedPath()- Same asunterminatebut returns a string
Some path operations perform I/O to the file-system and so provide both synchronous and asynchronous versions.
canonicalize()- Callsfs.realpathSync(this.path)and returns aFilecanonicalPath()- Same ascanonicalizebut returns aString
In asynchronous form:
asyncCanonicalize()- Same ascanonicalizebut Promises aFileasyncCanonicalPath()- Same asasyncCanonicalizebut Promises aString
Canonicalization will result in null if there is no real file.
Path Info and Comparison
Some useful information about a file path:
isAbsolute()- Returnstrueif the file an absolute path (path.isAbsolute())isRelative()- Returnstrueif 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 ifthisis less-than, equal or greater-thano. By default, directories sort before files (first ='d'). To instead group files before directories, pass'f'. To compare only paths, passfalse. in which case files sort before directories.equals(o)- Returnstrueifthisis equal too(compare(o) === 0)prefixes(o)- Returnstrueifthisis a path prefix ofo. It is recommended to useabsolutify()on both instances first to avoid confusion with..segments.
There are some static sort methods that can be used by Array.sort():
File.sorter- Callsf1.compare(f2, 'd')to group directories before files.File.sorterFilesFirst- Callsf1.compare(f2, 'f')to group files first.File.sorterByPath- Callsf1.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 aFile.Accessobject. If the file does not exist (or some other error is encountered), this object will have anerrorproperty.can(mode)- Returnstrueif this exists with the desired access (modeis'r','rw','rwx','w','wx'or'x').exists()- Returnstrueif the file exists.has(rel)- Returnstrueif a file or folder exists at therelpath from this file.hasDir(rel)- Returnstrueif a folder exists at therelpath from this file.hasFile(rel)- Returnstrueif a file exists at therelpath from this file.isHidden()- Returnstrueif 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()- Returnsfs.statSync(this.path)(anfs.Stats). If the file does not exist (or some other error is encountered), this object will have anerrorproperty.statLink()/restatLink()- Returnsfs.lstatSync(this.path)(anfs.Stats). If the file does not exist (or some other error is encountered), this object will have anerrorproperty.
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 aFile.AccessasyncCan(mode)- Promisestrueorfalse.asyncExists()- Promisestrueorfalse.asyncHas(rel)- TODOasyncHasDir(rel)- TODOasyncHasFile(rel)- TODOasyncIsHidden()- PromisestrueorfalseasyncStat()/asyncRestat()- Promises anfs.Statsviafs.stat()asyncStatLink()/asyncRestatLink()- Promises anfs.Statsviafs.lstat()
File Status
The fs.Stat structure is augmented
with an attrib property. This is an instance of File.Attribute and will have these
boolean properties:
A- ArchiveC- CompressedE- EncryptedH- HiddenO- OfflineR- ReadonlyS- 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 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 ifR_OKis set.rw- True ifR_OKandW_OKare both set.rx- True ifR_OKandX_OKare both set.rwx- True ifR_OK,W_OKandX_OKare all set.w- True ifW_OKis set.wx- True ifW_OKandX_OKare both set.x- True ifX_OKis set.mask- The combination offs.constantsflagsR_OK,W_OKand/orX_OKname- 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 forisDirectory())isSymLink(mode)(alias forisSymbolicLink())
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 isfalse)d- List only directories. (default isfalse)f- List only files (non-directories). (default isfalse)l- Cache the result ofstatLinkfor each file. (default isfalse)o- Order the items bysorter. (default istrue)O- Order the items bysorterFilesFirst. (default isfalse)s- Cache the result ofstatfor each file. (default isfalse)w- Indicates that Windows hidden flag alone determines hidden status (default isfalseso 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$/iThis 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$/iFile-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 untiltestpasses.upDir(rel)- Useup()withhasDir(rel)as thetest.upFile(rel)- Useup()withhasFile(rel)as thetest.
To climb the file-system and find a relatively locatable item:
upTo(rel)- Starting at this, climb untilhas(rel)istrueand then returnjoin(rel)from that location.upToDir(rel)- Same asupTo()but usinghasDir(rel)as thetest.upToFile(rel)- Same asupTo()but usinghasFile(rel)as thetest.
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)- TODOasyncUpDir(rel)- TODOasyncUpFile(rel)- TODOasyncUpTo(rel)- TODOasyncUpToDir(rel)- TODOasyncUpToFile(rel)- TODO
Descent
tips(mode, test)- Returns aFile[]of the top-most items passing thetest. Once a match is found, no descent into that folder is made (hence, the "tips" of the sub-tree). Useswalk(mode)to descend the file-system.walk(mode, matcher, before, after)- Callsbeforefor all items thatlist(mode, matcher)generates recursively, then processes those items and lastly callsafter. Bothbeforeandafterare 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 currentFilebeing processed.root- TheFileused to start the descent.stack- AFile[]of instances starting with theFileused to start things.stop- A boolean property that can be set totrueto abort thewalk.
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 asload()except a Promise is returned.load(options)- Reads, decodes and parses the file according to the providedoptions.
And serializing, encoding and writing is provided by:
asyncSave(data, options)- Same assave()except a Promise is returned.save(data, options)- Serializes and encodes the data and writes it to this file using the specifiedoptions.
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 427Using load() the message would be:
Cannot parse ~/code/package.json: Unexpected number in JSON at position 427With 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 forbinary.binary- Reads a file as a buffer.json- Extends thetextreader and provides aparsemethod to deserialize JSON data. This uses thejson5module to tolerate human friendly JSON.json:strict- Extendstextreader and uses strictJSON.parse().text- Reads a file asutf8encoding.txt- An alias fortext.
Predefined Writers
Writers are objects that manage options for serializing and writing files. The following writers come predefined:
bin- An alias forbinary.binary- Writes a file from a buffer.json- Extends thetextwriter and provides aserializemethod to write JSON data.json5- Extendsjsonwriter and usesjson5.stringify().text- Writes a file asutf8encoding. Accepts ajoinstring option to join the data if the data is an array (of lines perhaps).txt- An alias fortext.
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:dataandreader. Thedataparameter is the file's content and thereaderis the fully configuredreaderinstance.split- An optionalRegExporStringfor a call toString.split(). This is used by the defaultparsemethod.
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:dataandwriter. Thedataparameter is the raw file data and thewriteris the fully configuredwriterinstance.join- An optionalStringfor a call toArray.join()when file data is an array. This is used by the defaultserializemethod.
The json writer also supports these properties:
indentmaps to thespaceparameter forJSON.stringify.replacermap to thereplacerparameter forJSON.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.
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 aFileAccessfor theFileorString.exists(fs)- Returns true if theFileorStringexists.isDir(fs)- Returns true if theFileorStringis an existing directory.isFile(fs)- Returns true if theFileorStringis an existing file.join(fs...)- Returnpath.join()on theFileorStringargs as aFile.joinPath(fs...)- Returnpath.join()on theFileorStringargs as aString.resolve(fs...)- Returnpath.resolve()on theFileorStringargs as aFile.resolvePath(fs...)- Returnpath.resolve()on theFileorStringargs as aString.split(fs)- Returns aString[]from theFileorString.stat(fs)- Returns thestat()for theFileorString.sorter(fs1, fs2)- CallsFile.from(fs1).compare(fs2)(useful for sortingFile[]andString[]).
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()- Wrapsprocess.cwd()as aFile.home()- Wrapsos.homedir()as aFile.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. Theoptscan be a replacement PATH or an object with apathproperty that is the replacement. The asynchronous form isasyncWhich(name,opts).
Temp
The temp() and asyncTemp() static methods use the 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/fooThese 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/fooFinding 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 / GitHub
- json5 npm / GitHub
- mkdirp npm / GitHub
- rimraf npm / GitHub
- tmp npm / GitHub
- which npm / GitHub
Conclusion
For some opinions on when to use async methods see here.
Enjoy!
Copyright (c) 2016 Donald Griffin
8 years ago
8 years ago
8 years ago
8 years ago
8 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago