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
@@ -1,76 +1,16 @@
|
|
1
1
|
module Pakyow
|
2
2
|
module Presenter
|
3
3
|
module DocHelpers
|
4
|
-
def
|
5
|
-
|
6
|
-
until queue.empty?
|
7
|
-
node = queue.shift
|
8
|
-
catch(:reject) {
|
9
|
-
yield node
|
10
|
-
queue.concat(node.children)
|
11
|
-
}
|
12
|
-
end
|
4
|
+
def self.self_closing_tag?(tag)
|
5
|
+
%w[area base basefont br hr input img link meta].include? tag
|
13
6
|
end
|
14
7
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
return path if child == @doc
|
19
|
-
|
20
|
-
child.ancestors.each {|a|
|
21
|
-
# since ancestors goes all the way to doc root, stop when we get to the level of @doc
|
22
|
-
break if a.children.include?(@doc)
|
23
|
-
|
24
|
-
path.unshift(a.children.index(child))
|
25
|
-
child = a
|
26
|
-
}
|
27
|
-
|
28
|
-
return path
|
29
|
-
end
|
30
|
-
|
31
|
-
def path_within_path?(child_path, parent_path)
|
32
|
-
parent_path.each_with_index {|pp_step, i|
|
33
|
-
return false unless pp_step == child_path[i]
|
34
|
-
}
|
35
|
-
|
36
|
-
true
|
8
|
+
def self.form_field?(tag)
|
9
|
+
%w[input select textarea button].include? tag
|
37
10
|
end
|
38
11
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
# if path is empty we're at self
|
43
|
-
return o if path.empty?
|
44
|
-
|
45
|
-
path.each {|i|
|
46
|
-
if child = o.children[i]
|
47
|
-
o = child
|
48
|
-
else
|
49
|
-
break
|
50
|
-
end
|
51
|
-
}
|
52
|
-
|
53
|
-
return o
|
54
|
-
end
|
55
|
-
|
56
|
-
def view_from_path(path)
|
57
|
-
view = View.from_doc(doc_from_path(path))
|
58
|
-
view.related_views << self
|
59
|
-
|
60
|
-
# workaround for nokogiri in jruby (see https://github.com/sparklemotion/nokogiri/issues/1060)
|
61
|
-
view.doc.document.errors = []
|
62
|
-
|
63
|
-
return view
|
64
|
-
end
|
65
|
-
|
66
|
-
def to_html
|
67
|
-
@doc.to_html
|
68
|
-
end
|
69
|
-
|
70
|
-
alias :to_s :to_html
|
71
|
-
|
72
|
-
def ==(o)
|
73
|
-
self.class == o.class && self.to_html == o.to_html
|
12
|
+
def self.tag_without_value?(tag)
|
13
|
+
%w[select].include? tag
|
74
14
|
end
|
75
15
|
end
|
76
16
|
end
|
@@ -1,10 +1,33 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
1
3
|
module Pakyow
|
4
|
+
module Helpers; end
|
5
|
+
|
2
6
|
module AppHelpers
|
3
7
|
extend Forwardable
|
4
8
|
|
5
|
-
def_delegators :@presenter, :store, :store=, :content, :view
|
6
|
-
:
|
7
|
-
|
9
|
+
def_delegators :@presenter, :store, :store=, :content, :view=,
|
10
|
+
:template=, :page=, :path, :path=, :compose, :composer
|
11
|
+
|
12
|
+
def view
|
13
|
+
ViewContext.new(@presenter.view, context)
|
14
|
+
end
|
15
|
+
|
16
|
+
def partial(*args)
|
17
|
+
ViewContext.new(@presenter.partial(*args), context)
|
18
|
+
end
|
19
|
+
|
20
|
+
def template
|
21
|
+
ViewContext.new(@presenter.template, context)
|
22
|
+
end
|
23
|
+
|
24
|
+
def page
|
25
|
+
ViewContext.new(@presenter.page, context)
|
26
|
+
end
|
27
|
+
|
28
|
+
def container(*args)
|
29
|
+
ViewContext.new(@presenter.container(*args), context)
|
30
|
+
end
|
8
31
|
|
9
32
|
def presenter
|
10
33
|
@presenter
|
@@ -0,0 +1,321 @@
|
|
1
|
+
module Pakyow
|
2
|
+
module Presenter
|
3
|
+
class NokogiriDoc
|
4
|
+
PARTIAL_REGEX = /<!--\s*@include\s*([a-zA-Z0-9\-_]*)\s*-->/
|
5
|
+
|
6
|
+
attr_accessor :doc, :scopes
|
7
|
+
|
8
|
+
def breadth_first(doc)
|
9
|
+
queue = [doc]
|
10
|
+
until queue.empty?
|
11
|
+
node = queue.shift
|
12
|
+
catch(:reject) {
|
13
|
+
yield node
|
14
|
+
queue.concat(node.children)
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def path_to(child)
|
20
|
+
path = []
|
21
|
+
|
22
|
+
return path if child == @doc
|
23
|
+
|
24
|
+
child.ancestors.each {|a|
|
25
|
+
# since ancestors goes all the way to doc root, stop when we get to the level of @doc
|
26
|
+
break if a.children.include?(@doc)
|
27
|
+
|
28
|
+
path.unshift(a.children.index(child))
|
29
|
+
child = a
|
30
|
+
}
|
31
|
+
|
32
|
+
return path
|
33
|
+
end
|
34
|
+
|
35
|
+
def path_within_path?(child_path, parent_path)
|
36
|
+
parent_path.each_with_index {|pp_step, i|
|
37
|
+
return false unless pp_step == child_path[i]
|
38
|
+
}
|
39
|
+
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
def doc_from_path(path)
|
44
|
+
o = @doc
|
45
|
+
|
46
|
+
# if path is empty we're at self
|
47
|
+
return o if path.empty?
|
48
|
+
|
49
|
+
path.each {|i|
|
50
|
+
if child = o.children[i]
|
51
|
+
o = child
|
52
|
+
else
|
53
|
+
break
|
54
|
+
end
|
55
|
+
}
|
56
|
+
|
57
|
+
return o
|
58
|
+
end
|
59
|
+
|
60
|
+
def view_from_path(path)
|
61
|
+
view = View.from_doc(doc_from_path(path))
|
62
|
+
view.related_views << self
|
63
|
+
|
64
|
+
# workaround for nokogiri in jruby (see https://github.com/sparklemotion/nokogiri/issues/1060)
|
65
|
+
view.doc.document.errors = []
|
66
|
+
|
67
|
+
return view
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_html
|
71
|
+
@doc.to_html
|
72
|
+
end
|
73
|
+
|
74
|
+
alias :to_s :to_html
|
75
|
+
|
76
|
+
def title=(title)
|
77
|
+
return if @doc.nil?
|
78
|
+
|
79
|
+
if o = @doc.css('title').first
|
80
|
+
o.inner_html = Nokogiri::HTML::fragment(title.to_s)
|
81
|
+
elsif o = @doc.css('head').first
|
82
|
+
o.add_child(Nokogiri::HTML::fragment("<title>#{title}</title>"))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def title
|
87
|
+
return unless o = @doc.css('title').first
|
88
|
+
o.inner_text
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.from_doc(doc)
|
92
|
+
instance = allocate
|
93
|
+
instance.doc = doc
|
94
|
+
return instance
|
95
|
+
end
|
96
|
+
|
97
|
+
def initialize(html)
|
98
|
+
if html.match(/<html.*>/)
|
99
|
+
@doc = Nokogiri::HTML::Document.parse(html)
|
100
|
+
else
|
101
|
+
@doc = Nokogiri::HTML.fragment(html)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def initialize_copy(original_doc)
|
106
|
+
super
|
107
|
+
|
108
|
+
# this solves a memory leak that I believe is being
|
109
|
+
# caused by Nokogiri; don't like this approach much
|
110
|
+
# since it negatively affects performance
|
111
|
+
#
|
112
|
+
# https://gist.github.com/bryanp/2048ae4a38f94c9d97ef
|
113
|
+
if original_doc.is_a?(Nokogiri::HTML::DocumentFragment)
|
114
|
+
@doc = Nokogiri::HTML.fragment(original_doc.doc.to_html)
|
115
|
+
else
|
116
|
+
@doc = original_doc.doc.dup
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def soft_copy
|
121
|
+
dup
|
122
|
+
end
|
123
|
+
|
124
|
+
def set_attribute(name, value)
|
125
|
+
@doc[name.to_s] = value
|
126
|
+
end
|
127
|
+
|
128
|
+
def get_attribute(name)
|
129
|
+
@doc[name.to_s]
|
130
|
+
end
|
131
|
+
|
132
|
+
def remove_attribute(name)
|
133
|
+
@doc.remove_attribute(name.to_s)
|
134
|
+
end
|
135
|
+
|
136
|
+
def update_attribute(name, value)
|
137
|
+
if !@doc.is_a?(Nokogiri::XML::Element) && @doc.children.count == 1
|
138
|
+
@doc.children.first[name] = value
|
139
|
+
else
|
140
|
+
@doc.set_attribute(name, value)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def remove
|
145
|
+
if @doc.parent.nil?
|
146
|
+
# best we can do is to remove the children
|
147
|
+
@doc.children.remove
|
148
|
+
else
|
149
|
+
@doc.remove
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def clear
|
154
|
+
return if @doc.blank?
|
155
|
+
@doc.inner_html = ''
|
156
|
+
end
|
157
|
+
|
158
|
+
def text
|
159
|
+
@doc.inner_text
|
160
|
+
end
|
161
|
+
|
162
|
+
def text=(text)
|
163
|
+
@doc.content = text.to_s
|
164
|
+
end
|
165
|
+
|
166
|
+
def html
|
167
|
+
@doc.inner_html
|
168
|
+
end
|
169
|
+
|
170
|
+
def html=(html)
|
171
|
+
@doc.inner_html = Nokogiri::HTML.fragment(html.to_s)
|
172
|
+
end
|
173
|
+
|
174
|
+
def append(appendable)
|
175
|
+
content = appendable.is_a?(NokogiriDoc) ? appendable.doc : appendable
|
176
|
+
@doc.add_child(content)
|
177
|
+
end
|
178
|
+
|
179
|
+
def prepend(prependable)
|
180
|
+
content = prependable.is_a?(NokogiriDoc) ? prependable.doc : prependable
|
181
|
+
if first_child = @doc.children.first
|
182
|
+
first_child.add_previous_sibling(content)
|
183
|
+
else
|
184
|
+
@doc = content
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def after(insertable)
|
189
|
+
content = insertable.is_a?(NokogiriDoc) ? insertable.doc : insertable
|
190
|
+
@doc.after(content)
|
191
|
+
end
|
192
|
+
|
193
|
+
def before(insertable)
|
194
|
+
content = insertable.is_a?(NokogiriDoc) ? insertable.doc : insertable
|
195
|
+
@doc.before(content)
|
196
|
+
end
|
197
|
+
|
198
|
+
def replace(replacement)
|
199
|
+
content = replacement.is_a?(NokogiriDoc) ? replacement.doc : replacement
|
200
|
+
|
201
|
+
if @doc.parent.nil?
|
202
|
+
@doc.children.remove
|
203
|
+
@doc.inner_html = content
|
204
|
+
else
|
205
|
+
@doc.replace(content)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def scope(name)
|
210
|
+
scopes.select { |b| b[:scope] == name }
|
211
|
+
end
|
212
|
+
|
213
|
+
def prop(scope_name, prop_name)
|
214
|
+
return [] unless scope = scopes.select { |b| b[:scope] == scope_name }[0]
|
215
|
+
scope[:props].select { |b| b[:prop] == prop_name }
|
216
|
+
end
|
217
|
+
|
218
|
+
def container(name)
|
219
|
+
container = @containers[name.to_sym]
|
220
|
+
return container[:doc]
|
221
|
+
end
|
222
|
+
|
223
|
+
def scopes
|
224
|
+
find_scopes
|
225
|
+
end
|
226
|
+
|
227
|
+
def containers
|
228
|
+
find_containers
|
229
|
+
end
|
230
|
+
|
231
|
+
def partials
|
232
|
+
find_partials
|
233
|
+
end
|
234
|
+
|
235
|
+
def ==(o)
|
236
|
+
to_html == o.to_html
|
237
|
+
end
|
238
|
+
|
239
|
+
def tagname
|
240
|
+
@doc.name
|
241
|
+
end
|
242
|
+
|
243
|
+
def option(value: nil)
|
244
|
+
@doc.css('option[value="' + value.to_s + '"]').first
|
245
|
+
end
|
246
|
+
|
247
|
+
private
|
248
|
+
|
249
|
+
# returns an array of hashes that describe each scope
|
250
|
+
def find_scopes(doc = @doc, ignore_root = false)
|
251
|
+
scopes = []
|
252
|
+
breadth_first(doc) {|o|
|
253
|
+
next if o == doc && ignore_root
|
254
|
+
next if !scope = o[Config.presenter.scope_attribute]
|
255
|
+
|
256
|
+
scopes << {
|
257
|
+
:doc => NokogiriDoc.from_doc(o),
|
258
|
+
:scope => scope.to_sym,
|
259
|
+
:props => find_props(o)
|
260
|
+
}
|
261
|
+
|
262
|
+
if o == doc
|
263
|
+
# this is the root node, which we need as the first hash in the
|
264
|
+
# list of scopes, but we don't want to nest other scopes inside
|
265
|
+
# of it in this case
|
266
|
+
scopes.last[:nested] = []
|
267
|
+
else
|
268
|
+
scopes.last[:nested] = find_scopes(o, true)
|
269
|
+
# reject so children aren't traversed
|
270
|
+
throw :reject
|
271
|
+
end
|
272
|
+
}
|
273
|
+
|
274
|
+
return scopes
|
275
|
+
end
|
276
|
+
|
277
|
+
# returns an array of hashes, each with the container name and doc
|
278
|
+
def find_containers
|
279
|
+
containers = {}
|
280
|
+
|
281
|
+
@doc.traverse {|e|
|
282
|
+
next unless e.is_a?(Nokogiri::XML::Comment)
|
283
|
+
next unless match = e.text.strip.match(/@container( ([a-zA-Z0-9\-_]*))*/)
|
284
|
+
name = match[2] || :default
|
285
|
+
|
286
|
+
containers[name.to_sym] = { doc: NokogiriDoc.from_doc(e) }
|
287
|
+
}
|
288
|
+
|
289
|
+
return containers
|
290
|
+
end
|
291
|
+
|
292
|
+
def find_props(o)
|
293
|
+
props = []
|
294
|
+
breadth_first(o) {|so|
|
295
|
+
# don't go into deeper scopes
|
296
|
+
throw :reject if so != o && so[Config.presenter.scope_attribute]
|
297
|
+
|
298
|
+
next unless prop = so[Config.presenter.prop_attribute]
|
299
|
+
props << { :prop => prop.to_sym, :doc => NokogiriDoc.from_doc(so) }
|
300
|
+
}
|
301
|
+
|
302
|
+
return props
|
303
|
+
end
|
304
|
+
|
305
|
+
def find_partials
|
306
|
+
partials = {}
|
307
|
+
|
308
|
+
@doc.traverse { |e|
|
309
|
+
next unless e.is_a?(Nokogiri::XML::Comment)
|
310
|
+
next unless match = e.to_html.strip.match(PARTIAL_REGEX)
|
311
|
+
|
312
|
+
name = match[1]
|
313
|
+
partials[name.to_sym] = NokogiriDoc.from_doc(e)
|
314
|
+
}
|
315
|
+
|
316
|
+
return partials
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
@@ -5,7 +5,7 @@ module Pakyow
|
|
5
5
|
|
6
6
|
class << self
|
7
7
|
def load(path)
|
8
|
-
format =
|
8
|
+
format = String.split_at_last_dot(path)[-1]
|
9
9
|
name = File.basename(path, '.*').to_sym
|
10
10
|
contents = FileTest.file?(path) ? File.read(path) : nil
|
11
11
|
|
@@ -14,7 +14,6 @@ module Pakyow
|
|
14
14
|
end
|
15
15
|
|
16
16
|
attr_reader :path, :contents
|
17
|
-
attr_accessor :composer
|
18
17
|
|
19
18
|
def initialize(name, contents, path, format = :html)
|
20
19
|
@name, @contents, @path, @format = name, contents, path, format
|
@@ -45,7 +44,7 @@ module Pakyow
|
|
45
44
|
raise MissingContainer, "No container named #{container} in #{@path}"
|
46
45
|
}
|
47
46
|
|
48
|
-
return container.
|
47
|
+
return container.doc
|
49
48
|
end
|
50
49
|
|
51
50
|
def info(key = nil)
|
@@ -67,21 +66,13 @@ module Pakyow
|
|
67
66
|
@containers.each_pair { |name, container| yield(name, container) }
|
68
67
|
end
|
69
68
|
|
70
|
-
def composer=(composer)
|
71
|
-
@composer = composer
|
72
|
-
|
73
|
-
@containers.each do |name, container|
|
74
|
-
container.composer = composer
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
69
|
private
|
79
70
|
|
80
71
|
def parse_info
|
81
72
|
info = parse_front_matter(@contents)
|
82
73
|
info = {} if !info || !info.is_a?(Hash)
|
83
74
|
|
84
|
-
@info.merge!(
|
75
|
+
@info.merge!(Hash.symbolize(info))
|
85
76
|
end
|
86
77
|
|
87
78
|
def parse_content
|
@@ -96,11 +87,11 @@ module Pakyow
|
|
96
87
|
|
97
88
|
@contents.scan(within_regex) do |m|
|
98
89
|
container_name = m[0].to_sym
|
99
|
-
@containers[container_name] = Container.new(m[1]
|
90
|
+
@containers[container_name] = Container.new(m[1])
|
100
91
|
end
|
101
92
|
|
102
93
|
# find default content
|
103
|
-
@containers[:default] = Container.new(@contents.gsub(within_regex, '')
|
94
|
+
@containers[:default] = Container.new(@contents.gsub(within_regex, ''))
|
104
95
|
end
|
105
96
|
|
106
97
|
def parse_front_matter(contents)
|