curtain 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +108 -0
- data/Rakefile +10 -0
- data/curtain.gemspec +23 -0
- data/lib/curtain/block_helpers.rb +11 -0
- data/lib/curtain/caching.rb +39 -0
- data/lib/curtain/erubis.rb +33 -0
- data/lib/curtain/erubis_template.rb +26 -0
- data/lib/curtain/html_helpers.rb +33 -0
- data/lib/curtain/output_buffer.rb +12 -0
- data/lib/curtain/rendering.rb +50 -0
- data/lib/curtain/templating.rb +61 -0
- data/lib/curtain/url_helpers.rb +149 -0
- data/lib/curtain/variable_support.rb +17 -0
- data/lib/curtain/version.rb +3 -0
- data/lib/curtain.rb +35 -0
- data/test/caching_test.rb +51 -0
- data/test/curtain_test.rb +55 -0
- data/test/examples/body.erb +6 -0
- data/test/examples/cache.erb +3 -0
- data/test/examples/cache.slim +2 -0
- data/test/examples/index.erb +1 -0
- data/test/examples/layout.erb +1 -0
- data/test/examples/subdir/index.erb +1 -0
- data/test/examples/test.erb +4 -0
- data/test/html_helpers_test.rb +28 -0
- data/test/url_helpers_test.rb +47 -0
- metadata +166 -0
data/.gitignore
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.9.3@curtain --create
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Paul Barry
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
# Curtain
|
2
|
+
|
3
|
+
Curtain is a template rendering framework for Ruby. It is built on top of [Tilt](https://github.com/rtomayko/tilt). Curtain is not tied to any web framework like Rails or Sinatra, so it can be used standalone in any Ruby project.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'curtain'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install curtain
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
To use Curtain, you define a view and then have that view render templates:
|
22
|
+
|
23
|
+
# hello.erb
|
24
|
+
<h1><%= msg %></h1>
|
25
|
+
|
26
|
+
# my_view.rb
|
27
|
+
class MyView < Struct.new(:msg)
|
28
|
+
include Curtain
|
29
|
+
end
|
30
|
+
|
31
|
+
view = MyView.new("Hello, World!")
|
32
|
+
view.render("hello") # => <h1>Hello, World!</h1>
|
33
|
+
|
34
|
+
The template is rendered in the scope of the view object, so any methods defined in the view are available to the template. You don't have to create a subclass if you don't need to:
|
35
|
+
|
36
|
+
Curtain::View.new.render("hello", :msg => "Hello, World!")
|
37
|
+
|
38
|
+
There is an equivalent shortcut available:
|
39
|
+
|
40
|
+
Curtain.render("hello", :msg => "Hello, World!")
|
41
|
+
|
42
|
+
Curtain includes many useful methods. Here's a more realistic example that shows some of the built-in methods. If you have templates like this:
|
43
|
+
|
44
|
+
# friends.erb
|
45
|
+
<% cache "friends-#{current_user.id}", ttl: 5.minutes do %>
|
46
|
+
<% friends.each do |friend| %>
|
47
|
+
<%= render "profile", :profile => friend %>
|
48
|
+
<% end %>
|
49
|
+
<% end %>
|
50
|
+
|
51
|
+
# profile.erb
|
52
|
+
<ul>
|
53
|
+
<li><%= link_to profile.name, path(:profile, :id => profile.id) %></li>
|
54
|
+
</ul>
|
55
|
+
|
56
|
+
You can use them in this way:
|
57
|
+
|
58
|
+
class ApplicationView < Curtain::View
|
59
|
+
attr_accessor :current_user
|
60
|
+
end
|
61
|
+
|
62
|
+
class FriendsView < Curtain::View
|
63
|
+
delegate :friends, :to => :current_user
|
64
|
+
end
|
65
|
+
|
66
|
+
view = FriendsView.new(:current_user => User.first)
|
67
|
+
|
68
|
+
# The default template name is based on the name of the class of the view
|
69
|
+
view.render
|
70
|
+
|
71
|
+
### Variables
|
72
|
+
|
73
|
+
If you don't want to define a subclass of `Curtain::View` and add attributes to it, you can also use variables. `Curtain::View` supports the hash-like Ruby method `[]` and `[]=` to define variables that will act as locals in when the template is rendered:
|
74
|
+
|
75
|
+
# hello.erb
|
76
|
+
<h1><%= msg %></h1>
|
77
|
+
|
78
|
+
view = Curtain::View.new
|
79
|
+
view[:msg] = "Hello"
|
80
|
+
view.render # => "<h1>Hello</h1>"
|
81
|
+
|
82
|
+
Note that unlike locals, variables exist throughout nested scope of render calls:
|
83
|
+
|
84
|
+
# main.erb
|
85
|
+
foo: <%= foo %>
|
86
|
+
bar: <%= bar %>
|
87
|
+
<%= render "partial" %>
|
88
|
+
|
89
|
+
# partial.erb
|
90
|
+
foo: <%= foo %>
|
91
|
+
bar: <%= bar %>
|
92
|
+
|
93
|
+
class MainView < Curtain::View
|
94
|
+
end
|
95
|
+
|
96
|
+
view = MainView.new
|
97
|
+
view[:foo] = "foo"
|
98
|
+
view.render :bar => "bar"
|
99
|
+
|
100
|
+
This example would result in an error. As the main template is first rendered, foo is defined as "foo" because it is a variable, the bar is "bar" because it is passed in as a local. Then the partial template is rendered, and foo is still defined as "foo" because it is a variable, but since bar was a local passed to the rendering of main, it doesn't carry through to the rendering of partial.
|
101
|
+
|
102
|
+
## Contributing
|
103
|
+
|
104
|
+
1. Fork it
|
105
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
106
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
107
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
108
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/curtain.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.authors = ["Paul Barry"]
|
5
|
+
gem.email = ["mail@paulbarry.com"]
|
6
|
+
gem.description = %q{A template rendering framework}
|
7
|
+
gem.summary = %q{A template rendering framework}
|
8
|
+
gem.homepage = ""
|
9
|
+
|
10
|
+
gem.files = `git ls-files`.split($\)
|
11
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
12
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
13
|
+
gem.name = "curtain"
|
14
|
+
gem.require_paths = ["lib"]
|
15
|
+
gem.version = "0.1.0"
|
16
|
+
|
17
|
+
gem.add_runtime_dependency "activesupport"
|
18
|
+
gem.add_runtime_dependency "tilt"
|
19
|
+
|
20
|
+
gem.add_development_dependency "erubis"
|
21
|
+
gem.add_development_dependency "haml"
|
22
|
+
gem.add_development_dependency "slim"
|
23
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Curtain
|
2
|
+
class CacheNotSet < RuntimeError; end
|
3
|
+
|
4
|
+
module Caching
|
5
|
+
module ClassMethods
|
6
|
+
def cache(*args)
|
7
|
+
if args.empty?
|
8
|
+
if defined? @cache
|
9
|
+
@cache
|
10
|
+
elsif superclass.respond_to?(:cache)
|
11
|
+
superclass.cache
|
12
|
+
end
|
13
|
+
else
|
14
|
+
self.cache = args.first
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def cache=(cache)
|
19
|
+
@cache = cache
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.included(cls)
|
24
|
+
cls.extend(ClassMethods)
|
25
|
+
end
|
26
|
+
|
27
|
+
def cache(key, ttl=nil, &block)
|
28
|
+
if self.class.cache
|
29
|
+
unless value = self.class.cache.get(key)
|
30
|
+
value = capture(&block)
|
31
|
+
self.class.cache.set(key, value, ttl)
|
32
|
+
end
|
33
|
+
value
|
34
|
+
else
|
35
|
+
raise CacheNotSet.new("Cache not set, set it with Curtain.cache = Cache.new")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'erubis'
|
2
|
+
require 'curtain/output_buffer'
|
3
|
+
|
4
|
+
module Curtain
|
5
|
+
class Erubis < ::Erubis::Eruby
|
6
|
+
def add_text(src, text)
|
7
|
+
return if text.empty?
|
8
|
+
src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
|
9
|
+
end
|
10
|
+
|
11
|
+
BLOCK_EXPR = /\s+(do|\{)(\s*\|[^|]*\|)?\s*\Z/
|
12
|
+
|
13
|
+
def add_expr_literal(src, code)
|
14
|
+
if code =~ BLOCK_EXPR
|
15
|
+
src << '@output_buffer.append= ' << code
|
16
|
+
else
|
17
|
+
src << '@output_buffer.append= (' << code << ');'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_expr_escaped(src, code)
|
22
|
+
if code =~ BLOCK_EXPR
|
23
|
+
src << "@output_buffer.safe_append= " << code
|
24
|
+
else
|
25
|
+
src << "@output_buffer.safe_concat((" << code << ").to_s);"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_postamble(src)
|
30
|
+
src << '@output_buffer.to_s'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'tilt'
|
2
|
+
require 'curtain/erubis'
|
3
|
+
|
4
|
+
module Curtain
|
5
|
+
class ErubisTemplate < Tilt::Template
|
6
|
+
DEFAULT_OUTPUT_VARIABLE = '@output_buffer'
|
7
|
+
|
8
|
+
def self.engine_initialized?
|
9
|
+
defined? ::ERB
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize_engine
|
13
|
+
require_template_library 'erubis'
|
14
|
+
end
|
15
|
+
|
16
|
+
def prepare
|
17
|
+
@engine = Curtain::Erubis.new(data, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def precompiled_template(locals)
|
21
|
+
@engine.src
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
Tilt.prefer Curtain::ErubisTemplate, 'erb'
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module Curtain
|
4
|
+
module HTMLHelpers
|
5
|
+
def h(html)
|
6
|
+
CGI::escapeHTML(html)
|
7
|
+
end
|
8
|
+
|
9
|
+
def content_tag(name, attributes_or_body=nil, attributes=nil, &block)
|
10
|
+
result = "<#{name}"
|
11
|
+
|
12
|
+
if attributes_or_body.is_a?(Hash)
|
13
|
+
attributes = attributes_or_body
|
14
|
+
end
|
15
|
+
|
16
|
+
if attributes
|
17
|
+
result << " #{attributes.map{|k,v| %{#{k}="#{h(v)}"} }.join(' ')}>"
|
18
|
+
else
|
19
|
+
result << ">"
|
20
|
+
end
|
21
|
+
|
22
|
+
if block
|
23
|
+
result << capture(&block)
|
24
|
+
elsif !attributes_or_body.is_a?(Hash)
|
25
|
+
result << attributes_or_body.to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
result << "</#{name}>"
|
29
|
+
|
30
|
+
result
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'tilt'
|
2
|
+
require 'curtain/erubis_template'
|
3
|
+
|
4
|
+
module Curtain
|
5
|
+
module Rendering
|
6
|
+
# Renders the template
|
7
|
+
#
|
8
|
+
# @example Render the default template
|
9
|
+
# view.render
|
10
|
+
#
|
11
|
+
# @example Render the foo template
|
12
|
+
# view.render "foo.erb"
|
13
|
+
#
|
14
|
+
# @example You can use symbols and omit the extension
|
15
|
+
# view.render :foo
|
16
|
+
#
|
17
|
+
# @example You can specify what the local variables for the template should be
|
18
|
+
# view.render :foo, :bar => "baz"
|
19
|
+
#
|
20
|
+
# @example You can use the default template an specify locals
|
21
|
+
# view.render :bar => "baz"
|
22
|
+
#
|
23
|
+
# @param [String, Symbol] name The name of the template.
|
24
|
+
# The extension can be omitted.
|
25
|
+
# This parameter can be omiitted and the default template will be used.
|
26
|
+
# @param [Hash] locals
|
27
|
+
# @return [String] The result of rendering the template
|
28
|
+
# @see #default_template_name
|
29
|
+
def render(*args)
|
30
|
+
name = if args.length == 0 || args.first.is_a?(Hash)
|
31
|
+
self.class.template
|
32
|
+
else
|
33
|
+
args.first.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
locals = args.last.is_a?(Hash) ? args.last : {}
|
37
|
+
|
38
|
+
# TODO: Cache Template objects
|
39
|
+
template_file = self.class.find_template(name)
|
40
|
+
|
41
|
+
orig_buffer = @output_buffer
|
42
|
+
@output_buffer = Curtain::OutputBuffer.new
|
43
|
+
template = Tilt.new(template_file, :buffer => '@output_buffer', :use_html_safe => true, :disable_capture => true, :generator => Temple::Generators::RailsOutputBuffer)
|
44
|
+
template.render(self, variables.merge(locals))
|
45
|
+
@output_buffer
|
46
|
+
ensure
|
47
|
+
@output_buffer = orig_buffer
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Curtain
|
2
|
+
|
3
|
+
class TemplateNotFound < RuntimeError; end
|
4
|
+
|
5
|
+
module Templating
|
6
|
+
module ClassMethods
|
7
|
+
def template_directories(*args)
|
8
|
+
if args.empty?
|
9
|
+
if defined? @template_directories
|
10
|
+
@template_directories
|
11
|
+
elsif superclass.respond_to?(:template_directories)
|
12
|
+
superclass.template_directories
|
13
|
+
else
|
14
|
+
@template_directories ||= [Dir.pwd]
|
15
|
+
end
|
16
|
+
else
|
17
|
+
self.template_directories = args
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def template_directories=(directories)
|
22
|
+
@template_directories = Array(directories).map{|d| File.expand_path(d) }.uniq
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_template(name)
|
26
|
+
# Try exact match in each directory first
|
27
|
+
template_directories.each do |dir|
|
28
|
+
if file = Dir[File.join(dir.to_s, name.to_s)].first
|
29
|
+
return file
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Try wildcard matches in each directory
|
34
|
+
template_directories.each do |dir|
|
35
|
+
if file = Dir[File.join(dir, "#{name}.*")].first
|
36
|
+
return file
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
raise TemplateNotFound.new("Could not find a template matching '#{name}' in #{Array(template_directories).map{|d| File.expand_path(d) }}")
|
41
|
+
end
|
42
|
+
|
43
|
+
def template(*args)
|
44
|
+
if args.empty?
|
45
|
+
@template ||= name.underscore.sub(/_view$/,'')
|
46
|
+
else
|
47
|
+
@template = args.first
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def template=(template)
|
52
|
+
@template = template
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.included(cls)
|
57
|
+
cls.extend(ClassMethods)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module Curtain
|
2
|
+
class NamedRoutesNotSet < RuntimeError; end
|
3
|
+
class UnknownNamedRoute < RuntimeError; end
|
4
|
+
|
5
|
+
module UrlHelpers
|
6
|
+
|
7
|
+
# When a class includes {Curtain::UrlHelpers}, that class also extends
|
8
|
+
# this module, which makes these methods class methods on the class.
|
9
|
+
# These methods act as class-level attributes that accept
|
10
|
+
# a value at any level of a class hierarchy,
|
11
|
+
# using the value set on the specific class if it has one,
|
12
|
+
# otherwise it delegates it's way up the inheritance chain.
|
13
|
+
# The general pattern of these methods is that the getter accepts an
|
14
|
+
# option argument, which sets the value of the attribute.
|
15
|
+
# This style supports a natural DSL-like way of defining the
|
16
|
+
# values for the attribute definition in the class definition
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# class MyView
|
20
|
+
# include Curtain::UrlHelpers
|
21
|
+
# default_host "example.com"
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
module ClassMethods
|
25
|
+
|
26
|
+
def named_routes(*args)
|
27
|
+
if args.empty?
|
28
|
+
if defined? @named_routes
|
29
|
+
@named_routes
|
30
|
+
elsif superclass.respond_to?(:named_routes)
|
31
|
+
superclass.named_routes
|
32
|
+
end
|
33
|
+
else
|
34
|
+
self.named_routes = args.first
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def named_routes=(named_routes)
|
39
|
+
@named_routes = named_routes
|
40
|
+
end
|
41
|
+
|
42
|
+
def default_host(*args)
|
43
|
+
if args.empty?
|
44
|
+
if defined? @default_host
|
45
|
+
@default_host
|
46
|
+
elsif superclass.respond_to?(:default_host)
|
47
|
+
superclass.default_host
|
48
|
+
end
|
49
|
+
else
|
50
|
+
self.default_host = args.first
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def default_host=(default_host)
|
55
|
+
@default_host = default_host
|
56
|
+
end
|
57
|
+
|
58
|
+
def default_port(*args)
|
59
|
+
if args.empty?
|
60
|
+
if defined? @default_port
|
61
|
+
@default_port
|
62
|
+
elsif superclass.respond_to?(:default_port)
|
63
|
+
superclass.default_port
|
64
|
+
end
|
65
|
+
else
|
66
|
+
self.default_port = args.first
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def default_port=(default_port)
|
71
|
+
@default_port = default_port
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.included(cls)
|
76
|
+
cls.extend(ClassMethods)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Generates a URL
|
80
|
+
#
|
81
|
+
# @param [String, Symbol] route
|
82
|
+
# If this is a String, this will just use the same value in the path.
|
83
|
+
# If this is a Symbol, the path will be looked up in the named_routes
|
84
|
+
# @param [Hash] options
|
85
|
+
# @option options [String] :scheme
|
86
|
+
# The scheme to use for the URL, defaults to 'http'
|
87
|
+
# @option options [String] :host
|
88
|
+
# The host to use for the URL, uses the class-level attribute
|
89
|
+
# {Curtain::UrlHelpers::ClassMethods#default_host} as the default
|
90
|
+
# @option options [String] :port
|
91
|
+
# The port to use for the URL, uses the class-level attribute
|
92
|
+
# {Curtain::UrlHelpers::ClassMethods#default_host} as the default.
|
93
|
+
# The port is omitted if the scheme is 'http' and the port is 80
|
94
|
+
# or if the scheme is 'https' and the port is 443
|
95
|
+
# @option options [String] :fragment
|
96
|
+
# Sets the fragment portion of the URL, also known as the anchor
|
97
|
+
# @option options [String] :params
|
98
|
+
# A hash of parameters to include in the query string
|
99
|
+
# @return [String] The URL
|
100
|
+
# @example
|
101
|
+
# url("/posts",
|
102
|
+
# :scheme => 'http',
|
103
|
+
# :host => 'foo.com',
|
104
|
+
# :params => { :id => 30, :limit => 5 },
|
105
|
+
# :fragment => "time=1305298413")
|
106
|
+
# # => "http://foo.com/posts?id=30&limit=5#time=1305298413"
|
107
|
+
def url(route, options={})
|
108
|
+
options ||= {}
|
109
|
+
end
|
110
|
+
|
111
|
+
# Generates a relative path
|
112
|
+
#
|
113
|
+
# @param [String, Symbol] route
|
114
|
+
# If this is a String, this will just use the same value in the path.
|
115
|
+
# If this is a Symbol, the path will be looked up in the named_routes
|
116
|
+
def path(route, params={})
|
117
|
+
params ||= {}
|
118
|
+
|
119
|
+
path = case route
|
120
|
+
when String then route.dup
|
121
|
+
when Symbol
|
122
|
+
if self.class.named_routes
|
123
|
+
if p = self.class.named_routes[route]
|
124
|
+
p.dup
|
125
|
+
else
|
126
|
+
raise UnknownNamedRoute.new("Could not find named route for #{route.inspect}")
|
127
|
+
end
|
128
|
+
else
|
129
|
+
raise NamedRoutesNotSet.new("You must setup the named routes like 'Curtain::View.named_routes = {}'")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# You are going to need this, this is how you interpolate a params into a route
|
134
|
+
# and get back a subset of params that didn't match a route param
|
135
|
+
query_params = params.inject({}) do |acc,(k,v)|
|
136
|
+
unless path.sub!(/:#{k.to_param}(\/|\?|#|\Z)/,"#{v.to_param}\\1")
|
137
|
+
acc[k] = v
|
138
|
+
end
|
139
|
+
acc
|
140
|
+
end
|
141
|
+
|
142
|
+
if query_params.blank?
|
143
|
+
path
|
144
|
+
else
|
145
|
+
"#{path}?#{query_params.to_query}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/lib/curtain.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
require 'curtain/templating'
|
3
|
+
require 'curtain/rendering'
|
4
|
+
require 'curtain/block_helpers'
|
5
|
+
require 'curtain/url_helpers'
|
6
|
+
require 'curtain/caching'
|
7
|
+
require 'curtain/variable_support'
|
8
|
+
require 'curtain/html_helpers'
|
9
|
+
require 'curtain/version'
|
10
|
+
|
11
|
+
module Curtain
|
12
|
+
|
13
|
+
def self.included(cls)
|
14
|
+
cls.class_eval do
|
15
|
+
include Curtain::Templating
|
16
|
+
include Curtain::Rendering
|
17
|
+
include Curtain::BlockHelpers
|
18
|
+
include Curtain::UrlHelpers
|
19
|
+
include Curtain::Caching
|
20
|
+
include Curtain::VariableSupport
|
21
|
+
include Curtain::HTMLHelpers
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class View
|
26
|
+
include Curtain
|
27
|
+
|
28
|
+
def initialize(attrs={})
|
29
|
+
attrs.each do |k,v|
|
30
|
+
send("#{k}=", v)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'slim'
|
3
|
+
require 'curtain'
|
4
|
+
|
5
|
+
class CurtainTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
class TestCache
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
flush
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(k, opts={})
|
14
|
+
@store[k]
|
15
|
+
end
|
16
|
+
|
17
|
+
def set(k, v, ttl=nil, opts={})
|
18
|
+
@store[k] = v
|
19
|
+
end
|
20
|
+
|
21
|
+
def flush
|
22
|
+
@store = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
class CacheView < Curtain::View
|
28
|
+
cache TestCache.new
|
29
|
+
template_directories File.join(File.dirname(__FILE__), "examples")
|
30
|
+
template :cache
|
31
|
+
end
|
32
|
+
|
33
|
+
def setup
|
34
|
+
CacheView.cache.flush
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_cache_erb
|
38
|
+
view = CacheView.new
|
39
|
+
assert_equal "<h1>foo</h1>", view.render.strip
|
40
|
+
assert_equal "<h1>foo</h1>", view.class.cache.get("foo").strip
|
41
|
+
assert_equal "<h1>foo</h1>", view.render.strip
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_cache_slim
|
45
|
+
view = CacheView.new
|
46
|
+
result = view.render("cache.slim").strip
|
47
|
+
assert_equal "<h1>foo</h1>", view.class.cache.get("foo").strip
|
48
|
+
assert_equal "<h1>foo</h1>", result
|
49
|
+
assert_equal "<h1>foo</h1>", view.render("cache.slim").strip
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'slim'
|
3
|
+
require 'curtain'
|
4
|
+
|
5
|
+
class CurtainTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
# Using the top-level to test default template name behavior
|
8
|
+
class ::TestView < Curtain::View
|
9
|
+
self.template_directories = File.join(File.dirname(__FILE__), "examples")
|
10
|
+
|
11
|
+
attr_accessor :name
|
12
|
+
|
13
|
+
def shout(s)
|
14
|
+
s.upcase
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class SubdirView < Curtain::View
|
19
|
+
template :index
|
20
|
+
end
|
21
|
+
|
22
|
+
SubdirView.template_directories = [File.join(File.dirname(__FILE__), "examples", "subdir")] + Curtain::View.template_directories
|
23
|
+
|
24
|
+
def test_render_default
|
25
|
+
view = TestView.new
|
26
|
+
assert_equal "<h1>TEST</h1>\n", view.render
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_render_default_with_locals
|
30
|
+
view = TestView.new
|
31
|
+
assert_equal "<h1>TEST</h1>\n <p>Hello, World!</p>\n", view.render(:msg => "Hello, World!")
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_render_template
|
35
|
+
view = TestView.new
|
36
|
+
assert_equal "<h1>default</h1>\n", view.render(:index)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_render_template_with_locals
|
40
|
+
view = TestView.new
|
41
|
+
assert_equal "<h1>Hello, World!</h1>\n", view.render(:index, :msg => "Hello, World!")
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_render_multiple_template_directories
|
45
|
+
view = SubdirView.new
|
46
|
+
assert_equal "<h1>Subdir</h1>\n", view.render
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_variables_and_render_within_template
|
50
|
+
view = TestView.new
|
51
|
+
view[:msg] = "Hello, World!"
|
52
|
+
assert_equal "<html><body><h1>Hello, World!</h1>\n</body></html>\n", view.render("layout", :main => "body")
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1><%= defined?(msg) ? msg : 'default' %></h1>
|
@@ -0,0 +1 @@
|
|
1
|
+
<html><body><%= render main %></body></html>
|
@@ -0,0 +1 @@
|
|
1
|
+
<h1>Subdir</h1>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'slim'
|
3
|
+
require 'curtain'
|
4
|
+
|
5
|
+
class HTMLHelpersTest < Test::Unit::TestCase
|
6
|
+
include Curtain::BlockHelpers
|
7
|
+
include Curtain::HTMLHelpers
|
8
|
+
|
9
|
+
def test_content_tag_no_body
|
10
|
+
assert_equal "<p></p>", content_tag(:p)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_content_tag_body
|
14
|
+
assert_equal "<p>foo</p>", content_tag(:p, "foo")
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_content_tag_body_attributes
|
18
|
+
assert_equal %{<p bar="baz">foo</p>}, content_tag(:p, "foo", :bar => "baz")
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_content_tag_no_body_attributes
|
22
|
+
assert_equal %{<p bar="baz"></p>}, content_tag(:p, :bar => "baz")
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_content_tag_block_body_attributes
|
26
|
+
assert_equal(%{<p>foo</p>}, content_tag(:p){ "foo" })
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'slim'
|
3
|
+
require 'curtain'
|
4
|
+
|
5
|
+
class UrlHelpersTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
class TestView < Curtain::View
|
8
|
+
named_routes :home => "/home",
|
9
|
+
:profile => "/profiles/:id",
|
10
|
+
:post => "/:year/:month/:day/:slug"
|
11
|
+
end
|
12
|
+
|
13
|
+
def setup
|
14
|
+
@view = TestView.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_path_with_named_route
|
18
|
+
assert_equal "/home", @view.path(:home)
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_path_with_named_route_with_params
|
22
|
+
assert_equal "/home?q=foo", @view.path(:home, :q => "foo")
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_path_with_named_route_with_route_params
|
26
|
+
assert_equal "/profiles/42", @view.path(:profile, :id => 42)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_path_with_named_route_with_multiple_route_params
|
30
|
+
assert_equal "/2012/10/19/first-post",
|
31
|
+
@view.path(:post,
|
32
|
+
:year => 2012,
|
33
|
+
:month => 10,
|
34
|
+
:day => 19,
|
35
|
+
:slug => "first-post")
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_path_with_named_route_with_multiple_route_params_and_query_params
|
39
|
+
assert_equal "/2012/10/19/first-post?tag=foo",
|
40
|
+
@view.path(:post,
|
41
|
+
:year => 2012,
|
42
|
+
:month => 10,
|
43
|
+
:day => 19,
|
44
|
+
:slug => "first-post",
|
45
|
+
:tag => "foo")
|
46
|
+
end
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: curtain
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Paul Barry
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-03-04 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: tilt
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: erubis
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: haml
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: slim
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: A template rendering framework
|
95
|
+
email:
|
96
|
+
- mail@paulbarry.com
|
97
|
+
executables: []
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files: []
|
100
|
+
files:
|
101
|
+
- .gitignore
|
102
|
+
- .rvmrc
|
103
|
+
- Gemfile
|
104
|
+
- LICENSE
|
105
|
+
- README.md
|
106
|
+
- Rakefile
|
107
|
+
- curtain.gemspec
|
108
|
+
- lib/curtain.rb
|
109
|
+
- lib/curtain/block_helpers.rb
|
110
|
+
- lib/curtain/caching.rb
|
111
|
+
- lib/curtain/erubis.rb
|
112
|
+
- lib/curtain/erubis_template.rb
|
113
|
+
- lib/curtain/html_helpers.rb
|
114
|
+
- lib/curtain/output_buffer.rb
|
115
|
+
- lib/curtain/rendering.rb
|
116
|
+
- lib/curtain/templating.rb
|
117
|
+
- lib/curtain/url_helpers.rb
|
118
|
+
- lib/curtain/variable_support.rb
|
119
|
+
- lib/curtain/version.rb
|
120
|
+
- test/caching_test.rb
|
121
|
+
- test/curtain_test.rb
|
122
|
+
- test/examples/body.erb
|
123
|
+
- test/examples/cache.erb
|
124
|
+
- test/examples/cache.slim
|
125
|
+
- test/examples/index.erb
|
126
|
+
- test/examples/layout.erb
|
127
|
+
- test/examples/subdir/index.erb
|
128
|
+
- test/examples/test.erb
|
129
|
+
- test/html_helpers_test.rb
|
130
|
+
- test/url_helpers_test.rb
|
131
|
+
homepage: ''
|
132
|
+
licenses: []
|
133
|
+
post_install_message:
|
134
|
+
rdoc_options: []
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
|
+
none: false
|
145
|
+
requirements:
|
146
|
+
- - ! '>='
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
requirements: []
|
150
|
+
rubyforge_project:
|
151
|
+
rubygems_version: 1.8.25
|
152
|
+
signing_key:
|
153
|
+
specification_version: 3
|
154
|
+
summary: A template rendering framework
|
155
|
+
test_files:
|
156
|
+
- test/caching_test.rb
|
157
|
+
- test/curtain_test.rb
|
158
|
+
- test/examples/body.erb
|
159
|
+
- test/examples/cache.erb
|
160
|
+
- test/examples/cache.slim
|
161
|
+
- test/examples/index.erb
|
162
|
+
- test/examples/layout.erb
|
163
|
+
- test/examples/subdir/index.erb
|
164
|
+
- test/examples/test.erb
|
165
|
+
- test/html_helpers_test.rb
|
166
|
+
- test/url_helpers_test.rb
|