props_template 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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