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.
@@ -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