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