PageRenderTime 50ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/gems/facets-2.4.5/lib/more/facets/filelist.rb

https://bitbucket.org/mediashelf/fedora-migrator
Ruby | 497 lines | 226 code | 45 blank | 226 comment | 15 complexity | 0a2104d81b44a8f581e066333da5277c MD5 | raw file
Possible License(s): GPL-3.0, GPL-2.0, IPL-1.0, AGPL-1.0, LGPL-3.0
  1. # = FileList
  2. #
  3. # A FileList is essentially an array with helper methods
  4. # to make file manipulation easier.
  5. #
  6. # FileLists are lazy. When given a list of glob patterns for
  7. # possible files to be included in the file list, instead of
  8. # searching the file structures to find the files, a FileList holds
  9. # the pattern for latter use.
  10. #
  11. # This allows us to define a number of FileList to match any number of
  12. # files, but only search out the actual files when then FileList
  13. # itself is actually used. The key is that the first time an
  14. # element of the FileList/Array is requested, the pending patterns
  15. # are resolved into a real list of file names.
  16. #
  17. # fl = FileList.new
  18. # fl.include('./**/*')
  19. # fl.exclude('./*~')
  20. #
  21. # == History
  22. #
  23. # FileList was ported from Jim Weirich's Rake.
  24. #
  25. # == Authors
  26. #
  27. # * Jim Weirich
  28. #
  29. # == Todo
  30. #
  31. # * Should the exclusions really be the default?
  32. # Maybe have #exclude_typical instead.
  33. #
  34. # == Copying
  35. #
  36. # Copyright (C) 2002 Jim Weirich
  37. #
  38. # General Public License (GPL)
  39. #
  40. # Permission is hereby granted, free of charge, to any person obtaining
  41. # a copy of this software and associated documentation files (the
  42. # "Software"), to deal in the Software without restriction, including
  43. # without limitation the rights to use, copy, modify, merge, publish,
  44. # distribute, sublicense, and/or sell copies of the Software, and to
  45. # permit persons to whom the Software is furnished to do so, subject to
  46. # the following conditions:
  47. #
  48. # The above copyright notice and this permission notice shall be
  49. # included in all copies or substantial portions of the Software.
  50. #
  51. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  52. # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  53. # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  54. # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  55. # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  56. # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  57. # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  58. # = FileList
  59. #
  60. # A FileList is essentially an array with helper methods
  61. # to make file manipulation easier.
  62. #
  63. # FileLists are lazy. When given a list of glob patterns for
  64. # possible files to be included in the file list, instead of
  65. # searching the file structures to find the files, a FileList holds
  66. # the pattern for latter use.
  67. #
  68. # This allows us to define a number of FileList to match any number of
  69. # files, but only search out the actual files when then FileList
  70. # itself is actually used. The key is that the first time an
  71. # element of the FileList/Array is requested, the pending patterns
  72. # are resolved into a real list of file names.
  73. #
  74. # fl = FileList.new
  75. # fl.include('./**/*')
  76. # fl.exclude('./*~')
  77. #
  78. class FileList
  79. # TODO: Add glob options.
  80. #attr :glob_options
  81. #include Cloneable
  82. def clone
  83. sibling = self.class.new
  84. instance_variables.each do |ivar|
  85. value = self.instance_variable_get(ivar)
  86. sibling.instance_variable_set(ivar, value.rake_dup)
  87. end
  88. sibling
  89. end
  90. alias_method :dup, :clone
  91. # == Method Delegation
  92. #
  93. # The lazy evaluation magic of FileLists happens by implementing
  94. # all the array specific methods to call +resolve+ before
  95. # delegating the heavy lifting to an embedded array object
  96. # (@items).
  97. #
  98. # In addition, there are two kinds of delegation calls. The
  99. # regular kind delegates to the @items array and returns the
  100. # result directly. Well, almost directly. It checks if the
  101. # returned value is the @items object itself, and if so will
  102. # return the FileList object instead.
  103. #
  104. # The second kind of delegation call is used in methods that
  105. # normally return a new Array object. We want to capture the
  106. # return value of these methods and wrap them in a new FileList
  107. # object. We enumerate these methods in the +SPECIAL_RETURN+ list
  108. # below.
  109. # List of array methods (that are not in +Object+) that need to be
  110. # delegated.
  111. ARRAY_METHODS = Array.instance_methods - Object.instance_methods
  112. # List of additional methods that must be delegated.
  113. MUST_DEFINE = %w[to_a inspect]
  114. # List of methods that should not be delegated here (we define
  115. # special versions of them explicitly below).
  116. MUST_NOT_DEFINE = %w[to_a to_ary partition *]
  117. # List of delegated methods that return new array values which
  118. # need wrapping.
  119. SPECIAL_RETURN = %w[
  120. map collect sort sort_by select find_all reject grep
  121. compact flatten uniq values_at
  122. + - & |
  123. ]
  124. DELEGATING_METHODS = (ARRAY_METHODS + MUST_DEFINE - MUST_NOT_DEFINE).sort.uniq
  125. # Now do the delegation.
  126. DELEGATING_METHODS.each_with_index do |sym, i|
  127. if SPECIAL_RETURN.include?(sym)
  128. ln = __LINE__+1
  129. class_eval %{
  130. def #{sym}(*args, &block)
  131. resolve if @pending
  132. result = @items.send(:#{sym}, *args, &block)
  133. FileList.new.import(result)
  134. end
  135. }, __FILE__, ln
  136. else
  137. ln = __LINE__+1
  138. class_eval %{
  139. def #{sym}(*args, &block)
  140. resolve if @pending
  141. result = @items.send(:#{sym}, *args, &block)
  142. result.object_id == @items.object_id ? self : result
  143. end
  144. }, __FILE__, ln
  145. end
  146. end
  147. # Create a file list from the globbable patterns given. If you
  148. # wish to perform multiple includes or excludes at object build
  149. # time, use the "yield self" pattern.
  150. #
  151. # Example:
  152. # file_list = FileList.new['lib/**/*.rb', 'test/test*.rb']
  153. #
  154. # pkg_files = FileList.new['lib/**/*'] do |fl|
  155. # fl.exclude(/\bCVS\b/)
  156. # end
  157. #
  158. def initialize(*patterns)
  159. @pending_add = []
  160. @pending = false
  161. @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
  162. @exclude_re = nil
  163. @items = []
  164. patterns.each { |pattern| include(pattern) }
  165. yield self if block_given?
  166. end
  167. # Add file names defined by glob patterns to the file list. If an
  168. # array is given, add each element of the array.
  169. #
  170. # Example:
  171. # file_list.include("*.java", "*.cfg")
  172. # file_list.include %w( math.c lib.h *.o )
  173. #
  174. def include(*filenames)
  175. # TODO: check for pending
  176. filenames.each do |fn|
  177. if fn.respond_to? :to_ary
  178. include(*fn.to_ary)
  179. else
  180. @pending_add << fn
  181. end
  182. end
  183. @pending = true
  184. self
  185. end
  186. alias :add :include
  187. # Register a list of file name patterns that should be excluded
  188. # from the list. Patterns may be regular expressions, glob
  189. # patterns or regular strings.
  190. #
  191. # Note that glob patterns are expanded against the file system.
  192. # If a file is explicitly added to a file list, but does not exist
  193. # in the file system, then an glob pattern in the exclude list
  194. # will not exclude the file.
  195. #
  196. # Examples:
  197. # FileList['a.c', 'b.c'].exclude("a.c") => ['b.c']
  198. # FileList['a.c', 'b.c'].exclude(/^a/) => ['b.c']
  199. #
  200. # If "a.c" is a file, then ...
  201. # FileList['a.c', 'b.c'].exclude("a.*") => ['b.c']
  202. #
  203. # If "a.c" is not a file, then ...
  204. # FileList['a.c', 'b.c'].exclude("a.*") => ['a.c', 'b.c']
  205. #
  206. def exclude(*patterns)
  207. patterns.each do |pat| @exclude_patterns << pat end
  208. if ! @pending
  209. calculate_exclude_regexp
  210. reject! { |fn| fn =~ @exclude_re }
  211. end
  212. self
  213. end
  214. # Clear all the exclude patterns so that we exclude nothing.
  215. def clear_exclude
  216. @exclude_patterns = []
  217. calculate_exclude_regexp if ! @pending
  218. end
  219. # Define equality.
  220. def ==(array)
  221. to_ary == array
  222. end
  223. # Return the internal array object.
  224. def to_a
  225. resolve
  226. @items
  227. end
  228. # Return the internal array object.
  229. def to_ary
  230. resolve
  231. @items
  232. end
  233. # Redefine * to return either a string or a new file list.
  234. def *(other)
  235. result = @items * other
  236. case result
  237. when Array
  238. FileList.new.import(result)
  239. else
  240. result
  241. end
  242. end
  243. # Resolve all the pending adds now.
  244. def resolve
  245. if @pending
  246. @pending = false
  247. @pending_add.each do |fn| resolve_add(fn) end
  248. @pending_add = []
  249. resolve_exclude
  250. end
  251. self
  252. end
  253. def calculate_exclude_regexp
  254. ignores = []
  255. @exclude_patterns.each do |pat|
  256. case pat
  257. when Regexp
  258. ignores << pat
  259. when /[*.]/
  260. Dir[pat].each do |p| ignores << p end
  261. else
  262. ignores << Regexp.quote(pat)
  263. end
  264. end
  265. if ignores.empty?
  266. @exclude_re = /^$/
  267. else
  268. re_str = ignores.collect { |p| "(" + p.to_s + ")" }.join("|")
  269. @exclude_re = Regexp.new(re_str)
  270. end
  271. end
  272. def resolve_add(fn)
  273. case fn
  274. when Array
  275. fn.each { |f| self.resolve_add(f) }
  276. when %r{[*?]}
  277. add_matching(fn)
  278. else
  279. self << fn
  280. end
  281. end
  282. def resolve_exclude
  283. @exclude_patterns.each do |pat|
  284. case pat
  285. when Regexp
  286. reject! { |fn| fn =~ pat }
  287. when /[*.]/
  288. reject_list = Dir[pat]
  289. reject! { |fn| reject_list.include?(fn) }
  290. else
  291. reject! { |fn| fn == pat }
  292. end
  293. end
  294. self
  295. end
  296. # Return a new FileList with the results of running +sub+ against
  297. # each element of the oringal list.
  298. #
  299. # Example:
  300. # FileList['a.c', 'b.c'].sub(/\.c$/, '.o') => ['a.o', 'b.o']
  301. #
  302. def sub(pat, rep)
  303. inject(FileList.new) { |res, fn| res << fn.sub(pat,rep) }
  304. end
  305. # Return a new FileList with the results of running +gsub+ against
  306. # each element of the original list.
  307. #
  308. # Example:
  309. # FileList['lib/test/file', 'x/y'].gsub(/\//, "\\")
  310. # => ['lib\\test\\file', 'x\\y']
  311. #
  312. def gsub(pat, rep)
  313. inject(FileList.new) { |res, fn| res << fn.gsub(pat,rep) }
  314. end
  315. # Same as +sub+ except that the oringal file list is modified.
  316. def sub!(pat, rep)
  317. each_with_index { |fn, i| self[i] = fn.sub(pat,rep) }
  318. self
  319. end
  320. # Same as +gsub+ except that the original file list is modified.
  321. def gsub!(pat, rep)
  322. each_with_index { |fn, i| self[i] = fn.gsub(pat,rep) }
  323. self
  324. end
  325. # Return a new array with <tt>String#ext</tt> method applied to
  326. # each member of the array.
  327. #
  328. # This method is a shortcut for:
  329. #
  330. # array.collect { |item| item.ext(newext) }
  331. #
  332. # +ext+ is a user added method for the Array class.
  333. def ext(newext='')
  334. collect { |fn| fn.ext(newext) }
  335. end
  336. # Grep each of the files in the filelist using the given pattern.
  337. # If a block is given, call the block on each matching line,
  338. # passing the file name, line number, and the matching line of
  339. # text. If no block is given, a standard emac style
  340. # file:linenumber:line message will be printed to standard out.
  341. def egrep(pattern)
  342. each do |fn|
  343. open(fn) do |inf|
  344. count = 0
  345. inf.each do |line|
  346. count += 1
  347. if pattern.match(line)
  348. if block_given?
  349. yield fn, count, line
  350. else
  351. puts "#{fn}:#{count}:#{line}"
  352. end
  353. end
  354. end
  355. end
  356. end
  357. end
  358. # FileList version of partition. Needed because the nested arrays
  359. # should be FileLists in this version.
  360. def partition(&block) # :nodoc:
  361. resolve
  362. result = @items.partition(&block)
  363. [
  364. FileList.new.import(result[0]),
  365. FileList.new.import(result[1]),
  366. ]
  367. end
  368. # Convert a FileList to a string by joining all elements with a space.
  369. def to_s
  370. resolve if @pending
  371. self.join(' ')
  372. end
  373. # Add matching glob patterns.
  374. def add_matching(pattern)
  375. Dir[pattern].each do |fn|
  376. self << fn unless exclude?(fn)
  377. end
  378. end
  379. private :add_matching
  380. # Should the given file name be excluded?
  381. def exclude?(fn)
  382. calculate_exclude_regexp unless @exclude_re
  383. fn =~ @exclude_re
  384. end
  385. DEFAULT_IGNORE_PATTERNS = [
  386. /(^|[\/\\])CVS([\/\\]|$)/,
  387. /(^|[\/\\])\.svn([\/\\]|$)/,
  388. /(^|[\/\\])_darcs([\/\\]|$)/,
  389. /\.bak$/,
  390. /~$/,
  391. /(^|[\/\\])core$/
  392. ]
  393. @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
  394. def import(array)
  395. @items = array
  396. self
  397. end
  398. class << self
  399. # Create a new file list including the files listed. Similar to:
  400. #
  401. # FileList.new(*args)
  402. def [](*args)
  403. new(*args)
  404. end
  405. # Set the ignore patterns back to the default value. The
  406. # default patterns will ignore files
  407. # * containing "CVS" in the file path
  408. # * containing ".svn" in the file path
  409. # * containing "_darcs" in the file path
  410. # * ending with ".bak"
  411. # * ending with "~"
  412. # * named "core"
  413. #
  414. # Note that file names beginning with "." are automatically
  415. # ignored by Ruby's glob patterns and are not specifically
  416. # listed in the ignore patterns.
  417. def select_default_ignore_patterns
  418. @exclude_patterns = DEFAULT_IGNORE_PATTERNS.dup
  419. end
  420. # Clear the ignore patterns.
  421. def clear_ignore_patterns
  422. @exclude_patterns = [ /^$/ ]
  423. end
  424. end
  425. end # FileList
  426. # = TEST
  427. #
  428. # TODO This test needs a mock File class.
  429. # FileList can't be tested without a FIXTURE setup.
  430. # So don't bother running it directly from here, but
  431. # transfer it over to the test directory.
  432. # Need a better solution, is there a way to fake the
  433. # filesystem?
  434. =begin #no test
  435. require 'test/unit'
  436. class TC_FileList < Test::Unit::TestCase
  437. def test_filelist
  438. Dir.chdir File.join( $TESTDIR, 'filelist' ) do
  439. fl = FileList.new
  440. fl.include('*')
  441. assert_equal( ['testfile.txt', 'testfile2.txt'], fl.to_a )
  442. fl.exclude('*2.txt')
  443. assert_equal( ['testfile.txt'], fl.to_a )
  444. end
  445. end
  446. end
  447. =end