actionview 4.2.10 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +141 -272
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -3
- data/lib/action_view/base.rb +33 -21
- data/lib/action_view/buffers.rb +1 -1
- data/lib/action_view/context.rb +1 -1
- data/lib/action_view/dependency_tracker.rb +52 -20
- data/lib/action_view/digestor.rb +86 -83
- data/lib/action_view/flows.rb +9 -11
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/active_model_helper.rb +8 -8
- data/lib/action_view/helpers/asset_tag_helper.rb +74 -38
- data/lib/action_view/helpers/asset_url_helper.rb +160 -59
- data/lib/action_view/helpers/atom_feed_helper.rb +16 -16
- data/lib/action_view/helpers/cache_helper.rb +90 -35
- data/lib/action_view/helpers/capture_helper.rb +7 -6
- data/lib/action_view/helpers/controller_helper.rb +3 -2
- data/lib/action_view/helpers/csrf_helper.rb +3 -3
- data/lib/action_view/helpers/date_helper.rb +156 -108
- data/lib/action_view/helpers/debug_helper.rb +3 -4
- data/lib/action_view/helpers/form_helper.rb +475 -94
- data/lib/action_view/helpers/form_options_helper.rb +87 -47
- data/lib/action_view/helpers/form_tag_helper.rb +88 -57
- data/lib/action_view/helpers/javascript_helper.rb +10 -10
- data/lib/action_view/helpers/number_helper.rb +76 -59
- data/lib/action_view/helpers/output_safety_helper.rb +34 -4
- data/lib/action_view/helpers/record_tag_helper.rb +12 -99
- data/lib/action_view/helpers/rendering_helper.rb +3 -3
- data/lib/action_view/helpers/sanitize_helper.rb +17 -14
- data/lib/action_view/helpers/tag_helper.rb +198 -73
- data/lib/action_view/helpers/tags/base.rb +132 -97
- data/lib/action_view/helpers/tags/check_box.rb +17 -17
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +9 -33
- data/lib/action_view/helpers/tags/collection_helpers.rb +68 -36
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +3 -11
- data/lib/action_view/helpers/tags/collection_select.rb +2 -2
- data/lib/action_view/helpers/tags/date_select.rb +36 -36
- data/lib/action_view/helpers/tags/datetime_field.rb +1 -1
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +2 -2
- data/lib/action_view/helpers/tags/label.rb +5 -1
- data/lib/action_view/helpers/tags/password_field.rb +1 -1
- data/lib/action_view/helpers/tags/placeholderable.rb +1 -1
- data/lib/action_view/helpers/tags/radio_button.rb +4 -4
- data/lib/action_view/helpers/tags/search_field.rb +12 -9
- data/lib/action_view/helpers/tags/select.rb +9 -9
- data/lib/action_view/helpers/tags/text_area.rb +1 -1
- data/lib/action_view/helpers/tags/text_field.rb +5 -6
- data/lib/action_view/helpers/tags/translator.rb +15 -13
- data/lib/action_view/helpers/text_helper.rb +47 -30
- data/lib/action_view/helpers/translation_helper.rb +60 -30
- data/lib/action_view/helpers/url_helper.rb +132 -104
- data/lib/action_view/helpers.rb +1 -1
- data/lib/action_view/layouts.rb +59 -54
- data/lib/action_view/log_subscriber.rb +56 -7
- data/lib/action_view/lookup_context.rb +76 -61
- data/lib/action_view/model_naming.rb +1 -1
- data/lib/action_view/path_set.rb +28 -19
- data/lib/action_view/railtie.rb +30 -6
- data/lib/action_view/record_identifier.rb +51 -25
- data/lib/action_view/renderer/abstract_renderer.rb +19 -15
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +55 -0
- data/lib/action_view/renderer/partial_renderer.rb +208 -206
- data/lib/action_view/renderer/renderer.rb +2 -6
- data/lib/action_view/renderer/streaming_template_renderer.rb +46 -48
- data/lib/action_view/renderer/template_renderer.rb +65 -66
- data/lib/action_view/rendering.rb +16 -9
- data/lib/action_view/routing_url_for.rb +25 -17
- data/lib/action_view/tasks/cache_digests.rake +23 -0
- data/lib/action_view/template/error.rb +14 -13
- data/lib/action_view/template/handlers/builder.rb +7 -7
- data/lib/action_view/template/handlers/erb/deprecated_erubis.rb +9 -0
- data/lib/action_view/template/handlers/erb/erubi.rb +81 -0
- data/lib/action_view/template/handlers/erb/erubis.rb +81 -0
- data/lib/action_view/template/handlers/erb.rb +9 -76
- data/lib/action_view/template/handlers/html.rb +9 -0
- data/lib/action_view/template/handlers/raw.rb +1 -3
- data/lib/action_view/template/handlers.rb +8 -6
- data/lib/action_view/template/html.rb +2 -4
- data/lib/action_view/template/resolver.rb +133 -109
- data/lib/action_view/template/text.rb +5 -8
- data/lib/action_view/template/types.rb +15 -17
- data/lib/action_view/template.rb +51 -28
- data/lib/action_view/test_case.rb +32 -27
- data/lib/action_view/testing/resolvers.rb +29 -31
- data/lib/action_view/version.rb +1 -1
- data/lib/action_view/view_paths.rb +26 -32
- data/lib/action_view.rb +5 -5
- data/lib/assets/compiled/rails-ujs.js +685 -0
- metadata +23 -23
- data/lib/action_view/tasks/dependencies.rake +0 -23
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
gem "erubis"
|
|
2
|
+
require "erubis"
|
|
3
|
+
|
|
4
|
+
module ActionView
|
|
5
|
+
class Template
|
|
6
|
+
module Handlers
|
|
7
|
+
class ERB
|
|
8
|
+
class Erubis < ::Erubis::Eruby
|
|
9
|
+
# :nodoc: all
|
|
10
|
+
def add_preamble(src)
|
|
11
|
+
@newline_pending = 0
|
|
12
|
+
src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add_text(src, text)
|
|
16
|
+
return if text.empty?
|
|
17
|
+
|
|
18
|
+
if text == "\n"
|
|
19
|
+
@newline_pending += 1
|
|
20
|
+
else
|
|
21
|
+
src << "@output_buffer.safe_append='"
|
|
22
|
+
src << "\n" * @newline_pending if @newline_pending > 0
|
|
23
|
+
src << escape_text(text)
|
|
24
|
+
src << "'.freeze;"
|
|
25
|
+
|
|
26
|
+
@newline_pending = 0
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Erubis toggles <%= and <%== behavior when escaping is enabled.
|
|
31
|
+
# We override to always treat <%== as escaped.
|
|
32
|
+
def add_expr(src, code, indicator)
|
|
33
|
+
case indicator
|
|
34
|
+
when "=="
|
|
35
|
+
add_expr_escaped(src, code)
|
|
36
|
+
else
|
|
37
|
+
super
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
BLOCK_EXPR = /\s*((\s+|\))do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
|
42
|
+
|
|
43
|
+
def add_expr_literal(src, code)
|
|
44
|
+
flush_newline_if_pending(src)
|
|
45
|
+
if BLOCK_EXPR.match?(code)
|
|
46
|
+
src << "@output_buffer.append= " << code
|
|
47
|
+
else
|
|
48
|
+
src << "@output_buffer.append=(" << code << ");"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def add_expr_escaped(src, code)
|
|
53
|
+
flush_newline_if_pending(src)
|
|
54
|
+
if BLOCK_EXPR.match?(code)
|
|
55
|
+
src << "@output_buffer.safe_expr_append= " << code
|
|
56
|
+
else
|
|
57
|
+
src << "@output_buffer.safe_expr_append=(" << code << ");"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def add_stmt(src, code)
|
|
62
|
+
flush_newline_if_pending(src)
|
|
63
|
+
super
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def add_postamble(src)
|
|
67
|
+
flush_newline_if_pending(src)
|
|
68
|
+
src << "@output_buffer.to_s"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def flush_newline_if_pending(src)
|
|
72
|
+
if @newline_pending > 0
|
|
73
|
+
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
|
|
74
|
+
@newline_pending = 0
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -1,87 +1,20 @@
|
|
|
1
|
-
require 'erubis'
|
|
2
|
-
|
|
3
1
|
module ActionView
|
|
4
2
|
class Template
|
|
5
3
|
module Handlers
|
|
6
|
-
|
|
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*((\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_expr_append= " << code
|
|
53
|
-
else
|
|
54
|
-
src << "@output_buffer.safe_expr_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
|
|
4
|
+
autoload :Erubis, "action_view/template/handlers/erb/deprecated_erubis"
|
|
75
5
|
|
|
76
6
|
class ERB
|
|
7
|
+
autoload :Erubi, "action_view/template/handlers/erb/erubi"
|
|
8
|
+
autoload :Erubis, "action_view/template/handlers/erb/erubis"
|
|
9
|
+
|
|
77
10
|
# Specify trim mode for the ERB compiler. Defaults to '-'.
|
|
78
11
|
# See ERB documentation for suitable values.
|
|
79
12
|
class_attribute :erb_trim_mode
|
|
80
|
-
self.erb_trim_mode =
|
|
13
|
+
self.erb_trim_mode = "-"
|
|
81
14
|
|
|
82
15
|
# Default implementation used.
|
|
83
16
|
class_attribute :erb_implementation
|
|
84
|
-
self.erb_implementation =
|
|
17
|
+
self.erb_implementation = Erubi
|
|
85
18
|
|
|
86
19
|
# Do not escape templates of these mime types.
|
|
87
20
|
class_attribute :escape_whitelist
|
|
@@ -108,7 +41,7 @@ module ActionView
|
|
|
108
41
|
# expression
|
|
109
42
|
template_source = template.source.dup.force_encoding(Encoding::ASCII_8BIT)
|
|
110
43
|
|
|
111
|
-
erb = template_source.gsub(ENCODING_TAG,
|
|
44
|
+
erb = template_source.gsub(ENCODING_TAG, "")
|
|
112
45
|
encoding = $2
|
|
113
46
|
|
|
114
47
|
erb.force_encoding valid_encoding(template.source.dup, encoding)
|
|
@@ -118,8 +51,8 @@ module ActionView
|
|
|
118
51
|
|
|
119
52
|
self.class.erb_implementation.new(
|
|
120
53
|
erb,
|
|
121
|
-
:
|
|
122
|
-
:
|
|
54
|
+
escape: (self.class.escape_whitelist.include? template.type),
|
|
55
|
+
trim: (self.class.erb_trim_mode == "-")
|
|
123
56
|
).src
|
|
124
57
|
end
|
|
125
58
|
|
|
@@ -2,14 +2,16 @@ module ActionView #:nodoc:
|
|
|
2
2
|
# = Action View Template Handlers
|
|
3
3
|
class Template
|
|
4
4
|
module Handlers #:nodoc:
|
|
5
|
-
autoload :
|
|
6
|
-
autoload :
|
|
7
|
-
autoload :
|
|
5
|
+
autoload :Raw, "action_view/template/handlers/raw"
|
|
6
|
+
autoload :ERB, "action_view/template/handlers/erb"
|
|
7
|
+
autoload :Html, "action_view/template/handlers/html"
|
|
8
|
+
autoload :Builder, "action_view/template/handlers/builder"
|
|
8
9
|
|
|
9
10
|
def self.extended(base)
|
|
10
|
-
base.register_default_template_handler :
|
|
11
|
+
base.register_default_template_handler :raw, Raw.new
|
|
12
|
+
base.register_template_handler :erb, ERB.new
|
|
13
|
+
base.register_template_handler :html, Html.new
|
|
11
14
|
base.register_template_handler :builder, Builder.new
|
|
12
|
-
base.register_template_handler :raw, Raw.new
|
|
13
15
|
base.register_template_handler :ruby, :source.to_proc
|
|
14
16
|
end
|
|
15
17
|
|
|
@@ -42,7 +44,7 @@ module ActionView #:nodoc:
|
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
def template_handler_extensions
|
|
45
|
-
@@template_handlers.keys.map
|
|
47
|
+
@@template_handlers.keys.map(&:to_s).sort
|
|
46
48
|
end
|
|
47
49
|
|
|
48
50
|
def registered_template_handler(extension)
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
require "pathname"
|
|
2
2
|
require "active_support/core_ext/class"
|
|
3
3
|
require "active_support/core_ext/module/attribute_accessors"
|
|
4
|
-
require 'active_support/core_ext/string/filters'
|
|
5
4
|
require "action_view/template"
|
|
6
5
|
require "thread"
|
|
7
|
-
require "
|
|
6
|
+
require "concurrent/map"
|
|
8
7
|
|
|
9
8
|
module ActionView
|
|
10
9
|
# = Action View Resolver
|
|
@@ -36,23 +35,28 @@ module ActionView
|
|
|
36
35
|
|
|
37
36
|
# Threadsafe template cache
|
|
38
37
|
class Cache #:nodoc:
|
|
39
|
-
class SmallCache <
|
|
38
|
+
class SmallCache < Concurrent::Map
|
|
40
39
|
def initialize(options = {})
|
|
41
|
-
super(options.merge(:
|
|
40
|
+
super(options.merge(initial_capacity: 2))
|
|
42
41
|
end
|
|
43
42
|
end
|
|
44
43
|
|
|
45
44
|
# preallocate all the default blocks for performance/memory consumption reasons
|
|
46
|
-
PARTIAL_BLOCK = lambda {|cache, partial| cache[partial] = SmallCache.new}
|
|
47
|
-
PREFIX_BLOCK = lambda {|cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK)}
|
|
48
|
-
NAME_BLOCK = lambda {|cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK)}
|
|
49
|
-
KEY_BLOCK = lambda {|cache, key| cache[key] = SmallCache.new(&NAME_BLOCK)}
|
|
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) }
|
|
50
49
|
|
|
51
50
|
# usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
|
|
52
51
|
NO_TEMPLATES = [].freeze
|
|
53
52
|
|
|
54
53
|
def initialize
|
|
55
54
|
@data = SmallCache.new(&KEY_BLOCK)
|
|
55
|
+
@query_cache = SmallCache.new
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def inspect
|
|
59
|
+
"#<#{self.class.name}:0x#{(object_id << 1).to_s(16)} keys=#{@data.size} queries=#{@query_cache.size}>"
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
# Cache the templates returned by the block
|
|
@@ -71,28 +75,54 @@ module ActionView
|
|
|
71
75
|
end
|
|
72
76
|
end
|
|
73
77
|
|
|
78
|
+
def cache_query(query) # :nodoc:
|
|
79
|
+
if Resolver.caching?
|
|
80
|
+
@query_cache[query] ||= canonical_no_templates(yield)
|
|
81
|
+
else
|
|
82
|
+
yield
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
74
86
|
def clear
|
|
75
87
|
@data.clear
|
|
88
|
+
@query_cache.clear
|
|
76
89
|
end
|
|
77
90
|
|
|
78
|
-
|
|
91
|
+
# Get the cache size. Do not call this
|
|
92
|
+
# method. This method is not guaranteed to be here ever.
|
|
93
|
+
def size # :nodoc:
|
|
94
|
+
size = 0
|
|
95
|
+
@data.each_value do |v1|
|
|
96
|
+
v1.each_value do |v2|
|
|
97
|
+
v2.each_value do |v3|
|
|
98
|
+
v3.each_value do |v4|
|
|
99
|
+
size += v4.size
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
79
104
|
|
|
80
|
-
|
|
81
|
-
templates.empty? ? NO_TEMPLATES : templates
|
|
105
|
+
size + @query_cache.size
|
|
82
106
|
end
|
|
83
107
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return fresh_templates.blank? != cached_templates.blank?
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
def canonical_no_templates(templates)
|
|
111
|
+
templates.empty? ? NO_TEMPLATES : templates
|
|
89
112
|
end
|
|
90
113
|
|
|
91
|
-
|
|
114
|
+
def templates_have_changed?(cached_templates, fresh_templates)
|
|
115
|
+
# if either the old or new template list is empty, we don't need to (and can't)
|
|
116
|
+
# compare modification times, and instead just check whether the lists are different
|
|
117
|
+
if cached_templates.blank? || fresh_templates.blank?
|
|
118
|
+
return fresh_templates.blank? != cached_templates.blank?
|
|
119
|
+
end
|
|
92
120
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
121
|
+
cached_templates_max_updated_at = cached_templates.map(&:updated_at).max
|
|
122
|
+
|
|
123
|
+
# if a template has changed, it will be now be newer than all the cached templates
|
|
124
|
+
fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at }
|
|
125
|
+
end
|
|
96
126
|
end
|
|
97
127
|
|
|
98
128
|
cattr_accessor :caching
|
|
@@ -111,18 +141,22 @@ module ActionView
|
|
|
111
141
|
end
|
|
112
142
|
|
|
113
143
|
# Normalizes the arguments and passes it on to find_templates.
|
|
114
|
-
def find_all(name, prefix=nil, partial=false, details={}, key=nil, locals=[])
|
|
144
|
+
def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
|
|
115
145
|
cached(key, [name, prefix, partial], details, locals) do
|
|
116
|
-
find_templates(name, prefix, partial, details
|
|
146
|
+
find_templates(name, prefix, partial, details)
|
|
117
147
|
end
|
|
118
148
|
end
|
|
119
149
|
|
|
120
|
-
def find_all_anywhere(name, prefix, partial=false, details={}, key=nil, locals=[])
|
|
150
|
+
def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = [])
|
|
121
151
|
cached(key, [name, prefix, partial], details, locals) do
|
|
122
152
|
find_templates(name, prefix, partial, details, true)
|
|
123
153
|
end
|
|
124
154
|
end
|
|
125
155
|
|
|
156
|
+
def find_all_with_query(query) # :nodoc:
|
|
157
|
+
@cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
|
|
158
|
+
end
|
|
159
|
+
|
|
126
160
|
private
|
|
127
161
|
|
|
128
162
|
delegate :caching?, to: :class
|
|
@@ -130,8 +164,8 @@ module ActionView
|
|
|
130
164
|
# This is what child classes implement. No defaults are needed
|
|
131
165
|
# because Resolver guarantees that the arguments are present and
|
|
132
166
|
# normalized.
|
|
133
|
-
def find_templates(name, prefix, partial, details, outside_app_allowed)
|
|
134
|
-
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed) method"
|
|
167
|
+
def find_templates(name, prefix, partial, details, outside_app_allowed = false)
|
|
168
|
+
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed = false) method"
|
|
135
169
|
end
|
|
136
170
|
|
|
137
171
|
# Helpers that builds a path. Useful for building virtual paths.
|
|
@@ -143,9 +177,9 @@ module ActionView
|
|
|
143
177
|
# always check the cache before hitting the resolver. Otherwise,
|
|
144
178
|
# it always hits the resolver but if the key is present, check if the
|
|
145
179
|
# resolver is fresher before returning it.
|
|
146
|
-
def cached(key, path_info, details, locals)
|
|
180
|
+
def cached(key, path_info, details, locals)
|
|
147
181
|
name, prefix, partial = path_info
|
|
148
|
-
locals = locals.map
|
|
182
|
+
locals = locals.map(&:to_s).sort!
|
|
149
183
|
|
|
150
184
|
if key
|
|
151
185
|
@cache.cache(key, name, prefix, partial, locals) do
|
|
@@ -157,7 +191,7 @@ module ActionView
|
|
|
157
191
|
end
|
|
158
192
|
|
|
159
193
|
# Ensures all the resolver information is set in the template.
|
|
160
|
-
def decorate(templates, path_info, details, locals)
|
|
194
|
+
def decorate(templates, path_info, details, locals)
|
|
161
195
|
cached = nil
|
|
162
196
|
templates.each do |t|
|
|
163
197
|
t.locals = locals
|
|
@@ -170,117 +204,103 @@ module ActionView
|
|
|
170
204
|
|
|
171
205
|
# An abstract class that implements a Resolver with path semantics.
|
|
172
206
|
class PathResolver < Resolver #:nodoc:
|
|
173
|
-
EXTENSIONS = { :
|
|
207
|
+
EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
|
|
174
208
|
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
|
|
175
209
|
|
|
176
|
-
def initialize(pattern=nil)
|
|
210
|
+
def initialize(pattern = nil)
|
|
177
211
|
@pattern = pattern || DEFAULT_PATTERN
|
|
178
212
|
super()
|
|
179
213
|
end
|
|
180
214
|
|
|
181
215
|
private
|
|
182
216
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
217
|
+
def find_templates(name, prefix, partial, details, outside_app_allowed = false)
|
|
218
|
+
path = Path.build(name, prefix, partial)
|
|
219
|
+
query(path, details, details[:formats], outside_app_allowed)
|
|
220
|
+
end
|
|
187
221
|
|
|
188
|
-
|
|
189
|
-
|
|
222
|
+
def query(path, details, formats, outside_app_allowed)
|
|
223
|
+
query = build_query(path, details)
|
|
190
224
|
|
|
191
|
-
|
|
192
|
-
|
|
225
|
+
template_paths = find_template_paths(query)
|
|
226
|
+
template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
|
|
193
227
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
228
|
+
template_paths.map do |template|
|
|
229
|
+
handler, format, variant = extract_handler_and_format_and_variant(template)
|
|
230
|
+
contents = File.binread(template)
|
|
197
231
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
232
|
+
Template.new(contents, File.expand_path(template), handler,
|
|
233
|
+
virtual_path: path.virtual,
|
|
234
|
+
format: format,
|
|
235
|
+
variant: variant,
|
|
236
|
+
updated_at: mtime(template)
|
|
237
|
+
)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
206
240
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
241
|
+
def reject_files_external_to_app(files)
|
|
242
|
+
files.reject { |filename| !inside_path?(@path, filename) }
|
|
243
|
+
end
|
|
210
244
|
|
|
211
|
-
if RUBY_VERSION >= '2.2.0'
|
|
212
245
|
def find_template_paths(query)
|
|
213
|
-
Dir[query].reject
|
|
246
|
+
Dir[query].uniq.reject do |filename|
|
|
214
247
|
File.directory?(filename) ||
|
|
215
248
|
# deals with case-insensitive file systems.
|
|
216
249
|
!File.fnmatch(query, filename, File::FNM_EXTGLOB)
|
|
217
|
-
|
|
250
|
+
end
|
|
218
251
|
end
|
|
219
|
-
else
|
|
220
|
-
def find_template_paths(query)
|
|
221
|
-
# deals with case-insensitive file systems.
|
|
222
|
-
sanitizer = Hash.new { |h,dir| h[dir] = Dir["#{dir}/*"] }
|
|
223
252
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
253
|
+
def inside_path?(path, filename)
|
|
254
|
+
filename = File.expand_path(filename)
|
|
255
|
+
path = File.join(path, "")
|
|
256
|
+
filename.start_with?(path)
|
|
228
257
|
end
|
|
229
|
-
end
|
|
230
258
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
filename.start_with?(path)
|
|
235
|
-
end
|
|
259
|
+
# Helper for building query glob string based on resolver's pattern.
|
|
260
|
+
def build_query(path, details)
|
|
261
|
+
query = @pattern.dup
|
|
236
262
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
query = @pattern.dup
|
|
263
|
+
prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
|
|
264
|
+
query.gsub!(/:prefix(\/)?/, prefix)
|
|
240
265
|
|
|
241
|
-
|
|
242
|
-
|
|
266
|
+
partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
|
|
267
|
+
query.gsub!(/:action/, partial)
|
|
243
268
|
|
|
244
|
-
|
|
245
|
-
|
|
269
|
+
details.each do |ext, candidates|
|
|
270
|
+
if ext == :variants && candidates == :any
|
|
271
|
+
query.gsub!(/:#{ext}/, "*")
|
|
272
|
+
else
|
|
273
|
+
query.gsub!(/:#{ext}/, "{#{candidates.compact.uniq.join(',')}}")
|
|
274
|
+
end
|
|
275
|
+
end
|
|
246
276
|
|
|
247
|
-
|
|
248
|
-
query.gsub!(/\:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
|
|
277
|
+
File.expand_path(query, @path)
|
|
249
278
|
end
|
|
250
279
|
|
|
251
|
-
|
|
252
|
-
|
|
280
|
+
def escape_entry(entry)
|
|
281
|
+
entry.gsub(/[*?{}\[\]]/, '\\\\\\&'.freeze)
|
|
282
|
+
end
|
|
253
283
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
284
|
+
# Returns the file mtime from the filesystem.
|
|
285
|
+
def mtime(p)
|
|
286
|
+
File.mtime(p)
|
|
287
|
+
end
|
|
257
288
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
289
|
+
# Extract handler, formats and variant from path. If a format cannot be found neither
|
|
290
|
+
# from the path, or the handler, we should return the array of formats given
|
|
291
|
+
# to the resolver.
|
|
292
|
+
def extract_handler_and_format_and_variant(path)
|
|
293
|
+
pieces = File.basename(path).split(".".freeze)
|
|
294
|
+
pieces.shift
|
|
262
295
|
|
|
263
|
-
|
|
264
|
-
# from the path, or the handler, we should return the array of formats given
|
|
265
|
-
# to the resolver.
|
|
266
|
-
def extract_handler_and_format_and_variant(path, default_formats)
|
|
267
|
-
pieces = File.basename(path).split(".")
|
|
268
|
-
pieces.shift
|
|
269
|
-
|
|
270
|
-
extension = pieces.pop
|
|
271
|
-
unless extension
|
|
272
|
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
|
273
|
-
The file #{path} did not specify a template handler. The default is
|
|
274
|
-
currently ERB, but will change to RAW in the future.
|
|
275
|
-
MSG
|
|
276
|
-
end
|
|
296
|
+
extension = pieces.pop
|
|
277
297
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
298
|
+
handler = Template.handler_for_extension(extension)
|
|
299
|
+
format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
|
|
300
|
+
format &&= Template::Types[format]
|
|
281
301
|
|
|
282
|
-
|
|
283
|
-
|
|
302
|
+
[handler, format, variant]
|
|
303
|
+
end
|
|
284
304
|
end
|
|
285
305
|
|
|
286
306
|
# A resolver that loads files from the filesystem. It allows setting your own
|
|
@@ -306,7 +326,7 @@ module ActionView
|
|
|
306
326
|
#
|
|
307
327
|
# ActionController::Base.view_paths = FileSystemResolver.new(
|
|
308
328
|
# Rails.root.join("app/views"),
|
|
309
|
-
# ":prefix{
|
|
329
|
+
# ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}",
|
|
310
330
|
# )
|
|
311
331
|
#
|
|
312
332
|
# ==== Pattern format and variables
|
|
@@ -322,7 +342,7 @@ module ActionView
|
|
|
322
342
|
# * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
|
|
323
343
|
#
|
|
324
344
|
class FileSystemResolver < PathResolver
|
|
325
|
-
def initialize(path, pattern=nil)
|
|
345
|
+
def initialize(path, pattern = nil)
|
|
326
346
|
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
|
|
327
347
|
super(pattern)
|
|
328
348
|
@path = File.expand_path(path)
|
|
@@ -345,7 +365,11 @@ module ActionView
|
|
|
345
365
|
query = escape_entry(File.join(@path, path))
|
|
346
366
|
|
|
347
367
|
exts = EXTENSIONS.map do |ext, prefix|
|
|
348
|
-
|
|
368
|
+
if ext == :variants && details[ext] == :any
|
|
369
|
+
"{#{prefix}*,}"
|
|
370
|
+
else
|
|
371
|
+
"{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}"
|
|
372
|
+
end
|
|
349
373
|
end.join
|
|
350
374
|
|
|
351
375
|
query + exts
|