📝 Provide detailed docs on fsUtils

This commit is contained in:
Matt Colyer
2013-10-23 14:52:27 -07:00
parent b79068ea65
commit b288a5c68f

View File

@@ -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)