pakyow-presenter 0.8.0 → 0.9.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/pakyow-presenter/CHANGES +11 -1
- data/pakyow-presenter/lib/presenter/attributes.rb +18 -15
- data/pakyow-presenter/lib/presenter/base.rb +8 -2
- data/pakyow-presenter/lib/presenter/binder.rb +53 -43
- data/pakyow-presenter/lib/presenter/binder_set.rb +18 -27
- data/pakyow-presenter/lib/presenter/binding_eval.rb +23 -0
- data/pakyow-presenter/lib/presenter/config/presenter.rb +36 -61
- data/pakyow-presenter/lib/presenter/doc_helpers.rb +6 -66
- data/pakyow-presenter/lib/presenter/helpers.rb +26 -3
- data/pakyow-presenter/lib/presenter/nokogiri_doc.rb +321 -0
- data/pakyow-presenter/lib/presenter/page.rb +5 -14
- data/pakyow-presenter/lib/presenter/presenter.rb +14 -13
- data/pakyow-presenter/lib/presenter/string_doc.rb +287 -0
- data/pakyow-presenter/lib/presenter/string_doc_parser.rb +124 -0
- data/pakyow-presenter/lib/presenter/string_doc_renderer.rb +18 -0
- data/pakyow-presenter/lib/presenter/template.rb +13 -37
- data/pakyow-presenter/lib/presenter/view.rb +192 -424
- data/pakyow-presenter/lib/presenter/view_collection.rb +70 -112
- data/pakyow-presenter/lib/presenter/view_composer.rb +2 -47
- data/pakyow-presenter/lib/presenter/view_context.rb +89 -0
- data/pakyow-presenter/lib/presenter/view_store.rb +12 -12
- metadata +30 -10
- data/pakyow-presenter/lib/presenter/title_helpers.rb +0 -21
@@ -3,8 +3,6 @@ module Pakyow
|
|
3
3
|
class ViewCollection
|
4
4
|
include Enumerable
|
5
5
|
|
6
|
-
attr_accessor :context, :composer
|
7
|
-
|
8
6
|
def initialize
|
9
7
|
@views = []
|
10
8
|
end
|
@@ -13,70 +11,60 @@ module Pakyow
|
|
13
11
|
@views.each { |v| yield(v) }
|
14
12
|
end
|
15
13
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
def attrs(attrs = {})
|
15
|
+
inject(AttributesCollection.new) { |coll, view|
|
16
|
+
coll << view.attrs(attrs)
|
17
|
+
}
|
20
18
|
end
|
21
19
|
|
22
|
-
alias :attrs :attributes
|
23
|
-
|
24
20
|
def remove
|
25
|
-
|
21
|
+
each {|e| e.remove}
|
26
22
|
end
|
27
23
|
|
28
|
-
alias :delete :remove
|
29
|
-
|
30
|
-
# SEE COMMENT IN VIEW
|
31
|
-
# def add_class(val)
|
32
|
-
# self.each {|e| e.add_class(val)}
|
33
|
-
# end
|
34
|
-
|
35
|
-
# SEE COMMENT IN VIEW
|
36
|
-
# def remove_class(val)
|
37
|
-
# self.each {|e| e.remove_class(val)}
|
38
|
-
# end
|
39
|
-
|
40
24
|
def clear
|
41
|
-
|
25
|
+
each {|e| e.clear}
|
42
26
|
end
|
43
27
|
|
44
28
|
def text
|
45
|
-
|
29
|
+
map { |v| v.text }
|
46
30
|
end
|
47
31
|
|
48
32
|
def text=(text)
|
49
|
-
|
33
|
+
each {|e| e.text = text}
|
50
34
|
end
|
51
35
|
|
52
36
|
def html
|
53
|
-
|
37
|
+
map { |v| v.html }
|
54
38
|
end
|
55
39
|
|
56
40
|
def html=(html)
|
57
|
-
|
41
|
+
each {|e| e.html = html}
|
58
42
|
end
|
59
43
|
|
60
44
|
def to_html
|
61
|
-
|
45
|
+
map { |v| v.to_html }.join('')
|
62
46
|
end
|
63
47
|
|
64
48
|
alias :to_s :to_html
|
65
49
|
|
66
50
|
def append(content)
|
67
|
-
|
51
|
+
each do |view|
|
52
|
+
view.append(content)
|
53
|
+
end
|
68
54
|
end
|
69
55
|
|
70
|
-
alias :render :append
|
71
|
-
|
72
56
|
def prepend(content)
|
73
|
-
|
57
|
+
each do |view|
|
58
|
+
view.prepend(content)
|
59
|
+
end
|
74
60
|
end
|
75
61
|
|
76
62
|
def <<(val)
|
77
63
|
if val.is_a? View
|
78
64
|
@views << val
|
79
65
|
end
|
66
|
+
|
67
|
+
self
|
80
68
|
end
|
81
69
|
|
82
70
|
def concat(views)
|
@@ -84,12 +72,7 @@ module Pakyow
|
|
84
72
|
end
|
85
73
|
|
86
74
|
def [](i)
|
87
|
-
|
88
|
-
return if view.nil?
|
89
|
-
|
90
|
-
view.context = @context
|
91
|
-
view.composer = @composer
|
92
|
-
return view
|
75
|
+
@views[i]
|
93
76
|
end
|
94
77
|
|
95
78
|
def length
|
@@ -97,35 +80,25 @@ module Pakyow
|
|
97
80
|
end
|
98
81
|
|
99
82
|
def scope(name)
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
sv.context = @context
|
107
|
-
sv.composer = @composer
|
108
|
-
views << sv
|
83
|
+
inject(ViewCollection.new) { |coll, view|
|
84
|
+
scopes = view.scope(name)
|
85
|
+
next if scopes.nil?
|
86
|
+
|
87
|
+
scopes.inject(coll) { |coll, scoped_view|
|
88
|
+
coll << scoped_view
|
109
89
|
}
|
110
90
|
}
|
111
|
-
|
112
|
-
views
|
113
91
|
end
|
114
92
|
|
115
93
|
def prop(name)
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
sv.context = @context
|
123
|
-
sv.composer = @composer
|
124
|
-
views << sv
|
94
|
+
inject(ViewCollection.new) { |coll, view|
|
95
|
+
scopes = view.prop(name)
|
96
|
+
next if scopes.nil?
|
97
|
+
|
98
|
+
scopes.inject(coll) { |coll, scoped_view|
|
99
|
+
coll << scoped_view
|
125
100
|
}
|
126
101
|
}
|
127
|
-
|
128
|
-
views
|
129
102
|
end
|
130
103
|
|
131
104
|
# call-seq:
|
@@ -135,7 +108,7 @@ module Pakyow
|
|
135
108
|
#
|
136
109
|
def with(&block)
|
137
110
|
if block.arity == 0
|
138
|
-
|
111
|
+
instance_exec(&block)
|
139
112
|
else
|
140
113
|
yield(self)
|
141
114
|
end
|
@@ -153,18 +126,17 @@ module Pakyow
|
|
153
126
|
# (this is basically Bret's `map` function)
|
154
127
|
#
|
155
128
|
def for(data, &block)
|
156
|
-
data =
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
break unless datum = data[i]
|
129
|
+
data = Array.ensure(data)
|
130
|
+
each_with_index do |view, i|
|
131
|
+
datum = data[i]
|
132
|
+
break if datum.nil?
|
161
133
|
|
162
134
|
if block.arity == 1
|
163
|
-
|
135
|
+
view.instance_exec(data[i], &block)
|
164
136
|
else
|
165
|
-
block.call(
|
137
|
+
block.call(view, data[i])
|
166
138
|
end
|
167
|
-
|
139
|
+
end
|
168
140
|
end
|
169
141
|
|
170
142
|
# call-seq:
|
@@ -190,46 +162,32 @@ module Pakyow
|
|
190
162
|
# call-seq:
|
191
163
|
# match(data) => ViewCollection
|
192
164
|
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
# of self[data index] || self[-1], where n = data.length.
|
165
|
+
# Manipulates the current collection to match the data. The final ViewCollection object
|
166
|
+
# will consist n copies of self[data index] || self[-1], where n = data.length.
|
196
167
|
#
|
197
168
|
def match(data)
|
198
|
-
data =
|
199
|
-
data = [data] if (!data.is_a?(Enumerable) || data.is_a?(Hash))
|
169
|
+
data = Array.ensure(data)
|
200
170
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
171
|
+
# an empty set always means an empty view
|
172
|
+
if data.empty?
|
173
|
+
remove
|
174
|
+
else
|
175
|
+
original_view = self[-1].soft_copy if data.length > length
|
206
176
|
|
207
|
-
|
208
|
-
|
177
|
+
if length > data.length
|
178
|
+
self[data.length..-1].each do |view|
|
179
|
+
view.remove
|
180
|
+
end
|
181
|
+
else
|
182
|
+
data[length..-1].each do
|
183
|
+
duped_view = original_view.soft_copy
|
184
|
+
self[-1].after(duped_view)
|
185
|
+
self << duped_view
|
186
|
+
end
|
209
187
|
end
|
188
|
+
end
|
210
189
|
|
211
|
-
|
212
|
-
v.doc.before(d_v)
|
213
|
-
v.invalidate!(true)
|
214
|
-
|
215
|
-
new_v = View.from_doc(d_v)
|
216
|
-
|
217
|
-
# find binding subset (keeps us from refinding)
|
218
|
-
new_v.bindings = v.bindings.dup
|
219
|
-
new_v.scoped_as = v.scoped_as
|
220
|
-
new_v.context = @context
|
221
|
-
new_v.composer = @composer
|
222
|
-
|
223
|
-
views << new_v
|
224
|
-
}
|
225
|
-
|
226
|
-
# do not use self.remove since that refinds bindings
|
227
|
-
self.each {|v|
|
228
|
-
next if v.doc.parent.nil?
|
229
|
-
v.doc.remove
|
230
|
-
}
|
231
|
-
|
232
|
-
views
|
190
|
+
self
|
233
191
|
end
|
234
192
|
|
235
193
|
# call-seq:
|
@@ -238,7 +196,7 @@ module Pakyow
|
|
238
196
|
# Matches self to data and yields a view/datum pair.
|
239
197
|
#
|
240
198
|
def repeat(data, &block)
|
241
|
-
|
199
|
+
match(data).for(data, &block)
|
242
200
|
end
|
243
201
|
|
244
202
|
# call-seq:
|
@@ -247,7 +205,7 @@ module Pakyow
|
|
247
205
|
# Matches self with data and yields a view/datum pair with index.
|
248
206
|
#
|
249
207
|
def repeat_with_index(data, &block)
|
250
|
-
|
208
|
+
match(data).for_with_index(data, &block)
|
251
209
|
end
|
252
210
|
|
253
211
|
# call-seq:
|
@@ -255,9 +213,9 @@ module Pakyow
|
|
255
213
|
#
|
256
214
|
# Binds data across existing scopes.
|
257
215
|
#
|
258
|
-
def bind(data, bindings
|
259
|
-
self.for(data)
|
260
|
-
view.bind(datum, bindings)
|
216
|
+
def bind(data, bindings: {}, context: nil, &block)
|
217
|
+
self.for(data) do |view, datum|
|
218
|
+
view.bind(datum, bindings: bindings, context: context)
|
261
219
|
next if block.nil?
|
262
220
|
|
263
221
|
if block.arity == 1
|
@@ -265,7 +223,7 @@ module Pakyow
|
|
265
223
|
else
|
266
224
|
block.call(view, datum)
|
267
225
|
end
|
268
|
-
|
226
|
+
end
|
269
227
|
end
|
270
228
|
|
271
229
|
# call-seq:
|
@@ -273,9 +231,9 @@ module Pakyow
|
|
273
231
|
#
|
274
232
|
# Binds data across existing scopes, yielding a view/datum pair with index.
|
275
233
|
#
|
276
|
-
def bind_with_index(
|
234
|
+
def bind_with_index(*a, **k, &block)
|
277
235
|
i = 0
|
278
|
-
|
236
|
+
bind(*a, **k) do |ctx, datum|
|
279
237
|
if block.arity == 2
|
280
238
|
ctx.instance_exec(datum, i, &block)
|
281
239
|
else
|
@@ -291,8 +249,8 @@ module Pakyow
|
|
291
249
|
#
|
292
250
|
# Matches self to data then binds data to the view.
|
293
251
|
#
|
294
|
-
def apply(data, bindings
|
295
|
-
|
252
|
+
def apply(data, bindings: {}, context: nil, &block)
|
253
|
+
match(data).bind(data, bindings: bindings, context: context, &block)
|
296
254
|
end
|
297
255
|
end
|
298
256
|
end
|
@@ -12,7 +12,6 @@ module Pakyow
|
|
12
12
|
def_delegators :template, :title, :title=
|
13
13
|
def_delegators :parts, :scope, :prop
|
14
14
|
|
15
|
-
attr_accessor :context
|
16
15
|
attr_reader :store, :path, :page, :partials
|
17
16
|
|
18
17
|
def initialize(store, path = nil, opts = {}, &block)
|
@@ -35,10 +34,6 @@ module Pakyow
|
|
35
34
|
@partials = store.partials(path) unless path.nil?
|
36
35
|
end
|
37
36
|
|
38
|
-
@partials.each do |name, partial|
|
39
|
-
partial.composer = self
|
40
|
-
end
|
41
|
-
|
42
37
|
instance_exec(&block) if block_given?
|
43
38
|
end
|
44
39
|
|
@@ -47,6 +42,7 @@ module Pakyow
|
|
47
42
|
|
48
43
|
%w[store path page template partials view].each do |ivar|
|
49
44
|
value = original.instance_variable_get("@#{ivar}")
|
45
|
+
next if value.nil?
|
50
46
|
|
51
47
|
if value.is_a?(Hash)
|
52
48
|
dup_value = {}
|
@@ -57,34 +53,15 @@ module Pakyow
|
|
57
53
|
|
58
54
|
self.instance_variable_set("@#{ivar}", dup_value)
|
59
55
|
end
|
60
|
-
|
61
|
-
# update composer reference for partials
|
62
|
-
@partials.each do |name, partial|
|
63
|
-
partial.composer = self
|
64
|
-
end
|
65
|
-
|
66
|
-
# update composer reference for page
|
67
|
-
@page.composer = self
|
68
|
-
end
|
69
|
-
|
70
|
-
def precompose!
|
71
|
-
@view = build_view
|
72
|
-
clean!
|
73
56
|
end
|
74
57
|
|
75
58
|
def view
|
76
|
-
|
77
|
-
@view = build_view
|
78
|
-
clean!
|
79
|
-
end
|
80
|
-
|
81
|
-
return @view
|
59
|
+
build_view
|
82
60
|
end
|
83
61
|
alias_method :composed, :view
|
84
62
|
|
85
63
|
def template(template = nil)
|
86
64
|
if template.nil?
|
87
|
-
@template.context = context
|
88
65
|
return @template
|
89
66
|
end
|
90
67
|
|
@@ -99,7 +76,6 @@ module Pakyow
|
|
99
76
|
end
|
100
77
|
|
101
78
|
@template = template
|
102
|
-
dirty!
|
103
79
|
|
104
80
|
return self
|
105
81
|
end
|
@@ -110,48 +86,31 @@ module Pakyow
|
|
110
86
|
page = @store.page(page)
|
111
87
|
end
|
112
88
|
|
113
|
-
page.composer = self
|
114
89
|
@page = page
|
115
|
-
dirty!
|
116
90
|
|
117
91
|
return self
|
118
92
|
end
|
119
93
|
|
120
94
|
def includes(partial_map)
|
121
|
-
dirty!
|
122
|
-
|
123
95
|
@partials.merge!(remap_partials(partial_map))
|
124
96
|
end
|
125
97
|
|
126
98
|
def partials=(partial_map)
|
127
|
-
dirty!
|
128
99
|
@partials.merge!(remap_partials(partial_map))
|
129
100
|
end
|
130
101
|
|
131
102
|
def partial(name)
|
132
103
|
partial = @partials[name]
|
133
|
-
partial.context = context
|
134
104
|
return partial
|
135
105
|
end
|
136
106
|
|
137
107
|
def container(name)
|
138
108
|
container = @page.container(name)
|
139
|
-
container.context = context
|
140
109
|
return container
|
141
110
|
end
|
142
111
|
|
143
|
-
def dirty?
|
144
|
-
@dirty
|
145
|
-
end
|
146
|
-
|
147
|
-
def dirty!
|
148
|
-
@dirty = true
|
149
|
-
end
|
150
|
-
|
151
112
|
def parts
|
152
113
|
parts = ViewCollection.new
|
153
|
-
parts.context = @context
|
154
|
-
parts.composer = self
|
155
114
|
parts << @template
|
156
115
|
@page.each_container do |name, container| parts << container end
|
157
116
|
partials.each_pair do |name, partial| parts << partial end
|
@@ -160,10 +119,6 @@ module Pakyow
|
|
160
119
|
|
161
120
|
private
|
162
121
|
|
163
|
-
def clean!
|
164
|
-
@dirty = false
|
165
|
-
end
|
166
|
-
|
167
122
|
def build_view
|
168
123
|
raise MissingTemplate, "No template provided to view composer" if @template.nil?
|
169
124
|
raise MissingPage, "No page provided to view composer" if @page.nil?
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Pakyow
|
2
|
+
module Presenter
|
3
|
+
# This is a wrapper for View / ViewCollection that passes the current
|
4
|
+
# AppContext to all binding methods. This object is expected to only
|
5
|
+
# be used from the app, not internally.
|
6
|
+
#
|
7
|
+
class ViewContext
|
8
|
+
include Helpers
|
9
|
+
VIEW_CLASSES = [View, ViewCollection, Partial, Template, Container]
|
10
|
+
|
11
|
+
# The arities of misc view methods that switch the behavior from
|
12
|
+
# instance_exec to yield.
|
13
|
+
#
|
14
|
+
EXEC_ARITIES = { with: 0, for: 1, for_with_index: 2, repeat: 1,
|
15
|
+
repeat_with_index: 2, bind: 1, bind_with_index: 2, apply: 1 }
|
16
|
+
|
17
|
+
def initialize(view, context)
|
18
|
+
@view = view
|
19
|
+
@context = context
|
20
|
+
end
|
21
|
+
|
22
|
+
# View methods that expect context, so it can be mixed in.
|
23
|
+
#
|
24
|
+
%i[bind bind_with_index apply].each do |method|
|
25
|
+
define_method(method) do |data, **kargs, &block|
|
26
|
+
kargs[:context] ||= @context
|
27
|
+
ret = @view.send(method, data, **kargs, &wrap(method, &block))
|
28
|
+
handle_return_value(ret)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# View methods that return views, but don't expect context.
|
33
|
+
#
|
34
|
+
%i[with for for_with_index repeat repeat_with_index].each do |method|
|
35
|
+
define_method(method) do |*args, &block|
|
36
|
+
ret = @view.send(method, *args, &wrap(method, &block))
|
37
|
+
handle_return_value(ret)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Pass these through, handling the return value.
|
42
|
+
#
|
43
|
+
def method_missing(method, *args, &block)
|
44
|
+
ret = @view.send(method, *args, &block)
|
45
|
+
handle_return_value(ret)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def view?(obj)
|
51
|
+
VIEW_CLASSES.include?(obj.class)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns a new context for returned views, or the return value.
|
55
|
+
#
|
56
|
+
def handle_return_value(value)
|
57
|
+
if view?(value)
|
58
|
+
return ViewContext.new(value, @context)
|
59
|
+
end
|
60
|
+
|
61
|
+
value
|
62
|
+
end
|
63
|
+
|
64
|
+
# Wrap the block, substituting the view with the current view context.
|
65
|
+
#
|
66
|
+
def wrap(method, &block)
|
67
|
+
return if block.nil?
|
68
|
+
|
69
|
+
Proc.new do |*args|
|
70
|
+
ctx = args.map! { |arg|
|
71
|
+
view?(arg) ? ViewContext.new(arg, @context) : arg
|
72
|
+
}.find { |arg| arg.is_a?(ViewContext) }
|
73
|
+
|
74
|
+
case block.arity
|
75
|
+
when EXEC_ARITIES[method]
|
76
|
+
# Rejecting ViewContext handles the edge cases around the order of
|
77
|
+
# arguments from view methods (since view is not present in some
|
78
|
+
# situations and when it is present, is always the first arg).
|
79
|
+
ctx.instance_exec(*args.reject { |arg|
|
80
|
+
arg.is_a?(ViewContext)
|
81
|
+
}, &block)
|
82
|
+
else
|
83
|
+
block.call(*args)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|