props_template 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/props_template.rb +60 -0
- data/lib/props_template/base.rb +113 -0
- data/lib/props_template/base_with_extensions.rb +117 -0
- data/lib/props_template/core_ext.rb +19 -0
- data/lib/props_template/dependency_tracker.rb +50 -0
- data/lib/props_template/extension_manager.rb +107 -0
- data/lib/props_template/extensions/cache.rb +150 -0
- data/lib/props_template/extensions/deferment.rb +56 -0
- data/lib/props_template/extensions/fragment.rb +50 -0
- data/lib/props_template/extensions/partial_renderer.rb +187 -0
- data/lib/props_template/handler.rb +16 -0
- data/lib/props_template/key_formatter.rb +33 -0
- data/lib/props_template/layout_patch.rb +55 -0
- data/lib/props_template/railtie.rb +22 -0
- data/lib/props_template/searcher.rb +106 -0
- data/spec/layout_spec.rb +16 -0
- data/spec/props_template_spec.rb +282 -0
- data/spec/searcher_spec.rb +209 -0
- metadata +119 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9d1594043556583c84e0d4406165c623183b5843200c5fcdcac84f86ef7c30cc
|
4
|
+
data.tar.gz: 1b9cf4b6fa1accad65dee696ba8b3b998dc1f1687c1ef010980def63fceed882
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 25f9e1f56cead08969fa97f7b010059bfc073e67977c20d0b71977ada79a8282b9c037d8a9f9b3f744cfe48f09ae2a91e2442e2738433a124977bd04eea6d5eb
|
7
|
+
data.tar.gz: 0442331012ade9ae49c2adc2a34e2c4b2ac5d809030622334fb71ad98db221be1f8c1032e4c81d2874e519ab676fa467f333457124f92df24a710c4e0b5c1663
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'props_template/base_with_extensions'
|
4
|
+
require 'props_template/searcher'
|
5
|
+
require 'props_template/handler'
|
6
|
+
|
7
|
+
require 'active_support'
|
8
|
+
|
9
|
+
module Props
|
10
|
+
class Template
|
11
|
+
class << self
|
12
|
+
attr_accessor :template_lookup_options
|
13
|
+
end
|
14
|
+
|
15
|
+
self.template_lookup_options = { handlers: [:props] }
|
16
|
+
|
17
|
+
delegate :result!, :array!,
|
18
|
+
:commands_to_json!,
|
19
|
+
:deferred!,
|
20
|
+
:fragments!,
|
21
|
+
:set_block_content!,
|
22
|
+
:fragment_digest!,
|
23
|
+
to: :builder!
|
24
|
+
|
25
|
+
def initialize(context = nil, options = {})
|
26
|
+
@builder = BaseWithExtensions.new(self, context, options)
|
27
|
+
@context = context
|
28
|
+
end
|
29
|
+
|
30
|
+
def set!(key, options = {}, &block)
|
31
|
+
if block_given? && options[:search] && !@builder.is_a?(Searcher)
|
32
|
+
|
33
|
+
prev_builder = @builder
|
34
|
+
@builder = Searcher.new(self, options[:search], @context)
|
35
|
+
options.delete(:search)
|
36
|
+
@builder.set!(key, options, &block)
|
37
|
+
found_block, found_options = @builder.found!
|
38
|
+
@builder = prev_builder
|
39
|
+
|
40
|
+
if found_block
|
41
|
+
set!(key, found_options, &found_block)
|
42
|
+
end
|
43
|
+
else
|
44
|
+
@builder.set!(key, options, &block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def builder!
|
49
|
+
@builder
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def method_missing(*args, &block)
|
55
|
+
set!(*args, &block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
require 'props_template/railtie' if defined?(Rails)
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'oj'
|
2
|
+
require 'active_support'
|
3
|
+
|
4
|
+
#todo: active_support/core_ext/string/output_safety.rb
|
5
|
+
|
6
|
+
module Props
|
7
|
+
class InvalidScopeForArrayError < StandardError; end
|
8
|
+
class InvalidScopeForObjError < StandardError; end
|
9
|
+
|
10
|
+
class Base
|
11
|
+
def initialize
|
12
|
+
@commands = [[:push_object]]
|
13
|
+
@stream = Oj::StringWriter.new(mode: :rails)
|
14
|
+
@scope = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_block_content!(options = {})
|
18
|
+
@commands.push([:push_object])
|
19
|
+
@scope = nil
|
20
|
+
yield
|
21
|
+
@commands.push([:pop])
|
22
|
+
end
|
23
|
+
|
24
|
+
def handle_set_block(key, options)
|
25
|
+
@commands.push([:push_key, key.to_s])
|
26
|
+
set_block_content!(options) do
|
27
|
+
yield
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def set!(key, value = nil)
|
32
|
+
@scope ||= :object
|
33
|
+
if @scope == :array
|
34
|
+
raise InvalidScopeForObjError.new('Attempted to set! on an array! scope')
|
35
|
+
end
|
36
|
+
if block_given?
|
37
|
+
handle_set_block(key, value) do
|
38
|
+
yield
|
39
|
+
end
|
40
|
+
else
|
41
|
+
@commands.push([:push_key, key.to_s])
|
42
|
+
@commands.push([:push_value, value])
|
43
|
+
end
|
44
|
+
|
45
|
+
@scope = :object
|
46
|
+
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def refine_item_options(item, options)
|
51
|
+
options
|
52
|
+
end
|
53
|
+
|
54
|
+
def handle_collection_item(collection, item, index, options)
|
55
|
+
set_block_content!(options) do
|
56
|
+
yield
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def refine_all_item_options(all_options)
|
61
|
+
all_options
|
62
|
+
end
|
63
|
+
|
64
|
+
def handle_collection(collection, options)
|
65
|
+
all_opts = collection.map do |item|
|
66
|
+
refine_item_options(item, options.clone)
|
67
|
+
end
|
68
|
+
|
69
|
+
all_opts = refine_all_item_options(all_opts)
|
70
|
+
|
71
|
+
collection.each_with_index do |item, index|
|
72
|
+
pass_opts = all_opts[index]
|
73
|
+
handle_collection_item(collection, item, index, pass_opts) do
|
74
|
+
#todo: remove index?
|
75
|
+
yield item, index
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
#todo, add ability to define contents of array
|
80
|
+
def array!(collection, options = {})
|
81
|
+
if @scope.nil?
|
82
|
+
@scope = :array
|
83
|
+
else
|
84
|
+
raise InvalidScopeForArrayError.new('array! expects exclusive use of this block')
|
85
|
+
end
|
86
|
+
@commands[-1] = [:push_array]
|
87
|
+
|
88
|
+
if !collection.empty?
|
89
|
+
handle_collection(collection, options) do |item, index|
|
90
|
+
yield item, index
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
@scope = :array
|
95
|
+
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
|
99
|
+
def commands_to_json!(commands)
|
100
|
+
commands.each do |command|
|
101
|
+
@stream.send(*command)
|
102
|
+
end
|
103
|
+
json = @stream.to_s
|
104
|
+
@stream.reset
|
105
|
+
json
|
106
|
+
end
|
107
|
+
|
108
|
+
def result!
|
109
|
+
@commands.push([:pop])
|
110
|
+
commands_to_json!(@commands)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'props_template/base'
|
2
|
+
require 'props_template/extensions/partial_renderer'
|
3
|
+
require 'props_template/extensions/cache'
|
4
|
+
require 'props_template/extensions/deferment'
|
5
|
+
require 'props_template/extension_manager'
|
6
|
+
require 'props_template/key_formatter'
|
7
|
+
|
8
|
+
require 'active_support/core_ext/string/output_safety'
|
9
|
+
|
10
|
+
module Props
|
11
|
+
class BaseWithExtensions < Base
|
12
|
+
attr_reader :builder, :context, :fragments, :traveled_path, :deferred, :commands
|
13
|
+
|
14
|
+
def initialize(builder, context = nil, options = {})
|
15
|
+
@context = context
|
16
|
+
@builder = builder
|
17
|
+
#todo: refactor so deferred can be its own class
|
18
|
+
@em = ExtensionManager.new(self)
|
19
|
+
@traveled_path = []
|
20
|
+
@key_formatter = KeyFormatter.new(camelize: :lower)
|
21
|
+
super()
|
22
|
+
end
|
23
|
+
|
24
|
+
def deferred!
|
25
|
+
@em.deferred
|
26
|
+
end
|
27
|
+
|
28
|
+
def fragments!
|
29
|
+
@em.fragments
|
30
|
+
end
|
31
|
+
|
32
|
+
def fragment_digest!
|
33
|
+
@em.fragment_digest
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_block_content!(options = {})
|
37
|
+
return super if !@em.has_extensions(options)
|
38
|
+
|
39
|
+
@em.handle(@commands, options) do
|
40
|
+
yield
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def scoped_state
|
45
|
+
prev_state = [@commands, @em.deferred, @em.fragments]
|
46
|
+
@commands = []
|
47
|
+
@em = ExtensionManager.new(self)
|
48
|
+
yield
|
49
|
+
next_state = [@commands, @em.deferred, @em.fragments]
|
50
|
+
@commands = prev_state[0]
|
51
|
+
@em = ExtensionManager.new(self, prev_state[1], prev_state[2])
|
52
|
+
|
53
|
+
next_state
|
54
|
+
end
|
55
|
+
|
56
|
+
def set!(key, options = {}, &block)
|
57
|
+
key = @key_formatter.format(key)
|
58
|
+
|
59
|
+
if block_given?
|
60
|
+
options = @em.refine_options(options)
|
61
|
+
end
|
62
|
+
|
63
|
+
super(key, options, &block)
|
64
|
+
end
|
65
|
+
|
66
|
+
def handle_set_block(key, options)
|
67
|
+
@traveled_path.push(key)
|
68
|
+
n = 1
|
69
|
+
if suffix = options[:path_suffix]
|
70
|
+
n += suffix.length
|
71
|
+
@traveled_path.push(suffix)
|
72
|
+
end
|
73
|
+
|
74
|
+
super
|
75
|
+
|
76
|
+
@traveled_path.pop(n)
|
77
|
+
return
|
78
|
+
end
|
79
|
+
|
80
|
+
def handle_collection_item(collection, item, index, options)
|
81
|
+
if collection.respond_to? :member_key
|
82
|
+
member_key = collection.member_key
|
83
|
+
end
|
84
|
+
|
85
|
+
if !member_key
|
86
|
+
@traveled_path.push(index)
|
87
|
+
else
|
88
|
+
id = if item.respond_to? member_key
|
89
|
+
item.send(member_key)
|
90
|
+
elsif item.is_a? Hash
|
91
|
+
item[member_key] || item[member_key.to_sym]
|
92
|
+
end
|
93
|
+
|
94
|
+
if id.nil?
|
95
|
+
@traveled_path.push(index)
|
96
|
+
else
|
97
|
+
@traveled_path.push("#{member_key.to_s}=#{id}")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
super
|
102
|
+
|
103
|
+
@traveled_path.pop
|
104
|
+
return
|
105
|
+
end
|
106
|
+
|
107
|
+
def refine_all_item_options(all_options)
|
108
|
+
@em.refine_all_item_options(all_options)
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
def refine_item_options(item, options)
|
113
|
+
@em.refine_options(options, item)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Props
|
2
|
+
module Extensions
|
3
|
+
module Array
|
4
|
+
module Member
|
5
|
+
def member_at(index)
|
6
|
+
at(index)
|
7
|
+
end
|
8
|
+
|
9
|
+
def member_by(attribute, value)
|
10
|
+
raise NotImplementedError, 'Implement member_by(attr, value) in your own delegate'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Array
|
18
|
+
include Props::Extensions::Array::Member
|
19
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# This was taken from jbuilder
|
2
|
+
require 'props_template'
|
3
|
+
|
4
|
+
|
5
|
+
dependency_tracker = false
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'action_view'
|
9
|
+
require 'action_view/dependency_tracker'
|
10
|
+
dependency_tracker = ::ActionView::DependencyTracker
|
11
|
+
rescue LoadError
|
12
|
+
begin
|
13
|
+
require 'cache_digests'
|
14
|
+
dependency_tracker = ::CacheDigests::DependencyTracker
|
15
|
+
rescue LoadError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
if dependency_tracker
|
20
|
+
module Props
|
21
|
+
module DependencyTrackerMethods
|
22
|
+
# Matches:
|
23
|
+
# json.comments @post.comments, partial: "comments/comment", as: :comment
|
24
|
+
# json.array! @posts, partial: "posts/post", as: :post
|
25
|
+
#
|
26
|
+
INDIRECT_RENDERS = /
|
27
|
+
(?::partial\s*=>|partial:) # partial: or :partial =>
|
28
|
+
\s* # optional whitespace
|
29
|
+
\[* # optional Bracket
|
30
|
+
\s* # optional whitespace
|
31
|
+
(['"])([^'"]+)\1 # quoted value
|
32
|
+
/x
|
33
|
+
|
34
|
+
def dependencies
|
35
|
+
indirect_dependencies + explicit_dependencies
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def indirect_dependencies
|
41
|
+
source.scan(INDIRECT_RENDERS).map(&:second)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
Props::DependencyTracker = Class.new(dependency_tracker::ERBTracker)
|
47
|
+
Props::DependencyTracker.send :include, Props::DependencyTrackerMethods
|
48
|
+
|
49
|
+
ActionView::DependencyTracker.register_tracker :props, Props::DependencyTracker
|
50
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'props_template/extensions/partial_renderer'
|
2
|
+
require 'props_template/extensions/cache'
|
3
|
+
require 'props_template/extensions/deferment'
|
4
|
+
require 'props_template/extensions/fragment'
|
5
|
+
|
6
|
+
module Props
|
7
|
+
class ExtensionManager
|
8
|
+
attr_reader :base, :builder, :context
|
9
|
+
|
10
|
+
def initialize(base, defered=[], fragments={})
|
11
|
+
@base = base
|
12
|
+
@context = base.context
|
13
|
+
@builder = base.builder
|
14
|
+
@fragment = Fragment.new(base, fragments)
|
15
|
+
@deferment = Deferment.new(base, defered)
|
16
|
+
@partialer = Partialer.new(base, context, builder)
|
17
|
+
@cache = Cache.new(@context)
|
18
|
+
end
|
19
|
+
|
20
|
+
def refine_options(options, item = nil)
|
21
|
+
options = @partialer.refine_options(options, item)
|
22
|
+
options = @deferment.refine_options(options, item)
|
23
|
+
options = Cache.refine_options(options, item)
|
24
|
+
options
|
25
|
+
end
|
26
|
+
|
27
|
+
def refine_all_item_options(all_options)
|
28
|
+
all_options = @partialer.find_and_add_template(all_options)
|
29
|
+
all_options = @cache.multi_fetch_and_add_results(all_options)
|
30
|
+
all_options
|
31
|
+
end
|
32
|
+
|
33
|
+
def deferred
|
34
|
+
@deferment.deferred
|
35
|
+
end
|
36
|
+
|
37
|
+
def fragment_digest
|
38
|
+
@fragment.name
|
39
|
+
end
|
40
|
+
|
41
|
+
def fragments
|
42
|
+
@fragment.fragments
|
43
|
+
end
|
44
|
+
|
45
|
+
def has_extensions(options)
|
46
|
+
options[:defer] || options[:cache] || options[:partial]
|
47
|
+
end
|
48
|
+
|
49
|
+
def handle(commands, options)
|
50
|
+
return yield if !has_extensions(options)
|
51
|
+
|
52
|
+
if options[:defer]
|
53
|
+
placeholder = @deferment.handle(options)
|
54
|
+
commands.push([:push_value, placeholder])
|
55
|
+
@fragment.handle(options)
|
56
|
+
else
|
57
|
+
handle_cache(options) do
|
58
|
+
base.set_block_content! do
|
59
|
+
if options[:partial]
|
60
|
+
current_digest = @fragment.name
|
61
|
+
@fragment.handle(options)
|
62
|
+
@partialer.handle(options)
|
63
|
+
@fragment.name = current_digest
|
64
|
+
else
|
65
|
+
yield
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def content_for_cache(commands, deferred_paths, fragment_paths)
|
75
|
+
suffix = [
|
76
|
+
[:push_value, deferred_paths],
|
77
|
+
[:push_value, fragment_paths],
|
78
|
+
[:pop]
|
79
|
+
]
|
80
|
+
|
81
|
+
[[:push_array]] + commands + suffix
|
82
|
+
end
|
83
|
+
|
84
|
+
def handle_cache(options)
|
85
|
+
if options[:cache]
|
86
|
+
state = @cache.cache(*options[:cache]) do
|
87
|
+
commands = content_for_cache(*base.scoped_state {yield})
|
88
|
+
base.commands_to_json!(commands).strip
|
89
|
+
end
|
90
|
+
|
91
|
+
value, next_deferred, next_fragments = Oj.load(state)
|
92
|
+
base.commands.push([:push_value, value])
|
93
|
+
deferred.push(*next_deferred)
|
94
|
+
|
95
|
+
next_fragments.each do |k, v|
|
96
|
+
if fragments[k]
|
97
|
+
fragments[k].push(*v)
|
98
|
+
else
|
99
|
+
fragments[k] = v
|
100
|
+
end
|
101
|
+
end
|
102
|
+
else
|
103
|
+
yield
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|