Class: FuzzyFileFinder
- Inherits:
-
Object
- Object
- FuzzyFileFinder
- Defined in:
- lib/fuzzy_file_finder.rb
Overview
The “fuzzy” file finder provides a way for searching a directory tree with only a partial name. This is similar to the “cmd-T” feature in TextMate (macromates.com).
Usage:
finder = FuzzyFileFinder.new
finder.search("app/blogcon") do |match|
puts match[:highlighted_path]
end
In the above example, all files matching “app/blogcon” will be yielded to the block. The given pattern is reduced to a regular expression internally, so that any file that contains those characters in that order (even if there are other characters in between) will match.
In other words, “app/blogcon” would match any of the following (parenthesized strings indicate how the match was made):
-
(app)/controllers/(blog)_(con)troller.rb
-
lib/c(ap)_(p)ool/(bl)ue_(o)r_(g)reen_(co)loratio(n)
-
test/(app)/(blog)_(con)troller_test.rb
And so forth.
Defined Under Namespace
Modules: Version Classes: CharacterRun, Directory, FileSystemEntry, TooManyEntries
Instance Attribute Summary collapse
-
#ceiling ⇒ Object
readonly
The maximum number of files beneath all
roots
. -
#files ⇒ Object
readonly
The list of files beneath all
roots
. -
#ignores ⇒ Object
readonly
The list of glob patterns to ignore.
-
#roots ⇒ Object
readonly
The roots directory trees to search.
-
#shared_prefix ⇒ Object
readonly
The prefix shared by all
roots
.
Instance Method Summary collapse
-
#find(pattern, max = nil) ⇒ Object
Takes the given
pattern
(which must be a string, formatted as described in #search), and returns up tomax
matches in an Array. -
#initialize(directories = ['.'], ceiling = 10_000, ignores = nil) ⇒ FuzzyFileFinder
constructor
Initializes a new FuzzyFileFinder.
-
#inspect ⇒ Object
Displays the finder object in a sane, non-explosive manner.
-
#rescan! ⇒ Object
Rescans the subtree.
-
#search(pattern, &block) ⇒ Object
Takes the given
pattern
(which must be a string) and searches all files beneathroot
, yielding each match.
Constructor Details
#initialize(directories = ['.'], ceiling = 10_000, ignores = nil) ⇒ FuzzyFileFinder
Initializes a new FuzzyFileFinder. This will scan the given directories
, using ceiling
as the maximum number of entries to scan. If there are more than ceiling
entries a TooManyEntries exception will be raised.
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/fuzzy_file_finder.rb', line 112 def initialize(directories=['.'], ceiling=10_000, ignores=nil) directories = Array(directories) directories << "." if directories.empty? # expand any paths with ~ root_dirnames = directories.map { |d| File.(d) }.select { |d| File.directory?(d) }.uniq @roots = root_dirnames.map { |d| Directory.new(d, true) } @shared_prefix = determine_shared_prefix @shared_prefix_re = Regexp.new("^#{Regexp.escape(shared_prefix)}" + (shared_prefix.empty? ? "" : "/")) @files = [] @ceiling = ceiling @ignores = Array(ignores) rescan! end |
Instance Attribute Details
#ceiling ⇒ Object (readonly)
The maximum number of files beneath all roots
100 101 102 |
# File 'lib/fuzzy_file_finder.rb', line 100 def ceiling @ceiling end |
#files ⇒ Object (readonly)
The list of files beneath all roots
97 98 99 |
# File 'lib/fuzzy_file_finder.rb', line 97 def files @files end |
#ignores ⇒ Object (readonly)
The list of glob patterns to ignore.
106 107 108 |
# File 'lib/fuzzy_file_finder.rb', line 106 def ignores @ignores end |
#roots ⇒ Object (readonly)
The roots directory trees to search.
94 95 96 |
# File 'lib/fuzzy_file_finder.rb', line 94 def roots @roots end |
#shared_prefix ⇒ Object (readonly)
The prefix shared by all roots
.
103 104 105 |
# File 'lib/fuzzy_file_finder.rb', line 103 def shared_prefix @shared_prefix end |
Instance Method Details
#find(pattern, max = nil) ⇒ Object
Takes the given pattern
(which must be a string, formatted as described in #search), and returns up to max
matches in an Array. If max
is nil, all matches will be returned.
199 200 201 202 203 204 205 206 |
# File 'lib/fuzzy_file_finder.rb', line 199 def find(pattern, max=nil) results = [] search(pattern) do |match| results << match break if max && results.length >= max end return results end |
#inspect ⇒ Object
Displays the finder object in a sane, non-explosive manner.
209 210 211 |
# File 'lib/fuzzy_file_finder.rb', line 209 def inspect #:nodoc: "#<%s:0x%x roots=%s, files=%d>" % [self.class.name, object_id, roots.map { |r| r.name.inspect }.join(", "), files.length] end |
#rescan! ⇒ Object
Rescans the subtree. If the directory contents every change, you’ll need to call this to force the finder to be aware of the changes.
134 135 136 137 |
# File 'lib/fuzzy_file_finder.rb', line 134 def rescan! @files.clear roots.each { |root| follow_tree(root) } end |
#search(pattern, &block) ⇒ Object
Takes the given pattern
(which must be a string) and searches all files beneath root
, yielding each match.
pattern
is interpreted thus:
-
“foo” : look for any file with the characters ‘f’, ‘o’, and ‘o’ in its basename (discounting directory names). The characters must be in that order.
-
“foo/bar” : look for any file with the characters ‘b’, ‘a’, and ‘r’ in its basename (discounting directory names). Also, any successful match must also have at least one directory element matching the characters ‘f’, ‘o’, and ‘o’ (in that order.
-
“foo/bar/baz” : same as “foo/bar”, but matching two directory elements in addition to a file name of “baz”.
Each yielded match will be a hash containing the following keys:
-
:path refers to the full path to the file
-
:directory refers to the directory of the file
-
:name refers to the name of the file (without directory)
-
:highlighted_directory refers to the directory of the file with matches highlighted in parentheses.
-
:highlighted_name refers to the name of the file with matches highlighted in parentheses
-
:highlighted_path refers to the full path of the file with matches highlighted in parentheses
-
:abbr refers to an abbreviated form of :highlighted_path, where path segments without matches are compressed to just their first character.
-
:score refers to a value between 0 and 1 indicating how closely the file matches the given pattern. A score of 1 means the pattern matches the file exactly.
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/fuzzy_file_finder.rb', line 172 def search(pattern, &block) pattern.gsub!(" ", "") path_parts = pattern.split("/") path_parts.push "" if pattern[-1,1] == "/" file_name_part = path_parts.pop || "" if path_parts.any? path_regex_raw = "^(.*?)" + path_parts.map { |part| make_pattern(part) }.join("(.*?/.*?)") + "(.*?)$" path_regex = Regexp.new(path_regex_raw, Regexp::IGNORECASE) end file_regex_raw = "^(.*?)" << make_pattern(file_name_part) << "(.*)$" file_regex = Regexp.new(file_regex_raw, Regexp::IGNORECASE) path_matches = {} files.each do |file| path_match = match_path(file.parent, path_matches, path_regex, path_parts.length) next if path_match[:missed] match_file(file, file_regex, path_match, &block) end end |