pakyow-presenter 0.9.1 → 0.10.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.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/pakyow-presenter/CHANGELOG.md +94 -0
  3. data/pakyow-presenter/{MIT-LICENSE → LICENSE} +2 -2
  4. data/pakyow-presenter/README.md +36 -0
  5. data/pakyow-presenter/lib/pakyow-presenter.rb +1 -4
  6. data/pakyow-presenter/lib/presenter/attributes.rb +10 -1
  7. data/pakyow-presenter/lib/presenter/base.rb +2 -1
  8. data/pakyow-presenter/lib/presenter/binder.rb +20 -6
  9. data/pakyow-presenter/lib/presenter/binder_set.rb +21 -17
  10. data/pakyow-presenter/lib/presenter/config/presenter.rb +2 -2
  11. data/pakyow-presenter/lib/presenter/ext/app.rb +32 -1
  12. data/pakyow-presenter/lib/presenter/helpers.rb +6 -6
  13. data/pakyow-presenter/lib/presenter/page.rb +4 -4
  14. data/pakyow-presenter/lib/presenter/presenter.rb +32 -10
  15. data/pakyow-presenter/lib/presenter/string_doc.rb +78 -11
  16. data/pakyow-presenter/lib/presenter/string_doc_parser.rb +16 -7
  17. data/pakyow-presenter/lib/presenter/view.rb +55 -17
  18. data/pakyow-presenter/lib/presenter/view_collection.rb +74 -7
  19. data/pakyow-presenter/lib/presenter/view_composer.rb +52 -8
  20. data/pakyow-presenter/lib/presenter/view_context.rb +19 -1
  21. data/pakyow-presenter/lib/presenter/view_store.rb +202 -163
  22. data/pakyow-presenter/lib/presenter/view_store_loader.rb +43 -0
  23. data/pakyow-presenter/lib/presenter/view_version.rb +97 -0
  24. data/pakyow-presenter/lib/views/errors/404.html +5 -0
  25. data/pakyow-presenter/{README → lib/views/errors/500.html} +0 -0
  26. metadata +36 -21
  27. data/pakyow-presenter/CHANGES +0 -57
  28. data/pakyow-presenter/lib/presenter/nokogiri_doc.rb +0 -321
@@ -1,221 +1,260 @@
1
1
  module Pakyow
2
- class ViewStore
3
- attr_reader :name, :store_path, :templates
2
+ module Presenter
3
+ class ViewStore
4
+ attr_reader :store_name, :store_paths, :templates
4
5
 
5
- def initialize(store_path, name = :default)
6
- @name = name
7
- @store_path = store_path
6
+ def initialize(store_path_or_paths, store_name = :default)
7
+ @store_name = store_name
8
+ @store_paths = Array.ensure(store_path_or_paths)
8
9
 
9
- load_templates
10
- load_path_info
11
- end
10
+ load_templates
11
+ load_path_info
12
+ end
12
13
 
13
- def at?(view_path)
14
- begin
15
- at_path(view_path)
16
- return true
17
- rescue MissingView
18
- return false
14
+ def at?(view_path)
15
+ begin
16
+ at_path(view_path)
17
+ return true
18
+ rescue MissingView
19
+ return false
20
+ end
19
21
  end
20
- end
21
22
 
22
- def template(name_or_path)
23
- return name_or_path if name_or_path.is_a?(Template)
23
+ def template(name_or_path)
24
+ return name_or_path if name_or_path.is_a?(Template)
24
25
 
25
- if name_or_path.is_a?(Symbol)
26
- return template_with_name(name_or_path)
27
- else
28
- return at_path(name_or_path, :template)
26
+ if name_or_path.is_a?(Symbol)
27
+ return template_with_name(name_or_path)
28
+ else
29
+ return at_path(name_or_path, :template)
30
+ end
29
31
  end
30
- end
31
32
 
32
- def page(view_path)
33
- return view_path if view_path.is_a?(Page)
33
+ def page(view_path)
34
+ return view_path if view_path.is_a?(Page)
34
35
 
