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.
@@ -1,76 +1,16 @@
1
1
  module Pakyow
2
2
  module Presenter
3
3
  module DocHelpers
4
- def breadth_first(doc)
5
- queue = [doc]
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 path_to(child)
16
- path = []
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 doc_from_path(path)
40
- o = @doc
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, :view=,
6
- :partial, :template, :template=, :page, :page=, :path, :path=, :compose,
7
- :composer, :container
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 = Utils::String.split_at_last_dot(path)[-1]
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.to_html
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!(Utils::Hash.symbolize(info))
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], @format)
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, ''), @format)
94
+ @containers[:default] = Container.new(@contents.gsub(within_regex, ''))
104
95
  end
105
96
 
106
97
  def parse_front_matter(contents)