coffee-toaster
Version:
Minimalist manager and build system for CoffeeScript, an alternative for AMD's or CJS's OOP patterns, but with similar results. Made for those who dare to use class definitions in CoffeeScript while being able to easily inherit from external files. Powered with imports directives that use wildcards facilities, exposed scopes and excluded files filter options. The system can even use folders-as-namespaces to help you avoid naming collisions in architecture.
280 lines (194 loc) • 7.15 kB
text/coffeescript
#<< toaster/utils/fn-util
class FsUtil
# requires
path = require "path"
fs = require "fs"
pn = (require "path").normalize
FnUtil = toaster.utils.FnUtil
ArrayUtil = toaster.utils.ArrayUtil
# static methods
@rmdir_rf:(folderpath, root=true)->
files = fs.readdirSync folderpath
for file in files
file = "#{folderpath}/#{file}"
stats = fs.lstatSync file
if stats.isDirectory()
FsUtil.rmdir_rf file, false
fs.rmdirSync file
else
fs.unlinkSync file
fs.rmdirSync folderpath if root
@mkdir_p:(folderpath)->
folderpath = (folderpath.slice 0, -1) if (folderpath.slice -1) == "/"
folders = folderpath.split "/"
for folder, index in folders
continue if (folder = folders.slice( 0, index + 1 ).join "/") == ""
exists = fs.existsSync folder
if exists and index is folders.length - 1
throw new Error error( "Folder exists: #{folder.red}" )
return false
else if !exists
fs.mkdirSync folder, '0755'
return true
@cp_dir:(from, to)->
from = (from.slice 0, -1) if (from.slice -1) == "/"
to = (to.slice 0, -1) if (to.slice -1) == "/"
for file_from in (files = @find from, /.*/, false)
file_to = file_from.replace from, to
dir = ((file_to.split '/').slice 0, -1).join '/'
@mkdir_p dir unless fs.existsSync dir
fs.writeFileSync file_to, (fs.readFileSync file_from, "utf-8")
@find:( folderpath, pattern, include_dirs = false )->
found = []
files = fs.readdirSync folderpath
for file in files
filepath = pn "#{folderpath}/#{file}"
if fs.lstatSync( filepath ).isDirectory()
if include_dirs and filepath.match pattern
found = found.concat filepath
found_under = FsUtil.find filepath, pattern, include_dirs
found = found.concat found_under
else
found.push filepath if filepath.match pattern
return found
@ls_folders:( folderpath )->
found = []
files = fs.readdirSync folderpath
for file in files
filepath = "#{folderpath}/#{file}"
found.push filepath if fs.lstatSync( filepath ).isDirectory()
return found
@take_snapshot:( folderpath )->
found = []
files = fs.readdirSync folderpath
for file in files
filepath = "#{folderpath}/#{file}"
if fs.lstatSync( filepath ).isDirectory()
type = "folder"
else
type = "file"
found.push type: type, path: filepath
return found
@watch_file:(filepath, onchange, dispatch_create)->
filepath = pn filepath
FsUtil.unwatch_file filepath
if dispatch_create
onchange?( type:"file", path:filepath, action:"created" )
onchange?( type:"file", path:filepath, action:"watching" )
fs.watchFile filepath, {persistent: true, interval : 30}, (curr,prev)=>
mtime = curr.mtime.valueOf() != prev.mtime.valueOf()
ctime = curr.ctime.valueOf() != prev.ctime.valueOf()
# file is deleted
if curr.nlink is 0
onchange?( {type: "file", path:filepath, action: "deleted"} )
# file is modified
else if mtime || ctime
onchange?( {type: "file", path:filepath, action: "updated"} )
@unwatch_file:( filepath, onchange )->
FsUtil.watched[filepath] = false
fs.unwatchFile filepath
@snapshots: {}
@watchers: {}
@watched: {}
@watch_folder:(folderpath, filter_regexp, onchange, dispatch_create)->
# initialize listeners array
if FsUtil.watchers[folderpath]
watchers = FsUtil.watchers[folderpath]
else
watchers = FsUtil.watchers[folderpath] = []
folderpath = pn folderpath
onchange?( type:"folder", path:folderpath, action:"watching" )
# take snapshot
FsUtil.snapshots[folderpath] = FsUtil.take_snapshot folderpath
# loop all items
# ----------------------------------------------------------------------
for item in FsUtil.snapshots[folderpath]
# watch subfolders
# ------------------------------------------------------------------
if item.type == "folder"
if dispatch_create
onchange
type:item.type
path:item.path
action: "created"
FsUtil.watch_folder item.path,
filter_regexp,
onchange,
dispatch_create
# watch files
# ------------------------------------------------------------------
else if filter_regexp == null || filter_regexp.test item.path
if dispatch_create
onchange type:item.type, path:item.path, action: "created"
FsUtil.watch_file item.path, onchange
# add watcher to watchers/listeners array
watchers.push {
folderpath: folderpath
filter: filter_regexp
onchange: onchange
dispatch_create: dispatch_create
}
# watch folder if its not being watched already
unless FsUtil.watched[folderpath]
FsUtil.watched[folderpath] = true
on_folder_change = FnUtil.proxy FsUtil._on_folder_change, folderpath
fs.unwatchFile folderpath
fs.watchFile folderpath, {persistent:true, interval : 30}, on_folder_change
@_on_folder_change:( folderpath, curr, prev)=>
# abort if the folder doest not exist
return unless fs.existsSync folderpath
# get previous and current folder snapshot to compare
a = FsUtil.snapshots[folderpath]
b = FsUtil.snapshots[folderpath] = FsUtil.take_snapshot folderpath
# differs the two snapshots states
diff = ArrayUtil.diff a, b, "path"
# if there's no diff, abort the execution
return unless diff.length
# saving watchers reference
watchers = FsUtil.watchers[folderpath]
# loop all diff itens
# ----------------------------------------------------------------------
for item in diff
info = item.item
info.action = item.action
# item created
# ------------------------------------------------------------------
if info.action == "created"
# item is file
# --------------------------------------------------------------
if info.type == "file"
# looping all watchers (listeners)
for watcher in watchers
# dispatch item if it passes the filter regexp
if watcher.filter.test info.path
watcher.onchange?( info )
FsUtil.watch_file info.path, watcher.onchange
# item is folder
# --------------------------------------------------------------
else if info.type == "folder"
for watcher in watchers
watcher.onchange?( info )
FsUtil.watch_folder info.path,
watcher.filter,
watcher.onchange,
true
# folder deleted
# ------------------------------------------------------------------
else if info.action == "deleted" and info.type == "folder"
# notifies all watchers about the folder removal
for watcher in watchers
watcher.onchange?( info )
FsUtil.unwatch_folder info.path
@unwatch_folder:( folderpath, onchange )->
# loop through all watchers
for watcher_path, list of FsUtil.watchers
# if it matches the folder path resets all its listeners
if new RegExp("^#{folderpath}").test( watcher_path )
FsUtil.watchers[watcher_path] = null
# loop through all watched items
for item, exists of FsUtil.watched
# if its still being watched and matches the regexp
if exists && new RegExp("^#{folderpath}").test( item )
# unwatch item
FsUtil.watched[item] = false
fs.unwatchFile item