actionview 4.1.13 → 6.1.3.1
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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +181 -359
- data/MIT-LICENSE +1 -1
- data/README.rdoc +12 -6
- data/lib/action_view/base.rb +115 -43
- data/lib/action_view/buffers.rb +22 -4
- data/lib/action_view/cache_expiry.rb +52 -0
- data/lib/action_view/context.rb +8 -12
- data/lib/action_view/dependency_tracker.rb +61 -21
- data/lib/action_view/digestor.rb +89 -84
- data/lib/action_view/flows.rb +12 -13
- data/lib/action_view/gem_version.rb +6 -4
- data/lib/action_view/helpers/active_model_helper.rb +16 -11
- data/lib/action_view/helpers/asset_tag_helper.rb +311 -105
- data/lib/action_view/helpers/asset_url_helper.rb +197 -80
- data/lib/action_view/helpers/atom_feed_helper.rb +20 -17
- data/lib/action_view/helpers/cache_helper.rb +109 -45
- data/lib/action_view/helpers/capture_helper.rb +20 -22
- data/lib/action_view/helpers/controller_helper.rb +15 -4
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +8 -6
- data/lib/action_view/helpers/date_helper.rb +245 -140
- data/lib/action_view/helpers/debug_helper.rb +14 -17
- data/lib/action_view/helpers/form_helper.rb +875 -148
- data/lib/action_view/helpers/form_options_helper.rb +128 -82
- data/lib/action_view/helpers/form_tag_helper.rb +253 -91
- data/lib/action_view/helpers/javascript_helper.rb +37 -15
- data/lib/action_view/helpers/number_helper.rb +100 -77
- data/lib/action_view/helpers/output_safety_helper.rb +42 -10
- data/lib/action_view/helpers/rendering_helper.rb +26 -15
- data/lib/action_view/helpers/sanitize_helper.rb +79 -164
- data/lib/action_view/helpers/tag_helper.rb +277 -64
- data/lib/action_view/helpers/tags/base.rb +143 -92
- data/lib/action_view/helpers/tags/check_box.rb +20 -19
- data/lib/action_view/helpers/tags/checkable.rb +4 -2
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -30
- data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
- data/lib/action_view/helpers/tags/collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/color_field.rb +4 -3
- data/lib/action_view/helpers/tags/date_field.rb +3 -2
- data/lib/action_view/helpers/tags/date_select.rb +38 -37
- data/lib/action_view/helpers/tags/datetime_field.rb +14 -5
- data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
- data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
- data/lib/action_view/helpers/tags/email_field.rb +2 -0
- data/lib/action_view/helpers/tags/file_field.rb +2 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
- data/lib/action_view/helpers/tags/label.rb +41 -22
- data/lib/action_view/helpers/tags/month_field.rb +3 -2
- data/lib/action_view/helpers/tags/number_field.rb +2 -0
- data/lib/action_view/helpers/tags/password_field.rb +3 -1
- data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
- data/lib/action_view/helpers/tags/radio_button.rb +7 -6
- data/lib/action_view/helpers/tags/range_field.rb +2 -0
- data/lib/action_view/helpers/tags/search_field.rb +3 -0
- data/lib/action_view/helpers/tags/select.rb +11 -10
- data/lib/action_view/helpers/tags/tel_field.rb +2 -0
- data/lib/action_view/helpers/tags/text_area.rb +7 -1
- data/lib/action_view/helpers/tags/text_field.rb +11 -7
- data/lib/action_view/helpers/tags/time_field.rb +3 -2
- data/lib/action_view/helpers/tags/time_select.rb +2 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
- data/lib/action_view/helpers/tags/translator.rb +39 -0
- data/lib/action_view/helpers/tags/url_field.rb +2 -0
- data/lib/action_view/helpers/tags/week_field.rb +3 -2
- data/lib/action_view/helpers/tags.rb +4 -1
- data/lib/action_view/helpers/text_helper.rb +80 -45
- data/lib/action_view/helpers/translation_helper.rb +148 -67
- data/lib/action_view/helpers/url_helper.rb +289 -147
- data/lib/action_view/helpers.rb +5 -3
- data/lib/action_view/layouts.rb +68 -63
- data/lib/action_view/log_subscriber.rb +80 -13
- data/lib/action_view/lookup_context.rb +137 -92
- data/lib/action_view/model_naming.rb +4 -2
- data/lib/action_view/path_set.rb +30 -16
- data/lib/action_view/railtie.rb +62 -13
- data/lib/action_view/record_identifier.rb +53 -26
- data/lib/action_view/renderer/abstract_renderer.rb +152 -13
- data/lib/action_view/renderer/collection_renderer.rb +196 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +102 -0
- data/lib/action_view/renderer/partial_renderer.rb +61 -261
- data/lib/action_view/renderer/renderer.rb +67 -6
- data/lib/action_view/renderer/streaming_template_renderer.rb +58 -54
- data/lib/action_view/renderer/template_renderer.rb +83 -75
- data/lib/action_view/rendering.rb +73 -46
- data/lib/action_view/routing_url_for.rb +54 -17
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +44 -29
- data/lib/action_view/template/handlers/builder.rb +12 -13
- data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
- data/lib/action_view/template/handlers/erb.rb +23 -89
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +4 -4
- data/lib/action_view/template/handlers.rb +22 -9
- data/lib/action_view/template/html.rb +10 -11
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +24 -0
- data/lib/action_view/template/resolver.rb +267 -181
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +8 -10
- data/lib/action_view/template/types.rb +18 -18
- data/lib/action_view/template.rb +109 -99
- data/lib/action_view/test_case.rb +73 -53
- data/lib/action_view/testing/resolvers.rb +24 -33
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/version.rb +3 -1
- data/lib/action_view/view_paths.rb +74 -44
- data/lib/action_view.rb +14 -9
- data/lib/assets/compiled/rails-ujs.js +746 -0
- metadata +71 -26
- data/lib/action_view/helpers/record_tag_helper.rb +0 -108
- data/lib/action_view/tasks/dependencies.rake +0 -23
- data/lib/action_view/vendor/html-scanner/html/document.rb +0 -68
- data/lib/action_view/vendor/html-scanner/html/node.rb +0 -532
- data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +0 -188
- data/lib/action_view/vendor/html-scanner/html/selector.rb +0 -830
- data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +0 -107
- data/lib/action_view/vendor/html-scanner/html/version.rb +0 -11
- data/lib/action_view/vendor/html-scanner.rb +0 -20
@@ -1,10 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionView
|
2
4
|
module Template::Handlers
|
3
5
|
class Raw
|
4
|
-
def call(template)
|
5
|
-
|
6
|
-
|
7
|
-
'%q:' + escaped + ':;'
|
6
|
+
def call(template, source)
|
7
|
+
"#{source.inspect}.html_safe;"
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
@@ -1,16 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionView #:nodoc:
|
2
4
|
# = Action View Template Handlers
|
3
|
-
class Template
|
5
|
+
class Template #:nodoc:
|
4
6
|
module Handlers #:nodoc:
|
5
|
-
autoload :
|
6
|
-
autoload :
|
7
|
-
autoload :
|
7
|
+
autoload :Raw, "action_view/template/handlers/raw"
|
8
|
+
autoload :ERB, "action_view/template/handlers/erb"
|
9
|
+
autoload :Html, "action_view/template/handlers/html"
|
10
|
+
autoload :Builder, "action_view/template/handlers/builder"
|
8
11
|
|
9
12
|
def self.extended(base)
|
10
|
-
base.register_default_template_handler :
|
13
|
+
base.register_default_template_handler :raw, Raw.new
|
14
|
+
base.register_template_handler :erb, ERB.new
|
15
|
+
base.register_template_handler :html, Html.new
|
11
16
|
base.register_template_handler :builder, Builder.new
|
12
|
-
base.register_template_handler :
|
13
|
-
base.register_template_handler :ruby, :source.to_proc
|
17
|
+
base.register_template_handler :ruby, lambda { |_, source| source }
|
14
18
|
end
|
15
19
|
|
16
20
|
@@template_handlers = {}
|
@@ -22,7 +26,7 @@ module ActionView #:nodoc:
|
|
22
26
|
|
23
27
|
# Register an object that knows how to handle template files with the given
|
24
28
|
# extensions. This can be used to implement new template types.
|
25
|
-
# The handler must respond to
|
29
|
+
# The handler must respond to +:call+, which will be passed the template
|
26
30
|
# and should return the rendered template as a String.
|
27
31
|
def register_template_handler(*extensions, handler)
|
28
32
|
raise(ArgumentError, "Extension is required") if extensions.empty?
|
@@ -32,8 +36,17 @@ module ActionView #:nodoc:
|
|
32
36
|
@@template_extensions = nil
|
33
37
|
end
|
34
38
|
|
39
|
+
# Opposite to register_template_handler.
|
40
|
+
def unregister_template_handler(*extensions)
|
41
|
+
extensions.each do |extension|
|
42
|
+
handler = @@template_handlers.delete extension.to_sym
|
43
|
+
@@default_template_handlers = nil if @@default_template_handlers == handler
|
44
|
+
end
|
45
|
+
@@template_extensions = nil
|
46
|
+
end
|
47
|
+
|
35
48
|
def template_handler_extensions
|
36
|
-
@@template_handlers.keys.map
|
49
|
+
@@template_handlers.keys.map(&:to_s).sort
|
37
50
|
end
|
38
51
|
|
39
52
|
def registered_template_handler(extension)
|
@@ -1,22 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionView #:nodoc:
|
2
4
|
# = Action View HTML Template
|
3
|
-
class Template
|
5
|
+
class Template #:nodoc:
|
4
6
|
class HTML #:nodoc:
|
5
|
-
|
7
|
+
attr_reader :type
|
6
8
|
|
7
|
-
def initialize(string, type
|
9
|
+
def initialize(string, type)
|
8
10
|
@string = string.to_s
|
9
|
-
@type =
|
10
|
-
@type ||= Types[:html]
|
11
|
+
@type = type
|
11
12
|
end
|
12
13
|
|
13
14
|
def identifier
|
14
|
-
|
15
|
+
"html template"
|
15
16
|
end
|
16
17
|
|
17
|
-
|
18
|
-
'html template'
|
19
|
-
end
|
18
|
+
alias_method :inspect, :identifier
|
20
19
|
|
21
20
|
def to_str
|
22
21
|
ERB::Util.h(@string)
|
@@ -26,8 +25,8 @@ module ActionView #:nodoc:
|
|
26
25
|
to_str
|
27
26
|
end
|
28
27
|
|
29
|
-
def
|
30
|
-
|
28
|
+
def format
|
29
|
+
@type
|
31
30
|
end
|
32
31
|
end
|
33
32
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView #:nodoc:
|
4
|
+
class Template #:nodoc:
|
5
|
+
class Inline < Template #:nodoc:
|
6
|
+
# This finalizer is needed (and exactly with a proc inside another proc)
|
7
|
+
# otherwise templates leak in development.
|
8
|
+
Finalizer = proc do |method_name, mod| # :nodoc:
|
9
|
+
proc do
|
10
|
+
mod.module_eval do
|
11
|
+
remove_possible_method method_name
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def compile(mod)
|
17
|
+
super
|
18
|
+
ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView #:nodoc:
|
4
|
+
# = Action View RawFile Template
|
5
|
+
class Template #:nodoc:
|
6
|
+
class RawFile #:nodoc:
|
7
|
+
attr_accessor :type, :format
|
8
|
+
|
9
|
+
def initialize(filename)
|
10
|
+
@filename = filename.to_s
|
11
|
+
extname = ::File.extname(filename).delete(".")
|
12
|
+
@type = Template::Types[extname] || Template::Types[:text]
|
13
|
+
@format = @type.symbol
|
14
|
+
end
|
15
|
+
|
16
|
+
def identifier
|
17
|
+
@filename
|
18
|
+
end
|
19
|
+
|
20
|
+
def render(*args)
|
21
|
+
::File.read(@filename)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
# = Action View Renderable Template for objects that respond to #render_in
|
5
|
+
class Template
|
6
|
+
class Renderable # :nodoc:
|
7
|
+
def initialize(renderable)
|
8
|
+
@renderable = renderable
|
9
|
+
end
|
10
|
+
|
11
|
+
def identifier
|
12
|
+
@renderable.class.name
|
13
|
+
end
|
14
|
+
|
15
|
+
def render(context, *args)
|
16
|
+
@renderable.render_in(context)
|
17
|
+
end
|
18
|
+
|
19
|
+
def format
|
20
|
+
@renderable.format
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "pathname"
|
2
4
|
require "active_support/core_ext/class"
|
3
5
|
require "active_support/core_ext/module/attribute_accessors"
|
4
6
|
require "action_view/template"
|
5
7
|
require "thread"
|
6
|
-
require "
|
8
|
+
require "concurrent/map"
|
7
9
|
|
8
10
|
module ActionView
|
9
11
|
# = Action View Resolver
|
@@ -14,7 +16,7 @@ module ActionView
|
|
14
16
|
alias_method :partial?, :partial
|
15
17
|
|
16
18
|
def self.build(name, prefix, partial)
|
17
|
-
virtual = ""
|
19
|
+
virtual = +""
|
18
20
|
virtual << "#{prefix}/" unless prefix.empty?
|
19
21
|
virtual << (partial ? "_#{name}" : name)
|
20
22
|
new name, prefix, partial, virtual
|
@@ -33,69 +35,105 @@ module ActionView
|
|
33
35
|
alias :to_s :to_str
|
34
36
|
end
|
35
37
|
|
38
|
+
class PathParser # :nodoc:
|
39
|
+
def build_path_regex
|
40
|
+
handlers = Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
|
41
|
+
formats = Template::Types.symbols.map { |x| Regexp.escape(x) }.join("|")
|
42
|
+
locales = "[a-z]{2}(?:-[A-Z]{2})?"
|
43
|
+
variants = "[^.]*"
|
44
|
+
|
45
|
+
%r{
|
46
|
+
\A
|
47
|
+
(?:(?<prefix>.*)/)?
|
48
|
+
(?<partial>_)?
|
49
|
+
(?<action>.*?)
|
50
|
+
(?:\.(?<locale>#{locales}))??
|
51
|
+
(?:\.(?<format>#{formats}))??
|
52
|
+
(?:\+(?<variant>#{variants}))??
|
53
|
+
(?:\.(?<handler>#{handlers}))?
|
54
|
+
\z
|
55
|
+
}x
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse(path)
|
59
|
+
@regex ||= build_path_regex
|
60
|
+
match = @regex.match(path)
|
61
|
+
{
|
62
|
+
prefix: match[:prefix] || "",
|
63
|
+
action: match[:action],
|
64
|
+
partial: !!match[:partial],
|
65
|
+
locale: match[:locale]&.to_sym,
|
66
|
+
handler: match[:handler]&.to_sym,
|
67
|
+
format: match[:format]&.to_sym,
|
68
|
+
variant: match[:variant]
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
36
73
|
# Threadsafe template cache
|
37
74
|
class Cache #:nodoc:
|
38
|
-
class SmallCache <
|
75
|
+
class SmallCache < Concurrent::Map
|
39
76
|
def initialize(options = {})
|
40
|
-
super(options.merge(:
|
77
|
+
super(options.merge(initial_capacity: 2))
|
41
78
|
end
|
42
79
|
end
|
43
80
|
|
44
|
-
#
|
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)}
|
81
|
+
# Preallocate all the default blocks for performance/memory consumption reasons
|
82
|
+
PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new }
|
83
|
+
PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) }
|
84
|
+
NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) }
|
85
|
+
KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) }
|
49
86
|
|
50
|
-
#
|
87
|
+
# Usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
|
51
88
|
NO_TEMPLATES = [].freeze
|
52
89
|
|
53
90
|
def initialize
|
54
91
|
@data = SmallCache.new(&KEY_BLOCK)
|
92
|
+
@query_cache = SmallCache.new
|
93
|
+
end
|
94
|
+
|
95
|
+
def inspect
|
96
|
+
"#{to_s[0..-2]} keys=#{@data.size} queries=#{@query_cache.size}>"
|
55
97
|
end
|
56
98
|
|
57
99
|
# Cache the templates returned by the block
|
58
100
|
def cache(key, name, prefix, partial, locals)
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
101
|
+
@data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
|
102
|
+
end
|
103
|
+
|
104
|
+
def cache_query(query) # :nodoc:
|
105
|
+
@query_cache[query] ||= canonical_no_templates(yield)
|
71
106
|
end
|
72
107
|
|
73
108
|
def clear
|
74
109
|
@data.clear
|
110
|
+
@query_cache.clear
|
75
111
|
end
|
76
112
|
|
77
|
-
|
113
|
+
# Get the cache size. Do not call this
|
114
|
+
# method. This method is not guaranteed to be here ever.
|
115
|
+
def size # :nodoc:
|
116
|
+
size = 0
|
117
|
+
@data.each_value do |v1|
|
118
|
+
v1.each_value do |v2|
|
119
|
+
v2.each_value do |v3|
|
120
|
+
v3.each_value do |v4|
|
121
|
+
size += v4.size
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
78
126
|
|
79
|
-
|
80
|
-
templates.empty? ? NO_TEMPLATES : templates
|
127
|
+
size + @query_cache.size
|
81
128
|
end
|
82
129
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
if cached_templates.blank? || fresh_templates.blank?
|
87
|
-
return fresh_templates.blank? != cached_templates.blank?
|
130
|
+
private
|
131
|
+
def canonical_no_templates(templates)
|
132
|
+
templates.empty? ? NO_TEMPLATES : templates
|
88
133
|
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
134
|
end
|
96
135
|
|
97
|
-
cattr_accessor :caching
|
98
|
-
self.caching = true
|
136
|
+
cattr_accessor :caching, default: true
|
99
137
|
|
100
138
|
class << self
|
101
139
|
alias :caching? :caching
|
@@ -110,201 +148,183 @@ module ActionView
|
|
110
148
|
end
|
111
149
|
|
112
150
|
# Normalizes the arguments and passes it on to find_templates.
|
113
|
-
def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])
|
151
|
+
def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
|
152
|
+
locals = locals.map(&:to_s).sort!.freeze
|
153
|
+
|
114
154
|
cached(key, [name, prefix, partial], details, locals) do
|
115
|
-
|
155
|
+
_find_all(name, prefix, partial, details, key, locals)
|
116
156
|
end
|
117
157
|
end
|
118
158
|
|
159
|
+
def find_all_with_query(query) # :nodoc:
|
160
|
+
@cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
|
161
|
+
end
|
162
|
+
|
119
163
|
private
|
164
|
+
def _find_all(name, prefix, partial, details, key, locals)
|
165
|
+
find_templates(name, prefix, partial, details, locals)
|
166
|
+
end
|
120
167
|
|
121
168
|
delegate :caching?, to: :class
|
122
169
|
|
123
170
|
# This is what child classes implement. No defaults are needed
|
124
171
|
# because Resolver guarantees that the arguments are present and
|
125
172
|
# 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)
|
173
|
+
def find_templates(name, prefix, partial, details, locals = [])
|
174
|
+
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
|
133
175
|
end
|
134
176
|
|
135
177
|
# Handles templates caching. If a key is given and caching is on
|
136
178
|
# always check the cache before hitting the resolver. Otherwise,
|
137
179
|
# it always hits the resolver but if the key is present, check if the
|
138
180
|
# resolver is fresher before returning it.
|
139
|
-
def cached(key, path_info, details, locals)
|
181
|
+
def cached(key, path_info, details, locals)
|
140
182
|
name, prefix, partial = path_info
|
141
|
-
locals = locals.map { |x| x.to_s }.sort!
|
142
183
|
|
143
184
|
if key
|
144
185
|
@cache.cache(key, name, prefix, partial, locals) do
|
145
|
-
|
186
|
+
yield
|
146
187
|
end
|
147
188
|
else
|
148
|
-
|
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.variants = details[:variants] || [] if t.variants.empty?
|
159
|
-
t.virtual_path ||= (cached ||= build_path(*path_info))
|
189
|
+
yield
|
160
190
|
end
|
161
191
|
end
|
162
192
|
end
|
163
193
|
|
164
194
|
# An abstract class that implements a Resolver with path semantics.
|
165
195
|
class PathResolver < Resolver #:nodoc:
|
166
|
-
EXTENSIONS = { :
|
196
|
+
EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
|
167
197
|
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
|
168
198
|
|
169
|
-
def initialize
|
170
|
-
@pattern =
|
171
|
-
|
199
|
+
def initialize
|
200
|
+
@pattern = DEFAULT_PATTERN
|
201
|
+
@unbound_templates = Concurrent::Map.new
|
202
|
+
@path_parser = PathParser.new
|
203
|
+
super
|
172
204
|
end
|
173
205
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
query(path, details, details[:formats])
|
206
|
+
def clear_cache
|
207
|
+
@unbound_templates.clear
|
208
|
+
@path_parser = PathParser.new
|
209
|
+
super
|
179
210
|
end
|
180
211
|
|
181
|
-
|
182
|
-
|
212
|
+
private
|
213
|
+
def _find_all(name, prefix, partial, details, key, locals)
|
214
|
+
path = Path.build(name, prefix, partial)
|
215
|
+
query(path, details, details[:formats], locals, cache: !!key)
|
216
|
+
end
|
183
217
|
|
184
|
-
|
218
|
+
def query(path, details, formats, locals, cache:)
|
219
|
+
template_paths = find_template_paths_from_details(path, details)
|
220
|
+
template_paths = reject_files_external_to_app(template_paths)
|
221
|
+
|
222
|
+
template_paths.map do |template|
|
223
|
+
unbound_template =
|
224
|
+
if cache
|
225
|
+
@unbound_templates.compute_if_absent([template, path.virtual]) do
|
226
|
+
build_unbound_template(template, path.virtual)
|
227
|
+
end
|
228
|
+
else
|
229
|
+
build_unbound_template(template, path.virtual)
|
230
|
+
end
|
231
|
+
|
232
|
+
unbound_template.bind_locals(locals)
|
233
|
+
end
|
234
|
+
end
|
185
235
|
|
186
|
-
|
187
|
-
|
188
|
-
|
236
|
+
def source_for_template(template)
|
237
|
+
Template::Sources::File.new(template)
|
238
|
+
end
|
189
239
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
240
|
+
def build_unbound_template(template, virtual_path)
|
241
|
+
handler, format, variant = extract_handler_and_format_and_variant(template)
|
242
|
+
source = source_for_template(template)
|
243
|
+
|
244
|
+
UnboundTemplate.new(
|
245
|
+
source,
|
246
|
+
template,
|
247
|
+
handler,
|
248
|
+
virtual_path: virtual_path,
|
249
|
+
format: format,
|
250
|
+
variant: variant,
|
195
251
|
)
|
196
|
-
|
197
|
-
|
252
|
+
end
|
253
|
+
|
254
|
+
def reject_files_external_to_app(files)
|
255
|
+
files.reject { |filename| !inside_path?(@path, filename) }
|
256
|
+
end
|
257
|
+
|
258
|
+
def find_template_paths_from_details(path, details)
|
259
|
+
if path.name.include?(".")
|
260
|
+
ActiveSupport::Deprecation.warn("Rendering actions with '.' in the name is deprecated: #{path}")
|
261
|
+
end
|
262
|
+
|
263
|
+
query = build_query(path, details)
|
264
|
+
find_template_paths(query)
|
265
|
+
end
|
198
266
|
|
199
|
-
if RUBY_VERSION >= '2.2.0'
|
200
267
|
def find_template_paths(query)
|
201
|
-
Dir[query].reject
|
268
|
+
Dir[query].uniq.reject do |filename|
|
202
269
|
File.directory?(filename) ||
|
203
270
|
# deals with case-insensitive file systems.
|
204
271
|
!File.fnmatch(query, filename, File::FNM_EXTGLOB)
|
205
|
-
|
272
|
+
end
|
206
273
|
end
|
207
|
-
else
|
208
|
-
def find_template_paths(query)
|
209
|
-
# deals with case-insensitive file systems.
|
210
|
-
sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] }
|
211
274
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
275
|
+
def inside_path?(path, filename)
|
276
|
+
filename = File.expand_path(filename)
|
277
|
+
path = File.join(path, "")
|
278
|
+
filename.start_with?(path)
|
216
279
|
end
|
217
|
-
end
|
218
280
|
|
219
|
-
|
220
|
-
|
221
|
-
|
281
|
+
# Helper for building query glob string based on resolver's pattern.
|
282
|
+
def build_query(path, details)
|
283
|
+
query = @pattern.dup
|
284
|
+
|
285
|
+
prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
|
286
|
+
query.gsub!(/:prefix(\/)?/, prefix)
|
222
287
|
|
223
|
-
|
224
|
-
|
288
|
+
partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
|
289
|
+
query.gsub!(":action", partial)
|
225
290
|
|
226
|
-
|
227
|
-
|
291
|
+
details.each do |ext, candidates|
|
292
|
+
if ext == :variants && candidates == :any
|
293
|
+
query.gsub!(/:#{ext}/, "*")
|
294
|
+
else
|
295
|
+
query.gsub!(/:#{ext}/, "{#{candidates.compact.uniq.join(',')}}")
|
296
|
+
end
|
297
|
+
end
|
228
298
|
|
229
|
-
|
230
|
-
query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
|
299
|
+
File.expand_path(query, @path)
|
231
300
|
end
|
232
301
|
|
233
|
-
|
234
|
-
|
302
|
+
def escape_entry(entry)
|
303
|
+
entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
|
304
|
+
end
|
235
305
|
|
236
|
-
|
237
|
-
|
238
|
-
|
306
|
+
# Extract handler, formats and variant from path. If a format cannot be found neither
|
307
|
+
# from the path, or the handler, we should return the array of formats given
|
308
|
+
# to the resolver.
|
309
|
+
def extract_handler_and_format_and_variant(path)
|
310
|
+
details = @path_parser.parse(path)
|
239
311
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
end
|
312
|
+
handler = Template.handler_for_extension(details[:handler])
|
313
|
+
format = details[:format] || handler.try(:default_format)
|
314
|
+
variant = details[:variant]
|
244
315
|
|
245
|
-
|
246
|
-
|
247
|
-
# to the resolver.
|
248
|
-
def extract_handler_and_format_and_variant(path, default_formats)
|
249
|
-
pieces = File.basename(path).split(".")
|
250
|
-
pieces.shift
|
251
|
-
|
252
|
-
extension = pieces.pop
|
253
|
-
unless extension
|
254
|
-
message = "The file #{path} did not specify a template handler. The default is currently ERB, " \
|
255
|
-
"but will change to RAW in the future."
|
256
|
-
ActiveSupport::Deprecation.warn message
|
316
|
+
# Template::Types[format] and handler.default_format can return nil
|
317
|
+
[handler, format, variant]
|
257
318
|
end
|
258
|
-
|
259
|
-
handler = Template.handler_for_extension(extension)
|
260
|
-
format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
|
261
|
-
format &&= Template::Types[format]
|
262
|
-
|
263
|
-
[handler, format, variant]
|
264
|
-
end
|
265
319
|
end
|
266
320
|
|
267
|
-
# A resolver that loads files from the filesystem.
|
268
|
-
# resolving pattern. Such pattern can be a glob string supported by some variables.
|
269
|
-
#
|
270
|
-
# ==== Examples
|
271
|
-
#
|
272
|
-
# Default pattern, loads views the same way as previous versions of rails, eg. when you're
|
273
|
-
# looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}`
|
274
|
-
#
|
275
|
-
# FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}")
|
276
|
-
#
|
277
|
-
# This one allows you to keep files with different formats in separate subdirectories,
|
278
|
-
# eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`,
|
279
|
-
# `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc.
|
280
|
-
#
|
281
|
-
# FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{.:handlers,}")
|
282
|
-
#
|
283
|
-
# If you don't specify a pattern then the default will be used.
|
284
|
-
#
|
285
|
-
# In order to use any of the customized resolvers above in a Rails application, you just need
|
286
|
-
# to configure ActionController::Base.view_paths in an initializer, for example:
|
287
|
-
#
|
288
|
-
# ActionController::Base.view_paths = FileSystemResolver.new(
|
289
|
-
# Rails.root.join("app/views"),
|
290
|
-
# ":prefix{/:locale}/:action{.:formats,}{.:handlers,}"
|
291
|
-
# )
|
292
|
-
#
|
293
|
-
# ==== Pattern format and variables
|
294
|
-
#
|
295
|
-
# Pattern has to be a valid glob string, and it allows you to use the
|
296
|
-
# following variables:
|
297
|
-
#
|
298
|
-
# * <tt>:prefix</tt> - usually the controller path
|
299
|
-
# * <tt>:action</tt> - name of the action
|
300
|
-
# * <tt>:locale</tt> - possible locale versions
|
301
|
-
# * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
|
302
|
-
# * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
|
303
|
-
#
|
321
|
+
# A resolver that loads files from the filesystem.
|
304
322
|
class FileSystemResolver < PathResolver
|
305
|
-
|
323
|
+
attr_reader :path
|
324
|
+
|
325
|
+
def initialize(path)
|
306
326
|
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
|
307
|
-
super(
|
327
|
+
super()
|
308
328
|
@path = File.expand_path(path)
|
309
329
|
end
|
310
330
|
|
@@ -321,26 +341,92 @@ module ActionView
|
|
321
341
|
|
322
342
|
# An Optimized resolver for Rails' most common case.
|
323
343
|
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
|
324
|
-
def
|
325
|
-
|
344
|
+
def initialize(path)
|
345
|
+
super(path)
|
346
|
+
end
|
326
347
|
|
327
|
-
|
328
|
-
|
329
|
-
|
348
|
+
private
|
349
|
+
def find_candidate_template_paths(path)
|
350
|
+
# Instead of checking for every possible path, as our other globs would
|
351
|
+
# do, scan the directory for files with the right prefix.
|
352
|
+
query = "#{escape_entry(File.join(@path, path))}*"
|
330
353
|
|
331
|
-
|
332
|
-
|
354
|
+
Dir[query].reject do |filename|
|
355
|
+
File.directory?(filename)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
def find_template_paths_from_details(path, details)
|
360
|
+
if path.name.include?(".")
|
361
|
+
# Fall back to the unoptimized resolver, which will warn
|
362
|
+
return super
|
363
|
+
end
|
364
|
+
|
365
|
+
candidates = find_candidate_template_paths(path)
|
366
|
+
|
367
|
+
regex = build_regex(path, details)
|
368
|
+
|
369
|
+
candidates.uniq.reject do |filename|
|
370
|
+
# This regex match does double duty of finding only files which match
|
371
|
+
# details (instead of just matching the prefix) and also filtering for
|
372
|
+
# case-insensitive file systems.
|
373
|
+
!regex.match?(filename) ||
|
374
|
+
File.directory?(filename)
|
375
|
+
end.sort_by do |filename|
|
376
|
+
# Because we scanned the directory, instead of checking for files
|
377
|
+
# one-by-one, they will be returned in an arbitrary order.
|
378
|
+
# We can use the matches found by the regex and sort by their index in
|
379
|
+
# details.
|
380
|
+
match = filename.match(regex)
|
381
|
+
EXTENSIONS.keys.map do |ext|
|
382
|
+
if ext == :variants && details[ext] == :any
|
383
|
+
match[ext].nil? ? 0 : 1
|
384
|
+
elsif match[ext].nil?
|
385
|
+
# No match should be last
|
386
|
+
details[ext].length
|
387
|
+
else
|
388
|
+
found = match[ext].to_sym
|
389
|
+
details[ext].index(found)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
def build_regex(path, details)
|
396
|
+
query = Regexp.escape(File.join(@path, path))
|
397
|
+
exts = EXTENSIONS.map do |ext, prefix|
|
398
|
+
match =
|
399
|
+
if ext == :variants && details[ext] == :any
|
400
|
+
".*?"
|
401
|
+
else
|
402
|
+
arr = details[ext].compact
|
403
|
+
arr.uniq!
|
404
|
+
arr.map! { |e| Regexp.escape(e) }
|
405
|
+
arr.join("|")
|
406
|
+
end
|
407
|
+
prefix = Regexp.escape(prefix)
|
408
|
+
"(#{prefix}(?<#{ext}>#{match}))?"
|
409
|
+
end.join
|
410
|
+
|
411
|
+
%r{\A#{query}#{exts}\z}
|
412
|
+
end
|
333
413
|
end
|
334
414
|
|
335
415
|
# The same as FileSystemResolver but does not allow templates to store
|
336
416
|
# a virtual path since it is invalid for such resolvers.
|
337
417
|
class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
|
418
|
+
private_class_method :new
|
419
|
+
|
338
420
|
def self.instances
|
339
421
|
[new(""), new("/")]
|
340
422
|
end
|
341
423
|
|
342
|
-
def
|
343
|
-
super
|
424
|
+
def build_unbound_template(template, _)
|
425
|
+
super(template, nil)
|
426
|
+
end
|
427
|
+
|
428
|
+
def reject_files_external_to_app(files)
|
429
|
+
files
|
344
430
|
end
|
345
431
|
end
|
346
432
|
end
|