diff --git a/Rakefile b/Rakefile index 023dabe8..a90c47aa 100644 --- a/Rakefile +++ b/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 diff --git a/init.rb b/init.rb index 9e43d00e..f7119e55 100644 --- a/init.rb +++ b/init.rb @@ -1,4 +1,4 @@ require 'haml/engine' require 'haml/helpers' -ActionView::Base.register_template_handler("haml", HAML::Engine) \ No newline at end of file +ActionView::Base.register_template_handler('haml', Haml::Engine) \ No newline at end of file diff --git a/lib/haml/engine.rb b/lib/haml/engine.rb index d689a644..4da2bf4d 100644 --- a/lib/haml/engine.rb +++ b/lib/haml/engine.rb @@ -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. +

Hello world

+ + 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 << %|\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 << %|\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 + # Creates single line tags, i.e. ++ 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 "" + add "" 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 \ No newline at end of file diff --git a/lib/haml/helpers.rb b/lib/haml/helpers.rb index 2d7508eb..90d88fce 100644 --- a/lib/haml/helpers.rb +++ b/lib/haml/helpers.rb @@ -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 diff --git a/test/benchmark.rb b/test/benchmark.rb index 0b071481..1d3fad09 100644 --- a/test/benchmark.rb +++ b/test/benchmark.rb @@ -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"); } } diff --git a/test/engine_test.rb b/test/engine_test.rb index 1c9a93b7..566f4a2c 100644 --- a/test/engine_test.rb +++ b/test/engine_test.rb @@ -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("
Hampton Catlin
\n", render(".author= @content_for_layout + ' ' + @last_name")) end diff --git a/test/helper_test.rb b/test/helper_test.rb index 453c8f26..2d722fe8 100644 --- a/test/helper_test.rb +++ b/test/helper_test.rb @@ -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("

"), diff --git a/test/results/original_engine.xhtml b/test/results/original_engine.xhtml new file mode 100644 index 00000000..0ffbc725 --- /dev/null +++ b/test/results/original_engine.xhtml @@ -0,0 +1,29 @@ + + + + Stop. HAML time +
+

This is a title!

+

+ Lorem ipsum dolor sit amet, consectetur adipisicing elit +

+
    +
  • one
  • +
  • two
  • +
  • three
  • +
+

Cigarettes!

+

Man alive!

+
    +
  • Slippers
  • +
  • Shoes
  • +
  • Bathrobe
  • +
  • Coffee
  • +
+
+        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?
+      
+
+ + diff --git a/test/templates/helpers.haml b/test/templates/helpers.haml index 55009fd2..6fe0f15e 100644 --- a/test/templates/helpers.haml +++ b/test/templates/helpers.haml @@ -1,2 +1,2 @@ = h("&&&&&&&&&&&") #this is an ActionView Helper... should load -= flatten("Hello\nnextline") #a haml helper += ("Hello\nnextline") #a haml helper diff --git a/test/templates/original_engine.haml b/test/templates/original_engine.haml new file mode 100644 index 00000000..17b2e4cd --- /dev/null +++ b/test/templates/original_engine.haml @@ -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?