actionview 4.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +274 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +34 -0
  5. data/lib/action_view.rb +97 -0
  6. data/lib/action_view/base.rb +205 -0
  7. data/lib/action_view/buffers.rb +49 -0
  8. data/lib/action_view/context.rb +36 -0
  9. data/lib/action_view/dependency_tracker.rb +93 -0
  10. data/lib/action_view/digestor.rb +116 -0
  11. data/lib/action_view/flows.rb +76 -0
  12. data/lib/action_view/helpers.rb +64 -0
  13. data/lib/action_view/helpers/active_model_helper.rb +49 -0
  14. data/lib/action_view/helpers/asset_tag_helper.rb +322 -0
  15. data/lib/action_view/helpers/asset_url_helper.rb +355 -0
  16. data/lib/action_view/helpers/atom_feed_helper.rb +203 -0
  17. data/lib/action_view/helpers/cache_helper.rb +200 -0
  18. data/lib/action_view/helpers/capture_helper.rb +216 -0
  19. data/lib/action_view/helpers/controller_helper.rb +25 -0
  20. data/lib/action_view/helpers/csrf_helper.rb +30 -0
  21. data/lib/action_view/helpers/date_helper.rb +1075 -0
  22. data/lib/action_view/helpers/debug_helper.rb +39 -0
  23. data/lib/action_view/helpers/form_helper.rb +1876 -0
  24. data/lib/action_view/helpers/form_options_helper.rb +843 -0
  25. data/lib/action_view/helpers/form_tag_helper.rb +746 -0
  26. data/lib/action_view/helpers/javascript_helper.rb +75 -0
  27. data/lib/action_view/helpers/number_helper.rb +425 -0
  28. data/lib/action_view/helpers/output_safety_helper.rb +38 -0
  29. data/lib/action_view/helpers/record_tag_helper.rb +108 -0
  30. data/lib/action_view/helpers/rendering_helper.rb +90 -0
  31. data/lib/action_view/helpers/sanitize_helper.rb +256 -0
  32. data/lib/action_view/helpers/tag_helper.rb +176 -0
  33. data/lib/action_view/helpers/tags.rb +41 -0
  34. data/lib/action_view/helpers/tags/base.rb +148 -0
  35. data/lib/action_view/helpers/tags/check_box.rb +64 -0
  36. data/lib/action_view/helpers/tags/checkable.rb +16 -0
  37. data/lib/action_view/helpers/tags/collection_check_boxes.rb +44 -0
  38. data/lib/action_view/helpers/tags/collection_helpers.rb +85 -0
  39. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +36 -0
  40. data/lib/action_view/helpers/tags/collection_select.rb +28 -0
  41. data/lib/action_view/helpers/tags/color_field.rb +25 -0
  42. data/lib/action_view/helpers/tags/date_field.rb +13 -0
  43. data/lib/action_view/helpers/tags/date_select.rb +72 -0
  44. data/lib/action_view/helpers/tags/datetime_field.rb +22 -0
  45. data/lib/action_view/helpers/tags/datetime_local_field.rb +19 -0
  46. data/lib/action_view/helpers/tags/datetime_select.rb +8 -0
  47. data/lib/action_view/helpers/tags/email_field.rb +8 -0
  48. data/lib/action_view/helpers/tags/file_field.rb +8 -0
  49. data/lib/action_view/helpers/tags/grouped_collection_select.rb +29 -0
  50. data/lib/action_view/helpers/tags/hidden_field.rb +8 -0
  51. data/lib/action_view/helpers/tags/label.rb +65 -0
  52. data/lib/action_view/helpers/tags/month_field.rb +13 -0
  53. data/lib/action_view/helpers/tags/number_field.rb +18 -0
  54. data/lib/action_view/helpers/tags/password_field.rb +12 -0
  55. data/lib/action_view/helpers/tags/radio_button.rb +31 -0
  56. data/lib/action_view/helpers/tags/range_field.rb +8 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +24 -0
  58. data/lib/action_view/helpers/tags/select.rb +41 -0
  59. data/lib/action_view/helpers/tags/tel_field.rb +8 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +18 -0
  61. data/lib/action_view/helpers/tags/text_field.rb +29 -0
  62. data/lib/action_view/helpers/tags/time_field.rb +13 -0
  63. data/lib/action_view/helpers/tags/time_select.rb +8 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +20 -0
  65. data/lib/action_view/helpers/tags/url_field.rb +8 -0
  66. data/lib/action_view/helpers/tags/week_field.rb +13 -0
  67. data/lib/action_view/helpers/text_helper.rb +447 -0
  68. data/lib/action_view/helpers/translation_helper.rb +111 -0
  69. data/lib/action_view/helpers/url_helper.rb +625 -0
  70. data/lib/action_view/layouts.rb +426 -0
  71. data/lib/action_view/locale/en.yml +56 -0
  72. data/lib/action_view/log_subscriber.rb +44 -0
  73. data/lib/action_view/lookup_context.rb +249 -0
  74. data/lib/action_view/model_naming.rb +12 -0
  75. data/lib/action_view/path_set.rb +77 -0
  76. data/lib/action_view/railtie.rb +49 -0
  77. data/lib/action_view/record_identifier.rb +84 -0
  78. data/lib/action_view/renderer/abstract_renderer.rb +47 -0
  79. data/lib/action_view/renderer/partial_renderer.rb +492 -0
  80. data/lib/action_view/renderer/renderer.rb +50 -0
  81. data/lib/action_view/renderer/streaming_template_renderer.rb +103 -0
  82. data/lib/action_view/renderer/template_renderer.rb +96 -0
  83. data/lib/action_view/rendering.rb +145 -0
  84. data/lib/action_view/routing_url_for.rb +109 -0
  85. data/lib/action_view/tasks/dependencies.rake +17 -0
  86. data/lib/action_view/template.rb +340 -0
  87. data/lib/action_view/template/error.rb +141 -0
  88. data/lib/action_view/template/handlers.rb +53 -0
  89. data/lib/action_view/template/handlers/builder.rb +26 -0
  90. data/lib/action_view/template/handlers/erb.rb +145 -0
  91. data/lib/action_view/template/handlers/raw.rb +11 -0
  92. data/lib/action_view/template/resolver.rb +329 -0
  93. data/lib/action_view/template/text.rb +34 -0
  94. data/lib/action_view/template/types.rb +57 -0
  95. data/lib/action_view/test_case.rb +272 -0
  96. data/lib/action_view/testing/resolvers.rb +50 -0
  97. data/lib/action_view/vendor/html-scanner.rb +20 -0
  98. data/lib/action_view/vendor/html-scanner/html/document.rb +68 -0
  99. data/lib/action_view/vendor/html-scanner/html/node.rb +532 -0
  100. data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +188 -0
  101. data/lib/action_view/vendor/html-scanner/html/selector.rb +830 -0
  102. data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +107 -0
  103. data/lib/action_view/vendor/html-scanner/html/version.rb +11 -0
  104. data/lib/action_view/version.rb +11 -0
  105. data/lib/action_view/view_paths.rb +96 -0
  106. metadata +218 -0
