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.
- checksums.yaml +4 -4
- data/pakyow-presenter/CHANGELOG.md +94 -0
- data/pakyow-presenter/{MIT-LICENSE → LICENSE} +2 -2
- data/pakyow-presenter/README.md +36 -0
- data/pakyow-presenter/lib/pakyow-presenter.rb +1 -4
- data/pakyow-presenter/lib/presenter/attributes.rb +10 -1
- data/pakyow-presenter/lib/presenter/base.rb +2 -1
- data/pakyow-presenter/lib/presenter/binder.rb +20 -6
- data/pakyow-presenter/lib/presenter/binder_set.rb +21 -17
- data/pakyow-presenter/lib/presenter/config/presenter.rb +2 -2
- data/pakyow-presenter/lib/presenter/ext/app.rb +32 -1
- data/pakyow-presenter/lib/presenter/helpers.rb +6 -6
- data/pakyow-presenter/lib/presenter/page.rb +4 -4
- data/pakyow-presenter/lib/presenter/presenter.rb +32 -10
- data/pakyow-presenter/lib/presenter/string_doc.rb +78 -11
- data/pakyow-presenter/lib/presenter/string_doc_parser.rb +16 -7
- data/pakyow-presenter/lib/presenter/view.rb +55 -17
- data/pakyow-presenter/lib/presenter/view_collection.rb +74 -7
- data/pakyow-presenter/lib/presenter/view_composer.rb +52 -8
- data/pakyow-presenter/lib/presenter/view_context.rb +19 -1
- data/pakyow-presenter/lib/presenter/view_store.rb +202 -163
- data/pakyow-presenter/lib/presenter/view_store_loader.rb +43 -0
- data/pakyow-presenter/lib/presenter/view_version.rb +97 -0
- data/pakyow-presenter/lib/views/errors/404.html +5 -0
- data/pakyow-presenter/{README → lib/views/errors/500.html} +0 -0
- metadata +36 -21
- data/pakyow-presenter/CHANGES +0 -57
- data/pakyow-presenter/lib/presenter/nokogiri_doc.rb +0 -321
@@ -1,221 +1,260 @@
|
|
1
1
|
module Pakyow
|
2
|
-
|
3
|
-
|
2
|
+
module Presenter
|
3
|
+
class ViewStore
|
4
|
+
attr_reader :store_name, :store_paths, :templates
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
10
|
-
|
11
|
-
|
10
|
+
load_templates
|
11
|
+
load_path_info
|
12
|
+
end
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
23
|
-
|
23
|
+
def template(name_or_path)
|
24
|
+
return name_or_path if name_or_path.is_a?(Template)
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
33
|
-
|
33
|
+
def page(view_path)
|
34
|
+
return view_path if view_path.is_a?(Page)
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
44
|
-
|
45
|
-
|
40
|
+
def partials(view_path)
|
41
|
+
return at_path(view_path, :partials) || {}
|
42
|
+
end
|
46
43
|
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
def partial(view_path, name)
|
45
|
+
return partials(view_path)[name.to_sym]
|
46
|
+
end
|
50
47
|
|
51
|
-
|
52
|
-
|
53
|
-
|
48
|
+
def composer(view_path)
|
49
|
+
return at_path(view_path, :composer)
|
50
|
+
end
|
54
51
|
|
55
|
-
|
56
|
-
|
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
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
63
|
+
def infos
|
64
|
+
@path_info.each_pair do |path, info|
|
65
|
+
yield(info, path)
|
66
|
+
end
|
67
|
+
end
|
71
68
|
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
80
|
-
|
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
|
-
|
83
|
-
|
84
|
-
raise MissingPartial, "Could not find partial with any extension at #{expanded_path}" if matches.empty?
|
80
|
+
nil
|
81
|
+
end
|
85
82
|
|
86
|
-
|
87
|
-
|
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
|
-
|
90
|
+
# add underscore
|
91
|
+
expanded_path = expand_path((parts[0..-2] << "_#{parts[-1]}").join('/'))
|
90
92
|
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
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
|
-
|
104
|
-
unless template = @templates[name.to_sym]
|
105
|
-
raise MissingTemplate, "No template named '#{name}'"
|
106
|
-
end
|
100
|
+
private
|
107
101
|
|
108
|
-
|
109
|
-
|
102
|
+
def at_path(view_path, obj = nil)
|
103
|
+
normalized_path = normalize_path(view_path)
|
104
|
+
info = @path_info[normalized_path]
|
110
105
|
|
111
|
-
|
112
|
-
|
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
|
-
|
115
|
-
|
116
|
-
next if file =~ /^\./
|
116
|
+
def template_with_name(name)
|
117
|
+
load_templates
|
117
118
|
|
118
|
-
|
119
|
-
|
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
|
-
|
127
|
-
|
128
|
-
end
|
123
|
+
return template.dup
|
124
|
+
end
|
129
125
|
|
130
|
-
|
131
|
-
|
126
|
+
def load_templates
|
127
|
+
return if templates_loaded?
|
132
128
|
|
133
|
-
|
134
|
-
|
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
|
-
|
137
|
-
|
138
|
-
next if path == @store_path
|
134
|
+
Dir.entries(t_path).each do |file|
|
135
|
+
next if file =~ /^\./
|
139
136
|
|
140
|
-
|
141
|
-
|
137
|
+
template = Template.load(File.join(t_path, file))
|
138
|
+
@templates[template.name] = template
|
139
|
+
end
|
140
|
+
end
|
142
141
|
|
143
|
-
|
144
|
-
|
142
|
+
@templates_loaded = true
|
143
|
+
end
|
145
144
|
|
146
|
-
|
147
|
-
|
145
|
+
def templates_loaded?
|
146
|
+
@templates_loaded == true
|
147
|
+
end
|
148
148
|
|
149
|
-
|
149
|
+
def templates_path(store_path)
|
150
|
+
return File.join(store_path, Config.presenter.template_dir(@store_name))
|
151
|
+
end
|
150
152
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
#
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
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
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
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
|
-
|
176
|
-
|
177
|
-
|
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
|
-
|
227
|
+
return relative_path
|
183
228
|
end
|
184
|
-
end
|
185
229
|
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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
|
-
|
199
|
-
|
200
|
-
# skip non-partials
|
201
|
-
next unless File.basename(path)[0,1] == '_'
|
240
|
+
# skip directories
|
241
|
+
next if File.directory?(path)
|
202
242
|
|
203
|
-
|
204
|
-
|
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
|
-
|
207
|
-
|
246
|
+
name = File.basename(path.split('/')[-1], '.*')[1..-1]
|
247
|
+
partials[name.to_sym] = path
|
248
|
+
end
|
249
|
+
end
|
208
250
|
|
209
|
-
|
210
|
-
partials
|
211
|
-
|
251
|
+
# create instances
|
252
|
+
partials.each do |name, path|
|
253
|
+
partials[name] = Partial.load(path)
|
254
|
+
end
|
212
255
|
|
213
|
-
|
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>
|
File without changes
|