mynyml-merb-in-file-templates 0.3.1

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.
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
+