35
- raise ArgumentError, "Cannot build page for nil path" if view_path.nil?
36
- return at_path(view_path, :page)
37
- end
38
-
39
- def partials(view_path)
40
- return at_path(view_path, :partials) || {}
41
- end
36
+ raise ArgumentError, "Cannot build page for nil path" if view_path.nil?
37
+ return at_path(view_path, :page)
38
+ end
42
39
 
43
- def partial(view_path, name)
44
- return partials(view_path)[name.to_sym]
45
- end
40
+ def partials(view_path)
41
+ return at_path(view_path, :partials) || {}
42
+ end
46
43
 
47
- def composer(view_path)
48
- return at_path(view_path, :composer)
49
- end
44
+ def partial(view_path, name)
45
+ return partials(view_path)[name.to_sym]
46
+ end
50
47
 
51
- def view(view_path)
52
- return composer(view_path).view
53
- end
48
+ def composer(view_path)
49
+ return at_path(view_path, :composer)
50
+ end
54
51
 
55
- # iterations through known views, yielding each
56
- def views
57
- @path_info.each_pair do |path, info|
58
- yield(info[:composer].view, path)
52
+ def view(view_path)
53
+ return composer(view_path).view
59
54
  end
60
- end
61
55
 
62
- def infos
63
- @path_info.each_pair do |path, info|
64
- yield(info, path)
56
+ # iterations through known views, yielding each
57
+ def views
58
+ @path_info.each_pair do |path, info|
59
+ yield(info[:composer].view, path)
60
+ end
65
61
  end
66
- end
67
62
 
68
- def expand_path(view_path)
69
- File.join(@store_path, view_path)
70
- end
63
+ def infos
64
+ @path_info.each_pair do |path, info|
65
+ yield(info, path)
66
+ end
67
+ end
71
68
 
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('/')
69
+ def expand_path(view_path)
70
+ @store_paths.each do |store_path|
71
+ path = File.join(store_path, view_path)
78
72
 
79
- # add underscore
80
- expanded_path = expand_path((parts[0..-2] << "_#{parts[-1]}").join('/'))
73
+ if File.extname(path).empty?
74
+ return path if !Dir.glob(path + '.*').empty?
75
+ else
76
+ return path if File.exist?(path)
77
+ end
78
+ end
81
79
 
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?
80
+ nil
81
+ end
85
82
 
86
- return expanded_path + File.extname(matches[0])
87
- end
83
+ # Builds the full path to a partial.
84
+ #
85
+ # expand_partial_path('path/to/partial')
86
+ # # => '{store_path}/path/to/_partial.html'
87
+ def expand_partial_path(path)
88
+ parts = path.split('/')
88
89
 
89
- private
90
+ # add underscore
91
+ expanded_path = expand_path((parts[0..-2] << "_#{parts[-1]}").join('/'))
90
92
 
91
- def at_path(view_path, obj = nil)
92
- normalized_path = normalize_path(view_path)
93
- info = @path_info[normalized_path]
93
+ # attempt to find extension
94
+ matches = Dir.glob(expanded_path + '.*')
95
+ raise MissingPartial, "Could not find partial with any extension at #{expanded_path}" if matches.empty?
94
96
 
95
- if info.nil?
96
- raise MissingView, "No view at path '#{view_path}'"
97
- else
98
- #TODO need to consider whose responsibility it is to make the dups
99
- return (obj ? info[obj.to_sym] : info).dup
97
+ return expanded_path + File.extname(matches[0])
100
98
  end
101
- end
102
99
 
103
- def template_with_name(name)
104
- unless template = @templates[name.to_sym]
105
- raise MissingTemplate, "No template named '#{name}'"
106
- end
100
+ private
107
101
 
108
- return template.dup
109
- end
102
+ def at_path(view_path, obj = nil)
103
+ normalized_path = normalize_path(view_path)
104
+ info = @path_info[normalized_path]
110
105
 
