bal-util
Version:
Common utility functions for Node.js used and maintained by Benjamin Lupton
413 lines (343 loc) • 9.98 kB
text/coffeescript
# Import
pathUtil = require('path')
eachr = require('eachr')
typeChecker = require('typechecker')
extendr = require('extendr')
safefs = require('safefs')
extractOpts = require('extract-opts')
{TaskGroup} = require('taskgroup')
balUtilFlow = require('./flow')
ignorefs = require('ignorefs')
scandir = require('scandirectory')
# Define
balUtilPaths =
# =====================================
# Our Extensions
# Resolve a Case Sensitive Path
# next(err, result)
resolveCaseSensitivePath: (path, next) ->
# Resolve the parent path
parentPath = safefs.getParentPathSync(path) or '/'
return next(null, parentPath) if parentPath is '/'
safefs.resolveCaseSensitivePath parentPath, (err, parentPath) ->
safefs.readdir parentPath, (err, files) ->
return next(err) if err
relativePathLowerCase = relativePath.toLowerCase()
for file in files
if file.toLowerCase() is relativePathLowerCase
return next(null, pathUtil.join(parentPath, relativePath))
err = new Error("Could not find the path #{relativePath} inside #{parentPath}")
return next(err)
# Chain
safefs
# Copy a file
# Or rather overwrite a file, regardless of whether or not it was existing before
# next(err)
cp: (src,dst,next) ->
# Copy
safefs.readFile src, 'binary', (err,data) ->
# Error
return next(err) if err
# Success
safefs.writeFile dst, data, 'binary', (err) ->
# Forward
return next(err)
# Chain
@
# Prefix path
prefixPathSync: (path,parentPath) ->
path = path.replace /[\/\\]$/, ''
if /^([a-zA-Z]\:|\/)/.test(path) is false
path = pathUtil.join(parentPath,path)
return path
# Is it a directory?
# path can also be a stat object
# next(err,isDirectory,fileStat)
isDirectory: (path,next) ->
# Check if path is a stat object
if path?.isDirectory?
return next(null, path.isDirectory(), path)
# Otherwise fetch the stat and do the check
else
safefs.stat path, (err,stat) ->
# Error
return next(err) if err
# Success
return next(null, stat.isDirectory(), stat)
# Chain
@
# Generate a slug for a file
generateSlugSync: (path) ->
# Slugify
result = path.replace(/[^a-zA-Z0-9]/g,'-').replace(/^-/,'').replace(/-+/,'-')
# Return
return result
# Scan a directory into a list
# next(err,list)
scanlist: (path,next) ->
# Handle
scandir(
path: path
readFiles: true
ignoreHiddenFiles: true
next: (err,list) ->
return next(err,list)
)
# Chain
@
# Scan a directory into a tree
# next(err,tree)
scantree: (path,next) ->
# Handle
scandir(
path: path
readFiles: true
ignoreHiddenFiles: true
next: (err,list,tree) ->
return next(err,tree)
)
# Chain
@
# Copy a directory
# If the same file already exists, we will keep the source one
# Usage:
# cpdir({srcPath,outPath,next})
# cpdir(srcPath,outPath,next)
# Callbacks:
# next(err)
cpdir: (args...) ->
# Prepare
opts = {}
if args.length is 1
opts = args[0]
else if args.length >= 3
[srcPath,outPath,next] = args
opts = {srcPath,outPath,next}
else
err = new Error('balUtilPaths.cpdir: unknown arguments')
if next
return next(err)
else
throw err
# Create opts
scandirOpts = {
path: opts.srcPath
fileAction: (fileSrcPath,fileRelativePath,next) ->
# Prepare
fileOutPath = pathUtil.join(opts.outPath,fileRelativePath)
# Ensure the directory that the file is going to exists
safefs.ensurePath pathUtil.dirname(fileOutPath), (err) ->
# Error
if err
return next(err)
# The directory now does exist
# So let's now place the file inside it
balUtilPaths.cp fileSrcPath, fileOutPath, (err) ->
# Forward
return next(err)
next: opts.next
}
# Passed Scandir Opts
for opt in ['ignorePaths','ignoreHiddenFiles','ignoreCommonPatterns','ignoreCustomPatterns']
scandirOpts[opt] = opts[opt]
# Scan all the files in the diretory and copy them over asynchronously
scandir(scandirOpts)
# Chain
@
# Replace a directory
# If the same file already exists, we will keep the newest one
# Usage:
# rpdir({srcPath,outPath,next})
# rpdir(srcPath,outPath,next)
# Callbacks:
# next(err)
rpdir: (args...) ->
# Prepare
opts = {}
if args.length is 1
opts = args[0]
else if args.length >= 3
[srcPath,outPath,next] = args
opts = {srcPath,outPath,next}
else
err = new Error('balUtilPaths.cpdir: unknown arguments')
if next
return next(err)
else
throw err
# Create opts
scandirOpts = {
path: opts.srcPath
fileAction: (fileSrcPath,fileRelativePath,next) ->
# Prepare
fileOutPath = pathUtil.join(opts.outPath,fileRelativePath)
# Ensure the directory that the file is going to exists
safefs.ensurePath pathUtil.dirname(fileOutPath), (err) ->
# Error
return next(err) if err
# Check if it is worthwhile copying that file
balUtilPaths.isPathOlderThan fileOutPath, fileSrcPath, (err,older) ->
# The src path has been modified since the out path was generated
if older is true or older is null
# The directory now does exist
# So let's now place the file inside it
balUtilPaths.cp fileSrcPath, fileOutPath, (err) ->
# Forward
return next(err)
# The out path is new enough
else
return next()
next: opts.next
}
# Passed Scandir Opts
for opt in ['ignorePaths','ignoreHiddenFiles','ignoreCommonPatterns','ignoreCustomPatterns']
scandirOpts[opt] = opts[opt]
# Scan all the files in the diretory and copy them over asynchronously
scandir(scandirOpts)
# Chain
@
# Write tree
# next(err)
writetree: (dstPath,tree,next) ->
# Ensure Destination
safefs.ensurePath dstPath, (err) ->
# Checks
return next(err) if err
# Group
tasks = new TaskGroup(concurrency:0).done(next)
# Cycle
eachr tree, (value,fileRelativePath) -> tasks.addTask (complete) ->
fileFullPath = pathUtil.join(dstPath, fileRelativePath.replace(/^\/+/,''))
if typeChecker.isObject(value)
balUtilPaths.writetree(fileFullPath, value, complete)
else
safefs.writeFile(fileFullPath, value, complete)
# Run the tasks
tasks.run()
# Chain
@
# Read path
# Reads a path be it local or remote
# next(err,data)
readPath: (filePath,opts,next) ->
[opts,next] = extractOpts(opts, next)
# Request
if /^http/.test(filePath)
# Zlib
zlib = null
try
zlib = require('zlib')
catch err
# do nothing
# Options
requestOpts = require('url').parse(filePath)
requestOpts.path ?= requestOpts.pathname
requestOpts.method ?= 'GET'
requestOpts.headers ?= {}
requestOpts.headers['accept-encoding'] ?= 'gzip,deflate' if zlib
requestOpts.headers['user-agent'] ?= 'Wget/1.14 (linux-gnu)'
# Prepare request
http = if requestOpts.protocol is 'https:' then require('https') else require('http')
req = http.request(requestOpts)
req.setTimeout ?= (delay) ->
onTimeout = -> req.emit('error', new Error('request timed out'))
setTimeout(onTimeout, delay) # alias for node <=0.9
req.setTimeout(opts.timeout ? 10*1000) # 10 second timeout
req.once('error', next)
req.once('timeout', -> req.abort()) # must abort manually, will trigger error event
req.once 'response', (res) ->
locationHeader = res.headers?.location or null
if locationHeader and locationHeader isnt requestOpts.href
req.removeAllListeners()
balUtilPaths.readPath(locationHeader, opts, next)
return
chunks = []
res.on 'data', (chunk) ->
chunks.push(chunk)
res.once 'end', ->
data = new Buffer.concat(chunks)
switch res.headers['content-encoding']
when 'gzip'
zlib.unzip(data, next)
break
when 'deflate'
zlib.inflate(data, next)
break
else
next(null, data)
# End request, start response
req.end()
# Local
else
safefs.readFile filePath, (err, data) ->
return next(err) if err
return next(null, data)
# Chain
@
# Empty
# Check if the file does not exist, or is empty
# next(err,empty)
empty: (filePath,next) ->
# Check if we exist
safefs.exists filePath, (exists) ->
# Return empty if we don't exist
return next(null,true) unless exists
# We do exist, so check if we have content
safefs.stat filePath, (err,stat) ->
# Check
return next(err) if err
# Return whether or not we are actually empty
return next(null,stat.size is 0)
# Chain
@
# Is Path Older Than
# Checks if a path is older than a particular amount of millesconds
# next(err,older)
# older will be null if the path does not exist
isPathOlderThan: (aPath,bInput,next) ->
# Handle mtime
bMtime = null
if typeChecker.isNumber(bInput)
mode = 'time'
bMtime = new Date(new Date() - bInput)
else
mode = 'path'
bPath = bInput
# Check if the path exists
balUtilPaths.empty aPath, (err,empty) ->
# If it doesn't then we should return right away
return next(err,null) if empty or err
# We do exist, so let's check how old we are
safefs.stat aPath, (err,aStat) ->
# Check
return next(err) if err
# Prepare
compare = ->
# Time comparison
if aStat.mtime < bMtime
older = true
else
older = false
# Return result
return next(null,older)
# Perform the comparison
if mode is 'path'
# Check if the bPath exists
balUtilPaths.empty bPath, (err,empty) ->
# Return result if we are empty
return next(err,null) if empty or err
# It does exist so lets get the stat
safefs.stat bPath, (err,bStat) ->
# Check
return next(err) if err
# Assign the outer bMtime variable
bMtime = bStat.mtime
# Perform the comparison
return compare()
else
# We already have the bMtime
return compare()
# Chain
@
# Export
module.exports = balUtilPaths