pakyow-presenter 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|