pakyow-presenter 0.8.rc4 → 0.8.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.
@@ -3,6 +3,8 @@ module Pakyow
3
3
  class ViewCollection
4
4
  include Enumerable
5
5
 
6
+ attr_accessor :context, :composer
7
+
6
8
  def initialize
7
9
  @views = []
8
10
  end
@@ -67,14 +69,27 @@ module Pakyow
67
69
 
68
70
  alias :render :append
69
71
 
72
+ def prepend(content)
73
+ self.each {|e| e.prepend(content)}
74
+ end
75
+
70
76
  def <<(val)
71
77
  if val.is_a? View
72
78
  @views << val
73
79
  end
74
80
  end
75
81
 
82
+ def concat(views)
83
+ @views.concat(views)
84
+ end
85
+
76
86
  def [](i)
77
- @views[i]
87
+ view = @views[i]
88
+ return if view.nil?
89
+
90
+ view.context = @context
91
+ view.composer = @composer
92
+ return view
78
93
  end
79
94
 
80
95
  def length
@@ -83,9 +98,15 @@ module Pakyow
83
98
 
84
99
  def scope(name)
85
100
  views = ViewCollection.new
101
+ views.context = @context
102
+ views.composer = @composer
86
103
  self.each{|v|
87
104
  next unless svs = v.scope(name)
88
- svs.each{|sv| views << sv}
105
+ svs.each{ |sv|
106
+ sv.context = @context
107
+ sv.composer = @composer
108
+ views << sv
109
+ }
89
110
  }
90
111
 
91
112
  views
@@ -93,9 +114,15 @@ module Pakyow
93
114
 
94
115
  def prop(name)
95
116
  views = ViewCollection.new
117
+ views.context = @context
118
+ views.composer = @composer
96
119
  self.each{|v|
97
120
  next unless svs = v.prop(name)
98
- svs.each{|sv| views << sv}
121
+ svs.each{ |sv|
122
+ sv.context = @context
123
+ sv.composer = @composer
124
+ views << sv
125
+ }
99
126
  }
100
127
 
101
128
  views
@@ -106,11 +133,13 @@ module Pakyow
106
133
  #
107
134
  # Creates a context in which view manipulations can be performed.
108
135
  #
109
- # Unlike previous versions, the context can only be referenced by the
110
- # block argument. No `context` method will be available.s
111
- #
112
- def with
113
- yield(self)
136
+ def with(&block)
137
+ if block.arity == 0
138
+ self.instance_exec(&block)
139
+ else
140
+ yield(self)
141
+ end
142
+
114
143
  self
115
144
  end
116
145
 
@@ -129,10 +158,35 @@ module Pakyow
129
158
 
130
159
  self.each_with_index { |v,i|
131
160
  break unless datum = data[i]
132
- block.call(v, datum, i) if block_given?
161
+
162
+ if block.arity == 1
163
+ v.instance_exec(data[i], &block)
164
+ else
165
+ block.call(v, data[i])
166
+ end
133
167
  }
134
168
  end
135
169
 
170
+ # call-seq:
171
+ # for_with_index {|view, datum, i| block}
172
+ #
173
+ # Yields a view, its matching datum, and index. Datums are yielded until
174
+ # no more views or data is available. For the ViewCollection case, this
175
+ # means the block will be yielded self.length times.
176
+ #
177
+ def for_with_index(data, &block)
178
+ i = 0
179
+ self.for(data) do |ctx, datum|
180
+ if block.arity == 2
181
+ ctx.instance_exec(datum, i, &block)
182
+ else
183
+ block.call(ctx, datum, i)
184
+ end
185
+
186
+ i += 1
187
+ end
188
+ end
189
+
136
190
  # call-seq:
137
191
  # match(data) => ViewCollection
138
192
  #
@@ -145,6 +199,8 @@ module Pakyow
145
199
  data = [data] if (!data.is_a?(Enumerable) || data.is_a?(Hash))
146
200
 
147
201
  views = ViewCollection.new
202
+ views.context = @context
203
+ views.composer = @composer
148
204
  data.each_with_index {|datum,i|
149
205
  unless v = self[i]
150
206
 
@@ -154,18 +210,25 @@ module Pakyow
154
210
 
155
211
  d_v = v.doc.dup
156
212
  v.doc.before(d_v)
213
+ v.invalidate!(true)
157
214
 
158
215
  new_v = View.from_doc(d_v)
159
216
 
160
217
  # find binding subset (keeps us from refinding)
161
218
  new_v.bindings = v.bindings.dup
162
219
  new_v.scoped_as = v.scoped_as
220
+ new_v.context = @context
221
+ new_v.composer = @composer
163
222
 
164
223
  views << new_v
165
224
  }
