jamis-fuzzy_file_finder 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,10 +1,10 @@
1
1
 
2
- # Gem::Specification for Fuzzy_file_finder-1.0.1
2
+ # Gem::Specification for Fuzzy_file_finder-1.0.2
3
3
  # Originally generated by Echoe
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = %q{fuzzy_file_finder}
7
- s.version = "1.0.1"
7
+ s.version = "1.0.2"
8
8
 
9
9
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
10
  s.authors = ["Jamis Buck"]
@@ -37,7 +37,7 @@ class FuzzyFileFinder
37
37
  module Version
38
38
  MAJOR = 1
39
39
  MINOR = 0
40
- TINY = 1
40
+ TINY = 2
41
41
  STRING = [MAJOR, MINOR, TINY].join(".")
42
42
  end
43
43
 
@@ -62,56 +62,64 @@ class FuzzyFileFinder
62
62
 
63
63
  # Used internally to represent a file within the directory tree.
64
64
  class FileSystemEntry #:nodoc:
65
+ attr_reader :parent
65
66
  attr_reader :name
66
67
 
67
- def initialize(name)
68
+ def initialize(parent, name)
69
+ @parent = parent
68
70
  @name = name
69
71
  end
70
72
 
71
- def directory?
72
- false
73
+ def path
74
+ File.join(parent.name, name)
73
75
  end
74
76
  end
75
77
 
76
78
  # Used internally to represent a subdirectory within the directory
77
79
  # tree.
78
- class Directory < FileSystemEntry
79
- attr_reader :children
80
+ class Directory #:nodoc:
81
+ attr_reader :name
80
82
 
81
- def initialize(name)
82
- @children = []
83
- super
83
+ def initialize(name, is_root=false)
84
+ @name = name
85
+ @is_root = is_root
84
86
  end
85
87
 
86
- def directory?
87
- true
88
+ def root?
89
+ is_root
88
90
  end
89
91
  end
90
92
 
91
93
  # The roots directory trees to search.
92
94
  attr_reader :roots
93
95
 
94
- # The maximum number of files and directories (combined).
95
- attr_reader :ceiling
96
+ # The list of files beneath all +roots+
97
+ attr_reader :files
96
98
 
97
- # The number of directories beneath all +roots+
98
- attr_reader :directory_count
99
+ # The maximum number of files beneath all +roots+
100
+ attr_reader :ceiling
99
101
 
100
- # The number of files beneath all +roots+
101
- attr_reader :file_count
102
+ # The prefix shared by all +roots+.
103
+ attr_reader :shared_prefix
102
104
 
103
105
  # Initializes a new FuzzyFileFinder. This will scan the
104
- # given +directory+, using +ceiling+ as the maximum number
106
+ # given +directories+, using +ceiling+ as the maximum number
105
107
  # of entries to scan. If there are more than +ceiling+ entries
106
108
  # a TooManyEntries exception will be raised.
107
109
  def initialize(directories=['.'], ceiling=10_000)
108
- directories = directories.any? ? directories : ['.']
110
+ directories = Array(directories)
111
+ directories << "." if directories.empty?
109
112
 
110
113
  # expand any paths with ~
111
- root_dirnames = directories.map { |d| File.expand_path(d) }.select { |d| File.directory?(d) }
114
+ root_dirnames = directories.map { |d| File.expand_path(d) }.select { |d| File.directory?(d) }.uniq
115
+
116
+ @roots = root_dirnames.map { |d| Directory.new(d, true) }
117
+ @shared_prefix = determine_shared_prefix
118
+ @shared_prefix_re = Regexp.new("^#{Regexp.escape(shared_prefix)}" + (shared_prefix.empty? ? "" : "/"))
112
119
 
113
- @roots = root_dirnames.map { |d| Directory.new(d) }
120
+ @files = []
114
121
  @ceiling = ceiling
122
+
115
123
  rescan!
