props_template 0.13.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 +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
|