paths.rb 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. require 'set'
  2. module Rails
  3. module Paths
  4. # This object is an extended hash that behaves as root of the <tt>Rails::Paths</tt> system.
  5. # It allows you to collect information about how you want to structure your application
  6. # paths by a Hash like API. It requires you to give a physical path on initialization.
  7. #
  8. # root = Root.new "/rails"
  9. # root.add "app/controllers", :eager_load => true
  10. #
  11. # The command above creates a new root object and add "app/controllers" as a path.
  12. # This means we can get a +Rails::Paths::Path+ object back like below:
  13. #
  14. # path = root["app/controllers"]
  15. # path.eager_load? # => true
  16. # path.is_a?(Rails::Paths::Path) # => true
  17. #
  18. # The +Path+ object is simply an array and allows you to easily add extra paths:
  19. #
  20. # path.is_a?(Array) # => true
  21. # path.inspect # => ["app/controllers"]
  22. #
  23. # path << "lib/controllers"
  24. # path.inspect # => ["app/controllers", "lib/controllers"]
  25. #
  26. # Notice that when you add a path using +add+, the path object created already
  27. # contains the path with the same path value given to +add+. In some situations,
  28. # you may not want this behavior, so you can give :with as option.
  29. #
  30. # root.add "config/routes", :with => "config/routes.rb"
  31. # root["config/routes"].inspect # => ["config/routes.rb"]
  32. #
  33. # The +add+ method accepts the following options as arguments:
  34. # eager_load, autoload, autoload_once and glob.
  35. #
  36. # Finally, the +Path+ object also provides a few helpers:
  37. #
  38. # root = Root.new "/rails"
  39. # root.add "app/controllers"
  40. #
  41. # root["app/controllers"].expanded # => ["/rails/app/controllers"]
  42. # root["app/controllers"].existent # => ["/rails/app/controllers"]
  43. #
  44. # Check the <tt>Rails::Paths::Path</tt> documentation for more information.
  45. class Root < ::Hash
  46. attr_accessor :path
  47. def initialize(path)
  48. raise "Argument should be a String of the physical root path" if path.is_a?(Array)
  49. @current = nil
  50. @path = path
  51. @root = self
  52. super()
  53. end
  54. def []=(path, value)
  55. value = Path.new(self, path, value) unless value.is_a?(Path)
  56. super(path, value)
  57. end
  58. def add(path, options={})
  59. with = options[:with] || path
  60. self[path] = Path.new(self, path, with, options)
  61. end
  62. def all_paths
  63. values.tap { |v| v.uniq! }
  64. end
  65. def autoload_once
  66. filter_by(:autoload_once?)
  67. end
  68. def eager_load
  69. filter_by(:eager_load?)
  70. end
  71. def autoload_paths
  72. filter_by(:autoload?)
  73. end
  74. def load_paths
  75. filter_by(:load_path?)
  76. end
  77. protected
  78. def filter_by(constraint)
  79. all = []
  80. all_paths.each do |path|
  81. if path.send(constraint)
  82. paths = path.existent
  83. paths -= path.children.map { |p| p.send(constraint) ? [] : p.existent }.flatten
  84. all.concat(paths)
  85. end
  86. end
  87. all.uniq!
  88. all
  89. end
  90. end
  91. class Path < Array
  92. attr_reader :path
  93. attr_accessor :glob
  94. def initialize(root, current, *paths)
  95. options = paths.last.is_a?(::Hash) ? paths.pop : {}
  96. super(paths.flatten)
  97. @current = current
  98. @root = root
  99. @glob = options[:glob]
  100. options[:autoload_once] ? autoload_once! : skip_autoload_once!
  101. options[:eager_load] ? eager_load! : skip_eager_load!
  102. options[:autoload] ? autoload! : skip_autoload!
  103. options[:load_path] ? load_path! : skip_load_path!
  104. end
  105. def children
  106. keys = @root.keys.select { |k| k.include?(@current) }
  107. keys.delete(@current)
  108. @root.values_at(*keys.sort)
  109. end
  110. def first
  111. expanded.first
  112. end
  113. def last
  114. expanded.last
  115. end
  116. %w(autoload_once eager_load autoload load_path).each do |m|
  117. class_eval <<-RUBY, __FILE__, __LINE__ + 1
  118. def #{m}! # def eager_load!
  119. @#{m} = true # @eager_load = true
  120. end # end
  121. #
  122. def skip_#{m}! # def skip_eager_load!
  123. @#{m} = false # @eager_load = false
  124. end # end
  125. #
  126. def #{m}? # def eager_load?
  127. @#{m} # @eager_load
  128. end # end
  129. RUBY
  130. end
  131. # Expands all paths against the root and return all unique values.
  132. def expanded
  133. raise "You need to set a path root" unless @root.path
  134. result = []
  135. each do |p|
  136. path = File.expand_path(p, @root.path)
  137. if @glob
  138. if File.directory? path
  139. result.concat expand_dir(path, @glob)
  140. else
  141. # FIXME: I think we can remove this branch, but I'm not sure.
  142. # Say the filesystem has this file:
  143. #
  144. # /tmp/foobar
  145. #
  146. # and someone adds this path:
  147. #
  148. # /tmp/foo
  149. #
  150. # with a glob of "*", then this function will return
  151. #
  152. # /tmp/foobar
  153. #
  154. # We need to figure out if that is desired behavior.
  155. result.concat expand_file(path, @glob)
  156. end
  157. else
  158. result << path
  159. end
  160. end
  161. result.uniq!
  162. result
  163. end
  164. # Returns all expanded paths but only if they exist in the filesystem.
  165. def existent
  166. expanded.select { |f| File.exists?(f) }
  167. end
  168. def existent_directories
  169. expanded.select { |d| File.directory?(d) }
  170. end
  171. alias to_a expanded
  172. private
  173. def expand_file(path, glob)
  174. Dir[File.join(path, glob)].sort
  175. end
  176. def expand_dir(path, glob)
  177. Dir.chdir(path) do
  178. Dir.glob(@glob).map { |file| File.join path, file }.sort
  179. end
  180. end
  181. end
  182. end
  183. end