@@ -0,0 +1,53 @@
1
+ module ActionView #:nodoc:
2
+ # = Action View Template Handlers
3
+ class Template
4
+ module Handlers #:nodoc:
5
+ autoload :ERB, 'action_view/template/handlers/erb'
6
+ autoload :Builder, 'action_view/template/handlers/builder'
7
+ autoload :Raw, 'action_view/template/handlers/raw'
8
+
9
+ def self.extended(base)
10
+ base.register_default_template_handler :erb, ERB.new
11
+ base.register_template_handler :builder, Builder.new
12
+ base.register_template_handler :raw, Raw.new
13
+ base.register_template_handler :ruby, :source.to_proc
14
+ end
15
+
16
+ @@template_handlers = {}
17
+ @@default_template_handlers = nil
18
+
19
+ def self.extensions
20
+ @@template_extensions ||= @@template_handlers.keys
21
+ end
22
+
23
+ # Register an object that knows how to handle template files with the given
24
+ # extensions. This can be used to implement new template types.
25
+ # The handler must respond to `:call`, which will be passed the template
26
+ # and should return the rendered template as a String.
27
+ def register_template_handler(*extensions, handler)
28
+ raise(ArgumentError, "Extension is required") if extensions.empty?
29
+ extensions.each do |extension|
30
+ @@template_handlers[extension.to_sym] = handler
31
+ end
32
+ @@template_extensions = nil
33
+ end
34
+
35
+ def template_handler_extensions
36
+ @@template_handlers.keys.map {|key| key.to_s }.sort
37
+ end
38
+
39
+ def registered_template_handler(extension)
40
+ extension && @@template_handlers[extension.to_sym]
41
+ end
42
+
43
+ def register_default_template_handler(extension, klass)
44
+ register_template_handler(extension, klass)
45
+ @@default_template_handlers = klass
46
+ end
47
+
48
+ def handler_for_extension(extension)
49
+ registered_template_handler(extension) || @@default_template_handlers
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,26 @@
1
+ module ActionView
2
+ module Template::Handlers
3
+ class Builder
4
+ # Default format used by Builder.
5
+ class_attribute :default_format
6
+ self.default_format = :xml
7
+
8
+ def call(template)
9
+ require_engine
10
+ "xml = ::Builder::XmlMarkup.new(:indent => 2);" +
11
+ "self.output_buffer = xml.target!;" +
12
+ template.source +
13
+ ";xml.target!;"
14
+ end
15
+
16
+ protected
17
+
18
+ def require_engine
19
+ @required ||= begin
20
+ require "builder"
21
+ true
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,145 @@
1
+ require 'erubis'
2
+
3
+ module ActionView
4
+ class Template
5
+ module Handlers
6
+ class Erubis < ::Erubis::Eruby
7
+ def add_preamble(src)
8
+ @newline_pending = 0
9
+ src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
10
+ end
11
+
12
+ def add_text(src, text)
13
+ return if text.empty?
14
+
15
+ if text == "\n"
16
+ @newline_pending += 1
17
+ else
18
+ src << "@output_buffer.safe_append='"
19
+ src << "\n" * @newline_pending if @newline_pending > 0
20
+ src << escape_text(text)
21
+ src << "'.freeze;"
22
+
23
+ @newline_pending = 0
24
+ end
25
+ end
26
+
27
+ # Erubis toggles <%= and <%== behavior when escaping is enabled.
28
+ # We override to always treat <%== as escaped.
29
+ def add_expr(src, code, indicator)
30
+ case indicator
31
+ when '=='
32
+ add_expr_escaped(src, code)
33
+ else
34
+ super
35
+ end
36
+ end
37
+
38
+ BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
39
+
40
+ def add_expr_literal(src, code)
41
+ flush_newline_if_pending(src)
42
+ if code =~ BLOCK_EXPR
43
+ src << '@output_buffer.append= ' << code
44
+ else
45
+ src << '@output_buffer.append=(' << code << ');'
46
+ end
47
+ end
48
+
49
+ def add_expr_escaped(src, code)
50
+ flush_newline_if_pending(src)
51
+ if code =~ BLOCK_EXPR
52
+ src << "@output_buffer.safe_append= " << code
53
+ else
54
+ src << "@output_buffer.safe_append=(" << code << ");"
55
+ end
56
+ end
57
+
58
+ def add_stmt(src, code)
59
+ flush_newline_if_pending(src)
60
+ super
61
+ end
62
+
63
+ def add_postamble(src)
64
+ flush_newline_if_pending(src)
65
+ src << '@output_buffer.to_s'
66
+ end
67
+
68
+ def flush_newline_if_pending(src)
69
+ if @newline_pending > 0
70
+ src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
71
+ @newline_pending = 0
72
+ end
73
+ end
74
+ end
75
+
76
+ class ERB
77
+ # Specify trim mode for the ERB compiler. Defaults to '-'.
78
+ # See ERB documentation for suitable values.
79
+ class_attribute :erb_trim_mode
80
+ self.erb_trim_mode = '-'
81
+
82
+ # Default implementation used.
83
+ class_attribute :erb_implementation
84
+ self.erb_implementation = Erubis
85
+
86
+ # Do not escape templates of these mime types.
87
+ class_attribute :escape_whitelist
88
+ self.escape_whitelist = ["text/plain"]
89
+
90
+ ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
91
+
92
+ def self.call(template)
93
+ new.call(template)
94
+ end
95
+
96
+ def supports_streaming?
97
+ true
98
+ end
99
+
100
+ def handles_encoding?
101
+ true
102
+ end
103
+
104
+ def call(template)
105
+ # First, convert to BINARY, so in case the encoding is
106
+ # wrong, we can still find an encoding tag
107
+ # (<%# encoding %>) inside the String using a regular
108
+ # expression
109
+ template_source = template.source.dup.force_encoding(Encoding::ASCII_8BIT)
110
+
111
+ erb = template_source.gsub(ENCODING_TAG, '')
112
+ encoding = $2
113
+
114
+ erb.force_encoding valid_encoding(template.source.dup, encoding)
115
+
116
+ # Always make sure we return a String in the default_internal
117
+ erb.encode!
118
+
119
+ self.class.erb_implementation.new(
120
+ erb,
121
+ :escape => (self.class.escape_whitelist.include? template.type),
122
+ :trim => (self.class.erb_trim_mode == "-")
123
+ ).src
124
+ end
125
+
126
+ private
127
+
128
+ def valid_encoding(string, encoding)
129
+ # If a magic encoding comment was found, tag the
130
+ # String with this encoding. This is for a case
131
+ # where the original String was assumed to be,
132
+ # for instance, UTF-8, but a magic comment
133
+ # proved otherwise
134
+ string.force_encoding(encoding) if encoding
135
+
136
+ # If the String is valid, return the encoding we found
137
+ return string.encoding if string.valid_encoding?
138
+
139
+ # Otherwise, raise an exception
140
+ raise WrongEncodingError.new(string, string.encoding)
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,11 @@
1
+ module ActionView
2
+ module Template::Handlers
3
+ class Raw
4
+ def call(template)
5
+ escaped = template.source.gsub(':', '\:')
6
+
7
+ '%q:' + escaped + ':;'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,329 @@
1
+ require "pathname"
2
+ require "active_support/core_ext/class"
3
+ require "active_support/core_ext/module/attribute_accessors"
4
+ require "action_view/template"
5
+ require "thread"
6
+ require "thread_safe"
7
+
8
+ module ActionView
9
+ # = Action View Resolver
10
+ class Resolver
11
+ # Keeps all information about view path and builds virtual path.
12
+ class Path
13
+ attr_reader :name, :prefix, :partial, :virtual
14
+ alias_method :partial?, :partial
15
+
16
+ def self.build(name, prefix, partial)
17
+ virtual = ""
18
+ virtual << "#{prefix}/" unless prefix.empty?
19
+ virtual << (partial ? "_#{name}" : name)
20
+ new name, prefix, partial, virtual
21
+ end
22
+
23
+ def initialize(name, prefix, partial, virtual)
24
+ @name = name
25
+ @prefix = prefix
26
+ @partial = partial
27
+ @virtual = virtual
28
+ end
29
+
30
+ def to_str
31
+ @virtual
32
+ end
33
+ alias :to_s :to_str
34
+ end
35
+
36
+ # Threadsafe template cache
37
+ class Cache #:nodoc:
38
+ class SmallCache < ThreadSafe::Cache
39
+ def initialize(options = {})
40
+ super(options.merge(:initial_capacity => 2))
41
+ end
42
+ end
43
+
44
+ # preallocate all the default blocks for performance/memory consumption reasons
45
+ PARTIAL_BLOCK = lambda {|cache, partial| cache[partial] = SmallCache.new}
46
+ PREFIX_BLOCK = lambda {|cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK)}
47
+ NAME_BLOCK = lambda {|cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK)}
48
+ KEY_BLOCK = lambda {|cache, key| cache[key] = SmallCache.new(&NAME_BLOCK)}
49
+
50
+ # usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
51
+ NO_TEMPLATES = [].freeze
52
+
53
+ def initialize
54
+ @data = SmallCache.new(&KEY_BLOCK)
55
+ end
56
+
57
+ # Cache the templates returned by the block
58
+ def cache(key, name, prefix, partial, locals)
59
+ if Resolver.caching?
60
+ @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
61
+ else
62
+ fresh_templates = yield
63
+ cached_templates = @data[key][name][prefix][partial][locals]
64
+
65
+ if templates_have_changed?(cached_templates, fresh_templates)
66
+ @data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates)
67
+ else
68
+ cached_templates || NO_TEMPLATES
69
+ end
70
+ end
71
+ end
72
+
73
+ def clear
74
+ @data.clear
75
+ end
76
+
77
+ private
78
+
79
+ def canonical_no_templates(templates)
80
+ templates.empty? ? NO_TEMPLATES : templates
81
+ end
82
+
83
+ def templates_have_changed?(cached_templates, fresh_templates)
84
+ # if either the old or new template list is empty, we don't need to (and can't)
85
+ # compare modification times, and instead just check whether the lists are different
86
+ if cached_templates.blank? || fresh_templates.blank?
87
+ return fresh_templates.blank? != cached_templates.blank?
88
+ end
89
+
90
+ cached_templates_max_updated_at = cached_templates.map(&:updated_at).max
91
+
92
+ # if a template has changed, it will be now be newer than all the cached templates
93
+ fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at }
94
+ end
95
+ end
96
+
97
+ cattr_accessor :caching
98
+ self.caching = true
99
+
100
+ class << self
101
+ alias :caching? :caching
102
+ end
103
+
104
+ def initialize
105
+ @cache = Cache.new
106
+ end
107
+
108
+ def clear_cache
109
+ @cache.clear
110
+ end
111
+
112
+ # Normalizes the arguments and passes it on to find_templates.
113
+ def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])
114
+ cached(key, [name, prefix, partial], details, locals) do
115
+ find_templates(name, prefix, partial, details)
116
+ end
117
+ end
118
+
119
+ private
120
+
121
+ delegate :caching?, to: :class
122
+
123
+ # This is what child classes implement. No defaults are needed
124
+ # because Resolver guarantees that the arguments are present and
125
+ # normalized.
126
+ def find_templates(name, prefix, partial, details)
127
+ raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details) method"
128
+ end
129
+
130
+ # Helpers that builds a path. Useful for building virtual paths.
131
+ def build_path(name, prefix, partial)
132
+ Path.build(name, prefix, partial)
133
+ end
134
+
135
+ # Handles templates caching. If a key is given and caching is on
136
+ # always check the cache before hitting the resolver. Otherwise,
137
+ # it always hits the resolver but if the key is present, check if the
138
+ # resolver is fresher before returning it.
139
+ def cached(key, path_info, details, locals) #:nodoc:
140
+ name, prefix, partial = path_info
141
+ locals = locals.map { |x| x.to_s }.sort!
142
+
143
+ if key
144
+ @cache.cache(key, name, prefix, partial, locals) do
145
+ decorate(yield, path_info, details, locals)
146
+ end
147
+ else
148
+ decorate(yield, path_info, details, locals)
149
+ end
150
+ end
151
+
152
+ # Ensures all the resolver information is set in the template.
153
+ def decorate(templates, path_info, details, locals) #:nodoc:
154
+ cached = nil
155
+ templates.each do |t|
156
+ t.locals = locals
157
+ t.formats = details[:formats] || [:html] if t.formats.empty?
158
+ t.virtual_path ||= (cached ||= build_path(*path_info))
159
+ end
160
+ end
161
+ end
162
+
163
+ # An abstract class that implements a Resolver with path semantics.
164
+ class PathResolver < Resolver #:nodoc:
165
+ EXTENSIONS = { :locale => ".", :formats => ".", :variants => "+", :handlers => "." }
166
+ DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
167
+
168
+ def initialize(pattern=nil)
169
+ @pattern = pattern || DEFAULT_PATTERN
170
+ super()
171
+ end
172
+
173
+ private
174
+
175
+ def find_templates(name, prefix, partial, details)
176
+ path = Path.build(name, prefix, partial)
177
+ query(path, details, details[:formats])
178
+ end
179
+
180
+ def query(path, details, formats)
181
+ query = build_query(path, details)
182
+
183
+ # deals with case-insensitive file systems.
184
+ sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] }
185
+
186
+ template_paths = Dir[query].reject { |filename|
187
+ File.directory?(filename) ||
188
+ !sanitizer[File.dirname(filename)].include?(filename)
189
+ }
190
+
191
+ template_paths.map { |template|
192
+ handler, format = extract_handler_and_format(template, formats)
193
+ contents = File.binread template
194
+
195
+ Template.new(contents, File.expand_path(template), handler,
196
+ :virtual_path => path.virtual,
197
+ :format => format,
198
+ :updated_at => mtime(template))
199
+ }
200
+ end
201
+
202
+ # Helper for building query glob string based on resolver's pattern.
203
+ def build_query(path, details)
204
+ query = @pattern.dup
205
+
206
+ prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
207
+ query.gsub!(/\:prefix(\/)?/, prefix)
208
+
209
+ partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
210
+ query.gsub!(/\:action/, partial)
211
+
212
+ details.each do |ext, variants|
213
+ query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
214
+ end
215
+
216
+ File.expand_path(query, @path)
217
+ end
218
+
219
+ def escape_entry(entry)
220
+ entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
221
+ end
222
+
223
+ # Returns the file mtime from the filesystem.
224
+ def mtime(p)
225
+ File.mtime(p)
226
+ end
227
+
228
+ # Extract handler and formats from path. If a format cannot be a found neither
229
+ # from the path, or the handler, we should return the array of formats given
230
+ # to the resolver.
231
+ def extract_handler_and_format(path, default_formats)
232
+ pieces = File.basename(path).split(".")
233
+ pieces.shift
234
+
235
+ extension = pieces.pop
236
+ unless extension
237
+ message = "The file #{path} did not specify a template handler. The default is currently ERB, " \
238
+ "but will change to RAW in the future."
239
+ ActiveSupport::Deprecation.warn message
240
+ end
241
+
242
+ handler = Template.handler_for_extension(extension)
243
+ format = pieces.last && pieces.last.split(EXTENSIONS[:variants], 2).first # remove variant from format
244
+ format &&= Template::Types[format]
245
+
246
+ [handler, format]
247
+ end
248
+ end
249
+
250
+ # A resolver that loads files from the filesystem. It allows setting your own
251
+ # resolving pattern. Such pattern can be a glob string supported by some variables.
252
+ #
253
+ # ==== Examples
254
+ #
255
+ # Default pattern, loads views the same way as previous versions of rails, eg. when you're
256
+ # looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}`
257
+ #
258
+ # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}")
259
+ #
260
+ # This one allows you to keep files with different formats in separate subdirectories,
261
+ # eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`,
262
+ # `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc.
263
+ #
264
+ # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{.:handlers,}")
265
+ #
266
+ # If you don't specify a pattern then the default will be used.
267
+ #
268
+ # In order to use any of the customized resolvers above in a Rails application, you just need
269
+ # to configure ActionController::Base.view_paths in an initializer, for example:
270
+ #
271
+ # ActionController::Base.view_paths = FileSystemResolver.new(
272
+ # Rails.root.join("app/views"),
273
+ # ":prefix{/:locale}/:action{.:formats,}{.:handlers,}"
274
+ # )
275
+ #
276
+ # ==== Pattern format and variables
277
+ #
278
+ # Pattern has to be a valid glob string, and it allows you to use the
279
+ # following variables:
280
+ #
281
+ # * <tt>:prefix</tt> - usually the controller path
282
+ # * <tt>:action</tt> - name of the action
283
+ # * <tt>:locale</tt> - possible locale versions
284
+ # * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
285
+ # * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
286
+ #
287
+ class FileSystemResolver < PathResolver
288
+ def initialize(path, pattern=nil)
289
+ raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
290
+ super(pattern)
291
+ @path = File.expand_path(path)
292
+ end
293
+
294
+ def to_s
295
+ @path.to_s
296
+ end
297
+ alias :to_path :to_s
298
+
299
+ def eql?(resolver)
300
+ self.class.equal?(resolver.class) && to_path == resolver.to_path
301
+ end
302
+ alias :== :eql?
303
+ end
304
+
305
+ # An Optimized resolver for Rails' most common case.
306
+ class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
307
+ def build_query(path, details)
308
+ query = escape_entry(File.join(@path, path))
309
+
310
+ exts = EXTENSIONS.map do |ext, prefix|
311
+ "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}"
312
+ end.join
313
+
314
+ query + exts
315
+ end
316
+ end
317
+
318
+ # The same as FileSystemResolver but does not allow templates to store
319
+ # a virtual path since it is invalid for such resolvers.
320
+ class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
321
+ def self.instances
322
+ [new(""), new("/")]
323
+ end
324
+
325
+ def decorate(*)
326
+ super.each { |t| t.virtual_path = nil }
327
+ end
328
+ end
329
+ end