curtain 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.swp
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.9.3@curtain --create
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in curtain.gemspec
4
+ gemspec
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
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs = ["lib"]
7
+ t.test_files = FileList["test/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
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,11 @@
1
+ module Curtain
2
+ module BlockHelpers
3
+ def capture
4
+ original_buffer = @output_buffer
5
+ @output_buffer = Curtain::OutputBuffer.new
6
+ yield
7
+ ensure
8
+ @output_buffer = original_buffer
9
+ end
10
+ end
11
+ 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,12 @@
1
+ require 'active_support/core_ext/string/output_safety'
2
+
3
+ module Curtain
4
+ class OutputBuffer < ActiveSupport::SafeBuffer
5
+ def <<(value)
6
+ super(value.to_s)
7
+ end
8
+ alias :append= :<<
9
+ alias :safe_append= :<<
10
+ alias :safe_contact :<<
11
+ end
12
+ 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
@@ -0,0 +1,17 @@
1
+ module Curtain
2
+ module VariableSupport
3
+
4
+ def variables
5
+ @variables ||= HashWithIndifferentAccess.new
6
+ end
7
+
8
+ def [](var)
9
+ variables[var]
10
+ end
11
+
12
+ def []=(var,val)
13
+ variables[var] = val
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module Curtain
2
+ VERSION = '0.1.0'
3
+ 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,6 @@
1
+ <h1><%= msg %></h1>
2
+ <% if defined? main %>
3
+ This won't be reached,
4
+ because the local doesn't carry through to subsequent render calls,
5
+ but the variables, like methods on the view, do.
6
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <%= cache "foo" do %>
2
+ <h1>foo</h1>
3
+ <% end %>
@@ -0,0 +1,2 @@
1
+ = cache "foo" do
2
+ h1 foo
@@ -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,4 @@
1
+ <h1><%= shout "test" %></h1>
2
+ <% if defined? msg %>
3
+ <p><%= msg %></p>
4
+ <% end %>
@@ -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