trail.rb 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. require 'pathname'
  2. require 'hike/extensions'
  3. require 'hike/index'
  4. require 'hike/paths'
  5. module Hike
  6. # `Trail` is the public container class for holding paths and extensions.
  7. class Trail
  8. # `Trail#paths` is a mutable `Paths` collection.
  9. #
  10. # trail = Hike::Trail.new
  11. # trail.paths.push "~/Projects/hike/lib", "~/Projects/hike/test"
  12. #
  13. # The order of the paths is significant. Paths in the beginning of
  14. # the collection will be checked first. In the example above,
  15. # `~/Projects/hike/lib/hike.rb` would shadow the existent of
  16. # `~/Projects/hike/test/hike.rb`.
  17. attr_reader :paths
  18. # `Trail#extensions` is a mutable `Extensions` collection.
  19. #
  20. # trail = Hike::Trail.new
  21. # trail.paths.push "~/Projects/hike/lib"
  22. # trail.extensions.push ".rb"
  23. #
  24. # Extensions allow you to find files by just their name omitting
  25. # their extension. Is similar to Ruby's require mechanism that
  26. # allows you to require files with specifiying `foo.rb`.
  27. attr_reader :extensions
  28. # `Index#aliases` is a mutable `Hash` mapping an extension to
  29. # an `Array` of aliases.
  30. #
  31. # trail = Hike::Trail.new
  32. # trail.paths.push "~/Projects/hike/site"
  33. # trail.aliases['.htm'] = 'html'
  34. # trail.aliases['.xhtml'] = 'html'
  35. # trail.aliases['.php'] = 'html'
  36. #
  37. # Aliases provide a fallback when the primary extension is not
  38. # matched. In the example above, a lookup for "foo.html" will
  39. # check for the existence of "foo.htm", "foo.xhtml", or "foo.php".
  40. attr_reader :aliases
  41. # A Trail accepts an optional root path that defaults to your
  42. # current working directory. Any relative paths added to
  43. # `Trail#paths` will expanded relative to the root.
  44. def initialize(root = ".")
  45. @root = Pathname.new(root).expand_path
  46. @paths = Paths.new(@root)
  47. @extensions = Extensions.new
  48. @aliases = Hash.new { |h, k| h[k] = Extensions.new }
  49. end
  50. # `Trail#root` returns root path as a `String`. This attribute is immutable.
  51. def root
  52. @root.to_s
  53. end
  54. # Prepend `path` to `Paths` collection
  55. def prepend_paths(*paths)
  56. self.paths.unshift(*paths)
  57. end
  58. alias_method :prepend_path, :prepend_paths
  59. # Append `path` to `Paths` collection
  60. def append_paths(*paths)
  61. self.paths.push(*paths)
  62. end
  63. alias_method :append_path, :append_paths
  64. # Remove `path` from `Paths` collection
  65. def remove_path(path)
  66. self.paths.delete(path)
  67. end
  68. # Prepend `extension` to `Extensions` collection
  69. def prepend_extensions(*extensions)
  70. self.extensions.unshift(*extensions)
  71. end
  72. alias_method :prepend_extension, :prepend_extensions
  73. # Append `extension` to `Extensions` collection
  74. def append_extensions(*extensions)
  75. self.extensions.push(*extensions)
  76. end
  77. alias_method :append_extension, :append_extensions
  78. # Remove `extension` from `Extensions` collection
  79. def remove_extension(extension)
  80. self.extensions.delete(extension)
  81. end
  82. # Alias `new_extension` to `old_extension`
  83. def alias_extension(new_extension, old_extension)
  84. aliases[normalize_extension(new_extension)] = normalize_extension(old_extension)
  85. end
  86. # Remove the alias for `extension`
  87. def unalias_extension(extension)
  88. aliases.delete(normalize_extension(extension))
  89. end
  90. # `Trail#find` returns a the expand path for a logical path in the
  91. # path collection.
  92. #
  93. # trail = Hike::Trail.new "~/Projects/hike"
  94. # trail.extensions.push ".rb"
  95. # trail.paths.push "lib", "test"
  96. #
  97. # trail.find "hike/trail"
  98. # # => "~/Projects/hike/lib/hike/trail.rb"
  99. #
  100. # trail.find "test_trail"
  101. # # => "~/Projects/hike/test/test_trail.rb"
  102. #
  103. # `find` accepts multiple fallback logical paths that returns the
  104. # first match.
  105. #
  106. # trail.find "hike", "hike/index"
  107. #
  108. # is equivalent to
  109. #
  110. # trail.find("hike") || trail.find("hike/index")
  111. #
  112. # Though `find` always returns the first match, it is possible
  113. # to iterate over all shadowed matches and fallbacks by supplying
  114. # a block.
  115. #
  116. # trail.find("hike", "hike/index") { |path| warn path }
  117. #
  118. # This allows you to filter your matches by any condition.
  119. #
  120. # trail.find("application") do |path|
  121. # return path if mime_type_for(path) == "text/css"
  122. # end
  123. #
  124. def find(*args, &block)
  125. index.find(*args, &block)
  126. end
  127. # `Trail#index` returns an `Index` object that has the same
  128. # interface as `Trail`. An `Index` is a cached `Trail` object that
  129. # does not update when the file system changes. If you are
  130. # confident that you are not making changes the paths you are
  131. # searching, `index` will avoid excess system calls.
  132. #
  133. # index = trail.index
  134. # index.find "hike/trail"
  135. # index.find "test_trail"
  136. #
  137. def index
  138. Index.new(root, paths, extensions, aliases)
  139. end
  140. # `Trail#entries` is equivalent to `Dir#entries`. It is not
  141. # recommend to use this method for general purposes. It exists for
  142. # parity with `Index#entries`.
  143. def entries(*args)
  144. index.entries(*args)
  145. end
  146. # `Trail#stat` is equivalent to `File#stat`. It is not
  147. # recommend to use this method for general purposes. It exists for
  148. # parity with `Index#stat`.
  149. def stat(*args)
  150. index.stat(*args)
  151. end
  152. private
  153. def normalize_extension(extension)
  154. if extension[/^\./]
  155. extension
  156. else
  157. ".#{extension}"
  158. end
  159. end
  160. end
  161. end