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.
@@ -81,7 +81,7 @@ module Pakyow
81
81
  @context = context
82
82
 
83
83
  if @context.request.has_route_vars?
84
- @path = Utils::String.remove_route_vars(@context.request.route_path)
84
+ @path = String.remove_route_vars(@context.request.route_path)
85
85
  else
86
86
  @path = @context.request.path
87
87
  end
@@ -97,21 +97,15 @@ module Pakyow
97
97
 
98
98
  def content
99
99
  to_present = view
100
- view.is_a?(ViewComposer) ? view.composed.to_html : view.to_html
100
+ to_present.is_a?(ViewComposer) ? to_present.composed.to_html : to_present.to_html
101
101
  end
102
102
 
103
103
  def view
104
- view = @composer || @view
105
- raise MissingView if view.nil?
106
-
107
- view.context = @context
108
-
109
- return view
104
+ @composer || @view || raise(MissingView)
110
105
  end
111
106
 
112
107
  def view=(view)
113
108
  @view = view
114
- @view.context = @context
115
109
 
116
110
  # setting a view means we no longer use/need the composer
117
111
  @composer = nil
@@ -174,16 +168,23 @@ module Pakyow
174
168
  end
175
169
 
176
170
  def setup_for_path(path, explicit = false)
177
- @composer = store.composer(path)
178
- @path = path
179
- rescue MissingView => e # catches no view path error
171
+ @view_stores.each do |name, store|
172
+ begin
173
+ @composer = store.composer(path)
174
+ @path = path
175
+ return
176
+ rescue MissingView
177
+ end
178
+ end
179
+
180
+ e = MissingView.new("No view at path '#{path}'")
180
181
  explicit ? raise(e) : Pakyow.logger.debug(e.message)
181
182
  end
182
183
 
183
184
  def load_views
184
185
  @view_stores = {}
185
186
 
