File: find.rb

package info (click to toggle)
ruby-tins 1.32.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,248 kB
  • sloc: ruby: 6,659; makefile: 3
file content (149 lines) | stat: -rw-r--r-- 3,940 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
require 'enumerator'
require 'pathname'
require 'tins/module_group'

module Tins
  module Find
    EXPECTED_STANDARD_ERRORS = ModuleGroup[
      Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP,
      Errno::ENAMETOOLONG
    ]

    class Finder
      module PathExtension
        attr_accessor :finder

        def finder_stat
          finder.protect_from_errors do
            finder.follow_symlinks ? File.stat(self) : File.lstat(self)
          end
        end

        def file
          finder.protect_from_errors do
            File.new(self) if file?
          end
        end

        def file?
          finder.protect_from_errors { s = finder_stat and s.file? }
        end

        def directory?
          finder.protect_from_errors { s = finder_stat and s.directory? }
        end

        def exist?
          finder.protect_from_errors { File.exist?(self) }
        end

        def stat
          finder.protect_from_errors { File.stat(self) }
        end

        def lstat
          finder.protect_from_errors { File.lstat(self) }
        end

        def pathname
          Pathname.new(self)
        end

        def suffix
          pathname.extname[1..-1] || ''
        end
      end

      def initialize(opts = {})
        @show_hidden     = opts.fetch(:show_hidden)     { true }
        @raise_errors    = opts.fetch(:raise_errors)    { false }
        @follow_symlinks = opts.fetch(:follow_symlinks) { true }
        if opts.key?(:visit) && opts.key?(:suffix)
          raise ArgumentError, 'either use visit or suffix argument'
        elsif opts.key?(:visit)
          @visit = opts.fetch(:visit) { -> path { true } }
        elsif opts.key?(:suffix)
          @suffix = Array(opts[:suffix])
          @visit = -> path { @suffix.nil? || @suffix.empty? || @suffix.include?(path.suffix) }
        end
      end

      attr_accessor :show_hidden

      attr_accessor :raise_errors

      attr_accessor :follow_symlinks

      attr_accessor :suffix

      def visit_path?(path)
        if !defined?(@visit) || @visit.nil?
          true
        else
          @visit.(path)
        end
      end

      def find(*paths)
        block_given? or return enum_for(__method__, *paths)
        paths.collect! { |d| d.dup }
        while path = paths.shift
          path = prepare_path(path)
          catch(:prune) do
            stat = path.finder_stat or next
            visit_path?(path) and yield path
            if stat.directory?
              ps = protect_from_errors { Dir.entries(path) } or next
              ps.sort!
              ps.reverse_each do |p|
                next if p == "." or p == ".."
                next if !@show_hidden && p.start_with?('.')
                p = File.join(path, p)
                paths.unshift p
              end
            end
          end
        end
      end

      def prepare_path(path)
        path = path.dup
        path.extend PathExtension
        path.finder = self
        path
      end

      def protect_from_errors(errors = Find::EXPECTED_STANDARD_ERRORS)
        yield
      rescue errors
        raise_errors and raise
        return
      end
    end

    #
    # Calls the associated block with the name of every path and directory
    # listed as arguments, then recursively on their subdirectories, and so on.
    #
    # See the +Find+ module documentation for an example.
    #
    def find(*paths, &block) # :yield: path
      opts = Hash === paths.last ? paths.pop : {}
      Finder.new(opts).find(*paths, &block)
    end

    #
    # Skips the current path or directory, restarting the loop with the next
    # entry. If the current path is a directory, that directory will not be
    # recursively entered. Meaningful only within the block associated with
    # Find::find.
    #
    # See the +Find+ module documentation for an example.
    #
    def prune
      throw :prune
    end

    module_function :find, :prune
  end
end