File: file_system.rb

package info (click to toggle)
ruby-fakefs 3.2.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 544 kB
  • sloc: ruby: 7,622; makefile: 5
file content (196 lines) | stat: -rw-r--r-- 5,152 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# frozen_string_literal: true

module FakeFS
  # FileSystem module
  module FileSystem
    extend self

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

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

    def clear
      @dir_levels = nil
      @fs = nil
    end

    def files
      fs.entries
    end

    # 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

    # 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 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

    # copies directories and files from the real filesystem
    # into our fake one
    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 delete(path)
      return unless (node = FileSystem.find(path))
      node.delete
      true
    end

    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 path_parts(path)
      Globber.path_components(path)
    end

    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 current_dir
      find('.')
    end

    private

    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_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 directories_under(dir)
      children = dir.entries.select { |f| f.is_a? FakeDir }
      ([dir] + children + children.map { |c| directories_under(c) })
        .flatten.uniq
    end

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