mirror of
https://github.com/haml/haml.git
synced 2022-11-09 12:33:31 -05:00
Enginification
git-svn-id: svn://hamptoncatlin.com/haml/trunk@48 7063305b-7217-0410-af8c-cdc13e5119b9
This commit is contained in:
parent
8e245a6de7
commit
563d5d748e
9 changed files with 238 additions and 161 deletions
2
init.rb
2
init.rb
|
@ -1,4 +1,4 @@
|
|||
require 'haml/engine'
|
||||
require 'haml/helpers'
|
||||
|
||||
ActionView::Base.register_template_handler('haml', Haml::Engine)
|
||||
ActionView::Base.register_template_handler('haml', Haml::Template)
|
|
@ -3,53 +3,34 @@ require File.dirname(__FILE__) + '/helpers'
|
|||
module Haml #:nodoc:
|
||||
class Engine
|
||||
include Haml::Helpers
|
||||
|
||||
|
||||
# Set the maximum length for a line to be considered a one-liner
|
||||
# Lines <= the maximum will be rendered on one line,
|
||||
# i.e. <tt><p>Hello world</p></tt>
|
||||
ONE_LINER_LENGTH = 50
|
||||
MULTILINE_CHAR_VALUE = '|'[0]
|
||||
|
||||
def initialize(view)
|
||||
@view = view
|
||||
|
||||
def initialize(template, action_view=nil)
|
||||
@view = action_view
|
||||
@template = template #String
|
||||
@result = String.new
|
||||
@to_close_queue = []
|
||||
end
|
||||
|
||||
def render(template, local_assigns={})
|
||||
assigns = @view.assigns.dup
|
||||
|
||||
# Do content for layout on its own to keep things working in partials
|
||||
if content_for_layout = @view.instance_variable_get("@content_for_layout")
|
||||
assigns['content_for_layout'] = content_for_layout
|
||||
end
|
||||
|
||||
# Get inside the view object's world
|
||||
@view.instance_eval do
|
||||
# Set all the instance variables
|
||||
assigns.each do |key,val|
|
||||
instance_variable_set "@#{key}", val
|
||||
end
|
||||
# Set all the local assigns
|
||||
local_assigns.each do |key,val|
|
||||
class << self; self; end.send(:define_method, key) { val }
|
||||
end
|
||||
end
|
||||
|
||||
# Process each line of the template returning the resulting string
|
||||
template.each_with_index do |line, index|
|
||||
|
||||
def to_html
|
||||
# Process each line of the template
|
||||
@template.each_with_index do |line, index|
|
||||
count, line = count_soft_tabs(line)
|
||||
|
||||
surpress_render, line, count = handle_multiline(count, line)
|
||||
|
||||
if !surpress_render && count && line
|
||||
count, line = process_line(count, line)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Close all the open tags
|
||||
@to_close_queue.length.times { close_tag }
|
||||
|
||||
|
||||
# Return the result string
|
||||
@result
|
||||
end
|
||||
|
@ -61,7 +42,7 @@ module Haml #:nodoc:
|
|||
if count <= @to_close_queue.size && @to_close_queue.size > 0
|
||||
(@to_close_queue.size - count).times { close_tag }
|
||||
end
|
||||
|
||||
|
||||
case line.first
|
||||
when '.', '#'
|
||||
render_div(line)
|
||||
|
@ -70,9 +51,9 @@ module Haml #:nodoc:
|
|||
when '/'
|
||||
render_comment(line)
|
||||
when '='
|
||||
add template_eval(line[1, line.length]).to_s
|
||||
add template_eval(line[1, line.length]).to_s if @view
|
||||
when '~'
|
||||
add find_and_flatten(template_eval(line[1, line.length])).to_s
|
||||
add find_and_flatten(template_eval(line[1, line.length])).to_s if @view
|
||||
else
|
||||
add line.strip
|
||||
end
|
||||
|
@ -81,22 +62,28 @@ module Haml #:nodoc:
|
|||
end
|
||||
|
||||
def handle_multiline(count, line)
|
||||
# The code to handle how a multi-line object should work.
|
||||
if @multiline_buffer && line[-1] == MULTILINE_CHAR_VALUE # '|' is 124
|
||||
# Multilines are denoting by ending with a `|` (124)
|
||||
if @multiline_buffer && line[-1] == MULTILINE_CHAR_VALUE
|
||||
|
||||
# A multiline string is active, and is being continued
|
||||
@multiline_buffer += line[0...-1]
|
||||
supress_render = true
|
||||
|
||||
elsif line[-1] == MULTILINE_CHAR_VALUE
|
||||
|
||||
# A multiline string has just been activated, start adding the lines
|
||||
@multiline_buffer = line[0...-1]
|
||||
@multiline_count = count
|
||||
supress_render = true
|
||||
|
||||
elsif @multiline_buffer
|
||||
|
||||
# A multiline string has just ended, make line into the result
|
||||
process_line(@multiline_count, @multiline_buffer)
|
||||
@multiline_buffer = nil
|
||||
supress_render = false
|
||||
end
|
||||
|
||||
return supress_render, line, count
|
||||
end
|
||||
|
||||
|
@ -107,15 +94,27 @@ module Haml #:nodoc:
|
|||
end
|
||||
end
|
||||
|
||||
def build_attributes(attributes = {})
|
||||
attributes.empty? ? String.new : String.new(' ') << (attributes.collect {|a,v| "#{a.to_s}='#{v.to_s}'" unless v.nil? }).compact.join(' ')
|
||||
end
|
||||
|
||||
def open_tag(name, attributes = {})
|
||||
add "<#{name.to_s}#{build_attributes(attributes)}>"
|
||||
@to_close_queue.push name
|
||||
end
|
||||
|
||||
def close_tag
|
||||
add "</#{@to_close_queue.pop}>"
|
||||
end
|
||||
|
||||
def one_line_tag(name, value, attributes = {})
|
||||
add "<#{name.to_s}#{build_attributes(attributes)}>#{value}</#{name.to_s}>"
|
||||
end
|
||||
|
||||
def one_liner?(value)
|
||||
value.length <= ONE_LINER_LENGTH && value.scan(/\n/).empty?
|
||||
end
|
||||
|
||||
def print_tag(name, value, attributes = {})
|
||||
unless value.empty?
|
||||
if one_liner? value
|
||||
|
@ -136,45 +135,6 @@ module Haml #:nodoc:
|
|||
add "<#{name.to_s}#{build_attributes(attributes)} />"
|
||||
end
|
||||
|
||||
def build_attributes(attributes = {})
|
||||
attributes.empty? ? String.new : String.new(' ') << (attributes.collect {|a,v| "#{a.to_s}='#{v.to_s}'" unless v.nil? }).compact.join(' ')
|
||||
end
|
||||
|
||||
def close_tag
|
||||
add "</#{@to_close_queue.pop}>"
|
||||
end
|
||||
|
||||
def render_div(line)
|
||||
render_tag('%div' + line)
|
||||
end
|
||||
|
||||
def render_comment(line)
|
||||
add "<!-- #{line[1..line.length].strip} -->"
|
||||
end
|
||||
|
||||
def render_tag(line)
|
||||
line.scan(/[%]([-_a-z1-9]+)([-_a-z\.\#]*)(\{.*\})?(\[.*\])?([=\/\~]?)?(.*)?/).each do |tag_name, attributes, attributes_hash, object_ref, action, value|
|
||||
attributes = parse_class_and_id(attributes.to_s)
|
||||
attributes.merge!(template_eval(attributes_hash)) unless (attributes_hash.nil? || attributes_hash.empty?)
|
||||
|
||||
if object_ref && (object_ref = template_eval(object_ref).first)
|
||||
class_name = object_ref.class.to_s.underscore
|
||||
attributes.merge!(:id => "#{class_name}_#{object_ref.id}", :class => class_name)
|
||||
end
|
||||
|
||||
if action == '/'
|
||||
atomic_tag(tag_name, attributes)
|
||||
elsif action == '=' || action == '~'
|
||||
value = template_eval(value)
|
||||
value = find_and_flatten(value) if action == '~'
|
||||
print_tag(tag_name, value.to_s, attributes) if value
|
||||
else
|
||||
print_tag(tag_name, value.to_s.strip, attributes)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Searches for `#` and `.` characters indicating id and class attributes
|
||||
def parse_class_and_id(list)
|
||||
attributes = {}
|
||||
list.scan(/([#.])([-a-zA-Z_()]+)/).each do |type, property|
|
||||
|
@ -188,11 +148,45 @@ module Haml #:nodoc:
|
|||
attributes
|
||||
end
|
||||
|
||||
def one_liner?(value)
|
||||
value.length <= ONE_LINER_LENGTH && value.scan(/\n/).empty?
|
||||
def render_tag(line)
|
||||
line.scan(/[%]([-_a-z1-9]+)([-_a-z\.\#]*)(\{.*\})?(\[.*\])?([=\/\~]?)?(.*)?/).each do |tag_name, attributes, attributes_hash, object_ref, action, value|
|
||||
attributes = parse_class_and_id(attributes.to_s)
|
||||
|
||||
unless (attributes_hash.nil? || attributes_hash.empty?)
|
||||
# Determine whether to eval the attributes hash in the context of a template
|
||||
add_attributes = @view ? template_eval(attributes_hash) : eval(attributes_hash)
|
||||
attributes.merge!(add_attributes)
|
||||
end
|
||||
|
||||
|
||||
if @view
|
||||
if object_ref && (object_ref = template_eval(object_ref).first)
|
||||
class_name = object_ref.class.to_s.underscore
|
||||
attributes.merge!(:id => "#{class_name}_#{object_ref.id}", :class => class_name)
|
||||
end
|
||||
end
|
||||
|
||||
case action
|
||||
when '/'
|
||||
atomic_tag(tag_name, attributes)
|
||||
when '=', '~'
|
||||
value = template_eval(value) if @view
|
||||
value = find_and_flatten(value) if action == '~' and @view
|
||||
print_tag(tag_name, value.to_s, attributes) if value
|
||||
else
|
||||
print_tag(tag_name, value.to_s.strip, attributes)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Evaluates input in the context of the current ActionView instance
|
||||
def render_div(line)
|
||||
render_tag('%div' + line)
|
||||
end
|
||||
|
||||
def render_comment(line)
|
||||
add "<!-- #{line[1..line.length].strip} -->"
|
||||
end
|
||||
|
||||
def template_eval(args)
|
||||
@view.instance_eval(args)
|
||||
end
|
||||
|
|
32
lib/haml/template.rb
Normal file
32
lib/haml/template.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
require File.dirname(__FILE__) + '/engine'
|
||||
|
||||
module Haml
|
||||
class Template
|
||||
def initialize(view)
|
||||
@view = view
|
||||
end
|
||||
|
||||
def render(template, local_assigns={})
|
||||
assigns = @view.assigns.dup
|
||||
|
||||
# Do content for layout on its own to keep things working in partials
|
||||
if content_for_layout = @view.instance_variable_get("@content_for_layout")
|
||||
assigns['content_for_layout'] = content_for_layout
|
||||
end
|
||||
|
||||
# Get inside the view object's world
|
||||
@view.instance_eval do
|
||||
# Set all the instance variables
|
||||
assigns.each do |key,val|
|
||||
instance_variable_set "@#{key}", val
|
||||
end
|
||||
# Set all the local assigns
|
||||
local_assigns.each do |key,val|
|
||||
class << self; self; end.send(:define_method, key) { val }
|
||||
end
|
||||
end
|
||||
|
||||
Haml::Engine.new(template, @view).to_html
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,18 +1,14 @@
|
|||
require File.dirname(__FILE__) + '/../lib/haml/engine'
|
||||
|
||||
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
||||
|
||||
require 'rubygems'
|
||||
require 'action_view'
|
||||
|
||||
include Haml::Helpers
|
||||
require File.dirname(__FILE__) + '/../lib/haml/template'
|
||||
|
||||
ActionView::Base.register_template_handler("haml", Haml::Engine)
|
||||
ActionView::Base.register_template_handler("haml", Haml::Template)
|
||||
@base = ActionView::Base.new(File.dirname(__FILE__))
|
||||
|
||||
RUNS = (ARGV[0] || 100).to_i
|
||||
|
||||
Benchmark.bm do |b|
|
||||
b.report("haml: ") { RUNS.times { @base.render "templates/standard" } }
|
||||
b.report("rhtml:") { RUNS.times { @base.render "rhtml/standard" } }
|
||||
b.report("erb: ") { RUNS.times { @base.render "rhtml/standard" } }
|
||||
end
|
||||
|
|
|
@ -1,80 +1,53 @@
|
|||
require 'test/unit'
|
||||
require File.dirname(__FILE__) + '/../lib/haml/engine'
|
||||
require File.dirname(__FILE__) + '/mocks/article'
|
||||
|
||||
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
||||
|
||||
require 'rubygems'
|
||||
require 'action_view'
|
||||
|
||||
class HamlTest < Test::Unit::TestCase
|
||||
include Haml::Helpers
|
||||
|
||||
class EngineTest < Test::Unit::TestCase
|
||||
def setup
|
||||
ActionView::Base.register_template_handler("haml", Haml::Engine)
|
||||
@base = ActionView::Base.new(File.dirname(__FILE__) + "/templates/")
|
||||
@engine = Haml::Engine.new(@base)
|
||||
@base.instance_variable_set("@article", Article.new)
|
||||
end
|
||||
|
||||
def render(text)
|
||||
@engine.render(text)
|
||||
Haml::Engine.new(text).to_html
|
||||
end
|
||||
|
||||
def load_result(name)
|
||||
@result = ''
|
||||
File.new(File.dirname(__FILE__) + "/results/#{name}.xhtml").each_line { |l| @result += l}
|
||||
@result
|
||||
end
|
||||
|
||||
def assert_renders_correctly(name)
|
||||
load_result(name).split("\n").zip(@base.render(name).split("\n")).each do |pair|
|
||||
assert_equal(pair.first, pair.last)
|
||||
#puts pair.inspect
|
||||
end
|
||||
end
|
||||
|
||||
# Make sure our little environment builds
|
||||
def test_build_stub
|
||||
assert_not_nil(@engine)
|
||||
assert @engine.is_a?(Haml::Engine)
|
||||
assert_equal(Haml::Engine, @engine.class)
|
||||
end
|
||||
|
||||
def test_empty_render
|
||||
|
||||
def test_empty_render_should_remain_empty
|
||||
assert_equal('', render(''))
|
||||
end
|
||||
|
||||
def test_renderings
|
||||
assert_renders_correctly("very_basic")
|
||||
assert_renders_correctly("standard")
|
||||
assert_renders_correctly("helpers")
|
||||
assert_renders_correctly("whitespace_handling")
|
||||
assert_renders_correctly("original_engine")
|
||||
assert_renders_correctly("list")
|
||||
assert_renders_correctly("helpful")
|
||||
def test_normal_renders_should_not_eval
|
||||
assert_equal("", render("= 1+1"))
|
||||
assert_equal("", render("= @content_for_layout"))
|
||||
assert_equal("", render("~ @foobar"))
|
||||
end
|
||||
|
||||
def test_instance_variables
|
||||
@base.instance_eval("@content_for_layout = 'Hampton'")
|
||||
@base.instance_eval("@assigns['last_name'] = 'Catlin'")
|
||||
#make sure to reload!
|
||||
@engine = Haml::Engine.new(@base)
|
||||
assert_equal("<div class='author'>Hampton Catlin</div>\n", render(".author= @content_for_layout + ' ' + @last_name"))
|
||||
# This is ugly because Hashes are unordered; we don't always know the order
|
||||
# in which attributes will be returned.
|
||||
# There is probably a better way to do this.
|
||||
def test_attributes_should_render_correctly
|
||||
assert_equal("<div class='atlantis' style='ugly'>\n</div>", render(".atlantis{:style => 'ugly'}").chomp)
|
||||
rescue
|
||||
assert_equal("<div style='ugly' class='atlantis'>\n</div>", render(".atlantis{:style => 'ugly'}").chomp)
|
||||
end
|
||||
|
||||
def test_instance_variables_changing
|
||||
@base.instance_eval("@author = 'Hampton'")
|
||||
assert_equal("Hampton\n", render("= @author"))
|
||||
def test_ruby_code_should_work_inside_attributes
|
||||
author = 'hcatlin'
|
||||
assert_equal("<p class='3'>foo</p>", render("%p{:class => 1+2} foo").chomp)
|
||||
end
|
||||
|
||||
def test_nil_attribute
|
||||
assert_equal("<div class='no_attributes'>\n</div>\n",
|
||||
render(".no_attributes{:nil => nil}"))
|
||||
def test_nil_should_render_empty_tag
|
||||
assert_equal("<div class='no_attributes'>\n</div>",
|
||||
render(".no_attributes{:nil => nil}").chomp)
|
||||
end
|
||||
|
||||
def test_stripped_strings
|
||||
assert_equal("<div class='stripped'>This should have no spaces in front of it</div>\n",
|
||||
render(".stripped This should have no spaces in front of it"))
|
||||
def test_strings_should_get_stripped_inside_tags
|
||||
assert_equal("<div class='stripped'>This should have no spaces in front of it</div>",
|
||||
render(".stripped This should have no spaces in front of it").chomp)
|
||||
end
|
||||
|
||||
def test_one_liner_should_be_one_line
|
||||
assert_equal("<p>Hello</p>", render('%p Hello').chomp)
|
||||
end
|
||||
|
||||
def test_long_liner_should_not_print_on_one_line
|
||||
assert_equal("<div>\n #{'x' * 51}\n</div>", render("%div #{'x' * 51}").chomp)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,36 +1,34 @@
|
|||
require 'test/unit'
|
||||
require File.dirname(__FILE__) + '/../lib/haml/engine'
|
||||
require File.dirname(__FILE__) + '/../lib/haml/helpers'
|
||||
|
||||
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
||||
|
||||
require 'rubygems'
|
||||
require 'action_view'
|
||||
require 'active_support'
|
||||
|
||||
class HamlTest < Test::Unit::TestCase
|
||||
class HelperTest < Test::Unit::TestCase
|
||||
include Haml::Helpers
|
||||
|
||||
def test_find_and_flatten
|
||||
assert_equal(find_and_flatten("<br/><textarea></textarea><br/>"),
|
||||
"<br/><textarea></textarea><br/>")
|
||||
|
||||
assert_equal(find_and_flatten("<code lang='ruby'>TEST!</code>\t\t<p></p>"),
|
||||
"<code lang='ruby'>TEST!</code>\t\t<p></p>")
|
||||
|
||||
assert_equal(find_and_flatten("<pre>Hello\nWorld!\nYOU ARE \rFLAT?\n\rOMGZ!</pre></br>"),
|
||||
"<pre>Hello
World!
YOU ARE FLAT?
OMGZ!</pre></br>")
|
||||
assert_equal( "<div class='text_area_test_area'>\n <textarea>Two
 lines</textarea>\n</div>\n",
|
||||
find_and_flatten("<div class='text_area_test_area'>\n <textarea>Two\n lines</textarea>\n</div>\n"))
|
||||
assert_equal( "<code>Two
lines</code><pre>a
b
c</pre>",
|
||||
find_and_flatten("<code>Two\nlines</code><pre>a\nb\nc</pre>"))
|
||||
assert_equal( "<pre>Two
lines</pre>\n<pre>a
b
c</pre>",
|
||||
find_and_flatten("<pre>Two\nlines</pre>\n<pre>a\nb\nc</pre>"))
|
||||
|
||||
assert_equal(find_and_flatten("<div class='text_area_test_area'>\n <textarea>Two\n lines</textarea>\n</div>\n"),
|
||||
"<div class='text_area_test_area'>\n <textarea>Two
 lines</textarea>\n</div>\n")
|
||||
|
||||
assert_equal(find_and_flatten("<code>Two\nlines</code><pre>a\nb\nc</pre>"),
|
||||
"<code>Two
lines</code><pre>a
b
c</pre>")
|
||||
|
||||
assert_equal(find_and_flatten("<pre>Two\nlines</pre>\n<pre>a\nb\nc</pre>"),
|
||||
"<pre>Two
lines</pre>\n<pre>a
b
c</pre>")
|
||||
end
|
||||
|
||||
def test_tabs
|
||||
def test_tabs_should_render_correctly
|
||||
assert_equal(" ", tabs(1))
|
||||
assert_equal(" ", tabs(5))
|
||||
end
|
||||
|
||||
def test_list_of
|
||||
def test_list_of_should_render_correctly
|
||||
assert_equal("<li>1</li>\n<li>2</li>", (list_of([1, 2]) { |i| i.to_s}))
|
||||
assert_equal("<li>1</li>", (list_of([[1]]) { |i| i.first}))
|
||||
end
|
||||
|
|
10
test/results/content_for_layout.xhtml
Normal file
10
test/results/content_for_layout.xhtml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div id='content'>
|
||||
Lorem ipsum dolor sit amet
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
68
test/template_test.rb
Normal file
68
test/template_test.rb
Normal file
|
@ -0,0 +1,68 @@
|
|||
require 'test/unit'
|
||||
require 'rubygems'
|
||||
require 'action_view'
|
||||
|
||||
require File.dirname(__FILE__) + '/../lib/haml/template'
|
||||
require File.dirname(__FILE__) + '/mocks/article'
|
||||
|
||||
class TemplateTest < Test::Unit::TestCase
|
||||
def setup
|
||||
ActionView::Base.register_template_handler("haml", Haml::Template)
|
||||
@base = ActionView::Base.new(File.dirname(__FILE__) + "/../test/templates/")
|
||||
@base.instance_variable_set("@article", Article.new)
|
||||
end
|
||||
|
||||
def render(text)
|
||||
Haml::Engine.new(text, @base).to_html
|
||||
end
|
||||
|
||||
def load_result(name)
|
||||
@result = ''
|
||||
File.new(File.dirname(__FILE__) + "/results/#{name}.xhtml").each_line { |l| @result += l }
|
||||
@result
|
||||
end
|
||||
|
||||
def assert_renders_correctly(name)
|
||||
load_result(name).split("\n").zip(@base.render(name).split("\n")).each do |pair|
|
||||
assert_equal(pair.first, pair.last)
|
||||
end
|
||||
end
|
||||
|
||||
def test_empty_render_should_remain_empty
|
||||
assert_equal('', render(''))
|
||||
end
|
||||
|
||||
def test_templates_should_render_correctly
|
||||
%w{very_basic standard helpers whitespace_handling original_engine list helpful}.each do |template|
|
||||
assert_renders_correctly template
|
||||
end
|
||||
end
|
||||
|
||||
def test_action_view_templates_render_correctly
|
||||
@base.instance_variable_set("@content_for_layout", 'Lorem ipsum dolor sit amet')
|
||||
assert_renders_correctly 'content_for_layout'
|
||||
end
|
||||
|
||||
def test_instance_variables_should_work_inside_templates
|
||||
@base.instance_variable_set("@content_for_layout", 'something')
|
||||
assert_equal("<p>something</p>", render("%p= @content_for_layout").chomp)
|
||||
|
||||
@base.instance_eval("@author = 'Hampton Catlin'")
|
||||
assert_equal("<div class='author'>Hampton Catlin</div>", render(".author= @author").chomp)
|
||||
|
||||
@base.instance_eval("@author = 'Hampton'")
|
||||
assert_equal("Hampton", render("= @author").chomp)
|
||||
|
||||
@base.instance_eval("@author = 'Catlin'")
|
||||
assert_equal("Catlin", render("= @author").chomp)
|
||||
end
|
||||
|
||||
def test_instance_variables_should_work_inside_attributes
|
||||
@base.instance_eval("@author = 'hcatlin'")
|
||||
assert_equal("<p class='hcatlin'>foo</p>", render("%p{:class => @author} foo").chomp)
|
||||
end
|
||||
|
||||
def test_template_renders_should_eval
|
||||
assert_equal("2\n", render("= 1+1"))
|
||||
end
|
||||
end
|
6
test/templates/content_for_layout.haml
Normal file
6
test/templates/content_for_layout.haml
Normal file
|
@ -0,0 +1,6 @@
|
|||
!!!
|
||||
%html
|
||||
%head
|
||||
%body
|
||||
#content
|
||||
= @content_for_layout
|
Loading…
Add table
Reference in a new issue