116
124
  end
117
125
 
@@ -119,10 +127,8 @@ class FuzzyFileFinder
119
127
  # you'll need to call this to force the finder to be aware of
120
128
  # the changes.
121
129
  def rescan!
122
- roots.each { |r| r.children.clear }
123
- @file_count = 0
124
- @directory_count = 0
125
- roots.each { |r| follow_tree(r.name, r) }
130
+ @files.clear
131
+ roots.each { |root| follow_tree(root) }
126
132
  end
127
133
 
128
134
  # Takes the given +pattern+ (which must be a string) and searches
@@ -173,8 +179,12 @@ class FuzzyFileFinder
173
179
  file_regex_raw = "^(.*?)" << make_pattern(file_name_part) << "(.*)$"
174
180
  file_regex = Regexp.new(file_regex_raw, Regexp::IGNORECASE)
175
181
 
176
- roots.each do |root|
177
- do_search(path_regex, path_parts.length, file_regex, root, &block)
182
+ path_matches = {}
183
+ files.each do |file|
184
+ path_match = match_path(file.parent, path_matches, path_regex, path_parts.length)
185
+ next if path_match[:missed]
186
+
187
+ match_file(file, file_regex, path_match, &block)
178
188
  end
179
189
  end
180
190
 
@@ -192,27 +202,24 @@ class FuzzyFileFinder
192
202
 
193
203
  # Displays the finder object in a sane, non-explosive manner.
194
204
  def inspect #:nodoc:
195
- "#<%s:0x%x roots=%s, files=%d, directories=%d>" % [self.class.name, object_id, roots.map { |r| r.name.inspect }.join(", "), file_count, directory_count]
205
+ "#<%s:0x%x roots=%s, files=%d>" % [self.class.name, object_id, roots.map { |r| r.name.inspect }.join(", "), files.length]
196
206
  end
197
207
 
198
208
  private
199
209
 
200
- # Processes the given +path+ into the given +directory+ object,
201
- # recursively following subdirectories in a depth-first manner.
202
- def follow_tree(path, directory)
203
- Dir.entries(path).each do |entry|
210
+ # Recursively scans +directory+ and all files and subdirectories
211
+ # beneath it, depth-first.
212
+ def follow_tree(directory)
213
+ Dir.entries(directory.name).each do |entry|
204
214
  next if entry[0,1] == "."
205
- raise TooManyEntries if file_count + directory_count > ceiling
215
+ raise TooManyEntries if files.length > ceiling
216
+
217
+ full = File.join(directory.name, entry)
206
218
 
207
- full = path == "." ? entry : File.join(path, entry)
208
219
  if File.directory?(full)
209
- @directory_count += 1
210
- subdir = Directory.new(full)
211
- directory.children << subdir
212
- follow_tree(full, subdir)
220
+ follow_tree(Directory.new(full))
213
221
  else
214
- @file_count += 1
215
- directory.children << FileSystemEntry.new(entry)
222
+ files.push(FileSystemEntry.new(directory, entry))
216
223
  end
217
224
  end
218
225
  end
@@ -271,52 +278,62 @@ class FuzzyFileFinder
271
278
  return { :score => score, :result => runs.join }
272
279
  end
273
280
 
