pakyow-presenter 0.8.rc4 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.