curtain 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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