module FakeFS::FileSystem

def add(path, object = FakeDir.new)

def add(path, object = FakeDir.new)
  parts = path_parts(normalize_path(path))
  d = parts[0...-1].reduce(fs) do |dir, part|
    assert_dir dir[part] if dir[part]
    dir[part] ||= FakeDir.new(part, dir)
  end
  assert_dir d
  # Short-circuit if added path is file system root, to avoid adding nil-name fs entries:
  return fs if parts.empty?
  object.name = parts.last
  object.parent = d
  if object.is_a? FakeDir
    d[parts.last] ||= object
  else
    d[parts.last] = object
  end
end

def assert_dir(dir)

def assert_dir(dir)
  raise Errno::EEXIST, dir.name unless dir.is_a?(FakeDir)
end

def chdir(dir, &blk)

def chdir(dir, &blk)
  new_dir = find(dir)
  dir_levels.push dir.to_s if blk
  raise Errno::ENOENT, dir.to_s unless new_dir
  raise Errno::ENOTDIR, dir.to_s unless File.directory? new_dir
  dir_levels.push dir.to_s unless blk
  yield(dir) if blk
ensure
  dir_levels.pop if blk
end

def clear

def clear
  @dir_levels = nil
  @fs = nil
end

def clone(path, target = nil)

into our fake one
copies directories and files from the real filesystem
def clone(path, target = nil)
  path    = RealFile.expand_path(path)
  pattern = File.join(path, '**', '*')
  files   = if RealFile.file?(path)
              [path]
            else
              [path] + RealDir.glob(pattern, RealFile::FNM_DOTMATCH)
            end
  files.each do |f|
    target_path = target ? f.gsub(path, target) : f
    if RealFile.symlink?(f)
      FileUtils.ln_s(RealFile.readlink(f), f)
    elsif RealFile.file?(f)
      FileUtils.mkdir_p(File.dirname(f))
      File.open(target_path, File::WRITE_ONLY) do |g|
        g.print RealFile.read(f)
      end
    elsif RealFile.directory?(f)
      FileUtils.mkdir_p(target_path)
    end
  end
end

def current_dir

def current_dir
  find('.')
end

def delete(path)

def delete(path)
  return unless (node = FileSystem.find(path))
  node.delete
  true
end

def dir_levels

def dir_levels
  @dir_levels ||= ['/']
end

def directories_under(dir)

def directories_under(dir)
  children = dir.entries.select { |f| f.is_a? FakeDir }
  ([dir] + children + children.map { |c| directories_under(c) })
    .flatten.uniq
end

def files

def files
  fs.entries
end

def find(path, dir: nil)

Finds files/directories using the exact path, without expanding globs.
def find(path, dir: nil)
  parts = path_parts(normalize_path(path, dir: dir))
  return fs if parts.empty? # '/'
  find_recurser(fs, parts)
end

def find_recurser(dir, parts, find_flags = 0, gave_char_class = false)

def find_recurser(dir, parts, find_flags = 0, gave_char_class = false)
  return nil unless dir.respond_to? :[]
  head, *parts = parts
  match = dir.entries.find { |e| e.name == head }
  if parts.empty? # we're done recursing
    match
  else
    find_recurser(match, parts, find_flags, gave_char_class)
  end
end

def find_with_glob(path, find_flags = 0, gave_char_class = false, dir: nil)

Finds files/directories expanding globs.
def find_with_glob(path, find_flags = 0, gave_char_class = false, dir: nil)
  parts = path_parts(normalize_path(path, dir: dir))
  return fs if parts.empty? # '/'
  entries = Globber.expand(path).flat_map do |pattern|
    parts = path_parts(normalize_path(pattern, dir: dir))
    find_with_glob_recurser(fs, parts, find_flags, gave_char_class).flatten
  end
  case entries.length
  when 0 then nil
  when 1 then entries.first
  else entries
  end
end

def find_with_glob_recurser(dir, parts, find_flags = 0, gave_char_class = false)

def find_with_glob_recurser(dir, parts, find_flags = 0, gave_char_class = false)
  return [] unless dir.respond_to? :[]
  pattern, *parts = parts
  matches =
    case pattern
    when '**'
      case parts
      when ['*']
        parts = [] # end recursion
        directories_under(dir).map do |d|
          d.entries.select do |f|
            (f.is_a?(FakeFile) || f.is_a?(FakeDir)) &&
              f.name.match(/\A(?!\.)/)
          end
        end.flatten.uniq
      when []
        parts = [] # end recursion
        dir.entries.flatten.uniq
      else
        directories_under(dir)
      end
    else
      Globber.expand(pattern).flat_map do |subpattern|
        dir.matches(Globber.regexp(subpattern, find_flags, gave_char_class))
      end
    end
  if parts.empty? # we're done recursing
    matches
  else
    matches.map { |entry| find_with_glob_recurser(entry, parts, find_flags, gave_char_class) }
  end
end

def fs

def fs
  @fs ||= FakeDir.new('/')
end

def normalize_path(path, dir: nil)

def normalize_path(path, dir: nil)
  if Pathname.new(path).absolute?
    RealFile.expand_path(path)
  else
    dir ||= dir_levels
    dir = Array(dir)
    parts = dir + [path]
    RealFile.expand_path(parts.reduce do |base, part|
                           Pathname(base) + part
                         end.to_s)
  end
end

def path_parts(path)

def path_parts(path)
  Globber.path_components(path)
end