111
- def load_templates
112
- @templates = {}
106
+ if info.nil?
107
+ raise MissingView, "No view at path '#{view_path}'"
108
+ else
109
+ #TODO need to consider whose responsibility it is to make the dups
110
+ view = obj ? info[obj.to_sym] : info
111
+ raise MissingView, "No #{obj} at path '#{view_path}'" if view.nil?
112
+ return view.dup
113
+ end
114
+ end
113
115
 
114
- if File.exists?(templates_path)
115
- Dir.entries(templates_path).each do |file|
116
- next if file =~ /^\./
116
+ def template_with_name(name)
117
+ load_templates
117
118
 
118
- template = Template.load(File.join(templates_path, file))
119
- @templates[template.name] = template
119
+ unless template = @templates[name.to_sym]
120
+ raise MissingTemplate, "No template named '#{name}'"
120
121
  end
121
- else
122
- raise MissingTemplatesDir, "No templates found at '#{templates_path}'"
123
- end
124
- end
125
122
 
126
- def templates_path
127
- return File.join(@store_path, Config.presenter.template_dir(@name))
128
- end
123
+ return template.dup
124
+ end
129
125
 
130
- def load_path_info
131
- @path_info = {}
126
+ def load_templates
127
+ return if templates_loaded?
132
128
 
133
- # for keeping up with pages for previous paths
134
- pages = {}
129
+ @templates = {}
130
+ @store_paths.each do |store_path|
131
+ t_path = templates_path(store_path)
132
+ next unless File.exists?(t_path)
135
133
 
136
- Dir.walk(@store_path) do |path|
137
- # skip root
138
- next if path == @store_path
134
+ Dir.entries(t_path).each do |file|
135
+ next if file =~ /^\./
139
136
 
140
- # don't include templates
141
- next if path.include?(templates_path)
137
+ template = Template.load(File.join(t_path, file))
138
+ @templates[template.name] = template
139
+ end
140
+ end
142
141
 
143
- # skip partial files
144
- next if File.basename(path)[0,1] == '_'
142
+ @templates_loaded = true
143
+ end
145
144
 
146
- # skip non-empty folders (these files will be picked up)
147
- next if !Dir.glob(File.join(path, 'index.*')).empty?
145
+ def templates_loaded?
146
+ @templates_loaded == true
147
+ end
148
148
 
149
- normalized_path = normalize_path(path)
149
+ def templates_path(store_path)
150
+ return File.join(store_path, Config.presenter.template_dir(@store_name))
151
+ end
150
152
 
151
- # if path is a directory we know there's no index page
152
- # so use the previous index page instead. this allows
153
- # partials to be overridden at a path while the same
154
- # page is used
155
- if File.directory?(path)
156
- # gets the path for the previous page
157
- prev_path = normalized_path
158
- until page = pages[prev_path]
159
- prev_path = prev_path.split('/')[0..-2].join("/")
153
+ def load_path_info
154
+ @path_info = {}
155
+
156
+ # for keeping up with pages for previous paths
157
+ pages = {}
158
+
159
+ @store_paths.each do |store_path|
160
+ Dir.walk(store_path) do |path|
161
+ # don't include templates
162
+ next if path.include?(templates_path(store_path))
163
+
164
+ # skip partial files
165
+ next if File.basename(path)[0,1] == '_'
166
+
167
+ # skip non-empty folders (these files will be picked up)
168
+ next unless Dir.glob(File.join(path, 'index.*')).empty?
169
+
170
+ normalized_path = normalize_path(path, store_path)
171
+
172
+ # if path is a directory we know there's no index page
173
+ # so use the previous index page instead. this allows
174
+ # partials to be overridden at a path while the same
175
+ # page is used
176
+ if File.directory?(path)
177
+ # gets the path for the previous page
178
+ prev_path = normalized_path
179
+ until page = pages[prev_path]
180
+ break if prev_path.empty?
181
+ prev_path = prev_path.split('/')[0..-2].join("/")
182
+ end
183
+ page = page
184
+ else
185
+ page = Page.load(path)
186
+ pages[normalized_path] = page
187
+ end
188
+
189
+ unless page.nil?
190
+ template = template_with_name(page.info(:template))
191
+ end
192
+
193
+ #TODO more efficient way of doing this? lot of redundant calls here
194
+ partials = partials_at_path(path)
195
+
196
+ unless page.nil?
197
+ # compose template/page/partials
198
+ composer = ViewComposer.from_path(self, normalized_path, template: template, page: page, includes: partials)
199
+ end
200
+
201
+ info = {
202
+ page: page,
203
+ template: template,
204
+ partials: partials,
205
+ composer: composer,
206
+ }
207
+
208
+ @path_info[normalized_path] = info
160
209
  end
