moduleSprockets# Internal: File and path related utilities. Mixed into Environment.## Probably would be called FileUtils, but that causes namespace annoyances# when code actually wants to reference ::FileUtils.modulePathUtilsextendself# Public: Like `File.stat`.## path - String file or directory path## Returns nil if the file does not exist.defstat(path)ifFile.exist?(path)File.stat(path.to_s)elsenilendend# Public: Like `File.file?`.## path - String file path.## Returns true path exists and is a file.deffile?(path)ifstat=self.stat(path)stat.file?elsefalseendend# Public: Like `File.directory?`.## path - String file path.## Returns true path exists and is a directory.defdirectory?(path)ifstat=self.stat(path)stat.directory?elsefalseendend# Public: A version of `Dir.entries` that filters out `.` files and `~`# swap files.## path - String directory path## Returns an empty `Array` if the directory does not exist.defentries(path)ifFile.directory?(path)entries=Dir.entries(path,:encoding=>Encoding.default_internal)entries.reject!{|entry|entry.start_with?(".".freeze)||(entry.start_with?("#".freeze)&&entry.end_with?("#".freeze))||entry.end_with?("~".freeze)}entries.sort!else[]endend# Public: Check if path is absolute or relative.## path - String path.## Returns true if path is absolute, otherwise false.ifFile::ALT_SEPARATORrequire'pathname'# On Windows, ALT_SEPARATOR is \# Delegate to Pathname since the logic gets complex.defabsolute_path?(path)Pathname.new(path).absolute?endelsedefabsolute_path?(path)path[0]==File::SEPARATORendendifFile::ALT_SEPARATORSEPARATOR_PATTERN="#{Regexp.quote(File::SEPARATOR)}|#{Regexp.quote(File::ALT_SEPARATOR)}"elseSEPARATOR_PATTERN="#{Regexp.quote(File::SEPARATOR)}"end# Public: Check if path is explicitly relative.# Starts with "./" or "../".## path - String path.## Returns true if path is relative, otherwise false.defrelative_path?(path)path=~/^\.\.?($|#{SEPARATOR_PATTERN})/?true:falseend# Internal: Get relative path for root path and subpath.## path - String path# subpath - String subpath of path## Returns relative String path if subpath is a subpath of path, or nil if# subpath is outside of path.defsplit_subpath(path,subpath)return""ifpath==subpathpath=File.join(path,'')ifsubpath.start_with?(path)subpath[path.length..-1]elsenilendend# Internal: Detect root path and base for file in a set of paths.## paths - Array of String paths# filename - String path of file expected to be in one of the paths.## Returns [String root, String path]defpaths_split(paths,filename)paths.eachdo|path|ifsubpath=split_subpath(path,filename)returnpath,subpathendendnilend# Internal: Get path's extensions.## path - String## Returns an Array of String extnames.defpath_extnames(path)File.basename(path).scan(/\.[^.]+/)end# Internal: Match path extnames against available extensions.## path - String# extensions - Hash of String extnames to values## Returns [String extname, Object value] or nil nothing matched.defmatch_path_extname(path,extensions)basename=File.basename(path)i=basename.index('.'.freeze)whilei&&i<basename.length-1extname=basename[i..-1]ifvalue=extensions[extname]returnextname,valueendi=basename.index('.'.freeze,i+1)endnilend# Internal: Returns all parents for path## path - String absolute filename or directory# root - String path to stop at (default: system root)## Returns an Array of String paths.defpath_parents(path,root=nil)root="#{root}#{File::SEPARATOR}"ifrootparents=[]loopdoparent=File.dirname(path)breakifparent==pathbreakifroot&&!path.start_with?(root)parents<<path=parentendparentsend# Internal: Find target basename checking upwards from path.## basename - String filename: ".sprocketsrc"# path - String path to start search: "app/assets/javascripts/app.js"# root - String path to stop at (default: system root)## Returns String filename or nil.deffind_upwards(basename,path,root=nil)path_parents(path,root).eachdo|dir|filename=File.join(dir,basename)returnfilenameiffile?(filename)endnilend# Public: Stat all the files under a directory.## dir - A String directory## Returns an Enumerator of [path, stat].defstat_directory(dir)returnto_enum(__method__,dir)unlessblock_given?self.entries(dir).eachdo|entry|path=File.join(dir,entry)ifstat=self.stat(path)yieldpath,statendendnilend# Public: Recursive stat all the files under a directory.## dir - A String directory## Returns an Enumerator of [path, stat].defstat_tree(dir,&block)returnto_enum(__method__,dir)unlessblock_given?self.stat_directory(dir)do|path,stat|yieldpath,statifstat.directory?stat_tree(path,&block)endendnilend# Public: Recursive stat all the files under a directory in alphabetical# order.## dir - A String directory## Returns an Enumerator of [path, stat].defstat_sorted_tree(dir,&block)returnto_enum(__method__,dir)unlessblock_given?self.stat_directory(dir).sort_by{|path,stat|stat.directory??"#{path}/":path}.eachdo|path,stat|yieldpath,statifstat.directory?stat_sorted_tree(path,&block)endendnilend# Public: Write to a file atomically. Useful for situations where you# don't want other processes or threads to see half-written files.## Utils.atomic_write('important.file') do |file|# file.write('hello')# end## Returns nothing.defatomic_write(filename)dirname,basename=File.split(filename)basename=[basename,Thread.current.object_id,Process.pid,rand(1000000)].join('.')tmpname=File.join(dirname,basename)File.open(tmpname,'wb+')do|f|yieldfendFile.rename(tmpname,filename)ensureFile.delete(tmpname)ifFile.exist?(tmpname)endendend