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
. -
#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) ⇒ 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) ⇒ 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.
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/fuzzy_file_finder.rb', line 109 def initialize(directories=['.'], ceiling=10_000) 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 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 |
#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.
194 195 196 197 198 199 200 201 |
# File 'lib/fuzzy_file_finder.rb', line 194 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.
204 205 206 |
# File 'lib/fuzzy_file_finder.rb', line 204 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.
129 130 131 132 |
# File 'lib/fuzzy_file_finder.rb', line 129 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.
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/fuzzy_file_finder.rb', line 167 def search(pattern, &block) pattern.strip! 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 |