From b288a5c68f188cd05bfbc5687e480e37b893df2a Mon Sep 17 00:00:00 2001 From: Matt Colyer Date: Wed, 23 Oct 2013 14:52:27 -0700 Subject: [PATCH] :memo: Provide detailed docs on fsUtils --- src/fs-utils.coffee | 189 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 150 insertions(+), 39 deletions(-) diff --git a/src/fs-utils.coffee b/src/fs-utils.coffee index 19e16d7db..0d08b0eaf 100644 --- a/src/fs-utils.coffee +++ b/src/fs-utils.coffee @@ -7,9 +7,22 @@ rimraf = require 'rimraf' path = require 'path' # Public: Useful extensions to node's built-in fs module +# +# Important, this extends Node's builtin in ['fs' module][fs], which means that you +# can do anything that you can do with Node's 'fs' module plus a few extra +# functions that we've found to be helpful. +# +# [fs]: http://nodejs.org/api/fs.html fsExtensions = - # Make the given path absolute by resolving it against the - # current working directory. + # Public: Make the given path absolute by resolving it against the current + # working directory. + # + # * relativePath: + # The String containing the relative path. If the path is prefixed with + # '~', it will be expanded to the current user's home directory. + # + # Returns the absolute path or the relative path if it's unable to determine + # it's realpath. absolute: (relativePath) -> return null unless relativePath? @@ -25,11 +38,12 @@ fsExtensions = catch e relativePath - # Returns true if a file or folder at the specified path exists. + # Public: Returns true if a file or folder at the specified path exists. exists: (pathToCheck) -> + # TODO: rename to existsSync pathToCheck? and fs.statSyncNoException(pathToCheck) isnt false - # Returns true if the specified path is a directory that exists. + # Public: Returns true if the given path exists and is a directory. isDirectorySync: (directoryPath) -> return false unless directoryPath?.length > 0 if stat = fs.statSyncNoException(directoryPath) @@ -37,6 +51,7 @@ fsExtensions = else false + # Public: Asynchronously checks that the given path exists and is a directory. isDirectory: (directoryPath, done) -> return done(false) unless directoryPath?.length > 0 fs.exists directoryPath, (exists) -> @@ -49,7 +64,7 @@ fsExtensions = else done(false) - # Returns true if the specified path is a regular file that exists. + # Public: Returns true if the specified path exists and is a file. isFileSync: (filePath) -> return false unless filePath?.length > 0 if stat = fs.statSyncNoException(filePath) @@ -57,7 +72,7 @@ fsExtensions = else false - # Returns true if the specified path is executable. + # Public: Returns true if the specified path is executable. isExecutableSync: (pathToCheck) -> return false unless pathToCheck?.length > 0 if stat = fs.statSyncNoException(pathToCheck) @@ -65,8 +80,14 @@ fsExtensions = else false - # Returns an array with the paths of the files and folders - # contained in the directory path. + # Public: Returns an Array with the paths of the files and directories + # contained within the directory path. It is not recursive. + # + # * rootPath: + # The absolute path to the directory to list. + # * extensions: + # An array of extensions to filter the results by. If none are given, none + # are filtered (optional). listSync: (rootPath, extensions) -> return [] unless @isDirectorySync(rootPath) paths = fs.readdirSync(rootPath) @@ -74,6 +95,16 @@ fsExtensions = paths = paths.map (childPath) -> path.join(rootPath, childPath) paths + # Public: Asynchronously lists the files and directories in the given path. + # The listing is not recursive. + # + # * rootPath: + # The absolute path to the directory to list. + # * extensions: + # An array of extensions to filter the results by. If none are given, none + # are filtered (optional) + # * callback: + # The function to call list: (rootPath, rest...) -> extensions = rest.shift() if rest.length > 1 done = rest.shift() @@ -85,6 +116,7 @@ fsExtensions = paths = paths.map (childPath) -> path.join(rootPath, childPath) done(null, paths) + # Private: Returns only the paths which end with one of the given extensions. filterExtensions: (paths, extensions) -> extensions = extensions.map (ext) -> if ext is '' @@ -93,6 +125,7 @@ fsExtensions = '.' + ext.replace(/^\./, '') paths.filter (pathToCheck) -> _.include(extensions, path.extname(pathToCheck)) + # Deprecated: No one currently uses this. listTreeSync: (rootPath) -> paths = [] onPath = (childPath) -> @@ -101,22 +134,34 @@ fsExtensions = @traverseTreeSync(rootPath, onPath, onPath) paths + # Public: Moves the file or directory to the target synchronously. move: (source, target) -> + # TODO: This should be renamed to moveSync fs.renameSync(source, target) - # Remove the file or directory at the given path. + # Public: Removes the file or directory at the given path synchronously. remove: (pathToRemove) -> + # TODO: This should be renamed to removeSync rimraf.sync(pathToRemove) - # Open, read, and close a file, returning the file's contents. + # Public: Open, read, and close a file, returning the file's contents + # synchronously. read: (filePath) -> + # TODO: This should be renamed to readSync fs.readFileSync(filePath, 'utf8') - # Open, write, flush, and close a file, writing the given content. + # Public: Open, write, flush, and close a file, writing the given content + # synchronously. + # + # It also creates the necessary parent directories. writeSync: (filePath, content) -> mkdirp.sync(path.dirname(filePath)) fs.writeFileSync(filePath, content) + # Public: Open, write, flush, and close a file, writing the given content + # asynchronously. + # + # It also creates the necessary parent directories. write: (filePath, content, callback) -> mkdirp path.dirname(filePath), (error) -> if error? @@ -124,6 +169,7 @@ fsExtensions = else fs.writeFile(filePath, content, callback) + # Public: Copies the given path asynchronously. copy: (sourcePath, destinationPath, done) -> mkdirp path.dirname(destinationPath), (error) -> if error? @@ -145,11 +191,23 @@ fsExtensions = sourceStream.pipe(destinationStream) - # Create a directory at the specified path including any missing parent - # directories. + # Public: Create a directory at the specified path including any missing + # parent directories synchronously. makeTree: (directoryPath) -> + # TODO: rename to makeTreeSync mkdirp.sync(directoryPath) if directoryPath and not @exists(directoryPath) + # Public: Recursively walk the given path and execute the given functions + # synchronously. + # + # * rootPath: + # The String containing the directory to recurse into. + # * onFile: + # The function to execute on each file, receives a single argument the + # absolute path. + # * onDirectory: + # The function to execute on each directory, receives a single argument the + # absolute path (defaults to onFile) traverseTreeSync: (rootPath, onFile, onDirectory=onFile) -> return unless @isDirectorySync(rootPath) @@ -167,6 +225,17 @@ fsExtensions = traverse(rootPath, onFile, onDirectory) + # Public: Recursively walk the given path and execute the given functions + # asynchronously. + # + # * rootPath: + # The String containing the directory to recurse into. + # * onFile: + # The function to execute on each file, receives a single argument the + # absolute path. + # * onDirectory: + # The function to execute on each directory, receives a single argument the + # absolute path (defaults to onFile) traverseTree: (rootPath, onFile, onDirectory, onDone) -> fs.readdir rootPath, (error, files) -> if error @@ -194,10 +263,28 @@ fsExtensions = queue.drain = onDone queue.push(path.join(rootPath, file)) for file in files + # Public: Hashes the contents of the given file. + # + # * pathToDigest: + # The String containing the absolute path. + # + # Returns a String containing the MD5 hexadecimal hash. md5ForPath: (pathToDigest) -> contents = fs.readFileSync(pathToDigest) require('crypto').createHash('md5').update(contents).digest('hex') + # Public: Finds a relative path among the given array of paths. + # + # * loadPaths: + # An Array of absolute and relative paths to search. + # * pathToResolve: + # The string containing the path to resolve. + # * extensions: + # An array of extensions to pass to {resolveExtensions} in which case + # pathToResolve should not contain an extension (optional). + # + # Returns the absolute path of the file to be resolved if it's found and + # undefined otherwise. resolve: (args...) -> extensions = args.pop() if _.isArray(_.last(args)) pathToResolve = args.pop() @@ -218,10 +305,22 @@ fsExtensions = return @absolute(candidatePath) if @exists(candidatePath) undefined + # Deprecated: resolveOnLoadPath: (args...) -> loadPaths = Module.globalPaths.concat(module.paths) @resolve(loadPaths..., args...) + # Public: Finds the first file in the given path which matches the extension + # in the order given. + # + # * pathToResolve: + # The String containing relative or absolute path of the file in question + # without the extension or '.'. + # * extensions: + # The ordered Array of extensions to try. + # + # Returns the absolute path of the file if it exists with any of the given + # extensions, otherwise it's undefined. resolveExtension: (pathToResolve, extensions) -> for extension in extensions if extension == "" @@ -231,6 +330,7 @@ fsExtensions = return @absolute(pathWithExtension) if @exists(pathWithExtension) undefined + # Public: Returns true for extensions associated with compressed files. isCompressedExtension: (ext) -> _.indexOf([ '.gz' @@ -240,6 +340,7 @@ fsExtensions = '.zip' ], ext, true) >= 0 + # Public: Returns true for extensions associated with image files. isImageExtension: (ext) -> _.indexOf([ '.gif' @@ -249,9 +350,27 @@ fsExtensions = '.tiff' ], ext, true) >= 0 + # Public: Returns true for extensions associated with pdf files. isPdfExtension: (ext) -> ext is '.pdf' + # Public: Returns true for extensions associated with binary files. + isBinaryExtension: (ext) -> + _.indexOf([ + '.DS_Store' + '.a' + '.o' + '.so' + '.woff' + ], ext, true) >= 0 + + # Public: Returns true for files named similarily to 'README' + isReadmePath: (readmePath) -> + extension = path.extname(readmePath) + base = path.basename(readmePath, extension).toLowerCase() + base is 'readme' and (extension is '' or @isMarkdownExtension(extension)) + + # Private: Used by isReadmePath. isMarkdownExtension: (ext) -> _.indexOf([ '.markdown' @@ -262,24 +381,30 @@ fsExtensions = '.ron' ], ext, true) >= 0 - isBinaryExtension: (ext) -> - _.indexOf([ - '.DS_Store' - '.a' - '.o' - '.so' - '.woff' - ], ext, true) >= 0 + # Public: Reads and returns CSON, JSON or Plist files and returns the + # corresponding Object. + readObjectSync: (objectPath) -> + CSON = require 'season' + if CSON.isObjectPath(objectPath) + CSON.readFileSync(objectPath) + else + @readPlistSync(objectPath) - isReadmePath: (readmePath) -> - extension = path.extname(readmePath) - base = path.basename(readmePath, extension).toLowerCase() - base is 'readme' and (extension is '' or @isMarkdownExtension(extension)) + # Public: Reads and returns CSON, JSON or Plist files and calls the specified + # callback with the corresponding Object. + readObject: (objectPath, done) -> + CSON = require 'season' + if CSON.isObjectPath(objectPath) + CSON.readFile(objectPath, done) + else + @readPlist(objectPath, done) + # Private: Used by readObjectSync. readPlistSync: (plistPath) -> plist = require 'plist' plist.parseStringSync(@read(plistPath)) + # Private: Used by readObject. readPlist: (plistPath, done) -> plist = require 'plist' fs.readFile plistPath, 'utf8', (error, contents) -> @@ -291,18 +416,4 @@ fsExtensions = catch parseError done(parseError) - readObjectSync: (objectPath) -> - CSON = require 'season' - if CSON.isObjectPath(objectPath) - CSON.readFileSync(objectPath) - else - @readPlistSync(objectPath) - - readObject: (objectPath, done) -> - CSON = require 'season' - if CSON.isObjectPath(objectPath) - CSON.readFile(objectPath, done) - else - @readPlist(objectPath, done) - module.exports = _.extend({}, fs, fsExtensions)