186
- Config::Presenter.view_stores.each_pair {|name, path|
187
+ Pakyow::Config.presenter.view_stores.each_pair {|name, path|
187
188
  @view_stores[name] = ViewStore.new(path, name)
188
189
  }
189
190
  end
@@ -0,0 +1,287 @@
1
+ module Pakyow
2
+ module Presenter
3
+ class StringDoc
4
+ attr_reader :structure
5
+
6
+ TITLE_REGEX = /<title>(.*?)<\/title>/m
7
+
8
+ def initialize(html)
9
+ @structure = StringDocParser.new(html).structure
10
+ end
11
+
12
+ def self.from_structure(structure, node: nil)
13
+ instance = allocate
14
+ instance.instance_variable_set(:@structure, structure)
15
+ instance.instance_variable_set(:@node, node)
16
+ return instance
17
+ end
18
+
19
+ def self.ensure(object)
20
+ return object if object.is_a?(StringDoc)
21
+ StringDoc.new(object)
22
+ end
23
+
24
+ def initialize_copy(original_doc)
25
+ super
26
+
27
+ original_structure = original_doc.instance_variable_get(:@structure)
28
+ @structure = Utils::Dup.deep(original_structure) if original_structure
29
+
30
+ original_node = original_doc.instance_variable_get(:@node)
31
+ if original_node
32
+ node_index = original_structure.index(original_node)
33
+ @node = @structure[node_index]
34
+ end
35
+ end
36
+
37
+ # Creates a StringDoc instance with the same structure, but a duped node.
38
+ #
39
+ def soft_copy
40
+ StringDoc.from_structure(@structure, node: @node ? Utils::Dup.deep(@node) : nil)
41
+ end
42
+
43
+ def title
44
+ title_search do |n, match|
45
+ return match[1]
46
+ end
47
+ end
48
+
49
+ def title=(title)
50
+ title_search do |n, match|
51
+ n.gsub!(TITLE_REGEX, "<title>#{title}</title>")
52
+ end
53
+ end
54
+
55
+ def set_attribute(name, value)
56
+ return if attributes.nil?
57
+ attributes[name.to_sym] = value
58
+ end
59
+ alias :update_attribute :set_attribute
60
+
61
+ def get_attribute(name)
62
+ attributes[name.to_sym]
63
+ end
64
+
65
+ def remove_attribute(name)
66
+ attributes.delete(name.to_sym)
67
+ end
68
+
69
+ def remove
70
+ @structure.delete_if { |n| n.equal?(node) }
71
+ end
72
+
73
+ def clear
74
+ children.clear
75
+ end
76
+
77
+ def text
78
+ html.gsub(/<[^>]*>/, '')
79
+ end
80
+
81
+ def text=(text)
82
+ clear
83
+ children << [text, {}, []]
84
+ end
85
+
86
+ def html
87
+ StringDocRenderer.render(children)
88
+ end
89
+
90
+ def html=(html)
91
+ clear
92
+ children << [html, {}, []]
93
+ end
94
+
95
+ def append(doc)
96
+ doc = StringDoc.ensure(doc)
97
+
98
+ if doc.node?
99
+ children.push(doc.node)
100
+ else
101
+ children.concat(doc.structure)
102
+ end
103
+ end
104
+
105
+ def prepend(doc)
106
+ doc = StringDoc.ensure(doc)
107
+
108
+ if doc.node?
109
+ children.unshift(doc.node)
110
+ else
111
+ children.unshift(*doc.structure)
112
+ end
113
+ end
114
+
115
+ def after(doc)
116
+ doc = StringDoc.ensure(doc)
117
+
118
+ if doc.node?
119
+ @structure.push(doc.node)
120
+ else
121
+ @structure.concat(doc.structure)
122
+ end
123
+ end
124
+
125
+ def before(doc)
126
+ doc = StringDoc.ensure(doc)
127
+
128
+ if doc.node?
129
+ @structure.unshift(doc.node)
130
+ else
131
+ @structure.unshift(*doc.structure)
132
+ end
133
+ end
134
+
135
+ def replace(doc)
136
+ doc = StringDoc.ensure(doc)
137
+ index = @structure.index(node) || 0
138
+
139
+ if doc.node?
140
+ @structure.insert(index + 1, node)
141
+ else
142
+ @structure.insert(index + 1, *doc.structure)
143
+ end
144
+
145
+ @structure.delete_at(index)
146
+ end
147
+
148
+ def scope(scope_name)
149
+ scopes.select { |b| b[:scope] == scope_name }
150
+ end
151
+
152
+ def prop(scope_name, prop_name)
153
+ return [] unless scope = scopes.select { |s| s[:scope] == scope_name }[0]
154
+ scope[:props].select { |p| p[:prop] == prop_name }
155
+ end
156
+
157
+ def container(name)
158
+ containers.fetch(name, {})[:doc]
159
+ end
160
+
161
+ def containers
162
+ find_containers(@node ? [@node] : @structure)
163
+ end
164
+
165
+ def partials
166
+ find_partials(@node ? [@node] : @structure)
167
+ end
168
+
169
+ def scopes
170
+ find_scopes(@node ? [@node] : @structure)
171
+ end
172
+
173
+ def to_html
174
+ StringDocRenderer.render(@node ? [@node] : @structure)
175
+ end
176
+ alias :to_s :to_html
177
+
178
+ def ==(o)
179
+ #TODO do this without rendering?
180
+ # (at least in the case of comparing StringDoc to StringDoc)
181
+ to_s == o.to_s
182
+ end
183
+
184
+ def node
185
+ return @structure if @structure.empty?
186
+ return @node || @structure[0]
187
+ end
188
+
189
+ def node?
190
+ !@node.nil?
191
+ end
192
+
193
+ def tagname
194
+ node[0].gsub(/[^a-zA-Z]/, '')
195
+ end
196
+
197
+ def option(value: nil)
198
+ StringDoc.from_structure(node[2][0][2].select { |option|
199
+ option[1][:value] == value.to_s
200
+ })
201
+ end
202
+
203
+ private
204
+
205
+ def title_search
206
+ @structure.flatten.each do |n|
207
+ next unless n.is_a?(String)
208
+ if match = n.match(TITLE_REGEX)
209
+ yield n, match
210
+ end
211
+ end
212
+ end
213
+
214
+ # Returns the structure representing the attributes for the node
215
+ #
216
+ def attributes
217
+ node[1]
218
+ end
219
+
220
+ def children
221
+ node[2][0][2]
222
+ end
223
+
224
+ def find_containers(structure, primary_structure = @structure, containers = {})
225
+ return {} if structure.empty?
226
+ structure.inject(containers) { |s, e|
227
+ if e[1].has_key?(:container)
228
+ s[e[1][:container]] = { doc: StringDoc.from_structure(primary_structure, node: e) }
229
+ end
230
+ find_containers(e[2], e[2], s)
231
+ s
232
+ } || {}
233
+ end
234
+
235
+ def find_partials(structure, primary_structure = @structure, partials = {})
236
+ structure.inject(partials) { |s, e|
237
+ if e[1].has_key?(:partial)
238
+ s[e[1][:partial]] = StringDoc.from_structure(primary_structure, node: e)
239
+ end
240
+ find_partials(e[2], e[2], s)
241
+ s
242
+ } || {}
243
+ end
244
+
245
+ def find_scopes(structure, primary_structure = @structure, scopes = [])
246
+ ret_scopes = structure.inject(scopes) { |s, e|
247
+ if e[1].has_key?(:'data-scope')
248
+ s << {
249
+ doc: StringDoc.from_structure(primary_structure, node: e),
250
+ scope: e[1][:'data-scope'].to_sym,
251
+ props: find_node_props(e).concat(find_props(e[2])),
252
+ nested: find_scopes(e[2]),
253
+ }
254
+ end
255
+ # only find scopes if `e` is the root node or we're not decending into a nested scope
256
+ find_scopes(e[2], e[2], s) if e == node || !e[1].has_key?(:'data-scope')
257
+ s
258
+ } || []
259
+
260
+ ret_scopes
261
+ end
262
+
263
+ def find_props(structure, primary_structure = @structure, props = [])
264
+ structure.each do |e|
265
+ find_node_props(e, primary_structure, props)
266
+ end
267
+
268
+ props || []
269
+ end
270
+
271
+ def find_node_props(node, primary_structure = @structure, props = [])
272
+ if node[1].has_key?(:'data-prop')
273
+ props << {
274
+ doc: StringDoc.from_structure(primary_structure, node: node),
275
+ prop: node[1][:'data-prop'].to_sym,
276
+ }
277
+ end
278
+
279
+ unless node[1].has_key?(:'data-scope')
280
+ find_props(node[2], node[2], props)
281
+ end
282
+
283
+ props
284
+ end
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,124 @@
1
+ module Pakyow
2
+ module Presenter
3
+ class StringDocParser
4
+ PARTIAL_REGEX = /<!--\s*@include\s*([a-zA-Z0-9\-_]*)\s*-->/
5
+ CONTAINER_REGEX = /@container( ([a-zA-Z0-9\-_]*))*/
6
+
7
+ def initialize(html)
8
+ @html = html
9
+ structure
10
+ end
11
+
12
+ def structure
13
+ @structure ||= parse(doc_from_string(@html))
14
+ end
15
+
16
+ private
17
+
18
+ # Parses HTML and returns a nested structure representing the document.
19
+ #
20
+ def parse(doc)
21
+ structure = []
22
+
23
+ if doc.is_a?(Nokogiri::HTML::Document)
24
+ structure << ['<!DOCTYPE html>', {}, []]
25
+ end
26
+
27
+ breadth_first(doc) do |node, queue|
28
+ if node == doc
29
+ queue.concat(node.children)
30
+ next
31
+ end
32
+
33
+ children = node.children.reject {|n| n.is_a?(Nokogiri::XML::Text)}
34
+ attributes = node.attributes
35
+ if children.empty? && !significant?(node)
36
+ structure << [node.to_html, {}, []]
37
+ else
38
+ if significant?(node)
39
+ if scope?(node) || prop?(node) || option?(node)
40
+ attr_structure = attributes.inject({}) do |attrs, attr|
41
+ attrs[attr[1].name.to_sym] = attr[1].value
42
+ attrs
43
+ end
44
+
45
+ closing = [['>', {}, parse(node)]]
46
+ closing << ["</#{node.name}>", {}, []] unless self_closing?(node.name)
47
+ structure << ["<#{node.name} ", attr_structure, closing]
48
+ elsif container?(node)
49
+ match = node.text.strip.match(CONTAINER_REGEX)
50
+ name = (match[2] || :default).to_sym
51
+ structure << [node.to_html, { container: name }, []]
52
+ elsif partial?(node)
53
+ next unless match = node.to_html.strip.match(PARTIAL_REGEX)
54
+ name = match[1].to_sym
55
+ structure << [node.to_html, { partial: name }, []]
56
+ end
57
+ else
58
+ attr_s = attributes.inject('') { |s, a| s << " #{a[1].name}=\"#{a[1].value}\""; s }
59
+ closing = [['>', {}, parse(node)]]
60
+ closing << ['</' + node.name + '>', {}, []] unless self_closing?(node.name)
61
+ structure << ['<' + node.name + attr_s, {}, closing]
62
+ end
63
+ end
64
+ end
65
+
66
+ return structure
67
+ end
68
+
69
+ def significant?(node)
70
+ scope?(node) || prop?(node) || container?(node) || partial?(node) || option?(node)
71
+ end
72
+
73
+ def scope?(node)
74
+ return false unless node['data-scope']
75
+ return true
76
+ end
77
+
78
+ def prop?(node)
79
+ return false unless node['data-prop']
80
+ return true
81
+ end
82
+
83
+ def container?(node)
84
+ return false unless node.is_a?(Nokogiri::XML::Comment)
85
+ return false unless node.text.strip.match(CONTAINER_REGEX)
86
+ return true
87
+ end
88
+
89
+ def partial?(node)
90
+ return false unless node.is_a?(Nokogiri::XML::Comment)
91
+ return false unless node.to_html.strip.match(PARTIAL_REGEX)
92
+ return true
93
+ end
94
+
95
+ def option?(node)
96
+ node.name == 'option'
97
+ end
98
+
99
+ def breadth_first(doc)
100
+ queue = [doc]
101
+ until queue.empty?
102
+ catch(:reject) do
103
+ node = queue.shift
104
+ yield node, queue
105
+ end
106
+ end
107
+ end
108
+
109
+ def doc_from_string(string)
110
+ if string.match(/<html.*>/)
111
+ Nokogiri::HTML::Document.parse(string)
112
+ else
113
+ Nokogiri::HTML.fragment(string)
114
+ end
115
+ end
116
+
117
+ SELF_CLOSING = %w[area base basefont br hr input img link meta]
118
+ def self_closing?(tag)
119
+ SELF_CLOSING.include? tag
120
+ end
121
+
122
+ end
123
+ end
124
+ end