# frozen_string_literal: truemoduleFakeFS# FileSystem modulemoduleFileSystemextendselfdefdir_levels@dir_levels||=['/']enddeffs@fs||=FakeDir.new('/')enddefclear@dir_levels=nil@fs=nilenddeffilesfs.entriesend# Finds files/directories using the exact path, without expanding globs.deffind(path,dir: nil)parts=path_parts(normalize_path(path,dir: dir))returnfsifparts.empty?# '/'find_recurser(fs,parts)end# Finds files/directories expanding globs.deffind_with_glob(path,find_flags=0,gave_char_class=false,dir: nil)parts=path_parts(normalize_path(path,dir: dir))returnfsifparts.empty?# '/'entries=Globber.expand(path).flat_mapdo|pattern|parts=path_parts(normalize_path(pattern,dir: dir))find_with_glob_recurser(fs,parts,find_flags,gave_char_class).flattenendcaseentries.lengthwhen0thennilwhen1thenentries.firstelseentriesendenddefadd(path,object=FakeDir.new)parts=path_parts(normalize_path(path))d=parts[0...-1].reduce(fs)do|dir,part|assert_dirdir[part]ifdir[part]dir[part]||=FakeDir.new(part,dir)endassert_dird# Short-circuit if added path is file system root, to avoid adding nil-name fs entries:returnfsifparts.empty?object.name=parts.lastobject.parent=difobject.is_a?FakeDird[parts.last]||=objectelsed[parts.last]=objectendend# copies directories and files from the real filesystem# into our fake onedefclone(path,target=nil)path=RealFile.expand_path(path)pattern=File.join(path,'**','*')files=ifRealFile.file?(path)[path]else[path]+RealDir.glob(pattern,RealFile::FNM_DOTMATCH)endfiles.eachdo|f|target_path=target?f.gsub(path,target):fifRealFile.symlink?(f)FileUtils.ln_s(RealFile.readlink(f),f)elsifRealFile.file?(f)FileUtils.mkdir_p(File.dirname(f))File.open(target_path,File::WRITE_ONLY)do|g|g.printRealFile.read(f)endelsifRealFile.directory?(f)FileUtils.mkdir_p(target_path)endendenddefdelete(path)returnunless(node=FileSystem.find(path))node.deletetrueenddefchdir(dir,&blk)new_dir=find(dir)dir_levels.pushdir.to_sifblkraiseErrno::ENOENT,dir.to_sunlessnew_dirraiseErrno::ENOTDIR,dir.to_sunlessFile.directory?new_dirdir_levels.pushdir.to_sunlessblkyield(dir)ifblkensuredir_levels.popifblkenddefpath_parts(path)Globber.path_components(path)enddefnormalize_path(path,dir: nil)ifPathname.new(path).absolute?RealFile.expand_path(path)elsedir||=dir_levelsdir=Array(dir)parts=dir+[path]RealFile.expand_path(parts.reducedo|base,part|Pathname(base)+partend.to_s)endenddefcurrent_dirfind('.')endprivatedeffind_recurser(dir,parts,find_flags=0,gave_char_class=false)returnnilunlessdir.respond_to?:[]head,*parts=partsmatch=dir.entries.find{|e|e.name==head}ifparts.empty?# we're done recursingmatchelsefind_recurser(match,parts,find_flags,gave_char_class)endenddeffind_with_glob_recurser(dir,parts,find_flags=0,gave_char_class=false)return[]unlessdir.respond_to?:[]pattern,*parts=partsmatches=casepatternwhen'**'casepartswhen['*']parts=[]# end recursiondirectories_under(dir).mapdo|d|d.entries.selectdo|f|(f.is_a?(FakeFile)||f.is_a?(FakeDir))&&f.name.match(/\A(?!\.)/)endend.flatten.uniqwhen[]parts=[]# end recursiondir.entries.flatten.uniqelsedirectories_under(dir)endelseGlobber.expand(pattern).flat_mapdo|subpattern|dir.matches(Globber.regexp(subpattern,find_flags,gave_char_class))endendifparts.empty?# we're done recursingmatcheselsematches.map{|entry|find_with_glob_recurser(entry,parts,find_flags,gave_char_class)}endenddefdirectories_under(dir)children=dir.entries.select{|f|f.is_a?FakeDir}([dir]+children+children.map{|c|directories_under(c)}).flatten.uniqenddefassert_dir(dir)raiseErrno::EEXIST,dir.nameunlessdir.is_a?(FakeDir)endendend