pakyow-presenter 0.9.1 → 0.10.0

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