166
225
 
167
226
  # do not use self.remove since that refinds bindings
168
- self.each {|v| v.doc.remove}
227
+ self.each {|v|
228
+ next if v.doc.parent.nil?
229
+ v.doc.remove
230
+ }
231
+
169
232
  views
170
233
  end
171
234
 
@@ -178,18 +241,51 @@ module Pakyow
178
241
  self.match(data).for(data, &block)
179
242
  end
180
243
 
244
+ # call-seq:
245
+ # repeat_with_index(data) {|view, datum, i| block}
246
+ #
247
+ # Matches self with data and yields a view/datum pair with index.
248
+ #
249
+ def repeat_with_index(data, &block)
250
+ self.match(data).for_with_index(data, &block)
251
+ end
252
+
181
253
  # call-seq:
182
254
  # bind(data)
183
255
  #
184
256
  # Binds data across existing scopes.
185
257
  #
186
258
  def bind(data, bindings = {}, &block)
187
- self.for(data) {|view, datum, i|
259
+ self.for(data) {|view, datum|
188
260
  view.bind(datum, bindings)
189
- yield(view, datum, i) if block_given?
261
+ next if block.nil?
262
+
263
+ if block.arity == 1
264
+ view.instance_exec(datum, &block)
265
+ else
266
+ block.call(view, datum)
267
+ end
190
268
  }
191
269
  end
192
270
 
271
+ # call-seq:
272
+ # bind_with_index(data)
273
+ #
274
+ # Binds data across existing scopes, yielding a view/datum pair with index.
275
+ #
276
+ def bind_with_index(data, bindings = {}, &block)
277
+ i = 0
278
+ self.bind(data) do |ctx, datum|
279
+ if block.arity == 2
280
+ ctx.instance_exec(datum, i, &block)
281
+ else
282
+ block.call(ctx, datum, i)
283
+ end
284
+
285
+ i += 1
286
+ end
287
+ end
288
+
193
289
  # call-seq:
194
290
  # apply(data)
195
291
  #
@@ -0,0 +1,194 @@
1
+ module Pakyow
2
+ module Presenter
3
+ class ViewComposer
4
+ class << self
5
+ def from_path(store, path, opts = {}, &block)
6
+ ViewComposer.new(store, path, opts, &block)
7
+ end
8
+ end
9
+
10
+ extend Forwardable
11
+
12
+ def_delegators :template, :title, :title=
13
+ def_delegators :parts, :scope, :prop
14
+
15
+ attr_accessor :context
16
+ attr_reader :store, :path, :page, :partials
17
+
18
+ def initialize(store, path = nil, opts = {}, &block)
19
+ @store = store
20
+ @path = path
21
+
22
+ self.page = opts.fetch(:page) {
23
+ path
24
+ }
25
+
26
+ self.template = opts.fetch(:template) {
27
+ (@page.is_a?(Page) && @page.info(:template)) || path
28
+ }
29
+
30
+ @partials = {}
31
+
32
+ begin
33
+ @partials = includes(opts.fetch(:includes))
34
+ rescue
35
+ @partials = store.partials(path) unless path.nil?
36
+ end
37
+
38
+ @partials.each do |name, partial|
39
+ partial.composer = self
40
+ end
41
+
42
+ instance_exec(&block) if block_given?
43
+ end
44
+
45
+ def initialize_copy(original)
46
+ super
47
+
48
+ %w[store path page template partials view].each do |ivar|
49
+ value = original.instance_variable_get("@#{ivar}")
50
+
51
+ if value.is_a?(Hash)
52
+ dup_value = {}
53
+ value.each_pair { |key, value| dup_value[key] = value.dup }
54
+ else
55
+ dup_value = value.dup
56
+ end
57
+
58
+ self.instance_variable_set("@#{ivar}", dup_value)
59
+ end
60
+
61
+ # update composer reference for partials
62
+ @partials.each do |name, partial|
63
+ partial.composer = self
64
+ end
65
+
66
+ # update composer reference for page
67
+ @page.composer = self
68
+ end
69
+
70
+ def precompose!
71
+ @view = build_view
72
+ clean!
73
+ end
74
+
75
+ def view
76
+ if dirty?
77
+ @view = build_view
78
+ clean!
79
+ end
80
+
81
+ return @view
82
+ end
83
+ alias_method :composed, :view
84
+
85
+ def template(template = nil)
86
+ if template.nil?
87
+ @template.context = context
88
+ return @template
89
+ end
90
+
91
+ self.template = template
92
+ return self
93
+ end
94
+
95
+ def template=(template)
96
+ unless template.is_a?(Template)
97
+ # get template by name
98
+ template = @store.template(template)
99
+ end
100
+
101
+ @template = template
102
+ dirty!
103
+
104
+ return self
105
+ end
106
+
107
+ def page=(page)
108
+ unless page.is_a?(Page)
109
+ # get page by name
110
+ page = @store.page(page)
111
+ end
112
+
113
+ page.composer = self
114
+ @page = page
115
+ dirty!
116
+
117
+ return self
118
+ end
119
+
120
+ def includes(partial_map)
121
+ dirty!
122
+
123
+ @partials.merge!(remap_partials(partial_map))
124
+ end
125
+
126
+ def partials=(partial_map)
127
+ dirty!
128
+ @partials.merge!(remap_partials(partial_map))
129
+ end
130
+
131
+ def partial(name)
132
+ partial = @partials[name]
133
+ partial.context = context
134
+ return partial
135
+ end
136
+
137
+ def container(name)
138
+ container = @page.container(name)
139
+ container.context = context
140
+ return container
141
+ end
142
+
143
+ def dirty?
144
+ @dirty
145
+ end
146
+
147
+ def dirty!
148
+ @dirty = true
149
+ end
150
+
151
+ def parts
152
+ parts = ViewCollection.new
153
+ parts.context = @context
154
+ parts.composer = self
155
+ parts << @template
156
+ @page.each_container do |name, container| parts << container end
157
+ partials.each_pair do |name, partial| parts << partial end
158
+ return parts
159
+ end
160
+
161
+ private
162
+
163
+ def clean!
164
+ @dirty = false
165
+ end
166
+
167
+ def build_view
168
+ raise MissingTemplate, "No template provided to view composer" if @template.nil?
169
+ raise MissingPage, "No page provided to view composer" if @page.nil?
170
+
171
+ view = @template.dup.build(@page).includes(@partials)
172
+
173
+ # set title
174
+ title = @page.info(:title)
175
+ view.title = title unless title.nil?
176
+
177
+ return view
178
+ end
179
+
180
+ def remap_partials(partials)
181
+ Hash[partials.map { |name, partial_or_path|
182
+ if partial_or_path.is_a?(Partial)
183
+ partial = partial_or_path
184
+ else
185
+ partial = Partial.load(@store.expand_partial_path(path))
186
+ end
187
+
188
+ [name, partial]
189
+ }]
190
+ end
191
+
192
+ end
193
+ end
194
+ end
@@ -1,6 +1,6 @@
1
1
  module Pakyow
2
2
  class ViewStore
3
- attr_reader :name
3
+ attr_reader :name, :store_path, :templates
4
4
 
5
5
  def initialize(store_path, name = :default)
6
6
  @name = name
@@ -20,6 +20,8 @@ module Pakyow
20
20
  end
21
21
 
22
22
  def template(name_or_path)
23
+ return name_or_path if name_or_path.is_a?(Template)
24
+
23
25
  if name_or_path.is_a?(Symbol)
24
26
  return template_with_name(name_or_path)
25
27
  else
@@ -28,24 +30,62 @@ module Pakyow
28
30
  end
29
31
 
30
32
  def page(view_path)
33
+ return view_path if view_path.is_a?(Page)
34
+
35
+ raise ArgumentError, "Cannot build page for nil path" if view_path.nil?
31
36
  return at_path(view_path, :page).dup
32
37
  end
33
38
 
34
39
  def view(view_path)
35
- return at_path(view_path, :view).dup
40
+ return at_path(view_path, :composer).view.dup
41
+ end
42
+
43
+ def partials(view_path)
44
+ return at_path(view_path, :partials) || {}
36
45
  end
37
46
 
38
47
  def partial(view_path, name)
39
- return at_path(view_path, :partials)[name.to_sym]
48
+ return partials(view_path)[name.to_sym]
49
+ end
50
+
51
+ def composer(view_path)
52
+ return at_path(view_path, :composer).dup
40
53
  end
41
54
 
42
55
  # iterations through known views, yielding each
43
56
  def views
44
57
  @path_info.each_pair do |path, info|
45
- yield(info[:view], path)
58
+ yield(info[:composer].view, path)
59
+ end
60
+ end
61
+
62
+ def infos
63
+ @path_info.each_pair do |path, info|
64
+ yield(info, path)
46
65
  end
47
66
  end
48
67
 
68
+ def expand_path(view_path)
69
+ File.join(@store_path, view_path)
70
+ end
71
+
72
+ # Builds the full path to a partial.
73
+ #
74
+ # expand_partial_path('path/to/partial')
75
+ # # => '{store_path}/path/to/_partial.html'
76
+ def expand_partial_path(path)
77
+ parts = path.split('/')
78
+
79
+ # add underscore
80
+ expanded_path = expand_path((parts[0..-2] << "_#{parts[-1]}").join('/'))
81
+
82
+ # attempt to find extension
83
+ matches = Dir.glob(expanded_path + '.*')
84
+ raise MissingPartial, "Could not find partial with any extension at #{expanded_path}" if matches.empty?
85
+
86
+ return expanded_path + File.extname(matches[0])
87
+ end
88
+
49
89
  private
50
90
 
51
91
  def at_path(view_path, obj = nil)
@@ -55,7 +95,7 @@ module Pakyow
55
95
  if info.nil?
56
96
  raise MissingView, "No view at path '#{view_path}'"
57
97
  else
58
- return obj ? info[obj.to_sym] : info
98
+ return (obj ? info[obj.to_sym] : info).dup
59
99
  end
60
100
  end
61
101
 
@@ -64,7 +104,7 @@ module Pakyow
64
104
  raise MissingTemplate, "No template named '#{name}'"
65
105
  end
66
106
 
67
- return template
107
+ return template.dup
68
108
  end
69
109
 
70
110
  def load_templates
@@ -89,7 +129,10 @@ module Pakyow
89
129
  def load_path_info
90
130
  @path_info = {}
91
131
 
92
- DirUtils.walk_dir(@store_path) do |path|
132
+ # for keeping up with pages for previous paths
133
+ pages = {}
134
+
135
+ Utils::Dir.walk_dir(@store_path) do |path|
93
136
  # skip root
94
137
  next if path == @store_path
95
138
 
@@ -110,26 +153,30 @@ module Pakyow
110
153
  # page is used
111
154
  if File.directory?(path)
112
155
  # gets the path for the previous page
113
- prev_path = normalized_path.split('/')[0..-2].join("")
114
- page = @path_info[prev_path][:page]
156
+ prev_path = normalized_path
157
+ until page = pages[prev_path]
158
+ prev_path = prev_path.split('/')[0..-2].join("/")
159
+ end
160
+ page = page
115
161
  else
116
162
  page = Page.load(path)
163
+ pages[normalized_path] = page
117
164
  end
118
165
 
119
- template = template_with_name(page.template).dup
166
+ template = template_with_name(page.info(:template))
120
167
 
121
- #TODO more efficient way of doing this? lot
122
- # of redundant calls here
168
+ #TODO more efficient way of doing this? lot of redundant calls here
123
169
  partials = partials_at_path(path)
124
170
 
125
- # construct view
126
- view = template.build(page.dup.build(partials))
171
+ # compose template/page/partials
172
+ composer = ViewComposer.from_path(self, normalized_path, template: template, page: page, includes: partials)
173
+ composer.precompose!
127
174
 
128
175
  info = {
129
- view: view,
130
176
  page: page,
131
177
  template: template,
132
178
  partials: partials,
179
+ composer: composer,
133
180
  }
134
181
 
135
182
  @path_info[normalized_path] = info
@@ -140,7 +187,7 @@ module Pakyow
140
187
  relative_path = path.gsub(@store_path, '')
141
188
  relative_path = relative_path.gsub(File.extname(relative_path), '')
142
189
  relative_path = relative_path.gsub('index', '')
143
- relative_path = StringUtils.normalize_path(relative_path)
190
+ relative_path = Utils::String.normalize_path(relative_path)
144
191
 
145
192
  return relative_path
146
193
  end
@@ -149,7 +196,7 @@ module Pakyow
149
196
  view_path = File.dirname(view_path) unless File.directory?(view_path)
150
197
 
151
198
  partials = {}
152
- DirUtils.walk_dir(@store_path) do |path|
199
+ Utils::Dir.walk_dir(@store_path) do |path|
153
200
  # skip non-partials
154
201
  next unless File.basename(path)[0,1] == '_'
155
202
 
@@ -157,7 +204,7 @@ module Pakyow
157
204
  next if File.directory?(path)
158
205
 
159
206
  # skip files not within `view_path`
160
- next unless DirUtils.dir_within_dir?(File.dirname(path), view_path)
207
+ next unless Utils::Dir.dir_within_dir?(File.dirname(path), view_path)
161
208
 
162
209
  name = File.basename(path.split('/')[-1], '.*')[1..-1]
163
210
  partials[name.to_sym] = path
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pakyow-presenter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.rc4
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Powell
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-10-26 00:00:00.000000000 Z
12
+ date: 2014-03-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: pakyow-core
@@ -17,54 +17,54 @@ dependencies:
17
17
  requirements:
18
18
  - - '='
19
19
  - !ruby/object:Gem::Version
20
- version: 0.8.rc4
20
+ version: 0.8.0
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - '='
26
26
  - !ruby/object:Gem::Version
27
- version: 0.8.rc4
27
+ version: 0.8.0
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: nokogiri
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
- - - ~>
32
+ - - "~>"
33
33
  - !ruby/object:Gem::Version
34
34
  version: '1.6'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
- - - ~>
39
+ - - "~>"
40
40
  - !ruby/object:Gem::Version
41
41
  version: '1.6'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: minitest
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - ~>
46
+ - - "~>"
47
47
  - !ruby/object:Gem::Version
48
48
  version: '5.0'
49
49
  type: :development
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - ~>
53
+ - - "~>"
54
54
  - !ruby/object:Gem::Version
55
55
  version: '5.0'
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: pry
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
- - - ~>
60
+ - - "~>"
61
61
  - !ruby/object:Gem::Version
62
62
  version: '0.9'
63
63
  type: :development
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
- - - ~>
67
+ - - "~>"
68
68
  - !ruby/object:Gem::Version
69
69
  version: '0.9'
70
70
  description: A library for building a frontend for Pakyow applications, including
@@ -75,14 +75,15 @@ extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
77
  - pakyow-presenter/CHANGES
78
- - pakyow-presenter/README
79
78
  - pakyow-presenter/MIT-LICENSE
79
+ - pakyow-presenter/README
80
80
  - pakyow-presenter/lib/pakyow-presenter.rb
81
81
  - pakyow-presenter/lib/presenter/attributes.rb
82
82
  - pakyow-presenter/lib/presenter/base.rb
83
83
  - pakyow-presenter/lib/presenter/binder.rb
84
84
  - pakyow-presenter/lib/presenter/binder_set.rb
85
85
  - pakyow-presenter/lib/presenter/config/presenter.rb
86
+ - pakyow-presenter/lib/presenter/container.rb
86
87
  - pakyow-presenter/lib/presenter/doc_helpers.rb
87
88
  - pakyow-presenter/lib/presenter/exceptions.rb
88
89
  - pakyow-presenter/lib/presenter/ext/app.rb
@@ -91,8 +92,10 @@ files:
91
92
  - pakyow-presenter/lib/presenter/partial.rb
92
93
  - pakyow-presenter/lib/presenter/presenter.rb
93
94
  - pakyow-presenter/lib/presenter/template.rb
95
+ - pakyow-presenter/lib/presenter/title_helpers.rb
94
96
  - pakyow-presenter/lib/presenter/view.rb
95
97
  - pakyow-presenter/lib/presenter/view_collection.rb
98
+ - pakyow-presenter/lib/presenter/view_composer.rb
96
99
  - pakyow-presenter/lib/presenter/view_store.rb
97
100
  homepage: http://pakyow.com
98
101
  licenses:
@@ -104,17 +107,17 @@ require_paths:
104
107
  - pakyow-presenter/lib
105
108
  required_ruby_version: !ruby/object:Gem::Requirement
106
109
  requirements:
107
- - - '>='
110
+ - - ">="
108
111
  - !ruby/object:Gem::Version
109
112
  version: 1.9.3
110
113
  required_rubygems_version: !ruby/object:Gem::Requirement
111
114
  requirements:
112
- - - '>'
115
+ - - ">="
113
116
  - !ruby/object:Gem::Version
114
- version: 1.3.1
117
+ version: '0'
115
118
  requirements: []
116
119
  rubyforge_project: pakyow-presenter
117
- rubygems_version: 2.0.0
120
+ rubygems_version: 2.2.0
118
121
  signing_key:
119
122
  specification_version: 4
120
123
  summary: A library for building a frontend for Pakyow applications.