#

API Docs for: 0.3.17
Show: Inherited Protected Private Deprecated

# File: lib/files.js

    YUI.add('files', function(Y) {

    /**
    * Ported fileutils methods from [Selleck](http://github.com/rgrove/selleck)
    * @class Files
    * @module yuidoc
    */

    Y.Files = {};

    /*
    Selleck
    Copyright (c) 2011 Yahoo! Inc.
    Licensed under the BSD License.
    */

    var fs = require('graceful-fs');
        fsPath   = require('path'),
        nodeUtil = require('util'),
        useFS = (fs.exists) ? fs : fsPath;

    var exists = function(file, cb) {
        if (cb) {
            useFS.exists(file, cb);
        } else {
            return useFS.existsSync(file);
        }
    };

    Y.Files.exists = exists;

    /**
    * Copy a directory from one location to another
    * @method copyDirectory
    * @param {Path} source The source directory
    * @param {Path} dest The destination directory
    * @param {Boolean} [overwrite=false] Whether or not to overwrite destination files
      if they already exist.
    * @param {Function} callback The callback to be executed when complete.
    **/
    function copyDirectory(source, dest, overwrite, callback) {
        // Allow callback as third arg.
        if (typeof overwrite === 'function') {
            callback = overwrite;
            overwrite = null;
        }

        fs.stat(source, afterSourceStat);

        function afterSourceStat(err, stats) {
            if (err) { return callback(err); }

            if (!stats.isDirectory()) {
                return callback(new Error("Source is not a directory: " + source));
            }

            fs.lstat(dest, afterDestStat);
        }

        function afterDestStat(err, stats) {
            if (err && err.code !== 'ENOENT') { return callback(err); }

            if (stats) {
                // If the destination is a file or a link, either delete it or
                // bubble an error if overwrite isn't true.
                if (stats.isFile() || stats.isSymbolicLink()) {
                    if (overwrite) {
                        deletePath(dest); // TODO: make this async
                    } else {
                        callback(new Error("Destination already exists: " + dest));
                        return;
                    }
                }

                afterMkDir();
            } else {
                fs.mkdir(dest, 0755, afterMkDir);
            }
        }

        function afterMkDir(err) {
            if (err && err.code !== 'EEXIST') { return callback(err); }
            fs.readdir(source, afterReadDir);
        }

        function afterReadDir(err, files) {
            if (err) { return callback(err); }

            var pending = files.length,
                filename;

            if (!pending) { return callback(); }

            while ((filename = files.shift())) {
                copyPath(fsPath.join(source, filename), fsPath.join(dest, filename), overwrite, function (err) {
                    if (err) { return callback(err); }

                    pending -= 1;

                    if (!pending) {
                        callback();
                    }
                });
            }
        }
    }
    Y.Files.copyDirectory = copyDirectory;

    /**
    * Copy a file from one location to another
    * @method copyFile
    * @param {Path} source The source file
    * @param {Path} dest The destination file
    * @param {Boolean} [overwrite=false] Whether or not to overwrite destination files
      if they already exist.
    * @param {Callback} callback The callback to be executed when complete.
    * @param {Error} callback.err The Error returned from Node
    **/
    function copyFile(source, dest, overwrite, callback) {
        // Allow callback as third arg.
        if (typeof overwrite === 'function') {
            callback = overwrite;
            overwrite = null;
        }

        fs.lstat(source, function (err, sourceStats) {
            if (err) { return callback(err); }

            if (!sourceStats.isFile()) {
                return callback(new Error("Source is not a file: " + source));
            }

            fs.lstat(dest, function (err, destStats) {
                if (err && err.code !== 'ENOENT') { return callback(err); }

                if (destStats) {
                    if (overwrite) {
                        deletePath(dest); // TODO: make this async
                    } else {
                        callback(new Error("Destination already exists: " + dest));
                        return;
                    }
                }

                nodeUtil.pump(fs.createReadStream(source),
                        fs.createWriteStream(dest, {mode: 0655}), callback);
            });
        });
    }
    Y.Files.copyFile = copyFile;

    /**
    If _source_ is a file, copies it to _dest_. If it's a directory, recursively
    copies it and all files and directories it contains to _dest_.

    Note that when attempting to copy a file into a directory, you should specify
    the full path to the new file (including the new filename). Otherwise, it will
    be interpreted as an attempt to copy the _source_ file *over* the _dest_
    directory instead of *into* it.

    Known issues:
    - Doesn't preserve ownership or permissions on copied files/directories.

    @method copyPath
    @param {String} source Source path.
    @param {String} dest Destination path.
    @param {Boolean} [overwrite=false] Whether or not to overwrite destination files
    if they already exist.
    @param {Callback} callback The callback to execute when completed.
    @param {Error} callback.err
    **/
    function copyPath(source, dest, overwrite, callback) {
        var destStats   = statSync(dest),
            sourceStats = statSync(source);

        // Allow callback as third arg.
        if (typeof overwrite === 'function') {
            callback = overwrite;
            overwrite = null;
        }

        if (!sourceStats) {
            callback(new Error("Source not found: " + source));
            return;
        }

        if (sourceStats.isFile()) {
            copyFile(source, dest, overwrite, callback);
        } else if (sourceStats.isDirectory()) {
            copyDirectory(source, dest, overwrite, callback);
        } else {
            callback(new Error("Source is neither a file nor a directory: " + source));
        }
    }
    Y.Files.copyPath = copyPath;

    // TODO: copySymbolicLink()?

    /**
    If _path_ is a file, deletes it. If _path_ is a directory, recursively deletes
    it and all files and directories it contains.

    This method is synchronous.

    @method deletePath
    @param {String} path File or directory to delete.
    **/
    function deletePath(path) {
        var stats = fs.lstatSync(path);

        if (stats.isFile() || stats.isSymbolicLink()) {
            fs.unlinkSync(path);
        } else if (stats.isDirectory()) {
            fs.readdirSync(path).forEach(function (filename) {
                deletePath(fsPath.join(path, filename));
            });

            fs.rmdirSync(path);
        }
    }
    Y.Files.deletePath = deletePath;

    /**
    Check to see if this is a directory
    @method isDirectory
    @param {Path} path The path to check
    @param {Boolean} [link=false] Also validate a symlink
    @return {Boolean} True if it is a directory
    **/
    function isDirectory(path, link) {
        var i = false;
        link = (link === false) ? false : true;
        try {
            var stat = fs.lstatSync(path);

            if (stat) {
                if (stat.isSymbolicLink() && link) {
                    stat = fs.statSync(path);
                }
                i = stat.isDirectory();
            }
        } catch (e) {
            i = false;
        }

        return i;
    };

    Y.Files.isDirectory = isDirectory;

    /**
    Check to see if this is a File
    @method isFile
    @param {Path} path The path to check
    @param {Boolean} [link=false] Also validate a symlink
    @return {Boolean} True if it is a file
    **/
    function isFile(path, link) {
        var i = false;
        try {
            var stat = fs.lstatSync(path);

            if (stat) {
                if (stat.isSymbolicLink() && link) {
                    stat = fs.statSync(path);
                }
                i = stat.isFile();
            }
        } catch (e) {
            i = false;
        }

        return i;
    }
    Y.Files.isFile = isFile;

    /**
    Check to see if this is a SymLink
    @method isSymbolicLink
    @param {Path} path The path to check
    @return {Boolean} True if it is a link
    **/
    function isSymbolicLink(path) {
        var stats = lstatSync(path);
        return stats ? stats.isSymbolicLink() : false;
    }
    Y.Files.isSymbolicLink = isSymbolicLink;

    /**
    Like `fs.lstatSync()`, but returns `null` instead of throwing when _path_
    doesn't exist. Will still throw on other types of errors.

    @method lstatSync
    @param {String} path Path to stat.
    @return {fs.Stats|null} `fs.Stats` object, or `null` if _path_ doesn't exist.
    **/
    function lstatSync(path) {
        try {
            return fs.lstatSync(path);
        } catch (ex) {
            if (ex.code === 'ENOENT') {
                return null;
            }

            throw ex;
        }
    }
    Y.Files.lstatSync = lstatSync;

    /**
    Like `fs.statSync()`, but returns `null` instead of throwing when _path_
    doesn't exist. Will still throw on other types of errors.

    @method statSync
    @param {String} path Path to stat.
    @return {fs.Stats|null} `fs.Stats` object, or `null` if _path_ doesn't exist.
    **/
    function statSync(path) {
        try {
            return fs.statSync(path);
        } catch (ex) {
            if (ex.code === 'ENOENT') {
                return null;
            }

            throw ex;
        }
    }
    Y.Files.statSync = statSync;

    /**
    Copy the theme assets directory
    @method copyAssets
    @param {Path} from The source directory
    @param {Path} dest The destination directory
    @param {Boolean} deleteFirst Should the directory be deleted if it exists
    @param {Function} callback The callback to be executed
    */
    function copyAssets() {
        var args        = Array.prototype.slice.call(arguments),
            callback    = args.pop(),
            from        = args.shift(),
            to          = args.shift(),
            deleteFirst = args.shift();

        if (from[0] === from[1]) {
            if (isDirectory(from[0])) {
                if (deleteFirst && isDirectory(to)) {
                    deletePath(to);
                }

                copyPath(from[0], to, true, callback);
            } else {
                callback();
            }
        } else {
            if (isDirectory(from[0])) {
                if (deleteFirst && isDirectory(to)) {
                    deletePath(to);
                }

                copyPath(from[0], to, true, function() {
                    if (isDirectory(from[1])) {
                        copyPath(from[1], to, true, callback)
                    } else {
                        callback();
                    }
                });
            } else {
                callback();
            }

        }
    }

    Y.Files.copyAssets = copyAssets;

    /**
    * Helper method for getting JSON data from a local file
    * @method getJSON
    * @param {Path} filename The filename to parse JSON from
    * @return {Object} The JSON data
    */
    Y.Files.getJSON = function(filename) {
        var data = {};
        if (exists(filename)) {
            data = JSON.parse(fs.readFileSync(filename, "utf8"));
        };
        return data;
    };

    /**
    * Helper method for writing files to disk. It wraps the NodeJS file API
    * @method writeFile
    * @param {Path} file The filename to write to
    * @param {String} data The data to write
    * @param {Callback} callback*
    */

    var writeFileTimer = 100,
        readFileTimer = 100;

    var writeFile = function(file, data, cb) {
        var flags = {
            flags: "w", encoding: "utf8", mode: 0644
        }
        var args = arguments;
        if (cb) {
            fs.writeFile(file, data, flags, function(err) {
                if (err && err.message.match(/^EMFILE, Too many open files/)) {
                    Y.log('Writefile failed, too many open files (' + args[0] + '). Trying again.', 'warn', 'files');
                    writeFileTimer++;
                    Y.later(writeFileTimer, Y, writeFile, args);
                    return;
                }
                cb();
            });
        } else {
            var out = fs.createWriteStream(file, flags);
            out.write(data);
            out.end();
        }
    };

    Y.Files.writeFile = writeFile;

    var readFile = function(file, enc, cb) {
        var args = arguments;
        fs.readFile(file, enc, function(err, data) {
            if (err && err.message.match(/^EMFILE, Too many open files/)) {
                Y.log('Readfile failed, too many open files (' + args[0] + '). Trying again.', 'warn', 'files');
                readFileTimer++;
                Y.later(readFileTimer, Y, readFile, args);
                return;
            }
            cb(err, data);
        });
    };

    Y.Files.readFile = readFile;

    });