mynyml-merb-in-file-templates 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Martin Aumont
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,151 @@
1
+ merb-in-file-templates *ALPHA*
2
+ ==============================
3
+ WARNING: This plugin isn't stable yet! Feel free to try it out and send patches
4
+ though.
5
+
6
+ A plugin for the Merb framework that allows templates (views) to be defined in
7
+ the same file as the controller (Sinatra style). Especially useful for
8
+ --very-flat apps (making them truly flat), apps that have litle/small view
9
+ code, and for rapid prototyping.
10
+
11
+ ==== Features
12
+ * Seamless integration with #render, #display
13
+ * Respects template reloading
14
+ * Very simple to use
15
+ * Flexible
16
+
17
+ ==== Dependencies
18
+ * Merb > 0.9.4 (?)
19
+ * Rspec to run the specs
20
+
21
+ ==== Examples
22
+ #example for --very-flat app (single file app)
23
+
24
+ #...
25
+ class Application < Merb::Controller; end
26
+ class Products < Application
27
+ def index
28
+ @products = Product.all
29
+ render
30
+ end
31
+ def show
32
+ @product = Product[params[:id]]
33
+ render
34
+ end
35
+ end
36
+
37
+ __END__
38
+ @@ index.html.erb
39
+ <h1>Product List</h1>
40
+ <ul>
41
+ <% for product in @products -%>
42
+ <li><%= product.name %></li>
43
+ <% end -%>
44
+ </ul>
45
+
46
+ @@ show.html.erb
47
+ <h1><%= @product.name %></h1>
48
+
49
+ In-file templates cohabit peacefully with regular external templates. So in the
50
+ above example, show.html.erb could be defined in views/products/show.html.erb
51
+ and both templates will still be picked up normally. In case of a name conflict
52
+ between an in-file and an external template, the external one will take
53
+ precedence and won't be overwritten, keeping it safe from data loss.
54
+
55
+ Template names follow the same rules as regular templates (usually
56
+ action.mime_type.templating_engine).
57
+
58
+ Layouts, stylesheets and javascript can also be placed in in-file templates.
59
+
60
+ #...
61
+
62
+ __END__
63
+ @@ layout/application.css
64
+ #...
65
+
66
+ @@ layout/products.css
67
+ #...
68
+
69
+ @@ stylesheets/application.css
70
+ #...
71
+
72
+ @@ javascripts/jquery.js
73
+ #...
74
+
75
+ ==== Tweaking
76
+ In order to be fed into merb's templating system, in-file templates need to be
77
+ written to external files. This means you will see dynamically created view
78
+ files inside your Merb.dir_for(:view) directory/subdirectories.
79
+
80
+ The directory in which files are stored is chosen based on the templating
81
+ system's native mechanisms, and so merb-in-file-templates will respect any
82
+ changes to it. Therefore if you want to change where template files are stored,
83
+ you can play with Merb.push_path(:view, ...) and the controller's
84
+ #_template_location method.
85
+
86
+ Merb.push_path(:view, Merb.root / 'views')
87
+ class Application
88
+ def _template_location(context, type=nil, controller = controller_name)
89
+ "#{controller}.#{action_name}.#{type}"
90
+ end
91
+ end
92
+
93
+ This will tell Merb to look under the /views directory, for a template named
94
+ products.index.html.erb.
95
+
96
+ want even flatter?
97
+
98
+ Merb.push_path(:view, Merb.root)
99
+ class Application
100
+ def _template_location(context, type=nil, controller=controller_name)
101
+ "view.#{controller}.#{action_name}.#{type}"
102
+ end
103
+ end
104
+
105
+ will give you a template under the root dir called view.products.index.html.erb
106
+ (adding a common prefix, 'view.' in the example above, causes template files to
107
+ show up nicely grouped with ls, file managers, etc, so they look organized even
108
+ without being placed in a subdirectory).
109
+
110
+ If you mix in-file and external templates and you don't want them to be mixed
111
+ in the same directories, you can tell merb-in-file-templates to store its
112
+ templates somewhere else. This is done through the config hash:
113
+
114
+ Merb::Plugins.config[:in_file_templates] = {
115
+ :view_root => '...',
116
+ :stylesheets_root => '...',
117
+ :javascripts_root => '...',
118
+ }
119
+
120
+ For example, you could set
121
+
122
+ :view_root => Merb.root / 'tmp' / 'ift_views'
123
+
124
+ to store your files in merb-root/tmp/ift_views, or
125
+
126
+ :view_root => '/tmp'
127
+
128
+ to store files in your system's tmp directory.
129
+
130
+ Same goes for stylesheets and javascripts, but remember that those need to be
131
+ placed in a public directory, and that they need to be referenced properly from
132
+ within your html header. You can use the controller's
133
+ #ift_dir_for(:stylesheets) and #ift_dir_for(:javascripts) methods to find their
134
+ locations.
135
+
136
+ ==== Rake Task
137
+ TODO
138
+ (cleanup rake task before deployment..)
139
+
140
+ ==== Installation
141
+ TODO
142
+
143
+ ==== Contact
144
+ If you have suggestions, comments, a patch, a git pull request, rants, doc
145
+ fixes/improvements, etc., feel free to contact me: mynyml at gmail,
146
+ irc.freenode.net #rubyonrails, #merb
147
+
148
+ Happy Hacking!
149
+
150
+ -------------------------------------------------------------------------
151
+ Copyright (c) 2008 Martin Aumont (mynyml), released under the MIT license
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+
6
+ PLUGIN = "merb-in-file-templates"
7
+ NAME = "merb-in-file-templates"
8
+ GEM_VERSION = "0.3.1"
9
+ AUTHOR = "Martin Aumont"
10
+ EMAIL = "mynyml@gmail.com"
11
+ HOMEPAGE = "http://github.com/mynyml/"
12
+ SUMMARY = "Merb plugin that allows templates (views, css, js) to be defined in the same file as the controller"
13
+
14
+ spec = Gem::Specification.new do |s|
15
+ s.name = NAME
16
+ s.version = GEM_VERSION
17
+ s.platform = Gem::Platform::RUBY
18
+ s.has_rdoc = true
19
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
20
+ s.summary = SUMMARY
21
+ s.description = s.summary
22
+ s.author = AUTHOR
23
+ s.email = EMAIL
24
+ s.homepage = HOMEPAGE
25
+ s.add_dependency('merb', '>= 0.9.4')
26
+ s.require_path = 'lib'
27
+ #s.autorequire = PLUGIN
28
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
29
+ end
30
+
31
+ Rake::GemPackageTask.new(spec) do |pkg|
32
+ pkg.gem_spec = spec
33
+ end
34
+
35
+ desc "install the plugin locally"
36
+ task :install => [:package] do
37
+ sh %{sudo gem install pkg/#{NAME}-#{GEM_VERSION} --no-update-sources}
38
+ end
39
+
40
+ desc "create a gemspec file"
41
+ task :make_spec do
42
+ File.open("#{NAME}.gemspec", "w") do |file|
43
+ file.puts spec.to_ruby
44
+ end
45
+ end
46
+
47
+ namespace :jruby do
48
+
49
+ desc "Run :package and install the resulting .gem with jruby"
50
+ task :install => :package do
51
+ sh %{#{SUDO} jruby -S gem install pkg/#{NAME}-#{Merb::VERSION}.gem --no-rdoc --no-ri}
52
+ end
53
+
54
+ end
data/TODO ADDED
@@ -0,0 +1,10 @@
1
+ TODO:
2
+ * stylesheets/javascripts need to be cached as well
3
+ * fix caching hierarchy (e.g. layout should not be under a controller in
4
+ ift.cache)
5
+ * improve README
6
+ * test with haml/sass
7
+ * ensure compatibility with merb-parts and merb-slices
8
+ * see if there is an alternative to alias_method_chain'ing #render and #display
9
+ * ift_ namespace is kinda lame - alternative?
10
+ * fix or find workaround for autotest infinite loops on first pass
@@ -0,0 +1,23 @@
1
+ if defined?(Merb::Plugins)
2
+
3
+ require 'merb-in-file-templates/cache'
4
+ require 'merb-in-file-templates/in_file_templates_mixin'
5
+
6
+ # Config options
7
+ # This plugin will respect Merb::Config[:reload_templates]. This means that
8
+ # if set to true, templates will be rebuilt every time #render is called.
9
+ Merb::Plugins.config[:in_file_templates] = {
10
+ :view_root => nil,
11
+ :stylesheets_root => nil,
12
+ :javascripts_root => nil,
13
+ :garbage_collect => true #unused
14
+ }
15
+
16
+ Merb::BootLoader.after_app_loads do
17
+ Merb::Controller.class_eval do
18
+ include InFileTemplatesMixin
19
+ end
20
+ end
21
+
22
+ Merb::Plugins.add_rakefiles "merb-in-file-templates/merbtasks"
23
+ end
@@ -0,0 +1,45 @@
1
+ require 'yaml'
2
+
3
+ module InFileTemplatesMixin
4
+ class Cache #:nodoc:
5
+ attr_accessor :controller
6
+ def self.path
7
+ Merb.dir_for(:config) / 'ift.cache'
8
+ end
9
+ def initialize(controller_name)
10
+ self.controller = controller_name.to_s
11
+ end
12
+ def store(tpl_path)
13
+ self.cache[self.controller] ||= []
14
+ self.cache[self.controller] << tpl_path
15
+ self.flush
16
+ end
17
+ alias :<< :store
18
+ def files
19
+ self.cache[self.controller]
20
+ end
21
+ def reset!
22
+ self.cache.delete(self.controller)
23
+ self.flush
24
+ end
25
+ def exists?
26
+ !self.cache[self.controller].nil?
27
+ end
28
+ protected
29
+ def cache
30
+ @cache ||= self.load
31
+ end
32
+ def reload!
33
+ @cache = nil
34
+ end
35
+ def load
36
+ YAML::load_file(self.class.path) || {} rescue {}
37
+ end
38
+ def dump
39
+ open(self.class.path,'w+') do |file|
40
+ YAML::dump(self.cache,file)
41
+ end
42
+ end
43
+ alias :flush :dump
44
+ end
45
+ end
@@ -0,0 +1,416 @@
1
+ require 'fileutils'
2
+
3
+ # The in-file-templates mixin allows defining view templates inside controller
4
+ # files. Especially useful for --very-flat apps that have few/small view code,
5
+ # and for rapid prototyping.
6
+ #
7
+ # Templates are defined at the end of the controller file, following the
8
+ # __END__ keyword (__END__ must be placed at the very beginning of the line,
9
+ # with nothing following it). Each template is consists of its name, marked by
10
+ # @@ (@@ must also be placed at the very beginning of the line), followed be
11
+ # the template's content.
12
+ #
13
+ # Template names follow the same rules as regular merb templates:
14
+ #
15
+ # name.mime_type.templating_engine:
16
+ # index.html.erb, edit.html.haml, show.xml.bilder, etc
17
+ #
18
+ # ==== Examples
19
+ # class Application < Merb::Controller; end
20
+ # class Products < Application
21
+ # def index
22
+ # @products = Product.all
23
+ # render
24
+ # end
25
+ # def show
26
+ # @product = Product[params[:id]]
27
+ # render
28
+ # end
29
+ # end
30
+ #
31
+ # __END__
32
+ # @@ index.html.erb
33
+ # <h1>Product List</h1>
34
+ # <ul>
35
+ # <% for product in @products -%>
36
+ # <li><%= product.name %></li>
37
+ # <% end -%>
38
+ # </ul>
39
+ #
40
+ # @@ show.html.erb
41
+ # <h1><%= @product.name %></h1>
42
+ #
43
+ # ==== Notes
44
+ # Most methods are prefixed with ift_ (In File Templates). This is simply to
45
+ # avoid naming conflicts with controller methods.
46
+ module InFileTemplatesMixin
47
+
48
+ # When mixin is included in controller, define aliases to wrap #render and
49
+ # #display
50
+ #
51
+ # ==== Parameters
52
+ # base<Module>:: Module that is including InFileTemplatesMixin (probably a controller)
53
+ #
54
+ # ==== Notes
55
+ # http://yehudakatz.com/2008/05/22/the-greatest-thing-since-sliced-merb/
56
+ # "We consider cases of people using alias_method_chain on Merb to be a bug in
57
+ # Merb, and try to find ways to expose enough functionality so it will not be
58
+ # required."
59
+ # So could this code be written otherwise, or is it an exception to the above
60
+ # rule? what are the alternatives?
61
+ #_
62
+ # @public
63
+ def self.included(base)
64
+ base.send(:alias_method, :render_without_in_file_templates, :render)
65
+ base.send(:alias_method, :render, :render_with_in_file_templates)
66
+ base.send(:alias_method, :display_without_in_file_templates, :display)
67
+ base.send(:alias_method, :display, :display_with_in_file_templates)
68
+ end
69
+
70
+ # Wrap #render to gather template data from caller's file and trigger
71
+ # template build process.
72
+ #
73
+ # ==== Parameters
74
+ # Same as Merb::RenderMixin#render
75
+ #
76
+ # ==== Returns
77
+ # Same as Merb::RenderMixin#render
78
+ #
79
+ # ==== Raises
80
+ # Same as Merb::RenderMixin#render
81
+ #
82
+ # ==== Alternatives
83
+ # Same as Merb::RenderMixin#render
84
+ #_
85
+ # @public
86
+ def render_with_in_file_templates(*args) #:nodoc:
87
+ ift_build!((@ift_caller ||= caller).first.split(':').first)
88
+ render_without_in_file_templates(*args)
89
+ end
90
+
91
+ # Overwrite display and set caller so that #render (called internally by
92
+ # #display) knows what file to fetch template data from.
93
+ #
94
+ # ==== Parameters
95
+ # Same as Merb::RenderMixin#display
96
+ #
97
+ # ==== Returns
98
+ # Same as Merb::RenderMixin#display
99
+ #
100
+ # ==== Raises
101
+ # Same as Merb::RenderMixin#display
102
+ #
103
+ # ==== Alternatives
104
+ # Same as Merb::RenderMixin#display
105
+ # -----
106
+ # @public
107
+ def display_with_in_file_templates(*args) #:nodoc:
108
+ @ift_caller = caller
109
+ display_without_in_file_templates(*args)
110
+ end
111
+
112
+ # Render in-file template in a very basic fashion; only fetches the template
113
+ # as a string, without having it go through the templating system (which
114
+ # includes the templating engines). The method then is equivalent to directly
115
+ # returning a string from the action method.
116
+ #
117
+ # ==== Examples
118
+ # def index
119
+ # 'hello world'
120
+ # end
121
+ #
122
+ # is equivalent to:
123
+ #
124
+ # def index
125
+ # render_from_file(:index)
126
+ # end
127
+ # #...
128
+ # __END__
129
+ # @@ index
130
+ # hello world
131
+ #
132
+ # You can also use a templating engine manually:
133
+ #
134
+ # def index
135
+ # @name = 'world'
136
+ # Erubis::Eruby.new(render_from_file(:new)).result(binding)
137
+ # end
138
+ # #...
139
+ # __END__
140
+ # @@ index
141
+ # hello <%= @name %>
142
+ #
143
+ # ==== Parameters
144
+ # context<~to_s, nil>::
145
+ # Name of the template to fetch. Defaults to action name
146
+ # file<String, nil>::
147
+ # Path to a file in which to fetch the template data. Default is file this
148
+ # method has been called from
149
+ #
150
+ # ==== Returns
151
+ # String:: Raw template
152
+ #
153
+ # ==== Raises
154
+ # TemplateNotFound:: There is no template in the specified file.
155
+ #_
156
+ # @public
157
+ def render_from_file(context=action_name, file=nil)
158
+ file ||= caller.first.split(':').first
159
+ data = IO.read(file).split('__END__').last
160
+ ift_parse(data)[context.to_s] or raise(Merb::Controller::TemplateNotFound, "Template #{context} not found in #{File.expand_path(file)}")
161
+ end
162
+
163
+ # Builds templates from in-file data so that they can be picked up by merb's
164
+ # templating system. Building includes parsing the data to extract the
165
+ # templates and then writing them to files.
166
+ #
167
+ # Will skip rebuilding process if Merb::Config[:reload_templates] is false
168
+ #
169
+ # ==== Parameters
170
+ # file<String>::
171
+ # Path to a file in which to fetch the template data.
172
+ #_
173
+ # @semipublic
174
+ def ift_build!(file)
175
+ # write templates to view files if they haven't been already been
176
+ # written, or always if :reload_templates config option is true
177
+ return unless !ift_built? || Merb::Config[:reload_templates]
178
+
179
+ data = IO.read(file).split('__END__').last
180
+ templates = ift_parse(data)
181
+
182
+ self.ift_set_template_roots!
183
+ self.ift_garbage_collect!
184
+
185
+ templates.each do |name,data|
186
+ path = ift_path_for(name)
187
+ unless File.exist?(path) #don't overwrite externaly defined templates
188
+ self.ift_write_template(path, data)
189
+ @cache << path
190
+ end
191
+ end
192
+ end
193
+
194
+ # Construct path (including filename) for given template name.
195
+ # Template can be a view, stylesheet or javascript file.
196
+ #
197
+ # ==== Parameters
198
+ # name<String>:: Template name
199
+ #
200
+ # ==== Returns
201
+ # String:: Path to template
202
+ #
203
+ # ==== Examples
204
+ # # given @@ products/foo.html.erb
205
+ # ift_path_for('foo.html.erb') #=> /merb.root/views/products/foo.html.erb
206
+ #
207
+ # # given @@ bar.html.erb
208
+ # # and current controller is Products
209
+ # ift_path_for('bar.html.erb') #=> /merb.root/views/products/bar.html.erb
210
+ #
211
+ # # given @@ public/stylesheet/app.css
212
+ # ift_path_for('app.css') #=> /merb.root/public/stylesheets/app.css
213
+ #_
214
+ # @semipublic
215
+ def ift_path_for(name)
216
+ name =
217
+ if ift_stylesheet?(name)
218
+ self.ift_dir_for(:stylesheets) / File.basename(name)
219
+ elsif ift_javascript?(name)
220
+ self.ift_dir_for(:javascripts) / File.basename(name)
221
+ else
222
+ name = ift_template_dir / name unless name.include?('/') #add path prefix if omitted
223
+ self.ift_dir_for(:view) / name
224
+ end
225
+ File.expand_path(name)
226
+ end
227
+
228
+ # Get in-file template directory for a type. Equivalent to Merb.dir_for,
229
+ # but taking config options for template paths into consideration.
230
+ #
231
+ # ==== Parameters
232
+ # type<Symbol>::
233
+ # The type of path to retrieve directory for, e.g. :view. Accepted types
234
+ # are :view, :stylesheets and :javascripts
235
+ #
236
+ # ==== Returns
237
+ # String:: Path to the type directory
238
+ #_
239
+ # @public
240
+ def ift_dir_for(type)
241
+ case type
242
+ when :view
243
+ Merb::Plugins.config[:in_file_templates][:view_root] ||
244
+ self._template_root
245
+ when :stylesheets
246
+ Merb::Plugins.config[:in_file_templates][:stylesheets_root] ||
247
+ Merb.dir_for(:stylesheets)
248
+ when :javascripts
249
+ Merb::Plugins.config[:in_file_templates][:javascripts_root] ||
250
+ Merb.dir_for(:javascripts)
251
+ end
252
+ end
253
+
254
+ # Find out if template defines a stylesheet, based on template's name.
255
+ #
256
+ # ==== Parameters
257
+ # name<String>:: Template name
258
+ #
259
+ # ==== Returns
260
+ # Boolean:: True if stylesheet, false otherwise
261
+ #
262
+ # ==== Examples
263
+ # # given Merb.dir_for(:stylesheets) #=> '/merb.root/public/stylesheets'
264
+ # ift_stylesheet?('public/stylesheets/app.css') #=> true
265
+ # ift_stylesheet?('stylesheets/app.css') #=> true
266
+ # ift_stylesheet?('index.html.erb') #=> false
267
+ #_
268
+ # @semipublic
269
+ def ift_stylesheet?(name)
270
+ dir = File.dirname(name).chomp('.')
271
+ dir.empty? ? false : !!(Merb.dir_for(:stylesheet) =~ /#{dir}$/)
272
+ end
273
+
274
+ # Find out if template defines a javascript, based on template's name.
275
+ #
276
+ # ==== Parameters
277
+ # name<String>:: Template name
278
+ #
279
+ # ==== Returns
280
+ # Boolean:: True if javascript, false otherwise
281
+ #
282
+ # ==== Examples
283
+ # # given Merb.dir_for(:javascript) #=> '/merb.root/public/javascripts'
284
+ # ift_javascript?('public/javascripts/app.js') #=> true
285
+ # ift_javascript?('javascripts/app.js') #=> true
286
+ # ift_javascript?('index.html.erb') #=> false
287
+ # ift_javascript?('app.js') #=> false
288
+ #_
289
+ # @semipublic
290
+ def ift_javascript?(name)
291
+ dir = File.dirname(name).chomp('.')
292
+ dir.empty? ? false : !!(Merb.dir_for(:javascript) =~ /#{dir}$/)
293
+ end
294
+
295
+ # Get path to current controller's template location, relative to template
296
+ # root (not view root).
297
+ #
298
+ # ==== Returns
299
+ # String:: relative path to template location
300
+ #_
301
+ # @semipublic
302
+ def ift_template_dir
303
+ File.dirname(
304
+ self._template_location("im in ur gems, playin' wif ur templetz")
305
+ )
306
+ end
307
+
308
+ # Parse given data and extract individual templates and their names.
309
+ #
310
+ # ==== Parameters
311
+ # data<String>:: Raw templates data
312
+ #
313
+ # ==== Returns
314
+ # Hash:: Template names (keys) and their raw content (values)
315
+ #
316
+ # ==== Examples
317
+ # @@ template_name
318
+ # content here
319
+ #
320
+ # @@ index.html.erb
321
+ # the two @ signs must be at the beginning of the line
322
+ #
323
+ # @@ template names can even include spaces
324
+ # pretty unconventional though
325
+ #_
326
+ # @semipublic
327
+ def ift_parse(data)
328
+ templates, current, ignore = {}, nil, false
329
+ data.each_line do |line|
330
+ #(templates[current = $2], ignore = '', $1=='#') and next if line =~ /^(\#)*@@\s*(.*)/
331
+ #templates[current] << line if current unless ignore
332
+ if line =~ /^(\#)*@@\s*(.*)/
333
+ #ignore = ($1 == '#') #skip commented out templates
334
+ #next if ignore
335
+ #current = $2
336
+ #templates[current] = ''
337
+ ignore = ($1 == '#') and next #skip commented out templates
338
+ templates[current = $2] = ''
339
+ elsif ignore
340
+ next
341
+ elsif current
342
+ templates[current] << line
343
+ end
344
+ end
345
+ templates
346
+ end
347
+
348
+ # Rid template directory of all dynamically created files (those created
349
+ # through #ift_build!).
350
+ #_
351
+ # @semipublic
352
+ def ift_garbage_collect!
353
+ ift_init_cache
354
+ if @cache.exists?
355
+ @cache.files.each do |file|
356
+ file.chomp!
357
+ FileUtils.rm(file) if File.exist?(file)
358
+ end
359
+ @cache.reset!
360
+ end
361
+ end
362
+
363
+ protected
364
+
365
+ # Add custom template root to controller's #_template_roots, based on config
366
+ # option. If option isn't set, template_roots stay unchanged.
367
+ #_
368
+ # @private
369
+ def ift_set_template_roots!
370
+ if root = Merb::Plugins.config[:in_file_templates][:view_root]
371
+ self.class._template_roots.unshift([root, :_template_location])
372
+ end
373
+ end
374
+
375
+ # Write template data to a file.
376
+ #
377
+ # ==== Parameters
378
+ # file<String>:: Path name to file in which to store template data.
379
+ # data<String>:: Template data
380
+ #_
381
+ # @private
382
+ def ift_write_template(file, data)
383
+ dir = File.dirname(file)
384
+ FileUtils.mkdir_p(dir) unless File.directory?(dir)
385
+ open(file, 'w+') {|f| f.write(data) }
386
+ end
387
+
388
+ # Determine whether templates have already been built.
389
+ #
390
+ # ==== Returns
391
+ # Boolean::
392
+ # True if templates have been built for this controller, false otherwise
393
+ #_
394
+ # @private
395
+ def ift_built?
396
+ ift_init_cache
397
+ @cache.exists?
398
+ end
399
+
400
+ # Initialize cache.
401
+ # Because the cache depends on controller specific information, it has to be
402
+ # set while execution is in actual controller (as opposed to parent).
403
+ #_
404
+ # @private
405
+ def ift_init_cache
406
+ #@cache ||= Cache.new(self.controller_name)
407
+ @cache = Cache.new(self.controller_name)
408
+ end
409
+
410
+ # unused; optional garbage collection not yet implemented.
411
+ #_
412
+ # @private
413
+ def ift_garbage_collect?
414
+ !!Merb::Plugins.config[:in_file_templates][:garbage_collect]
415
+ end
416
+ end
@@ -0,0 +1,18 @@
1
+ namespace :in_file_templates do
2
+ desc "Remove all generated template files"
3
+ task :clean do
4
+ require 'fileutils'
5
+ YAML::load_file(Merb.dir_for(:config) / 'ift.cache').values.flatten.each do |file|
6
+ unless File.exist?(file)
7
+ file_path = file.gsub(Dir.pwd, '')
8
+ cache_path = (Merb.dir_for(:config) / 'ift.cache').gsub(Dir.pwd,'')
9
+ msg = "Warning: Potential cache corruption. "
10
+ msg << "File #{path} is listed in the cache "
11
+ msg << "(#{cache_path}) but doesn't exist."
12
+ raise msg
13
+ else
14
+ FileUtils.rm(file)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ # as much as I hate external fixtures, in this case the file #render is called
2
+ # from matters so this controller must be split off into its own file
3
+ class Controller3 < Merb::Controller
4
+ def index; render; end
5
+ end
6
+
7
+ __END__
8
+ @@ foo
9
+ fou
10
+
11
+ @@ controller3/index.html.erb
12
+ 3rdctrl-index
13
+
14
+ @@ controller3/qwerty.xml.haml
15
+ funky
@@ -0,0 +1,336 @@
1
+ require 'fileutils'
2
+ begin
3
+ require 'rubygems'
4
+ require 'ruby-debug'
5
+ rescue
6
+ #pass
7
+ end
8
+ require File.dirname(__FILE__) + '/spec_helper'
9
+
10
+ describe "In-file templates" do
11
+
12
+ before(:all) do
13
+ Merb.root = File.dirname(__FILE__) / '..' / 'root'
14
+ Merb.push_path(:view, Merb.root / 'views', '**/*.rb')
15
+ Merb.push_path(:config, Merb.root / 'config')
16
+ Merb.push_path(:public, Merb.root / 'public')
17
+ Merb.push_path(:stylesheets, Merb.dir_for(:public) / "stylesheets", nil)
18
+ Merb.push_path(:javascripts, Merb.dir_for(:public) / "javascripts", nil)
19
+ #-----
20
+ FileUtils.mkdir(Merb.dir_for(:config)) unless File.exist?(Merb.dir_for(:config))
21
+ # --------- Fixtures
22
+ class Controller1 < Merb::Controller
23
+ provides :html, :xml
24
+ def index; render; end
25
+ def show; render_from_file("foo", __FILE__); end
26
+ end
27
+ class Controller2 < Merb::Controller
28
+ provides :html, :xml
29
+ def index; render; end
30
+ end
31
+ require @controller3_file = File.dirname(__FILE__) / 'controller3.rb'
32
+ # ---------- Views
33
+ # (see end of file for rest of views)
34
+ dir = Merb.dir_for(:view) / 'controller1'
35
+ FileUtils.mkdir_p(dir)
36
+ open(dir / 'static.html.erb','w+') {|file| file.write("=^.^=\n") }
37
+ end
38
+
39
+ before(:each) do
40
+ Merb::Config[:reload_templates] = true
41
+ Merb::Template::METHOD_LIST.clear #reset inline templates cache
42
+ Controller1._template_roots = nil
43
+ Controller2._template_roots = nil
44
+ Controller3._template_roots = nil
45
+ @controller1 = Controller1.new(fake_request)
46
+ @controller2 = Controller2.new(fake_request)
47
+ @controller3 = Controller3.new(fake_request)
48
+ @controller = @controller1 #synonym
49
+ clean_tpl_files(@controller1)
50
+ clean_tpl_files(@controller2)
51
+ clean_tpl_files(@controller3)
52
+ end
53
+
54
+ after(:all) do
55
+ clean_tpl_files(@controller1)
56
+ clean_tpl_files(@controller2)
57
+ clean_tpl_files(@controller3)
58
+ #Dir[Merb.root / '*'].each {|e| FileUtils::DryRun.rm_rf(e) }
59
+ Dir[Merb.root / '*'].each {|e| FileUtils.rm_rf(e) }
60
+ end
61
+
62
+ def clean_tpl_files(controller)
63
+ controller.send(:ift_garbage_collect!)
64
+ end
65
+
66
+ it "should be integrated with render" do
67
+ @controller.render(:index).strip.should == "1stctrl-index"
68
+ end
69
+
70
+ it "should be callable from within a controller action" do
71
+ @controller._dispatch(:show)
72
+ @controller.body.strip.should == "bar"
73
+ @controller._dispatch(:index)
74
+ @controller.body.strip.should == "1stctrl-index"
75
+ end
76
+
77
+ it "should be integrated with display" do
78
+ obj = Object.new; def obj.to_html; '<b>a</b>'; end
79
+ @controller.action_name = 'fu'
80
+ @controller.display(obj).strip.should == '<b>a</b>'
81
+ @controller.display(obj,:index).strip.should == '1stctrl-index'
82
+ end
83
+
84
+ it "should parse template engine code" do
85
+ @controller.render(:dynamic).strip.should == "dynamic str"
86
+ end
87
+
88
+ it "should reload templates when code reloading is on" do
89
+ Merb::Config[:reload_templates] = true
90
+ @controller.render(:index).strip.should == "1stctrl-index"
91
+ @controller.stub!(:ift_parse).and_return({'controller1/index.html.erb' => 'foobar'})
92
+ @controller.render(:index).strip.should == 'foobar'
93
+ end
94
+
95
+ it "should not reload templates when code reloading is off" do
96
+ Merb::Config[:reload_templates] = false
97
+ @controller.render(:index).strip.should == "1stctrl-index"
98
+ @controller.stub!(:ift_parse).and_return({'controller1/index.html.erb' => 'foobar'})
99
+ @controller.render(:index).strip.should == '1stctrl-index'
100
+ Merb::Config[:reload_templates] = true
101
+ @controller.render(:index).strip.should == 'foobar'
102
+ end
103
+
104
+ it "should obey template content types (1)" do
105
+ @controller.content_type(:xml)
106
+ @controller.render(:index).strip.should == '<x>ml</x>'
107
+ end
108
+
109
+ it "should obey template content types (2)" do
110
+ @controller.content_type(:html)
111
+ @controller.render(:index).strip.should == '1stctrl-index'
112
+ end
113
+
114
+ it "should look for templates in file where #render is called" do
115
+ @controller3._dispatch(:index)
116
+ @controller3.body.strip.should == "3rdctrl-index"
117
+ end
118
+
119
+ it "should not get confused with many controllers in different files" do
120
+ @controller1._dispatch(:index)
121
+ @controller1.body.strip.should == "1stctrl-index"
122
+ @controller3._dispatch(:index)
123
+ @controller3.body.strip.should == "3rdctrl-index"
124
+ end
125
+
126
+ it "should not get confused with many controllers in the same file" do
127
+ @controller1.render(:index).strip.should == '1stctrl-index'
128
+ @controller2.render(:index).strip.should == '2ndctrl-index'
129
+ end
130
+
131
+ it "should create cache file if it doesn't exist" do
132
+ path = InFileTemplatesMixin::Cache.path
133
+ cache = File.exist?(path) ? (YAML::load_file(path) || {}) : {}
134
+ FileUtils.rm(path) if File.exist?(path)
135
+ File.file?(path).should be_false
136
+ @controller.render(:index).strip.should == '1stctrl-index'
137
+ File.file?(path).should be_true
138
+ YAML::load_file(path).should_not be_false #false when not proper yaml
139
+ end
140
+
141
+ it "should respect changes to config options" do
142
+ view_root = Merb.root / 'tmp' / 'ift' / 'views'
143
+ css_root = Merb.dir_for(:stylesheets) / 'ift'
144
+ js_root = Merb.dir_for(:javascripts) / 'ift'
145
+ #-----
146
+ Merb::Plugins.config[:in_file_templates] = {
147
+ :view_root => view_root,
148
+ :stylesheets_root => css_root,
149
+ :javascripts_root => js_root
150
+ }
151
+ #-----
152
+ @controller.render(:index)
153
+ #-----
154
+ view = view_root / @controller._template_location('') / 'index.html.erb'
155
+ css = css_root / 'app.css'
156
+ js = js_root / 'app.js'
157
+ #-----
158
+ File.exist?(view).should be_true
159
+ File.exist?(css).should be_true
160
+ File.exist?(js).should be_true
161
+ end
162
+
163
+ it "should respect changes to template location" do
164
+ def @controller._template_location(context, type=nil, controller=controller_name)
165
+ "#{controller}.#{action_name}.#{type}"
166
+ end
167
+ @controller.stub!(:ift_parse).and_return({'controller1.index.html.erb' => '=0.0='})
168
+ #-----
169
+ @controller._dispatch(:index)
170
+ @controller.body.strip.should == "=0.0="
171
+ end
172
+
173
+ it "should ignore commented out templates" do
174
+ lambda {
175
+ @controller.render(:commented_out)
176
+ }.should raise_error(Merb::ControllerExceptions::TemplateNotFound)
177
+ end
178
+
179
+ it "should guess template path prefix when not explicitly specified" do
180
+ @controller.render(:onlyname).strip.should include('Defaults')
181
+ end
182
+
183
+ it "should render layouts" do
184
+ @controller.render(:index, :layout => :custom).strip.should == "hd\n1stctrl-index\nft"
185
+ end
186
+
187
+ it "should render stylesheets" do
188
+ @controller.render(:index)
189
+ File.exist?(@controller.ift_dir_for(:stylesheets) / 'app.css').should be_true
190
+ end
191
+
192
+ it "should render javascript" do
193
+ @controller.render(:index)
194
+ File.exist?(@controller.ift_dir_for(:javascripts) / 'app.js').should be_true
195
+ end
196
+
197
+ describe "#render_from_file" do
198
+
199
+ it "should read the template data from the file it is called from" do
200
+ @controller.render_from_file(:foo).strip.should == "bar"
201
+ @controller.render_from_file('controller1/index.html.erb').strip.should == "1stctrl-index"
202
+ end
203
+
204
+ it "should read template's data from provided file" do
205
+ @controller.render_from_file(:foo, @controller3_file).strip.should == "fou"
206
+ end
207
+
208
+ it "should raise an error when it cannot find the template" do
209
+ lambda {
210
+ @controller.render_from_file(:baz)
211
+ }.should raise_error(Merb::ControllerExceptions::TemplateNotFound)
212
+ end
213
+
214
+ it "should be able to find templates based on action name" do
215
+ @controller.action_name = 'foo'
216
+ @controller.render_from_file.strip.should == "bar"
217
+ end
218
+ end
219
+
220
+ # For the purposes of this plugin, static templates refer to templates
221
+ # defined in external files (the regular way), as opposed to those
222
+ # dynamically generated form in-file templates.
223
+ describe "cohabiting with static templates" do
224
+
225
+ it "should be able to render from both in-file template and static template" do
226
+ @controller.render(:index).strip.should == '1stctrl-index'
227
+ @controller.render(:static).strip.should == '=^.^='
228
+ end
229
+
230
+ it "should not modify static templates that are already in view directories" do
231
+ static = (
232
+ @controller._template_root /
233
+ @controller._template_location('') /
234
+ 'static.html.erb'
235
+ )
236
+ File.file?(static).should be_true
237
+ file_prev = File.new(static)
238
+ @controller.render(:index).strip.should == '1stctrl-index'
239
+ File.file?(static).should be_true
240
+ file_prev.read.should == File.new(static).read
241
+ end
242
+
243
+ it "should not overwrite a static template with an in-file template" do
244
+ # views/controller1/ contains a file called static.html.erb
245
+ @controller.stub!(:ift_parse).and_return({'static.html.erb' => '=0.0='})
246
+ @controller.render(:static).strip.should == '=^.^='
247
+ end
248
+
249
+ it "should give precedence to static templates" do
250
+ # views/controller1/ contains a file called static.html.erb
251
+ @controller.stub!(:ift_parse).and_return({'static.html.erb' => '=0.0='})
252
+ @controller.render(:static).strip.should == '=^.^='
253
+ end
254
+ end
255
+
256
+ describe "rake tasks" do
257
+ #setup for rake task specs thanks to
258
+ #http://blog.nicksieger.com/articles/2007/06/11/test-your-rake-tasks
259
+
260
+ before(:all) { require 'rake' }
261
+
262
+ before(:each) do
263
+ @rake = Rake::Application.new
264
+ Rake.application = @rake
265
+ load 'lib/merb-in-file-templates/merbtasks.rb'
266
+ end
267
+
268
+ after(:each) do
269
+ Rake.application = nil
270
+ end
271
+
272
+ it "should garbage collect the dynamic views" do
273
+ @controller1._dispatch(:index)
274
+ @controller3._dispatch(:index)
275
+ File.file?(@controller1.ift_path_for('foo' )).should be_true
276
+ File.file?(@controller1.ift_path_for('index.xml.erb' )).should be_true
277
+ File.file?(@controller1.ift_path_for('index.html.erb' )).should be_true
278
+ File.file?(@controller1.ift_path_for('dynamic.html.erb')).should be_true
279
+ File.file?(@controller3.ift_path_for('foo' )).should be_true
280
+ File.file?(@controller3.ift_path_for('index.html.erb' )).should be_true
281
+ File.file?(@controller3.ift_path_for('qwerty.xml.haml' )).should be_true
282
+ @rake['in_file_templates:clean'].invoke
283
+ File.file?(@controller1.ift_path_for('foo' )).should be_false
284
+ File.file?(@controller1.ift_path_for('index.xml.erb' )).should be_false
285
+ File.file?(@controller1.ift_path_for('index.html.erb' )).should be_false
286
+ File.file?(@controller1.ift_path_for('dynamic.html.erb')).should be_false
287
+ File.file?(@controller3.ift_path_for('foo' )).should be_false
288
+ File.file?(@controller3.ift_path_for('index.html.erb' )).should be_false
289
+ File.file?(@controller3.ift_path_for('qwerty.xml.haml' )).should be_false
290
+ end
291
+
292
+ it "should raise warn that cache might be corrupted if a listed file doesn't exist" do
293
+ @controller.render(:index).strip.should == '1stctrl-index'
294
+ @controller.instance_variable_get(:@cache).store(@controller.ift_path_for('rogue.php'))
295
+ lambda {
296
+ @rake['in_file_templates:clean'].invoke
297
+ }.should raise_error
298
+ end
299
+ end
300
+ end
301
+
302
+ __END__
303
+ @@ foo
304
+ bar
305
+
306
+ @@ layout/custom.html.erb
307
+ hd
308
+ <%= catch_content(:for_layout).strip %>
309
+ ft
310
+
311
+ @@ controller1/index.html.erb
312
+ 1stctrl-index
313
+
314
+ @@ controller2/index.html.erb
315
+ 2ndctrl-index
316
+
317
+ @@ controller1/dynamic.html.erb
318
+ <%= "dynamic str" %>
319
+
320
+ @@ controller1/index.xml.erb
321
+ <x>ml</x>
322
+
323
+ #@@ controller1/commented_out
324
+ should not be parsed
325
+
326
+ @@ onlyname.html.erb
327
+ Defaults to current controller (the one #render is called from) when no path
328
+ prefix is specified. Useful when there's a single controller in the file, but
329
+ be careful about creating conflicts when there's multiple controllers and they
330
+ have templates of the same name
331
+
332
+ @@ public/stylesheets/app.css
333
+ a { color: red; }
334
+
335
+ @@ javascripts/app.js
336
+ var x = 'x'
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format progress
3
+ --loadby mtime
4
+ --reverse
@@ -0,0 +1,13 @@
1
+ $TESTING=true
2
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require 'rubygems'
5
+ require 'merb-core'
6
+ require 'merb-in-file-templates'
7
+ require 'spec'
8
+
9
+ Merb.start :environment => 'test'
10
+
11
+ Spec::Runner.configure do |config|
12
+ config.include Merb::Test::RequestHelper
13
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mynyml-merb-in-file-templates
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.1
5
+ platform: ruby
6
+ authors:
7
+ - Martin Aumont
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-06-10 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: merb
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.9.4
23
+ version:
24
+ description: Merb plugin that allows templates (views, css, js) to be defined in the same file as the controller
25
+ email: mynyml@gmail.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - README
32
+ - LICENSE
33
+ - TODO
34
+ files:
35
+ - LICENSE
36
+ - README
37
+ - Rakefile
38
+ - TODO
39
+ - lib/merb-in-file-templates
40
+ - lib/merb-in-file-templates/cache.rb
41
+ - lib/merb-in-file-templates/in_file_templates_mixin.rb
42
+ - lib/merb-in-file-templates/merbtasks.rb
43
+ - lib/merb-in-file-templates.rb
44
+ - spec/merb-in-file-templates_spec.rb
45
+ - spec/spec.opts
46
+ - spec/controller3.rb
47
+ - spec/spec_helper.rb
48
+ has_rdoc: true
49
+ homepage: http://github.com/mynyml/
50
+ post_install_message:
51
+ rdoc_options: []
52
+
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.0.1
71
+ signing_key:
72
+ specification_version: 2
73
+ summary: Merb plugin that allows templates (views, css, js) to be defined in the same file as the controller
74
+ test_files: []
75
+