mirror of
https://github.com/haml/haml.git
synced 2022-11-09 12:33:31 -05:00
Refatored, and added a test to assert that the refactored engine renders the same as the original engine
git-svn-id: svn://hamptoncatlin.com/haml/trunk@27 7063305b-7217-0410-af8c-cdc13e5119b9
This commit is contained in:
parent
82ff534b59
commit
49ff7de516
10 changed files with 174 additions and 147 deletions
1
Rakefile
1
Rakefile
|
@ -3,7 +3,6 @@ require 'rake/testtask'
|
|||
require 'rake/rdoctask'
|
||||
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
||||
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => :test
|
||||
|
||||
|
|
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::Engine)
|
|
@ -1,42 +1,52 @@
|
|||
require File.dirname(__FILE__) + '/helpers'
|
||||
|
||||
module HAML
|
||||
|
||||
module Haml
|
||||
class Engine
|
||||
attr_accessor :base
|
||||
|
||||
include HAMLHelpers
|
||||
|
||||
def initialize(base)
|
||||
@base = base
|
||||
@happy_land = HappyLand.new(@base, @base.assigns)
|
||||
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. +<p>Hello world</p>+
|
||||
ONE_LINER_LENGTH = 50
|
||||
|
||||
def initialize(view)
|
||||
@view = view
|
||||
@result = String.new
|
||||
@to_close_queue = []
|
||||
end
|
||||
|
||||
def render(template = "", locals = {})
|
||||
@result, @to_close_queue = "", []
|
||||
|
||||
#this helps get the right values for helpers.
|
||||
#though, it is definitely in the "hack" category
|
||||
@base.assigns.each do |key,value|
|
||||
@base.instance_eval("@#{key} = value")
|
||||
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
|
||||
|
||||
@happy_land.set_locals(locals)
|
||||
@happy_land.update_instance_variables
|
||||
# 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
|
||||
|
||||
#main loop handling line reading
|
||||
#and interpretation
|
||||
template.each_line do |line|
|
||||
if line.strip[0, 3] == "!!!"
|
||||
@result << %|<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n|
|
||||
else
|
||||
count, line = count_soft_tabs(line)
|
||||
|
||||
if count <= @to_close_queue.size && @to_close_queue.size > 0
|
||||
(@to_close_queue.size - count).times { close_tag }
|
||||
end
|
||||
|
||||
case line.first
|
||||
# Process each line of the template returning the resuting string
|
||||
template.split(/\n/).map do |line|
|
||||
count, line = count_soft_tabs(line)
|
||||
|
||||
if count && line
|
||||
if line.strip[0, 3] == '!!!'
|
||||
@result << %|<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n|
|
||||
else
|
||||
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)
|
||||
when '%'
|
||||
|
@ -44,32 +54,33 @@ module HAML
|
|||
when '/'
|
||||
render_comment(line)
|
||||
when '='
|
||||
add template_eval(line[1, line.length])
|
||||
add template_eval(line[1, line.length]).to_s
|
||||
when '~'
|
||||
add find_and_flatten(template_eval(line[1, line.length]))
|
||||
add find_and_flatten(template_eval(line[1, line.length])).to_s
|
||||
else
|
||||
add line.strip
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Close all the open tags
|
||||
@to_close_queue.length.times { close_tag }
|
||||
|
||||
# Return the result string
|
||||
@result
|
||||
end
|
||||
|
||||
def add(line)
|
||||
return nil if line.nil?
|
||||
line.to_s.each_line { |me| add_single(me) }
|
||||
end
|
||||
|
||||
def add_single(line = "")
|
||||
@result << tabs(@to_close_queue.size)
|
||||
@result << line.chomp + "\n"
|
||||
return if line.nil?
|
||||
line.to_s.each_line do |me|
|
||||
@result << tabs(@to_close_queue.size) << me.chomp << "\n"
|
||||
end
|
||||
end
|
||||
|
||||
def open_tag(name, attributes = {})
|
||||
add "<#{name.to_s}#{build_attributes(attributes)}>"
|
||||
@to_close_queue.push(name)
|
||||
@to_close_queue.push name
|
||||
end
|
||||
|
||||
def one_line_tag(name, value, attributes = {})
|
||||
|
@ -78,35 +89,34 @@ module HAML
|
|||
|
||||
def print_tag(name, value, attributes = {})
|
||||
unless value.empty?
|
||||
if one_liner?(value)
|
||||
if one_liner? value
|
||||
one_line_tag(name, value, attributes)
|
||||
else
|
||||
open_tag(name, attributes)
|
||||
add(value)
|
||||
add value
|
||||
close_tag
|
||||
end
|
||||
else
|
||||
open_tag(name, attributes)
|
||||
add(value)
|
||||
add value
|
||||
end
|
||||
end
|
||||
|
||||
#used to create single line tags... aka <hello />
|
||||
# Creates single line tags, i.e. +<hello />+
|
||||
def atomic_tag(name, attributes = {})
|
||||
add "<#{name.to_s}#{build_attributes(attributes)} />"
|
||||
end
|
||||
|
||||
def build_attributes(attributes = {})
|
||||
return "" if attributes.empty?
|
||||
" " + (attributes.collect { |attr_name, val| attr_name.to_s + "='" + val.to_s + "'" unless val.nil? }).compact.join(" ")
|
||||
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 "</#{name = @to_close_queue.pop}>"
|
||||
add "</#{@to_close_queue.pop}>"
|
||||
end
|
||||
|
||||
def render_div(line)
|
||||
render_tag("%div" + line)
|
||||
render_tag('%div' + line)
|
||||
end
|
||||
|
||||
def render_comment(line)
|
||||
|
@ -114,85 +124,46 @@ module HAML
|
|||
end
|
||||
|
||||
def render_tag(line)
|
||||
broken_up = line.scan(/[%]([-_a-z1-9]+)([-_a-z\.\#]*)(\{.*\})?([=\/\~]?)?(.*)?/)
|
||||
broken_up.each do |tag_name, attributes, attributes_hash, action, value|
|
||||
line.scan(/[%]([-_a-z1-9]+)([-_a-z\.\#]*)(\{.*\})?([=\/\~]?)?(.*)?/).each do |tag_name, attributes, attributes_hash, action, value|
|
||||
attributes = parse_class_and_id(attributes.to_s)
|
||||
attributes.merge!(template_eval(attributes_hash)) unless (attributes_hash.nil? || attributes_hash.empty?)
|
||||
|
||||
#TODO: this is seriously dirty stuff.
|
||||
#check to see if we're a one liner
|
||||
if(action == "\/")
|
||||
if action == '\/'
|
||||
atomic_tag(tag_name, attributes)
|
||||
elsif(action == "=" || action == "~")
|
||||
elsif action == '=' || action == '~'
|
||||
value = template_eval(value)
|
||||
value = find_and_flatten(value) if action == "~"
|
||||
print_tag(tag_name, value.to_s, attributes) if value != false
|
||||
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
|
||||
|
||||
# Search for `#` and `.` characters indicating id and class attributes
|
||||
# respectively
|
||||
#
|
||||
# Returns the attributes hash
|
||||
def parse_class_and_id(list)
|
||||
attributes = {}
|
||||
list.scan(/([#.])([-a-zA-Z_()]+)/).each do |type, property|
|
||||
case type
|
||||
when "."
|
||||
attributes[:class] = property
|
||||
when "#"
|
||||
attributes[:id] = property
|
||||
end
|
||||
case type
|
||||
when '.'
|
||||
attributes[:class] = property
|
||||
when '#'
|
||||
attributes[:id] = property
|
||||
end
|
||||
end
|
||||
attributes
|
||||
end
|
||||
|
||||
def one_liner?(value)
|
||||
((value.length < 50) && value.scan(/\n/).empty?)
|
||||
value.length <= ONE_LINER_LENGTH && value.scan(/\n/).empty?
|
||||
end
|
||||
|
||||
def template_eval(code)
|
||||
@happy_land.instance_eval(code)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class HappyLand #:nodoc
|
||||
include HAMLHelpers
|
||||
|
||||
def initialize(base, hash_of_assigns, hash_of_locals = {})
|
||||
hash_of_assigns.each do |key, value|
|
||||
eval("@#{key} = value")
|
||||
end
|
||||
@__locals = hash_of_locals
|
||||
@__base = base
|
||||
end
|
||||
|
||||
def base
|
||||
@__base
|
||||
end
|
||||
|
||||
def update_instance_variables
|
||||
base.instance_variables.each do |key|
|
||||
value = base.instance_eval(key)
|
||||
eval("#{key} = value")
|
||||
end
|
||||
end
|
||||
|
||||
def set_locals(hash_of_locals)
|
||||
@__locals.merge!(hash_of_locals)
|
||||
end
|
||||
|
||||
def instance_eval(code)
|
||||
eval(code)
|
||||
end
|
||||
|
||||
def method_missing(action, *args, &block)
|
||||
if action.to_s[0] == 64
|
||||
@__base.instance_eval(action)
|
||||
else
|
||||
@__locals[action.to_s] || @__locals[action.to_sym] || @__base.send(action, *args, &block)
|
||||
end
|
||||
# Evaluate in the context of the view object we recieved in the constructor
|
||||
def template_eval(args)
|
||||
@view.instance_eval(args)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,24 +1,22 @@
|
|||
|
||||
module HAMLHelpers
|
||||
|
||||
def flatten(input)
|
||||
input.gsub(/\n/, '
').gsub(/\r/, '')
|
||||
end
|
||||
|
||||
def find_and_flatten(input)
|
||||
sets = input.scan(/<(textarea|code|pre)[^>]*>(.*?)<\/\1>/im)
|
||||
sets.each do |thing|
|
||||
input = input.gsub(thing[1], flatten(thing[1]))
|
||||
module Haml
|
||||
module Helpers
|
||||
def flatten(input)
|
||||
input.gsub(/\n/, '
').gsub(/\r/, '')
|
||||
end
|
||||
input
|
||||
end
|
||||
|
||||
def tabs(count)
|
||||
" " * count
|
||||
end
|
||||
def find_and_flatten(input)
|
||||
input.scan(/<(textarea|code|pre)[^>]*>(.*?)<\/\1>/im).each do |thing|
|
||||
input = input.gsub(thing[1], flatten(thing[1]))
|
||||
end
|
||||
input
|
||||
end
|
||||
|
||||
def count_soft_tabs(line)
|
||||
[line.index(/[^ ]/)/2, line.strip]
|
||||
end
|
||||
def tabs(count)
|
||||
' ' * count
|
||||
end
|
||||
|
||||
def count_soft_tabs(line)
|
||||
line.index(/[^ ]/) ? [line.index(/[^ ]/)/2, line.strip] : []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
require File.dirname(__FILE__) + '/../lib/haml/engine'
|
||||
|
||||
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
||||
|
||||
require 'rubygems'
|
||||
require 'action_view'
|
||||
include HAMLHelpers
|
||||
#require 'benchmark'
|
||||
|
||||
ActionView::Base.register_template_handler("haml", HAML::Engine)
|
||||
include Haml::Helper
|
||||
|
||||
ActionView::Base.register_template_handler("haml", Haml::Engine)
|
||||
@base = ActionView::Base.new(File.dirname(__FILE__))
|
||||
|
||||
|
||||
RUNS = 2
|
||||
RUNS = 2000
|
||||
Benchmark.bm do |x|
|
||||
x.report("haml: ") { RUNS.times { @base.render("templates/standard"); } }
|
||||
x.report("rhtml:") { RUNS.times { @base.render( "rhtml/standard"); } }
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
require 'test/unit'
|
||||
require File.dirname(__FILE__) + '/../lib/haml/engine'
|
||||
|
||||
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
||||
|
||||
require 'rubygems'
|
||||
require 'action_view'
|
||||
|
||||
class HamlTest < Test::Unit::TestCase
|
||||
include HAMLHelpers
|
||||
include Haml::Helpers
|
||||
|
||||
def setup
|
||||
ActionView::Base.register_template_handler("haml", HAML::Engine)
|
||||
ActionView::Base.register_template_handler("haml", Haml::Engine)
|
||||
@base = ActionView::Base.new(File.dirname(__FILE__) + "/../test/templates/")
|
||||
@base.instance_eval("@hello_world = 'Hello, World!'")
|
||||
@engine = HAML::Engine.new(@base)
|
||||
@engine = Haml::Engine.new(@base)
|
||||
end
|
||||
|
||||
def render(text)
|
||||
|
@ -19,27 +20,26 @@ class HamlTest < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def load_result(name)
|
||||
@result = ""
|
||||
File.new(File.dirname(__FILE__) + "/results/" + name + ".xhtml").each_line { |l| @result += l}
|
||||
@result = ''
|
||||
File.new(File.dirname(__FILE__) + "/results/#{name}.xhtml").each_line { |l| @result += l}
|
||||
@result
|
||||
end
|
||||
|
||||
def assert_renders_correctly(name)
|
||||
load_result(name).scan(/\n/).zip(@base.render(name).scan(/\n/)).each do |pair|
|
||||
#test each line to make sure it matches... (helps with error messages to do them seperately)
|
||||
assert_equal(pair.first, pair.last)
|
||||
end
|
||||
#assert_equal(load_result(name), @base.render(name))
|
||||
end
|
||||
|
||||
# Make sure our little environment builds
|
||||
def test_build_stub
|
||||
assert_not_nil(@engine)
|
||||
assert_equal(HAML::Engine, @engine.class)
|
||||
assert @engine.is_a?(Haml::Engine)
|
||||
assert_equal(Haml::Engine, @engine.class)
|
||||
end
|
||||
|
||||
def test_empty_render
|
||||
assert_equal("", render(""))
|
||||
assert_equal('', render(''))
|
||||
end
|
||||
|
||||
def test_renderings
|
||||
|
@ -47,13 +47,14 @@ class HamlTest < Test::Unit::TestCase
|
|||
assert_renders_correctly("standard")
|
||||
assert_renders_correctly("helpers")
|
||||
assert_renders_correctly("whitespace_handling")
|
||||
assert_renders_correctly("original_engine")
|
||||
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)
|
||||
@engine = Haml::Engine.new(@base)
|
||||
assert_equal("<div class='author'>Hampton Catlin</div>\n", render(".author= @content_for_layout + ' ' + @last_name"))
|
||||
end
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
require 'test/unit'
|
||||
require File.dirname(__FILE__) + '/../lib/haml/engine'
|
||||
|
||||
$:.unshift File.join(File.dirname(__FILE__), "..", "lib")
|
||||
|
||||
require 'rubygems'
|
||||
require 'action_view'
|
||||
|
||||
class HamlTest < Test::Unit::TestCase
|
||||
include HAMLHelpers
|
||||
include Haml::Helpers
|
||||
|
||||
def test_find_and_flatten
|
||||
assert_equal(find_and_flatten("<br/><textarea></textarea><br/>"),
|
||||
|
|
29
test/results/original_engine.xhtml
Normal file
29
test/results/original_engine.xhtml
Normal file
|
@ -0,0 +1,29 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Stop. HAML time</title>
|
||||
<div id='content'>
|
||||
<h1>This is a title!</h1>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit
|
||||
</p>
|
||||
<ul>
|
||||
<li>one</li>
|
||||
<li>two</li>
|
||||
<li>three</li>
|
||||
</ul>
|
||||
<p style='color:green' class='foo'>Cigarettes!</p>
|
||||
<h2>Man alive!</h2>
|
||||
<ul class='things'>
|
||||
<li>Slippers</li>
|
||||
<li>Shoes</li>
|
||||
<li>Bathrobe</li>
|
||||
<li>Coffee</li>
|
||||
</ul>
|
||||
<pre>
|
||||
This is some text that's in a pre block!
|
||||
Let's see what happens when it's rendered! What about now, since we're on a new line?
|
||||
</pre>
|
||||
</div>
|
||||
</head>
|
||||
</html>
|
|
@ -1,2 +1,2 @@
|
|||
= h("&&&&&&&&&&&") #this is an ActionView Helper... should load
|
||||
= flatten("Hello\nnextline") #a haml helper
|
||||
= ("Hello\nnextline") #a haml helper
|
||||
|
|
26
test/templates/original_engine.haml
Normal file
26
test/templates/original_engine.haml
Normal file
|
@ -0,0 +1,26 @@
|
|||
!!!
|
||||
%html
|
||||
%head
|
||||
%title Stop. HAML time
|
||||
#content
|
||||
%h1 This is a title!
|
||||
%p Lorem ipsum dolor sit amet, consectetur adipisicing elit
|
||||
|
||||
%ul
|
||||
%li one
|
||||
%li two
|
||||
%li three
|
||||
|
||||
%p{ :class => 'foo', :style => 'color:green' } Cigarettes!
|
||||
|
||||
%h2 Man alive
|
||||
|
||||
%ul.things
|
||||
%li Slippers
|
||||
%li Shoes
|
||||
%li Bathrobe
|
||||
%li Coffee
|
||||
|
||||
%pre
|
||||
This is some text that's in a pre block!
|
||||
Let's see what happens when it's rendered! What about now, since we're on a new line?
|
Loading…
Add table
Reference in a new issue