274
- # Do the actual search, recursively. +path_regex+ is either nil,
275
- # or a regular expression to match against directory names. The
276
- # +path_segments+ parameter is an integer indicating how many
277
- # directory segments there were in the original pattern. The
278
- # +file_regex+ is a regular expression to match against the file
279
- # name, +under+ is a Directory object to search. Matches are
280
- # yielded.
281
- def do_search(path_regex, path_segments, file_regex, under, &block)
282
- # If a path_regex is present, match the current directory against
283
- # it and, if there is a match, compute the score and highlighted
284
- # result.
285
- path_match = path_regex && under.name.match(path_regex)
286
-
287
- if path_match
288
- path_match_result = build_match_result(path_match, path_segments)
289
- path_match_score = path_match_result[:score]
290
- path_match_result = path_match_result[:result]
281
+ # Match the given path against the regex, caching the result in +path_matches+.
282
+ # If +path+ is already cached in the path_matches cache, just return the cached
283
+ # value.
284
+ def match_path(path, path_matches, path_regex, path_segments)
285
+ return path_matches[path] if path_matches.key?(path)
286
+
287
+ matchable_name = path.name.sub(@shared_prefix_re, "")
288
+
289
+ if path_regex
290
+ match = matchable_name.match(path_regex)
291
+
292
+ path_matches[path] =
293
+ match && build_match_result(match, path_segments) ||
294
+ { :score => 1, :result => matchable_name, :missed => true }
291
295
  else
292
- path_match_score = 1
296
+ path_matches[path] = { :score => 1, :result => matchable_name }
293
297
  end
298
+ end
294
299
 
295
- # determine whether +under+ is one of our root objects or not
296
- is_root = roots.any? { |root| root == under }
297
-
298
- # For each child of the directory, search under subdirectories, or
299
- # match files.
300
- under.children.each do |entry|
301
- full = is_root ? entry.name : File.join(under.name, entry.name)
302
- if entry.directory?
303
- do_search(path_regex, path_segments, file_regex, entry, &block)
304
- elsif (path_regex.nil? || path_match) && file_match = entry.name.match(file_regex)
305
- match_result = build_match_result(file_match, 1)
306
- highlighted_directory = path_match_result || under.name
307
- full_match_result = File.join(highlighted_directory, match_result[:result])
308
- abbr = File.join(highlighted_directory.gsub(/[^\/]+/) { |m| m.index("(") ? m : m[0,1] }, match_result[:result])
309
-
310
- result = { :path => full,
311
- :abbr => abbr,
312
- :directory => under.name,
313
- :name => entry.name,
314
- :highlighted_directory => highlighted_directory,
315
- :highlighted_name => match_result[:result],
316
- :highlighted_path => full_match_result,
317
- :score => path_match_score * match_result[:score] }
318
- yield result
300
+ # Match +file+ against +file_regex+. If it matches, yield the match
301
+ # metadata to the block.
302
+ def match_file(file, file_regex, path_match, &block)
303
+ if file_match = file.name.match(file_regex)
304
+ match_result = build_match_result(file_match, 1)
305
+ full_match_result = File.join(path_match[:result], match_result[:result])
306
+ abbr = File.join(path_match[:result].gsub(/[^\/]+/) { |m| m.index("(") ? m : m[0,1] }, match_result[:result])
307
+
308
+ result = { :path => file.path,
309
+ :abbr => abbr,
310
+ :directory => file.parent.name,
311
+ :name => file.name,
312
+ :highlighted_directory => path_match[:result],
313
+ :highlighted_name => match_result[:result],
314
+ :highlighted_path => full_match_result,
315
+ :score => path_match[:score] * match_result[:score] }
316
+ yield result
317
+ end
318
+ end
319
+
320
+ def determine_shared_prefix
321
+ # the common case: if there is only a single root, then the entire
322
+ # name of the root is the shared prefix.
323
+ return roots.first.name if roots.length == 1
324
+
325
+ split_roots = roots.map { |root| root.name.split(%r{/}) }
326
+ segments = split_roots.map { |root| root.length }.max
327
+ master = split_roots.pop
328
+
329
+ segments.times do |segment|
330
+ if !split_roots.all? { |root| root[segment] == master[segment] }
331
+ return master[0,segment].join("/")
319
332
  end
320
333
  end
334
+
335
+ # shouldn't ever get here, since we uniq the root list before
336
+ # calling this method, but if we do, somehow...
337
+ return roots.first.name
321
338
  end
322
339
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jamis-fuzzy_file_finder
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jamis Buck