161
- page = page
162
- else
163
- page = Page.load(path)
164
- pages[normalized_path] = page
165
210
  end
211
+ end
166
212
 
167
- template = template_with_name(page.info(:template))
168
-
169
- #TODO more efficient way of doing this? lot of redundant calls here
170
- partials = partials_at_path(path)
171
-
172
- # compose template/page/partials
173
- composer = ViewComposer.from_path(self, normalized_path, template: template, page: page, includes: partials)
213
+ def normalize_path(path, store_path = nil)
214
+ if store_path
215
+ relative_path = path.gsub(store_path, '')
216
+ else
217
+ relative_path = path
218
+ @store_paths.each do |store_path|
219
+ relative_path = relative_path.gsub(store_path, '')
220
+ end
221
+ end
174
222
 
175
- info = {
176
- page: page,
177
- template: template,
178
- partials: partials,
179
- composer: composer,
180
- }
223
+ relative_path = relative_path.gsub(File.extname(relative_path), '')
224
+ relative_path = relative_path.gsub('index', '')
225
+ relative_path = String.normalize_path(relative_path)
181
226
 
182
- @path_info[normalized_path] = info
227
+ return relative_path
183
228
  end
184
- end
185
229
 
186
- def normalize_path(path)
187
- relative_path = path.gsub(@store_path, '')
188
- relative_path = relative_path.gsub(File.extname(relative_path), '')
189
- relative_path = relative_path.gsub('index', '')
190
- relative_path = String.normalize_path(relative_path)
230
+ def partials_at_path(view_path)
231
+ view_path = File.dirname(view_path) unless File.directory?(view_path)
232
+ view_path = normalize_path(view_path)
191
233
 
192
- return relative_path
193
- end
194
-
195
- def partials_at_path(view_path)
196
- view_path = File.dirname(view_path) unless File.directory?(view_path)
234
+ partials = {}
235
+ @store_paths.each do |store_path|
236
+ Dir.walk(store_path) do |path|
237
+ # skip non-partials
238
+ next unless File.basename(path)[0,1] == '_'
197
239
 
198
- partials = {}
199
- Dir.walk(@store_path) do |path|
200
- # skip non-partials
201
- next unless File.basename(path)[0,1] == '_'
240
+ # skip directories
241
+ next if File.directory?(path)
202
242
 
203
- # skip directories
204
- next if File.directory?(path)
243
+ # skip files not within `view_path`
244
+ next unless Dir.within_dir?(normalize_path(File.dirname(path), store_path), view_path)
205
245
 
206
- # skip files not within `view_path`
207
- next unless Dir.within_dir?(File.dirname(path), view_path)
246
+ name = File.basename(path.split('/')[-1], '.*')[1..-1]
247
+ partials[name.to_sym] = path
248
+ end
249
+ end
208
250
 
209
- name = File.basename(path.split('/')[-1], '.*')[1..-1]
210
- partials[name.to_sym] = path
211
- end
251
+ # create instances
252
+ partials.each do |name, path|
253
+ partials[name] = Partial.load(path)
254
+ end
212
255
 
213
- # create instances
214
- partials.each do |name, path|
215
- partials[name] = Partial.load(path)
256
+ return partials
216
257
  end
217
-
218
- return partials
219
258
  end
220
259
  end
221
260
  end
@@ -0,0 +1,43 @@
1
+ module Pakyow
2
+ module Presenter
3
+ class ViewStoreLoader
4
+ include Singleton
5
+
6
+ def initialize
7
+ @last_mod = {}
8
+ end
9
+
10
+ def modified?(name, paths)
11
+ paths = Array.ensure(paths)
12
+
13
+ if !@last_mod.key?(name)
14
+ modified(name)
15
+ return true
16
+ end
17
+
18
+ paths.each do |path|
19
+ Dir.walk(path) do |p|
20
+ next if FileTest.directory?(p)
21
+
22
+ if File.mtime(p) > @last_mod[name]
23
+ modified(name)
24
+ return true
25
+ end
26
+ end
27
+ end
28
+
29
+ false
30
+ end
31
+
32
+ def reset
33
+ @last_mod = {}
34
+ end
35
+
36
+ private
37
+
38
+ def modified(name)
39
+ @last_mod[name] = Time.now
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,97 @@
1
+ class Pakyow::Presenter::ViewVersion
2
+ attr_reader :default, :empty, :versions
3
+
4
+ def initialize(views)
5
+ @empty = views.find { |view| view.version == :empty }
6
+ @default = views.find { |view| !view.doc.get_attribute(:'data-default').nil? } || views.find { |view| view.version != :empty }
7
+ @versions = views
8
+ end
9
+
10
+ def initialize_copy(original_view)
11
+ super
12
+
13
+ @empty = original_view.empty.soft_copy if original_view.empty
14
+ @versions = original_view.versions.map { |view| view.soft_copy }
15
+ @default = versions.first
16
+ end
17
+
18
+ def apply(data, bindings: {}, context: nil, &block)
19
+ data = Array.ensure(data)
20
+
21
+ if data.empty?
22
+ @default = @empty
23
+ cleanup
24
+ self
25
+ else
26
+ cleanup
27
+
28
+ match(data).bind(data, bindings: bindings, context: context, &block)
29
+ end
30
+ end
31
+
32
+ def version(data, &block)
33
+ data = Array.ensure(data)
34
+ coll = Pakyow::Presenter::ViewCollection.new
35
+
36
+ if data.empty?
37
+ @versions.each(&:remove)
38
+ else
39
+ @empty.remove if @empty
40
+ self_dup = self.dup
41
+
42
+ view = process_version(self, data.first, &block)
43
+ working = view
44
+ coll << view
45
+
46
+ data[1..-1].inject(coll) { |coll, datum|
47
+ duped = self_dup.dup
48
+ view = process_version(duped, datum, &block)
49
+
50
+ working.after(view)
51
+ working = view
52
+ coll << view
53
+ }
54
+
55
+ coll
56
+ end
57
+ end
58
+
59
+ def bind(data, bindings: {}, context: nil, &block)
60
+ @versions.each do |view|
61
+ view.bind(data, bindings: bindings, context: context, &block)
62
+ end
63
+ end
64
+
65
+ def use(version)
66
+ @versions.each do |view|
67
+ @default = view if view.version == version
68
+ end
69
+
70
+ cleanup
71
+
72
+ @default
73
+ end
74
+
75
+ def cleanup
76
+ @versions.reject { |view| view == @default }.each(&:remove)
77
+ @versions = [@default]
78
+ end
79
+
80
+ def method_missing(method, *args, &block)
81
+ if @default.respond_to?(method)
82
+ @default.send(method, *args, &block)
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def process_version(version, datum, &block)
89
+ if block.arity == 1
90
+ version.instance_exec(datum, &block)
91
+ else
92
+ block.call(version, datum)
93
+ end
94
+
95
+ version.default
96
+ end
97
+ end
@@ -0,0 +1,5 @@
1
+ <p class="lead">
2
+ But, we recommend you start by creating a view at <code>app/views/{view_path}</code>.
3
+ The view will be served up automatically without writing any back-end code! This pattern
4
+ is called view-first development (you could also call it <em>awesome</em>).
5
+ </p>